- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化 - Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab - 四档响应式(桌面/笔记本/平板/手机)、深浅主题 - 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测 - 真实凭据已脱敏,运行时配置在 backend/.env(不入 git) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
29 KiB
PRD.md —— PlantAI-rag-new 产品需求文档
版本 1.0 | 作者 PM | 本文件由 PM 独占写入,所有开发/测试 agent 只读
本文件是全项目需求的唯一来源。 开发 agent 按此实现,测试 agent 按此写用例。 每个功能点有唯一编号
FR-<模块>-<序号>,测试用例必须在追溯矩阵中与之一一对应。 需求有歧义时,向 PM 提出,不要自行解释。
0. 产品定位
面向植物生物信息学研究者的文献知识问答工作台。用户提出关于植物基因家族的科学问题,系统从已构建的文献知识库(约 100 篇论文、44,595 条关系断言)中检索证据,生成有引用锚定的答案,并展示完整的检索过程、证据来源与知识图谱。同时提供论文抽取任务的管理入口。
使用场景:桌面深度研究为主,手机端随时查询为重要补充(实验室、会议、田间)。
不做什么:不做多轮对话记忆(上游 max_rounds:1,每次提问独立检索);不做文献全文阅读器;不做基因家族分析流程执行(那是 PlantAI-dev 的职责)。
1. 术语表
| 术语 | 含义 |
|---|---|
| 会话 Session | 一组问答的容器,有标题、状态、时间。注意:会话是组织单位,不是上下文单位——同一会话内的多次提问互相独立 |
| 消息 Message | 会话内的一条记录,role 为 user 或 assistant |
| 块 Block | 助手消息的内容单元,判别式联合类型(text/table/code/stream/pipeline/tool/sources/graph) |
| 来源 Source | 一条支撑答案的文献证据,含标题、片段、分数、PMID/DOI 等 |
| 证据链 Chain | 知识图谱上的一条多跳推理路径,由若干 hop 组成 |
| 阶段 Stage | 上游 QA 服务推送的细粒度检索步骤(十余个) |
| 相位 Phase | 前端展示用的五阶段归并(规划/检索/证据/生成/完成) |
| 上游 Upstream | 现有的三个后端服务:QA(7920)、抽取(8000/7900)、Neo4j |
| BFF | 本项目的适配层后端,前端唯一直连对象 |
2. 全局约定
2.1 状态枚举
会话状态 idle | running | done | error
idle排队中/新建未提问 —— 空心点,ink-subtlerunning有进行中的问答 —— 青色#00D4FF脉冲点done最近一次问答成功 —— success#2ED3A0实心点error最近一次问答失败或中断 —— error#FF5C7A实心点
消息状态 streaming | done | error
相位状态 waiting | running | done(对应字形 ◌ / ● / ✓)
2.2 错误处理原则
- BFF 一切错误返回统一信封
{"ok":false,"error":{"code","message","degraded"}},HTTP 状态码语义正确。 - 前端不显示原始异常堆栈,显示
error.message的中文可读文案。 degraded:true表示「上游不可用」而非「请求有误」,UI 用降级提示(横幅/离线态)而非错误弹窗。- 失败必须可见:任何静默吞掉错误的实现视为缺陷。
2.3 文案约定
用户可见文案统一走 src/i18n/labels.js。例外:后端返回的 error.message 直接展示(后端已中文化)。
2.4 数据不伪造原则
UI 只展示后端真实返回的数据。禁止:占位假数据流入生产路径、把空结果渲染成看起来有内容的样子、用动画暗示实际不存在的流式过程(客户端揭示动画是明示的视觉设计,允许,但不得声称是 token 流)。
3. 模块与编号
| 模块 | 前缀 | 范围 |
|---|---|---|
| 应用外壳 | FR-SHELL |
三栏布局、顶栏、状态栏、折叠、背景 |
| 主题与无障碍 | FR-THEME |
深浅主题、动效降级、焦点、对比度 |
| 会话管理 | FR-SESSION |
列表、分组、增删改查、搜索 |
| 对话与问答 | FR-CHAT |
消息渲染、作曲器、流式、流水线、持久化 |
| 检索过程 | FR-RETRIEVAL |
工作台 Tab1 |
| 文献来源 | FR-SOURCE |
工作台 Tab2 |
| 提取任务 | FR-EXTRACT |
工作台 Tab3 |
| 知识图谱 | FR-KG |
工作台 Tab4 |
| 响应式 | FR-RESP |
四档断点与移动端专项 |
| 健康与降级 | FR-HEALTH |
上游探活与降级行为 |
4. FR-SHELL 应用外壳
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-SHELL-001 | 桌面三栏网格 | grid-template-columns: 260px minmax(0,1fr) 380px;grid-template-rows: 48px minmax(0,1fr) 28px;区域 "top top top" / "side chat bench" / "status status status"。页面本身不滚动(body{overflow:hidden}),滚动发生在各栏内部 |
1440×900 下三栏宽度精确;缩放窗口不出现横向滚动条 |
| FR-SHELL-002 | 顶栏 | 高 48px。左:9px 渐变发光圆点 + 「海古纪 PlantAI」渐变文字(background-clip:text)。中:当前会话标题(超长省略)+ 状态徽章。右:主题切换按钮。背景 color-mix(in srgb, var(--pa-canvas) 88%, transparent) + backdrop-filter: blur(14px),底部 1px hairline |
无会话时中部显示「就绪」徽章;标题超长省略而非换行 |
| FR-SHELL-003 | 底部状态栏 | 高 28px。左:4 个 7px agent 状态点(规划/检索/证据/生成),与流水线相位联动。右:PlantAI RAG v0.1 + mono 的 QA 版本号 + 会话数 + 上游健康标记 |
问答进行中四点按相位依次点亮;QA 版本号来自 /api/qa/info |
| FR-SHELL-004 | 左栏折叠 | 折叠后宽 64px 图标轨,显示 3 个 34×34 图标按钮(会话/图谱/设置),当前项左侧 2px 渐变竖条。折叠状态写入 localStorage pa-side-collapsed |
折叠/展开过渡 300ms ease-out;刷新后保持 |
| FR-SHELL-005 | 右栏折叠 | 折叠后保留 28px 手柄条(折叠 ≠ 隐藏),点击手柄重新展开。状态写入 localStorage pa-bench-collapsed |
折叠后手柄可见可点;刷新后保持 |
| FR-SHELL-006 | Aurora 背景 | 固定层,z-index:0,pointer-events:none。两个 560/620px 渐变光斑,blur(120px),aurora-drift 16s 往复;叠加 48px 点阵,顶部渐隐遮罩。浅色主题下 --pa-aurora-opacity:.08 |
不遮挡任何交互;浅色下明显衰减 |
| FR-SHELL-007 | 首屏初始化 | 顺序:读 localStorage 主题并写 documentElement.dataset.theme(必须在挂载前,避免闪白)→ 挂载应用 → 并行请求会话列表与 /api/health、/api/qa/info → 无会话则显示欢迎空态 |
深色主题下刷新无白屏闪烁;接口失败不阻塞渲染 |
5. FR-THEME 主题与无障碍
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-THEME-001 | 双主题等价 | 深色为默认。浅色不是降级稿,是同一套语义的另一组令牌值。所有组件必须在两套主题下都正确 | 逐组件走查两套主题无失色、无对比度不足 |
| FR-THEME-002 | 切换与持久化 | 顶栏按钮切换,写 localStorage['pa-theme'],同步 documentElement.dataset.theme。图标日/月互换并 rotate 180°(400ms) |
切换即时生效;刷新保持 |
| FR-THEME-003 | 浅色 glow 衰减 | 浅色下所有辉光乘 --pa-glow-opacity:.35——浅底上不衰减会糊 |
浅色下按钮悬停辉光明显弱于深色 |
| FR-THEME-004 | 动效降级 | @media (prefers-reduced-motion: reduce) 下关闭全部 animation/transition;状态改由静态符号表达(实心/空心/✓/✕),不依赖运动传达信息 |
开启系统「减少动态效果」后无任何动画,且状态仍可辨认 |
| FR-THEME-005 | 焦点与键盘 | :focus-visible 输出 2px rgba(94,106,210,.5) 焦点环,offset 1px。所有交互元素键盘可达,Tab 顺序符合视觉顺序 |
纯键盘可完成:新建会话→输入→发送→切换 tab |
| FR-THEME-006 | 对比度 | 深色 ink/canvas ≈15:1,ink-muted/canvas ≥4.5:1;浅色同标准 | 抽检主要文本对比度达标 |
6. FR-SESSION 会话管理
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-SESSION-001 | 列表与分组 | 按 updated_at 倒序,分「今天 / 昨天 / 更早」三组,组标签 11px/500/字距0.6px。空列表显示引导文案 |
跨零点后分组正确迁移 |
| FR-SESSION-002 | 状态点四态 | 见 §2.1。running 脉冲动画,其余静态 |
四种状态视觉可区分,且不仅靠颜色(形状:空心/实心 + 动/静) |
| FR-SESSION-003 | 新建会话 | 顶部渐变按钮(40px 高)。行为:调 POST /api/sessions 创建 → 切为活动会话 → 对话区显示欢迎空态。不预先落一条空消息 |
连点两次产生两个独立会话,不重复 |
| FR-SESSION-004 | 选择与加载 | 点击会话 → GET /api/sessions/{id} 拉取消息 → 渲染历史。加载中显示骨架屏。切换会话时若有进行中的流,中断该流并把原会话置 error(标注「已中断」) |
历史消息含表格/图谱块能正确重渲染 |
| FR-SESSION-005 | 搜索过滤 | 搜索框对 title 做不区分大小写的子串匹配,实时过滤(前端过滤,不发请求)。无匹配显示「无匹配会话」 |
输入中文/英文均可过滤;清空恢复全量 |
| FR-SESSION-006 | 重命名 | 双击标题或右键菜单进入编辑,回车提交 PATCH /api/sessions/{id},Esc 取消。空标题不允许提交 |
重命名后列表与顶栏标题同步更新 |
| FR-SESSION-007 | 删除 | 需二次确认。确认后 DELETE /api/sessions/{id}(级联删消息)。若删的是当前会话,切到列表中最新一个;列表空则显示欢迎空态 |
删除后刷新不再出现;消息一并清除 |
| FR-SESSION-008 | 标题自动生成 | 新会话首次提问后,用问题前 30 字符(超出加省略号)作为标题,调 PATCH 落库。用户手动改过标题的会话(meta.title_manual=true)不再自动覆盖 |
首问后标题自动变化;手动改名后再提问不被覆盖 |
| FR-SESSION-009 | 活动态高亮 | 左侧 2px 渐变竖条 + color-mix(in srgb, var(--pa-g-to) 8%, transparent) 底 + 22% 内描边 |
活动项一眼可辨,与悬停态区分 |
7. FR-CHAT 对话与问答(核心模块)
7.1 基础渲染
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-CHAT-001 | 欢迎空态 | 消息为空时显示:46px 渐变菱形(✧)+ 渐变标题「海古纪 PlantAI 知识工作台」+ 副标题 + 3 张建议卡(示例问题)。点击建议卡直接以该文本提问 | 卡片悬停上浮 2px + 青色微光;点击即发起真实提问 |
| FR-CHAT-002 | 用户消息 | 右对齐,最大宽 70%,渐变底(深色 135deg,#2A2A3E,#1C1C2A;浅色 #E8ECFF,#F4FBFF)+ hairline-strong 边,r12,padding 10/15px |
长文本正确换行不溢出 |
| FR-CHAT-003 | 助手消息 | 无底色全文(Linear 式),左缘 2px 透明竖线悬停变渐变。头部:24px 渐变头像(✦)+ 「PlantAI 智能体」+ 时间。内容按块联合顺序渲染 | 多块混排顺序正确 |
| FR-CHAT-004 | Markdown 渲染 | markdown-it 解析 → DOMPurify 消毒 → v-html。表格加 md-table 类:thead 用 2px 渐变下边框,数据格 mono(表头为「说明」的列除外,保持比例字体);代码块 code-bg + hairline + mono 12.5px |
LLM 输出的表格/列表/加粗正确渲染;注入脚本被消毒 |
| FR-CHAT-014 | 自动滚动 | 新消息或流式更新时滚到底。例外:用户手动向上滚动后暂停自动滚动,并显示「回到最新」按钮;点击或用户滚回底部后恢复 | 阅读历史时不被强制拽到底 |
| FR-CHAT-015 | 内联引用 | 答案文本中的引用标记渲染为可点 chip,点击滚动定位到工作台「文献来源」中对应条目并高亮 2s | 引用 chip 与来源列表编号一致 |
7.2 作曲器
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-CHAT-005 | 输入框 | r16 surface-1 卡片,:focus-within 渐变描边 + 青色辉光。textarea 无边框透明,14px/1.6,min-height 46px,max-height 160px 后内部滚动。占位符提示示例问题 |
多行输入自动增高至上限后滚动 |
| FR-CHAT-006 | 三种模式 | pill 切换,参数集定义在 config/modes.js:问答 {use_llm:true} → /api/qa/chat/stream方案 {use_llm:true, native_chain_enabled:true, native_chain_max_hops:3, native_chain_top_k:5} → 同上,深证据链检索 {use_llm:false} → /api/qa/search,只出来源不出答案激活态渐变底 + 白字 + 青光 |
三种模式发出的请求体字段正确;检索模式不产生答案块 |
| FR-CHAT-007 | 发送与快捷键 | Enter 换行,⌘/Ctrl+Enter 发送。发送按钮渐变 pill,空输入或忙碌时 disabled(opacity .45)。底部提示文案说明快捷键 |
快捷键在中英文输入法下均生效;空输入不可发送 |
| FR-CHAT-011 | 停止/中断 | 流式进行中,发送按钮变为「停止」。点击 → AbortController.abort() → 关闭 SSE → 当前消息置 error 并标注「已停止」→ 已收到的块保留不丢 |
停止后 BFF 侧上游连接也关闭(不留悬挂请求) |
7.3 流式问答(最复杂的部分,详细说明)
FR-CHAT-008 流式问答主流程
业务逻辑,按时序:
- 用户发送 → 前端立即在界面追加用户消息气泡 →
POST /api/sessions/{id}/messages落库(role:user) - 会话状态置
running,左栏状态点开始脉冲,底栏 agent 点复位 - 前端追加一条助手消息骨架(
status:streaming),内含一个pipeline块,五相位全为waiting fetch+ReadableStream打开POST /api/qa/chat/stream(不能用 EventSource——请求是带 JSON body 的 POST)- 逐帧消费:
event: progress→ 取phase字段(BFF 已计算好,前端不解析上游 stage 名)→ 更新pipeline块对应相位为running,此前相位置done;同时把该事件的details追加为工作台「检索过程」的一张阶段卡event: complete→ payload 即完整归一化响应 → 用answer生成text块(走揭示动画)、sources生成sources块、图谱数据生成graph块;pipeline全部置doneevent: error→ 消息置error,展示error.message,保留已收到的块event: timeout→ 同 error,文案为「检索超时」event: raw→ 记入raw,不渲染:注释帧(keepalive)→ 忽略
- 每 ~3s 调
PATCH /api/sessions/{id}/messages/{mid}做检查点持久化 - 终态:消息置
done/error落库,会话状态同步,底栏 agent 点定格
边界与异常:
- 上游最长 240s:期间必须持续有视觉反馈(相位卡 + shimmer + 已耗时计数),不得让用户面对静止界面
- 网络中断(移动端切网常见):
fetchreject → 置error,文案「连接中断」,提供「重试」按钮。不自动重连——每次 LLM 调用昂贵,自动重试会放大成本 - 页面刷新中途:重载后显示最后检查点内容 + 「已中断」标注,会话状态
error - 同一会话并发提问:忙碌时发送按钮 disabled,从交互上杜绝
FR-CHAT-009 五阶段流水线卡
| 相位 | 颜色令牌 | 上游 stage(由 BFF 归并) |
|---|---|---|
| 规划 | --pa-t-planning #C0A8DD |
understanding_started、entities_identified、related_search_started |
| 检索 | --pa-t-execution #9FBBE0 |
concepts_found、subquestion_retrieved、agent_review_started、subquestion_retry_started |
| 证据 | --pa-t-knowledge #9FC9A2 |
evidence_accepted、evidence_review_completed |
| 生成 | --pa-t-validation #DFA88F |
answer_generation_started |
| 完成 | --pa-t-done #C08532 |
completed |
渲染:纵向时间线,每相位 = 16px 状态点(✓ done / ● running / ◌ waiting)+ 2px 连接线 + 相位名 + 描述(取自最近一次该相位事件的摘要)+ 右侧状态文字;running 相位额外显示 11px 旋转 spinner。卡片头部「⚙ 检索流水线」+ 整体状态徽章(语义色)。
关键约束:相位色只回答「哪一步在做什么」,语义色只回答「整体成败」,两者不得混用。
FR-CHAT-010 答案揭示动画
上游不做 token 级流式,完整答案在 complete 一次到达。前端以 30ms/字符的速度揭示,尾部显示 8×15px 青色闪烁光标(blink 1s),下方显示 3px shimmer 进度线。揭示期间用户可点击「跳过」立即显示全文。揭示结束替换为最终 text 块。
边界:答案超过 2000 字符时,揭示速度自动加快(总时长上限 8s),避免长答案等待过久。
FR-CHAT-012 错误与重试
- 上游返回 HTTP≥400 且
detail.error_code == "invalid_llm_citation":BFF 已自动重试一次;若仍失败,前端展示「引用校验失败,请重试」并提供重试按钮 - 其他错误:展示
error.message,提供重试按钮 - 重试 = 用相同问题与模式重新发起,在同一条助手消息位置替换,不新增用户消息
FR-CHAT-013 消息持久化与恢复
- 块联合完整落库(
stream块为瞬态,不落库,落库时已转为text) raw字段保存完整归一化响应,供排查与重渲染- 刷新后从
GET /api/sessions/{id}恢复全部块,包括表格、来源、图谱
8. FR-RETRIEVAL 检索过程(工作台 Tab1)
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-RETRIEVAL-001 | 阶段卡列表 | 每个 SSE progress 事件生成一张卡,沿用原型「工具调用卡」视觉:折叠头(相位色图标 + 阶段中文名 + 状态点 + 耗时 mono)+ 展开体(details 以 mono 参数块呈现 + 摘要行) |
一次问答后卡片数量与收到的 progress 事件数一致 |
| FR-RETRIEVAL-002 | 相位筛选 | chip:全部/规划/检索/证据/生成。激活态实心 #5E6AD2 底 + 白字 |
筛选后仅显示对应相位卡片 |
| FR-RETRIEVAL-003 | 展开折叠 | 点击头部切换,chevron 旋转 90°,展开体 fade-slide 200ms。运行中的卡默认展开,完成后自动折叠 |
展开状态互相独立 |
| FR-RETRIEVAL-004 | 运行中效果 | 运行中卡片头部 shimmer 扫光 1.8s 循环 + 状态点脉冲 | 完成后动效停止 |
| FR-RETRIEVAL-005 | 检索计划 | 若响应含 retrieval_plan,在列表顶部以只读信息块展示;另展示 /api/qa/info 的 12 段 pipeline 描述作为静态说明 |
无 plan 时该区域不显示(不留空框) |
| FR-RETRIEVAL-006 | 空态 | 未提问时显示「发起提问后,这里会实时展示检索过程」 | 空态不显示假卡片 |
9. FR-SOURCE 文献来源(工作台 Tab2)
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-SOURCE-001 | 来源列表 | 沿用原型「产物文件」的 mono 列表视觉。每条:序号 + 标题(超长省略)+ mono 元信息行(PMID:xxx / DOI:xxx / 分数)+ 证据片段(默认 2 行截断,点击展开) |
条目数与 source_count 一致 |
| FR-SOURCE-002 | 字段展示 | 展示归一化后的 title / snippet / score / pmid / pmcid / doi / section / support_count。缺失字段不显示占位符,不显示 undefined/null |
各字段缺失组合下渲染正常 |
| FR-SOURCE-003 | 外链跳转 | 链接优先级:url > doi(拼 https://doi.org/{doi})> pmid(拼 PubMed)> pmcid(拼 PMC)。均无则不显示链接图标。新标签页打开,带 rel="noopener" |
点击真实跳转到正确文献页 |
| FR-SOURCE-004 | 证据链 | chains 按 chain_id 分组,每组可展开为逐跳列表:源实体 —[关系]→ 目标实体,每跳附 paper_id/PMID。跳数以徽章显示 |
多跳链条展开顺序正确 |
| FR-SOURCE-005 | 会话级聚合 | 顶部切换「本次回答 / 本会话全部」。后者调 GET /api/sessions/{id}/sources,按 paper_id 去重并累计出现次数 |
去重正确,计数准确 |
| FR-SOURCE-006 | 空态 | 无来源时显示「本次检索未命中文献」,不显示空列表框 | 空结果不误导为加载中 |
10. FR-EXTRACT 提取任务(工作台 Tab3)
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-EXTRACT-001 | 任务列表 | GET /api/extraction/jobs。每条沿用原型进度条视觉:任务名 + mono 百分比 + 6px 轨道 + 渐变填充 + sweep 扫光;100% 转为纯 success 色且停止扫光。上游无 progress 数值,百分比由 BFF 按 stage 派生(queued=5 / mineru=35 / llm=75 / completed=100,failed 显示失败态不显示进度条) |
列表与上游一致;派生数值正确;failed 任务呈现为错误态 |
| FR-EXTRACT-002 | 任务详情 | 点击展开 GET /api/extraction/jobs/{id},展示状态、message(中文进度描述)、统计字段(stage/species/figure_table 计数)、plan(仅 completed 有,未完成置 null 不显示) |
详情字段与上游一致;未完成任务不显示 plan 且不报错 |
| FR-EXTRACT-003 | 运行日志 | GET /api/extraction/run-log,mono 等宽展示,最大高度内滚动,自动贴底 |
长日志不撑破布局 |
| FR-EXTRACT-004 | 论文上传 | 作曲器区域附件按钮 → 选 PDF → POST /api/extraction/upload(multipart)→ 显示上传进度 → 成功返回 job_id 并刷新列表。限制:仅 PDF,单文件 ≤50MB。上游当前环境 MinerU 二进制路径缺失会导致新任务 failed(历史 27 个 failed 任务同因)——若发生,展示上游 message 的明确错误文案 |
非 PDF 被拒;超限被拒;错误文案可读 |
| FR-EXTRACT-005 | 轮询刷新 | 存在 running 状态任务时每 5s 轮询列表;全部终态则停止轮询。切到其他 tab 时暂停轮询 |
无进行中任务时不空转 |
| FR-EXTRACT-006 | 离线降级 | 抽取上游不可用时显示离线提示与重试按钮,不影响其他 tab | 8000/7900 全停时该 tab 优雅降级 |
11. FR-KG 知识图谱(工作台 Tab4)
双数据源策略(重要):
- 答案图谱:来自当前消息的
graph块(由 QA 响应的knowledge_graph/native_graph_chains归一化而来)。永远可用,Neo4j 挂了也不受影响。 - 自由浏览:来自
GET /api/kg/*,BFF 用 Bolt 直连 Neo4j。上游/api/v1/kg/*已损坏(指向未运行的 7687),不可依赖。
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-KG-001 | 答案图谱渲染 | 手写 SVG 力导向布局。节点圆点 + 标签,边为渐变线(#5E6AD2→#00D4FF)带关系名。节点数 >50 时自动降级为列表视图并提示 |
图谱与答案引用的实体一致 |
| FR-KG-002 | 节点着色与图例 | 按实体 type 着色,用品牌色 + 辅助紫 + 五相位色构成调色板,底部图例。同一 type 在会话内颜色稳定 |
图例与节点颜色对应 |
| FR-KG-003 | 节点详情 | 点击节点 → 侧滑详情面板,展示实体属性;若 Neo4j 可用,额外调 GET /api/kg/entities/{name} 拉全量属性 |
Neo4j 不可用时仍能显示消息内已有属性 |
| FR-KG-004 | 实体搜索 | 搜索框 → GET /api/kg/entities?q= → 按 name_norm 精确/前缀匹配(走 name_norm 索引,0.1s 内返回)→ 选中渲染其邻域。注意上游无全文索引,搜索是精确/前缀匹配,非模糊全文检索 |
中英文实体名(精确或前缀)均可搜到;响应 <1s |
| FR-KG-005 | 邻域展开 | GET /api/kg/entities/{name}/neighborhood?depth=&limit=。深度选择 1/2,默认 1(两跳实测约 13s,需提示较慢)。节点上限 200,每跳按 paper_count/support_count 降序截断防 hub 吃满预算 |
深度切换生效;超限有提示不卡死 |
| FR-KG-006 | 缩放与平移 | 滚轮/双指缩放(0.3×–3×),拖拽平移,双击复位。触摸端必须支持双指缩放与单指拖拽 | 手机上可流畅缩放平移 |
| FR-KG-007 | 离线降级 | Neo4j 不可用时:搜索与邻域功能禁用并提示离线;答案图谱照常渲染(这是双数据源的意义) | 断开 Neo4j 后 tab 不白屏 |
| FR-KG-008 | 空态 | 本次回答无图谱数据时提示「本次检索未产生图谱关系」,并引导使用实体搜索 | 空态不显示空画布 |
12. FR-RESP 响应式(手机为一等公民)
12.1 断点
| 编号 | 档位 | 宽度 | 布局规则 |
|---|---|---|---|
| FR-RESP-001 | Desktop | ≥1280px | 完整三栏 260/1fr/380 |
| FR-RESP-002 | Laptop | 1024–1279px | 三栏保留;左栏自动折叠为 64px 轨;右栏收窄至 340px |
| FR-RESP-003 | Tablet | 768–1023px | 对话单栏铺满;左栏改左抽屉(scrim 遮罩 + 滑入),顶栏加汉堡按钮;右栏改右抽屉,顶栏加工作台按钮并显示来源数角标 |
| FR-RESP-004 | Mobile | <768px | 单栏;顶栏 = 汉堡 + 标题 + 工作台按钮;对话铺满;作曲器吸底;工作台为全屏覆盖层 + 顶部 tab 条;底部状态栏隐藏,四个 agent 点合并为顶栏一枚聚合状态点 |
抽屉与覆盖层动效沿用 ease-out 300ms;打开时 scrim 淡入,点击 scrim 或按 Esc 关闭。
12.2 移动端专项
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-RESP-005 | viewport 与安全区 | <meta viewport ... viewport-fit=cover>;作曲器 padding-bottom: env(safe-area-inset-bottom);顶栏 env(safe-area-inset-top) |
iPhone 刘海/Home 条不遮挡内容 |
| FR-RESP-006 | iOS 输入放大规避 | <768px 时 textarea font-size:16px(低于 16px 必触发 iOS 自动缩放)。禁止用 maximum-scale=1 规避——伤害无障碍 |
iPhone Safari 聚焦输入框页面不放大 |
| FR-RESP-007 | 悬停态触摸等价 | 所有 hover 样式包在 @media (hover: hover) 内。触摸设备下:来源外链图标常驻可见、助手消息左缘线常驻、会话条目去掉悬停底色改用点击反馈 |
手机上无「需要悬停才出现」的功能 |
| FR-RESP-008 | 触控目标 | <768px 下会话条目、bench tab、模式 pill、图标按钮 min-height:44px(iOS HIG 标准,高于 DESIGN.md 的 36px) |
手机上不误触 |
| FR-RESP-009 | 视口高度 | 用 100dvh,保留 100vh 回退,规避移动浏览器地址栏伸缩导致的高度跳变 |
滚动时布局不跳动 |
| FR-RESP-010 | 虚拟键盘 | 监听 visualViewport 的 resize:键盘弹出时压缩消息区高度并保持贴底,作曲器始终可见 |
键盘弹出不遮挡输入框 |
| FR-RESP-011 | 表格横向滚动 | <768px 下答案内 md-table 包一层横向滚动容器,右侧加渐隐提示。不压缩字号(可读性优先) |
宽表格可横滑查看完整内容 |
| FR-RESP-012 | 移动端性能降级 | <768px 下 aurora 模糊半径降低或 --pa-aurora-opacity:0;图谱节点数上限降至 30 |
中端手机滚动流畅无卡顿 |
| FR-RESP-013 | 手势(可选) | 左缘右滑开会话抽屉,右滑关工作台覆盖层。不作为验收门槛 | 实现则可用,不实现不算缺陷 |
13. FR-HEALTH 健康与降级
| 编号 | 功能点 | 业务逻辑与边界 | 验收要点 |
|---|---|---|---|
| FR-HEALTH-001 | 健康轮询 | 挂载后请求 GET /api/health,此后每 30s 一次。结果写入 store,驱动状态栏标记与各降级态。轮询失败不弹窗,仅标记 |
上游恢复后 30s 内自动解禁 |
| FR-HEALTH-002 | QA 不可用 | /api/qa/* 返 503 degraded:作曲器禁用并显示「知识问答服务当前不可用」,历史会话仍可浏览 |
停掉 7920 后 UI 明确提示而非静默失败 |
| FR-HEALTH-003 | Neo4j 不可用 | 图谱 tab 搜索/邻域禁用并提示离线;答案图谱不受影响 | 见 FR-KG-007 |
| FR-HEALTH-004 | 抽取不可用 | 见 FR-EXTRACT-006 | 仅该 tab 降级 |
| FR-HEALTH-005 | 网络中断 | 流式中断时提示「连接中断」+ 重试按钮;不自动重连(避免重复触发昂贵 LLM 调用) | 手机切换网络后有明确提示 |
14. 非功能要求
| 项 | 要求 |
|---|---|
| 首屏可交互 | 桌面 <2s(本地网络),手机 <3s |
| 长任务反馈 | QA 最长 240s,期间必须持续有视觉反馈,无静止超过 5s 的状态 |
| 数据安全 | Neo4j 凭据只存在于 BFF 配置,绝不下发到浏览器;LLM 输出必须经 DOMPurify 消毒后才 v-html |
| 离线容忍 | 前端零外网请求(无 Google Fonts、无 CDN),符合 DESIGN.md 禁令 |
| 浏览器支持 | Safari 16+(iOS)、Chrome 110+、Firefox 110+ |
15. FR 索引(供追溯矩阵使用)
SHELL: 001–007(7)|THEME: 001–006(6)|SESSION: 001–009(9)|CHAT: 001–015(15)|RETRIEVAL: 001–006(6)|SOURCE: 001–006(6)|EXTRACT: 001–006(6)|KG: 001–008(8)|RESP: 001–013(13)|HEALTH: 001–005(5)
合计 81 个功能点。 测试用例追溯矩阵必须覆盖全部 81 项(FR-RESP-013 标注为可选,仍需有用例但允许标记为 N/A)。