后端: - 重构 agents, heartbeats, locks, meetings, resources, roles, workflows 路由 - 新增 orchestrator 和 providers 路由 - 新增 CLI 调用器和流程编排服务 - 添加日志配置和依赖项 前端: - 更新 AgentsPage、SettingsPage、WorkflowPage 页面 - 扩展 api.ts 新增 API 接口 其他: - 清理测试 agent 数据文件 - 新增示例工作流和项目审计报告 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
261 lines
8.0 KiB
Python
261 lines
8.0 KiB
Python
"""
|
||
会议管理 API 路由
|
||
接入 MeetingScheduler(栅栏同步)+ MeetingRecorder(会议记录)
|
||
"""
|
||
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel
|
||
from typing import List, Optional
|
||
from dataclasses import asdict
|
||
from datetime import datetime
|
||
|
||
from ..services.meeting_scheduler import get_meeting_scheduler
|
||
from ..services.meeting_recorder import get_meeting_recorder
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
class MeetingCreate(BaseModel):
|
||
title: str
|
||
agenda: Optional[str] = ""
|
||
meeting_type: Optional[str] = "design_review"
|
||
attendees: List[str] = []
|
||
steps: Optional[List[str]] = None
|
||
|
||
|
||
class MeetingWaitRequest(BaseModel):
|
||
agent_id: str
|
||
timeout: Optional[int] = 300
|
||
|
||
|
||
class DiscussionRequest(BaseModel):
|
||
agent_id: str
|
||
agent_name: Optional[str] = ""
|
||
content: str
|
||
step: Optional[str] = ""
|
||
|
||
|
||
class ProgressRequest(BaseModel):
|
||
step: str
|
||
|
||
|
||
class FinishRequest(BaseModel):
|
||
consensus: Optional[str] = ""
|
||
|
||
|
||
def _meeting_to_dict(meeting) -> dict:
|
||
"""将 MeetingInfo 转为前端友好的 dict"""
|
||
return {
|
||
"meeting_id": meeting.meeting_id,
|
||
"title": meeting.title,
|
||
"date": meeting.date,
|
||
"status": meeting.status,
|
||
"attendees": meeting.attendees,
|
||
"steps": [
|
||
{"step_id": s.step_id, "label": s.label, "status": s.status}
|
||
for s in meeting.steps
|
||
],
|
||
"discussions": [
|
||
{
|
||
"agent_id": d.agent_id,
|
||
"agent_name": d.agent_name,
|
||
"content": d.content,
|
||
"timestamp": d.timestamp,
|
||
"step": d.step
|
||
}
|
||
for d in meeting.discussions
|
||
],
|
||
"progress_summary": meeting.progress_summary,
|
||
"consensus": meeting.consensus,
|
||
"created_at": meeting.created_at,
|
||
"ended_at": meeting.ended_at
|
||
}
|
||
|
||
|
||
@router.get("")
|
||
@router.get("/")
|
||
async def list_meetings(date: Optional[str] = None):
|
||
"""获取会议列表(默认今天)"""
|
||
recorder = get_meeting_recorder()
|
||
meetings = await recorder.list_meetings(date)
|
||
return {"meetings": [_meeting_to_dict(m) for m in meetings]}
|
||
|
||
|
||
@router.get("/today")
|
||
async def list_today_meetings():
|
||
"""获取今日会议"""
|
||
recorder = get_meeting_recorder()
|
||
meetings = await recorder.list_meetings()
|
||
return {"meetings": [_meeting_to_dict(m) for m in meetings]}
|
||
|
||
|
||
@router.post("/")
|
||
async def create_meeting(meeting: MeetingCreate):
|
||
"""创建新会议(同时创建调度记录和会议记录)"""
|
||
recorder = get_meeting_recorder()
|
||
scheduler = get_meeting_scheduler()
|
||
|
||
meeting_id = f"meeting-{int(datetime.now().timestamp())}"
|
||
|
||
# 在调度器中创建(用于栅栏同步)
|
||
await scheduler.create_meeting(
|
||
meeting_id=meeting_id,
|
||
title=meeting.title,
|
||
expected_attendees=meeting.attendees
|
||
)
|
||
|
||
# 在记录器中创建(用于记录内容)
|
||
meeting_info = await recorder.create_meeting(
|
||
meeting_id=meeting_id,
|
||
title=meeting.title,
|
||
attendees=meeting.attendees,
|
||
steps=meeting.steps
|
||
)
|
||
|
||
return _meeting_to_dict(meeting_info)
|
||
|
||
|
||
@router.post("/create")
|
||
async def create_meeting_alt(meeting: MeetingCreate):
|
||
"""创建会议 API(前端使用的端点,与 POST / 相同)"""
|
||
return await create_meeting(meeting)
|
||
|
||
|
||
@router.get("/{meeting_id}")
|
||
async def get_meeting(meeting_id: str, date: Optional[str] = None):
|
||
"""获取会议详情"""
|
||
recorder = get_meeting_recorder()
|
||
meeting_info = await recorder.get_meeting(meeting_id, date)
|
||
if not meeting_info:
|
||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||
return _meeting_to_dict(meeting_info)
|
||
|
||
|
||
@router.get("/{meeting_id}/queue")
|
||
async def get_meeting_queue(meeting_id: str):
|
||
"""获取会议等待队列"""
|
||
scheduler = get_meeting_scheduler()
|
||
queue = await scheduler.get_queue(meeting_id)
|
||
if not queue:
|
||
raise HTTPException(status_code=404, detail="Meeting queue not found")
|
||
return {
|
||
"meeting_id": queue.meeting_id,
|
||
"title": queue.title,
|
||
"status": queue.status,
|
||
"expected_attendees": queue.expected_attendees,
|
||
"arrived_attendees": queue.arrived_attendees,
|
||
"missing_attendees": queue.missing_attendees,
|
||
"progress": queue.progress,
|
||
"is_ready": queue.is_ready
|
||
}
|
||
|
||
|
||
@router.post("/{meeting_id}/wait")
|
||
async def wait_for_meeting(meeting_id: str, request: MeetingWaitRequest):
|
||
"""栅栏同步等待(阻塞直到所有参会者到齐或超时)"""
|
||
scheduler = get_meeting_scheduler()
|
||
status = await scheduler.wait_for_meeting(
|
||
agent_id=request.agent_id,
|
||
meeting_id=meeting_id,
|
||
timeout=request.timeout or 300
|
||
)
|
||
return {"meeting_id": meeting_id, "status": status}
|
||
|
||
|
||
@router.post("/{meeting_id}/end")
|
||
async def end_meeting(meeting_id: str):
|
||
"""结束会议(调度层)"""
|
||
scheduler = get_meeting_scheduler()
|
||
success = await scheduler.end_meeting(meeting_id)
|
||
if not success:
|
||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||
return {"success": True, "meeting_id": meeting_id}
|
||
|
||
|
||
@router.post("/{meeting_id}/join")
|
||
async def join_meeting(meeting_id: str, data: dict):
|
||
"""Agent 加入会议"""
|
||
agent_id = data.get("agent_id", "")
|
||
scheduler = get_meeting_scheduler()
|
||
await scheduler.add_attendee(meeting_id, agent_id)
|
||
return {"success": True, "meeting_id": meeting_id, "agent_id": agent_id}
|
||
|
||
|
||
@router.post("/{meeting_id}/discuss")
|
||
async def add_discussion(meeting_id: str, data: DiscussionRequest):
|
||
"""添加讨论内容"""
|
||
recorder = get_meeting_recorder()
|
||
await recorder.add_discussion(
|
||
meeting_id=meeting_id,
|
||
agent_id=data.agent_id,
|
||
agent_name=data.agent_name or data.agent_id,
|
||
content=data.content,
|
||
step=data.step or ""
|
||
)
|
||
return {"success": True, "meeting_id": meeting_id}
|
||
|
||
|
||
@router.post("/{meeting_id}/finish")
|
||
async def finish_meeting(meeting_id: str, data: FinishRequest):
|
||
"""完成会议(记录层 - 保存共识并标记完成)"""
|
||
recorder = get_meeting_recorder()
|
||
success = await recorder.end_meeting(
|
||
meeting_id=meeting_id,
|
||
consensus=data.consensus or ""
|
||
)
|
||
if not success:
|
||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||
|
||
# 同时结束调度
|
||
scheduler = get_meeting_scheduler()
|
||
await scheduler.end_meeting(meeting_id)
|
||
|
||
return {"success": True, "meeting_id": meeting_id}
|
||
|
||
|
||
@router.post("/{meeting_id}/progress")
|
||
async def update_progress(meeting_id: str, data: ProgressRequest):
|
||
"""更新会议进度"""
|
||
recorder = get_meeting_recorder()
|
||
await recorder.update_progress(
|
||
meeting_id=meeting_id,
|
||
step_label=data.step
|
||
)
|
||
return {"success": True, "meeting_id": meeting_id}
|
||
|
||
|
||
@router.post("/record/create")
|
||
async def create_meeting_record(data: dict):
|
||
"""创建会议记录(前端使用的端点)"""
|
||
recorder = get_meeting_recorder()
|
||
meeting_id = data.get("meeting_id", f"meeting-{int(datetime.now().timestamp())}")
|
||
meeting_info = await recorder.create_meeting(
|
||
meeting_id=meeting_id,
|
||
title=data.get("title", "未命名会议"),
|
||
attendees=data.get("attendees", []),
|
||
steps=data.get("steps", [])
|
||
)
|
||
|
||
# 同时在调度器中注册
|
||
scheduler = get_meeting_scheduler()
|
||
await scheduler.create_meeting(
|
||
meeting_id=meeting_id,
|
||
title=data.get("title", "未命名会议"),
|
||
expected_attendees=data.get("attendees", [])
|
||
)
|
||
|
||
return _meeting_to_dict(meeting_info)
|
||
|
||
|
||
@router.post("/record/{meeting_id}/discussion")
|
||
async def add_meeting_discussion(meeting_id: str, data: dict):
|
||
"""添加会议讨论(前端使用的端点)"""
|
||
recorder = get_meeting_recorder()
|
||
await recorder.add_discussion(
|
||
meeting_id=meeting_id,
|
||
agent_id=data.get("agent_id", ""),
|
||
agent_name=data.get("agent_name", data.get("agent_id", "")),
|
||
content=data.get("content", ""),
|
||
step=data.get("step", "")
|
||
)
|
||
return {"success": True, "meeting_id": meeting_id, "discussion": data}
|