diff --git a/.claudeignore b/.claudeignore new file mode 100644 index 0000000..fbfdd50 --- /dev/null +++ b/.claudeignore @@ -0,0 +1 @@ +.claude/ vendor/ diff --git a/.gitignore b/.gitignore index b745f03..3602b17 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ dist/ .venv/ .venv-backend/ .env -backend/data/ +sdk/data/ __pycache__/ *.pyc .pytest_cache/ @@ -15,3 +15,4 @@ coverage/ **/data/ .DS_Store +tooling/build/ diff --git a/README.md b/README.md index bed7017..bc3dfa9 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,14 @@ ## 目录 ``` -backend/ BFF(app/ 应用代码,mock/ 前端开发用 mock server) -frontend/ Vue 3 + Vite 前端(src/ 源码) -docs/ 全部文档:PRD / API / UI_SPEC / ARCHITECTURE / TEST_PLAN / TEST_CASES / DISCOVERY / 测试报告 -deploy/ 部署脚本:sync.sh(本地→远端)、bff_restart.sh、start-dev.sh、集成检查、E2E 验收 -tests/ 测试(backend 接口、frontend 单元、e2e) +sdk/ BFF(app/ 应用代码,mock/ 前端开发用 mock server) +ui/ Vue 3 + Vite 前端(src/ 源码) +agent/ 底层服务:qa / methodology / legacy-rag + vendor 共享池(编译交付) +knowledge/ 向量知识库打包定义(数据不入 git) +tooling/ 统一打包工具(编译/加密/组装) +docs/ 全部文档:PRD / API / UI_SPEC / ARCHITECTURE / TEST_PLAN / TEST_CASES / DISCOVERY / 测试报告 +deploy/ 部署脚本:sync.sh(本地→远端)、bff_restart.sh、start-dev.sh、集成检查、E2E 验收 +tests/ 测试(sdk 接口、ui 单元、e2e) ``` ## 快速启动(远端) @@ -30,7 +33,7 @@ tests/ 测试(backend 接口、frontend 单元、e2e) ssh 100.109.52.26 'bash ~/5090/teamWorkspace/zh/PlantAI-rag-new/deploy/bff_restart.sh' # 2. 启动 Vite 开发服务器(开发模式) -ssh 100.109.52.26 'cd ~/5090/teamWorkspace/zh/PlantAI-rag-new/frontend && \ +ssh 100.109.52.26 'cd ~/5090/teamWorkspace/zh/PlantAI-rag-new/ui && \ VITE_BFF_TARGET=http://127.0.0.1:8090 setsid nohup npm run dev > /tmp/plantai-vite.log 2>&1 < /dev/null & disown' # 3. Mac 侧访问隧道 @@ -38,7 +41,7 @@ ssh -N -L 5180:127.0.0.1:5180 -L 8090:127.0.0.1:8090 100.109.52.26 # 浏览器打开 http://localhost:5180 ``` -生产形态:`npm run build` 产出 `frontend/dist`,BFF 启动时自动静态托管,单进程从 `:8090` 直接服务。 +生产形态:`npm run build` 产出 `ui/dist`,BFF 启动时自动静态托管,单进程从 `:8090` 直接服务。 ## 开发工作流 @@ -52,10 +55,10 @@ ssh -N -L 5180:127.0.0.1:5180 -L 8090:127.0.0.1:8090 100.109.52.26 ```bash # 后端(conda plantai-bff 或本地 .venv-backend) -cd backend && python -m pytest . -q # 单元 + 接口 +cd sdk && python -m pytest . -q # 单元 + 接口 # 前端 -cd frontend && npx vitest run --config vitest.config.js # 单元 + 组件 -cd frontend && npx playwright test # E2E 三档视口(mobile/tablet/desktop) +cd ui && npx vitest run --config vitest.config.js # 单元 + 组件 +cd ui && npx playwright test # E2E 三档视口(mobile/tablet/desktop) # 上游连通冒烟 bash tests/smoke_upstreams.sh ``` diff --git a/agent/README.md b/agent/README.md new file mode 100644 index 0000000..2cc61e9 --- /dev/null +++ b/agent/README.md @@ -0,0 +1,10 @@ +# agent/ —— 底层服务三件套 + +| 目录 | 服务 | 端口 | 入口(白名单明文) | conda | +|---|---|---|---|---| +| qa/ | QA V1.1.73 断言锚定检索 | 7920(影子 7921) | scripts/qa_v1_1_73_server.py | gfpy312 | +| methodology/ | Methodology 提取 | 8000(影子 8001) | scripts/methodology_server.py | gfpy312 | +| legacy-rag/ | 旧路由族(11 路由) | 7910(影子 7911) | api/main.py | test4py311 | +| vendor/ | 共享源码池(非服务) | — | — | — | + +约束:仅 shell 启动(`scripts/{start,stop,status,restart}.sh`);编译产物 `.so` 隐藏源码;`compile.manifest` 记录模块级白名单。vendor 为源码单份,编译时复制进各 agent 暂存目录。 diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 1689a30..0000000 --- a/backend/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# backend/.gitignore(最小版,各 agent 勿改,如需调整向 PM 提出) -__pycache__/ -*.pyc -.pytest_cache/ -.env -data/ diff --git a/deploy/bff_restart.sh b/deploy/bff_restart.sh index 3b84e27..ab5c6cc 100644 --- a/deploy/bff_restart.sh +++ b/deploy/bff_restart.sh @@ -13,8 +13,8 @@ conda activate plantai-bff pkill -f "[u]vicorn app.main:app" 2>/dev/null sleep 2 -cd "$ROOT/backend" -# 加载运行时凭据(backend/.env,不入 git)并导出给应用进程。 +cd "$ROOT/sdk" +# 加载运行时凭据(sdk/.env,不入 git)并导出给应用进程。 # pydantic-settings 会把 .env 载入 settings 对象,但 services/neo4j.py 的 # _env() 直接读 os.environ,故需显式 set -a 导出。 set -a @@ -29,7 +29,7 @@ if ss -tln | grep -q ":8090 "; then echo "BFF UP ($BIND:8090)" curl -s --max-time 20 http://127.0.0.1:8090/api/health | head -c 200 echo - # 生产形态:静态托管 frontend/dist(存在时 BFF 单进程服务整个前端) + # 生产形态:静态托管 ui/dist(存在时 BFF 单进程服务整个前端) curl -s --max-time 10 http://127.0.0.1:8090/ | grep -o "[^<]*" || echo "(静态托管未生效,需同步 dist)" else echo "BFF FAIL —— 日志尾部:" diff --git a/deploy/m5_acceptance.js b/deploy/m5_acceptance.js index d3d3678..006c51d 100644 --- a/deploy/m5_acceptance.js +++ b/deploy/m5_acceptance.js @@ -26,7 +26,7 @@ async function waitFor(fn, ms, iv = 700, label = '') { const page = await ctx.newPage(); const errors = []; page.on('pageerror', (e) => errors.push(e.message)); - // BFF 静态托管需 frontend/dist 存在且 BFF 重启——先探测是否已挂载 + // BFF 静态托管需 ui/dist 存在且 BFF 重启——先探测是否已挂载 const prod = await page.goto('http://127.0.0.1:8090/', { waitUntil: 'domcontentloaded' }).then(() => true).catch(() => false); if (prod) { await waitFor(() => page.locator('.app-shell').count(), 15000, 500, '生产形态 AppShell'); diff --git a/deploy/neo4j_recreate.sh b/deploy/neo4j_recreate.sh index 3c20ac2..a622f4c 100644 --- a/deploy/neo4j_recreate.sh +++ b/deploy/neo4j_recreate.sh @@ -20,9 +20,9 @@ CONTAINER="neo4j-plant-srt-direct-v1-full" IMAGE="neo4j:5.15.0-community" DATA_ROOT="/home/xzq/home/wutx/gene-family-mas-v2/data/neo4j_plant_srt_direct_v1_docker" RAG_DIR="/home/xzq/5090/PlanAI-cooperate/RAG/rag-develop" -ENV_FILE="/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/backend/.env" +ENV_FILE="/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/sdk/.env" -# 1. 从 backend/.env 读真实密码(容器 Env 里的初始值是 password,运行时已改,必须用真实的) +# 1. 从 sdk/.env 读真实密码(容器 Env 里的初始值是 password,运行时已改,必须用真实的) NEO4J_PASSWORD=$(grep -oE "^BFF_NEO4J_PASSWORD_DIRECT=.*" "$ENV_FILE" | cut -d= -f2) if [ -z "$NEO4J_PASSWORD" ]; then echo "ERROR: 未在 $ENV_FILE 找到 BFF_NEO4J_PASSWORD_DIRECT" diff --git a/deploy/start-dev.sh b/deploy/start-dev.sh index 8b1dba9..6501e90 100644 --- a/deploy/start-dev.sh +++ b/deploy/start-dev.sh @@ -3,10 +3,10 @@ set -euo pipefail ROOT="/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new" echo "== 启动 BFF (127.0.0.1:8090) ==" -(cd "$ROOT/backend" && source ~/miniconda3/etc/profile.d/conda.sh && conda activate plantai-bff && \ +(cd "$ROOT/sdk" && source ~/miniconda3/etc/profile.d/conda.sh && conda activate plantai-bff && \ nohup uvicorn app.main:app --host 127.0.0.1 --port 8090 > /tmp/plantai-bff.log 2>&1 &) echo "== 启动 Vite dev (127.0.0.1:5180) ==" -(cd "$ROOT/frontend" && nohup npm run dev > /tmp/plantai-vite.log 2>&1 &) +(cd "$ROOT/ui" && nohup npm run dev > /tmp/plantai-vite.log 2>&1 &) sleep 3 curl -s http://127.0.0.1:8090/api/health > /dev/null && echo "BFF OK" || echo "BFF 未就绪,见 /tmp/plantai-bff.log" curl -s http://127.0.0.1:5180 > /dev/null && echo "Vite OK" || echo "Vite 未就绪,见 /tmp/plantai-vite.log" diff --git a/deploy/sync.sh b/deploy/sync.sh index e68a2de..06dbdd8 100644 --- a/deploy/sync.sh +++ b/deploy/sync.sh @@ -10,7 +10,7 @@ LOCAL_DIR="$(cd "$(dirname "$0")/.." && pwd)" rsync -az --delete -e "ssh $SSH_OPTS" \ --exclude 'node_modules' --exclude '__pycache__' --exclude '.venv' \ --exclude '.venv-backend' \ - --exclude 'backend/data' --exclude 'frontend/dist' --exclude '.pytest_cache' \ + --exclude 'sdk/data' --exclude 'ui/dist' --exclude 'tooling/build' --exclude 'knowledge' --exclude '.pytest_cache' \ --exclude '.env' \ --exclude '.git' \ "$LOCAL_DIR/" "$REMOTE_HOST:$REMOTE_DIR/" 2>&1 | grep -v "remote port forwarding" || true diff --git a/deploy/vite_restart.sh b/deploy/vite_restart.sh index f09a1a7..7c7426e 100644 --- a/deploy/vite_restart.sh +++ b/deploy/vite_restart.sh @@ -12,7 +12,7 @@ TARGET="${1:-8090}" pkill -f "node_modules/.bin/[v]ite" 2>/dev/null sleep 2 -cd "$ROOT/frontend" +cd "$ROOT/ui" VITE_BFF_TARGET="http://127.0.0.1:${TARGET}" \ setsid npm run dev > /tmp/plantai-vite.log 2>&1 < /dev/null & disown diff --git a/docs/ACCEPTANCE.md b/docs/ACCEPTANCE.md index 247686c..dcdd116 100644 --- a/docs/ACCEPTANCE.md +++ b/docs/ACCEPTANCE.md @@ -40,7 +40,7 @@ - 验收脚本:`deploy/m5_acceptance.js`(15 断言,chromium headless) - 运行位置:远端(真实 BFF 8090 生产形态 + Vite 5180 开发形态双链路) - 用时:约 3 分钟(含一次真实 QA ≈ 50s) -- 期间修复:BFF 静态托管 dist 路径解析(`backend/frontend/dist` → 项目根解析,2026-08-25 PM) +- 期间修复:BFF 静态托管 dist 路径解析(`sdk/ui/dist` → 项目根解析,2026-08-25 PM) ## 5. 结论 @@ -49,4 +49,4 @@ 下一步建议(用户决策,不在本次范围): - DeepSeek 充值 → 验证自然语言答案质量 - 长期运行监控(/api/health 轮询、上游探活) -- 可选:laptop 档 E2E project 补齐、tests/backend L2 respx 独立测试 +- 可选:laptop 档 E2E project 补齐、tests/sdk L2 respx 独立测试 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8e0941b..a314b5d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -61,11 +61,11 @@ | BFF 监听 | `127.0.0.1:8090`(占用则退 8091) | | Vite dev | `127.0.0.1:5180`(占用则退 5181) | | BFF conda 环境 | `plantai-bff`(Python 3.11,新建,**不复用** `gfpy312`) | -| SQLite 路径 | `backend/data/plantai.db`(WAL 模式) | +| SQLite 路径 | `sdk/data/plantai.db`(WAL 模式) | | 前端工具链 | Vite **5.4.x** + @vitejs/plugin-vue **5.2.x** + Vue **3.5.x** + Pinia **2.3.x** | | Node | v18.19.1(已 EOL)——**禁止升级 Vite 至 7**(需 Node 20+),**禁止用 `npm create vite`** | -生产形态:`npm run build` 产出 `frontend/dist`,BFF 用 `StaticFiles` 挂载,单进程从 `:8090` 提供服务。 +生产形态:`npm run build` 产出 `ui/dist`,BFF 用 `StaticFiles` 挂载,单进程从 `:8090` 提供服务。 --- @@ -83,7 +83,7 @@ ```bash rsync -az --delete \ --exclude 'node_modules' --exclude '__pycache__' --exclude '.venv' \ - --exclude 'backend/data' --exclude 'frontend/dist' --exclude '.pytest_cache' \ + --exclude 'sdk/data' --exclude 'ui/dist' --exclude '.pytest_cache' \ /Users/jerry/haiguji/PlantAI-rag-new/ \ 100.109.52.26:/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new/ ``` @@ -91,7 +91,7 @@ rsync -az --delete \ **规则**: - 所有 agent **只在本地路径写文件**,绝不用 SSH heredoc 改代码。 - 同步由 **PM 统一执行**,agent 不自行 rsync(避免半成品覆盖远端)。 -- `backend/data`(数据库)、`node_modules`、`dist` 不同步,属远端运行时产物。 +- `sdk/data`(数据库)、`node_modules`、`dist` 不同步,属远端运行时产物。 - 文档最终必须在远端 `docs/` 下可查——每次里程碑结束由 PM 同步一次。 --- @@ -102,17 +102,17 @@ rsync -az --delete \ | Agent | 职责 | 独占路径 | |---|---|---| -| **PM** | 契约文档、集成、缺陷派发、验收、rsync | `docs/{PRD,API,UI_SPEC,ARCHITECTURE,TEST_PLAN,ACCEPTANCE,CHANGELOG}.md`、`deploy/**`、`src/i18n/labels.js`、`src/config/modes.js`、`backend/mock/**` | +| **PM** | 契约文档、集成、缺陷派发、验收、rsync | `docs/{PRD,API,UI_SPEC,ARCHITECTURE,TEST_PLAN,ACCEPTANCE,CHANGELOG}.md`、`deploy/**`、`src/i18n/labels.js`、`src/config/modes.js`、`sdk/mock/**` | | **D0 探测** | 上游探测与选型 | `docs/DISCOVERY.md` | -| **A1 后端·骨架** | app 工厂、配置、SQLite、会话与消息 CRUD、归一化器、错误信封 | `backend/pyproject.toml`、`backend/run.sh`、`backend/.env.example`、`backend/app/{main,config,db,schemas,upstream}.py`、`backend/app/routers/{health,sessions}.py`、`backend/app/services/normalize.py` | -| **A2 后端·QA** | QA 代理、SSE 中继、阶段映射、重试与看门狗 | `backend/app/routers/qa.py`、`backend/app/services/sse_relay.py` | -| **A3 后端·图谱与抽取** | Neo4j Bolt 直连、抽取代理与回落 | `backend/app/routers/{kg,extraction}.py`、`backend/app/services/{neo4j,extraction}.py` | -| **B1 前端·基础** | 令牌层、AppShell、四档响应式框架、主题、顶栏/底栏、通用 UI、健康轮询 | `frontend/{package.json,vite.config.js,vitest.config.js,index.html}`、`frontend/src/{main.js,App.vue}`、`frontend/src/styles/**`、`frontend/src/components/{layout,ui}/**`、`frontend/src/stores/{theme,health}.js`、`frontend/src/api/http.js`、`tests/frontend/unit/b1/**` | -| **B2 前端·会话与对话** | 会话列表与抽屉、对话区、作曲器、流水线卡、SSE 客户端 | `frontend/src/components/{session,chat}/**`、`frontend/src/stores/{sessions,chat}.js`、`frontend/src/sse/**`、`frontend/src/api/{qa,sessions}.js`、`tests/frontend/unit/b2/**` | -| **B3 前端·工作台** | 右侧四个 tab:检索过程、文献来源、提取任务、知识图谱 | `frontend/src/components/bench/**`、`frontend/src/stores/bench.js`、`frontend/src/api/{kg,extraction}.js`、`tests/frontend/unit/b3/**` | -| **T 测试** | 用例编写与执行、缺陷报告 | `tests/backend/api/**`、`tests/e2e/**`、`playwright.config.js`、`docs/TEST_CASES.md`、`docs/TEST_REPORT_*.md` | +| **A1 后端·骨架** | app 工厂、配置、SQLite、会话与消息 CRUD、归一化器、错误信封 | `sdk/pyproject.toml`、`sdk/run.sh`、`sdk/.env.example`、`sdk/app/{main,config,db,schemas,upstream}.py`、`sdk/app/routers/{health,sessions}.py`、`sdk/app/services/normalize.py` | +| **A2 后端·QA** | QA 代理、SSE 中继、阶段映射、重试与看门狗 | `sdk/app/routers/qa.py`、`sdk/app/services/sse_relay.py` | +| **A3 后端·图谱与抽取** | Neo4j Bolt 直连、抽取代理与回落 | `sdk/app/routers/{kg,extraction}.py`、`sdk/app/services/{neo4j,extraction}.py` | +| **B1 前端·基础** | 令牌层、AppShell、四档响应式框架、主题、顶栏/底栏、通用 UI、健康轮询 | `ui/{package.json,vite.config.js,vitest.config.js,index.html}`、`ui/src/{main.js,App.vue}`、`ui/src/styles/**`、`ui/src/components/{layout,ui}/**`、`ui/src/stores/{theme,health}.js`、`ui/src/api/http.js`、`tests/ui/unit/b1/**` | +| **B2 前端·会话与对话** | 会话列表与抽屉、对话区、作曲器、流水线卡、SSE 客户端 | `ui/src/components/{session,chat}/**`、`ui/src/stores/{sessions,chat}.js`、`ui/src/sse/**`、`ui/src/api/{qa,sessions}.js`、`tests/ui/unit/b2/**` | +| **B3 前端·工作台** | 右侧四个 tab:检索过程、文献来源、提取任务、知识图谱 | `ui/src/components/bench/**`、`ui/src/stores/bench.js`、`ui/src/api/{kg,extraction}.js`、`tests/ui/unit/b3/**` | +| **T 测试** | 用例编写与执行、缺陷报告 | `tests/sdk/api/**`、`tests/e2e/**`、`playwright.config.js`、`docs/TEST_CASES.md`、`docs/TEST_REPORT_*.md` | -后端各 agent 的单测放在各自归属子树内(`backend/app/**/test_*.py`,随代码一起交付),运行 `pytest backend/app -q`;T 的 L2 接口测试与 L5 e2e 放在 `tests/`。 +后端各 agent 的单测放在各自归属子树内(`sdk/app/**/test_*.py`,随代码一起交付),运行 `pytest sdk/app -q`;T 的 L2 接口测试与 L5 e2e 放在 `tests/`。 ### 跨归属的接缝 @@ -131,10 +131,10 @@ rsync -az --delete \ ## 6. 并行解耦:mock server -前端三个 agent **不等待后端**。M0 阶段 PM 产出 `backend/mock/mock_server.py`: +前端三个 agent **不等待后端**。M0 阶段 PM 产出 `sdk/mock/mock_server.py`: - FastAPI,监听 `127.0.0.1:8099` -- 实现 `API.md` 中**全部**接口,返回 `backend/mock/fixtures/*.json` 里的固定数据 +- 实现 `API.md` 中**全部**接口,返回 `sdk/mock/fixtures/*.json` 里的固定数据 - 包含一条**可回放的 SSE 事件序列**(按真实时序 sleep),让流式 UI 能在无上游时开发 - 支持 `?scenario=` 查询参数切换场景:`normal` / `empty`(空结果)/ `slow`(慢响应)/ `error`(上游故障)/ `degraded`(部分降级) @@ -166,7 +166,7 @@ PlantAI-rag-new/ ├── deploy/ │ ├── sync.sh # 本地 → 远端 rsync │ └── start-dev.sh # 拉起 BFF + vite,打印隧道命令 -├── backend/ +├── sdk/ │ ├── pyproject.toml run.sh .env.example │ ├── data/ # plantai.db(不入库、不同步) │ ├── mock/ @@ -176,7 +176,7 @@ PlantAI-rag-new/ │ ├── main.py config.py db.py schemas.py upstream.py │ ├── routers/ health.py sessions.py qa.py kg.py extraction.py │ └── services/ normalize.py sse_relay.py neo4j.py extraction.py -├── frontend/ +├── ui/ │ ├── package.json vite.config.js index.html vitest.config.js │ └── src/ │ ├── main.js App.vue @@ -192,8 +192,8 @@ PlantAI-rag-new/ │ ├── chat/ ChatPanel、MessageList、Composer、PipelineCard、ToolCard … │ └── bench/ BenchPanel、RetrievalPane、SourcesPane、ExtractionPane、KgPane … ├── tests/ -│ ├── backend/ # pytest -│ ├── frontend/ # vitest +│ ├── sdk/ # pytest +│ ├── ui/ # vitest │ └── e2e/ # playwright └── playwright.config.js ``` diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 38616d3..690d163 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,8 +5,8 @@ - **计划批准**:新前端 + 薄适配层方案获批。端口 8090(BFF)/ 5180(Vite dev),并行部署不切换。 - **M0 启动**:远端目录树建立;8090/5180 端口复核空闲;本地 staging 目录 `/Users/jerry/haiguji/PlantAI-rag-new/` 建立。 - **契约文档定稿**:`PRD.md`(81 个 FR)、`API.md`、`UI_SPEC.md`、`ARCHITECTURE.md`、`TEST_PLAN.md` 完成并锁定。 -- **Mock 层完成**:`backend/mock/mock_server.py` + 21 个 fixtures(含 SSE 回放序列)。 -- **共享前端文件完成**:`frontend/src/i18n/labels.js`、`frontend/src/config/modes.js`。 +- **Mock 层完成**:`sdk/mock/mock_server.py` + 21 个 fixtures(含 SSE 回放序列)。 +- **共享前端文件完成**:`ui/src/i18n/labels.js`、`ui/src/config/modes.js`。 - **环境就绪**:`plantai-bff` conda 环境(3.11);npm 依赖 187 包(Vite 5.4.11/Vue 3.5.13);Playwright + Chromium headless shell 131 下载完成;mock server 在 8099 运行并全接口验证通过。 - **D0 探测完成**(`DISCOVERY.md`,345 行)。**契约偏差修正**(基于真实上游): - Neo4j 当选 `neo4j-plant-srt-direct-v1-full`(bolt 18788,QA 7920 实际使用)。schema 为 **canonical**(`SRT2CanonicalEntity`/`SRT2CanonicalAssertion`),非假设的 `:Entity`;**无全文索引**,名字检索走 `name_norm` 前缀索引(0.09s)。 @@ -21,8 +21,8 @@ - **M0 验收达成**:契约文档、mock(8099 运行中且已按真实上游对齐:canonical schema / queued-runing 派生进度 / run-log JSON)、conda 环境、npm 依赖、Playwright、本地 staging + 远端副本、BFF 骨架 import 通过。 - **本地开发环境补齐**:`.venv-backend`(3.12,供后端 agent 本地自测)+ 本地 node_modules;`deploy/sync.sh` 排除规则修正(`.venv-backend`)。 -- **跨归属占位脚手架**:backend/app 全部模块 + frontend main.js/App.vue 占位,由各 agent 在 M1 以真实实现替换。 -- **M1 发射**:A1/A2/A3(后端三线)+ B1/B2/B3(前端三线)+ T(测试用例)七个 agent 并行。前端三入口占位组件已由 PM 提供;文件归属含测试目录拆分(`tests/frontend/unit/b1|b2|b3`,后端单测随代码 `backend/app/**/test_*.py`)。 +- **跨归属占位脚手架**:sdk/app 全部模块 + ui main.js/App.vue 占位,由各 agent 在 M1 以真实实现替换。 +- **M1 发射**:A1/A2/A3(后端三线)+ B1/B2/B3(前端三线)+ T(测试用例)七个 agent 并行。前端三入口占位组件已由 PM 提供;文件归属含测试目录拆分(`tests/ui/unit/b1|b2|b3`,后端单测随代码 `sdk/app/**/test_*.py`)。 - **A1 完成**(15 文件,29 单测全过):config/db/schemas/normalize/upstream/health/sessions/main + pyproject + run.sh + .env.example。7 条偏差全部 PM 裁决通过并回填 API.md(hop 中间态字段、来源聚合取首条、search answer 空串、PATCH 空 body 幂等、title_manual 保护、凭据仅入 BFF 配置、新增 python-multipart)。 - **待办**:A1 报告 A3 的 17 个测试失败(`test_kg.py` 用 ASGITransport 直挂裸 router 与 fastapi 0.141 不兼容;`test_extraction.py` 两处 respx 失败)——待 A3 汇报后统一派发。 - **B3 完成**(工作台四 tab,54 单测全过,`vite build` 通过):BenchPanel/RetrievalPane/SourcesPane/SourceRow/ChainGroup/ExtractionPane/ProgressItem/KgPane/KgGraphView(手写 SVG 力导向,无第三方库)/KgEntityDetail/graphLayout/bench store/两 API 模块。15 条工作台文案已由 PM 合并进 labels.js,benchLabels.js 改为薄别名。 @@ -62,7 +62,7 @@ - **R1 修复集成(PM)**:本地全量回归后端 121 测/前端 156 测全绿;mock 本地远端 md5 一致;BFF 重启后 health 无索引误报;R2 全量回归已派发 T。 - **R2 全量回归通过(T)**:后端 121/121、前端 156/156、Playwright 48 条(35 pass/12 skip 依赖充值/1 flake 复跑通过)、上游 smoke 7/7、行覆盖 87%/80.59%、FR 追溯 81/81。**R1 八缺陷全确认修复**(含 E2E-01 刷新恢复补验三档全通)。无新缺陷。唯一失败为环境性 flake(tablet 主流程 300s 超时,单独复跑 52.3s 通过,上游 QA 串行排队所致)。T 清理了全部测试污染会话数据。 - **M4 判定(PM)**:主/辅门禁全达标、产品用例全 pass → 按 TEST_PLAN 纪律收尾。收尾前采纳两处加固:① E2E 真实流超时 480s/终态等待 360s(T 测试代码);② Vite 忽略 coverage 目录(测试期覆盖率文件触发 198 次页面重载,干扰 E2E 与浏览器访问)。 -- **M5 最终验收通过(PM,2026-08-25)**:`deploy/m5_acceptance.js` 15/15 全过。生产形态打通(BFF 8090 单进程静态托管,修复 dist 路径解析:backend/frontend/dist → 项目根;sync 排除 dist 需单独同步)。真实 SQLite 历史恢复(相位卡 5/5、引用 chip 高亮定位)、真实问答全链路(答案 698 字 + 4 引用)、三档视口档位全正确。`docs/ACCEPTANCE.md` 归档。**项目收官**——遗留唯一外部依赖:DeepSeek 余额(充值即恢复,零代码改动)。 +- **M5 最终验收通过(PM,2026-08-25)**:`deploy/m5_acceptance.js` 15/15 全过。生产形态打通(BFF 8090 单进程静态托管,修复 dist 路径解析:sdk/ui/dist → 项目根;sync 排除 dist 需单独同步)。真实 SQLite 历史恢复(相位卡 5/5、引用 chip 高亮定位)、真实问答全链路(答案 698 字 + 4 引用)、三档视口档位全正确。`docs/ACCEPTANCE.md` 归档。**项目收官**——遗留唯一外部依赖:DeepSeek 余额(充值即恢复,零代码改动)。 ## DeepSeek 恢复与 QA 修复(2026-08-25 下午) diff --git a/docs/TEST_CASES.md b/docs/TEST_CASES.md index addfd09..fc52d8a 100644 --- a/docs/TEST_CASES.md +++ b/docs/TEST_CASES.md @@ -603,24 +603,24 @@ ```bash # L1 后端单元(远端 plantai-bff conda 环境;A 组自测,T 复跑确认) -cd backend && pytest tests -m unit -q +cd sdk && pytest tests -m unit -q # L2 后端接口(respx mock 上游;用例见 L2 标注项) -cd backend && pytest tests -m api -q +cd sdk && pytest tests -m api -q # L2s 上游 smoke(仅集成点 M2 与验收前手动各跑一次;远端本机执行) bash tests/smoke_upstreams.sh -# L3+L4 前端(vitest;远端 frontend/) -cd frontend && npx vitest run +# L3+L4 前端(vitest;远端 ui/) +cd ui && npx vitest run # L5 E2E(三档视口;dev server 由 playwright webServer 自动拉起或复用已启动的 5180) -cd frontend && npx playwright test # playwright 未安装时:npx playwright@1.49.1 test +cd ui && npx playwright test # playwright 未安装时:npx playwright@1.49.1 test ``` ### 3.2 Mock 场景使用(ARCHITECTURE.md §6) -mock server:`python backend/mock/mock_server.py`(127.0.0.1:8099),场景经查询参数或请求头 `X-Mock-Scenario` 切换。E2E 中由测试代码在请求头注入或 mock 路由拦截实现: +mock server:`python sdk/mock/mock_server.py`(127.0.0.1:8099),场景经查询参数或请求头 `X-Mock-Scenario` 切换。E2E 中由测试代码在请求头注入或 mock 路由拦截实现: | scenario | 用途 | 使用用例示例 | |---|---|---| @@ -643,14 +643,14 @@ mock server:`python backend/mock/mock_server.py`(127.0.0.1:8099),场景 ### 3.4 L5 运行环境一次性准备(已验证) - **Node 18(远端)**:`npx playwright@1.49.1 test` 首次运行自动下载 chromium 1148(≈150MB)。实测 CDN 可达(Mac 直连 2.9MB/s)。 -- **`@playwright/test` 模块解析**:spec 文件位于 `tests/e2e/`,在 `frontend/node_modules` 之外,Node 默认解析路径找不到 `@playwright/test`,会报 `ERR_MODULE_NOT_FOUND`。**已在 Mac 本地验证**(playwright 1.61.1 实测复现;1.49.1 CJS 变换同样回退到默认 ESM 解析,机制相同)。运行处须一次性执行(不污染任何 agent 归属文件——`node_modules` 不入库不同步): +- **`@playwright/test` 模块解析**:spec 文件位于 `tests/e2e/`,在 `ui/node_modules` 之外,Node 默认解析路径找不到 `@playwright/test`,会报 `ERR_MODULE_NOT_FOUND`。**已在 Mac 本地验证**(playwright 1.61.1 实测复现;1.49.1 CJS 变换同样回退到默认 ESM 解析,机制相同)。运行处须一次性执行(不污染任何 agent 归属文件——`node_modules` 不入库不同步): ```bash # 远端,一次性 cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new - ln -sfn frontend/node_modules node_modules # 若目录已存在先确认其内容再覆盖 - cd frontend && npm install --save-dev @playwright/test@1.49.1 # 写入 package.json 需 PM 裁决,见 §5 未决问题 4 + ln -sfn ui/node_modules node_modules # 若目录已存在先确认其内容再覆盖 + cd ui && npm install --save-dev @playwright/test@1.49.1 # 写入 package.json 需 PM 裁决,见 §5 未决问题 4 ``` - Mac 本地本次验证的等价做法(未改动 `frontend/package.json`):安装到 `/tmp/pw-test-runner` 后 `ln -sfn /tmp/pw-test-runner/node_modules/@playwright/test frontend/node_modules/@playwright/test`,再 `ln -sfn frontend/node_modules <repo>/node_modules`,以 playwright 1.61.1(匹配本机已缓存 chromium-1228)跑通 6/6。 + Mac 本地本次验证的等价做法(未改动 `ui/package.json`):安装到 `/tmp/pw-test-runner` 后 `ln -sfn /tmp/pw-test-runner/node_modules/@playwright/test ui/node_modules/@playwright/test`,再 `ln -sfn ui/node_modules <repo>/node_modules`,以 playwright 1.61.1(匹配本机已缓存 chromium-1228)跑通 6/6。 - **本地 Node 26 已知问题**:playwright 1.49.1 的 `module.register()` loader 在 Node 26 + 系统高负载下启动极慢/挂起;远端 Node 18 无此问题。本地验证用 playwright 1.61.1 替代。 - **本机验证结果(M0 冒烟)**:`6 passed`(mobile/tablet/desktop × 2 条冒烟)。冒烟断言对 B1 已交付的 AppShell 走真实分支(`.app-shell/.top-bar/.status-bar`,移动端 `.status-bar` 按 FR-RESP-004 断言隐藏),M0 占位期走占位回退分支。 @@ -660,7 +660,7 @@ mock server:`python backend/mock/mock_server.py`(127.0.0.1:8099),场景 | 文件 | 内容 | |---|---| -| `frontend/playwright.config.js` | 三项目 mobile(iPhone 14 390×844,触摸+UA)/tablet(iPad 768×1024)/desktop(1440×900);chromium;baseURL `http://127.0.0.1:5180`;webServer `npm run dev -- --strictPort`(已有 dev 复用);失败保留 trace/screenshot | +| `ui/playwright.config.js` | 三项目 mobile(iPhone 14 390×844,触摸+UA)/tablet(iPad 768×1024)/desktop(1440×900);chromium;baseURL `http://127.0.0.1:5180`;webServer `npm run dev -- --strictPort`(已有 dev 复用);失败保留 trace/screenshot | | `tests/e2e/smoke.spec.js` | 2 条冒烟(TC-E2E-SMOKE-01/02),三档视口各跑一次共 6 条结果。AppShell 断言带 M0 占位回退(B1 交付后自动走真实分支,无需改测试) | | `tests/smoke_upstreams.sh` | L2s 冒烟:QA 7920 health/info、抽取 8000 health/jobs、7900 health、Neo4j(HTTP 18575 Cypher 只读查询)、mock 8099 health;PASS/FAIL 表 + 非零退出码;支持 `QA_URL/EXTRACT_URL/ALT_URL/MOCK_URL/NEO4J_HTTP/TIMEOUT` 环境变量覆盖 | @@ -671,5 +671,5 @@ mock server:`python backend/mock/mock_server.py`(127.0.0.1:8099),场景 1. 若干 UI 类断言依赖 UI_SPEC 类名(如 `agent-dot-item`、`p-step`);若 B1 实现时类名有偏差,需按 UI_SPEC §5.3 对齐(T 不自行改用例契约)。 2. TC-EXTRACT-005/007(上传进度/超限)依赖 B2/B3 上传 UI 与 50MB 校验落点(前端或 BFF)——已按「前端拦截或 422」双路径写预期,无需改用例。 3. TC-KG 节点详情在 Neo4j 不可用时的属性来源为「消息内 graph 块已有属性」;graph 块节点若不含属性字段,该断言在实现时需以实际块结构微调步骤(预期语义不变)。 -4. **L5 依赖安装需 PM 裁决**:`tests/e2e/` 的 spec 需要解析 `@playwright/test`,而它位于 `frontend/node_modules` 之外。两个方案二选一:(a) 远端一次性 `npm install --save-dev @playwright/test@1.49.1`(写入 B1 归属的 `frontend/package.json`,需 PM 批准);(b) 保持 `npx playwright@1.49.1` 并在远端建 `node_modules` 根符号链接 + `@playwright/test` 符号链接(不改 package.json,M2 前由 T/PM 在远端一次性执行)。本波按 (b) 在 Mac 本地验证通过(6/6 pass),建议远端集成点采用 (b)。 +4. **L5 依赖安装需 PM 裁决**:`tests/e2e/` 的 spec 需要解析 `@playwright/test`,而它位于 `ui/node_modules` 之外。两个方案二选一:(a) 远端一次性 `npm install --save-dev @playwright/test@1.49.1`(写入 B1 归属的 `ui/package.json`,需 PM 批准);(b) 保持 `npx playwright@1.49.1` 并在远端建 `node_modules` 根符号链接 + `@playwright/test` 符号链接(不改 package.json,M2 前由 T/PM 在远端一次性执行)。本波按 (b) 在 Mac 本地验证通过(6/6 pass),建议远端集成点采用 (b)。 5. **task 验证命令差异**:任务书给的验证命令 `npx playwright test`(无版本)在本机无已安装 playwright 时会从 npm 拉最新版;远端 Node 18 + `npx playwright@1.49.1` 仍会拉 1.49.1 的 chromium 1148(≈150MB,CDN 实测可达)。已在本文件 §3.4 记录完整步骤。 diff --git a/docs/TEST_PLAN.md b/docs/TEST_PLAN.md index ae783d4..9a1d7a7 100644 --- a/docs/TEST_PLAN.md +++ b/docs/TEST_PLAN.md @@ -10,10 +10,10 @@ | 层 | 工具 | 归属 | 覆盖对象 | 运行方式 | |---|---|---|---|---| -| L1 后端单元 | pytest | A1/A2/A3 自测 | normalize 别名收敛、SSE 帧解析、阶段→相位映射、SQLite CRUD、错误信封 | `cd backend && pytest tests -m unit` | +| L1 后端单元 | pytest | A1/A2/A3 自测 | normalize 别名收敛、SSE 帧解析、阶段→相位映射、SQLite CRUD、错误信封 | `cd sdk && pytest tests -m unit` | | L2 后端接口 | pytest + respx(mock 上游) | T 主写,A 组配合 | 每个 BFF 接口的正常/降级/超时/重试路径 | `pytest tests -m api` | | L2s 上游 smoke | 脚本 | T | 对真实 7920/8000/Neo4j 的最小连通验证(**不在 CI 每轮跑**,仅集成点与验收前跑一次) | `bash tests/smoke_upstreams.sh` | -| L3 前端单元 | Vitest | B 组自测 + T 补写 | stores、SSE parser、labels 映射、modes 参数集、工具函数 | `cd frontend && npx vitest run` | +| L3 前端单元 | Vitest | B 组自测 + T 补写 | stores、SSE parser、labels 映射、modes 参数集、工具函数 | `cd ui && npx vitest run` | | L4 前端组件 | Vitest + @vue/test-utils | B 组自测 + T 补写 | 每个组件的全部状态(默认/悬停/激活/禁用/加载/空/错误) | 同上 | | L5 端到端 | Playwright | T | 完整流程 × 三档视口 | `npx playwright test` | | L6 验收 | 人工 + 浏览器 | PM | 真实上游 + 视觉走查 | 见验收脚本 | @@ -30,10 +30,10 @@ | 项 | 值 | |---|---| | L1/L2 运行处 | 远端 `plantai-bff` conda 环境(pytest) | -| L3/L4 运行处 | 远端 `frontend/`(Vitest,Node 18) | +| L3/L4 运行处 | 远端 `ui/`(Vitest,Node 18) | | L5 运行处 | 远端(Playwright chromium,headed 不强制),**或** Mac 本地通过 SSH 隧道打到远端 | | L5 视口 | iPhone 14 390×844 | iPad 768×1024 | Desktop 1440×900 | -| mock 服务 | `python backend/mock/mock_server.py`,端口 8099,scenario 参数切场景 | +| mock 服务 | `python sdk/mock/mock_server.py`,端口 8099,scenario 参数切场景 | | 真实上游 | 7920 / 8000 / 7900 / Neo4j(按 DISCOVERY.md 选型) | ## 4. 缺陷分级 @@ -74,16 +74,16 @@ ```bash # 后端(远端,plantai-bff 环境) -cd backend && pytest tests -m unit -q # L1 -cd backend && pytest tests -m api -q # L2(respx mock) +cd sdk && pytest tests -m unit -q # L1 +cd sdk && pytest tests -m api -q # L2(respx mock) bash tests/smoke_upstreams.sh # L2s 真实连通(手动触发) # 前端(远端) -cd frontend && npx vitest run # L3+L4 -cd frontend && npx playwright test # L5(三档视口配置在 playwright.config.js) +cd ui && npx vitest run # L3+L4 +cd ui && npx playwright test # L5(三档视口配置在 playwright.config.js) # mock(开发与 L5 的 error/degraded 场景) -python backend/mock/mock_server.py # 127.0.0.1:8099 +python sdk/mock/mock_server.py # 127.0.0.1:8099 ``` ## 8. 报告模板(TEST_REPORT_R{n}.md 必须包含) diff --git a/docs/TEST_REPORT_R1.md b/docs/TEST_REPORT_R1.md index 64c74a7..00af813 100644 --- a/docs/TEST_REPORT_R1.md +++ b/docs/TEST_REPORT_R1.md @@ -10,11 +10,11 @@ |---|---| | 被测版本 | M1 集成后(PM 已修复集成问题清单,见协调通知) | | 运行环境 | 远端 plantai-bff 机(100.109.52.26);BFF 8090(真实上游 7920/8000/7900/Neo4j 18788 全通);Vite 5180 → 8090;mock 8099 运行中 | -| L1/L2 运行 | `backend` 全量 pytest(conda plantai-bff,pytest 9.1.1;本轮补充安装 pytest/pytest-asyncio/pytest-cov) | -| L3/L4 运行 | `frontend` vitest 2.1.8(137 用例) | +| L1/L2 运行 | `sdk` 全量 pytest(conda plantai-bff,pytest 9.1.1;本轮补充安装 pytest/pytest-asyncio/pytest-cov) | +| L3/L4 运行 | `ui` vitest 2.1.8(137 用例) | | L5 运行 | Playwright 1.49.1 + chromium-1148,三档视口 42→48 条测试 | | L2s 运行 | `bash tests/smoke_upstreams.sh`(本轮修复 Neo4j 认证缺陷后重跑) | -| 轮次纪律 | 只跑测试与写报告,未修改任何产品代码(`frontend/src/**`、`backend/app/**` 零改动;测试脚本自身缺陷修复除外) | +| 轮次纪律 | 只跑测试与写报告,未修改任何产品代码(`ui/src/**`、`sdk/app/**` 零改动;测试脚本自身缺陷修复除外) | ## 2. 首页统计 @@ -91,15 +91,15 @@ ## 5. 遗留问题与风险 1. **QA LLM 402 余额不足(已知,非缺陷)**:chat 答案为 fallback 证据列表而非自然语言。涉及答案内容的断言(TC-CHAT-027 等)按 TEST_CASES §3.3 回退结构断言;**依赖充值的用例未在本轮验证最终答案质量**。BUG-R1-06(markdown)恰由 fallback 列表暴露——充值后自然语言答案同样会命中,修复不受 402 影响。 -2. **L2 独立接口测试未建立**:`tests/backend/` 仍为空(A 组单测已覆盖 routers 层 119 条)。TEST_CASES 中标注 L2 的用例(检查点节奏、级联删除、INVALID_CITATION 重试等)在后续轮次需补齐 respx mock 测试,当前靠 A 组单测 + L5 间接覆盖。 +2. **L2 独立接口测试未建立**:`tests/sdk/` 仍为空(A 组单测已覆盖 routers 层 119 条)。TEST_CASES 中标注 L2 的用例(检查点节奏、级联删除、INVALID_CITATION 重试等)在后续轮次需补齐 respx mock 测试,当前靠 A 组单测 + L5 间接覆盖。 3. **E2E-01 步骤 7–8 被 BUG-R1-06 阻断未执行**:刷新后历史恢复(TC-CHAT-013 的 L5 路径)待修复后补验;L1 侧 `test_sessions_api.py` 已覆盖持久化,风险低。 4. **Laptop 档(1024–1279)无独立 Playwright project**:TEST_CASES 的 TC-RESP-002 档位用例(左栏自动折叠 64px/右栏 340px)本轮未跑,建议下轮加 laptop 项目或在该视口手动验证。 -5. **本轮 T 自身操作事故**(已恢复,记录备查):为测前端覆盖率在远端执行 `npm install --save-dev`,短暂改写了 B1 的 `frontend/package.json` 并打断 Playwright 依赖(一次运行中途崩溃)。已还原 package.json 至 B1 原版并用 `--no-save` 重装。后续 T 在远端装包一律 `--no-save`,写入 package.json 的需求走 PM。 +5. **本轮 T 自身操作事故**(已恢复,记录备查):为测前端覆盖率在远端执行 `npm install --save-dev`,短暂改写了 B1 的 `ui/package.json` 并打断 Playwright 依赖(一次运行中途崩溃)。已还原 package.json 至 B1 原版并用 `--no-save` 重装。后续 T 在远端装包一律 `--no-save`,写入 package.json 的需求走 PM。 6. **测试数据污染**:R1 新建会话已按前缀清理(R1主流程/R1检索/R1会话/R1中断 等),远端 SQLite 中 `水稻SPL基因家族…` 等自动标题会话为测试产生,已尽量清理,PM 若在意可执行一次数据盘点。 7. **48 条 L5 中 12 条 skip 为档位门控设计**(如 mock 场景仅桌面代表、E2E-05 仅桌面),已在 TEST_CASES 执行说明中记录;如需三档全跑需 PM 确认(真实上游流成本高:每档主流程 ≈1 分钟、检索 ≈92s)。 ## 6. 证据归档 - 远端日志:`/tmp/r1_pytest.log`(119 passed)、`/tmp/r1_cov.log`(87%)、`/tmp/r1_vitest_cov.log`(137 passed,81.54%)、`/tmp/r1_pw_rest.log`(L5 主批次)、`/tmp/r1_pw_target.log`(mock 场景桌面批次)、`/tmp/r1_pw_full2.log`(首轮全量,含 6/6 smoke) -- 失败截图/trace:远端 `frontend/test-results/`(按用例目录归档;7 个缺陷各有一式三份视口证据,其中 BUG-R1-04/05 为桌面证据) +- 失败截图/trace:远端 `ui/test-results/`(按用例目录归档;7 个缺陷各有一式三份视口证据,其中 BUG-R1-04/05 为桌面证据) - smoke 输出:本文件 §2.1 引用;修复后 7/7 PASS diff --git a/docs/TEST_REPORT_R2.md b/docs/TEST_REPORT_R2.md index ee1a4ef..55fd4ae 100644 --- a/docs/TEST_REPORT_R2.md +++ b/docs/TEST_REPORT_R2.md @@ -10,11 +10,11 @@ |---|---| | 被测版本 | M4 第二轮:R1 的 8 个缺陷修复后(修复清单见协调通知) | | 运行环境 | 远端 plantai-bff 机;BFF 8090(真实上游 7920/8000/7900/Neo4j 全通,health 无索引误报);Vite 5180→8090(HMR 已同步修复);mock 8099(本地/远端 md5 一致) | -| L1/L2 | `backend` pytest 全量 + `--cov=app` | -| L3/L4 | `frontend` vitest 全量 + `--coverage` | +| L1/L2 | `sdk` pytest 全量 + `--cov=app` | +| L3/L4 | `ui` vitest 全量 + `--coverage` | | L5 | Playwright 1.49.1 + chromium-1148,三档视口 48 条 | | L2s | `bash tests/smoke_upstreams.sh` | -| 铁律 | 只跑测试与写报告;未修改任何产品代码(`frontend/src/**`、`backend/app/**` 零改动) | +| 铁律 | 只跑测试与写报告;未修改任何产品代码(`ui/src/**`、`sdk/app/**` 零改动) | ## 2. 首页统计 @@ -80,7 +80,7 @@ 1. **tablet E2E-01 全量中的 flake(唯一失败)**:final3 中 tablet 档主流程 300s 超时(trace 显示流未在时限内终态,afterEach 清理正常执行),单独复跑 52.3s 通过;mobile/desktop 同轮 53.2s/53.6s 通过。判定为**上游 QA 排队/偶发慢**(同一 spec 三档三次全量中 2/3 通过),非产品缺陷。建议下轮把真实流 E2E 的 test timeout 提到 480s、终态等待上限 360s,消除环境抖动误报。 2. **12 条 skip 维持**(设计门控):mock 场景仅桌面代表、E2E-05 仅桌面(真实流成本高,跨档行为一致)。如需三档全跑需 PM 确认。 3. **Laptop 档(1024–1279)仍无 Playwright project**(R1 遗留问题 4):TC-RESP-002(左栏自动折叠 64px/右栏 340px)本轮仍未 L5 覆盖,需 PM 决定是否加档。 -4. **`tests/backend/` L2 respx 独立测试仍空**(R1 遗留问题 2):L2 用例(检查点节奏、级联删除、INVALID_CITATION 重试等)靠 A 组单测 + L5 间接覆盖。 +4. **`tests/sdk/` L2 respx 独立测试仍空**(R1 遗留问题 2):L2 用例(检查点节奏、级联删除、INVALID_CITATION 重试等)靠 A 组单测 + L5 间接覆盖。 5. **QA LLM 402 仍存在**:chat 答案为 fallback 证据列表;「依赖充值」断言维持结构回退。若这是最后一轮,充值后的答案质量断言移交 L6 验收。 6. **共享数据**:R2 测试会话已全部清理(按 ID 快照 + 时间窗删除 58 条),远端 SQLite 现仅剩 PM 集成期 7 条会话。 7. **T 自身测试缺陷修复(R2 内)**:E2E-07 接受降级列表形态、E2E-01 等 md-text 终态(弃等 stream-wrap 消失的脆断言)、markdown 触发条件收紧、E2E-05 改位置定位、清理改 ID 快照——均已同步远端。 @@ -88,7 +88,7 @@ ## 6. 证据归档 - 远端日志:`/tmp/r2_pytest.log`、`/tmp/r2_cov.log`(121 passed,87%)、`/tmp/r2_vitest_cov.log`(156 passed,80.59%)、`/tmp/r2_smoke.log`(7/7)、`/tmp/r2_pw_final3.log`(L5 全量)、`/tmp/r2_pw_m1.log`(mobile 补跑)、`/tmp/r2_pw_t1.log`(tablet 补跑) -- 失败截图/trace:`frontend/test-results/`(final3 tablet E2E-01 一份,为 flake 证据) +- 失败截图/trace:`ui/test-results/`(final3 tablet E2E-01 一份,为 flake 证据) - 归档:`/home/xzq/5090/teamWorkspace/zh/r1_test_evidence/`(R1 证据保留;R2 日志在 /tmp 保留至 PM 决定归档) ## 7. 轮次结论 diff --git a/docs/UI_SPEC.md b/docs/UI_SPEC.md index 1722f73..818af2d 100644 --- a/docs/UI_SPEC.md +++ b/docs/UI_SPEC.md @@ -8,7 +8,7 @@ --- -## 1. 设计令牌(`frontend/src/styles/tokens.css`) +## 1. 设计令牌(`ui/src/styles/tokens.css`) ### 1.1 深色主题(默认)`[data-theme="dark"]` @@ -84,7 +84,7 @@ --- -## 2. 动画规格(`frontend/src/styles/animations.css`) +## 2. 动画规格(`ui/src/styles/animations.css`) | 动画 | 时长/缓动 | 用途 | 关键帧要点 | |---|---|---|---| @@ -213,7 +213,7 @@ grid: columns 260px minmax(0,1fr) 380px --- -## 5. 四档响应式(`frontend/src/styles/responsive.css`,覆盖 §3) +## 5. 四档响应式(`ui/src/styles/responsive.css`,覆盖 §3) | 档位 | 规则 | 具体行为 | |---|---|---| diff --git a/knowledge/README.md b/knowledge/README.md new file mode 100644 index 0000000..67103b0 --- /dev/null +++ b/knowledge/README.md @@ -0,0 +1,3 @@ +# knowledge/ —— 向量知识库打包定义 + +`manifest.yaml` 记录 KB 内容与源(见方案 §五)。**数据永不入 git**——本目录只有打包定义与校验脚本的元信息。实际快照/加密副本/单一 tar 均产出于 `tooling/build/<version>/`(gitignored)。 diff --git a/knowledge/manifest.yaml b/knowledge/manifest.yaml new file mode 100644 index 0000000..04e1c58 --- /dev/null +++ b/knowledge/manifest.yaml @@ -0,0 +1,21 @@ +# knowledge/manifest.yaml —— 向量知识库打包定义(数据不入 git) +# 内容与源按方案 §五;此文件仅为元信息占位,P5 阶段填充真实清单。 +version: 2.0.0 +sources: + qdrant: + collection: qdrant-plant-srt-pc2025-v3-20260817-normalized + upstream_port: 27333 + neo4j: + normalized_bolt: bolt://127.0.0.1:19788 + canonical_bolt: bolt://127.0.0.1:18788 + registry: + paper_metadata_registry_v1_20260717: 17M +encryption: + vectors: plaintext + assertion_payload: AES-256-GCM + graph_storage: plaintext-in-tar +expected_counts: + papers: 31207 + canonical_entities: 3820996 + assertions: 8246875 + normalized_entities: 6316654 diff --git a/backend/.env.example b/sdk/.env.example similarity index 74% rename from backend/.env.example rename to sdk/.env.example index 013d92c..68784e0 100644 --- a/backend/.env.example +++ b/sdk/.env.example @@ -1,4 +1,4 @@ -# backend/.env.example —— BFF 全部配置键(pydantic-settings,键名大小写不敏感) +# sdk/.env.example —— BFF 全部配置键(pydantic-settings,键名大小写不敏感) # 复制为 .env 后按需修改。以下默认值即 config.py 的默认值,本地开发可不改。 # ---- 应用 ---- @@ -15,7 +15,7 @@ RAG_ALT_BASE_URL=http://127.0.0.1:7900 # 注意:凭据只进 BFF 配置,绝不下发到浏览器。 NEO4J_BOLT_URIS=bolt://127.0.0.1:18788,bolt://127.0.0.1:19788,bolt://127.0.0.1:17687 NEO4J_USER=neo4j -NEO4J_PASSWORDS=<in backend/.env, 不入 git> +NEO4J_PASSWORDS=<in sdk/.env, 不入 git> # ---- 超时阶梯(秒)---- TIMEOUT_HEALTH=1 @@ -25,8 +25,8 @@ TIMEOUT_UPLOAD=600 TIMEOUT_EXTRACTION=15 # ---- 本地 ---- -DB_PATH=backend/data/plantai.db +DB_PATH=data/plantai.db HEALTH_CACHE_TTL=5 -# ---- 生产形态:BFF 静态托管 frontend/dist(M6 启用)---- -FRONTEND_DIST_DIR=frontend/dist +# ---- 生产形态:BFF 静态托管 ui/dist(M6 启用)---- +FRONTEND_DIST_DIR=ui/dist diff --git a/sdk/.gitignore b/sdk/.gitignore new file mode 100644 index 0000000..15bf056 --- /dev/null +++ b/sdk/.gitignore @@ -0,0 +1,6 @@ +# sdk/.gitignore(最小版,各 agent 勿改,如需调整向 PM 提出) +__pycache__/ +*.pyc +.pytest_cache/ +.env +data/ diff --git a/backend/app/__init__.py b/sdk/app/__init__.py similarity index 100% rename from backend/app/__init__.py rename to sdk/app/__init__.py diff --git a/backend/app/config.py b/sdk/app/config.py similarity index 82% rename from backend/app/config.py rename to sdk/app/config.py index 3725bb2..ac816e0 100644 --- a/backend/app/config.py +++ b/sdk/app/config.py @@ -1,6 +1,6 @@ """config.py —— 环境变量驱动的 BFF 配置(pydantic-settings)。 -全部键见 backend/.env.example。凭据类键(Neo4j)只存在于本进程, +全部键见 sdk/.env.example。凭据类键(Neo4j)只存在于本进程, **绝不下发到浏览器**(不进入任何 API 响应)。 上游拓扑与默认值依据 docs/DISCOVERY.md: @@ -39,7 +39,7 @@ class Settings(BaseSettings): ) neo4j_user: str = "neo4j" neo4j_passwords: str = ( - "" # 真实凭据在 backend/.env(不入 git),见 backend/.env.example + "" # 真实凭据在 sdk/.env(不入 git),见 sdk/.env.example ) # ---- 超时阶梯(秒)---- @@ -50,11 +50,11 @@ class Settings(BaseSettings): timeout_extraction: float = 15.0 # 抽取其余接口 # ---- 本地 ---- - db_path: str = "backend/data/plantai.db" + db_path: str = "data/plantai.db" # 相对 sdk/(BFF cwd),bff_restart.sh 保证 health_cache_ttl: float = 5.0 - # ---- 生产形态:BFF 静态托管 frontend/dist(ARCHITECTURE.md §3,M6 挂载)---- - frontend_dist_dir: str = "frontend/dist" + # ---- 生产形态:BFF 静态托管 ui/dist(ARCHITECTURE.md §3,M6 挂载)---- + frontend_dist_dir: str = "ui/dist" # 生产形态:静态托管 ui/dist @lru_cache(maxsize=1) diff --git a/backend/app/db.py b/sdk/app/db.py similarity index 100% rename from backend/app/db.py rename to sdk/app/db.py diff --git a/backend/app/main.py b/sdk/app/main.py similarity index 90% rename from backend/app/main.py rename to sdk/app/main.py index 284015d..0b8c5c7 100644 --- a/backend/app/main.py +++ b/sdk/app/main.py @@ -4,7 +4,7 @@ - CORS:允许 http://localhost:5180 与 http://127.0.0.1:5180(Vite dev)。 - lifespan:init_db(幂等迁移);关闭回收 httpx 客户端与 Neo4j 连接。 - 全局 RequestValidationError → VALIDATION 信封(API.md §1,2026-08-25 PM 集成)。 -- 生产形态(M6):frontend/dist 存在时静态托管(ARCHITECTURE.md §3)。 +- 生产形态(M6):ui/dist 存在时静态托管(ARCHITECTURE.md §3)。 """ from __future__ import annotations @@ -78,9 +78,9 @@ def create_app() -> FastAPI: app.router.lifespan_context = _lifespan - # 生产形态:静态托管 frontend/dist(存在时挂载;本地开发期可无)。 - # 2026-08-25 PM 修复:dist 路径必须基于项目根解析(BFF 工作目录是 backend/, - # 相对路径会解析成 backend/frontend/dist);sync.sh 排除 dist,需单独同步。 + # 生产形态:静态托管 ui/dist(存在时挂载;本地开发期可无)。 + # 2026-08-25 PM 修复:dist 路径必须基于项目根解析(BFF 工作目录是 sdk/, + # 相对路径会解析成 sdk/ui/dist);sync.sh 排除 dist,需单独同步。 project_root = Path(__file__).resolve().parent.parent.parent dist = project_root / settings.frontend_dist_dir if not Path(settings.frontend_dist_dir).is_absolute() else Path(settings.frontend_dist_dir) if dist.is_dir(): diff --git a/backend/app/routers/__init__.py b/sdk/app/routers/__init__.py similarity index 100% rename from backend/app/routers/__init__.py rename to sdk/app/routers/__init__.py diff --git a/backend/app/routers/extraction.py b/sdk/app/routers/extraction.py similarity index 100% rename from backend/app/routers/extraction.py rename to sdk/app/routers/extraction.py diff --git a/backend/app/routers/figures.py b/sdk/app/routers/figures.py similarity index 100% rename from backend/app/routers/figures.py rename to sdk/app/routers/figures.py diff --git a/backend/app/routers/health.py b/sdk/app/routers/health.py similarity index 100% rename from backend/app/routers/health.py rename to sdk/app/routers/health.py diff --git a/backend/app/routers/kg.py b/sdk/app/routers/kg.py similarity index 100% rename from backend/app/routers/kg.py rename to sdk/app/routers/kg.py diff --git a/backend/app/routers/qa.py b/sdk/app/routers/qa.py similarity index 100% rename from backend/app/routers/qa.py rename to sdk/app/routers/qa.py diff --git a/backend/app/routers/sessions.py b/sdk/app/routers/sessions.py similarity index 100% rename from backend/app/routers/sessions.py rename to sdk/app/routers/sessions.py diff --git a/backend/app/routers/test_kg.py b/sdk/app/routers/test_kg.py similarity index 100% rename from backend/app/routers/test_kg.py rename to sdk/app/routers/test_kg.py diff --git a/backend/app/routers/test_qa.py b/sdk/app/routers/test_qa.py similarity index 100% rename from backend/app/routers/test_qa.py rename to sdk/app/routers/test_qa.py diff --git a/backend/app/schemas.py b/sdk/app/schemas.py similarity index 100% rename from backend/app/schemas.py rename to sdk/app/schemas.py diff --git a/backend/app/services/__init__.py b/sdk/app/services/__init__.py similarity index 100% rename from backend/app/services/__init__.py rename to sdk/app/services/__init__.py diff --git a/backend/app/services/extraction.py b/sdk/app/services/extraction.py similarity index 100% rename from backend/app/services/extraction.py rename to sdk/app/services/extraction.py diff --git a/backend/app/services/figures.py b/sdk/app/services/figures.py similarity index 99% rename from backend/app/services/figures.py rename to sdk/app/services/figures.py index 3b2f72e..40923ad 100644 --- a/backend/app/services/figures.py +++ b/sdk/app/services/figures.py @@ -268,7 +268,7 @@ async def proxy_figure(pmcid: str, raw_url: str) -> tuple[bytes, str] | None: return p.read_bytes(), _content_type(p.suffix) return None # 外网:磁盘缓存 - cache_dir = Path("backend/data/figures_cache") / pmcid + cache_dir = Path("data/figures_cache") / pmcid # 相对 sdk/(BFF cwd) cache_dir.mkdir(parents=True, exist_ok=True) fname = Path(raw_url.split("/")[-1]).name or "figure" cache_file = cache_dir / fname diff --git a/backend/app/services/neo4j.py b/sdk/app/services/neo4j.py similarity index 99% rename from backend/app/services/neo4j.py rename to sdk/app/services/neo4j.py index 2d192ee..a0915d7 100644 --- a/backend/app/services/neo4j.py +++ b/sdk/app/services/neo4j.py @@ -2,7 +2,7 @@ 选型与凭据依据 DISCOVERY.md §2: - 首选 neo4j-plant-srt-direct-v1-full(bolt 127.0.0.1:18788,db `neo4j`, - 用户 neo4j / 密码在 backend/.env),schema canonical,QA 7920 生产使用。 + 用户 neo4j / 密码在 sdk/.env),schema canonical,QA 7920 生产使用。 - 回退 neo4j-plant-srt-pc2025-v3-20260817-normalized(bolt 127.0.0.1:19788), schema 不同(Entity/ASSERTS),本波未做 Cypher 适配(TODO)。 - 再次 plantai-protected-neo4j-1(bolt 127.0.0.1:17687),空库。 diff --git a/backend/app/services/normalize.py b/sdk/app/services/normalize.py similarity index 100% rename from backend/app/services/normalize.py rename to sdk/app/services/normalize.py diff --git a/backend/app/services/sse_relay.py b/sdk/app/services/sse_relay.py similarity index 100% rename from backend/app/services/sse_relay.py rename to sdk/app/services/sse_relay.py diff --git a/backend/app/services/test_extraction.py b/sdk/app/services/test_extraction.py similarity index 100% rename from backend/app/services/test_extraction.py rename to sdk/app/services/test_extraction.py diff --git a/backend/app/services/test_neo4j.py b/sdk/app/services/test_neo4j.py similarity index 100% rename from backend/app/services/test_neo4j.py rename to sdk/app/services/test_neo4j.py diff --git a/backend/app/services/test_normalize.py b/sdk/app/services/test_normalize.py similarity index 98% rename from backend/app/services/test_normalize.py rename to sdk/app/services/test_normalize.py index 68a00af..8723245 100644 --- a/backend/app/services/test_normalize.py +++ b/sdk/app/services/test_normalize.py @@ -1,6 +1,6 @@ """services/test_normalize.py —— 归一化器单测。 -输入用 backend/mock/fixtures/upstream_real_shape_sample.json(真实上游形态样本, +输入用 sdk/mock/fixtures/upstream_real_shape_sample.json(真实上游形态样本, DISCOVERY.md §4.1),断言 API.md §7/§8/§9 的归一化输出关键字段。 fixture 是单一事实源(ARCHITECTURE.md §6),本测试同时守护 mock/BFF 契约一致。 """ diff --git a/backend/app/services/test_sse_relay.py b/sdk/app/services/test_sse_relay.py similarity index 100% rename from backend/app/services/test_sse_relay.py rename to sdk/app/services/test_sse_relay.py diff --git a/backend/app/test_db.py b/sdk/app/test_db.py similarity index 100% rename from backend/app/test_db.py rename to sdk/app/test_db.py diff --git a/backend/app/test_sessions_api.py b/sdk/app/test_sessions_api.py similarity index 100% rename from backend/app/test_sessions_api.py rename to sdk/app/test_sessions_api.py diff --git a/backend/app/upstream.py b/sdk/app/upstream.py similarity index 100% rename from backend/app/upstream.py rename to sdk/app/upstream.py diff --git a/backend/mock/fixtures/extraction_job.json b/sdk/mock/fixtures/extraction_job.json similarity index 100% rename from backend/mock/fixtures/extraction_job.json rename to sdk/mock/fixtures/extraction_job.json diff --git a/backend/mock/fixtures/extraction_jobs.json b/sdk/mock/fixtures/extraction_jobs.json similarity index 100% rename from backend/mock/fixtures/extraction_jobs.json rename to sdk/mock/fixtures/extraction_jobs.json diff --git a/backend/mock/fixtures/extraction_processing_status.json b/sdk/mock/fixtures/extraction_processing_status.json similarity index 100% rename from backend/mock/fixtures/extraction_processing_status.json rename to sdk/mock/fixtures/extraction_processing_status.json diff --git a/backend/mock/fixtures/extraction_summary.json b/sdk/mock/fixtures/extraction_summary.json similarity index 100% rename from backend/mock/fixtures/extraction_summary.json rename to sdk/mock/fixtures/extraction_summary.json diff --git a/backend/mock/fixtures/extraction_upload.json b/sdk/mock/fixtures/extraction_upload.json similarity index 100% rename from backend/mock/fixtures/extraction_upload.json rename to sdk/mock/fixtures/extraction_upload.json diff --git a/backend/mock/fixtures/health.json b/sdk/mock/fixtures/health.json similarity index 100% rename from backend/mock/fixtures/health.json rename to sdk/mock/fixtures/health.json diff --git a/backend/mock/fixtures/kg_entities.json b/sdk/mock/fixtures/kg_entities.json similarity index 100% rename from backend/mock/fixtures/kg_entities.json rename to sdk/mock/fixtures/kg_entities.json diff --git a/backend/mock/fixtures/kg_entity.json b/sdk/mock/fixtures/kg_entity.json similarity index 100% rename from backend/mock/fixtures/kg_entity.json rename to sdk/mock/fixtures/kg_entity.json diff --git a/backend/mock/fixtures/kg_neighborhood.json b/sdk/mock/fixtures/kg_neighborhood.json similarity index 100% rename from backend/mock/fixtures/kg_neighborhood.json rename to sdk/mock/fixtures/kg_neighborhood.json diff --git a/backend/mock/fixtures/kg_overview.json b/sdk/mock/fixtures/kg_overview.json similarity index 100% rename from backend/mock/fixtures/kg_overview.json rename to sdk/mock/fixtures/kg_overview.json diff --git a/backend/mock/fixtures/kg_status.json b/sdk/mock/fixtures/kg_status.json similarity index 100% rename from backend/mock/fixtures/kg_status.json rename to sdk/mock/fixtures/kg_status.json diff --git a/backend/mock/fixtures/qa_chat.json b/sdk/mock/fixtures/qa_chat.json similarity index 100% rename from backend/mock/fixtures/qa_chat.json rename to sdk/mock/fixtures/qa_chat.json diff --git a/backend/mock/fixtures/qa_chat_empty.json b/sdk/mock/fixtures/qa_chat_empty.json similarity index 100% rename from backend/mock/fixtures/qa_chat_empty.json rename to sdk/mock/fixtures/qa_chat_empty.json diff --git a/backend/mock/fixtures/qa_info.json b/sdk/mock/fixtures/qa_info.json similarity index 100% rename from backend/mock/fixtures/qa_info.json rename to sdk/mock/fixtures/qa_info.json diff --git a/backend/mock/fixtures/qa_search.json b/sdk/mock/fixtures/qa_search.json similarity index 100% rename from backend/mock/fixtures/qa_search.json rename to sdk/mock/fixtures/qa_search.json diff --git a/backend/mock/fixtures/session_created.json b/sdk/mock/fixtures/session_created.json similarity index 100% rename from backend/mock/fixtures/session_created.json rename to sdk/mock/fixtures/session_created.json diff --git a/backend/mock/fixtures/session_detail.json b/sdk/mock/fixtures/session_detail.json similarity index 100% rename from backend/mock/fixtures/session_detail.json rename to sdk/mock/fixtures/session_detail.json diff --git a/backend/mock/fixtures/sessions.json b/sdk/mock/fixtures/sessions.json similarity index 100% rename from backend/mock/fixtures/sessions.json rename to sdk/mock/fixtures/sessions.json diff --git a/backend/mock/fixtures/sources_aggregate.json b/sdk/mock/fixtures/sources_aggregate.json similarity index 100% rename from backend/mock/fixtures/sources_aggregate.json rename to sdk/mock/fixtures/sources_aggregate.json diff --git a/backend/mock/fixtures/sse_frames.json b/sdk/mock/fixtures/sse_frames.json similarity index 100% rename from backend/mock/fixtures/sse_frames.json rename to sdk/mock/fixtures/sse_frames.json diff --git a/backend/mock/fixtures/upstream_real_shape_sample.json b/sdk/mock/fixtures/upstream_real_shape_sample.json similarity index 100% rename from backend/mock/fixtures/upstream_real_shape_sample.json rename to sdk/mock/fixtures/upstream_real_shape_sample.json diff --git a/backend/mock/mock_server.py b/sdk/mock/mock_server.py similarity index 100% rename from backend/mock/mock_server.py rename to sdk/mock/mock_server.py diff --git a/backend/pyproject.toml b/sdk/pyproject.toml similarity index 100% rename from backend/pyproject.toml rename to sdk/pyproject.toml diff --git a/backend/run.sh b/sdk/run.sh similarity index 96% rename from backend/run.sh rename to sdk/run.sh index 5783bd5..13fb039 100755 --- a/backend/run.sh +++ b/sdk/run.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # run.sh —— 本地起 BFF(ARCHITECTURE.md §3:127.0.0.1:8090,占用则退 8091)。 -# 用法:bash backend/run.sh +# 用法:bash sdk/run.sh set -euo pipefail cd "$(dirname "$0")" diff --git a/tests/frontend/unit/b1/appshell.test.js b/tests/frontend/unit/b1/appshell.test.js index 41f32cf..f115cbb 100644 --- a/tests/frontend/unit/b1/appshell.test.js +++ b/tests/frontend/unit/b1/appshell.test.js @@ -10,7 +10,7 @@ import { mount } from '@vue/test-utils'; import { createPinia } from 'pinia'; import { nextTick } from 'vue'; -import AppShell from '../../../../frontend/src/components/layout/AppShell.vue'; +import AppShell from '../../../../ui/src/components/layout/AppShell.vue'; // mock fetch(health store 若被使用则不真发请求) vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } })))); diff --git a/tests/frontend/unit/b1/health.test.js b/tests/frontend/unit/b1/health.test.js index c0828e9..e97cd26 100644 --- a/tests/frontend/unit/b1/health.test.js +++ b/tests/frontend/unit/b1/health.test.js @@ -1,7 +1,7 @@ // health store 单测(B1):轮询与降级 getter(mock fetch)(FR-HEALTH-001..004) import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createPinia, setActivePinia } from 'pinia'; -import { useHealthStore } from '../../../../frontend/src/stores/health.js'; +import { useHealthStore } from '../../../../ui/src/stores/health.js'; // mock fetch(http.js 的 fetch 封装走全局 fetch) const fetchMock = vi.fn(); diff --git a/tests/frontend/unit/b1/http.test.js b/tests/frontend/unit/b1/http.test.js index 89ab7e9..5afc0ab 100644 --- a/tests/frontend/unit/b1/http.test.js +++ b/tests/frontend/unit/b1/http.test.js @@ -1,6 +1,6 @@ // http.js 单测(B1):错误信封解析 + 非 2xx / 网络错误归一(API.md §1) import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { api, ApiError } from '../../../../frontend/src/api/http.js'; +import { api, ApiError } from '../../../../ui/src/api/http.js'; const fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); diff --git a/tests/frontend/unit/b1/theme.test.js b/tests/frontend/unit/b1/theme.test.js index ddd6f43..0815737 100644 --- a/tests/frontend/unit/b1/theme.test.js +++ b/tests/frontend/unit/b1/theme.test.js @@ -1,7 +1,7 @@ // theme store 单测(B1):持久化与 dataset 同步(FR-THEME-002 / UI_SPEC §6) import { beforeEach, describe, expect, it } from 'vitest'; import { createPinia, setActivePinia } from 'pinia'; -import { useThemeStore, applyTheme, currentTheme } from '../../../../frontend/src/stores/theme.js'; +import { useThemeStore, applyTheme, currentTheme } from '../../../../ui/src/stores/theme.js'; describe('theme store', () => { beforeEach(() => { diff --git a/tests/frontend/unit/b2/chat.store.test.js b/tests/frontend/unit/b2/chat.store.test.js index 4f539b1..054884c 100644 --- a/tests/frontend/unit/b2/chat.store.test.js +++ b/tests/frontend/unit/b2/chat.store.test.js @@ -5,14 +5,14 @@ // retry(FR-CHAT-012)、恢复与「已中断」、错误帧映射(FR-CHAT-008)。 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; -import { newSkeletonMessage, qaToBlocks, PIPELINE_PHASES } from '../../../../frontend/src/stores/chat.js'; +import { newSkeletonMessage, qaToBlocks, PIPELINE_PHASES } from '../../../../ui/src/stores/chat.js'; // ---- mock 依赖模块 ---- let postImpl; let frames = []; let updateSessionCalls = []; -vi.mock('../../../../frontend/src/api/qa.js', () => ({ +vi.mock('../../../../ui/src/api/qa.js', () => ({ chatStream: vi.fn(async (body, handlers) => { for (const f of frames) { if (f.event === 'complete') handlers.onComplete({ payload: f.data }); @@ -28,7 +28,7 @@ vi.mock('../../../../frontend/src/api/qa.js', () => ({ info: vi.fn(async () => ({})), })); -vi.mock('../../../../frontend/src/api/sessions.js', () => { +vi.mock('../../../../ui/src/api/sessions.js', () => { const calls = { messages: [], patches: [] }; return { createMessage: vi.fn(async (sid, body) => { @@ -49,10 +49,10 @@ vi.mock('../../../../frontend/src/api/sessions.js', () => { }; }); -import { useChatStore } from '../../../../frontend/src/stores/chat.js'; -import { useSessionsStore } from '../../../../frontend/src/stores/sessions.js'; -import * as qaApi from '../../../../frontend/src/api/qa.js'; -import * as sessApi from '../../../../frontend/src/api/sessions.js'; +import { useChatStore } from '../../../../ui/src/stores/chat.js'; +import { useSessionsStore } from '../../../../ui/src/stores/sessions.js'; +import * as qaApi from '../../../../ui/src/api/qa.js'; +import * as sessApi from '../../../../ui/src/api/sessions.js'; const calls = sessApi._calls; diff --git a/tests/frontend/unit/b2/chatpanel.health.test.js b/tests/frontend/unit/b2/chatpanel.health.test.js index 20ec87f..bfdda88 100644 --- a/tests/frontend/unit/b2/chatpanel.health.test.js +++ b/tests/frontend/unit/b2/chatpanel.health.test.js @@ -6,17 +6,17 @@ import { mount, flushPromises } from '@vue/test-utils'; import { setActivePinia, createPinia } from 'pinia'; // mock health store(B1 归属,不实改其文件):qaOk 由 data.upstreams.qa.ok 派生 -vi.mock('../../../../frontend/src/stores/health.js', () => ({ +vi.mock('../../../../ui/src/stores/health.js', () => ({ useHealthStore: vi.fn(() => healthState), })); // mock bench store(B3):ChatPanel 对 bench 的引用须容错(本测试不关心工作台) -vi.mock('../../../../frontend/src/stores/bench.js', () => ({ +vi.mock('../../../../ui/src/stores/bench.js', () => ({ useBenchStore: vi.fn(() => ({ setActiveTab: vi.fn(), setSourceScope: vi.fn() })), })); // sessions/chat store 用真实现(chat.store 内部 mock api 层,见下) -vi.mock('../../../../frontend/src/api/sessions.js', () => ({ +vi.mock('../../../../ui/src/api/sessions.js', () => ({ listSessions: vi.fn(async () => []), createSession: vi.fn(async () => ({ id: 's1', title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} })), getSession: vi.fn(async () => ({ id: 's1', title: 't', status: 'done', meta: {}, messages: [] })), @@ -26,14 +26,14 @@ vi.mock('../../../../frontend/src/api/sessions.js', () => ({ updateMessage: vi.fn(), getSessionSources: vi.fn(), })); -vi.mock('../../../../frontend/src/api/qa.js', () => ({ +vi.mock('../../../../ui/src/api/qa.js', () => ({ chatStream: vi.fn(), chat: vi.fn(), search: vi.fn(), info: vi.fn(), })); -import ChatPanel from '../../../../frontend/src/components/chat/ChatPanel.vue'; +import ChatPanel from '../../../../ui/src/components/chat/ChatPanel.vue'; /** health 状态(响应式替换:mock 工厂每次返回同一对象) */ let healthState; diff --git a/tests/frontend/unit/b2/components.test.js b/tests/frontend/unit/b2/components.test.js index 7f58076..1263eaf 100644 --- a/tests/frontend/unit/b2/components.test.js +++ b/tests/frontend/unit/b2/components.test.js @@ -3,9 +3,9 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import { setActivePinia, createPinia } from 'pinia'; -import Composer from '../../../../frontend/src/components/chat/Composer.vue'; -import AssistantMessage from '../../../../frontend/src/components/chat/AssistantMessage.vue'; -import SessionItem from '../../../../frontend/src/components/session/SessionItem.vue'; +import Composer from '../../../../ui/src/components/chat/Composer.vue'; +import AssistantMessage from '../../../../ui/src/components/chat/AssistantMessage.vue'; +import SessionItem from '../../../../ui/src/components/session/SessionItem.vue'; describe('Composer(FR-CHAT-005/007/011)', () => { beforeEach(() => { diff --git a/tests/frontend/unit/b2/markdown.test.js b/tests/frontend/unit/b2/markdown.test.js index 8e0355d..3e1dd8c 100644 --- a/tests/frontend/unit/b2/markdown.test.js +++ b/tests/frontend/unit/b2/markdown.test.js @@ -1,7 +1,7 @@ // @vitest-environment jsdom // markdown.test.js —— Markdown 渲染 + DOMPurify 消毒 + 自定义表格/代码类(FR-CHAT-004) import { describe, it, expect } from 'vitest'; -import { renderMarkdown, NON_MONO_HEADER } from '../../../../frontend/src/components/chat/markdown.js'; +import { renderMarkdown, NON_MONO_HEADER } from '../../../../ui/src/components/chat/markdown.js'; describe('renderMarkdown(FR-CHAT-004)', () => { it('LLM 输出 markdown → 消毒 HTML', () => { diff --git a/tests/frontend/unit/b2/parser.test.js b/tests/frontend/unit/b2/parser.test.js index 977ddb6..bf72883 100644 --- a/tests/frontend/unit/b2/parser.test.js +++ b/tests/frontend/unit/b2/parser.test.js @@ -1,7 +1,7 @@ // @vitest-environment node // sse/parser.test.js —— SSE 解析器单测(多行 data / 注释帧 / 无 event 名 / CRLF / 分块 UTF-8) import { describe, it, expect, vi } from 'vitest'; -import { parseFrame, SseParser, consumeSseStream } from '../../../../frontend/src/sse/parser.js'; +import { parseFrame, SseParser, consumeSseStream } from '../../../../ui/src/sse/parser.js'; describe('parseFrame', () => { it('解析单行 data 与 event 名', () => { diff --git a/tests/frontend/unit/b2/sessions.store.test.js b/tests/frontend/unit/b2/sessions.store.test.js index b78de05..5e37379 100644 --- a/tests/frontend/unit/b2/sessions.store.test.js +++ b/tests/frontend/unit/b2/sessions.store.test.js @@ -5,7 +5,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; const updateCalls = []; -vi.mock('../../../../frontend/src/api/sessions.js', () => ({ +vi.mock('../../../../ui/src/api/sessions.js', () => ({ listSessions: vi.fn(async () => []), createSession: vi.fn(async () => ({ id: 'new-1', title: '新会话', status: 'idle', created_at: '2026-08-25T00:00:00Z', updated_at: '2026-08-25T00:00:00Z', message_count: 0, meta: {} })), getSession: vi.fn(async () => ({})), @@ -16,7 +16,7 @@ vi.mock('../../../../frontend/src/api/sessions.js', () => ({ getSessionSources: vi.fn(), })); -import { useSessionsStore, groupKeyOf, deriveTitle } from '../../../../frontend/src/stores/sessions.js'; +import { useSessionsStore, groupKeyOf, deriveTitle } from '../../../../ui/src/stores/sessions.js'; const iso = (s) => new Date(s).toISOString(); @@ -60,7 +60,7 @@ describe('sessions store', () => { }); it('分组:按 updated_at 倒序 + 三组', async () => { - const { listSessions } = await import('../../../../frontend/src/api/sessions.js'); + const { listSessions } = await import('../../../../ui/src/api/sessions.js'); // 2026-08-26 修复:夹具日期相对「真实今天」动态生成——硬编码日期会随时间 // 腐烂(store 分组用真实 new Date()),此前每天翻车。 const d = (daysAgo) => { @@ -83,7 +83,7 @@ describe('sessions store', () => { }); it('搜索过滤:title 子串不区分大小写 + 无匹配(FR-SESSION-005)', async () => { - const { listSessions } = await import('../../../../frontend/src/api/sessions.js'); + const { listSessions } = await import('../../../../ui/src/api/sessions.js'); listSessions.mockResolvedValueOnce([ { id: 'a', title: '水稻SPL基因家族', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: {} }, { id: 'b', title: 'NAC 转录因子研究综述', status: 'done', created_at: 'x', updated_at: iso('2026-08-24T10:00:00'), message_count: 2, meta: {} }, @@ -102,7 +102,7 @@ describe('sessions store', () => { }); it('新建会话:切为活动会话 + 连点两次产生两个独立会话(FR-SESSION-003)', async () => { - const { createSession } = await import('../../../../frontend/src/api/sessions.js'); + const { createSession } = await import('../../../../ui/src/api/sessions.js'); let n = 0; createSession.mockImplementation(async () => ({ id: `new-${++n}`, title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} })); const store = useSessionsStore(); @@ -114,7 +114,7 @@ describe('sessions store', () => { }); it('删除:删除当前会话 → 切最新一个;列表空 → activeId=null(FR-SESSION-007)', async () => { - const { listSessions, deleteSession } = await import('../../../../frontend/src/api/sessions.js'); + const { listSessions, deleteSession } = await import('../../../../ui/src/api/sessions.js'); listSessions.mockResolvedValueOnce([ { id: 'a', title: 'A', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: {} }, { id: 'b', title: 'B', status: 'done', created_at: 'x', updated_at: iso('2026-08-24T10:00:00'), message_count: 2, meta: {} }, @@ -132,7 +132,7 @@ describe('sessions store', () => { }); it('标题自动生成:首问 PATCH 前 30 字符;title_manual 不再覆盖(FR-SESSION-008)', async () => { - const { listSessions } = await import('../../../../frontend/src/api/sessions.js'); + const { listSessions } = await import('../../../../ui/src/api/sessions.js'); const q = '水稻SPL基因家族的功能研究有哪些关键进展?'; listSessions.mockResolvedValueOnce([ { id: 'a', title: '新会话', status: 'running', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 1, meta: { title_manual: false } }, @@ -149,7 +149,7 @@ describe('sessions store', () => { }); it('重命名:PATCH 后本地同步(FR-SESSION-006)', async () => { - const { listSessions } = await import('../../../../frontend/src/api/sessions.js'); + const { listSessions } = await import('../../../../ui/src/api/sessions.js'); listSessions.mockResolvedValueOnce([ { id: 'a', title: '旧标题', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: { title_manual: false } }, ]); diff --git a/tests/frontend/unit/b3/bench.store.test.js b/tests/frontend/unit/b3/bench.store.test.js index 2c857ee..715b65a 100644 --- a/tests/frontend/unit/b3/bench.store.test.js +++ b/tests/frontend/unit/b3/bench.store.test.js @@ -2,15 +2,15 @@ // 覆盖:tab 切换、相位筛选、轮询启停(fake timers)、sources 两种来源切换、会话级去重。 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { createPinia, setActivePinia, getActivePinia } from 'pinia'; -import { useBenchStore, normalizeStageEvent, __setHttpGet } from '../../../../frontend/src/stores/bench.js'; +import { useBenchStore, normalizeStageEvent, __setHttpGet } from '../../../../ui/src/stores/bench.js'; -const extractionApi = () => import('../../../../frontend/src/api/extraction.js'); +const extractionApi = () => import('../../../../ui/src/api/extraction.js'); // http.js(B1 归属)尚未交付:用注入式 get 模拟统一信封,不依赖文件存在 const httpGet = vi.fn(); __setHttpGet(httpGet); -vi.mock('../../../../frontend/src/api/extraction.js', () => ({ +vi.mock('../../../../ui/src/api/extraction.js', () => ({ listJobs: vi.fn(), getJob: vi.fn(), uploadPaper: vi.fn(), @@ -19,7 +19,7 @@ vi.mock('../../../../frontend/src/api/extraction.js', () => ({ getSummary: vi.fn(), })); -vi.mock('../../../../frontend/src/api/kg.js', () => ({ +vi.mock('../../../../ui/src/api/kg.js', () => ({ kgStatus: vi.fn(), kgOverview: vi.fn(), searchEntities: vi.fn(), diff --git a/tests/frontend/unit/b3/benchpanel.test.js b/tests/frontend/unit/b3/benchpanel.test.js index 0c8a22e..b091f46 100644 --- a/tests/frontend/unit/b3/benchpanel.test.js +++ b/tests/frontend/unit/b3/benchpanel.test.js @@ -2,10 +2,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import { createPinia, setActivePinia } from 'pinia'; -import { useBenchStore } from '../../../../frontend/src/stores/bench.js'; -import BenchPanel from '../../../../frontend/src/components/bench/BenchPanel.vue'; +import { useBenchStore } from '../../../../ui/src/stores/bench.js'; +import BenchPanel from '../../../../ui/src/components/bench/BenchPanel.vue'; -vi.mock('../../../../frontend/src/api/extraction.js', () => ({ +vi.mock('../../../../ui/src/api/extraction.js', () => ({ listJobs: vi.fn().mockResolvedValue({ jobs: [], count: 0 }), getJob: vi.fn(), uploadPaper: vi.fn(), @@ -13,7 +13,7 @@ vi.mock('../../../../frontend/src/api/extraction.js', () => ({ getProcessingStatus: vi.fn(), getSummary: vi.fn(), })); -vi.mock('../../../../frontend/src/api/kg.js', () => ({ +vi.mock('../../../../ui/src/api/kg.js', () => ({ kgStatus: vi.fn().mockResolvedValue({ ok: true }), kgOverview: vi.fn(), searchEntities: vi.fn(), diff --git a/tests/frontend/unit/b3/extraction.pane.test.js b/tests/frontend/unit/b3/extraction.pane.test.js index a0372b9..18d11e6 100644 --- a/tests/frontend/unit/b3/extraction.pane.test.js +++ b/tests/frontend/unit/b3/extraction.pane.test.js @@ -2,11 +2,11 @@ import { describe, it, expect, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { createPinia, setActivePinia } from 'pinia'; -import { deriveProgressPct, shouldPoll } from '../../../../frontend/src/components/bench/kgPaneCompat.js'; -import { useBenchStore } from '../../../../frontend/src/stores/bench.js'; -import ProgressItem from '../../../../frontend/src/components/bench/ProgressItem.vue'; +import { deriveProgressPct, shouldPoll } from '../../../../ui/src/components/bench/kgPaneCompat.js'; +import { useBenchStore } from '../../../../ui/src/stores/bench.js'; +import ProgressItem from '../../../../ui/src/components/bench/ProgressItem.vue'; -vi.mock('../../../../frontend/src/api/extraction.js', () => ({ +vi.mock('../../../../ui/src/api/extraction.js', () => ({ listJobs: vi.fn().mockResolvedValue({ jobs: [], count: 0 }), getJob: vi.fn(), uploadPaper: vi.fn(), diff --git a/tests/frontend/unit/b3/graphLayout.test.js b/tests/frontend/unit/b3/graphLayout.test.js index b15e1e6..aa8fbb1 100644 --- a/tests/frontend/unit/b3/graphLayout.test.js +++ b/tests/frontend/unit/b3/graphLayout.test.js @@ -1,6 +1,6 @@ // graphLayout.test.js —— B3 单测:手写力导向布局(graphLayout.js) import { describe, it, expect } from 'vitest'; -import { forceLayout, buildPalette, hashString } from '../../../../frontend/src/components/bench/graphLayout.js'; +import { forceLayout, buildPalette, hashString } from '../../../../ui/src/components/bench/graphLayout.js'; describe('forceLayout 手写力导向(FR-KG-001)', () => { const small = { diff --git a/tests/frontend/unit/b3/kg.pane.test.js b/tests/frontend/unit/b3/kg.pane.test.js index f3491f3..cc31860 100644 --- a/tests/frontend/unit/b3/kg.pane.test.js +++ b/tests/frontend/unit/b3/kg.pane.test.js @@ -1,16 +1,16 @@ // kg.pane.test.js —— B3 单测:KgPane(节点配色稳定性、>50 节点降级、离线态、搜索流程 mock) import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createPinia, setActivePinia, getActivePinia } from 'pinia'; -import { buildPalette } from '../../../../frontend/src/components/bench/graphLayout.js'; +import { buildPalette } from '../../../../ui/src/components/bench/graphLayout.js'; import { isDegradedGraph, isKgOffline, NODE_LIMIT_DESKTOP, NODE_LIMIT_MOBILE, -} from '../../../../frontend/src/components/bench/kgPaneCompat.js'; -import { useBenchStore } from '../../../../frontend/src/stores/bench.js'; +} from '../../../../ui/src/components/bench/kgPaneCompat.js'; +import { useBenchStore } from '../../../../ui/src/stores/bench.js'; -vi.mock('../../../../frontend/src/api/extraction.js', () => ({ +vi.mock('../../../../ui/src/api/extraction.js', () => ({ listJobs: vi.fn(), getJob: vi.fn(), uploadPaper: vi.fn(), @@ -18,7 +18,7 @@ vi.mock('../../../../frontend/src/api/extraction.js', () => ({ getProcessingStatus: vi.fn(), getSummary: vi.fn(), })); -vi.mock('../../../../frontend/src/api/kg.js', () => ({ +vi.mock('../../../../ui/src/api/kg.js', () => ({ kgStatus: vi.fn(), searchEntities: vi.fn().mockResolvedValue({ query: 'OsSPL', @@ -87,7 +87,7 @@ describe('KgPane 离线降级(FR-KG-007)', () => { }); it('store.checkKgStatus 失败 → kgReady=false + kgError=true', async () => { - const { kgStatus } = await import('../../../../frontend/src/api/kg.js'); + const { kgStatus } = await import('../../../../ui/src/api/kg.js'); kgStatus.mockRejectedValue({ code: 'UPSTREAM_DOWN', message: 'Neo4j 不可用', degraded: true }); setActivePinia(createPinia()); const store = useBenchStore(); @@ -97,7 +97,7 @@ describe('KgPane 离线降级(FR-KG-007)', () => { }); it('store.checkKgStatus 成功 → kgReady=true', async () => { - const { kgStatus } = await import('../../../../frontend/src/api/kg.js'); + const { kgStatus } = await import('../../../../ui/src/api/kg.js'); kgStatus.mockResolvedValue({ ok: true, active: 'neo4j-plant-srt-direct-v1-full' }); setActivePinia(createPinia()); const store = useBenchStore(); @@ -108,7 +108,7 @@ describe('KgPane 离线降级(FR-KG-007)', () => { describe('KgPane 搜索流程(mock,FR-KG-004)', () => { it('搜索返回 name_norm 匹配结果(精确/前缀),选中后加载邻域', async () => { - const { searchEntities, getNeighborhood } = await import('../../../../frontend/src/api/kg.js'); + const { searchEntities, getNeighborhood } = await import('../../../../ui/src/api/kg.js'); const data = await searchEntities('OsSPL', 20); expect(data.results).toHaveLength(2); expect(data.results[0].name).toBe('OsSPL14'); diff --git a/tests/frontend/unit/b3/retrieval.pane.test.js b/tests/frontend/unit/b3/retrieval.pane.test.js index ca17051..0f51f03 100644 --- a/tests/frontend/unit/b3/retrieval.pane.test.js +++ b/tests/frontend/unit/b3/retrieval.pane.test.js @@ -1,10 +1,10 @@ // retrieval.pane.test.js —— B3 单测:RetrievalPane(相位筛选、空态) import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createPinia, setActivePinia, getActivePinia } from 'pinia'; -import { useBenchStore, normalizeStageEvent } from '../../../../frontend/src/stores/bench.js'; -import { STAGE_PHASE } from '../../../../frontend/src/i18n/labels.js'; +import { useBenchStore, normalizeStageEvent } from '../../../../ui/src/stores/bench.js'; +import { STAGE_PHASE } from '../../../../ui/src/i18n/labels.js'; -vi.mock('../../../../frontend/src/api/extraction.js', () => ({ +vi.mock('../../../../ui/src/api/extraction.js', () => ({ listJobs: vi.fn(), getJob: vi.fn(), uploadPaper: vi.fn(), diff --git a/tests/frontend/unit/b3/sources.pane.test.js b/tests/frontend/unit/b3/sources.pane.test.js index 524cb74..4b2c847 100644 --- a/tests/frontend/unit/b3/sources.pane.test.js +++ b/tests/frontend/unit/b3/sources.pane.test.js @@ -1,7 +1,7 @@ // sources.pane.test.js —— B3 单测:SourcesPane 相关(外链优先级、null 字段不渲染) // 组件渲染走 @vue/test-utils,判定规则走 kgPaneCompat 纯函数双保险。 import { describe, it, expect } from 'vitest'; -import { resolveSourceLink, aggregateSessionSources } from '../../../../frontend/src/components/bench/kgPaneCompat.js'; +import { resolveSourceLink, aggregateSessionSources } from '../../../../ui/src/components/bench/kgPaneCompat.js'; describe('SourcesPane 外链优先级(FR-SOURCE-003)', () => { it('url > doi > pmid > pmcid', () => { diff --git a/tests/frontend/unit/b3/vitest.config.js b/tests/frontend/unit/b3/vitest.config.js index bc1be33..7d43334 100644 --- a/tests/frontend/unit/b3/vitest.config.js +++ b/tests/frontend/unit/b3/vitest.config.js @@ -1,14 +1,14 @@ // vitest.config.js —— B3 单测局部配置(B3 归属,位于 tests/frontend/unit/b3/) -// B1 尚未交付 frontend/vitest.config.js,此处提供最小可用配置保证 B3 单测可独立运行。 -// 因本目录在 frontend/ 之外,node_modules 解析与别名均需显式指到 frontend/node_modules。 +// B1 尚未交付 ui/vitest.config.js,此处提供最小可用配置保证 B3 单测可独立运行。 +// 因本目录在 ui/ 之外,node_modules 解析与别名均需显式指到 ui/node_modules。 // 待 B1 总配置就绪后,PM 集成时可删除本文件,改用统一配置(需 include 覆盖本目录)。 import { fileURLToPath } from 'node:url'; import path from 'node:path'; -import { defineConfig } from '../../../../frontend/node_modules/vitest/dist/config.js'; -import vue from '../../../../frontend/node_modules/@vitejs/plugin-vue/dist/index.mjs'; +import { defineConfig } from '../../../../ui/node_modules/vitest/dist/config.js'; +import vue from '../../../../ui/node_modules/@vitejs/plugin-vue/dist/index.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const nm = (rel) => path.resolve(__dirname, '../../../../frontend/node_modules', rel); +const nm = (rel) => path.resolve(__dirname, '../../../../ui/node_modules', rel); export default defineConfig({ plugins: [vue()], diff --git a/tests/smoke_upstreams.sh b/tests/smoke_upstreams.sh index 768814f..9f07d88 100755 --- a/tests/smoke_upstreams.sh +++ b/tests/smoke_upstreams.sh @@ -11,7 +11,7 @@ # ALT_URL 回落抽取服务根,默认 http://127.0.0.1:7900 # MOCK_URL mock server 根,默认 http://127.0.0.1:8099 # NEO4J_HTTP Neo4j HTTP 根,默认 http://127.0.0.1:18575(direct-v1-full 实例,DISCOVERY.md §1) -# NEO4J_USER / NEO4J_PASSWORD Neo4j 认证,默认 neo4j / <NEO4J_PASSWORD from backend/.env>(DISCOVERY.md §2.1) +# NEO4J_USER / NEO4J_PASSWORD Neo4j 认证,默认 neo4j / <NEO4J_PASSWORD from sdk/.env>(DISCOVERY.md §2.1) # TIMEOUT curl 超时秒,默认 4 # # 注意:不探测 QA /search 与 /chat/stream —— 实测单次 37~92s(DISCOVERY.md §4), @@ -57,12 +57,12 @@ check_http() { } # check_neo4j <名称> <HTTP 根>:POST db/neo4j/tx/commit 一条最小只读 Cypher。 -# 契约(DISCOVERY.md §2.1):direct-v1-full 需要 Basic auth(neo4j/<NEO4J_PASSWORD from backend/.env>); +# 契约(DISCOVERY.md §2.1):direct-v1-full 需要 Basic auth(neo4j/<NEO4J_PASSWORD from sdk/.env>); # 凭据经环境变量 NEO4J_USER/NEO4J_PASSWORD 覆盖(默认指向当选实例)。 check_neo4j() { local name="$1" base="$2" local user="${NEO4J_USER:-neo4j}" - local pass="${NEO4J_PASSWORD:-<NEO4J_PASSWORD from backend/.env>}" + local pass="${NEO4J_PASSWORD:-<NEO4J_PASSWORD from sdk/.env>}" local out code out=$(curl -s -m "$TIMEOUT" -o "$TMP" -w '%{http_code}' \ -u "$user:$pass" \ diff --git a/tooling/README.md b/tooling/README.md new file mode 100644 index 0000000..39507cf --- /dev/null +++ b/tooling/README.md @@ -0,0 +1,9 @@ +# tooling/ —— 统一打包工具(收编 encryption-test 工具链) + +- build_all.sh —— 一键构建入口(读 tooling.env) +- compile/ —— Cython 编译脚本(SDK + 3 agent) +- audit/ —— 编译树审计(audit_compiled_tree.py 等) +- kb/ —— KB 快照/加密/打包 +- crypto/ —— 密钥与 AES-GCM 原语(无 TPM 版) +- assemble/ —— 安装目录组装 + 部署脚本模板 +- build/<version>/ —— 构建产物(gitignored) diff --git a/frontend/.gitignore b/ui/.gitignore similarity index 100% rename from frontend/.gitignore rename to ui/.gitignore diff --git a/frontend/index.html b/ui/index.html similarity index 100% rename from frontend/index.html rename to ui/index.html diff --git a/frontend/package-lock.json b/ui/package-lock.json similarity index 100% rename from frontend/package-lock.json rename to ui/package-lock.json diff --git a/frontend/package.json b/ui/package.json similarity index 100% rename from frontend/package.json rename to ui/package.json diff --git a/frontend/playwright.config.js b/ui/playwright.config.js similarity index 100% rename from frontend/playwright.config.js rename to ui/playwright.config.js diff --git a/frontend/public/haiguji-favicon.jpg b/ui/public/haiguji-favicon.jpg similarity index 100% rename from frontend/public/haiguji-favicon.jpg rename to ui/public/haiguji-favicon.jpg diff --git a/frontend/src/App.vue b/ui/src/App.vue similarity index 100% rename from frontend/src/App.vue rename to ui/src/App.vue diff --git a/frontend/src/api/extraction.js b/ui/src/api/extraction.js similarity index 100% rename from frontend/src/api/extraction.js rename to ui/src/api/extraction.js diff --git a/frontend/src/api/http.js b/ui/src/api/http.js similarity index 100% rename from frontend/src/api/http.js rename to ui/src/api/http.js diff --git a/frontend/src/api/kg.js b/ui/src/api/kg.js similarity index 100% rename from frontend/src/api/kg.js rename to ui/src/api/kg.js diff --git a/frontend/src/api/qa.js b/ui/src/api/qa.js similarity index 100% rename from frontend/src/api/qa.js rename to ui/src/api/qa.js diff --git a/frontend/src/api/sessions.js b/ui/src/api/sessions.js similarity index 100% rename from frontend/src/api/sessions.js rename to ui/src/api/sessions.js diff --git a/frontend/src/assets/haiguji-logo.png b/ui/src/assets/haiguji-logo.png similarity index 100% rename from frontend/src/assets/haiguji-logo.png rename to ui/src/assets/haiguji-logo.png diff --git a/frontend/src/components/bench/BenchPanel.vue b/ui/src/components/bench/BenchPanel.vue similarity index 100% rename from frontend/src/components/bench/BenchPanel.vue rename to ui/src/components/bench/BenchPanel.vue diff --git a/frontend/src/components/bench/ChainGroup.vue b/ui/src/components/bench/ChainGroup.vue similarity index 100% rename from frontend/src/components/bench/ChainGroup.vue rename to ui/src/components/bench/ChainGroup.vue diff --git a/frontend/src/components/bench/ExtractionPane.vue b/ui/src/components/bench/ExtractionPane.vue similarity index 100% rename from frontend/src/components/bench/ExtractionPane.vue rename to ui/src/components/bench/ExtractionPane.vue diff --git a/frontend/src/components/bench/KgEntityDetail.vue b/ui/src/components/bench/KgEntityDetail.vue similarity index 100% rename from frontend/src/components/bench/KgEntityDetail.vue rename to ui/src/components/bench/KgEntityDetail.vue diff --git a/frontend/src/components/bench/KgGraphView.vue b/ui/src/components/bench/KgGraphView.vue similarity index 100% rename from frontend/src/components/bench/KgGraphView.vue rename to ui/src/components/bench/KgGraphView.vue diff --git a/frontend/src/components/bench/KgPane.vue b/ui/src/components/bench/KgPane.vue similarity index 100% rename from frontend/src/components/bench/KgPane.vue rename to ui/src/components/bench/KgPane.vue diff --git a/frontend/src/components/bench/ProgressItem.vue b/ui/src/components/bench/ProgressItem.vue similarity index 100% rename from frontend/src/components/bench/ProgressItem.vue rename to ui/src/components/bench/ProgressItem.vue diff --git a/frontend/src/components/bench/RetrievalPane.vue b/ui/src/components/bench/RetrievalPane.vue similarity index 100% rename from frontend/src/components/bench/RetrievalPane.vue rename to ui/src/components/bench/RetrievalPane.vue diff --git a/frontend/src/components/bench/SourceRow.vue b/ui/src/components/bench/SourceRow.vue similarity index 100% rename from frontend/src/components/bench/SourceRow.vue rename to ui/src/components/bench/SourceRow.vue diff --git a/frontend/src/components/bench/SourcesPane.vue b/ui/src/components/bench/SourcesPane.vue similarity index 100% rename from frontend/src/components/bench/SourcesPane.vue rename to ui/src/components/bench/SourcesPane.vue diff --git a/frontend/src/components/bench/benchLabels.js b/ui/src/components/bench/benchLabels.js similarity index 100% rename from frontend/src/components/bench/benchLabels.js rename to ui/src/components/bench/benchLabels.js diff --git a/frontend/src/components/bench/graphLayout.js b/ui/src/components/bench/graphLayout.js similarity index 100% rename from frontend/src/components/bench/graphLayout.js rename to ui/src/components/bench/graphLayout.js diff --git a/frontend/src/components/bench/kgPaneCompat.js b/ui/src/components/bench/kgPaneCompat.js similarity index 100% rename from frontend/src/components/bench/kgPaneCompat.js rename to ui/src/components/bench/kgPaneCompat.js diff --git a/frontend/src/components/chat/AssistantMessage.vue b/ui/src/components/chat/AssistantMessage.vue similarity index 100% rename from frontend/src/components/chat/AssistantMessage.vue rename to ui/src/components/chat/AssistantMessage.vue diff --git a/frontend/src/components/chat/ChatPanel.vue b/ui/src/components/chat/ChatPanel.vue similarity index 100% rename from frontend/src/components/chat/ChatPanel.vue rename to ui/src/components/chat/ChatPanel.vue diff --git a/frontend/src/components/chat/Composer.vue b/ui/src/components/chat/Composer.vue similarity index 100% rename from frontend/src/components/chat/Composer.vue rename to ui/src/components/chat/Composer.vue diff --git a/frontend/src/components/chat/EmptyState.vue b/ui/src/components/chat/EmptyState.vue similarity index 100% rename from frontend/src/components/chat/EmptyState.vue rename to ui/src/components/chat/EmptyState.vue diff --git a/frontend/src/components/chat/MessageList.vue b/ui/src/components/chat/MessageList.vue similarity index 100% rename from frontend/src/components/chat/MessageList.vue rename to ui/src/components/chat/MessageList.vue diff --git a/frontend/src/components/chat/PipelineCard.vue b/ui/src/components/chat/PipelineCard.vue similarity index 100% rename from frontend/src/components/chat/PipelineCard.vue rename to ui/src/components/chat/PipelineCard.vue diff --git a/frontend/src/components/chat/SuggestCard.vue b/ui/src/components/chat/SuggestCard.vue similarity index 100% rename from frontend/src/components/chat/SuggestCard.vue rename to ui/src/components/chat/SuggestCard.vue diff --git a/frontend/src/components/chat/ToolCard.vue b/ui/src/components/chat/ToolCard.vue similarity index 100% rename from frontend/src/components/chat/ToolCard.vue rename to ui/src/components/chat/ToolCard.vue diff --git a/frontend/src/components/chat/UserMessage.vue b/ui/src/components/chat/UserMessage.vue similarity index 100% rename from frontend/src/components/chat/UserMessage.vue rename to ui/src/components/chat/UserMessage.vue diff --git a/frontend/src/components/chat/markdown.js b/ui/src/components/chat/markdown.js similarity index 100% rename from frontend/src/components/chat/markdown.js rename to ui/src/components/chat/markdown.js diff --git a/frontend/src/components/layout/AppShell.vue b/ui/src/components/layout/AppShell.vue similarity index 100% rename from frontend/src/components/layout/AppShell.vue rename to ui/src/components/layout/AppShell.vue diff --git a/frontend/src/components/layout/AuroraBg.vue b/ui/src/components/layout/AuroraBg.vue similarity index 100% rename from frontend/src/components/layout/AuroraBg.vue rename to ui/src/components/layout/AuroraBg.vue diff --git a/frontend/src/components/layout/Drawer.vue b/ui/src/components/layout/Drawer.vue similarity index 100% rename from frontend/src/components/layout/Drawer.vue rename to ui/src/components/layout/Drawer.vue diff --git a/frontend/src/components/layout/StatusBar.vue b/ui/src/components/layout/StatusBar.vue similarity index 100% rename from frontend/src/components/layout/StatusBar.vue rename to ui/src/components/layout/StatusBar.vue diff --git a/frontend/src/components/layout/TopBar.vue b/ui/src/components/layout/TopBar.vue similarity index 100% rename from frontend/src/components/layout/TopBar.vue rename to ui/src/components/layout/TopBar.vue diff --git a/frontend/src/components/session/SearchBox.vue b/ui/src/components/session/SearchBox.vue similarity index 100% rename from frontend/src/components/session/SearchBox.vue rename to ui/src/components/session/SearchBox.vue diff --git a/frontend/src/components/session/SessionItem.vue b/ui/src/components/session/SessionItem.vue similarity index 100% rename from frontend/src/components/session/SessionItem.vue rename to ui/src/components/session/SessionItem.vue diff --git a/frontend/src/components/session/SessionPanel.vue b/ui/src/components/session/SessionPanel.vue similarity index 100% rename from frontend/src/components/session/SessionPanel.vue rename to ui/src/components/session/SessionPanel.vue diff --git a/frontend/src/components/session/SessionRail.vue b/ui/src/components/session/SessionRail.vue similarity index 100% rename from frontend/src/components/session/SessionRail.vue rename to ui/src/components/session/SessionRail.vue diff --git a/frontend/src/components/ui/EmptyNotice.vue b/ui/src/components/ui/EmptyNotice.vue similarity index 100% rename from frontend/src/components/ui/EmptyNotice.vue rename to ui/src/components/ui/EmptyNotice.vue diff --git a/frontend/src/components/ui/IconButton.vue b/ui/src/components/ui/IconButton.vue similarity index 100% rename from frontend/src/components/ui/IconButton.vue rename to ui/src/components/ui/IconButton.vue diff --git a/frontend/src/components/ui/SkeletonCard.vue b/ui/src/components/ui/SkeletonCard.vue similarity index 100% rename from frontend/src/components/ui/SkeletonCard.vue rename to ui/src/components/ui/SkeletonCard.vue diff --git a/frontend/src/components/ui/StatusBadge.vue b/ui/src/components/ui/StatusBadge.vue similarity index 100% rename from frontend/src/components/ui/StatusBadge.vue rename to ui/src/components/ui/StatusBadge.vue diff --git a/frontend/src/components/ui/StatusDot.vue b/ui/src/components/ui/StatusDot.vue similarity index 100% rename from frontend/src/components/ui/StatusDot.vue rename to ui/src/components/ui/StatusDot.vue diff --git a/frontend/src/config/modes.js b/ui/src/config/modes.js similarity index 100% rename from frontend/src/config/modes.js rename to ui/src/config/modes.js diff --git a/frontend/src/i18n/labels.js b/ui/src/i18n/labels.js similarity index 100% rename from frontend/src/i18n/labels.js rename to ui/src/i18n/labels.js diff --git a/frontend/src/main.js b/ui/src/main.js similarity index 100% rename from frontend/src/main.js rename to ui/src/main.js diff --git a/frontend/src/sse/parser.js b/ui/src/sse/parser.js similarity index 100% rename from frontend/src/sse/parser.js rename to ui/src/sse/parser.js diff --git a/frontend/src/stores/bench.js b/ui/src/stores/bench.js similarity index 100% rename from frontend/src/stores/bench.js rename to ui/src/stores/bench.js diff --git a/frontend/src/stores/chat.js b/ui/src/stores/chat.js similarity index 100% rename from frontend/src/stores/chat.js rename to ui/src/stores/chat.js diff --git a/frontend/src/stores/health.js b/ui/src/stores/health.js similarity index 100% rename from frontend/src/stores/health.js rename to ui/src/stores/health.js diff --git a/frontend/src/stores/sessions.js b/ui/src/stores/sessions.js similarity index 100% rename from frontend/src/stores/sessions.js rename to ui/src/stores/sessions.js diff --git a/frontend/src/stores/theme.js b/ui/src/stores/theme.js similarity index 100% rename from frontend/src/stores/theme.js rename to ui/src/stores/theme.js diff --git a/frontend/src/styles/animations.css b/ui/src/styles/animations.css similarity index 100% rename from frontend/src/styles/animations.css rename to ui/src/styles/animations.css diff --git a/frontend/src/styles/base.css b/ui/src/styles/base.css similarity index 100% rename from frontend/src/styles/base.css rename to ui/src/styles/base.css diff --git a/frontend/src/styles/responsive.css b/ui/src/styles/responsive.css similarity index 100% rename from frontend/src/styles/responsive.css rename to ui/src/styles/responsive.css diff --git a/frontend/src/styles/tokens.css b/ui/src/styles/tokens.css similarity index 100% rename from frontend/src/styles/tokens.css rename to ui/src/styles/tokens.css diff --git a/frontend/vite.config.js b/ui/vite.config.js similarity index 100% rename from frontend/vite.config.js rename to ui/vite.config.js diff --git a/frontend/vitest.config.js b/ui/vitest.config.js similarity index 71% rename from frontend/vitest.config.js rename to ui/vitest.config.js index 6f80b2c..240a888 100644 --- a/frontend/vitest.config.js +++ b/ui/vitest.config.js @@ -1,11 +1,11 @@ // vitest.config.js —— B1 归属。单测配置(jsdom + @vue/test-utils)。 -// 测试文件位于项目根 tests/frontend/unit(frontend/ 之外),依赖解析自 frontend/node_modules: +// 测试文件位于项目根 tests/frontend/unit(ui/ 之外),依赖解析自 ui/node_modules: // - server.fs.allow 放行上级目录 // - include 用绝对路径(与运行 cwd 无关) -// - 测试文件中的裸导入(pinia/vue/@vue/test-utils)以 alias 指到 frontend/node_modules 的 ESM 入口 +// - 测试文件中的裸导入(pinia/vue/@vue/test-utils)以 alias 指到 ui/node_modules 的 ESM 入口 // 运行方式(任选): -// cd frontend && npx vitest run tests/frontend/unit/b1 -// 或项目根:npx --prefix frontend vitest run --config frontend/vitest.config.js tests/frontend/unit/b1 +// cd ui && npx vitest run tests/frontend/unit/b1 +// 或项目根:npx --prefix ui vitest run --config ui/vitest.config.js tests/frontend/unit/b1 import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; import vue from '@vitejs/plugin-vue'; @@ -19,14 +19,14 @@ export default defineConfig({ cacheDir: 'node_modules/.vite-vitest', resolve: { alias: { - // 测试文件在 frontend/ 外,裸包名解析不到 frontend/node_modules + // 测试文件在 ui/ 外,裸包名解析不到 ui/node_modules pinia: `${nodeModules}/pinia/dist/pinia.mjs`, vue: `${nodeModules}/vue/index.mjs`, '@vue/test-utils': `${nodeModules}/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs`, }, }, server: { - // 测试文件位于项目根 tests/(frontend/ 之外),放行上级目录 + // 测试文件位于项目根 tests/(ui/ 之外),放行上级目录 fs: { allow: ['..'] }, }, test: {