235 lines
7.5 KiB
Python
235 lines
7.5 KiB
Python
from __future__ import annotations
|
||
|
||
from .shared import *
|
||
from .schemas import BrowserCaptureRequest, FeishuCaptureRequest, GitHubCaptureRequest, ProjectFromTextRequest, ProjectFromTextResponse
|
||
|
||
@router.post("/project-from-text", response_model=ProjectFromTextResponse)
|
||
async def create_project_from_text(
|
||
request: ProjectFromTextRequest,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
tenant_id: int = Depends(get_tenant_id)
|
||
):
|
||
"""
|
||
零配置项目创建 - 从一句话生成完整项目
|
||
|
||
示例输入:
|
||
```
|
||
我们要做一个 AI 客服工具,基于 GPT-4o mini,对标 XX 竞品,3 周上线
|
||
```
|
||
|
||
AI 将自动生成:
|
||
- 完整的项目配置
|
||
- 竞品分析信号
|
||
- 项目宪章
|
||
- 任务链(含依赖关系)
|
||
- 风险预警规则
|
||
"""
|
||
try:
|
||
service = get_ai_steward_service(db)
|
||
|
||
result = await service.create_project_from_natural_language(
|
||
input_text=request.input_text,
|
||
creator_id=int(current_user.id),
|
||
tenant_id=tenant_id
|
||
)
|
||
|
||
return ProjectFromTextResponse(**result)
|
||
|
||
except Exception as e:
|
||
raise HTTPException(
|
||
status_code=500,
|
||
detail=f"项目创建失败: {str(e)}"
|
||
)
|
||
|
||
@router.post("/signal/capture-feishu")
|
||
@audited_write(caller="ai")
|
||
async def capture_feishu_signal(
|
||
request: FeishuCaptureRequest,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
tenant_id: int = Depends(get_tenant_id)
|
||
):
|
||
"""
|
||
从飞书群聊捕获信号
|
||
|
||
AI 会自动判断消息是否包含有价值的项目或技术信息,
|
||
如果是,则创建信号并自动关联到相关项目
|
||
"""
|
||
try:
|
||
service = ProactiveSignalCaptureService(db)
|
||
result = await service.capture_from_feishu(
|
||
group_chat_id=request.group_chat_id,
|
||
message_content=request.message_content,
|
||
sender=request.sender,
|
||
tenant_id=tenant_id,
|
||
feishu_message_id=request.feishu_message_id,
|
||
)
|
||
response_payload = dict(result) if isinstance(result, dict) else {"result": result}
|
||
|
||
should_enqueue_reply = bool(response_payload.get("direct_reply_required"))
|
||
reply_text = str(response_payload.get("direct_reply_text") or "").strip() or "已收到,正在处理。"
|
||
source_message_id = (str(request.feishu_message_id or "").strip()[:128] or None)
|
||
normalized_chat_id = str(request.group_chat_id or "").strip()
|
||
if should_enqueue_reply and normalized_chat_id:
|
||
try:
|
||
outbox_id = await FeishuOutboxService.enqueue(
|
||
db,
|
||
tenant_id=int(tenant_id),
|
||
chat_id=normalized_chat_id,
|
||
text=reply_text,
|
||
source_message_id=source_message_id,
|
||
dedupe_seed=f"capture-feishu:{normalized_chat_id}:{str(request.sender or '').strip()[:64]}",
|
||
trace_id=str(response_payload.get("ai_trace_id") or "").strip()[:64] or None,
|
||
)
|
||
await db.commit()
|
||
response_payload["reply_outbox_enqueued"] = bool(outbox_id)
|
||
response_payload["reply_outbox_id"] = int(outbox_id) if outbox_id else None
|
||
except Exception as outbox_exc:
|
||
await db.rollback()
|
||
response_payload["reply_outbox_enqueued"] = False
|
||
response_payload["reply_outbox_error"] = str(outbox_exc)[:200]
|
||
return response_payload
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"飞书信号捕获失败: {str(e)}")
|
||
|
||
@router.post("/signal/capture-browser")
|
||
async def capture_browser_signal(
|
||
request: BrowserCaptureRequest,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
tenant_id: int = Depends(get_tenant_id)
|
||
):
|
||
"""
|
||
从浏览器标签页捕获信号
|
||
|
||
监控用户访问的技术文档、竞品页面、GitHub issues等
|
||
"""
|
||
try:
|
||
service = ProactiveSignalCaptureService(db)
|
||
result = await service.capture_from_browser(
|
||
user_id=request.user_id,
|
||
url=request.url,
|
||
title=request.title,
|
||
content_preview=request.content_preview,
|
||
tenant_id=tenant_id
|
||
)
|
||
return result
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"浏览器信号捕获失败: {str(e)}")
|
||
|
||
@router.post("/signal/capture-github")
|
||
async def capture_github_signal(
|
||
request: GitHubCaptureRequest,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
tenant_id: int = Depends(get_tenant_id)
|
||
):
|
||
"""
|
||
从GitHub事件捕获信号
|
||
|
||
监控竞品仓库更新、上游依赖更新等
|
||
"""
|
||
try:
|
||
service = ProactiveSignalCaptureService(db)
|
||
result = await service.capture_from_github(
|
||
repo_url=request.repo_url,
|
||
event_type=request.event_type,
|
||
event_data=request.event_data,
|
||
tenant_id=tenant_id
|
||
)
|
||
return result
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"GitHub信号捕获失败: {str(e)}")
|
||
|
||
@router.post("/signal/start-hn-monitor")
|
||
async def start_hn_monitor(
|
||
background_tasks: BackgroundTasks,
|
||
interval_minutes: int = 30,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
tenant_id: int = Depends(get_tenant_id)
|
||
):
|
||
"""
|
||
启动 HackerNews 定时监控(后台任务)
|
||
|
||
定期抓取 HackerNews 首页,分析技术趋势
|
||
"""
|
||
async def run_monitor():
|
||
service = ProactiveSignalCaptureService(db)
|
||
await service.start_hackernews_monitor(interval_minutes=interval_minutes, tenant_id=tenant_id)
|
||
|
||
background_tasks.add_task(run_monitor)
|
||
return {
|
||
"status": "started",
|
||
"message": f"HackerNews 监控已启动,间隔: {interval_minutes} 分钟"
|
||
}
|
||
|
||
@router.post("/sync-signal")
|
||
async def sync_external_signal(
|
||
source: str,
|
||
content: str,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
tenant_id: int = Depends(get_tenant_id)
|
||
):
|
||
"""
|
||
手动同步外部信号
|
||
|
||
支持从以下来源同步:
|
||
- 飞书群聊
|
||
- GitHub commits/PRs
|
||
- 浏览器标签页
|
||
"""
|
||
from ...models import Signal
|
||
from ...services.ai_steward_service import get_ai_steward_service
|
||
|
||
service = get_ai_steward_service(db)
|
||
|
||
# AI 分析信号重要性
|
||
prompt = f"""分析以下信号的重要性:
|
||
|
||
来源:{source}
|
||
内容:{content}
|
||
|
||
请评估:
|
||
1. impact_score (0-1)
|
||
2. priority (low/medium/high/critical)
|
||
3. 是否需要立即行动
|
||
|
||
以 JSON 格式返回。"""
|
||
|
||
try:
|
||
from ...services.llm_service import LLMService
|
||
llm = LLMService()
|
||
response = await llm.generate(
|
||
prompt,
|
||
db=db,
|
||
tenant_id=tenant_id,
|
||
)
|
||
|
||
# 创建信号
|
||
signal = Signal(
|
||
source=source,
|
||
content=content,
|
||
impact_score=0.7,
|
||
priority='medium',
|
||
processed=False,
|
||
tenant_id=tenant_id
|
||
)
|
||
db.add(signal)
|
||
await db.commit()
|
||
|
||
return {
|
||
'signal_id': str(signal.id),
|
||
'analysis': response,
|
||
'status': 'captured'
|
||
}
|
||
|
||
except Exception as e:
|
||
raise HTTPException(
|
||
status_code=500,
|
||
detail=f"信号同步失败: {str(e)}"
|
||
)
|
||
|