PMFlow/backend/app/api/ai_steward_api/routes_capture.py

235 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)}"
)