Full-stack application for document-to-knowledge-graph pipeline: - Backend: FastAPI + LangGraph ReAct agent + DeepSeek + MinerU parsing - Frontend: React 19 + Vite + D3.js + shadcn/ui - Pipeline: MinerU parsing → LangExtract entity extraction → KG building Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
50 KiB
50 KiB
GraphRAG Studio — 前端 Web 系统设计规范 v1.0
基于
docs/backend_service_specification-v1.0.md接口规范 前端架构:原生 HTML + CSS + JS + D3.js v7(SPA,零构建依赖) 更新日期:2026-03-05
目录
一、总体架构
1.1 技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| 应用类型 | SPA(单页应用) | 5 页无缝切换,无刷新体验 |
| 路由 | Hash 路由(原生 JS) | 无需构建工具,#/dashboard #/documents 等 |
| 框架 | 原生 HTML + CSS + JS | 与现有 index.html 一致,零构建依赖,直接在浏览器运行 |
| 图形渲染 | D3.js v7(CDN) | 复用现有 KG 可视化逻辑(graphrag_pipeline/static/index.html) |
| Markdown 渲染 | marked.js v9(CDN) | Chat 页 AI 答案 Markdown 渲染 |
| API 通信 | Fetch API | 原生支持,封装统一错误处理 |
| 图标 | Unicode / SVG 内联 | 零依赖(无需图标库 CDN) |
1.2 路由设计
hash 路由 → DOM 区域显示/隐藏
#/dashboard → 显示 <section id="page-dashboard">
#/documents → 显示 <section id="page-documents">
#/graph → 显示 <section id="page-graph"> + 初始化 D3
#/chat → 显示 <section id="page-chat">
#/search → 显示 <section id="page-search">
/ → 重定向到 #/dashboard
URL 参数传递(hash query):
#/graph?doc_id=abc12345 → KG Explorer 按文档筛选
#/graph?node=tech_graphrag_0 → KG Explorer 聚焦节点
#/chat?q=What+is+GraphRAG → Chat 预填问题
1.3 全局状态管理
// app.js 中维护的全局状态(内存)
const AppState = {
currentPage: 'dashboard',
kg: {
nodes: [], // 全量节点(加载后缓存)
edges: [], // 全量边(加载后缓存)
loaded: false,
},
documents: [], // 文档列表缓存
activeJobs: {}, // job_id → polling timer
chatHistory: [], // 当前会话消息历史
health: null, // 最近一次 health 响应
};
1.4 API 客户端(api.js)
const API = {
BASE: 'http://localhost:8000/api/v1',
async get(path, params = {}) {
const url = new URL(this.BASE + path);
Object.entries(params).forEach(([k, v]) => v != null && url.searchParams.set(k, v));
const res = await fetch(url);
const json = await res.json();
if (json.code !== 0) throw new APIError(json.code, json.msg);
return json.data;
},
async post(path, body = {}) {
const res = await fetch(this.BASE + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const json = await res.json();
if (json.code !== 0) throw new APIError(json.code, json.msg);
return json.data;
},
async postForm(path, formData) {
const res = await fetch(this.BASE + path, { method: 'POST', body: formData });
const json = await res.json();
if (json.code !== 0) throw new APIError(json.code, json.msg);
return json.data;
},
async delete(path) { ... },
// 轮询直到条件满足
async poll(path, params = {}, interval = 3000, until = (d) => d.status === 'done') {
return new Promise((resolve, reject) => {
const timer = setInterval(async () => {
try {
const data = await this.get(path, params);
if (until(data)) { clearInterval(timer); resolve(data); }
if (data.status === 'failed') { clearInterval(timer); reject(new Error(data.error)); }
} catch (e) { clearInterval(timer); reject(e); }
}, interval);
});
}
};
1.5 文件结构
graphrag_pipeline/static/
├── index.html # 保留(旧 Flask KG 可视化,向后兼容)
└── app/
├── index.html # SPA 主入口(GraphRAG Studio)
├── css/
│ ├── variables.css # CSS 变量(颜色 / 间距 / 字体)
│ ├── base.css # Reset + 通用组件(btn, badge, card, modal...)
│ └── layout.css # Sidebar + Header + Footer 布局 + 响应式
└── js/
├── app.js # 路由器 + 页面切换 + 全局状态
├── api.js # Fetch 封装(baseURL, 统一错误处理)
├── components.js # Toast / Modal / Progress / Skeleton
└── pages/
├── dashboard.js
├── documents.js
├── graph.js # D3 力导向图(基于现有 index.html 重构)
├── chat.js
└── search.js
二、设计语言与风格系统
2.1 调色板(基于现有 index.html 扩展)
:root {
/* ── 背景层级 ── */
--bg-base: #0f1117; /* 页面底色 */
--bg-s1: #161b22; /* sidebar / header / card surface */
--bg-s2: #21262d; /* hover state / input bg / tag bg */
--bg-s3: #1c2128; /* tooltip / popover / code bg */
/* ── 边框 ── */
--border: #30363d;
--border-muted:#21262d;
/* ── 文字 ── */
--text-1: #f0f6fc; /* 主文字 */
--text-2: #c9d1d9; /* 正文 */
--text-3: #8b949e; /* 辅助/label */
--text-4: #484f58; /* placeholder / 极弱 */
/* ── 强调色 ── */
--blue: #58a6ff; /* 链接 / 激活 / 聚焦 / 进度条 */
--green: #3fb950; /* 成功 / indexed 状态 */
--green-btn: #238636; /* 主操作按钮背景 */
--green-hover: #2ea043; /* 主按钮 hover */
--red: #f85149; /* 错误 / 危险 / failed */
--yellow: #d29922; /* 警告 / indexing */
--purple: #8957e5; /* 紫色强调 */
/* ── 实体类型颜色(与 D3 图谱一一对应)── */
--type-tech: #58a6ff; /* TECHNOLOGY — 蓝 */
--type-concept:#bc8cff; /* CONCEPT — 紫 */
--type-person: #3fb950; /* PERSON — 绿 */
--type-org: #ff7b72; /* ORGANIZATION— 红 */
--type-loc: #ffa657; /* LOCATION — 橙 */
/* ── 字体 ── */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
/* ── 圆角 ── */
--r-sm: 4px;
--r-md: 6px;
--r-lg: 8px;
--r-xl: 12px;
/* ── 阴影 ── */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.4);
--shadow-md: 0 4px 16px rgba(0,0,0,0.5);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.6);
/* ── 过渡 ── */
--transition: 150ms ease;
}
2.2 按钮规范(4 变体)
| 变体 | 背景 | 边框 | 文字 | 用途 |
|---|---|---|---|---|
.btn-primary |
--green-btn |
--green-btn |
#fff |
主操作(Upload, Send, Index) |
.btn-secondary |
--bg-s2 |
--border |
--text-2 |
次要操作(Cancel, Filter) |
.btn-ghost |
transparent | none | --text-3 |
内联操作(图标按钮) |
.btn-danger |
--bg-s2 |
--border |
--red |
危险操作(Delete) |
.btn {
padding: 6px 14px;
border-radius: var(--r-md);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all var(--transition);
display: inline-flex; align-items: center; gap: 6px;
}
.btn-primary { background: var(--green-btn); border: 1px solid var(--green-btn); color: #fff; }
.btn-primary:hover { background: var(--green-hover); }
.btn-sm { padding: 4px 10px; font-size: 12px; }
2.3 Status Badge 规范
/* 通用 badge */
.badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 2px 8px;
border-radius: 20px;
font-size: 11px; font-weight: 600;
}
/* 状态 badge */
.badge-indexed { background: #1a3a22; color: var(--green); }
.badge-indexing { background: #2d2a16; color: var(--yellow); }
.badge-uploaded { background: var(--bg-s3); color: var(--text-3); }
.badge-failed { background: #3b1a1a; color: var(--red); }
/* 实体类型 badge */
.badge-TECHNOLOGY { background: #162032; color: var(--type-tech); }
.badge-CONCEPT { background: #1e1632; color: var(--type-concept); }
.badge-PERSON { background: #132318; color: var(--type-person); }
.badge-ORGANIZATION { background: #2e1a1a; color: var(--type-org); }
.badge-LOCATION { background: #2a1e10; color: var(--type-loc); }
/* 活跃点(动画) */
.badge-indexing::before {
content: ''; width: 6px; height: 6px; border-radius: 50%;
background: var(--yellow);
animation: pulse 1.5s infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
2.4 字体层级
| 层级 | size | weight | color | 用途 |
|---|---|---|---|---|
text-h1 |
20px | 600 | --text-1 |
页面大标题 |
text-h2 |
16px | 600 | --text-1 |
区块标题 |
text-h3 |
13px | 600 uppercase | --text-3 |
面板子标题 |
text-body |
14px | 400 | --text-2 |
正文 |
text-sm |
12px | 400 | --text-3 |
辅助信息 |
text-badge |
11px | 600 | — | 标签/徽章 |
text-mono |
13px | 400 | --text-2 |
代码/路径/工具输出 |
三、整体布局
3.1 CSS Grid 骨架
┌──────────────────────────────────────────────────────────────┐
│ HEADER (56px, position: sticky, top: 0, z-index: 100) │
│ [≡] GraphRAG Studio [🔍 Search entities...] [● API] │
├─────────┬────────────────────────────────────────────────────┤
│ │ │
│ SIDEBAR │ MAIN CONTENT AREA │
│ (220px) │ (overflow-y: auto) │
│ fixed │ │
│ ────── │ — 各页面 <section> 区域 — │
│ Home │ │
│ Docs │ │
│ Graph │ │
│ Chat │ │
│ Search │ │
│ ────── │ │
│ System │ │
│ │ │
├─────────┴────────────────────────────────────────────────────┤
│ STATUS BAR (32px) [job 进度] [v1.0.0] [● ok] │
└──────────────────────────────────────────────────────────────┘
.app {
display: grid;
grid-template-areas: "header header" "sidebar main" "footer footer";
grid-template-columns: var(--sidebar-w, 220px) 1fr;
grid-template-rows: 56px 1fr 32px;
height: 100vh;
overflow: hidden;
}
header { grid-area: header; }
.sidebar{ grid-area: sidebar; overflow-y: auto; }
main { grid-area: main; overflow-y: auto; }
footer { grid-area: footer; }
3.2 Sidebar 导航结构
┌─────────────────────────┐
│ G GraphRAG Studio │ ← Logo 区(16px font, weight 700)
├─────────────────────────┤
│ ◈ Dashboard │ ← 激活状态: bg rgba(88,166,255,0.1), left border 2px #58a6ff
│ ▤ Documents [ 5] │ ← 右侧数量 badge(文档总数)
│ ◉ KG Explorer │
│ ◇ Chat [ 50] │ ← 查询历史数
│ ⊕ Search │
├─────────────────────────┤
│ ☰ System │
└─────────────────────────┘
.nav-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 16px;
border-radius: var(--r-md);
font-size: 14px; color: var(--text-3);
cursor: pointer; transition: all var(--transition);
border-left: 2px solid transparent;
margin: 1px 8px;
}
.nav-item:hover { background: var(--bg-s2); color: var(--text-2); }
.nav-item.active {
background: rgba(88,166,255,0.1);
color: var(--blue);
border-left-color: var(--blue);
}
.nav-badge {
margin-left: auto;
background: var(--bg-s2);
color: var(--text-3);
font-size: 11px; font-weight: 600;
padding: 1px 6px;
border-radius: 10px;
}
3.3 Header 内容规范
┌──────────────────────────────────────────────────────────────┐
│ [≡] GraphRAG Studio │ [🔍 Search entities... ] │ [● healthy] API: 8000 │
└──────────────────────────────────────────────────────────────┘
| 区域 | 内容 | 说明 |
|---|---|---|
| 左 | [≡] 折叠按钮 + Logo 文字 |
点击折叠/展开 sidebar(220px ↔ 72px) |
| 中 | 全局搜索框 | 输入后跳转 #/search?q={input},宽度 max 400px |
| 右 | Health 指示器 + API 地址 | 绿点=healthy, 红点=error |
全局搜索框触发逻辑:
- 输入 3+ 字符 → 实时调用
GET /api/v1/search/entities?q=...&limit=5 - 下拉展示最多 5 条搜索建议(entity name + type badge)
- 按 Enter → 跳转
#/search?q={input} - 点击建议项 → 跳转
#/graph?node={node_id}
3.4 Status Bar 内容规范
[Indexing paper.pdf... Stage: Extracting entities 2/4 ████████░░ 65%] v1.0.0 ● healthy
└── 有 active job 时显示 └── 常驻
四、页面清单与详细设计
Page 1 — Dashboard (#/dashboard)
目的: 系统全局概览,快速导航到各功能,最近活动监控。
4.1.1 布局
┌──────────────────────────────────────────────────────────────┐
│ Overview [+ Upload & Index] │
├──────────┬──────────┬──────────┬────────────────────────────┤
│ 40 │ 780 │ 5 │ 50 │
│ Nodes │ Edges │ Docs │ Queries │
│ KG 节点 │ KG 边 │ 文档总数 │ 问答次数 │
├──────────┴──────────┴──────────┴────────────────────────────┤
│ System Health │
│ MinerU venv ● ok LangExtract venv ● ok │
│ DeepSeek API ● ok Storage ● ok │
├──────────────────────────────────────────────────────────────┤
│ Recent Documents [View All →] │
│ ────────────────────────────────────────────────────────── │
│ paper.pdf PDF 4p ● indexed 2026-03-05 [KG] │
│ report.docx DOCX 12p ● indexing [███░░░ 65%] [✕] │
│ slides.pptx PPTX 24p ● uploaded ─ [▶] │
├──────────────────────────────────────────────────────────────┤
│ Quick Actions │
│ [◉ Explore KG] [◇ Start Chat] [⊕ Search] [⚡ Demo] │
└──────────────────────────────────────────────────────────────┘
4.1.2 指标卡设计
.metrics-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
padding: 20px;
}
.metric-card {
background: var(--bg-s1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: 20px;
text-align: center;
}
.metric-value { font-size: 32px; font-weight: 700; color: var(--text-1); }
.metric-label { font-size: 12px; color: var(--text-3); margin-top: 4px; }
/* 悬停时用颜色区分不同指标 */
.metric-card:nth-child(1) .metric-value { color: var(--blue); } /* Nodes */
.metric-card:nth-child(2) .metric-value { color: var(--purple); } /* Edges */
.metric-card:nth-child(3) .metric-value { color: var(--green); } /* Docs */
.metric-card:nth-child(4) .metric-value { color: var(--yellow); } /* Queries */
4.1.3 API 调用时机
| 调用 | 时机 | 接口 |
|---|---|---|
| 加载指标卡 | 页面初始化 | GET /api/v1/system/stats |
| 加载 Health | 页面初始化 | GET /api/v1/health |
| 加载最近文档 | 页面初始化 | GET /api/v1/documents?page=1&page_size=5 |
| 轮询刷新 | 每 10 秒 | GET /api/v1/system/stats + GET /api/v1/health |
| 行内启动 Index | 点击 [▶] |
POST /api/v1/index/start |
| 行内 Index 进度 | 启动后每 3s | GET /api/v1/index/status/{job_id} |
4.1.4 交互逻辑
[+ Upload & Index]→ 打开 Upload Modal(同 Documents 页上传区),完成后刷新列表[KG]→ 跳转#/graph?doc_id={doc_id}[▶]→ 行内启动 indexing,进度条替换操作按钮,轮询进度[✕]→ 取消正在运行的 indexing job[Explore KG]→#/graph[Start Chat]→#/chat[Search]→#/search[Demo]→ 调用GET /api/v1/system/demo,将数据存入AppState.kg,跳转#/graph
Page 2 — Document Manager (#/documents)
目的: 文件上传、列表管理、触发/监控索引任务、查看索引结果。
4.2.1 布局
┌──────────────────────────────────────────────────────────────┐
│ Documents (5) [+ Upload Files] │
├──────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ⬆ │ │
│ │ Drag & Drop files here │ │
│ │ PDF · DOCX · DOC · PPTX · PPT · PNG · JPG · HTML │ │
│ │ Max 200MB per file │ │
│ │ [Browse Files] │ │
│ └──────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ [All ▾] [Status: All ▾] [🔍 Filter docs...] │
├──────────────────────────────────────────────────────────────┤
│ Filename Format Pages Status Date Act │
│ ──────────────────────────────────────────────────────── │
│ paper.pdf PDF 4 ●indexed 03-05 [◉][🗑]│
│ │
│ report.docx DOCX 12 ●indexed 03-04 [◉][🗑]│
│ ▼ 40 nodes · 780 edges · 4p · 45 ext · 42.1s │
│ Types: TECHNOLOGY(4) CONCEPT(36) [View KG] [Show Ext] │
│ │
│ processing.pptx PPTX 24 ●indexing [✕] │
│ Stage: Extracting entities page 2/4... │
│ ████████████░░░░░░░░░░ 45% (18s) │
│ │
│ image.png PNG 1 ●uploaded 03-03 [▶][🗑]│
│ │
│ failed.pdf PDF - ●failed 03-02 [⟳][🗑]│
│ Error: MinerU failed: timeout after 600s │
└──────────────────────────────────────────────────────────────┘
4.2.2 上传区交互
拖拽行为:
dragenter → 上传区边框变蓝 (#58a6ff),背景 rgba(88,166,255,0.05)
dragleave → 恢复默认
drop → 提取 event.dataTransfer.files,验证后开始上传
客户端预校验(drop / browse 后立即执行):
1. 扩展名检查(对照 ALLOWED_EXTENSIONS 列表)
2. 文件大小 ≤ 200MB
→ 校验失败: Toast 错误 + 文件名标红(不上传)
→ 校验通过: 开始上传流程
多文件串行处理:
files[] → 逐一执行 Upload → IndexStart → Poll 流程
同时上传时显示队列状态
4.2.3 上传 + 索引完整流程
Step 1: POST /api/v1/documents/upload (multipart/form-data)
↓ 返回 doc_id
行项目添加到列表,status: "uploaded"
Step 2: 询问确认 "Start indexing now? [Yes] [Later]"
→ Yes: 继续 Step 3
→ Later: 保持 "uploaded" 状态,显示 [▶] 按钮
Step 3: POST /api/v1/index/start { doc_id }
↓ 返回 job_id
status → "indexing",显示进度条
Step 4: 轮询 GET /api/v1/index/status/{job_id}(每 3s)
progress.parsed_pages / total_pages → 进度百分比
stage → 行内文字说明
Step 5: status=done:
GET /api/v1/index/result/{job_id} → 获取 stats
更新 status → "indexed"
展开结果摘要行(节点数/边数/耗时)
Toast: "✅ paper.pdf indexed: 40 nodes, 780 edges"
AppState.kg.loaded = false (触发 KG Explorer 重新加载)
4.2.4 索引结果展开行
▼ [展开]
40 nodes · 780 edges · 4 pages · 45 extractions · 42.1s
TECHNOLOGY(4) CONCEPT(36)
[◉ View in KG] [≡ Show Extractions]
[Show Extractions] 展开面板:
text type alignment page
────────────────────────────────────────────
GraphRAG TECHNOLOGY match_exact 0
knowledge... CONCEPT match_exact 0
MinerU TECHNOLOGY match_exact 1
...(滚动,最多显示 50 条)
Page 3 — KG Explorer (#/graph)
目的: 全屏交互式知识图谱可视化,节点筛选与详情查看。
4.3.1 布局(三栏)
┌─────────────┬───────────────────────────────────┬─────────────┐
│ FILTER │ D3 FORCE-DIRECTED GRAPH │ DETAIL │
│ (280px) │ (flex:1) │ (300px) │
│ │ ← 点击节点后出现 → │ │
│ ─ Docs ─── │ │ [× Close] │
│ [All Docs ▾]│ ●GraphRAG ─────── ●LLMs │ │
│ │ / │ \ │ ┄ GraphRAG │
│ ─ Types ── │ ● ● ● │ TECHNOLOGY│
│ ☑ TECH (4) │ │ ────────── │
│ ☑ CONC (36) │ [+ ][ -][⊡] │ Page: 0 │
│ ☑ PERS (0) │ [🔍 Search node...] │ Conf: exact │
│ ☑ ORG (0) │ │ Degree: 39 │
│ ☑ LOC (0) │ │ Central: 1.0│
│ │ │ ────────── │
│ ─ Confidence│ │ Neighbors │
│ ☑ exact │ [图例] │ (39 total) │
│ ☑ greater │ ●TECH ●CONC ●PERS ●ORG ●LOC │ ●knowledge │
│ ☑ lesser │ │ ●LLMs │
│ □ fuzzy │ │ ●MinerU │
│ │ │ [All 39 →] │
│ ─ Export ── │ │ │
│ [📷 PNG] │ │ [💬 Ask AI] │
│ [⬇ JSON] │ │ │
└─────────────┴───────────────────────────────────┴─────────────┘
4.3.2 D3 力导向图规范
节点视觉映射:
| 属性 | 映射规则 |
|---|---|
| 颜色 | entity type → 5 色(--type-tech/concept/person/org/loc) |
| 半径 | r = Math.max(4, Math.log(degree + 1) * 4) |
| 描边 | 正常: 1.5px same-color opacity 0.6;hover: 2.5px white |
| 透明度 | 正常: 0.9;非聚焦(highlight 时): 0.1 |
边视觉映射:
| 属性 | 规则 |
|---|---|
| 颜色 | #30363d |
| 透明度 | 正常 0.25;高亮节点相关边 0.8 |
| 宽度 | 1px |
Force 参数:
d3.forceSimulation(nodes)
.force('link', d3.forceLink(edges).id(d => d.id).distance(60).strength(0.3))
.force('charge', d3.forceManyBody().strength(-120))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide().radius(d => d.r + 4))
.alphaDecay(0.02)
交互事件:
node.mouseover → 显示 Tooltip(name + type + page + conf + degree)
node.click → 右侧 Detail Panel 展开
高亮该节点(r 增大 1.5x)
相连边 opacity 0.8,其余节点 opacity 0.1
canvas.click → 取消选中,Detail Panel 收起,恢复所有透明度
node.drag → pin 到固定位置(fx/fy 设置)
zoom → d3.zoom().scaleExtent([0.1, 8])
工具栏功能:
[+] → transform.k * 1.3 (zoom in)
[-] → transform.k / 1.3 (zoom out)
[⊡ Fit] → fitToView():计算节点范围,自动缩放和平移
[🔍] → search input: GET /api/v1/search/entities?q=...
找到节点 → 闪烁高亮 + 平移居中
[📷 PNG] → html2canvas 或 SVG → PNG download
[⬇ JSON] → GET /api/v1/kg/export → 触发文件下载
4.3.3 URL 参数处理
// graph.js 初始化时解析
const params = new URLSearchParams(window.location.hash.split('?')[1]);
const docFilter = params.get('doc_id'); // 按文档筛选
const nodeHighlight = params.get('node'); // 聚焦节点
if (docFilter) {
// 勾选对应 doc 的 checkbox,其余节点淡化
}
if (nodeHighlight) {
// 找到对应节点,高亮 + Detail Panel 展开 + 平移居中
}
4.3.4 API 调用时机
| 调用 | 时机 | 接口 |
|---|---|---|
| 加载全量节点 | 页面初始化(优先读缓存) | GET /api/v1/kg/nodes?page_size=200 |
| 加载全量边 | 页面初始化 | GET /api/v1/kg/edges?page_size=500 |
| 节点详情 | 点击节点 | GET /api/v1/kg/nodes/{node_id} |
| 邻居列表 | 点击节点 | GET /api/v1/kg/nodes/{node_id}/neighbors?hops=1 |
| 工具栏搜索 | 输入 2+ 字符,500ms debounce | GET /api/v1/search/entities?q=... |
性能说明: 当前 KG 为 40 节点 + 780 边,D3 可流畅渲染。若节点超过 500,启用 Canvas 渲染模式(D3 WebGL fallback)。
Page 4 — QA Chat (#/chat)
目的: 多轮 KG 问答,可视化 ReAct 推理过程,Cited Node 跳转联动。
4.4.1 布局(双栏)
┌──────────────────┬────────────────────────────────────────────┐
│ HISTORY (240px) │ CHAT AREA │
│ │ │
│ [+ New Chat] │ ── Welcome ──────────────────────────── ─│
│ │ KG Assistant │
│ ─ Today ────── │ Ask me anything about the knowledge │
│ What is GraphRAG │ graph. Try one of the suggestions below. │
│ How does MinerU │ [💡 Give me an overview of the KG] │
│ │ [💡 List all TECHNOLOGY entities] │
│ ─ Yesterday ─── │ [💡 How does GraphRAG relate to LLMs?] │
│ List all tech... │ │
│ │ ─── 2026-03-05 10:30 ────────────────── │
│ │ │
│ │ YOU 10:30 │
│ │ What is GraphRAG and how does it │
│ │ relate to knowledge graphs? │
│ │ │
│ │ KG ASSISTANT 10:30 │
│ │ GraphRAG is a knowledge graph- │
│ │ enhanced RAG system developed by │
│ │ Microsoft Research... │
│ │ │
│ │ ▶ Tool Calls (2 steps) [展开] │
│ │ │
│ │ Cited: [GraphRAG] [knowledge graphs] │
│ │ [LLMs] [retrieval-augmented...] │
│ │ ↑ 点击跳转 KG Explorer │
│ │ ⏱ 8.4s │
│ ├────────────────────────────────────────────┤
│ │ [Ask about the knowledge graph... ][▶] │
└──────────────────┴────────────────────────────────────────────┘
4.4.2 消息气泡设计
/* 用户消息(右对齐) */
.msg-user {
align-self: flex-end;
background: var(--blue);
color: #fff;
border-radius: var(--r-lg) var(--r-lg) var(--r-sm) var(--r-lg);
padding: 10px 14px;
max-width: 70%;
font-size: 14px;
}
/* AI 消息(左对齐) */
.msg-assistant {
align-self: flex-start;
background: var(--bg-s1);
border: 1px solid var(--border);
border-radius: var(--r-sm) var(--r-lg) var(--r-lg) var(--r-lg);
padding: 12px 16px;
max-width: 85%;
}
/* 消息内 Markdown 渲染 */
.msg-assistant .answer-text {
font-size: 14px; line-height: 1.7; color: var(--text-2);
/* 使用 marked.js 渲染 */
}
4.4.3 Tool Call 展开面板
▶ Tool Calls (2 steps) [展开 ▾ / 收起 ▲]
── Step 1: search_entities ─────────────────────────────
Input:
{ "query": "GraphRAG" }
Output:
Found 1 entity(ies) matching 'GraphRAG':
[TECHNOLOGY] "GraphRAG" (confidence=match_exact, page=0, id=tech_graphrag_0)
── Step 2: get_neighbors ────────────────────────────────
Input:
{ "entity_name": "GraphRAG", "hops": 1 }
Output:
Neighbors of 'GraphRAG' [TECHNOLOGY] within 1 hop(s):
Hop 1 — 39 related entities:
[CONCEPT] knowledge graphs
[TECHNOLOGY] LLMs
...
.tool-calls-panel {
background: var(--bg-s3);
border: 1px solid var(--border);
border-radius: var(--r-md);
margin: 8px 0;
font-size: 12px;
font-family: var(--font-mono);
color: var(--text-3);
overflow: hidden;
}
.tool-call-step {
padding: 10px 14px;
border-bottom: 1px solid var(--border-muted);
}
.tool-call-name { color: var(--yellow); font-weight: 600; }
.tool-call-section { color: var(--text-4); margin-top: 4px; }
4.4.4 Cited Nodes 设计
Cited: [◉ GraphRAG] [◉ knowledge graphs] [◉ LLMs] ...
↑ 点击 → window.location.hash = '#/graph?node=' + node_id
↑ hover → 小 Tooltip 显示节点 type
.cited-node-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 8px;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--bg-s2);
color: var(--blue);
font-size: 12px;
cursor: pointer;
transition: all var(--transition);
}
.cited-node-chip:hover {
background: rgba(88,166,255,0.15);
border-color: var(--blue);
}
4.4.5 Thinking 动画
<div class="thinking-indicator">
<span></span><span></span><span></span>
</div>
.thinking-indicator span {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--text-3);
display: inline-block;
animation: thinking 1.4s infinite;
}
.thinking-indicator span:nth-child(2) { animation-delay: 0.2s; }
.thinking-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes thinking { 0%,80%,100%{transform:scale(0.7)}40%{transform:scale(1)} }
4.4.6 API 调用时机
| 调用 | 时机 | 接口 |
|---|---|---|
| 发送问题 | 点击 Send / Enter | POST /api/v1/query |
| 加载历史列表 | 进入 Chat 页 | GET /api/v1/query/history?page_size=20 |
| Cited Node 悬停 | mouseover 时 | GET /api/v1/kg/nodes/{node_id}(可缓存) |
多轮对话 history 维护(前端):
// 每次发送携带完整历史
const payload = {
question: inputText,
history: AppState.chatHistory.flatMap(msg => [
{ role: 'human', content: msg.question },
{ role: 'ai', content: msg.answer }
])
};
const result = await API.post('/query', payload);
AppState.chatHistory.push(result);
Page 5 — Search (#/search)
目的: 多模式知识图谱搜索(实体关键词 / 路径 / 子图)。
4.5.1 布局
┌──────────────────────────────────────────────────────────────┐
│ Search Knowledge Graph │
│ ┌──────────────────────────────────┐ [Type: All ▾] [Search]│
│ │ 🔍 Enter entity name... │ │
│ └──────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ [Entity Search ▌] [Path Search] [Graph Search] │
├──────────────────────────┬───────────────────────────────────┤
│ Results (2) │ Preview Graph │
│ ───────────────── │ ────────────── │
│ ┌────────────────────┐ │ │
│ │ GraphRAG │ │ ●GraphRAG │
│ │ TECHNOLOGY pg.0 │ │ ╱│╲ │
│ │ Degree: 39 · exact │ │ ● ● ● │
│ │ [View KG] [Chat] │ │ ↑ D3 mini-graph │
│ ├────────────────────┤ │ (当前选中节点的1-hop邻居子图) │
│ │ GraphRAG pipeline │ │ │
│ │ CONCEPT pg.1 │ │ │
│ │ Degree: 39 · exact │ │ │
│ │ [View KG] [Chat] │ │ │
│ └────────────────────┘ │ │
└──────────────────────────┴───────────────────────────────────┘
4.5.2 Tab — Path Search
From: [🔍 GraphRAG... ▾] To: [🔍 LLMs... ▾] Hops: [3▾] [Find Path]
─── Results: 1 path found (length 1) ───
GraphRAG ──CO_OCCURS_IN──→ LLMs
─── Visualization ───
●GraphRAG ───────────────── ●LLMs
(节点颜色 = type,边标注 relation)
节点选择器设计:
点击 [🔍] → 展开搜索下拉
输入关键词 → 实时调用 GET /api/v1/search/entities?q=...&limit=10
选择节点 → 记录 node_id,显示名称
4.5.3 Tab — Graph Search
[retrieval ] [☑ Include Neighbors] [Search]
Found 3 matching nodes · 87 subgraph edges
─────────────────────────────────────
[D3 子图可视化 - 全宽,仅展示匹配节点和邻边]
─────────────────────────────────────
Matched nodes:
● retrieval-augmented generation [CONCEPT] page 0
● RAG systems [CONCEPT] page 0
● vector similarity search [CONCEPT] page 2
4.5.4 搜索框 URL 同步
用户修改搜索框 → 更新 hash query: #/search?q={query}&type={type}&tab={tab}
页面初始化 → 解析 hash query → 预填表单 → 自动触发搜索
从 Header 全局搜索跳转 → #/search?q={input}
4.5.5 API 调用时机
| 调用 | 时机 | 接口 |
|---|---|---|
| 实体搜索 | 点击 Search / Enter | GET /api/v1/search/entities?q=...&type=... |
| 结果 Preview Graph | 点击搜索结果行 | GET /api/v1/kg/nodes/{id}/neighbors?hops=1 |
| 路径搜索 | 点击 Find Path | GET /api/v1/search/path?from=...&to=... |
| 子图搜索 | 点击 Search | GET /api/v1/search/graph?q=...&include_neighbors=true |
五、响应式设计规范
5.1 断点定义
| 断点名 | 宽度范围 | 布局策略 |
|---|---|---|
| Desktop | > 1280px | 完整布局(Sidebar 220px + 内容 + Detail Panel) |
| Laptop | 1024 – 1280px | Sidebar 折叠为图标模式(72px),内容区扩展 |
| Tablet | 768 – 1024px | Sidebar 隐藏,汉堡菜单触发 Drawer,内容全屏 |
| Mobile | < 768px | 底部 Tab Bar,全屏内容,面板改为底部 Sheet |
5.2 CSS 媒体查询框架
/* ── Laptop: Sidebar 折叠 ── */
@media (max-width: 1280px) {
.app { --sidebar-w: 72px; }
.nav-label, .nav-badge, .sidebar-logo-text { display: none; }
.nav-item { justify-content: center; padding: 12px; }
}
/* ── Tablet: Sidebar 变 Drawer ── */
@media (max-width: 1024px) {
.app { grid-template-columns: 0 1fr; }
.sidebar {
position: fixed; left: 0; top: 0; bottom: 0;
width: 220px; z-index: 500;
transform: translateX(-220px);
transition: transform 0.2s ease;
box-shadow: var(--shadow-lg);
}
.sidebar.open { transform: translateX(0); }
.sidebar-overlay { display: block; } /* 点击遮罩关闭 */
}
/* ── Mobile: 底部 Tab Bar ── */
@media (max-width: 768px) {
.app { grid-template-rows: 56px 1fr 56px; }
.sidebar { display: none; }
.bottom-nav { display: flex; position: fixed; bottom: 0; ... }
/* KG Explorer 三栏 → 全屏 */
.graph-filter-panel { display: none; }
.graph-detail-panel { position: fixed; bottom: 0; left: 0; right: 0;
height: 60vh; border-radius: 16px 16px 0 0; z-index: 400; }
/* Chat 历史面板 → 顶部 Drawer */
.chat-history { position: fixed; top: 56px; left: 0; right: 0;
height: 50vh; z-index: 400; transform: translateY(-100%); }
/* 指标卡 4 列 → 2 列 */
.metrics-grid { grid-template-columns: repeat(2, 1fr); }
/* Search 结果+预览 → 上下堆叠 */
.search-results-layout { flex-direction: column; }
}
5.3 各页面移动端特殊处理
| 页面 | 桌面布局 | 移动端变化 |
|---|---|---|
| Dashboard | 4列指标卡 | 2×2 网格 |
| Documents | 表格列表 | 卡片堆叠(隐藏 Pages、Date 列) |
| KG Explorer | 三栏 | 图谱全屏,Filter 通过 FAB 触发底部 Sheet |
| Chat | 双栏 | 历史面板隐藏,顶部 [历史] 按钮触发 Drawer |
| Search | 双栏结果+预览 | 上下堆叠(预览缩小为 200px) |
六、关键交互模式规范
6.1 Toast 通知系统
位置: right: 24px; top: 72px (header 下方)
宽度: 320px
堆叠: 最多 3 条,从上往下,间距 8px
类型视觉:
✅ Success bg #1a3a22 border-left 3px #3fb950 icon ✓
⚠️ Warning bg #2d2a16 border-left 3px #d29922 icon !
❌ Error bg #3b1a1a border-left 3px #f85149 icon ✗
ℹ️ Info bg #161f2e border-left 3px #58a6ff icon i
生命周期:
出现: slide-in from right (200ms ease-out)
停留: 4000ms(hover 时暂停计时)
消失: fade-out (300ms) → 从 DOM 移除
调用方式 (components.js):
Toast.success("paper.pdf indexed: 40 nodes, 780 edges")
Toast.error("Failed to upload: file too large")
Toast.info("Loading knowledge graph...")
6.2 全局 Loading 状态
1. Header 进度条(API 请求时)
height: 2px; position: absolute; top: 0; width: 100%
颜色: var(--blue)
短请求: indeterminate animation
长请求(轮询中): 真实进度百分比
2. Skeleton Loader(列表加载时)
模拟行结构的灰色矩形,shimmer 动画
Document 列表: 3 行 skeleton rows
Entity 列表: 5 行 skeleton rows
3. 图谱 Loading
svg 中央显示: "Loading KG... (40 nodes, 780 edges)"
3 点动画
4. Chat Thinking
见 4.4.5 三点跳动动画
6.3 空状态(Empty State)
每个页面无数据时的引导设计:
KG Explorer(无 KG 数据):
┌────────────────────────────────────┐
│ ◉ (大图标) │
│ No knowledge graph yet │
│ Upload documents and start │
│ indexing to build your KG │
│ [Upload & Index →] │
└────────────────────────────────────┘
Chat(无问题历史):
欢迎页 + Suggested Prompts(见 4.4.1)
Search(无结果):
"No entities found for 'query'"
"Try: different keyword, check KG Explorer"
[Explore KG]
Documents(无文档):
直接聚焦上传区(拖拽提示更突出)
6.4 错误处理规范
| 错误类型 | 处理方式 |
|---|---|
| API code ≠ 0 | Toast.error(msg) + console.error |
| fetch 网络失败 | Toast.error("Network error. Is API server running on :8000?") + [Retry] 按钮 |
| 超时(QA > 60s) | Toast.warning("Request timeout, please try again") |
| code 3002 (KG 为空) | 页面内空状态引导,不弹 Toast |
| code 2001/3001 (不存在) | Toast.error + 刷新当前列表 |
| Indexing failed | 行内展示错误信息 + [⟳ Retry] 按钮 |
| 文件校验失败 | 文件名标红 + inline 错误说明(不弹 Toast) |
6.5 页面间联动(Cross-page Navigation)
| 触发位置 | 操作 | 目标 | 参数 |
|---|---|---|---|
| Dashboard [KG] 按钮 | 点击 | KG Explorer | #/graph?doc_id={doc_id} |
| Dashboard [Explore KG] | 点击 | KG Explorer | #/graph |
| Dashboard [Demo] | 点击 | KG Explorer | 加载 demo → #/graph |
| Documents [◉] 按钮 | 点击 | KG Explorer | #/graph?doc_id={doc_id} |
| Chat Cited Node 标签 | 点击 | KG Explorer | #/graph?node={node_id} |
| KG Detail Panel [💬] | 点击 | Chat | #/chat?q=Tell+me+about+{name} |
| Search 结果 [View KG] | 点击 | KG Explorer | #/graph?node={node_id} |
| Search 结果 [Chat] | 点击 | Chat | #/chat?q=What+is+{name} |
| Header 全局搜索 | Enter | Search | #/search?q={input} |
| Header 全局搜索 | 点击建议项 | KG Explorer | #/graph?node={node_id} |
6.6 确认对话框规范
触发场景: 删除文档、取消 indexing job
样式: 居中 Modal(360px),遮罩层 rgba(0,0,0,0.6)
┌─────────────────────────────────────┐
│ Delete document? │
│ │
│ "paper.pdf" and all its associated │
│ KG data will be permanently deleted│
│ (40 nodes, 780 edges removed) │
│ │
│ [Cancel] [Delete →] │
└─────────────────────────────────────┘
[Delete] → bg: --red,hover: #d73a2f
[Cancel] → btn-secondary
七、文件结构
7.1 新建文件清单
| 文件路径 | 说明 |
|---|---|
graphrag_pipeline/static/app/index.html |
SPA 主入口,包含 5 个 <section> 页面区域,引入所有 CSS/JS |
graphrag_pipeline/static/app/css/variables.css |
CSS 变量定义(颜色、字体、圆角、阴影) |
graphrag_pipeline/static/app/css/base.css |
Reset + 通用组件样式(btn/badge/card/modal/toast/skeleton) |
graphrag_pipeline/static/app/css/layout.css |
骨架布局(app grid)+ Sidebar + Header + Footer + 响应式 |
graphrag_pipeline/static/app/js/app.js |
路由器 + 全局 AppState + 页面初始化调度 |
graphrag_pipeline/static/app/js/api.js |
Fetch 封装 + 错误处理 + 轮询 helper |
graphrag_pipeline/static/app/js/components.js |
Toast / Modal / Progress / Skeleton / Tooltip |
graphrag_pipeline/static/app/js/pages/dashboard.js |
Dashboard 页面逻辑 |
graphrag_pipeline/static/app/js/pages/documents.js |
Documents 页面逻辑(上传 + 索引流程) |
graphrag_pipeline/static/app/js/pages/graph.js |
KG Explorer + D3 力导向图(复用现有 index.html) |
graphrag_pipeline/static/app/js/pages/chat.js |
Chat 页面逻辑(消息渲染 + 工具链 + Cited Nodes) |
graphrag_pipeline/static/app/js/pages/search.js |
Search 页面逻辑(3 个 Tab 模式) |
7.2 FastAPI 静态文件服务配置
在 api_server.py 中添加:
from fastapi.staticfiles import StaticFiles
# 挂载新 SPA(GraphRAG Studio)
app.mount("/studio", StaticFiles(directory="static/app", html=True), name="studio")
# 访问地址: http://localhost:8000/studio/
7.3 访问入口
| 地址 | 说明 |
|---|---|
http://localhost:8000/studio/ |
GraphRAG Studio(新 SPA) |
http://localhost:8000/ 或 http://localhost:5000/ |
旧 Flask KG 可视化(向后兼容) |
http://localhost:8000/docs |
FastAPI 自动生成 Swagger UI |
附:API 端点 → 页面使用矩阵
| API 端点 | Dashboard | Documents | KG Explorer | Chat | Search |
|---|---|---|---|---|---|
GET /system/stats |
✅ | — | — | — | — |
GET /health |
✅ | — | — | — | — |
GET /system/demo |
✅ | — | ✅ | — | — |
GET /system/formats |
— | ✅ | — | — | — |
POST /documents/upload |
✅ Modal | ✅ | — | — | — |
GET /documents |
✅ Recent | ✅ List | — | — | — |
GET /documents/{id} |
— | ✅ | — | — | — |
DELETE /documents/{id} |
— | ✅ | — | — | — |
POST /index/start |
✅ | ✅ | — | — | — |
GET /index/status/{id} |
✅ Poll | ✅ Poll | — | — | — |
GET /index/result/{id} |
— | ✅ | — | — | — |
DELETE /index/jobs/{id} |
✅ | ✅ | — | — | — |
GET /kg/nodes |
— | — | ✅ | — | — |
GET /kg/edges |
— | — | ✅ | — | — |
GET /kg/nodes/{id} |
— | — | ✅ Click | ✅ Hover | ✅ |
GET /kg/nodes/{id}/neighbors |
— | — | ✅ Detail | — | ✅ Preview |
GET /kg/stats |
— | — | ✅ | — | — |
GET /kg/export |
— | — | ✅ Download | — | — |
POST /query |
— | — | — | ✅ | — |
GET /query/history |
— | — | — | ✅ Sidebar | — |
POST /query/batch |
— | — | — | — | — |
GET /search/entities |
— | — | ✅ Toolbar | — | ✅ Tab1 |
GET /search/path |
— | — | — | — | ✅ Tab2 |
GET /search/graph |
— | — | — | — | ✅ Tab3 |