|
|
@@ -0,0 +1,449 @@
|
|
|
+# Dashboard 前端重做计划
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
+
|
|
|
+**Goal:** 在保留像素风视觉风格的基础上,重做 dashboard 前端:砍掉废数据、加图表、拆分文件、新增伏笔追踪和系统状态页。
|
|
|
+
|
|
|
+**Architecture:** React 19 + Vite,新增 Recharts 图表库和 react-router-dom 路由。保留现有 CSS 变量和像素风设计系统。后端补 3 个聚合 API。
|
|
|
+
|
|
|
+**Tech Stack:** React 19, Vite, Recharts, react-router-dom v7, react-force-graph-2d (替换 3d)
|
|
|
+
|
|
|
+**Design:** `dashboard/frontend/design.md`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 文件结构
|
|
|
+
|
|
|
+### 前端(dashboard/frontend/src/)
|
|
|
+
|
|
|
+```
|
|
|
+src/
|
|
|
+├── main.jsx # 入口,挂载 Router
|
|
|
+├── App.jsx # Layout shell(侧边栏 + Router Outlet)
|
|
|
+├── api.js # API 请求(保留,补新端点)
|
|
|
+├── index.css # 全局样式(保留,补图表样式)
|
|
|
+├── components/
|
|
|
+│ ├── PixelChart.jsx # Recharts 像素风封装(统一 tooltip/grid/axis 样式)
|
|
|
+│ ├── Badge.jsx # Badge 组件
|
|
|
+│ └── DataTable.jsx # 带分页的表格组件(从 MiniTable 提取)
|
|
|
+└── pages/
|
|
|
+ ├── OverviewPage.jsx # 总览(精简版,含趋势图)
|
|
|
+ ├── CharactersPage.jsx # 角色图鉴(原 EntitiesPage + GraphPage 合并)
|
|
|
+ ├── PacingPage.jsx # 节奏雷达(新:Strand + 钩子 + 字数图表)
|
|
|
+ ├── ForeshadowingPage.jsx # 伏笔追踪(新:甘特图 + 状态统计)
|
|
|
+ ├── FilesPage.jsx # 文档浏览(原有,移出来)
|
|
|
+ └── SystemPage.jsx # 系统状态(新:Runtime + commit + RAG 环境)
|
|
|
+```
|
|
|
+
|
|
|
+### 后端(dashboard/app.py 补充 API)
|
|
|
+
|
|
|
+```
|
|
|
+/api/stats/chapter-trend # 聚合:每章字数 + 审查得分 + 钩子强度
|
|
|
+/api/commits # 最近 N 个 commit 的 meta + projection_status
|
|
|
+/api/contracts/summary # MASTER_SETTING 摘要 + 当前卷/章合同存在性
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 1: 后端补 3 个聚合 API
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `webnovel-writer/dashboard/app.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: 加 `/api/stats/chapter-trend`**
|
|
|
+
|
|
|
+从 index.db 聚合查询,一次返回每章的字数、审查得分、钩子类型和强度:
|
|
|
+
|
|
|
+```python
|
|
|
+@app.get("/api/stats/chapter-trend")
|
|
|
+def chapter_trend(limit: int = 50):
|
|
|
+ with closing(_get_db()) as conn:
|
|
|
+ chapters = _fetchall_safe(conn,
|
|
|
+ "SELECT chapter, word_count, title FROM chapters ORDER BY chapter DESC LIMIT ?", (limit,))
|
|
|
+ reading = _fetchall_safe(conn,
|
|
|
+ "SELECT chapter, hook_type, hook_strength FROM chapter_reading_power ORDER BY chapter DESC LIMIT ?", (limit,))
|
|
|
+ reviews = _fetchall_safe(conn,
|
|
|
+ "SELECT end_chapter as chapter, overall_score FROM review_metrics ORDER BY end_chapter DESC LIMIT ?", (limit,))
|
|
|
+
|
|
|
+ reading_map = {r["chapter"]: r for r in reading}
|
|
|
+ review_map = {r["chapter"]: r for r in reviews}
|
|
|
+
|
|
|
+ result = []
|
|
|
+ for ch in chapters:
|
|
|
+ c = ch["chapter"]
|
|
|
+ result.append({
|
|
|
+ "chapter": c,
|
|
|
+ "title": ch.get("title", ""),
|
|
|
+ "word_count": ch.get("word_count", 0),
|
|
|
+ "hook_type": reading_map.get(c, {}).get("hook_type"),
|
|
|
+ "hook_strength": reading_map.get(c, {}).get("hook_strength"),
|
|
|
+ "review_score": review_map.get(c, {}).get("overall_score"),
|
|
|
+ })
|
|
|
+ result.sort(key=lambda x: x["chapter"])
|
|
|
+ return result
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 加 `/api/commits`**
|
|
|
+
|
|
|
+读取 `.story-system/commits/` 目录下的 commit JSON 文件:
|
|
|
+
|
|
|
+```python
|
|
|
+@app.get("/api/commits")
|
|
|
+def list_commits(limit: int = 20):
|
|
|
+ commits_dir = _story_system_dir() / "commits"
|
|
|
+ if not commits_dir.is_dir():
|
|
|
+ return []
|
|
|
+ files = sorted(commits_dir.glob("*.commit.json"), reverse=True)[:limit]
|
|
|
+ result = []
|
|
|
+ for f in files:
|
|
|
+ try:
|
|
|
+ data = json.loads(f.read_text(encoding="utf-8"))
|
|
|
+ result.append({
|
|
|
+ "file": f.name,
|
|
|
+ "chapter": data.get("meta", {}).get("chapter"),
|
|
|
+ "status": data.get("meta", {}).get("status"),
|
|
|
+ "projection_status": data.get("projection_status", {}),
|
|
|
+ })
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ return result
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 加 `/api/contracts/summary`**
|
|
|
+
|
|
|
+读取合同树文件存在性和关键字段:
|
|
|
+
|
|
|
+```python
|
|
|
+@app.get("/api/contracts/summary")
|
|
|
+def contracts_summary():
|
|
|
+ ss = _story_system_dir()
|
|
|
+ master = {}
|
|
|
+ master_path = ss / "MASTER_SETTING.json"
|
|
|
+ if master_path.is_file():
|
|
|
+ try:
|
|
|
+ data = json.loads(master_path.read_text(encoding="utf-8"))
|
|
|
+ route = data.get("route", {})
|
|
|
+ master = {
|
|
|
+ "exists": True,
|
|
|
+ "primary_genre": route.get("primary_genre", ""),
|
|
|
+ "core_tone": data.get("master_constraints", {}).get("core_tone", ""),
|
|
|
+ }
|
|
|
+ except Exception:
|
|
|
+ master = {"exists": True, "parse_error": True}
|
|
|
+ else:
|
|
|
+ master = {"exists": False}
|
|
|
+
|
|
|
+ volumes = sorted((ss / "volumes").glob("*.json")) if (ss / "volumes").is_dir() else []
|
|
|
+ chapters = sorted((ss / "chapters").glob("*.json")) if (ss / "chapters").is_dir() else []
|
|
|
+ reviews = sorted((ss / "reviews").glob("*.json")) if (ss / "reviews").is_dir() else []
|
|
|
+
|
|
|
+ return {
|
|
|
+ "master_setting": master,
|
|
|
+ "volume_count": len(volumes),
|
|
|
+ "chapter_count": len(chapters),
|
|
|
+ "review_count": len(reviews),
|
|
|
+ "anti_patterns_exists": (ss / "anti_patterns.json").is_file(),
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 合并 PR #50 的 env-status API**
|
|
|
+
|
|
|
+从 PR #50 的 diff 中提取 `/api/env-status` 和 `/api/env-status/probe` 的代码,加入 app.py。
|
|
|
+
|
|
|
+- [ ] **Step 5: 运行后端测试确认不破坏现有 API**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_dashboard_app.py -v --tb=short
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add webnovel-writer/dashboard/app.py
|
|
|
+git commit -m "feat(dashboard): add chapter-trend, commits, contracts-summary, env-status APIs"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 2: 前端基础设施(路由 + 图表封装 + 文件拆分)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `dashboard/frontend/package.json`
|
|
|
+- Modify: `dashboard/frontend/src/main.jsx`
|
|
|
+- Modify: `dashboard/frontend/src/App.jsx`
|
|
|
+- Modify: `dashboard/frontend/src/api.js`
|
|
|
+- Modify: `dashboard/frontend/src/index.css`
|
|
|
+- Create: `dashboard/frontend/src/components/PixelChart.jsx`
|
|
|
+- Create: `dashboard/frontend/src/components/Badge.jsx`
|
|
|
+- Create: `dashboard/frontend/src/components/DataTable.jsx`
|
|
|
+
|
|
|
+- [ ] **Step 1: 安装依赖**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd webnovel-writer/dashboard/frontend
|
|
|
+npm install recharts react-router-dom react-force-graph-2d
|
|
|
+npm uninstall react-force-graph-3d
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 创建 PixelChart.jsx**
|
|
|
+
|
|
|
+封装 Recharts 的像素风样式,统一 tooltip/grid/axis:
|
|
|
+
|
|
|
+```jsx
|
|
|
+// 像素风 Recharts 封装
|
|
|
+// - CartesianGrid: stroke="#e8dcc4" strokeDasharray="3 3"
|
|
|
+// - Tooltip: border="2px solid #2a220f", background="#fffaf0", 无圆角
|
|
|
+// - XAxis/YAxis: tick fill="#8f7f5c" fontSize=12
|
|
|
+// - 主线 stroke="#26a8ff", 次线 stroke="#f5a524"
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 提取 Badge 和 DataTable 公共组件**
|
|
|
+
|
|
|
+从现有 App.jsx 中提取 `MiniTable` → `DataTable.jsx`,提取 badge 渲染逻辑 → `Badge.jsx`。
|
|
|
+
|
|
|
+- [ ] **Step 4: 改造 main.jsx 加路由**
|
|
|
+
|
|
|
+```jsx
|
|
|
+import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
|
|
+// 每个 page 组件 lazy import
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: 瘦身 App.jsx 为纯 Layout Shell**
|
|
|
+
|
|
|
+App.jsx 只保留:侧边栏 + SSE 连接 + Router Outlet。所有页面逻辑移到 pages/ 下。
|
|
|
+
|
|
|
+- [ ] **Step 6: 补图表相关 CSS**
|
|
|
+
|
|
|
+在 index.css 中补充 Recharts 容器样式:
|
|
|
+
|
|
|
+```css
|
|
|
+.chart-container {
|
|
|
+ border: 3px solid var(--border-main);
|
|
|
+ box-shadow: var(--shadow-main);
|
|
|
+ background: var(--bg-card);
|
|
|
+ padding: 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+.recharts-tooltip-wrapper .pixel-tooltip {
|
|
|
+ border: 2px solid var(--border-main) !important;
|
|
|
+ background: var(--bg-card) !important;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 7: 更新 api.js**
|
|
|
+
|
|
|
+补新端点的 fetch 函数:
|
|
|
+
|
|
|
+```js
|
|
|
+export const fetchChapterTrend = (limit = 50) => fetchJSON('/api/stats/chapter-trend', { limit })
|
|
|
+export const fetchCommits = (limit = 20) => fetchJSON('/api/commits', { limit })
|
|
|
+export const fetchContractsSummary = () => fetchJSON('/api/contracts/summary')
|
|
|
+export const fetchEnvStatus = () => fetchJSON('/api/env-status')
|
|
|
+export const fetchEnvProbe = () => fetchJSON('/api/env-status/probe')
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 8: 构建验证**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd webnovel-writer/dashboard/frontend && npm run build
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 9: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add webnovel-writer/dashboard/frontend/
|
|
|
+git commit -m "refactor(dashboard): add routing, chart components, split file structure"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 3: 总览页重做
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `dashboard/frontend/src/pages/OverviewPage.jsx`
|
|
|
+
|
|
|
+- [ ] **Step 1: 实现总览页**
|
|
|
+
|
|
|
+保留现有内容,做以下改动:
|
|
|
+
|
|
|
+**保留:**
|
|
|
+- 5 个统计卡(总字数、当前章节、Story Runtime、主角状态、紧急伏笔)
|
|
|
+- Strand Weave 分布条
|
|
|
+- 待回收伏笔 Top 5 表格
|
|
|
+
|
|
|
+**新增:**
|
|
|
+- 章节趋势折线图(用 PixelChart + Recharts LineChart)
|
|
|
+ - X 轴:章节号
|
|
|
+ - 左 Y 轴:审查得分(蓝线)
|
|
|
+ - 右 Y 轴:字数(琥珀线)
|
|
|
+ - 数据来源:`/api/stats/chapter-trend`
|
|
|
+
|
|
|
+**删除:**
|
|
|
+- MergedDataView(全量数据视图)——整个组件删掉
|
|
|
+- FULL_DATA_GROUPS / FULL_DATA_DOMAINS 常量——删掉
|
|
|
+
|
|
|
+**修改:**
|
|
|
+- "最近章节"改为从 chapter-trend 取,显示最近 3 章的摘要卡片
|
|
|
+
|
|
|
+- [ ] **Step 2: 构建验证**
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 4: 角色图鉴页(合并实体 + 图谱)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `dashboard/frontend/src/pages/CharactersPage.jsx`
|
|
|
+
|
|
|
+- [ ] **Step 1: 实现角色图鉴页**
|
|
|
+
|
|
|
+合并原 EntitiesPage + GraphPage 为一页,两个 tab 切换:
|
|
|
+
|
|
|
+**Tab 1:列表视图**(原 EntitiesPage,保留不动)
|
|
|
+- 左列表 + 右详情 + 状态变化历史
|
|
|
+
|
|
|
+**Tab 2:关系图谱**
|
|
|
+- 换成 react-force-graph-2d(从 3d 降级)
|
|
|
+- 保留颜色编码和节点标签
|
|
|
+
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 5: 节奏雷达页(新)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `dashboard/frontend/src/pages/PacingPage.jsx`
|
|
|
+
|
|
|
+- [ ] **Step 1: 实现节奏雷达页**
|
|
|
+
|
|
|
+三个区块:
|
|
|
+
|
|
|
+**区块 1:Strand 分布(堆叠条形图)**
|
|
|
+- 最近 20 章的 Strand 分布
|
|
|
+- 用 Recharts BarChart,堆叠模式
|
|
|
+- Quest=蓝,Fire=粉红,Constellation=紫
|
|
|
+
|
|
|
+**区块 2:钩子强度折线图**
|
|
|
+- X 轴:章节号
|
|
|
+- Y 轴:strong=3, medium=2, weak=1
|
|
|
+- 连续 weak 标红区域
|
|
|
+- 数据来源:chapter-trend API 的 hook_strength 字段
|
|
|
+
|
|
|
+**区块 3:最近 10 章节奏卡片**
|
|
|
+- 每章一个小卡片行:钩子类型 + 强度 badge + Strand + 是否过渡章
|
|
|
+
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 6: 伏笔追踪页(新)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `dashboard/frontend/src/pages/ForeshadowingPage.jsx`
|
|
|
+
|
|
|
+- [ ] **Step 1: 实现伏笔追踪页**
|
|
|
+
|
|
|
+**顶部:4 个统计卡**
|
|
|
+- 总伏笔数、活跃数、已回收数、超期/紧急数
|
|
|
+
|
|
|
+**中部:伏笔时间线(横向甘特图)**
|
|
|
+- 用 Recharts BarChart 横向模式模拟甘特图
|
|
|
+- X 轴:章节号范围
|
|
|
+- 每条伏笔一行:从埋设章到目标章画一条横条
|
|
|
+- 颜色:绿=已回收,黄=活跃,红=快超期/已超期
|
|
|
+
|
|
|
+**底部:完整伏笔表格**
|
|
|
+- 列:内容、状态、埋设章、目标章、紧急度
|
|
|
+- 数据来源:`/api/project/info` 的 `plot_threads.foreshadowing`
|
|
|
+
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 7: 系统状态页(新)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `dashboard/frontend/src/pages/SystemPage.jsx`
|
|
|
+
|
|
|
+- [ ] **Step 1: 实现系统状态页**
|
|
|
+
|
|
|
+**区块 1:Story Runtime 健康**
|
|
|
+- 调 `/api/story-runtime/health`
|
|
|
+- 显示 mainline/fallback 状态、fallback_sources、latest commit status
|
|
|
+
|
|
|
+**区块 2:合同树概览**
|
|
|
+- 调 `/api/contracts/summary`
|
|
|
+- 显示 MASTER_SETTING 的题材/调性、volume/chapter/review 合同数量
|
|
|
+
|
|
|
+**区块 3:最近 Commit 历史**
|
|
|
+- 调 `/api/commits`
|
|
|
+- 表格:章节号、状态(accepted/rejected badge)、projection_status 五项
|
|
|
+
|
|
|
+**区块 4:RAG 环境(PR #50)**
|
|
|
+- 调 `/api/env-status`
|
|
|
+- 显示 embed/rerank key 配置状态、vector_db 大小、rag_mode
|
|
|
+- "诊断" 按钮调 `/api/env-status/probe`,显示延迟和连通性
|
|
|
+
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Task 8: 文档浏览页迁移 + 清理
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `dashboard/frontend/src/pages/FilesPage.jsx`
|
|
|
+- Modify: `dashboard/frontend/src/App.jsx` (最终清理)
|
|
|
+
|
|
|
+- [ ] **Step 1: 迁移 FilesPage**
|
|
|
+
|
|
|
+从 App.jsx 移到 `pages/FilesPage.jsx`,逻辑不变。
|
|
|
+
|
|
|
+- [ ] **Step 2: 最终清理 App.jsx**
|
|
|
+
|
|
|
+确认 App.jsx 只剩 Layout Shell(侧边栏 + Router Outlet + SSE),无页面逻辑。
|
|
|
+
|
|
|
+删除所有已迁移的组件和常量(DashboardPage、EntitiesPage、GraphPage 等)。
|
|
|
+
|
|
|
+- [ ] **Step 3: 前端构建 + 验证**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd webnovel-writer/dashboard/frontend && npm run build
|
|
|
+```
|
|
|
+
|
|
|
+启动 dashboard 确认所有 6 个页面正常:
|
|
|
+```bash
|
|
|
+cd webnovel-writer && python -m dashboard.server --project-root "{test_project}" --no-browser
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add webnovel-writer/dashboard/
|
|
|
+git commit -m "feat(dashboard): complete frontend rebuild with charts, pacing, foreshadowing, system pages"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 导航变更汇总
|
|
|
+
|
|
|
+| 旧导航 | 新导航 | 变化 |
|
|
|
+|--------|--------|------|
|
|
|
+| 📊 数据总览 | 📊 总览 | 删全量视图,加趋势折线图 |
|
|
|
+| 👤 设定词典 | 👤 角色图鉴 | 合并关系图谱,2D 替 3D |
|
|
|
+| 🕸️ 关系图谱 | _(合并到角色)_ | 删除独立页面 |
|
|
|
+| 📝 章节一览 | 📈 节奏雷达 | 新页面,Strand/钩子/字数图表 |
|
|
|
+| 📁 文档浏览 | 📁 文档浏览 | 不变 |
|
|
|
+| 🔥 追读力 | 🔖 伏笔追踪 | 新页面,替代纯表格追读力 |
|
|
|
+| _(无)_ | ⚙️ 系统状态 | 新页面 |
|
|
|
+
|
|
|
+## 删除的数据
|
|
|
+
|
|
|
+- RAG 查询日志
|
|
|
+- 工具调用统计
|
|
|
+- Override 合约明细
|
|
|
+- 债务事件明细
|
|
|
+- 无效事实列表
|
|
|
+- 写作清单评分原始数据
|
|
|
+- 全量数据视图(MergedDataView)
|