366 lines
11 KiB
Python
366 lines
11 KiB
Python
"""
|
||
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",
|
||
},
|
||
)
|