2026-06-17 00:09:54 +08:00
|
|
|
|
"""
|
|
|
|
|
|
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}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-17 23:35:42 +08:00
|
|
|
|
@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}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-18 00:01:15 +08:00
|
|
|
|
@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}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-18 07:47:03 +08:00
|
|
|
|
@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}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-17 00:09:54 +08:00
|
|
|
|
@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",
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|