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

202 lines
6.6 KiB
Python

from __future__ import annotations
from .shared import *
from .schemas import BrowserCaptureRequest, FeishuCaptureRequest, GitHubCaptureRequest
@router.post("/project-from-text")
async def create_project_from_text():
return JSONResponse(
status_code=410,
content=error_response(410, "旧项目创建入口已下线,请使用新项目启动台生成草案并确认创建。"),
)
@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)}"
)