Files
sanguo_moziplus_v2/docs/design/topic9-dashboard-design.md
T
2026-05-17 12:52:48 +08:00

467 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 课题9:Dashboard 前后端完整设计方案
**版本**: v2.0(评审修订版)
**作者**: 庞统(副军师)🐦
**日期**: 2026-05-17
**状态**: 待司马懿确认
**评审记录**: Mail #278(司马懿 P0+P1 评审)→ 已全部修复 → Mail #279(验证通过)
---
## 一、核心定位
### 1.1 Dashboard 不是管理控制台,是 AI 工作可视化窗口
| 传统 Dashboard | AI Native Dashboardv2.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` 表:
```typescript
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
```typescript
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` 是 INTEGER1-10),前端映射:
```typescript
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: 🔍 审查
审查意见:评审人 + verdictAPPROVE/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 已有 APIF5 实现)
| 端点 | 方法 | 说明 |
|------|------|------|
| `/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` 查询参数。
```python
@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
```
**工作量**: 30minqueries.py 已有基础方法,只需组合)
#### API-2: `GET /api/projects/{pid}/tasks/{tid}/events`
**优先级**: P0
**说明**: 任务事件时间线查询。
**实现方案**: 新增路由端点。
```python
@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 方法。
```python
@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: 后端补 APIB1-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/cancelled17 处引用)
> 构建产物 12:37 部署在 8083,前端可访问。