from __future__ import annotations from .shared import * from .schemas import BrowserCaptureRequest, FeishuCaptureRequest, GitHubCaptureRequest, ProjectFromTextRequest, ProjectFromTextResponse @router.post("/project-from-text", response_model=ApiResponse[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 success_response(ProjectFromTextResponse(**result).model_dump()) except Exception as e: return JSONResponse( status_code=500, content=error_response(ErrorCode.INTERNAL_ERROR, 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)}" )