""" Team API — 虚拟团队管理 + 项目执行 """ from fastapi import APIRouter, Depends, Query, HTTPException from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from pydantic import BaseModel, Field from typing import List, Optional, Dict, Any import json import logging from app.core.database import get_db from app.api.auth import get_current_user from app.models.user import User from app.services.team_service import TeamService, get_preset_roles from app.services.team_orchestrator import TeamOrchestrator logger = logging.getLogger(__name__) router = APIRouter( prefix="/api/v1/teams", tags=["teams"], responses={ 401: {"description": "未授权"}, 404: {"description": "资源不存在"}, 400: {"description": "请求参数错误"}, }, ) # ──────────────────────────── Schemas ──────────────────────────── class TeamCreate(BaseModel): name: str description: str = "" workspace_id: Optional[str] = None config: Optional[Dict[str, Any]] = None class TeamUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None config: Optional[Dict[str, Any]] = None status: Optional[str] = None class MemberAdd(BaseModel): agent_id: str role: str position: int = 0 is_lead: bool = False class ProjectExecute(BaseModel): project_description: str = Field(..., min_length=1, description="项目描述") auto_approve_files: bool = Field(default=True, description="是否自动批准文件写入(跳过确认弹窗)") # ──────────────────────────── Endpoints ──────────────────────────── @router.get("/preset-roles") def preset_roles(): """获取预置角色定义(PM/设计师/开发/测试/DevOps)。""" return {"roles": get_preset_roles()} @router.post("") def create_team( body: TeamCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """创建团队。""" svc = TeamService(db) team = svc.create_team( name=body.name, user_id=current_user.id, description=body.description, workspace_id=body.workspace_id, config=body.config, ) return {"data": team.to_dict()} @router.post("/template/software-company") def create_software_company_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「软件公司虚拟团队」模板。 自动创建 5 个角色 Agent(PM/设计师/开发/测试/DevOps)并组建团队。 若用户已有同名 Agent 则复用。 """ svc = TeamService(db) result = svc.create_software_company_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.post("/template/education-training") def create_education_training_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「教育培训团队」模板。 自动创建 4 个角色 Agent(课程设计/主讲讲师/作业助教/教务管理)并组建团队。 若用户已有同名 Agent 则复用。 """ svc = TeamService(db) result = svc.create_education_training_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.post("/template/platform-engineering") def create_platform_engineering_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「天工平台工程团队」模板。 自动创建 5 个角色 Agent(全栈开发/前端体验/平台运维/质量保障/产品负责人)并组建团队。 专用于天工智能体平台的自我维护与迭代升级。 若用户已有同名 Agent 则复用。 """ svc = TeamService(db) result = svc.create_platform_engineering_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.post("/template/tech-documentation") def create_tech_doc_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「技术文档团队」模板。 自动创建 5 个角色 Agent(文档架构师/技术写手/API文档专员/翻译审校/发布管理)并组建团队。 专用于软件项目的技术文档体系建设。 若用户已有同名 Agent 则复用。 """ svc = TeamService(db) result = svc.create_tech_doc_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.post("/template/health-management") def create_health_management_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「健康管理团队」模板。""" svc = TeamService(db) result = svc.create_health_management_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.post("/template/medical-consultation") def create_medical_consultation_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「医疗咨询团队」模板。""" svc = TeamService(db) result = svc.create_medical_consultation_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.post("/template/user-simulation-test") def create_user_simulation_test_template( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """一键创建「系统应用测试团队」模板。 自动创建 5 个角色 Agent(测试规划师/功能测试员/体验审核员/边界探索员/性能评估员)并组建团队。 若用户已有同名 Agent 则复用。 """ svc = TeamService(db) result = svc.create_user_simulation_test_template( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": result} @router.get("") def list_teams( workspace_id: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """列出当前用户/工作区的所有团队。""" svc = TeamService(db) teams = svc.list_teams( user_id=current_user.id, workspace_id=workspace_id, ) return {"data": [t.to_dict() for t in teams]} @router.get("/{team_id}") def get_team( team_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """获取团队详情(含成员列表)。""" svc = TeamService(db) result = svc.get_team_with_members(team_id) if not result: raise HTTPException(status_code=404, detail="团队不存在") return {"data": result} @router.put("/{team_id}") def update_team( team_id: str, body: TeamUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """更新团队信息。""" svc = TeamService(db) team = svc.update_team(team_id, **body.model_dump(exclude_none=True)) if not team: raise HTTPException(status_code=404, detail="团队不存在") return {"data": team.to_dict()} @router.delete("/{team_id}") def delete_team( team_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """删除团队及其所有成员。""" svc = TeamService(db) ok = svc.delete_team(team_id) if not ok: raise HTTPException(status_code=404, detail="团队不存在") return {"message": "团队已删除"} @router.post("/{team_id}/members") def add_member( team_id: str, body: MemberAdd, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """为团队添加/替换角色成员。""" svc = TeamService(db) member = svc.add_member( team_id=team_id, agent_id=body.agent_id, role=body.role, position=body.position, is_lead=body.is_lead, ) if not member: raise HTTPException(status_code=404, detail="团队不存在") return {"data": member.to_dict()} @router.delete("/{team_id}/members/{member_id}") def remove_member( team_id: str, member_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """从团队移除成员。""" svc = TeamService(db) ok = svc.remove_member(team_id, member_id) if not ok: raise HTTPException(status_code=404, detail="成员不存在") return {"message": "成员已移除"} @router.get("/{team_id}/members") def list_members( team_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """列出团队成员。""" svc = TeamService(db) members = svc.get_members(team_id) return {"data": [m.to_dict() for m in members]} @router.post("/{team_id}/execute") async def execute_project( team_id: str, body: ProjectExecute, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """执行团队项目(非流式)。 PM 先分解计划,各角色按阶段顺序执行,QA 最终审查。 """ orch = TeamOrchestrator(db, team_id, current_user.id) try: result = await orch.execute(body.project_description, auto_approve_files=body.auto_approve_files) return {"data": result} except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error("团队执行失败: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"团队执行失败: {e}") @router.post("/{team_id}/execute/stream") async def execute_project_stream( team_id: str, body: ProjectExecute, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """执行团队项目(SSE 流式)。""" async def event_generator(): orch = TeamOrchestrator(db, team_id, current_user.id) try: async for event in orch.execute_stream(body.project_description, auto_approve_files=body.auto_approve_files): yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n" except ValueError as e: yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n" except Exception as e: logger.error("团队流式执行失败: %s", e, exc_info=True) yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n" return StreamingResponse( event_generator(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no", }, )