# 课题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` 表：

```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` 是 INTEGER（1-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: 🔍 审查

审查意见：评审人 + 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` 查询参数。

```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
```

**工作量**: 30min（queries.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: 后端补 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，前端可访问。
