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 /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 /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//`(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=
+NEO4J_PASSWORDS=
# ---- 超时阶梯(秒)----
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 / (DISCOVERY.md §2.1)
+# NEO4J_USER / NEO4J_PASSWORD Neo4j 认证,默认 neo4j / (DISCOVERY.md §2.1)
# TIMEOUT curl 超时秒,默认 4
#
# 注意:不探测 QA /search 与 /chat/stream —— 实测单次 37~92s(DISCOVERY.md §4),
@@ -57,12 +57,12 @@ check_http() {
}
# check_neo4j <名称> :POST db/neo4j/tx/commit 一条最小只读 Cypher。
-# 契约(DISCOVERY.md §2.1):direct-v1-full 需要 Basic auth(neo4j/);
+# 契约(DISCOVERY.md §2.1):direct-v1-full 需要 Basic auth(neo4j/);
# 凭据经环境变量 NEO4J_USER/NEO4J_PASSWORD 覆盖(默认指向当选实例)。
check_neo4j() {
local name="$1" base="$2"
local user="${NEO4J_USER:-neo4j}"
- local pass="${NEO4J_PASSWORD:-}"
+ local pass="${NEO4J_PASSWORD:-}"
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// —— 构建产物(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: {