17 KiB
课题9:Dashboard 前后端完整设计方案
版本: v2.0(评审修订版) 作者: 庞统(副军师)🐦 日期: 2026-05-17 状态: 待司马懿确认 评审记录: Mail #278(司马懿 P0+P1 评审)→ 已全部修复 → Mail #279(验证通过)
一、核心定位
1.1 Dashboard 不是管理控制台,是 AI 工作可视化窗口
| 传统 Dashboard | AI Native Dashboard(v2.0) |
|---|---|
| 用户主动操作(点按钮、填表单) | AI 主动展示(推送状态、产出、决策请求) |
| 用户监控一切 | AI 只在关键点拉人,其余自主 |
| 表格+表单为主 | 时间线+卡片+对话为主 |
| 用户是驾驶员 | 用户是乘客,偶尔导航 |
1.2 参考的优秀实践
| 实践 | 借鉴点 |
|---|---|
| Edict MiniPipe | 任务卡片上的状态管线可视化 |
| Hermes Kanban | 卡片 + 结果区 + 评论线程 + 事件流 + Nudge 按钮 |
| Claude Code App | 多 Tab 切换详情面板(产出/评审/事件) |
| Linear | 精准通知不噪音;状态流转按钮守卫 |
| Devin | AI 自主执行的观察窗口理念 |
| OpenClaw Control Center | 双入口对等(对话 + Dashboard) |
二、数据模型
2.1 V2Task 类型(对齐后端 tasks 表)
前端核心类型,字段完全对齐后端 src/blackboard/db.py 的 tasks 表:
interface V2Task {
// 基础字段(直接对应 tasks 表列)
id: string;
title: string;
description: string;
status: TaskStatus; // 8 状态枚举,见 §2.2
assignee: string | null;
assigned_by: string | null;
depends_on: string | null; // 依赖任务 ID
parent_task: string | null; // 父任务 ID
priority: number; // 1-10 整数(P0 修复:前端曾用 string,已改 number)
task_type: string;
created_at: string;
updated_at: string;
claimed_at: string | null;
completed_at: string | null;
started_at: string | null;
deadline: string | null;
retry_count: number;
max_retries: number;
risk_level: 'low' | 'standard' | 'high' | 'critical';
escalated: number; // 0/1 整数(与后端 INTEGER 一致)
// 聚合字段(后端 summary API 或前端多次查询)
comments_count: number;
outputs_count: number;
review_status: 'none' | 'pending' | 'approved' | 'rejected' | 'rebuttal';
latest_event: string | null;
project_id: string;
}
2.2 状态机(8 状态,对齐后端 VALID_STATUSES)
pending ──→ claimed ──→ working ──→ review ──→ done
│ │ │ │
│ │ ├→ blocked ├→ failed
│ │ │ │
└→ cancelled←┴───────────┴───────────┘
线性管线(5 步):pending → claimed → working → review → done
旁路状态:failed(标记在 working 位置)、blocked(标记在 working 位置)
终态:done、cancelled
2.3 状态流转守卫(对齐后端 VALID_TRANSITIONS)
const VALID_TRANSITIONS: Record<string, string[]> = {
pending: ['claimed', 'cancelled'],
claimed: ['working', 'pending', 'cancelled'],
working: ['review', 'blocked', 'failed', 'cancelled'],
review: ['done', 'pending', 'failed', 'cancelled'],
blocked: ['pending', 'cancelled'],
failed: ['pending'],
done: [],
cancelled: [],
};
与后端 src/blackboard/db.py 的 VALID_TRANSITIONS 完全一致。
2.4 Priority 映射
后端 priority 是 INTEGER(1-10),前端映射:
const PRIORITY_META: Record<number, { color: string; label: string }> = {
1: { color: '#6b7280', label: '低' },
2: { color: '#6b7280', label: '低' },
3: { color: '#3b82f6', label: '中' },
4: { color: '#3b82f6', label: '中' },
5: { color: '#f59e0b', label: '高' },
6: { color: '#f59e0b', label: '高' },
7: { color: '#ff5270', label: '紧急' },
8-10: { color: '#ff5270', label: '紧急' },
};
三、前端页面设计
3.1 页面结构
| 页面 | 组件 | 优先级 | 状态 |
|---|---|---|---|
| 📜 任务看板 | EdictBoard | P0 | ✅ Mock UI 已完成,待对接 API |
| 🔍 任务详情 | TaskModal | P0 | ✅ Mock UI 已完成,待对接 API |
| 🏛️ 朝堂议政 | CourtDiscussion | P1 | 迁移自 v1.0 |
| 🔌 编排调度 | MonitorPanel | P1 | 迁移自 v1.0 |
| 🤺 将军总览 | OfficialPanel | P1 | 迁移自 v1.0 |
| 🔔 通知中心 | NotificationCenter | P1 | 占位(Header 铃铛) |
| 📊 AI Briefing | AIBriefing | P2 | 占位页面 |
| ⚙️ 系统设置 | SettingsPanel | P1 | 迁移自 v1.0 |
3.2 任务看板(EdictBoard)
顶部统计(4 格)
| 格 | 内容 | 数据来源 |
|---|---|---|
| 活跃任务 | working + claimed + review 数 | tasks 表 count |
| 已完成 | done 数 | tasks 表 count |
| 失败/阻塞 | failed + blocked 数 | tasks 表 count |
| 审查中 | review 数 | tasks 表 count |
状态管线可视化(5 步线性)
📋待认领 → 👤已认领 → ⚔️执行中 → 🔍审查中 → ✅已完成
- 管线上游步骤显示 ✓(绿色),当前步骤高亮(蓝色),下游步骤灰显
failed状态:在 working 位置显示 ✗(红色)blocked状态:在 working 位置显示 🚧(黄色)cancelled状态:管线全部灰显
筛选栏
8 个状态筛选按钮(all/pending/claimed/working/review/done/failed/blocked)+ 搜索框。
任务卡片
每张卡片展示:
- 状态管线(5 步 MiniPipe)
- ID + 标题
- 标签行:状态标签 + 负责人(agent emoji)+ 优先级 + 升级标记 + 依赖标记
- 元信息行:更新时间 + 产出数 + 评论数 + 审查状态图标
- 最新事件:单行文本截断
- 底部:风险等级 + 重试次数 + 截止时间 + 详情按钮
3.3 任务详情面板(TaskModal)
点击任务卡片弹出 Modal,4 Tab 布局:
Tab 1: 📋 总览
| 区域 | 内容 | 数据来源 |
|---|---|---|
| 需求描述 | task.description | tasks 表 |
| 状态操作 | 基于 VALID_TRANSITIONS 的操作按钮 | §2.3 守卫 |
| 任务信息 | 类型/优先级/风险/重试/时间/分配人(8 字段网格) | tasks 表 |
| 事件时间线 | 逆序事件列表(图标 + 描述 + 时间 + agent) | events 表 |
| 评论/交接 | handoff/progress/review/rebuttal/debate/observation 6 种类型 | comments 表 |
| 决策记录 | 决策人 + 决策类型 + 理由 | decisions 表 |
| Checkpoint | 占位(虚线框 + "v2.7 提供") | ❌ 后端未实现 |
Tab 2: 📦 产出
产出物列表:agent + 内容 + 类型标签 + 尝试次数 + 时间。数据来源:outputs 表。
Tab 3: 🔍 审查
审查意见:评审人 + verdict(APPROVE/REJECT)+ 置信度 + 风险等级 + 辩论轮次 + 摘要。数据来源:reviews 表。
Tab 4: 🧠 经验
关联经验:标题 + 摘要 + 标签。左侧彩色边框区分 best_practice(绿)/ pitfall(红)/ environment(蓝)。数据来源:experiences 表。
3.4 通知中心(Header 铃铛)
🔔 (3) → 点击展开通知面板
- 按 4 级分组(🔴 Critical > 🟡 Warning > 🟢 Info > 🔵 Silent)
- 每条:时间 + 级别图标 + 内容摘要 + 关联任务链接
- 🔴 可展开操作按钮
- 支持标记已读/全部已读
当前状态:SSE broker 已实现(F17),ticker 未推送事件。前端做铃铛 UI + 占位。
3.5 AI Briefing
日报/周报自动生成。格式:
📊 今日战报 (2026-05-17)
━━━━━━━━━━━━━━━━━━━━
📋 完成任务:3 个
🔄 进行中:2 个
⚠️ 需关注:1 个
💰 Token 消耗:127K
🧠 新增经验:2 条
当前状态:后端未实现。前端做占位页面。
四、后端 API 设计
4.1 已有 API(F5 实现)
| 端点 | 方法 | 说明 |
|---|---|---|
/api/projects/{pid}/tasks |
GET | 任务列表(支持 status 筛选) |
/api/projects/{pid}/tasks |
POST | 创建任务 |
/api/projects/{pid}/tasks/{tid} |
GET | 单任务基本信息 |
/api/projects/{pid}/tasks/{tid} |
PATCH | 更新任务 |
/api/projects/{pid}/tasks/{tid}/status |
POST | 状态流转 |
/api/projects/{pid}/tasks/{tid}/claim |
POST | 认领任务 |
/api/projects/{pid}/tasks/{tid}/comments |
GET/POST | 评论 CRUD |
/api/projects/{pid}/tasks/{tid}/outputs |
GET/POST | 产出 CRUD |
/api/projects/{pid}/tasks/{tid}/decisions |
GET/POST | 决策 CRUD |
/api/projects/{pid}/tasks/{tid}/observations |
GET/POST | 观察 CRUD |
/api/projects/{pid}/tasks/{tid}/reviews |
GET/POST | 审查 CRUD |
/api/events |
GET (SSE) | SSE 事件流 |
/api/daemon/status |
GET | Daemon 状态 |
/api/projects |
GET/POST | 项目管理 |
4.2 需新增的 API
API-1: GET /api/projects/{pid}/tasks/{tid}?expand=comments,outputs,reviews,events,decisions
优先级: P0
说明: 任务详情聚合,一次返回全部关联数据,避免前端多次请求。
实现方案: 在 blackboard_routes.py 的 get_task 端点增加 expand 查询参数。
@router.get("/tasks/{task_id}")
async def get_task(
task_id: str,
expand: str = "", # 逗号分隔: "comments,outputs,reviews,events,decisions"
db_path: Path = Depends(get_db_path),
):
task = queries.get_task(db_path, task_id)
if not task:
raise HTTPException(404, "Task not found")
result = dict(task)
if expand:
for rel in expand.split(","):
rel = rel.strip()
if rel == "comments":
result["comments"] = queries.list_comments(db_path, task_id)
elif rel == "outputs":
result["outputs"] = queries.list_outputs(db_path, task_id)
elif rel == "reviews":
result["reviews"] = queries.list_reviews(db_path, task_id)
# 聚合 review_status
if result["reviews"]:
latest = result["reviews"][-1]
result["review_status"] = "approved" if latest["verdict"] == "APPROVE" else "rejected"
else:
result["review_status"] = "none"
elif rel == "events":
result["events"] = queries.list_events(db_path, task_id)
# 聚合 latest_event
if result["events"]:
result["latest_event"] = result["events"][-1]["detail"]
elif rel == "decisions":
result["decisions"] = queries.list_decisions(db_path, task_id)
# 聚合计数
result["comments_count"] = queries.count_comments(db_path, task_id)
result["outputs_count"] = queries.count_outputs(db_path, task_id)
return result
工作量: 30min(queries.py 已有基础方法,只需组合)
API-2: GET /api/projects/{pid}/tasks/{tid}/events
优先级: P0 说明: 任务事件时间线查询。 实现方案: 新增路由端点。
@router.get("/tasks/{task_id}/events")
async def get_task_events(
task_id: str,
limit: int = 50,
offset: int = 0,
db_path: Path = Depends(get_db_path),
):
return queries.list_events(db_path, task_id, limit=limit, offset=offset)
前置: queries.list_events() 已存在(events 表 CRUD 已实现)。
工作量: 15min
API-3: GET /api/projects/{pid}/tasks/{tid}/experiences
优先级: P1 说明: 查询与任务关联的经验(通过 task_id 检索 experiences.jsonl)。 实现方案: 新增路由端点 + queries 方法。
@router.get("/tasks/{task_id}/experiences")
async def get_task_experiences(
task_id: str,
project_id: str,
):
# 从 experiences.jsonl 读取,过滤 source_task == task_id
experiences = experience_store.list_by_task(project_id, task_id)
return experiences
工作量: 30min(需在 experience_store 增加 list_by_task 方法)
4.3 已有 API 但需增强
| 端点 | 增强 | 说明 |
|---|---|---|
GET /api/projects/{pid}/tasks |
增加 counts 返回 |
任务列表 API 返回各状态计数,供统计卡片使用 |
GET /api/events (SSE) |
ticker 事件推送 | ticker 状态变更时自动通过 SSE 推送(F17 SSEBroker 已实现,只需在 ticker 中调用 publish) |
4.4 完整路由表
/api/projects GET/POST
/api/projects/{pid} GET/DELETE
/api/projects/{pid}/tasks GET/POST
/api/projects/{pid}/tasks/{tid} GET/PATCH/DELETE
/api/projects/{pid}/tasks/{tid}/status POST
/api/projects/{pid}/tasks/{tid}/claim POST
/api/projects/{pid}/tasks/{tid}/comments GET/POST
/api/projects/{pid}/tasks/{tid}/outputs GET/POST
/api/projects/{pid}/tasks/{tid}/decisions GET/POST
/api/projects/{pid}/tasks/{tid}/observations GET/POST
/api/projects/{pid}/tasks/{tid}/reviews GET/POST
/api/projects/{pid}/tasks/{tid}/events GET ← 新增 API-2
/api/projects/{pid}/tasks/{tid}/experiences GET ← 新增 API-3
/api/events GET (SSE)
/api/daemon/status GET
五、实现方案
5.1 后端改动清单
| # | 改动 | 文件 | 优先级 | 工作量 |
|---|---|---|---|---|
| B1 | get_task 增加 expand 参数 |
blackboard_routes.py + queries.py |
P0 | 30min |
| B2 | 新增 GET /tasks/{tid}/events 路由 |
blackboard_routes.py |
P0 | 15min |
| B3 | 新增 GET /tasks/{tid}/experiences 路由 |
blackboard_routes.py + experience_store.py |
P1 | 30min |
| B4 | 任务列表 API 返回状态计数 | blackboard_routes.py |
P1 | 15min |
| B5 | ticker 状态变更推送 SSE | ticker.py |
P1 | 1h |
5.2 前端改动清单
| # | 改动 | 文件 | 优先级 | 工作量 |
|---|---|---|---|---|
| F1 | EdictBoard Mock → 对接真实 API | EdictBoard.tsx |
P0 | 1h |
| F2 | TaskModal Mock → 对接真实 API | TaskModal.tsx |
P0 | 1.5h |
| F3 | api.ts 补新 API 方法 | api.ts |
P0 | 30min |
| F4 | store.ts 补状态字段 | store.ts |
P0 | 15min |
| F5 | SSE EventSource 替代轮询 | App.tsx + store.ts |
P1 | 1h |
| F6 | 通知中心铃铛占位 | NotificationCenter.tsx + App.tsx |
P1 | 30min |
| F7 | AI Briefing 占位页面 | AIBriefing.tsx |
P2 | 15min |
| F8 | 其余 v1.0 Tab 适配 | 各 Panel 组件 | P1 | 2h |
5.3 执行顺序
Phase 1: 后端补 API(B1-B3) ~1.5h
Phase 2: 前端核心对接(F1-F4) ~3h
Phase 3: SSE + 通知占位(B5+F5+F6) ~2.5h
Phase 4: 其余 Tab 适配(F8) ~2h
Phase 5: 联调 + E2E 验证 ~1h
总计: ~10h
5.4 不在本轮范围
| 功能 | 原因 | 计划 |
|---|---|---|
| Checkpoint(验证/决策/执行) | 后端无数据模型,需新设计 | v2.7 专项 |
| AI Briefing 生成 | 后端聚合逻辑未设计 | v2.7 专项 |
| Artifact 文件预览/下载 | 后端无文件存储机制 | v2.7 评估 |
| 朝堂议政迁移 | 需要 CourtDiscussion 适配 v2.6 API | v2.7 |
六、UI 组件文件结构
src/frontend/src/
├── App.tsx # 主布局 + Header + Tab 路由
├── api.ts # API 层(所有后端调用)
├── store.ts # Zustand 全局状态
├── types.ts # V2Task 等类型定义(从 EdictBoard 提取)
└── components/
├── EdictBoard.tsx # 任务看板(卡片 + 筛选 + 搜索)
├── TaskModal.tsx # 任务详情(4 Tab)
├── NotificationCenter.tsx # 通知铃铛(占位)
├── CourtDiscussion.tsx # 朝堂议政(迁移自 v1.0)
├── MonitorPanel.tsx # 编排调度(迁移自 v1.0)
├── OfficialPanel.tsx # 将军总览(迁移自 v1.0)
├── ModelConfig.tsx # 模型配置(迁移自 v1.0)
├── SkillsConfig.tsx # 技能配置(迁移自 v1.0)
├── SessionsPanel.tsx # 传令巡哨(迁移自 v1.0)
├── MemorialPanel.tsx # 奏折阁(迁移自 v1.0)
├── SettingsPanel.tsx # 系统设置(迁移自 v1.0)
└── GlobalSearch.tsx # 全局搜索
七、评审修订记录
司马懿 Mail #278 评审 → 修复
| 项目 | 级别 | 修复内容 |
|---|---|---|
| P0: priority 类型 | 必修 | string → number(1-10),前端 PRIORITY_META 按整数映射 |
| P1: 状态管线 | 必修 | 4 步 → 5 步线性 + blocked 旁路,8 状态全覆盖 |
| P1: VALID_TRANSITIONS | 必修 | 完全对齐后端 db.py 的 8×N 映射 |
| P2: 遗漏字段 | 建议 | V2Task 补 depends_on, parent_task |
司马懿 Mail #279 确认
✅ P0 priority: number ✅ P1 STATUS_ORDER 含 review/blocked ✅ P1 TaskModal 状态映射含 reviewing/blocked/cancelled(17 处引用) 构建产物 12:37 部署在 8083,前端可访问。