fix(bff): 断流崩溃根治——SSE 取消链模块明文化 + 团队文档归档

客户端中途断开流时 Cython 协程 __Pyx_Coroutine_SendEx 断言 abort 整个 BFF。
证据链:3.2.8 崩 db.c / 3.3.0 仍崩(升级无效) / db 明文后迁移 sse_relay.c,
证明取消链上任意 Cython 协程均可触发。最终把 db.py/qa.py/sse_relay.py
加入编译白名单(明文),隔离实例断流探针 10/10 存活,生产同构建实测通过。
代价:3 模块失去编译硬化(无凭据内容),编译模块 30->27。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jerry
2026-09-03 21:13:56 +08:00
co-authored by Claude Fable 5
parent 5888e1d5c1
commit 8467dec3a8
12 changed files with 734 additions and 2 deletions
+41
View File
@@ -0,0 +1,41 @@
===== 服务器关键环境事实(doc_writer 实施团队共享上下文,2026-09-03 实测) =====
【路径】
- 服务器仓库: /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new (git main,当前干净,HEAD aab24d2)
- 部署根: /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/(sdk/ + ui/,另有 legacy-rag/ qa/ methodology/)
- venv: /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python(py3.12.13)
- UI 源码: /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/ui/(vite dev 5180,vite.config proxy 8090;生产 dist 由 TL 构建同步)
【端口】8090 BFF / 8091 QA(金标,零改动)/ 8092-8093 legacy RAG / 8094 vstore(新) / 8099 mock / 5180 vite dev
【版本】venv 已装:python-docx 1.1.2、pymupdf 1.28.2、faiss-cpu 1.14.3、cryptography 50.0.1、markdown-it-py 4.2.0、PyYAML、httpx、numpy;无 pandoc/libreoffice
【LLM/嵌入】ollama 在跑:bge-m3:567m(1024 维,0.37s/条,QA 现役同款);qwen3:8b-q4_K_M 本机可用(保密档 LLM)
【字体】/usr/share/fonts/opentype/noto/NotoSansCJK-*.ttc、NotoSerifCJK-*.ttc、truetype/wps-office/(PDF 必须用真实字体文件,禁用 PyMuPDF 内置 CJK)
===== 关键代码事实 =====
- BFF 是 Cython 编译部署:db.py/services/ 编译为 .so;main.py/config.py/schemas.py 是明文白名单(pydantic 模型只能放 schemas.py;服务层只用 dataclass/dict)
- 测试命名先例:sdk/app/services/test_*.py、sdk/app/routers/test_*.py(新增照此)
- PipelineCard.vue:pc-head 含 pc-title + 状态 badge;块结构 {type:pipeline, status, steps:[{name,state,desc,phase}]};PHASE_COLOR_VAR 键:planning/retrieval/evidence/generation/done(在 ui/src/i18n/labels.js:24-30)
- BenchPanel.vue:tabs 数组 + bench.activeTab===key 的 v-if pane 链(retrieval/sources/extract/kg);bench.js setActiveTab(key) 任意 key 可用
- 前端已有 markdown-it 14.1.0 + dompurify 3.2.3(渲染/消毒现成);上传先例:extraction multipart;http.js 600s 兜底
- 左栏展开/收起按钮样式:ui/src/styles/base.css:697-711 .side-handle-btn(26×26 圆角、hairline 描边、surface-1 底、跨左栏边缘半嵌、垂直居中)
- BFF 重启:bash /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/deploy/bff_restart.sh(setsid 脱离 SSH,日志 /tmp/plantai-bff.log)
- vstore 将新增 deploy/vstore_restart.sh(仿 bff_restart.sh:uvicorn vstore.api:app --port 8094)
- 备份目录先例:/home/xzq/backups/<name>-<TS>/
===== 团队纪律(所有 agent 必须遵守) =====
1. 所有改码只在服务器仓库 /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new(经 ssh xzq@8.163.92.8 执行)
2. 开发 agent 只动自己模块的文件;不越界改其他模块
3. 零改动:QA 树(8091)、ui/src/stores/modes.js、ui/src/i18n/labels.js、ui/src/stores/bench.js(store)、ui/src/api/qa.js、ui/src/sse/parser.js;capabilities.py 零改动
4. 不 push git、不做 commit/tag(由 TL 统一决定)
5. 不改服务器运行中的生产进程;不自行部署(部署/重启由 TL 执行)
6. 不自行跑 ssh、不连生产端口探测(连 curl 127.0.0.1:8090/8091 也不行——别动运行中的服务)
7. 单元测试只跑不依赖生产服务/网络的(可用 mock/fixture/tmpdir);pytest 入口由 TL 统一执行
8. 每个 agent 有独立工作文件目录(见各任务书),互不写入
9. 明文安全:不落未加密的模版/附件内容到仓库或日志;测试样例可用占位文本
10. 产出报告写在本任务书指定的报告文件
===== 需要改代码前必读的仓库文件 =====
- sdk/app/config.py、sdk/app/schemas.py、sdk/app/db.py、sdk/app/main.py(P2 必读全文)
- sdk/app/routers/qa.py(错误信封格式与 multipart 先例)、sdk/app/routers/extraction.py 或类似(上传先例)
- ui/src/stores/chat.js、ui/src/components/chat/Composer.vue、PipelineCard.vue、AssistantMessage.vue、ui/src/components/bench/BenchPanel.vue、ui/src/components/layout/AppShell.vue、ui/src/styles/base.css(P3 必读全文)
- tooling/compile/compile_sdk.sh(编译清单机制——P2 若增删编译白名单文件需 TL 确认)
+25
View File
@@ -0,0 +1,25 @@
# P5 验收方案(Team Leader 亲自执行,R2 100% PASS 后开始)
## 验收范围(计划「验证」章节 1-5 全部)
1. 全量 pytest(sdk 域 + 域 2)——R2 已含,TL 复核数字
2. curl 端到端全链(含保密断言/路径配置/宕机演练)
3. 意图 30 条前后端双跑——R2 已含,TL 复核
4. 浏览器人工验收(TL 亲自,经 SSH 隧道):
- 拖文件进聊天框 → chips → 自然语言写文档 → 骨架卡「文档生成」徽标 → 自动切「文档」TAB → 预览渲染(标题/表格/列表)→ 就地编辑保存刷新仍在 → prompt「第2段改正式一点」→ 预览更新 → 导出 PDF/DOCX → 浏览器自动下载且文件可打开
- QA 流程开启能力时 pipeline 卡显示能力徽标
- 右栏拖宽生效 + 宽度持久化 + 手柄与左栏一致(跨边缘半嵌/箭头镜像)
- 三模式问答/停止按钮/多会话回归
5. 批量导入压测(R2 DC-PERF 已覆盖,TL 复核数字)
## 隧道(TL 本机 Mac)
- ssh -N -L 15180:127.0.0.1:5180 -L 15090:127.0.0.1:8090 xzq@8.163.92.8
- UI: http://localhost:15180(vite dev 最新前端)
## 验收留痕
- 验收结论写入 tests/docgen/test_case_report.md 尾部「最终验收(TL)」区
- 附:验收日期/执行人/各项 PASS|FAIL/已知问题(断流崩溃既有问题不重复展开)
## 注意
- 浏览器验收会真实调用 deepseek(生成耗时 2-6 分钟),选一个短指令(如「帮我写一份简短的测试通知」)控制时长
- 下载断言:浏览器下载目录出现文件 + python-docx/pymupdf 打开验证(TL 在服务器端开下载产物)
- 停止按钮测试注意:若触发断流崩溃(既有问题),按既有口径重启恢复并记录
+50
View File
@@ -0,0 +1,50 @@
# 任务书:开发 agent-1 —— P1 后端服务层(vstore 独立包 + BFF services/docgen)
## 前置必读
1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守)
2. 本任务书 + 模块职责边界
## 职责范围(只改这些文件,不越界)
A. 新增 vstore 独立包(仓库根 /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/vstore/):
- pyproject.toml(依赖:fastapi/uvicorn/httpx/cryptography/faiss-cpu/numpy/python-docx/pymupdf/PyYAML,版本对齐 venv 已装)
- vstore/settings.py —— VstorePaths dataclass + 限额常量(MAX_ATTACH_COUNT=5、MAX_ATTACH_BYTES=20MB、MAX_ATTACH_CHARS=60000、MAX_SECTIONS=12、MAX_OUTLINE_CHUNKS=3);env 键:VSTORE_KEY(Fernet key,必填)、VSTORE_ROOT、VSTORE_VAULT_PATH、VSTORE_INDEX_PATH(可覆盖:本地路径或 http(s):// URL)、VSTORE_EMBED_URL(默认 http://127.0.0.1:11434)、VSTORE_EMBED_MODEL(默认 bge-m3:567m)
- vstore/crypto.py —— Fernet 封装 encrypt_bytes/decrypt_bytes;.enc 落盘;secure_delete
- vstore/parsing.py —— extract_docx->{outline, paragraphs, tables, style_hints}、extract_pdf(pymupdf ≤300页)、extract_text_file(utf-8→gb18030)、outline_to_markdown、outline_to_embedding_chunks(>7500字符按节切块≤3)
- vstore/embed.py —— embed_batch(texts):httpx 并发≤8 调 ollama /api/embed(url/model 由 settings)
- vstore/backend.py —— parse_backend(spec)->LocalBackend|RemoteBackend:含 :// 且 scheme http/https → RemoteBackend(vault/index 操作转 HTTP 指向远程根,接口语义与本地一致);其余(含 file://)→ LocalBackend(本机磁盘);启动握手校验(/health + 空检索),失败日志告警并拒绝(抛异常阻止启动)
- vstore/vault.py —— 单一主库:add(file_bytes, filename, meta, as_template)->id(解析→提取→嵌入→加密落盘→索引;原文不出函数,末尾丢弃);get_decrypted(id);delete(id);list/search(meta)
- vstore/retrieval.py —— faiss IndexFlatIP + index.json 单一事实源(行号==json 下标);search(q, top_k, category, as_template) 三档降级(向量→meta 关键词→最新8条);增量写锁 + rebuild 临时索引 os.replace 原子切换
- vstore/api.py —— FastAPI app:POST /api/vstore/ingest(multipart: file+name/category/description/tags/as_template)→{ok,item:{id,name,as_template,embed_chunks}};GET /api/vstore/search?q&top_k&category&as_template→{ok,mode,items[]};GET /api/vstore/documents/{id}(明文全文+outline+meta,仅生成时取);GET /api/vstore/documents/{id}/style(docx 样式提取);DELETE /api/vstore/documents/{id};POST /api/vstore/rebuild-index;GET /api/vstore/health
- vstore/tests/ —— 单元测试:crypto 加解密、vault 加密落盘无明文断言(tmpdir + 假 embed,不碰真 ollama)、retrieval 假 embed、backend 路径解析三形态(裸路径/file:///http(s)://)+ 远程握手失败拒绝、api 端点(FastAPI TestClient)
B. 新增 sdk/app/services/docgen/(BFF 侧纯生成/导出,不含存储检索):
- settings.py —— DocgenPaths + 限额常量(与 vstore 对齐)
- client.py —— vstore HTTP 客户端(httpx.AsyncClient,超时/重试;方法 ingest/search/get_document/get_style/delete/rebuild/health;所有失败路径不抛未捕获异常,返回错误结构供上层兜底)
- parsing.py —— BFF 侧样式提取 extract_docx_style(bytes)->style_hints(python-docx,供导出对齐)
- llm.py —— complete_text(system,user,*,temperature=0.3,max_tokens=8000)->str;complete_json(...)(response_format json_object + 一次 JSON 修复重试);3 次指数退避 1.5s/3s/6s(429/5xx/超时);httpx 裸调(不用 openai SDK——阻塞事件循环);env 键 DOCGEN_LLM_BASE_URL/DOCGEN_LLM_API_KEY/DOCGEN_LLM_MODEL/DOCGEN_LLM_TIMEOUT=300
- intent.py —— 服务端意图路由 route(text, has_attachments, has_active_artifact)->qa|docgen|edit|export。规则词典法:漏判安全(不命中一律 qa)。优先级:导出词(导出/转成/下载+格式词 pdf/docx/word)→ 编辑词(改/加/删/润色+当前有活动文档)→ 写文档意图(写类动词:写/拟/起草/生成/撰写/帮我写/给我写 + 文档类型词词典 + 量词 一篇/一份/一个/一则 等)→ 附件信号(has_attachments + 写类动词)→ qa。文档类型词:论文/报告/通知/通告/公告/请示/批复/函/申请书/申报书/标书/合同/协议/方案/计划/总结/汇报/讲话稿/致辞/证明/纪要/简报/材料/说明书/指南/申请书等。**词典与判定规则要和前端一致(同一条用例表两边测)**
- md2docx.py —— 行级状态机→Word 内建样式(Normal/Heading 1-3/List Bullet/List Number/Table Grid/Quote);中文:正文宋体/标题黑体,font.name 与 w:eastAsia 同时设置;表格表头加粗灰底
- md2pdf.py —— markdown-it-py 出 HTML → PyMuPDF Story→PDF;字体用 /usr/share/fonts 真实文件(NotoSansCJK/NotoSerifCJK/wps-office),禁用内置 CJK;若 Story 路线中文异常,降级自写排版器(标题/段落/表格画线)
- pipeline.py —— run_generate(db, description, library_ids, template_id)->dict:经 client 取明文(vstore 解密)→ 选模版(指定>检索 as_template=true>跳过)→ 大纲(模版 outline 裁剪≤12 节;无模版 complete_json 出 {title,sections[]},失败重试1次再失败六段式默认)→ 逐节生成合并(单节失败重试2次仍失败占位)→ humanize 按行保护(标题/表格/列表行原样,只 humanize 正文段;humanize 现成函数见 sdk/app/services/,只调用不修改)→ md+docx 落盘 → 写 doc_artifacts 表(表结构由 dev2 定,用 CREATE IF NOT EXISTS + 宽松字段,dev1 用 dict 方式按约定字段读写,先跟 dev2 对齐字段名,见下方【跨模块契约】)
- editor.py —— apply_edit(md, instruction)->str(当前文档全文 + 只输出完整新 md;长文档分段改);smart_export(md, format, template_style)->Path
- 单元测试 sdk/app/services/test_docgen_*.py —— intent 路由 20-30 用例(与前端同用例表)、client 假 vstore(httpx MockTransport 或 monkeypatch)、md2docx(打开验证段落/样式/中文字体)、md2pdf 冒烟(tmpdir)、pipeline mock LLM 全流程(monkeypatch llm + client)、editor(mock LLM)
## 【跨模块契约】(与 dev2 对齐,dev2 会在 sdk/app/db.py 建表)
- doc_library 表字段:id TEXT PK、name、category、tags、description、as_template INTEGER、enc_path、embed_sha、active、created_at
- doc_artifacts 表字段:id TEXT PK、session_id、status、title、template_name、md_path、docx_path、pdf_path、error、created_at
- BFF->vstore 全部经 client.py;router 层(dev2)只 import client.py 与 pipeline/editor
- 错误信封沿用现有格式(见 sdk/app/routers/ 现有代码,dev2 负责)
## 【纪律再强调】
- 不碰生产运行中的服务(8090/8091 等);不自行部署;单元测试只用 tmpdir/mock/fixture,不依赖 ollama/LLM 真调用
- 只动本任务书列出的文件;发现需要改其他文件 → 记入报告交 TL 裁决
- 不用 pydantic 模型(服务层只用 dataclass/dict)——Cython 编译纪律
- 不 commit、不 push、不 touch 部署树
## 【自测要求】
- 每个模块写完即写对应单元测试并跑通:cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new && /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python -m pytest vstore/tests/ sdk/app/services/test_docgen_*.py -q(排除依赖 ollama/LLM 的用例,用 mock)
- 自测结果写入 docs/team/report_dev1.md(列出:完成模块清单、每个测试文件与用例数、通过数、已知限制)
## 【完成标准】
报告写入 docs/team/report_dev1.md 后,通过 /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/docs/team/report_dev1.md 的方式让 TL 知道(在你的最终回复中明确写「DEV1 P1 完成」+ 报告路径 + 自测通过数)
【重要补充】意图路由的规范词典与 30 条用例表已冻结于 docs/team/cases_intent.md(唯一事实源)——intent 实现与测试一律以它为准,不得自造词典/自造用例。
+45
View File
@@ -0,0 +1,45 @@
# 任务书:开发 agent-2 —— P2 BFF 集成/API + 批量导入工具
## 前置必读
1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守)
2. 本任务书 + 【跨模块契约】;P1 模块由 dev1 开发,你只按契约 import,不替 dev1 写服务层
## 职责范围(只改这些文件,不越界)
A. sdk/app/config.py(改动)—— Settings 加:docgen_root、docgen_artifact_ttl_days=30、docgen_quota_gb=50、vstore_base_url(默认 http://127.0.0.1:8094)、docgen_llm_base_url(默认 deepseek)、docgen_llm_api_key、docgen_llm_model、docgen_llm_timeout=300
B. sdk/app/schemas.py(改动)—— Block 联合加 ArtifactBlock(type:"artifact"、artifact_id/title/template_name/status);新增 ArtifactOut、DocgenGenerateRequest、DocgenEditRequest、DocgenExportRequest 等 pydantic 模型(schemas.py 是明文白名单,pydantic 只能放这里)
C. sdk/app/db.py(改动)—— _SCHEMA 加两表(幂等 CREATE IF NOT EXISTS,try/except 不阻断启动):
- doc_library:id TEXT PK、name、category、tags、description、as_template INTEGER、enc_path、embed_sha、active、created_at
- doc_artifacts:id TEXT PK、session_id、status、title、template_name、md_path、docx_path、pdf_path、error、created_at
D. sdk/app/main.py(改动)—— 挂载 docgen.router
E. 新增 sdk/app/routers/docgen.py —— BFF 端点(向 UI 暴露;对 vstore 全部经 services/docgen/client.py 透传;错误信封沿用现有格式,参考 sdk/app/routers/qa.py 等现有路由写法):
- POST /api/docgen/library(multipart):上传入库(用户拖拽即走此路)→ 透传 vstore /ingest → {ok, items:[{library_id, name, size, as_template}]}
- POST /api/docgen/generate(JSON {description, session_id, library_ids[], template_id?}):同步生成(600s 兜底)→ {ok, artifact:{...}}
- GET /api/docgen/artifacts/{id}/content(md 原文);PATCH …/content(保存编辑 body {markdown})
- POST /api/docgen/artifacts/{id}/edit:{instruction} → {ok, markdown}
- POST /api/docgen/artifacts/{id}/export:{format:"pdf"|"docx"} → {ok, download_url}
- GET /api/docgen/artifacts/{id}/download?format=:FileResponse attachment(RFC 5987 文件名)
- 主库管理(透传 vstore):GET /api/docgen/library?q=&category=&as_template=、GET/DELETE /api/docgen/library/{id}、PATCH /api/docgen/library/{id}(改 meta/as_template)、POST /api/docgen/library/rebuild-index
- 意图护栏:generate/edit/export 在 BFF 侧跑 services/docgen/intent.py 同规则校验(非 docgen 意图 → 422 提示走 QA)
- vstore 失败兜底:入库失败 → 错误返回(前端 chips 标红可重试);检索失败 → 空模版集合(生成跳过模版);document 拉取失败 → 该资料缺失占位提示(不阻断)
F. 新增 tooling/scripts/docgen_import.py(不进编译链,直连 vstore HTTP)—— 读目录/CSV 批量 ingest(并发 8),as_template=true 默认;rebuild-index 原子切换;--dry-run 先例;导入完成删除源明文目录(--keep-source 可保留)
G. 新增 sdk/app/routers/test_docgen_api.py —— 路由层测试(TestClient + mock pipeline/client,不依赖真实 vstore/LLM)
## 【跨模块契约】(与 dev1 对齐)
- 服务层只 import:services/docgen/client.py、pipeline.py、editor.py、intent.py(dev1 提供;若 dev1 尚未交付,先用最小接口桩并标记 TODO,交 TL 协调)
- db 两表字段如上;服务层以 dict 读写,不引 pydantic
- BFF->vstore 全部经 client.py;router 不直接 httpx 调 vstore
- 用户消息/会话落库:generate 只负责生成与 artifact 落库;聊天消息落库由前端 chat.js 走现有机制(你不用改 sessions/messages)
## 【纪律再强调】
- 不碰生产运行中的服务(8090/8091 等);不自行部署;测试用 TestClient + mock,不连真实 vstore/ollama/LLM
- 只动本任务书列出的文件;发现需要改其他文件(如 capabilities.py、qa.py)→ 记入报告交 TL 裁决
- 不 commit、不 push、不 touch 部署树
- db.py 加表只追加 _SCHEMA,不改现有表定义
## 【自测要求】
- cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new && /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python -m pytest sdk/app/routers/test_docgen_api.py -q
- config/schemas 改动能 import 通过;db 加表在 tmp sqlite 上执行验证幂等
- 自测结果写入 docs/team/report_dev2.md
## 【完成标准】
最终回复中明确写「DEV2 P2 完成」+ 报告路径 docs/team/report_dev2.md + 自测通过数
+36
View File
@@ -0,0 +1,36 @@
# 任务书:开发 agent-3 —— P3 前端 UI(自然触发 + 文档 TAB + 能力徽标 + 右栏可拖宽)
## 前置必读
1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守)
2. 本任务书;改任何文件前先读其全文,理解现有结构再动
## 职责范围(只改这些文件,不越界)
A. 新增 ui/src/api/docgen.js —— uploadLibrary/generate/getArtifactContent/saveArtifactContent/editArtifact/exportArtifact/listLibrary/downloadUrl(复用现有 http.js 封装,600s 兜底)
B. 新增 ui/src/i18n/docgen.js —— 全部新文案(含能力徽标「文档生成」)
C. 新增 ui/src/intent.js —— 前端意图路由 route(text, hasAttachments, hasActiveArtifact)->qa|docgen|edit|export。**规则词典与后端 services/docgen/intent.py 完全一致**(同一词典表、同一优先级:导出词→编辑词+有活动文档→写文档意图(写类动词+文档类型词+量词)→附件+写类动词→qa;漏判安全)。导出文档类型词词典与后端一致:论文/报告/通知/通告/公告/请示/批复/函/申请书/申报书/标书/合同/协议/方案/计划/总结/汇报/讲话稿/致辞/证明/纪要/简报/材料/说明书/指南
D. 新增 ui/src/stores/docgen.js —— 文档 TAB 状态:活动 artifact、md 内容、编辑态、导出态、模版检索候选;不 import bench.js 的状态(只调 setActiveTab)
E. 修改 ui/src/components/chat/Composer.vue —— 拖拽区:dragover 高亮 → drop 收集(前端预检 20MB/5 个)→ 先调 uploadLibrary(入库主向量库,持久可用)→ chips(可移除,移除仅从本次引用拿掉);发送时按 intent.js 路由:docgen → chat.sendDocgen;QA → 原路径(附件注记进消息文本)。**send() 现有逻辑零改动,docgen 走独立分支**
F. 修改 ui/src/stores/chat.js —— 新增 sendDocgen(description, libraryIds):用户消息落库(走现有消息落库机制)→ 助手骨架(kind:docgen,pipeline 块带 capabilities:[{key:doc_writer, label:文档生成}] 徽标,steps 五步:解析资料/检索模版/规划大纲/逐节生成/导出文档,phase 复用 PHASE_COLOR_VAR 键 planning/retrieval/evidence/generation/done)→ generate(600s)→ 成功:blocks=[pipeline(done), artifact 块] + 持久化 + 自动切 bench.setActiveTab(doc);失败 _errorMarker。QA 路径的 pipeline 块注入本次实际启用的会话能力 chips(联网搜索/答案润色/Superpowers,label 取自 settings store 已加载 manifest)。现有 send/_runStream 零改动
G. 修改 ui/src/components/chat/PipelineCard.vue —— pc-head 加 capabilities 徽标区(v-for 渲染 chip,复用现有 badge 视觉),block.capabilities 缺省时零渲染(既有消息无影响)
H. 新增 ui/src/components/chat/DocCard.vue —— 消息流内文档卡片:标题/模版名/「打开预览」按钮(切到文档 TAB)
I. 新增 ui/src/components/bench/DocPane.vue —— 第 5 TAB 主体:markdown-it 渲染 HTML 预览(复用现有 markdown.js 渲染+消毒);块级就地编辑(标题/正文/列表/表格行 contenteditable,blur 序列化回 md + debounce PATCH 保存);「用 prompt 修改」输入行(调 edit 端点);「导出」按钮组(PDF/DOCX)
J. 修改 ui/src/components/bench/BenchPanel.vue —— tabs 数组加 {key:doc, label:文档} + pane 链加 v-else-if 挂 DocPane(两处模板改动,无逻辑改动)
K. 修改 ui/src/components/chat/AssistantMessage.vue —— 块渲染加 artifact 分支
L. 修改 ui/src/components/layout/AppShell.vue + ui/src/styles/base.css —— 右栏可拖拽改宽 + 手柄仿左栏:① .bench-wrap 左边缘加 .bench-resizer(宽 8px、cursor:col-resize,mousedown 拖动实时改宽,min 300px / max 60vw,mouseup 持久化 localStorage pa-bench-width,初始 380px,折叠态不拖拽);② 展开/收起按钮改成左栏 .side-handle-btn 同款:26×26 圆角、hairline 描边、surface-1 底、跨右栏左边缘半嵌(left:0 + translate(-50%,-50%))、垂直居中、图标 13px;③ 箭头方向镜像左栏:折叠态朝左(指向内容侧)、展开态朝右;④ 折叠态 28px 竖条保留为点击区,按钮嵌其上(同款式);⑤ 宽度变化与手柄位置联动(transition 0.3s 同左栏)
## 零改动红线
ui/src/stores/modes.js、ui/src/i18n/labels.js、ui/src/stores/bench.js(store)、ui/src/api/qa.js、ui/src/sse/parser.js
## 【纪律再强调】
- 不碰生产运行中的服务(8090/8091/5180 等);不自行重启 vite、不自行构建 dist
- 只动本任务书列出的文件;发现需要改其他文件 → 记入报告交 TL 裁决
- 不 commit、不 push、不 touch 部署树
## 【自测要求】
- 前端自测:cd ui && npx vitest run(如仓库已有前端测试惯例则照做;intent.js 必须有单元测试——与后端同用例表 20-30 条);无测试框架先例则至少 node --input-type=module 直接 import intent.js 跑断言脚本(intent.js 保持纯函数无 Vue 依赖以便 node 直测)
- 自测结果写入 docs/team/report_dev3.md(完成模块清单、测试通过数、已知限制)
## 【完成标准】
最终回复中明确写「DEV3 P3 完成」+ 报告路径 docs/team/report_dev3.md + 自测通过数
【重要补充】意图路由的规范词典与 30 条用例表已冻结于 docs/team/cases_intent.md(唯一事实源)——intent 实现与测试一律以它为准,不得自造词典/自造用例。
+36
View File
@@ -0,0 +1,36 @@
# 任务书:测试 agent —— P0 测试治理(编制全部测试用例 + 报告框架)
## 前置必读
1. docs/docgen_team_context.md(服务器环境事实 + 团队纪律,全部遵守)
2. 本任务书 + 仓库现有测试先例(sdk/app/services/test_*.py、sdk/app/routers/test_*.py、tests/ 目录)——先读 2-3 个现有测试文件学断言风格
## 你的角色
- 编制【全部】测试用例(目标:覆盖率 100%——每一个计划模块、每一个端点、每一条意图路由规则、每一个 UI 交互点都有对应用例)
- 用例完成后提交 Team Leader 审核;审核过关前【不执行任何测试】(TL 会指令你何时开始轮测)
- 每轮测试逐项执行并逐条留痕;复测与最终结果同样逐条留痕
## 交付物(只写这些文件,不越界)
A. tests/docgen/test_case_report.md —— 测试用例总报告(唯一事实源):
- 头部:文档版本、编制人(测试 agent)、编制日期、审核状态(待 TL 审核/已审核通过)
- 用例分组(编号规则 DC-<组>-<NN>):DC-UNIT-CRYPTO、DC-UNIT-VAULT、DC-UNIT-BACKEND、DC-UNIT-RETRIEVAL、DC-UNIT-INTENT、DC-UNIT-CONVERT(md2docx/md2pdf)、DC-UNIT-PIPELINE、DC-UNIT-EDITOR、DC-UNIT-CLIENT、DC-API(端点逐条)、DC-INTENT-CASES(20-30 条意图对照,前后端同一张表)、DC-E2E-CURL(端到端脚本步骤)、DC-CONF(路径配置三形态+握手拒绝)、DC-FAILOVER(vstore 宕机演练)、DC-UI(浏览器验收清单逐条)、DC-REGRESSION(三模式问答/停止/多会话)、DC-PERF(百级导入压测)
- 每条用例:编号/模块/前置/步骤/预期/实测/结论(PASS|FAIL)/轮次/日期/执行人(实测列每轮追加留痕,不覆盖历史)
- 报告内建「轮次记录」区:每轮开始记轮次号+日期+执行人,结束记汇总(总条数/PASS/FAIL/FAIL 明细)
B. tests/docgen/run_all.sh + 各分组执行脚本 —— 每条可自动化的用例有对应脚本/命令(curl 场景、pytest 选择器、意图对照表脚本、UI 清单核对表),用例编号与脚本一一对应,可复跑
C. tests/docgen/cases_intent.md —— 意图对照表(20-30 条,前后端共用:输入文本/附件标志/活动文档标志/期望结果)
## 用例编写要求
1. 每条用例:可判定(预期明确)、可复跑、失败可定位到模块
2. 单元类用例与开发自测互补但不重复造轮子:你的用例是验收口径(黑盒为主),开发的自测是白盒口径
3. E2E 用例写清 curl 命令序列与每步断言(含 ls vault/ 只见 .enc、明文 grep 无命中等保密断言)
4. UI 用例是浏览器人工/半自动验证清单(每步可核对),写清检查点
5. 覆盖计划「验证」章节的 5 条全部要求 + 计划中所有功能点(自然触发意图、加密入库、vstore 独立服务、能力徽标、文档 TAB 预览编辑导出下载、右栏拖宽+手柄、模版检索、批量导入、路径配置、降级兜底)
## 【纪律再强调】
- 不碰生产运行中的服务;未经 TL 指令【不执行测试】(此刻你只编用例)
- 只写 tests/docgen/ 下文件;不 commit、不 push
- 报告用中文
## 【完成标准】
最终回复中明确写「QA P0 用例编制完成」+ 报告路径 tests/docgen/test_case_report.md + 用例总数 + 覆盖声明(对照计划功能点清单)
【重要补充】意图路由的规范词典与 30 条用例表已冻结于 docs/team/cases_intent.md(唯一事实源)——intent 实现与测试一律以它为准,不得自造词典/自造用例。
+68
View File
@@ -0,0 +1,68 @@
# 意图路由规范(唯一事实源)——dev1(Python)/dev3(JS)/测试 agent 三方共用
## 路由签名
route(text, has_attachments, has_active_artifact) -> "qa" | "docgen" | "edit" | "export"
## 判定顺序(按优先级从上到下,命中即返回)
### 1. export(需 has_active_artifact == True)
条件:text 含任一 EXPORT_WORD 且含任一 FORMAT_WORD
- EXPORT_WORD = [导出, 转成, 转为, 转换成, 下载成, 输出为, 存为, 转pdf, 转word]
- FORMAT_WORD = [pdf, docx, doc, word](英文不区分大小写;"doc" 是 "docx" 的子串,判定时 FORMAT_WORD 逐一 contains 即可)
无活动文档时不命中本规则,继续向下判定。
### 2. edit(需 has_active_artifact == True)
条件:text 含任一 EDIT_WORD
- EDIT_WORD = [改, 修改, 调整, 删, 删除, 加一段, 补充, 润色, 缩写, 扩写, 重写, 改写, 优化, 加个, 增加一段]
无活动文档时不命中本规则,继续向下判定。
### 3. docgen(写文档意图)
条件:text 含任一 WRITE_VERB 且含任一 DOC_TYPE
- WRITE_VERB = [写, 撰写, 拟, 起草, 编制, 生成, 编写, 拟定, 草拟, 制定]
- DOC_TYPE = [论文, 报告, 通知, 通告, 公告, 请示, 批复, 函, 申请书, 申请, 申报书, 标书, 合同, 协议, 方案, 计划, 总结, 汇报, 讲话稿, 致辞, 证明, 纪要, 简报, 材料, 说明书, 指南, 文档, 申报材料, 工作总结, 调研报告, 可行性报告, 项目书, 策划书, 演讲稿, 新闻稿, 文案, 简历, 制度, 规定, 办法, 细则, 章程, 声明, 邀请函, 感谢信, 慰问信, 介绍信, 推荐信, 倡议书, 述职报告, 开题报告, 结题报告, 实验报告, 分析报告, 会议纪要]
- 明确排除(不属于 DOC_TYPE,防误判):介绍, 内容, 代码, 诗, 作业, 问题, 建议, 想法
### 4. docgen(附件信号)
条件:has_attachments == True 且 text 含任一 WRITE_VERB
(兜底:拖入文件 + 写类动词,即使无文档类型词也视为生成意图)
### 5. 其余一律 "qa"(漏判安全原则:不命中绝不进 docgen)
## 用例表(30 条,前后端实现与测试共用同一张表)
| # | 输入 text | 附件 | 活动文档 | 期望 |
|---|---|---|---|---|
| 1 | 给我写一篇关于小麦育种的论文 | 否 | 否 | docgen |
| 2 | 帮我写一个关于疫情防控的通知,参考xxx文档的格式 | 否 | 否 | docgen |
| 3 | 你来帮我起草一份项目总结报告 | 否 | 否 | docgen |
| 4 | 拟一份关于安全生产的通知 | 否 | 否 | docgen |
| 5 | 写一个基金申请文档,参考xxxx和xxxx资料,你来润色 | 否 | 否 | docgen |
| 6 | 根据这个文档,帮我写一篇申报材料 | 是 | 否 | docgen |
| 7 | 写一段水稻基因的介绍 | 否 | 否 | qa |
| 8 | 水稻基因是什么?介绍一下 | 否 | 否 | qa |
| 9 | 帮我总结一下这个问题的要点 | 否 | 否 | qa |
| 10 | 写一首关于秋天的诗 | 否 | 否 | qa |
| 11 | 帮我写代码实现一个排序函数 | 否 | 否 | qa |
| 12 | 这个方案你怎么看? | 否 | 否 | qa |
| 13 | 通知今天下午开会 | 否 | 否 | qa |
| 14 | 导出成PDF | 否 | 是 | export |
| 15 | 把文档转成word发给我 | 否 | 是 | export |
| 16 | 导出成PDF | 否 | 否 | qa |
| 17 | 第2段改正式一点 | 否 | 是 | edit |
| 18 | 把标题改成黑体 | 否 | 是 | edit |
| 19 | 润色一下第二段 | 否 | 是 | edit |
| 20 | 第2段改正式一点 | 否 | 否 | qa |
| 21 | 帮我写个方案,参考上次上传的材料 | 否 | 否 | docgen |
| 22 | 写一份年度工作计划 | 否 | 否 | docgen |
| 23 | 起草会议纪要 | 否 | 否 | docgen |
| 24 | 帮我生成一个市场分析报告 | 否 | 否 | docgen |
| 25 | 写一篇演讲稿,要有感染力 | 否 | 否 | docgen |
| 26 | 帮我写一份劳动合同 | 否 | 否 | docgen |
| 27 | 转成PDF | 否 | 是 | export |
| 28 | 帮我写个会议通知模板 | 否 | 是 | docgen |
| 29 | 给这个通知加个结尾 | 否 | 是 | edit |
| 30 | 增加一段关于风险的描述 | 否 | 是 | edit |
## 一致性要求
- dev1 的 sdk/app/services/docgen/intent.py 与 dev3 的 ui/src/intent.js 必须按本表逐条通过
- 测试 agent 的 DC-INTENT-CASES 组直接引用本表(不得另造规则)
+105
View File
@@ -0,0 +1,105 @@
# P4 部署清单(TL 执行,逐步核对)
> 纪律:备份先行、只动服务器、不 push git、不碰 QA 树(8091)、生产 BFF 8090 只在最后一步重启。
## 0. 前置确认
- [ ] dev1/dev2/dev3/QA v1.1 全部交付且 TL 审核通过
- [ ] dev1 交付后:TL 统一删除 routers/docgen.py 顶部 _STUB 桩段并核对签名
- [ ] 意图 30 条双跑(后端 pytest + 前端 node)通过
## 1. 备份(不可跳过)
```bash
TS=$(date +%Y%m%d-%H%M); BK=/home/xzq/backups/docgen-$TS; mkdir -p $BK
tar czf $BK/deploy-sdk.tar.gz -C /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified sdk
cp /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/sdk/data/plantai.db $BK/plantai.db
cp /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/sdk/.env $BK/env.backup
```
## 2. 全量测试(服务器仓库,venv)
```bash
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python -m pytest \
vstore/tests/ sdk/app/services/test_docgen_*.py sdk/app/routers/test_docgen_api.py \
tooling/scripts/test_docgen_import.py tests/docgen/ -c tests/docgen/pytest.ini -q
```
## 3. BFF 编译 + 替换部署树
```bash
bash tooling/compile/compile_sdk.sh sdk /tmp/sdk-docgen-build /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python
rsync -a --delete --exclude data --exclude .env --exclude mock /tmp/sdk-docgen-build/ /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/sdk/
# 部署树 .env 追加(不覆盖已有):
# VSTORE_BASE_URL=http://127.0.0.1:8094
# DOCGEN_LLM_API_KEY / DOCGEN_LLM_BASE_URL / DOCGEN_LLM_MODEL(复制 QA 的 deepseek 值)
# DOCGEN_ROOT
```
## 4. vstore 部署 + 起服
```bash
mkdir -p /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/vstore
rsync -a REPO/vstore/ /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-deploy-unified/vstore/
# 部署树 vstore/.env:VSTORE_KEY(取自 /home/xzq/backups/docgen-vstore-key.txt)、
# VSTORE_ROOT 或 VSTORE_VAULT_PATH/VSTORE_INDEX_PATH(本地路径或 URL)、
# VSTORE_EMBED_URL=http://127.0.0.1:11434、VSTORE_EMBED_MODEL=bge-m3:567m
bash deploy/vstore_restart.sh
curl -s http://127.0.0.1:8094/api/vstore/health
```
## 5. BFF 重启 + 前端构建
```bash
bash deploy/bff_restart.sh
cd ui && npm run build # 生产 dist → 部署树 ui/dist(按现有 sync 规则)
```
## 6. 通知测试 agent 开始 R1(受控端口:vstore 8094 / BFF 8090 / UI 5180 或 dist)
## 7. 回滚(出问题时)
```bash
rsync -a --delete --exclude data --exclude .env $BK/deploy-sdk.tar.gz 解包覆盖 sdk/
cp $BK/plantai.db 回原位; bash deploy/bff_restart.sh
```
## 附:P4 部署实际记录(2026-09-03 15:05)
- 备份:/home/xzq/backups/docgen-20260903-1425(deploy-sdk.tar.gz + plantai.db + env.backup)
- 编译:30 模块 Cython 3.2.8 硬化成功(docgen 全链路 .so);rsync 替换部署树(保留 data/.env/mock)
- vstore:8094 已起服,local 后端;部署树 vstore/.env 曾因 key 值二次加前缀报 Fernet 格式错(已修,重写 .env 后 ingest 正常)
- vstore_restart.sh 已修 cd 目标(vstore 是包,须从部署根 -m vstore.api:app)
- BFF:8090 重启后 openapi 确认 8 个 docgen 端点;健康检查五上游 + db 正常
- 冒烟:vstore ingest 成功、vault 仅 .enc、明文 grep 无命中;BFF 透传入库/list 正常;完整 QA 流(不断流)7 progress + complete 正常
- vite:5180 已重启(v5.4.11,19s 就绪)
## 附:既有问题归档(非本次引入,A/B 证据)
**「客户端中途断流 → BFF 崩溃」**:`__Pyx_Coroutine_SendEx: Assertion f->f_back == NULL`(db.c)。
- 复现:curl --max-time 20 打 /api/qa/chat/stream 后超时断连 → BFF abort
- A/B:本次构建(db.c:13468)与 9月2日旧构建(db.c:13218,tooling/build/sdk-compiled 备份)**均崩溃**,断言一致 → 既有问题(Cython 3.2.8 协程取消缺陷,与 docgen 无关)
- 影响:用户点「停止」或网络断连可能触发 BFF 进程退出(bff_restart.sh 可快速恢复);前端「停止」按钮走 AbortController 的路径是否触发待 V1.5 专项验证与修复
- 决策:不纳入本轮 docgen 闭环;记录后另立专项
## 附:断流崩溃根治专项(用户指令 2026-09-03,doc_writer 闭环完成后立即开始)
**用户指令**:记录下来,当前任务完成后立即开始根治断流崩溃。
**已知事实(A/B 证据)**:旧构建(9月2日,db.c:13218)与新构建(db.c:13468)断流均崩,Cython 3.2.8 协程取消缺陷;当前无任何自动拉起(无 systemd/supervisor/cron 守护)。
**拟修路径(按序验证)**:
1. Cython 3.2.8 → 3.3.0 重编译 → 隔离实例断流探针 10 连发 A/B
2. 仍崩则取消敏感模块(db.py / sse_relay.py)降级明文 Python(compile_sdk.sh 白名单机制支持)
3. 配套加固(可选):BFF 自动守护(systemd Restart=always 或 cron 探测重启)
**验收**:隔离实例断流探针不崩 + 生产替换后「停止按钮/断网」实测 + 三模式问答回归。
## 附:断流崩溃根治完成记录(2026-09-03 19:30)
**修复方案(最终)**:编译白名单追加 3 个模块(app/db.py、app/routers/qa.py、app/services/sse_relay.py)——SSE 流取消链明文化,取消路径全程走 CPython 原生协程。
**证据链**:
- Cython 3.2.8 构建:崩溃 db.c:13468
- Cython 3.3.0 构建:崩溃 db.c:14416(升级无效,3.3.0 未修复此缺陷)
- db.py 明文:崩溃迁移 sse_relay.c:29531(同一断言)→ 证明取消链上任意 Cython 协程均可能触发
- db+qa+sse_relay 明文化:隔离实例断流探针 10/10 存活
- 生产同构建替换后:真实断流探针存活 + 完整 QA 流 7 progress + complete 正常 + 五上游健康
**代价与风险**:3 个模块失去 Cython 硬化(源码可读,无敏感凭据——凭据在 .env/环境);编译模块 30→27。白名单理由已写入 compile_sdk.sh 注释。
**备份**:/home/xzq/backups/docgen-crashfix-20260903-1922(可回滚)
+91
View File
@@ -0,0 +1,91 @@
# DEV1 P1 自测报告(2026-09-03)
## 完成模块清单
### A. vstore 独立包(仓库根 vstore/,端口 8094 独立服务)
| 文件 | 职责 |
|---|---|
| pyproject.toml | 包定义(fastapi/uvicorn/httpx/cryptography/faiss-cpu/numpy/python-docx/pymupdf/PyYAML/python-multipart) |
| settings.py | VstorePaths dataclass + 限额常量(MAX_ATTACH_COUNT=5、MAX_ATTACH_BYTES=20MB、MAX_ATTACH_CHARS=60000、MAX_SECTIONS=12、MAX_OUTLINE_CHUNKS=3);VSTORE_KEY 必填(缺失抛异常拒绝启动);VSTORE_ROOT/VAULT_PATH/INDEX_PATH(本地或 http(s)://)/EMBED_URL/EMBED_MODEL |
| crypto.py | Fernet 封装 encrypt_bytes/decrypt_bytes;.enc 原子落盘;secure_delete 3 遍覆写 |
| parsing.py | extract_docx→{outline,paragraphs,tables,style_hints}、extract_pdf(pymupdf ≤300 页)、extract_text_file(utf-8→gb18030)、outline_to_markdown、outline_to_embedding_chunks(>7500 字符按节切块 ≤3) |
| embed.py | embed_batch:httpx 线程池并发 ≤8 调 ollama /api/embed(url/model 由 settings);单条失败降级零向量不整体失败 |
| backend.py | parse_backend:http(s):// → RemoteBackend(HTTP 转译,接口语义与本地一致),其余(含 file://)→ LocalBackend;混合形态拒绝;启动握手(health+空检索)失败抛异常拒绝启动 |
| vault.py | 单一主库 add/get_decrypted/delete/search/rebuild_index;明文纪律(原文不出函数末尾 del,磁盘只有 .enc);检索 meta 不含正文(keywords 只用用户 meta) |
| retrieval.py | faiss IndexFlatIP + index.json 单一事实源(行号==json 下标);三档降级(向量→meta 关键词→最新 8 条);向量档分数阈值(零向量查询正确降级);增量写锁;rebuild 临时文件+os.replace 原子切换 |
| api.py | FastAPI:/api/vstore/{ingest,search,documents/{id},documents/{id}/style,rebuild-index,health};错误信封 {ok,error:{code,message,degraded}} 与 BFF 一致 |
### B. sdk/app/services/docgen/(BFF 侧服务层)+ services/test_docgen_*.py
| 文件 | 职责 |
|---|---|
| settings.py | DocgenPaths + 限额常量(与 vstore 对齐);DOCGEN_ROOT |
| client.py | httpx.AsyncClient 客户端;重试(指数退避 0.5/1/2s);ingest/search/get_document/get_style/delete/update_metadata/rebuild/health;失败抛 DocgenUnavailable(router 兜底 UPSTREAM_DOWN)或返回错误结构,不抛未捕获异常 |
| parsing.py | extract_docx_style(bytes)→style_hints(python-docx,导出对齐) |
| llm.py | complete_text/complete_json(response_format json_object+一次 JSON 修复重试);3 次指数退避 1.5s/3s/6s(429/5xx/超时);httpx 裸调不用 openai SDK;env 键 DOCGEN_LLM_BASE_URL/API_KEY/MODEL/TIMEOUT=300 |
| intent.py | route(text,has_attachments,has_active_artifact)→qa\|docgen\|edit\|export,词典与判定顺序严格按 cases_intent.md;漏判安全(不命中一律 qa);is_docgen_intent(async)供 dev2 护栏 |
| md2docx.py | 行级状态机→Word 内建样式(Normal/Heading 1-3/List Bullet/List Number/Table Grid/Quote);中文正文宋体/标题黑体,font.name 与 w:eastAsia 同时设置;表头加粗灰底 |
| md2pdf.py | markdown-it-py(commonmark) 出 HTML→PyMuPDF Story→PDF;字体用 /usr/share/fonts 真实文件(NotoSerifCJK 正文/NotoSansCJK 标题,禁用内置 CJK);Story 失败/空文本自动降级自写排版器(TextWriter 逐块排版) |
| pipeline.py | generate_document(description,*,documents,template)→{markdown,md_path}:资料明文拉取(失败占位不阻断)→模版大纲(裁剪≤12 节)→无模版 complete_json(失败重试 1 次→六段式默认)→逐节生成(单节重试 2 次仍失败占位)→humanize 按行保护(标题/表格/列表行原样,只 humanize 正文段)→md 落盘 DOCGEN_ROOT/artifacts/ |
| editor.py | apply_edit(md,instruction)→{markdown}(长文档分段改,段失败保留原段);smart_export(md,format,style)→Path |
| converter.py(新增适配层) | docx_from_markdown(markdown,title)→bytes / pdf_from_markdown→bytes(async;dev2 桩实际 import 的签名;内调 md2docx/md2pdf 核心,bytes 不落中间文件) |
## 测试文件与用例数
| 测试文件 | 用例数 | 通过 | 说明 |
|---|---|---|---|
| vstore/tests/test_settings.py | 6 | 6 | 限额常量/路径/密钥必填 |
| vstore/tests/test_crypto.py | 7 | 7 | 加解密/落盘原子/secure_delete |
| vstore/tests/test_parsing.py | 12 | 12 | docx/pdf/文本/大纲派生 |
| vstore/tests/test_embed.py | 5 | 5 | httpx.Client 注入,不碰真 ollama |
| vstore/tests/test_backend.py | 9 | 9 | 三形态解析+远程握手失败拒绝 |
| vstore/tests/test_retrieval.py | 12 | 12 | faiss 三档降级/原子 rebuild |
| vstore/tests/test_vault.py | 13 | 13 | 加密落盘无明文断言/增删检索 |
| vstore/tests/test_api.py | 8 | 8 | FastAPI TestClient |
| sdk/app/services/test_docgen_intent.py | 30+3 | 33 | cases_intent.md 冻结 30 条全过 |
| sdk/app/services/test_docgen_client.py | 12 | 12 | MockTransport,不连真 vstore |
| sdk/app/services/test_docgen_llm.py | 8 | 8 | 假 httpx:重试/JSON 修复/认证头 |
| sdk/app/services/test_docgen_md2docx.py | 5 | 5 | 打开验证段落/样式/中文字体 |
| sdk/app/services/test_docgen_md2pdf.py | 3 | 3 | tmpdir 冒烟,验证中文可提取 |
| sdk/app/services/test_docgen_pipeline.py | 6 | 6 | monkeypatch llm+client 全流程 |
| sdk/app/services/test_docgen_editor.py | 6 | 6 | mock LLM |
| sdk/app/services/test_docgen_parsing.py | 2 | 2 | 样式提取 |
| sdk/app/services/test_docgen_converter.py | 2 | 2 | dev2 桩签名 bytes 产物 |
| **合计** | **145** | **145** | 全部通过 |
自测命令:`cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new && /home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python -m pytest vstore/tests/ sdk/app/services/test_docgen_*.py -q` → **145 passed**
## 与 dev2 桩签名对齐确认(TL 契约通知核对)
已读 sdk/app/routers/docgen.py 桩段并逐函数核对,验证 router 实际 import 的是本包真实模块(非桩):
- client.py:ingest_bytes(filename, content, *, name, category, tags, description, as_template) / search_documents(query, *, as_template, top_k) / delete_document / update_metadata / rebuild_index + DocgenUnavailable —— **签名一致,均为 async**
- pipeline.generate_document(description, *, documents, template) -> {markdown, md_path} —— 一致;**artifact 落库由 dev2 router 负责**(pipeline 拿不到 artifact_id,md 先写 DOCGEN_ROOT/artifacts/<uuid>.md,router 再按 artifact_id 落一次)
- editor.apply_edit(markdown, instruction) -> {markdown} —— 一致
- intent.is_docgen_intent(text) —— 已按 dev2 await 方式实现为 async 并返回 bool;route 四分类保留
- 转换器:dev2 实际 import 的是 `services/docgen.converter` 的 docx_from_markdown(markdown, title) -> bytes(async,两参),与 TL 通知的 `md2docx.py 导出 (markdown, out_path) -> Path` 不一致 —— **已双层满足**:md2docx/md2pdf 同步核心按 TL 签名提供,新增 converter.py 适配 dev2 桩
- ingest 返回含 document_id/name/category/tags/description/as_template/enc_path/embed_sha/embed_chunks —— 与 dev2 落库字段对齐
## 已知限制(TL 已裁决,P1 正式验收通过)
以下为 TL 对报告 1-9 条的裁决结果(全部通过,P1 验收完成):
1. **converter.py 保留**(裁决通过):dev2 桩 import 的是 converter 的 bytes 签名,保留适配层正确,不改 dev2。「双层满足」方案即最终形态。
2. **update_metadata 维持现状**:BFF doc_library 落库为准;vstore PATCH 端点列入 V1.5 演进(本期不加)。
3. **wps-office 缺失**:以 Noto 为实际方案(NotoSerifCJK 正文 / NotoSansCJK 标题),记录为已知限制,不算缺陷。
4. **commonmark preset**:维持现状,V1.5 可考虑 linkify-it-py。
5. **远程后端联测**:R1 轮测 DC-CONF-03/04 会真实起受控假远端验证,若暴露问题由 dev1 修复。
6. **startup deprecation**:与 BFF 现有风格一致,不改。
7. **vstore library 列表端点**:BFF 走 doc_library 表,不需要。
8. **测试位置**:符合先例,通过。
9. **vstore_restart.sh**:TL 已创建(deploy/vstore_restart.sh),无需 dev1 处理。
状态:P1 已交付并验收通过,dev1 收工待命;R1 轮测若暴露 vstore/services 侧 bug,TL 指名修复。
## R1 修复记录(2026-09-03)
### FAILOVER-02 节标题重复(已修复)
- 问题:pipeline 逐节合并时无脑插入 `## 节标题`,LLM 输出首节自带同文本标题时出现重复标题。
- 修复:新增 `_merge_section(section_title, section_md)` 辅助函数——输出首行若是标题行(# 1-6 级)且文本与节标题相同(忽略前后空格与标题层级差异),保留 LLM 自带标题不重复插入;否则正常插入 `## 节标题`。H1 总标题仍只加一次(主流程不变)。
- 变更文件:`sdk/app/services/docgen/pipeline.py`(合并循环改走 `_merge_section`;`written` 拼接改用合并后的 block);`sdk/app/services/test_docgen_pipeline.py`(新增 3 用例)。
- 新增用例:
1. `test_generate_merge_no_duplicate_title_when_llm_output_has_heading`——mock 按节输出自带标题(## 与 ### 两个层级变体),断言每节标题行恰 1 条、节名全文恰 1 次;
2. `test_generate_merge_inserts_title_when_llm_output_has_no_heading`——mock 输出不带标题,断言正常插入且恰 1 次;
3. `test_merge_section_helper_rules`——`_merge_section` 单元规则(同文本去重/异文本插入/无标题插入/前后空格容忍)。
- 自测:`pytest sdk/app/services/test_docgen_pipeline.py -q` → 9 passed;全量 `pytest vstore/tests/ sdk/app/services/test_docgen_*.py -q` → 148 passed(无回归)。
+144
View File
@@ -0,0 +1,144 @@
# P2 开发报告 —— 开发 agent-2(BFF 集成/API + 批量导入工具)
日期:2026-09-03
## 一、改动文件清单
### 修改(4 个)
| 文件 | 改动 |
|---|---|
| sdk/app/config.py | Settings 追加 docgen 键:docgen_root(默认 data/docgen)、docgen_artifact_ttl_days=30、docgen_quota_gb=50、docgen_llm_base_url(默认 https://api.deepseek.com)、docgen_llm_api_key(默认空)、docgen_llm_model=deepseek-chat、docgen_llm_timeout=300、vstore_base_url(默认 http://127.0.0.1:8094)。全部可被环境变量覆盖 |
| sdk/app/schemas.py | Block 联合追加 ArtifactBlock(type:"artifact"、artifact_id/title/template_name/status);新增 §12 模型:ArtifactOut、LibraryItemOut、LibraryPatch、DocgenGenerateRequest、DocgenEditRequest、ArtifactContentPatch、DocgenExportRequest(format Literal["pdf","docx"])、DocgenImportLine(导入工具 CSV 契约) |
| sdk/app/db.py | _SCHEMA 不动;新增 _DOCGEN_SCHEMA 两表(doc_library:id/name/category/tags/description/as_template/enc_path/embed_sha/active/created_at;doc_artifacts:id/session_id/status/title/template_name/md_path/docx_path/pdf_path/error/created_at)+ 4 个索引。connect() 内单独 executescript,try/except 失败只记日志不阻断启动;CREATE IF NOT EXISTS 幂等 |
| sdk/app/main.py | 挂载 docgen.router(import 加入 routers 包导入行 + include_router) |
### 新增(4 个)
| 文件 | 内容 |
|---|---|
| sdk/app/routers/docgen.py | BFF 全部 docgen 端点(见 §二);错误信封沿用 app.upstream.envelope |
| sdk/app/routers/test_docgen_api.py | 路由层测试:TestClient + monkeypatch mock 服务层 + 临时 SQLite,不连真实 vstore/LLM |
| tooling/scripts/docgen_import.py | 批量导入工具:并发 8、--dry-run、--keep-source、metadata.csv 映射、成功才删源明文目录、失败不删不重建、rebuild-index |
| tooling/scripts/test_docgen_import.py | 导入工具单测:httpx.MockTransport 假 vstore |
## 二、端点与行为(routers/docgen.py)
- POST /api/docgen/library(multipart,100MB 护栏)→ client.ingest_bytes → {ok, items:[{library_id,name,size,as_template}]};ingest 失败不落库,VALIDATION 信封(chips 标红可重试);vstore 不可达 → 503 UPSTREAM_DOWN
- POST /api/docgen/generate:description 必填 ≤2000 字;意图护栏 → 模版检索(失败→空模版集不阻断)→ 资料落库行(library_ids 空=全部 active)→ pipeline.generate_document → artifact 落库 {ok, artifact};服务未就绪 → 503 + 落 error 行(前端可查状态)
- GET/PATCH /artifacts/{id}/content(md 原文/保存编辑)
- POST /artifacts/{id}/edit:{instruction} → {ok, markdown},编辑结果同步落盘
- POST /artifacts/{id}/export:{format:"pdf"|"docx"} → {ok, download_url},同步转换(内容取最新编辑版)
- GET /artifacts/{id}/download?format=:FileResponse attachment(RFC 5987 文件名)
- 主库:GET /library(q/category/as_template 落库过滤)、GET/PATCH/DELETE /library/{id}(PATCH/DELETE 的 vstore 透传失败容错)、POST /library/rebuild-index(透传,不可达 → 503)
- 意图护栏:generate/edit/export 先跑 intent.is_docgen_intent,非 docgen 意图 → 422 INTENT_REJECTED(提示走 QA)
- 路径穿越防护:_safe_rel 拒绝绝对路径与 "..";制品落盘在 docgen_root 下
## 三、dev1 依赖与接口桩(重要)
services/docgen/ 当时尚未交付,docgen.py 按任务书契约做了最小接口桩(try/except ImportError 回落,签名与契约一致):
- client.py:DocgenUnavailable、ingest_bytes、search_documents、delete_document、update_metadata、rebuild_index
- pipeline.py:generate_document(description, *, documents, template)
- editor.py:apply_edit(markdown, instruction)
- intent.py:is_docgen_intent(text)
- converter.py:docx_from_markdown / pdf_from_markdown(任务书未列但 export 必需,桩同签)
**待 TL 联动联测项:**
1. dev1 交付后删掉 docgen.py 顶部桩段即可(router 主体零改动),但需对齐桩签名与 dev1 真实签名(尤其 client 函数名/参数、generate_document 返回结构 {markdown, md_path}、edit 返回 {markdown, md_path?})
2. 任务书兜底要求「document 拉取失败 → 缺失占位提示(不阻断)」:资料全文拉取属服务层职责(router 只传 doc_library 落库行 dict 给 pipeline),具体占位形态待 dev1 服务层实现后联测
3. export 的转换实现(docx/pdf 生成)由 dev1 converter.py 负责;契约未在任务书列明,本报告提请 TL 确认归属
## 四、自测结果
- 路由层:pytest sdk/app/routers/test_docgen_api.py -q → **29 passed**(上传 3、生成 4、content/edit/export/download 12、主库 8、块联合 2;含 422/404/503/204 与 vstore 容错路径)
- 导入工具:pytest tooling/scripts/test_docgen_import.py -q → **7 passed**(dry-run、成功删源、keep-source、部分失败不删不重建、并发 ingest)
- 回归抽查(只跑目标相关,未跑全量):sdk/app/test_db.py 5 passed;sdk/app/test_sessions_api.py 13 passed(Block 联合扩展无回归)
- config/schemas:import + 默认值断言通过(含 DocgenExportRequest 非法格式 422 校验)
- db:tmp sqlite 验证两表字段齐全 + 二次 connect 幂等通过
- main.py:openapi 确认 8 个 docgen 路径挂载
- 合计:**54 passed,0 failed**(29+7+5+13)
## 五、已知限制 / 需 TL 裁决
1. **QUOTA_EXCEEDED 信封未实现**:docgen_quota_gb 配置键已就位,但 quota 校验逻辑(制品目录体积扫描)属服务层/运维职责,任务书端点清单未包含对应入口——是否需要 BFF 侧实现,请 TL 裁决
2. **docgen_artifact_ttl_days 暂无消费者**:配置键已就位,TTL 清理循环建议放在 vstore 或运维侧,任务书未列对应端点
3. **INTENT_REJECTED / QUOTA_EXCEEDED 是 envelope 新 code**:upstream.py 的 _ERROR_MESSAGES/status 映射表未含这两个 code,docgen.py 调用时显式传 status=422/507 与 message,未改 upstream.py(不在任务书文件清单)
4. **legacy.py 既有告警**:openapi 生成时 Duplicate Operation ID 告警(legacy 路由,非本次改动引入)
5. **编译链**:docgen.py / test_docgen_api.py 属新增文件,compile_sdk.sh 白名单机制(main/config/schemas 明文)无需改动;docgen_import.py 在 tooling/ 不进编译链——无需 TL 确认
6. 服务层以 dict 读写落库(不引 pydantic),与契约一致
## 六、纪律自检
- 未碰 capabilities.py/qa.py 等非清单文件;未连生产端口(8090/8091 等);未 commit/push;未重启任何服务
- tests/docgen/、deploy/vstore_restart.sh、docs/ 下其他文件为他家产物,未动
## 七、补丁记录(2026-09-03,TL 裁决后追加)
TL 对报告 §五 3 个问题的裁决:1 QUOTA_EXCEEDED 由 BFF 实现;2 TTL 由 BFF 惰性清理;3 新错误码维持现状(不改 upstream.py);4 converter 归属 dev1(交付后 TL 统一删桩,本 agent 不处理)。
### 补丁内容(只改 sdk/app/routers/docgen.py 与 test_docgen_api.py)
1. **配额 _check_quota()**(routers/docgen.py):
- artifacts/ 目录体积扫描(rglob + st_size,跳过符号链接,不抛异常)
- 超 docgen_quota_gb → 507 QUOTA_EXCEEDED 信封(degraded=true,含已用/配额信息)
- 拒绝点:generate(意图护栏之后、模版检索之前)、export(护栏之后)
- generate 成功后顺带复查一次(写盘后体积更准;超限仅告警日志,artifact 已存在仍返回成功)
2. **TTL 惰性清理 _prune_expired_artifacts()**(routers/docgen.py):
- created_at 超 docgen_artifact_ttl_days 的 artifact:删除 md/docx/pdf 文件(unlink missing_ok,路径穿越防护 _safe_rel)+ DB 行 status 置 expired、路径列清空
- 在 generate 开始前调用;清理异常只记日志不阻断生成
3. 模块 docstring 更新(配额/TTL 语义说明)
### 新增测试用例(test_docgen_api.py,+5)
- test_generate_quota_exceeded_507:quota=0 设置下 generate 返回 507 QUOTA_EXCEEDED(degraded=true)
- test_export_quota_exceeded_507:同设置下 export 返回 507
- test_check_quota_passes_when_under_limit:默认设置下 generate/export 不被误拦
- test_generate_prunes_expired_artifacts:过期行(31 天前)文件被删 + status 置 expired;新鲜行(1 天前)不受影响
- test_prune_expired_missing_files_tolerated:过期行文件缺失时清理容错(不抛异常,行仍置 expired)
### 补丁后自测
- 路由层:`pytest sdk/app/routers/test_docgen_api.py -q` → **34 passed**(原 29 + 新 5)
- 导入工具:`pytest tooling/scripts/test_docgen_import.py -q` → 7 passed(无回归)
- 合计:**41 passed,0 failed**
## 八、测试污染修复记录(2026-09-03,TL 联测诊断后)
### 现象
单独跑 test_docgen_api.py 34 全过;与任何其他测试文件合跑 → 32 ERROR。
错误链:`'test_library_upload_ok' requested an async fixture 'db', with no plugin or hook that handled it` → fixture 链 `assert not self._finalizers` 崩溃。
### 根因(TL 诊断,已采纳)
pytest-asyncio 1.4.0 的 event_loop fixture 跨文件复用 bug:async fixture(db)与 sync 测试(client/测试函数)混用,多文件合跑时 loop finalizers 跨文件残留,async fixture 链崩溃。本仓库可靠模式是 QA 验收脚本 tests/docgen/test_docgen_api_acceptance.py 的纯 sync fixture + 手动事件循环。
### 修复(方案 A,只改 sdk/app/routers/test_docgen_api.py)
db fixture 由 async 改为纯 sync:connect/close 分别经 `_run()`(asyncio.new_event_loop().run_until_complete)执行;fixture 链(db/client/_reset_mocks)全 sync;测试函数内 db 访问维持 `_run(async def inner(): ...)` 闭包模式(QA 验收脚本同款)。文件头部注释记录诊断原因,防回归。
### 三组合跑结果(验收标准全过)
1. `pytest sdk/app/routers/test_docgen_api.py tooling/scripts/test_docgen_import.py -q` → **41 passed**(无 ERROR)
2. `pytest sdk/app/routers/test_docgen_api.py -q` → **34 passed**
3. `pytest sdk/app/routers/test_docgen_api.py tooling/scripts/test_docgen_import.py sdk/app/services/test_docgen_*.py vstore/tests/ -q` → **186 passed**(与 TL 预计一致)
未动被测代码 docgen.py(dev1 删桩替换后由 TL 管理)。
## 九、R1 修复记录(2026-09-03,TL 审阅 FAIL 1:护栏结构性矛盾)
### 根因(TL 定性)
_intent_guard 对所有端点统一调 is_docgen_intent(text)(= route(text, False, False) == "docgen"):
- generate 传 description → 正确
- edit 传 instruction(如「润色一下第一段」)→ route 判 edit(has_active_artifact 硬编码 False)→ 恒 422
- export 传合成文本 → 恒 422
### 修复(按 TL 要求,只改 sdk/app/routers/docgen.py 护栏相关行 + test_docgen_api.py)
1. import 增加 route:`from ..services.docgen.intent import is_docgen_intent, route`(is_docgen_intent 布尔接口未动)
2. _intent_guard 改为 `_intent_guard(text, *, has_active_artifact: bool)`,内部直接 route 三参判定:
- generate:has_active_artifact=False → route(...) == "docgen" 放行(维持原语义)
- edit:has_active_artifact=True → route(...) in ("docgen", "edit") 放行(编辑指令判 edit)
- export:取消护栏(format 已有 DocgenExportRequest Literal 白名单;DC-API-09 format 校验继续生效)
- 路由异常 → 放行(漏判安全,护栏不阻断)
3. 测试适配真实 route:generate 各用例 description 改为含写动词+文档类型(「写一份…」);edit 拒绝用例 monkeypatch 目标改为 route(机制验证);新增 4 个验收用例:
- edit「润色一下第一段」→ 200(route 判 edit 放行)
- export 合法 format → 200(无护栏)
- export 非法 format → 422 VALIDATION(白名单仍生效)
- edit 问答类指令「帮我查个文献」→ 422 INTENT_REJECTED(护栏仍生效)
- generate「今天天气怎么样」→ 422 INTENT_REJECTED(真实 route 判定)
### 自测结果
- `pytest sdk/app/routers/test_docgen_api.py sdk/app/services/test_docgen_*.py sdk/app/test_db.py sdk/app/test_sessions_api.py -q` → **135 passed**
- 污染回归组合:`test_docgen_api.py + tooling/scripts/test_docgen_import.py` → 45 passed;`+ sdk/app/services/test_docgen_*.py + vstore/tests/` → 193 passed
- 均无 ERROR、无 FAILED
+87
View File
@@ -0,0 +1,87 @@
# DEV3 P3 自测报告 —— 前端 UI(自然触发 + 文档 TAB + 能力徽标 + 右栏可拖宽)
- 日期:2026-09-03
- 归属:开发 agent-3(P3 前端 UI)
- 事实源:docs/team/brief_dev3_p3.md、docs/team/cases_intent.md、docs/docgen_team_context.md
## 一、完成模块清单(按任务书 A–L)
| 编号 | 文件 | 状态 | 说明 |
|---|---|---|---|
| A | ui/src/api/docgen.js | 新增 | uploadLibrary / generate / getArtifactContent / saveArtifactContent / editArtifact / exportArtifact / listLibrary / downloadUrl,全部经 http.js 封装(默认 600s 读超时兜底);端点契约按 P2 任务书 docgen.py 约定(dev2 开发中,联调后生效) |
| B | ui/src/i18n/docgen.js | 新增 | DOCGEN_STEPS 五步(解析资料/检索模版/规划大纲/逐节生成/导出文档,相位复用 PHASE_COLOR_VAR 键 planning/retrieval/evidence/generation/done)、CAP_BADGES 能力徽标文案、DG 全量新文案 |
| C | ui/src/intent.js | 新增 | 纯函数无 Vue 依赖,与后端 intent.py 同词典同规则(EXPORT_WORD/FORMAT_WORD/EDIT_WORD/WRITE_VERB/DOC_TYPE/DOC_TYPE_EXCLUDE 与 cases_intent.md 逐词一致);漏判安全(不命中一律 qa) |
| D | ui/src/stores/docgen.js | 新增 | 文档 TAB 状态:activeArtifact/md/editing/exporting/templateCandidates;不 import bench.js 状态,只调 bench.setActiveTab(动态 import 防循环依赖) |
| E | ui/src/components/chat/Composer.vue | 修改 | 拖拽上传(dragover 高亮遮罩 → drop 预检 20MB×5 → uploadLibrary 入库 → chips 可移除/失败可重试);发送按 intent.js 分流:docgen → chat.sendDocgen、edit → docgen store 预填指令+切 TAB、export → 导出请求、qa → 原 emit('send') 路径(附件注记进消息文本)。send() 原逻辑零改动 |
| F | ui/src/stores/chat.js | 修改 | 新增 newDocgenSkeletonMessage(kind:docgen,pipeline 块 capabilities:[{key:doc_writer,label:文档生成}],steps 五步)、collectQaCaps/injectQaCaps(QA 路径注入本次启用能力 chips:联网搜索/答案润色/Superpowers,label 取 settings store manifest)、sendDocgen(description, libraryIds)(用户消息落库→骨架→generate 600s→成功 pipeline done + artifact 块 + 持久化 + 自动切 bench.setActiveTab('doc'),失败 _errorMarker 标注可重试)、retry 对 docgen 消息同位置替换走文档生成路径。send/_runStream 零改动(仅骨架落库后一行 injectQaCaps) |
| G | ui/src/components/chat/PipelineCard.vue | 修改 | pc-head 加 capabilities 徽标区(v-for chip,复用 badge 视觉,渐变描边);block.capabilities 缺省时零渲染;标题支持 block.title 覆盖(docgen 显示「文档生成流水线」) |
| H | ui/src/components/chat/DocCard.vue | 新增 | 消息流内文档卡片:标题/模版名/「打开预览」(自足闭环:docgen store 加载 + bench 切文档 TAB,不向上游抛事件) |
| I | ui/src/components/bench/DocPane.vue | 新增 | 第 5 TAB 主体:markdown-it 渲染预览(复用 markdown.js 消毒);块级就地编辑(标题/段落/列表/表格行 contenteditable,blur 序列化回 md + debounce 800ms PATCH);「用 prompt 修改」输入行(edit 端点);导出按钮组(PDF/DOCX → download_url 下载,手动兜底) |
| J | ui/src/components/bench/BenchPanel.vue | 修改 | tabs 数组 = labels.BENCH_TABS + DOCGEN_TAB{key:doc,label:文档}(不碰 labels.js 红线);pane 链 v-else-if 挂 DocPane(两处模板改动,无逻辑改动) |
| K | ui/src/components/chat/AssistantMessage.vue | 修改 | 块渲染加 artifact 分支(DocCard) |
| L | ui/src/components/layout/AppShell.vue + ui/src/styles/base.css | 修改 | ① .bench-resizer 左边缘 8px col-resize 拖拽实时改宽(min 300px / max 60vw,mouseup 持久化 localStorage pa-bench-width,初始 380px,折叠态隐藏不拖拽,拖拽中禁用过渡跟手);② 展开/收起按钮改左栏 .side-handle-btn 同款(26×26 圆角、hairline 描边、surface-1 底、跨右栏左边缘半嵌 left:0+translate(-50%,-50%)、垂直居中、图标 13px);③ 箭头镜像左栏:折叠态朝左(指向内容侧)、展开态朝右;④ 折叠态 28px 竖条保留为点击区(bench-wrap 条身点击展开,按钮嵌其上同款式);⑤ 宽度变化与手柄位置联动(transition 0.3s 同左栏,.bench-resizing 时禁用过渡) |
## 二、自测结果
1. **intent.js 30 条用例(docs/team/cases_intent.md 表)**:30 / 30 通过。
测试方式:node --input-type=module 直接 import ui/src/intent.js 跑断言脚本(intent.js 保持纯函数无 Vue 依赖,脚本存于 /tmp/test_intent_dev3.mjs,未落仓库)。英文格式词已按事实源要求做大小写不敏感判定(PDF→pdf)。
2. **新 JS 文件语法**:node --check 全部通过(intent.js / api/docgen.js / i18n/docgen.js / stores/docgen.js / stores/chat.js)。
3. **.vue 单文件组件编译**:@vue/compiler-sfc 逐文件编译(script setup + template)7 个改动/新增 .vue 全部通过(Composer / DocCard / DocPane / PipelineCard / AssistantMessage / BenchPanel / AppShell)。检查脚本在 /tmp/vue_sfc_check.mjs,未落仓库。
4. **既有前端单测回归**:`npx vitest run` 全量 172 条 —— 171 通过,1 失败:
- 失败项:tests/frontend/unit/b3/benchpanel.test.js「四 tab 来自 labels.BENCH_TABS」期望 4 个 tab,实际 5 个(P3 按任务书 J 新增「文档」TAB 的必然结果,属任务书要求的行为变化)。
- b1 appshell.test.js 7/7 通过(旧 .bench-collapse-btn 保留在 DOM 仅 CSS 退役,折叠断言未破坏);
- b2 chat.store.test.js 19/19、b2 components.test.js 20/20 通过(send/_runStream 零改动 + 徽标区缺省零渲染已回归验证)。
## 三、已知限制
1. **后端联调未做**:docgen 端点契约按 P2 任务书编写(dev2 开发中)。api/docgen.js 的路径/字段(items[].library_id、artifact.artifact_id、download_url 等)待 BFF 联调后可能微调。
2. **前端到后端的 artifact 字段别名**:generate 响应可能返回 artifact_id 或 id,api 层已容错。
3. **pipeline 中间状态是简化模拟**:sendDocgen 等待 600s 同步 generate 期间,仅把 planning 步置 running(无法获得后端实时阶段);生成成功/失败后整体置 done/error。若后端后续支持进度回调,可在此扩展。
4. **附件 chips 的引用持久性**:用户消息落库仅 text(现有 messages 接口只有 text 字段),library_ids 放在前端消息 meta 上,会话刷新后不保留。重试时退化为无附件生成(不阻塞)。
5. **DocPane 就地编辑不支持全部 markdown**:序列化覆盖标题/段落/列表/表格/引用/代码块,其它行内格式(加粗/斜体/链接)在编辑序列化后会丢失(预览完整保留)。
6. **intent.js 的 exclude 过滤逻辑**:按事实源表的 30 条用例全部通过,但事实源未指定「exclude 词命中时若同时有 doc_type 应如何处理」的精确算法(现为:排除词优先否决,即便同时含文档类型词)。后端 dev1 实现若与我对齐无碍;若有差异需按用例表裁决。
7. **session_caps 未加载时能力徽标不显示**:QA 能力徽标依赖 settings store 已加载 manifest(现状是只有打开设置面板才 load)。若希望问答时徽标始终准确,需在会话创建/发送时触发 settings.load——涉及 settings store 调用时机,交 TL 裁决。
## 四、需要 TL 裁决/联动的问题
1. **b3 benchpanel.test.js 需更新**(测试 agent 归属):第 5 个「文档」TAB 是任务书 J 要求的行为变化,该断言「tabs 长度为 4」需改为 5 并追加文档 TAB 用例。
2. **B1 的 .bench-collapse-btn 保留方案确认**:旧折叠按钮保留 DOM 但 CSS display:none(为了不破坏 b1 appshell.test.js 的折叠断言)。这是「视觉退役但保留 DOM」的折中,若 TL 希望彻底移除并同步更新 b1 测试,请告知。
3. **右栏拖宽覆盖 laptop 档(1024–1279px)固定 340px 列宽**:P3 用 .app-shell 内联 grid-template-columns 覆盖了 responsive.css 的 laptop 档固定列宽(用户拖宽优先于档位默认收窄);B1 原「laptop 自动收窄 340px」的行为被替换为「保持 380px/用户宽度」。如需保留档位收窄行为,需 B1 联动确认。
4. **能力徽标显示时机**(见限制 7):settings store 的 load 时机是否要与会话创建/发送联动。
5. **artifact 块落库的持久化结构**:前端落库 artifact 块用 {type:'artifact', artifact_id, title, template_name, status};P2 的 schemas.py 定义了 ArtifactBlock 字段(artifact_id/title/template_name/status),前端已按此对齐,请 dev2 联调时确认无出入。
6. **api/docgen.js 端点契约确认**:uploadLibrary 返回 items[].library_id(P2 契约);若 dev2 实际实现返回结构不同,需按实际调整 docgen.js 与 Composer 的 chip 解析(集中在 api 层与 Composer.uploadOne 两处)。
## 五、TL 裁决跟进(2026-09-03 微补丁)
- 裁决 1/2/3/5/6:无需改动(测试 agent 更新 b3 测试、保留 DOM 折中接受、拖宽优先于档位收窄列入 R1 已知行为变化、契约已核实对齐)。
- 裁决 4 已实施:Composer.vue onMounted 增加「manifest 为空时静默 load 一次」(manifest 为空时 settings.load(sessionId),sessionId 取自 chat store,失败静默;不碰 send/_runStream 内部)。自测:@vue/compiler-sfc 编译 7/7 通过;b2 components.test.js + b1 appshell.test.js 27/27 通过。
## 六、R1 修复记录(2026-09-03)
TL 审阅 R1 结果定性 2 个 bug,已修复并自测:
1. **BUG-2(FAIL,已修)**:DocPane 编辑模式 contenteditable 未接线——原实现误以为 CSS 能加 HTML 属性(注释「由 :deep 样式承担」不成立),编辑模式实际不可编辑。修复:DocPane.vue 新增 `wireContenteditable()`,`enterEdit` 与 `onEditBlur` 重建后在 nextTick 遍历 editRoot.children 程序化设置 contenteditable="true"(h1-h6/p/pre/blockquote 给元素本身,ul/ol 给 li,table 给 tr),不改 renderMarkdown 全局。附带:onEditBlur 加 30ms 防抖,避免 blur→重建→focus 迁移的二次 blur 重复序列化。
2. **BUG-3(契约缝隙,已修)**:后端 ArtifactOut 无 markdown 字段,生成成功切 TAB 显示「内容不可用」。修复:docgen store 的 adoptArtifact 改为 `adoptArtifact(artifact, {initialMd})`——无本地 md 时自动调 getArtifactContent 拉取后再渲染;chat.js sendDocgen 成功路径把 payload.markdown(若有)作为 initialMd 传入。
3. **BUG-4(顺带修)**:open 同 id 短路不拉取。修复:同 id 且有本地内容且非 force 才复用;切换不同文档或 force 时清旧 md 再拉取(避免旧内容残留与误判)。删除废弃的 setContent(由 initialMd 取代)。
自测结果:
- node --check:chat.js / docgen.js / intent.js / api 层全部通过
- @vue/compiler-sfc 编译检查:7 个 .vue 全部通过
- vitest 全量:**173 / 173 通过**(测试 agent 更新后的 b3 用例含文档 TAB)
- R1 临时 mount 级自测(不落仓库,跑完即删):DocPane 编辑接线(h1/p/li contenteditable=true)、blur 序列化重建后仍接线、adoptArtifact 自动拉取、open 短路语义 4 条全部通过
## 七、R2 修复记录(2026-09-03)
TL 审阅 R2 结果定性 DC-UI-08 为修复不完整(真实 bug):根 div 挂 @blur 收不到事件——**blur 不冒泡**,子 contenteditable 块失焦时 onEditBlur 永远不触发(真实用户流程 PATCH 次数=0)。已修复并自测:
1. **事件监听**:`@blur` → `@focusout`(focusout 冒泡,根 div 可收到所有子块失焦)。onEditBlur 增加 relatedTarget 判断:`editRoot.contains(e.relatedTarget)` 时 = 块间焦点迁移,不序列化(防重建打断输入)。
2. **焦点恢复**:失焦块在顶层 children 的索引被记录,序列化重建后 nextTick 恢复焦点到同索引块(体验:焦点不丢失)。
3. **退出编辑 flush**:点「预览」退出时 focusout 定时器会因 editing=false 被跳过——exitEdit 现在同步序列化 flush 最后一次未保存编辑(不 flush 会丢字)。
自测结果:
- @vue/compiler-sfc 编译检查:7 个 .vue 全部通过
- vitest 全量:**173 / 173 通过**
- R2 临时行为测试(mount 级,不落仓库,跑完即删)3/3:
- 块间焦点切换(relatedTarget 在编辑区内)不触发序列化保存
- 失焦到编辑区外触发一次序列化 + PATCH(debounce 合并),重建后仍接线
- 退出编辑 flush 最后一次未保存编辑
+6 -2
View File
@@ -31,10 +31,14 @@ rsync -a --exclude '__pycache__' --exclude 'test_*.py' --exclude 'data' --exclud
"$SOURCE_ROOT/" "$OUTPUT_DIR/"
# 白名单明文:main.py + config.py + __init__.py(编译前从发现集排除)
WHITELIST_PY=("app/main.py" "app/config.py" "app/schemas.py")
WHITELIST_PY=("app/main.py" "app/config.py" "app/schemas.py" "app/db.py" "app/routers/qa.py" "app/services/sse_relay.py")
# 白名单理由:main.py=uvicorn 入口;config.py=运维可读面;
# schemas.py=pydantic v2 模型(validator 方法被 Cython 转 cyfunction 触发
# PydanticUserError,实测编译后无法导入——纯数据契约面,无敏感逻辑,明文保留)
# PydanticUserError,实测编译后无法导入——纯数据契约面,无敏感逻辑,明文保留);
# db.py=2026-09-03 断流崩溃根治:客户端中途断开流时 Cython 协程
# __Pyx_Coroutine_SendEx 断言 abort 整个 BFF(Cython 3.2.8/3.3.0 均复现,
# A/B 证据见 docs/team/deploy_plan.md 附档)。db 是崩溃协程热点,
# 降级明文 Python 走 CPython 原生协程取消,根治断言崩溃
_is_whitelisted() {
local rel="$1"
for w in "${WHITELIST_PY[@]}"; do [[ "$rel" == "$w" ]] && return 0; done