P0/M1: 四部分重构——frontend→ui、backend→sdk 改名 + 引用修复 + 骨架
- git mv 保留历史:ui/(Vue3+Vite)、sdk/(BFF 中间层)
- 路径语义修正:db_path/figures_cache 改相对 sdk/(BFF cwd),
历史库 sdk/backend/data 迁移至 sdk/data/(会话/图片缓存无损)
- 引用修复 ~15 处:config.py、deploy/*.sh、vitest/playwright 配置、
单测导入路径、docs、README、gitignore、.env.example
- 骨架:agent/{qa,methodology,legacy-rag,vendor}、knowledge/manifest.yaml、
tooling/{compile,kb,crypto,assemble,audit,tools,scripts} + README
- 门禁验证:sdk pytest 125 通过、ui vitest 158 通过、
生产 BFF 脚本重启四上游全绿 + 静态托管 + 会话库无损
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
.claude/ vendor/
|
||||||
+2
-1
@@ -4,7 +4,7 @@ dist/
|
|||||||
.venv/
|
.venv/
|
||||||
.venv-backend/
|
.venv-backend/
|
||||||
.env
|
.env
|
||||||
backend/data/
|
sdk/data/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
@@ -15,3 +15,4 @@ coverage/
|
|||||||
**/data/
|
**/data/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
tooling/build/
|
||||||
|
|||||||
@@ -16,11 +16,14 @@
|
|||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
```
|
```
|
||||||
backend/ BFF(app/ 应用代码,mock/ 前端开发用 mock server)
|
sdk/ BFF(app/ 应用代码,mock/ 前端开发用 mock server)
|
||||||
frontend/ Vue 3 + Vite 前端(src/ 源码)
|
ui/ Vue 3 + Vite 前端(src/ 源码)
|
||||||
docs/ 全部文档:PRD / API / UI_SPEC / ARCHITECTURE / TEST_PLAN / TEST_CASES / DISCOVERY / 测试报告
|
agent/ 底层服务:qa / methodology / legacy-rag + vendor 共享池(编译交付)
|
||||||
deploy/ 部署脚本:sync.sh(本地→远端)、bff_restart.sh、start-dev.sh、集成检查、E2E 验收
|
knowledge/ 向量知识库打包定义(数据不入 git)
|
||||||
tests/ 测试(backend 接口、frontend 单元、e2e)
|
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'
|
ssh 100.109.52.26 'bash ~/5090/teamWorkspace/zh/PlantAI-rag-new/deploy/bff_restart.sh'
|
||||||
|
|
||||||
# 2. 启动 Vite 开发服务器(开发模式)
|
# 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'
|
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 侧访问隧道
|
# 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
|
# 浏览器打开 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
|
```bash
|
||||||
# 后端(conda plantai-bff 或本地 .venv-backend)
|
# 后端(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 ui && npx vitest run --config vitest.config.js # 单元 + 组件
|
||||||
cd frontend && npx playwright test # E2E 三档视口(mobile/tablet/desktop)
|
cd ui && npx playwright test # E2E 三档视口(mobile/tablet/desktop)
|
||||||
# 上游连通冒烟
|
# 上游连通冒烟
|
||||||
bash tests/smoke_upstreams.sh
|
bash tests/smoke_upstreams.sh
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 暂存目录。
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# backend/.gitignore(最小版,各 agent 勿改,如需调整向 PM 提出)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
.pytest_cache/
|
|
||||||
.env
|
|
||||||
data/
|
|
||||||
@@ -13,8 +13,8 @@ conda activate plantai-bff
|
|||||||
pkill -f "[u]vicorn app.main:app" 2>/dev/null
|
pkill -f "[u]vicorn app.main:app" 2>/dev/null
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
cd "$ROOT/backend"
|
cd "$ROOT/sdk"
|
||||||
# 加载运行时凭据(backend/.env,不入 git)并导出给应用进程。
|
# 加载运行时凭据(sdk/.env,不入 git)并导出给应用进程。
|
||||||
# pydantic-settings 会把 .env 载入 settings 对象,但 services/neo4j.py 的
|
# pydantic-settings 会把 .env 载入 settings 对象,但 services/neo4j.py 的
|
||||||
# _env() 直接读 os.environ,故需显式 set -a 导出。
|
# _env() 直接读 os.environ,故需显式 set -a 导出。
|
||||||
set -a
|
set -a
|
||||||
@@ -29,7 +29,7 @@ if ss -tln | grep -q ":8090 "; then
|
|||||||
echo "BFF UP ($BIND:8090)"
|
echo "BFF UP ($BIND:8090)"
|
||||||
curl -s --max-time 20 http://127.0.0.1:8090/api/health | head -c 200
|
curl -s --max-time 20 http://127.0.0.1:8090/api/health | head -c 200
|
||||||
echo
|
echo
|
||||||
# 生产形态:静态托管 frontend/dist(存在时 BFF 单进程服务整个前端)
|
# 生产形态:静态托管 ui/dist(存在时 BFF 单进程服务整个前端)
|
||||||
curl -s --max-time 10 http://127.0.0.1:8090/ | grep -o "<title>[^<]*" || echo "(静态托管未生效,需同步 dist)"
|
curl -s --max-time 10 http://127.0.0.1:8090/ | grep -o "<title>[^<]*" || echo "(静态托管未生效,需同步 dist)"
|
||||||
else
|
else
|
||||||
echo "BFF FAIL —— 日志尾部:"
|
echo "BFF FAIL —— 日志尾部:"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ async function waitFor(fn, ms, iv = 700, label = '') {
|
|||||||
const page = await ctx.newPage();
|
const page = await ctx.newPage();
|
||||||
const errors = [];
|
const errors = [];
|
||||||
page.on('pageerror', (e) => errors.push(e.message));
|
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);
|
const prod = await page.goto('http://127.0.0.1:8090/', { waitUntil: 'domcontentloaded' }).then(() => true).catch(() => false);
|
||||||
if (prod) {
|
if (prod) {
|
||||||
await waitFor(() => page.locator('.app-shell').count(), 15000, 500, '生产形态 AppShell');
|
await waitFor(() => page.locator('.app-shell').count(), 15000, 500, '生产形态 AppShell');
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ CONTAINER="neo4j-plant-srt-direct-v1-full"
|
|||||||
IMAGE="neo4j:5.15.0-community"
|
IMAGE="neo4j:5.15.0-community"
|
||||||
DATA_ROOT="/home/xzq/home/wutx/gene-family-mas-v2/data/neo4j_plant_srt_direct_v1_docker"
|
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"
|
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)
|
NEO4J_PASSWORD=$(grep -oE "^BFF_NEO4J_PASSWORD_DIRECT=.*" "$ENV_FILE" | cut -d= -f2)
|
||||||
if [ -z "$NEO4J_PASSWORD" ]; then
|
if [ -z "$NEO4J_PASSWORD" ]; then
|
||||||
echo "ERROR: 未在 $ENV_FILE 找到 BFF_NEO4J_PASSWORD_DIRECT"
|
echo "ERROR: 未在 $ENV_FILE 找到 BFF_NEO4J_PASSWORD_DIRECT"
|
||||||
|
|||||||
+2
-2
@@ -3,10 +3,10 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ROOT="/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new"
|
ROOT="/home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new"
|
||||||
echo "== 启动 BFF (127.0.0.1:8090) =="
|
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 &)
|
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) =="
|
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
|
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: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"
|
curl -s http://127.0.0.1:5180 > /dev/null && echo "Vite OK" || echo "Vite 未就绪,见 /tmp/plantai-vite.log"
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ LOCAL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|||||||
rsync -az --delete -e "ssh $SSH_OPTS" \
|
rsync -az --delete -e "ssh $SSH_OPTS" \
|
||||||
--exclude 'node_modules' --exclude '__pycache__' --exclude '.venv' \
|
--exclude 'node_modules' --exclude '__pycache__' --exclude '.venv' \
|
||||||
--exclude '.venv-backend' \
|
--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 '.env' \
|
||||||
--exclude '.git' \
|
--exclude '.git' \
|
||||||
"$LOCAL_DIR/" "$REMOTE_HOST:$REMOTE_DIR/" 2>&1 | grep -v "remote port forwarding" || true
|
"$LOCAL_DIR/" "$REMOTE_HOST:$REMOTE_DIR/" 2>&1 | grep -v "remote port forwarding" || true
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ TARGET="${1:-8090}"
|
|||||||
pkill -f "node_modules/.bin/[v]ite" 2>/dev/null
|
pkill -f "node_modules/.bin/[v]ite" 2>/dev/null
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
cd "$ROOT/frontend"
|
cd "$ROOT/ui"
|
||||||
VITE_BFF_TARGET="http://127.0.0.1:${TARGET}" \
|
VITE_BFF_TARGET="http://127.0.0.1:${TARGET}" \
|
||||||
setsid npm run dev > /tmp/plantai-vite.log 2>&1 < /dev/null &
|
setsid npm run dev > /tmp/plantai-vite.log 2>&1 < /dev/null &
|
||||||
disown
|
disown
|
||||||
|
|||||||
+2
-2
@@ -40,7 +40,7 @@
|
|||||||
- 验收脚本:`deploy/m5_acceptance.js`(15 断言,chromium headless)
|
- 验收脚本:`deploy/m5_acceptance.js`(15 断言,chromium headless)
|
||||||
- 运行位置:远端(真实 BFF 8090 生产形态 + Vite 5180 开发形态双链路)
|
- 运行位置:远端(真实 BFF 8090 生产形态 + Vite 5180 开发形态双链路)
|
||||||
- 用时:约 3 分钟(含一次真实 QA ≈ 50s)
|
- 用时:约 3 分钟(含一次真实 QA ≈ 50s)
|
||||||
- 期间修复:BFF 静态托管 dist 路径解析(`backend/frontend/dist` → 项目根解析,2026-08-25 PM)
|
- 期间修复:BFF 静态托管 dist 路径解析(`sdk/ui/dist` → 项目根解析,2026-08-25 PM)
|
||||||
|
|
||||||
## 5. 结论
|
## 5. 结论
|
||||||
|
|
||||||
@@ -49,4 +49,4 @@
|
|||||||
下一步建议(用户决策,不在本次范围):
|
下一步建议(用户决策,不在本次范围):
|
||||||
- DeepSeek 充值 → 验证自然语言答案质量
|
- DeepSeek 充值 → 验证自然语言答案质量
|
||||||
- 长期运行监控(/api/health 轮询、上游探活)
|
- 长期运行监控(/api/health 轮询、上游探活)
|
||||||
- 可选:laptop 档 E2E project 补齐、tests/backend L2 respx 独立测试
|
- 可选:laptop 档 E2E project 补齐、tests/sdk L2 respx 独立测试
|
||||||
|
|||||||
+19
-19
@@ -61,11 +61,11 @@
|
|||||||
| BFF 监听 | `127.0.0.1:8090`(占用则退 8091) |
|
| BFF 监听 | `127.0.0.1:8090`(占用则退 8091) |
|
||||||
| Vite dev | `127.0.0.1:5180`(占用则退 5181) |
|
| Vite dev | `127.0.0.1:5180`(占用则退 5181) |
|
||||||
| BFF conda 环境 | `plantai-bff`(Python 3.11,新建,**不复用** `gfpy312`) |
|
| 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** |
|
| 前端工具链 | 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`** |
|
| 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
|
```bash
|
||||||
rsync -az --delete \
|
rsync -az --delete \
|
||||||
--exclude 'node_modules' --exclude '__pycache__' --exclude '.venv' \
|
--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/ \
|
/Users/jerry/haiguji/PlantAI-rag-new/ \
|
||||||
100.109.52.26:/home/xzq/5090/teamWorkspace/zh/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 改代码。
|
- 所有 agent **只在本地路径写文件**,绝不用 SSH heredoc 改代码。
|
||||||
- 同步由 **PM 统一执行**,agent 不自行 rsync(避免半成品覆盖远端)。
|
- 同步由 **PM 统一执行**,agent 不自行 rsync(避免半成品覆盖远端)。
|
||||||
- `backend/data`(数据库)、`node_modules`、`dist` 不同步,属远端运行时产物。
|
- `sdk/data`(数据库)、`node_modules`、`dist` 不同步,属远端运行时产物。
|
||||||
- 文档最终必须在远端 `docs/` 下可查——每次里程碑结束由 PM 同步一次。
|
- 文档最终必须在远端 `docs/` 下可查——每次里程碑结束由 PM 同步一次。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -102,17 +102,17 @@ rsync -az --delete \
|
|||||||
|
|
||||||
| Agent | 职责 | 独占路径 |
|
| 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` |
|
| **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` |
|
| **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 中继、阶段映射、重试与看门狗 | `backend/app/routers/qa.py`、`backend/app/services/sse_relay.py` |
|
| **A2 后端·QA** | QA 代理、SSE 中继、阶段映射、重试与看门狗 | `sdk/app/routers/qa.py`、`sdk/app/services/sse_relay.py` |
|
||||||
| **A3 后端·图谱与抽取** | Neo4j Bolt 直连、抽取代理与回落 | `backend/app/routers/{kg,extraction}.py`、`backend/app/services/{neo4j,extraction}.py` |
|
| **A3 后端·图谱与抽取** | Neo4j Bolt 直连、抽取代理与回落 | `sdk/app/routers/{kg,extraction}.py`、`sdk/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/**` |
|
| **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 客户端 | `frontend/src/components/{session,chat}/**`、`frontend/src/stores/{sessions,chat}.js`、`frontend/src/sse/**`、`frontend/src/api/{qa,sessions}.js`、`tests/frontend/unit/b2/**` |
|
| **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:检索过程、文献来源、提取任务、知识图谱 | `frontend/src/components/bench/**`、`frontend/src/stores/bench.js`、`frontend/src/api/{kg,extraction}.js`、`tests/frontend/unit/b3/**` |
|
| **B3 前端·工作台** | 右侧四个 tab:检索过程、文献来源、提取任务、知识图谱 | `ui/src/components/bench/**`、`ui/src/stores/bench.js`、`ui/src/api/{kg,extraction}.js`、`tests/ui/unit/b3/**` |
|
||||||
| **T 测试** | 用例编写与执行、缺陷报告 | `tests/backend/api/**`、`tests/e2e/**`、`playwright.config.js`、`docs/TEST_CASES.md`、`docs/TEST_REPORT_*.md` |
|
| **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
|
## 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`
|
- FastAPI,监听 `127.0.0.1:8099`
|
||||||
- 实现 `API.md` 中**全部**接口,返回 `backend/mock/fixtures/*.json` 里的固定数据
|
- 实现 `API.md` 中**全部**接口,返回 `sdk/mock/fixtures/*.json` 里的固定数据
|
||||||
- 包含一条**可回放的 SSE 事件序列**(按真实时序 sleep),让流式 UI 能在无上游时开发
|
- 包含一条**可回放的 SSE 事件序列**(按真实时序 sleep),让流式 UI 能在无上游时开发
|
||||||
- 支持 `?scenario=` 查询参数切换场景:`normal` / `empty`(空结果)/ `slow`(慢响应)/ `error`(上游故障)/ `degraded`(部分降级)
|
- 支持 `?scenario=` 查询参数切换场景:`normal` / `empty`(空结果)/ `slow`(慢响应)/ `error`(上游故障)/ `degraded`(部分降级)
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ PlantAI-rag-new/
|
|||||||
├── deploy/
|
├── deploy/
|
||||||
│ ├── sync.sh # 本地 → 远端 rsync
|
│ ├── sync.sh # 本地 → 远端 rsync
|
||||||
│ └── start-dev.sh # 拉起 BFF + vite,打印隧道命令
|
│ └── start-dev.sh # 拉起 BFF + vite,打印隧道命令
|
||||||
├── backend/
|
├── sdk/
|
||||||
│ ├── pyproject.toml run.sh .env.example
|
│ ├── pyproject.toml run.sh .env.example
|
||||||
│ ├── data/ # plantai.db(不入库、不同步)
|
│ ├── data/ # plantai.db(不入库、不同步)
|
||||||
│ ├── mock/
|
│ ├── mock/
|
||||||
@@ -176,7 +176,7 @@ PlantAI-rag-new/
|
|||||||
│ ├── main.py config.py db.py schemas.py upstream.py
|
│ ├── main.py config.py db.py schemas.py upstream.py
|
||||||
│ ├── routers/ health.py sessions.py qa.py kg.py extraction.py
|
│ ├── routers/ health.py sessions.py qa.py kg.py extraction.py
|
||||||
│ └── services/ normalize.py sse_relay.py neo4j.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
|
│ ├── package.json vite.config.js index.html vitest.config.js
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── main.js App.vue
|
│ ├── main.js App.vue
|
||||||
@@ -192,8 +192,8 @@ PlantAI-rag-new/
|
|||||||
│ ├── chat/ ChatPanel、MessageList、Composer、PipelineCard、ToolCard …
|
│ ├── chat/ ChatPanel、MessageList、Composer、PipelineCard、ToolCard …
|
||||||
│ └── bench/ BenchPanel、RetrievalPane、SourcesPane、ExtractionPane、KgPane …
|
│ └── bench/ BenchPanel、RetrievalPane、SourcesPane、ExtractionPane、KgPane …
|
||||||
├── tests/
|
├── tests/
|
||||||
│ ├── backend/ # pytest
|
│ ├── sdk/ # pytest
|
||||||
│ ├── frontend/ # vitest
|
│ ├── ui/ # vitest
|
||||||
│ └── e2e/ # playwright
|
│ └── e2e/ # playwright
|
||||||
└── playwright.config.js
|
└── playwright.config.js
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-5
@@ -5,8 +5,8 @@
|
|||||||
- **计划批准**:新前端 + 薄适配层方案获批。端口 8090(BFF)/ 5180(Vite dev),并行部署不切换。
|
- **计划批准**:新前端 + 薄适配层方案获批。端口 8090(BFF)/ 5180(Vite dev),并行部署不切换。
|
||||||
- **M0 启动**:远端目录树建立;8090/5180 端口复核空闲;本地 staging 目录 `/Users/jerry/haiguji/PlantAI-rag-new/` 建立。
|
- **M0 启动**:远端目录树建立;8090/5180 端口复核空闲;本地 staging 目录 `/Users/jerry/haiguji/PlantAI-rag-new/` 建立。
|
||||||
- **契约文档定稿**:`PRD.md`(81 个 FR)、`API.md`、`UI_SPEC.md`、`ARCHITECTURE.md`、`TEST_PLAN.md` 完成并锁定。
|
- **契约文档定稿**:`PRD.md`(81 个 FR)、`API.md`、`UI_SPEC.md`、`ARCHITECTURE.md`、`TEST_PLAN.md` 完成并锁定。
|
||||||
- **Mock 层完成**:`backend/mock/mock_server.py` + 21 个 fixtures(含 SSE 回放序列)。
|
- **Mock 层完成**:`sdk/mock/mock_server.py` + 21 个 fixtures(含 SSE 回放序列)。
|
||||||
- **共享前端文件完成**:`frontend/src/i18n/labels.js`、`frontend/src/config/modes.js`。
|
- **共享前端文件完成**:`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 运行并全接口验证通过。
|
- **环境就绪**:`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 行)。**契约偏差修正**(基于真实上游):
|
- **D0 探测完成**(`DISCOVERY.md`,345 行)。**契约偏差修正**(基于真实上游):
|
||||||
- Neo4j 当选 `neo4j-plant-srt-direct-v1-full`(bolt 18788,QA 7920 实际使用)。schema 为 **canonical**(`SRT2CanonicalEntity`/`SRT2CanonicalAssertion`),非假设的 `:Entity`;**无全文索引**,名字检索走 `name_norm` 前缀索引(0.09s)。
|
- 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 通过。
|
- **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`)。
|
- **本地开发环境补齐**:`.venv-backend`(3.12,供后端 agent 本地自测)+ 本地 node_modules;`deploy/sync.sh` 排除规则修正(`.venv-backend`)。
|
||||||
- **跨归属占位脚手架**:backend/app 全部模块 + frontend main.js/App.vue 占位,由各 agent 在 M1 以真实实现替换。
|
- **跨归属占位脚手架**:sdk/app 全部模块 + ui 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`)。
|
- **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 完成**(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 汇报后统一派发。
|
- **待办**: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 改为薄别名。
|
- **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。
|
- **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 清理了全部测试污染会话数据。
|
- **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 与浏览器访问)。
|
- **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 下午)
|
## DeepSeek 恢复与 QA 修复(2026-08-25 下午)
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -603,24 +603,24 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# L1 后端单元(远端 plantai-bff conda 环境;A 组自测,T 复跑确认)
|
# L1 后端单元(远端 plantai-bff conda 环境;A 组自测,T 复跑确认)
|
||||||
cd backend && pytest tests -m unit -q
|
cd sdk && pytest tests -m unit -q
|
||||||
|
|
||||||
# L2 后端接口(respx mock 上游;用例见 L2 标注项)
|
# L2 后端接口(respx mock 上游;用例见 L2 标注项)
|
||||||
cd backend && pytest tests -m api -q
|
cd sdk && pytest tests -m api -q
|
||||||
|
|
||||||
# L2s 上游 smoke(仅集成点 M2 与验收前手动各跑一次;远端本机执行)
|
# L2s 上游 smoke(仅集成点 M2 与验收前手动各跑一次;远端本机执行)
|
||||||
bash tests/smoke_upstreams.sh
|
bash tests/smoke_upstreams.sh
|
||||||
|
|
||||||
# L3+L4 前端(vitest;远端 frontend/)
|
# L3+L4 前端(vitest;远端 ui/)
|
||||||
cd frontend && npx vitest run
|
cd ui && npx vitest run
|
||||||
|
|
||||||
# L5 E2E(三档视口;dev server 由 playwright webServer 自动拉起或复用已启动的 5180)
|
# 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)
|
### 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 | 用途 | 使用用例示例 |
|
| scenario | 用途 | 使用用例示例 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -643,14 +643,14 @@ mock server:`python backend/mock/mock_server.py`(127.0.0.1:8099),场景
|
|||||||
### 3.4 L5 运行环境一次性准备(已验证)
|
### 3.4 L5 运行环境一次性准备(已验证)
|
||||||
|
|
||||||
- **Node 18(远端)**:`npx playwright@1.49.1 test` 首次运行自动下载 chromium 1148(≈150MB)。实测 CDN 可达(Mac 直连 2.9MB/s)。
|
- **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
|
```bash
|
||||||
# 远端,一次性
|
# 远端,一次性
|
||||||
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
|
cd /home/xzq/5090/teamWorkspace/zh/PlantAI-rag-new
|
||||||
ln -sfn frontend/node_modules node_modules # 若目录已存在先确认其内容再覆盖
|
ln -sfn ui/node_modules node_modules # 若目录已存在先确认其内容再覆盖
|
||||||
cd frontend && npm install --save-dev @playwright/test@1.49.1 # 写入 package.json 需 PM 裁决,见 §5 未决问题 4
|
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 替代。
|
- **本地 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 占位期走占位回退分支。
|
- **本机验证结果(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/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` 环境变量覆盖 |
|
| `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 不自行改用例契约)。
|
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」双路径写预期,无需改用例。
|
2. TC-EXTRACT-005/007(上传进度/超限)依赖 B2/B3 上传 UI 与 50MB 校验落点(前端或 BFF)——已按「前端拦截或 422」双路径写预期,无需改用例。
|
||||||
3. TC-KG 节点详情在 Neo4j 不可用时的属性来源为「消息内 graph 块已有属性」;graph 块节点若不含属性字段,该断言在实现时需以实际块结构微调步骤(预期语义不变)。
|
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 记录完整步骤。
|
5. **task 验证命令差异**:任务书给的验证命令 `npx playwright test`(无版本)在本机无已安装 playwright 时会从 npm 拉最新版;远端 Node 18 + `npx playwright@1.49.1` 仍会拉 1.49.1 的 chromium 1148(≈150MB,CDN 实测可达)。已在本文件 §3.4 记录完整步骤。
|
||||||
|
|||||||
+9
-9
@@ -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` |
|
| L2 后端接口 | pytest + respx(mock 上游) | T 主写,A 组配合 | 每个 BFF 接口的正常/降级/超时/重试路径 | `pytest tests -m api` |
|
||||||
| L2s 上游 smoke | 脚本 | T | 对真实 7920/8000/Neo4j 的最小连通验证(**不在 CI 每轮跑**,仅集成点与验收前跑一次) | `bash tests/smoke_upstreams.sh` |
|
| 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 补写 | 每个组件的全部状态(默认/悬停/激活/禁用/加载/空/错误) | 同上 |
|
| L4 前端组件 | Vitest + @vue/test-utils | B 组自测 + T 补写 | 每个组件的全部状态(默认/悬停/激活/禁用/加载/空/错误) | 同上 |
|
||||||
| L5 端到端 | Playwright | T | 完整流程 × 三档视口 | `npx playwright test` |
|
| L5 端到端 | Playwright | T | 完整流程 × 三档视口 | `npx playwright test` |
|
||||||
| L6 验收 | 人工 + 浏览器 | PM | 真实上游 + 视觉走查 | 见验收脚本 |
|
| L6 验收 | 人工 + 浏览器 | PM | 真实上游 + 视觉走查 | 见验收脚本 |
|
||||||
@@ -30,10 +30,10 @@
|
|||||||
| 项 | 值 |
|
| 项 | 值 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| L1/L2 运行处 | 远端 `plantai-bff` conda 环境(pytest) |
|
| 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 运行处 | 远端(Playwright chromium,headed 不强制),**或** Mac 本地通过 SSH 隧道打到远端 |
|
||||||
| L5 视口 | iPhone 14 390×844 | iPad 768×1024 | Desktop 1440×900 |
|
| 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 选型) |
|
| 真实上游 | 7920 / 8000 / 7900 / Neo4j(按 DISCOVERY.md 选型) |
|
||||||
|
|
||||||
## 4. 缺陷分级
|
## 4. 缺陷分级
|
||||||
@@ -74,16 +74,16 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 后端(远端,plantai-bff 环境)
|
# 后端(远端,plantai-bff 环境)
|
||||||
cd backend && pytest tests -m unit -q # L1
|
cd sdk && pytest tests -m unit -q # L1
|
||||||
cd backend && pytest tests -m api -q # L2(respx mock)
|
cd sdk && pytest tests -m api -q # L2(respx mock)
|
||||||
bash tests/smoke_upstreams.sh # L2s 真实连通(手动触发)
|
bash tests/smoke_upstreams.sh # L2s 真实连通(手动触发)
|
||||||
|
|
||||||
# 前端(远端)
|
# 前端(远端)
|
||||||
cd frontend && npx vitest run # L3+L4
|
cd ui && npx vitest run # L3+L4
|
||||||
cd frontend && npx playwright test # L5(三档视口配置在 playwright.config.js)
|
cd ui && npx playwright test # L5(三档视口配置在 playwright.config.js)
|
||||||
|
|
||||||
# mock(开发与 L5 的 error/degraded 场景)
|
# 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 必须包含)
|
## 8. 报告模板(TEST_REPORT_R{n}.md 必须包含)
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 被测版本 | M1 集成后(PM 已修复集成问题清单,见协调通知) |
|
| 被测版本 | M1 集成后(PM 已修复集成问题清单,见协调通知) |
|
||||||
| 运行环境 | 远端 plantai-bff 机(100.109.52.26);BFF 8090(真实上游 7920/8000/7900/Neo4j 18788 全通);Vite 5180 → 8090;mock 8099 运行中 |
|
| 运行环境 | 远端 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) |
|
| L1/L2 运行 | `sdk` 全量 pytest(conda plantai-bff,pytest 9.1.1;本轮补充安装 pytest/pytest-asyncio/pytest-cov) |
|
||||||
| L3/L4 运行 | `frontend` vitest 2.1.8(137 用例) |
|
| L3/L4 运行 | `ui` vitest 2.1.8(137 用例) |
|
||||||
| L5 运行 | Playwright 1.49.1 + chromium-1148,三档视口 42→48 条测试 |
|
| L5 运行 | Playwright 1.49.1 + chromium-1148,三档视口 42→48 条测试 |
|
||||||
| L2s 运行 | `bash tests/smoke_upstreams.sh`(本轮修复 Neo4j 认证缺陷后重跑) |
|
| L2s 运行 | `bash tests/smoke_upstreams.sh`(本轮修复 Neo4j 认证缺陷后重跑) |
|
||||||
| 轮次纪律 | 只跑测试与写报告,未修改任何产品代码(`frontend/src/**`、`backend/app/**` 零改动;测试脚本自身缺陷修复除外) |
|
| 轮次纪律 | 只跑测试与写报告,未修改任何产品代码(`ui/src/**`、`sdk/app/**` 零改动;测试脚本自身缺陷修复除外) |
|
||||||
|
|
||||||
## 2. 首页统计
|
## 2. 首页统计
|
||||||
|
|
||||||
@@ -91,15 +91,15 @@
|
|||||||
## 5. 遗留问题与风险
|
## 5. 遗留问题与风险
|
||||||
|
|
||||||
1. **QA LLM 402 余额不足(已知,非缺陷)**:chat 答案为 fallback 证据列表而非自然语言。涉及答案内容的断言(TC-CHAT-027 等)按 TEST_CASES §3.3 回退结构断言;**依赖充值的用例未在本轮验证最终答案质量**。BUG-R1-06(markdown)恰由 fallback 列表暴露——充值后自然语言答案同样会命中,修复不受 402 影响。
|
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` 已覆盖持久化,风险低。
|
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 项目或在该视口手动验证。
|
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 若在意可执行一次数据盘点。
|
6. **测试数据污染**:R1 新建会话已按前缀清理(R1主流程/R1检索/R1会话/R1中断 等),远端 SQLite 中 `水稻SPL基因家族…` 等自动标题会话为测试产生,已尽量清理,PM 若在意可执行一次数据盘点。
|
||||||
7. **48 条 L5 中 12 条 skip 为档位门控设计**(如 mock 场景仅桌面代表、E2E-05 仅桌面),已在 TEST_CASES 执行说明中记录;如需三档全跑需 PM 确认(真实上游流成本高:每档主流程 ≈1 分钟、检索 ≈92s)。
|
7. **48 条 L5 中 12 条 skip 为档位门控设计**(如 mock 场景仅桌面代表、E2E-05 仅桌面),已在 TEST_CASES 执行说明中记录;如需三档全跑需 PM 确认(真实上游流成本高:每档主流程 ≈1 分钟、检索 ≈92s)。
|
||||||
|
|
||||||
## 6. 证据归档
|
## 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)
|
- 远端日志:`/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
|
- smoke 输出:本文件 §2.1 引用;修复后 7/7 PASS
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 被测版本 | M4 第二轮:R1 的 8 个缺陷修复后(修复清单见协调通知) |
|
| 被测版本 | M4 第二轮:R1 的 8 个缺陷修复后(修复清单见协调通知) |
|
||||||
| 运行环境 | 远端 plantai-bff 机;BFF 8090(真实上游 7920/8000/7900/Neo4j 全通,health 无索引误报);Vite 5180→8090(HMR 已同步修复);mock 8099(本地/远端 md5 一致) |
|
| 运行环境 | 远端 plantai-bff 机;BFF 8090(真实上游 7920/8000/7900/Neo4j 全通,health 无索引误报);Vite 5180→8090(HMR 已同步修复);mock 8099(本地/远端 md5 一致) |
|
||||||
| L1/L2 | `backend` pytest 全量 + `--cov=app` |
|
| L1/L2 | `sdk` pytest 全量 + `--cov=app` |
|
||||||
| L3/L4 | `frontend` vitest 全量 + `--coverage` |
|
| L3/L4 | `ui` vitest 全量 + `--coverage` |
|
||||||
| L5 | Playwright 1.49.1 + chromium-1148,三档视口 48 条 |
|
| L5 | Playwright 1.49.1 + chromium-1148,三档视口 48 条 |
|
||||||
| L2s | `bash tests/smoke_upstreams.sh` |
|
| L2s | `bash tests/smoke_upstreams.sh` |
|
||||||
| 铁律 | 只跑测试与写报告;未修改任何产品代码(`frontend/src/**`、`backend/app/**` 零改动) |
|
| 铁律 | 只跑测试与写报告;未修改任何产品代码(`ui/src/**`、`sdk/app/**` 零改动) |
|
||||||
|
|
||||||
## 2. 首页统计
|
## 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,消除环境抖动误报。
|
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 确认。
|
2. **12 条 skip 维持**(设计门控):mock 场景仅桌面代表、E2E-05 仅桌面(真实流成本高,跨档行为一致)。如需三档全跑需 PM 确认。
|
||||||
3. **Laptop 档(1024–1279)仍无 Playwright project**(R1 遗留问题 4):TC-RESP-002(左栏自动折叠 64px/右栏 340px)本轮仍未 L5 覆盖,需 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 验收。
|
5. **QA LLM 402 仍存在**:chat 答案为 fallback 证据列表;「依赖充值」断言维持结构回退。若这是最后一轮,充值后的答案质量断言移交 L6 验收。
|
||||||
6. **共享数据**:R2 测试会话已全部清理(按 ID 快照 + 时间窗删除 58 条),远端 SQLite 现仅剩 PM 集成期 7 条会话。
|
6. **共享数据**:R2 测试会话已全部清理(按 ID 快照 + 时间窗删除 58 条),远端 SQLite 现仅剩 PM 集成期 7 条会话。
|
||||||
7. **T 自身测试缺陷修复(R2 内)**:E2E-07 接受降级列表形态、E2E-01 等 md-text 终态(弃等 stream-wrap 消失的脆断言)、markdown 触发条件收紧、E2E-05 改位置定位、清理改 ID 快照——均已同步远端。
|
7. **T 自身测试缺陷修复(R2 内)**:E2E-07 接受降级列表形态、E2E-01 等 md-text 终态(弃等 stream-wrap 消失的脆断言)、markdown 触发条件收紧、E2E-05 改位置定位、清理改 ID 快照——均已同步远端。
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
## 6. 证据归档
|
## 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 补跑)
|
- 远端日志:`/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 决定归档)
|
- 归档:`/home/xzq/5090/teamWorkspace/zh/r1_test_evidence/`(R1 证据保留;R2 日志在 /tmp 保留至 PM 决定归档)
|
||||||
|
|
||||||
## 7. 轮次结论
|
## 7. 轮次结论
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 设计令牌(`frontend/src/styles/tokens.css`)
|
## 1. 设计令牌(`ui/src/styles/tokens.css`)
|
||||||
|
|
||||||
### 1.1 深色主题(默认)`[data-theme="dark"]`
|
### 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)
|
||||||
|
|
||||||
| 档位 | 规则 | 具体行为 |
|
| 档位 | 规则 | 具体行为 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# knowledge/ —— 向量知识库打包定义
|
||||||
|
|
||||||
|
`manifest.yaml` 记录 KB 内容与源(见方案 §五)。**数据永不入 git**——本目录只有打包定义与校验脚本的元信息。实际快照/加密副本/单一 tar 均产出于 `tooling/build/<version>/`(gitignored)。
|
||||||
@@ -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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# backend/.env.example —— BFF 全部配置键(pydantic-settings,键名大小写不敏感)
|
# sdk/.env.example —— BFF 全部配置键(pydantic-settings,键名大小写不敏感)
|
||||||
# 复制为 .env 后按需修改。以下默认值即 config.py 的默认值,本地开发可不改。
|
# 复制为 .env 后按需修改。以下默认值即 config.py 的默认值,本地开发可不改。
|
||||||
|
|
||||||
# ---- 应用 ----
|
# ---- 应用 ----
|
||||||
@@ -15,7 +15,7 @@ RAG_ALT_BASE_URL=http://127.0.0.1:7900
|
|||||||
# 注意:凭据只进 BFF 配置,绝不下发到浏览器。
|
# 注意:凭据只进 BFF 配置,绝不下发到浏览器。
|
||||||
NEO4J_BOLT_URIS=bolt://127.0.0.1:18788,bolt://127.0.0.1:19788,bolt://127.0.0.1:17687
|
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_USER=neo4j
|
||||||
NEO4J_PASSWORDS=<in backend/.env, 不入 git>
|
NEO4J_PASSWORDS=<in sdk/.env, 不入 git>
|
||||||
|
|
||||||
# ---- 超时阶梯(秒)----
|
# ---- 超时阶梯(秒)----
|
||||||
TIMEOUT_HEALTH=1
|
TIMEOUT_HEALTH=1
|
||||||
@@ -25,8 +25,8 @@ TIMEOUT_UPLOAD=600
|
|||||||
TIMEOUT_EXTRACTION=15
|
TIMEOUT_EXTRACTION=15
|
||||||
|
|
||||||
# ---- 本地 ----
|
# ---- 本地 ----
|
||||||
DB_PATH=backend/data/plantai.db
|
DB_PATH=data/plantai.db
|
||||||
HEALTH_CACHE_TTL=5
|
HEALTH_CACHE_TTL=5
|
||||||
|
|
||||||
# ---- 生产形态:BFF 静态托管 frontend/dist(M6 启用)----
|
# ---- 生产形态:BFF 静态托管 ui/dist(M6 启用)----
|
||||||
FRONTEND_DIST_DIR=frontend/dist
|
FRONTEND_DIST_DIR=ui/dist
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# sdk/.gitignore(最小版,各 agent 勿改,如需调整向 PM 提出)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
.env
|
||||||
|
data/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""config.py —— 环境变量驱动的 BFF 配置(pydantic-settings)。
|
"""config.py —— 环境变量驱动的 BFF 配置(pydantic-settings)。
|
||||||
|
|
||||||
全部键见 backend/.env.example。凭据类键(Neo4j)只存在于本进程,
|
全部键见 sdk/.env.example。凭据类键(Neo4j)只存在于本进程,
|
||||||
**绝不下发到浏览器**(不进入任何 API 响应)。
|
**绝不下发到浏览器**(不进入任何 API 响应)。
|
||||||
|
|
||||||
上游拓扑与默认值依据 docs/DISCOVERY.md:
|
上游拓扑与默认值依据 docs/DISCOVERY.md:
|
||||||
@@ -39,7 +39,7 @@ class Settings(BaseSettings):
|
|||||||
)
|
)
|
||||||
neo4j_user: str = "neo4j"
|
neo4j_user: str = "neo4j"
|
||||||
neo4j_passwords: str = (
|
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 # 抽取其余接口
|
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
|
health_cache_ttl: float = 5.0
|
||||||
|
|
||||||
# ---- 生产形态:BFF 静态托管 frontend/dist(ARCHITECTURE.md §3,M6 挂载)----
|
# ---- 生产形态:BFF 静态托管 ui/dist(ARCHITECTURE.md §3,M6 挂载)----
|
||||||
frontend_dist_dir: str = "frontend/dist"
|
frontend_dist_dir: str = "ui/dist" # 生产形态:静态托管 ui/dist
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
- CORS:允许 http://localhost:5180 与 http://127.0.0.1:5180(Vite dev)。
|
- CORS:允许 http://localhost:5180 与 http://127.0.0.1:5180(Vite dev)。
|
||||||
- lifespan:init_db(幂等迁移);关闭回收 httpx 客户端与 Neo4j 连接。
|
- lifespan:init_db(幂等迁移);关闭回收 httpx 客户端与 Neo4j 连接。
|
||||||
- 全局 RequestValidationError → VALIDATION 信封(API.md §1,2026-08-25 PM 集成)。
|
- 全局 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
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -78,9 +78,9 @@ def create_app() -> FastAPI:
|
|||||||
|
|
||||||
app.router.lifespan_context = _lifespan
|
app.router.lifespan_context = _lifespan
|
||||||
|
|
||||||
# 生产形态:静态托管 frontend/dist(存在时挂载;本地开发期可无)。
|
# 生产形态:静态托管 ui/dist(存在时挂载;本地开发期可无)。
|
||||||
# 2026-08-25 PM 修复:dist 路径必须基于项目根解析(BFF 工作目录是 backend/,
|
# 2026-08-25 PM 修复:dist 路径必须基于项目根解析(BFF 工作目录是 sdk/,
|
||||||
# 相对路径会解析成 backend/frontend/dist);sync.sh 排除 dist,需单独同步。
|
# 相对路径会解析成 sdk/ui/dist);sync.sh 排除 dist,需单独同步。
|
||||||
project_root = Path(__file__).resolve().parent.parent.parent
|
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)
|
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():
|
if dist.is_dir():
|
||||||
@@ -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 p.read_bytes(), _content_type(p.suffix)
|
||||||
return None
|
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)
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
fname = Path(raw_url.split("/")[-1]).name or "figure"
|
fname = Path(raw_url.split("/")[-1]).name or "figure"
|
||||||
cache_file = cache_dir / fname
|
cache_file = cache_dir / fname
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
选型与凭据依据 DISCOVERY.md §2:
|
选型与凭据依据 DISCOVERY.md §2:
|
||||||
- 首选 neo4j-plant-srt-direct-v1-full(bolt 127.0.0.1:18788,db `neo4j`,
|
- 首选 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),
|
- 回退 neo4j-plant-srt-pc2025-v3-20260817-normalized(bolt 127.0.0.1:19788),
|
||||||
schema 不同(Entity/ASSERTS),本波未做 Cypher 适配(TODO)。
|
schema 不同(Entity/ASSERTS),本波未做 Cypher 适配(TODO)。
|
||||||
- 再次 plantai-protected-neo4j-1(bolt 127.0.0.1:17687),空库。
|
- 再次 plantai-protected-neo4j-1(bolt 127.0.0.1:17687),空库。
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""services/test_normalize.py —— 归一化器单测。
|
"""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 的归一化输出关键字段。
|
DISCOVERY.md §4.1),断言 API.md §7/§8/§9 的归一化输出关键字段。
|
||||||
fixture 是单一事实源(ARCHITECTURE.md §6),本测试同时守护 mock/BFF 契约一致。
|
fixture 是单一事实源(ARCHITECTURE.md §6),本测试同时守护 mock/BFF 契约一致。
|
||||||
"""
|
"""
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# run.sh —— 本地起 BFF(ARCHITECTURE.md §3:127.0.0.1:8090,占用则退 8091)。
|
# run.sh —— 本地起 BFF(ARCHITECTURE.md §3:127.0.0.1:8090,占用则退 8091)。
|
||||||
# 用法:bash backend/run.sh
|
# 用法:bash sdk/run.sh
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ import { mount } from '@vue/test-utils';
|
|||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { nextTick } from 'vue';
|
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 若被使用则不真发请求)
|
// mock fetch(health store 若被使用则不真发请求)
|
||||||
vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }))));
|
vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }))));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// health store 单测(B1):轮询与降级 getter(mock fetch)(FR-HEALTH-001..004)
|
// health store 单测(B1):轮询与降级 getter(mock fetch)(FR-HEALTH-001..004)
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
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)
|
// mock fetch(http.js 的 fetch 封装走全局 fetch)
|
||||||
const fetchMock = vi.fn();
|
const fetchMock = vi.fn();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// http.js 单测(B1):错误信封解析 + 非 2xx / 网络错误归一(API.md §1)
|
// http.js 单测(B1):错误信封解析 + 非 2xx / 网络错误归一(API.md §1)
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
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();
|
const fetchMock = vi.fn();
|
||||||
vi.stubGlobal('fetch', fetchMock);
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// theme store 单测(B1):持久化与 dataset 同步(FR-THEME-002 / UI_SPEC §6)
|
// theme store 单测(B1):持久化与 dataset 同步(FR-THEME-002 / UI_SPEC §6)
|
||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
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', () => {
|
describe('theme store', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
// retry(FR-CHAT-012)、恢复与「已中断」、错误帧映射(FR-CHAT-008)。
|
// retry(FR-CHAT-012)、恢复与「已中断」、错误帧映射(FR-CHAT-008)。
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { setActivePinia, createPinia } from 'pinia';
|
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 依赖模块 ----
|
// ---- mock 依赖模块 ----
|
||||||
let postImpl;
|
let postImpl;
|
||||||
let frames = [];
|
let frames = [];
|
||||||
let updateSessionCalls = [];
|
let updateSessionCalls = [];
|
||||||
|
|
||||||
vi.mock('../../../../frontend/src/api/qa.js', () => ({
|
vi.mock('../../../../ui/src/api/qa.js', () => ({
|
||||||
chatStream: vi.fn(async (body, handlers) => {
|
chatStream: vi.fn(async (body, handlers) => {
|
||||||
for (const f of frames) {
|
for (const f of frames) {
|
||||||
if (f.event === 'complete') handlers.onComplete({ payload: f.data });
|
if (f.event === 'complete') handlers.onComplete({ payload: f.data });
|
||||||
@@ -28,7 +28,7 @@ vi.mock('../../../../frontend/src/api/qa.js', () => ({
|
|||||||
info: vi.fn(async () => ({})),
|
info: vi.fn(async () => ({})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../frontend/src/api/sessions.js', () => {
|
vi.mock('../../../../ui/src/api/sessions.js', () => {
|
||||||
const calls = { messages: [], patches: [] };
|
const calls = { messages: [], patches: [] };
|
||||||
return {
|
return {
|
||||||
createMessage: vi.fn(async (sid, body) => {
|
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 { useChatStore } from '../../../../ui/src/stores/chat.js';
|
||||||
import { useSessionsStore } from '../../../../frontend/src/stores/sessions.js';
|
import { useSessionsStore } from '../../../../ui/src/stores/sessions.js';
|
||||||
import * as qaApi from '../../../../frontend/src/api/qa.js';
|
import * as qaApi from '../../../../ui/src/api/qa.js';
|
||||||
import * as sessApi from '../../../../frontend/src/api/sessions.js';
|
import * as sessApi from '../../../../ui/src/api/sessions.js';
|
||||||
|
|
||||||
const calls = sessApi._calls;
|
const calls = sessApi._calls;
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import { mount, flushPromises } from '@vue/test-utils';
|
|||||||
import { setActivePinia, createPinia } from 'pinia';
|
import { setActivePinia, createPinia } from 'pinia';
|
||||||
|
|
||||||
// mock health store(B1 归属,不实改其文件):qaOk 由 data.upstreams.qa.ok 派生
|
// 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),
|
useHealthStore: vi.fn(() => healthState),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// mock bench store(B3):ChatPanel 对 bench 的引用须容错(本测试不关心工作台)
|
// 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() })),
|
useBenchStore: vi.fn(() => ({ setActiveTab: vi.fn(), setSourceScope: vi.fn() })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// sessions/chat store 用真实现(chat.store 内部 mock api 层,见下)
|
// 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 () => []),
|
listSessions: vi.fn(async () => []),
|
||||||
createSession: vi.fn(async () => ({ id: 's1', title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} })),
|
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: [] })),
|
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(),
|
updateMessage: vi.fn(),
|
||||||
getSessionSources: vi.fn(),
|
getSessionSources: vi.fn(),
|
||||||
}));
|
}));
|
||||||
vi.mock('../../../../frontend/src/api/qa.js', () => ({
|
vi.mock('../../../../ui/src/api/qa.js', () => ({
|
||||||
chatStream: vi.fn(),
|
chatStream: vi.fn(),
|
||||||
chat: vi.fn(),
|
chat: vi.fn(),
|
||||||
search: vi.fn(),
|
search: vi.fn(),
|
||||||
info: 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 工厂每次返回同一对象) */
|
/** health 状态(响应式替换:mock 工厂每次返回同一对象) */
|
||||||
let healthState;
|
let healthState;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { setActivePinia, createPinia } from 'pinia';
|
import { setActivePinia, createPinia } from 'pinia';
|
||||||
import Composer from '../../../../frontend/src/components/chat/Composer.vue';
|
import Composer from '../../../../ui/src/components/chat/Composer.vue';
|
||||||
import AssistantMessage from '../../../../frontend/src/components/chat/AssistantMessage.vue';
|
import AssistantMessage from '../../../../ui/src/components/chat/AssistantMessage.vue';
|
||||||
import SessionItem from '../../../../frontend/src/components/session/SessionItem.vue';
|
import SessionItem from '../../../../ui/src/components/session/SessionItem.vue';
|
||||||
|
|
||||||
describe('Composer(FR-CHAT-005/007/011)', () => {
|
describe('Composer(FR-CHAT-005/007/011)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// @vitest-environment jsdom
|
// @vitest-environment jsdom
|
||||||
// markdown.test.js —— Markdown 渲染 + DOMPurify 消毒 + 自定义表格/代码类(FR-CHAT-004)
|
// markdown.test.js —— Markdown 渲染 + DOMPurify 消毒 + 自定义表格/代码类(FR-CHAT-004)
|
||||||
import { describe, it, expect } from 'vitest';
|
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)', () => {
|
describe('renderMarkdown(FR-CHAT-004)', () => {
|
||||||
it('LLM 输出 markdown → 消毒 HTML', () => {
|
it('LLM 输出 markdown → 消毒 HTML', () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// @vitest-environment node
|
// @vitest-environment node
|
||||||
// sse/parser.test.js —— SSE 解析器单测(多行 data / 注释帧 / 无 event 名 / CRLF / 分块 UTF-8)
|
// sse/parser.test.js —— SSE 解析器单测(多行 data / 注释帧 / 无 event 名 / CRLF / 分块 UTF-8)
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
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', () => {
|
describe('parseFrame', () => {
|
||||||
it('解析单行 data 与 event 名', () => {
|
it('解析单行 data 与 event 名', () => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|||||||
import { setActivePinia, createPinia } from 'pinia';
|
import { setActivePinia, createPinia } from 'pinia';
|
||||||
|
|
||||||
const updateCalls = [];
|
const updateCalls = [];
|
||||||
vi.mock('../../../../frontend/src/api/sessions.js', () => ({
|
vi.mock('../../../../ui/src/api/sessions.js', () => ({
|
||||||
listSessions: vi.fn(async () => []),
|
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: {} })),
|
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 () => ({})),
|
getSession: vi.fn(async () => ({})),
|
||||||
@@ -16,7 +16,7 @@ vi.mock('../../../../frontend/src/api/sessions.js', () => ({
|
|||||||
getSessionSources: vi.fn(),
|
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();
|
const iso = (s) => new Date(s).toISOString();
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ describe('sessions store', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('分组:按 updated_at 倒序 + 三组', async () => {
|
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 修复:夹具日期相对「真实今天」动态生成——硬编码日期会随时间
|
// 2026-08-26 修复:夹具日期相对「真实今天」动态生成——硬编码日期会随时间
|
||||||
// 腐烂(store 分组用真实 new Date()),此前每天翻车。
|
// 腐烂(store 分组用真实 new Date()),此前每天翻车。
|
||||||
const d = (daysAgo) => {
|
const d = (daysAgo) => {
|
||||||
@@ -83,7 +83,7 @@ describe('sessions store', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('搜索过滤:title 子串不区分大小写 + 无匹配(FR-SESSION-005)', async () => {
|
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([
|
listSessions.mockResolvedValueOnce([
|
||||||
{ id: 'a', title: '水稻SPL基因家族', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: {} },
|
{ 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: {} },
|
{ 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 () => {
|
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;
|
let n = 0;
|
||||||
createSession.mockImplementation(async () => ({ id: `new-${++n}`, title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} }));
|
createSession.mockImplementation(async () => ({ id: `new-${++n}`, title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} }));
|
||||||
const store = useSessionsStore();
|
const store = useSessionsStore();
|
||||||
@@ -114,7 +114,7 @@ describe('sessions store', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('删除:删除当前会话 → 切最新一个;列表空 → activeId=null(FR-SESSION-007)', async () => {
|
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([
|
listSessions.mockResolvedValueOnce([
|
||||||
{ id: 'a', title: 'A', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: {} },
|
{ 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: {} },
|
{ 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 () => {
|
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基因家族的功能研究有哪些关键进展?';
|
const q = '水稻SPL基因家族的功能研究有哪些关键进展?';
|
||||||
listSessions.mockResolvedValueOnce([
|
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 } },
|
{ 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 () => {
|
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([
|
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 } },
|
{ id: 'a', title: '旧标题', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: { title_manual: false } },
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
// 覆盖:tab 切换、相位筛选、轮询启停(fake timers)、sources 两种来源切换、会话级去重。
|
// 覆盖:tab 切换、相位筛选、轮询启停(fake timers)、sources 两种来源切换、会话级去重。
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { createPinia, setActivePinia, getActivePinia } from 'pinia';
|
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 模拟统一信封,不依赖文件存在
|
// http.js(B1 归属)尚未交付:用注入式 get 模拟统一信封,不依赖文件存在
|
||||||
const httpGet = vi.fn();
|
const httpGet = vi.fn();
|
||||||
__setHttpGet(httpGet);
|
__setHttpGet(httpGet);
|
||||||
|
|
||||||
vi.mock('../../../../frontend/src/api/extraction.js', () => ({
|
vi.mock('../../../../ui/src/api/extraction.js', () => ({
|
||||||
listJobs: vi.fn(),
|
listJobs: vi.fn(),
|
||||||
getJob: vi.fn(),
|
getJob: vi.fn(),
|
||||||
uploadPaper: vi.fn(),
|
uploadPaper: vi.fn(),
|
||||||
@@ -19,7 +19,7 @@ vi.mock('../../../../frontend/src/api/extraction.js', () => ({
|
|||||||
getSummary: vi.fn(),
|
getSummary: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../frontend/src/api/kg.js', () => ({
|
vi.mock('../../../../ui/src/api/kg.js', () => ({
|
||||||
kgStatus: vi.fn(),
|
kgStatus: vi.fn(),
|
||||||
kgOverview: vi.fn(),
|
kgOverview: vi.fn(),
|
||||||
searchEntities: vi.fn(),
|
searchEntities: vi.fn(),
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
import { useBenchStore } from '../../../../frontend/src/stores/bench.js';
|
import { useBenchStore } from '../../../../ui/src/stores/bench.js';
|
||||||
import BenchPanel from '../../../../frontend/src/components/bench/BenchPanel.vue';
|
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 }),
|
listJobs: vi.fn().mockResolvedValue({ jobs: [], count: 0 }),
|
||||||
getJob: vi.fn(),
|
getJob: vi.fn(),
|
||||||
uploadPaper: vi.fn(),
|
uploadPaper: vi.fn(),
|
||||||
@@ -13,7 +13,7 @@ vi.mock('../../../../frontend/src/api/extraction.js', () => ({
|
|||||||
getProcessingStatus: vi.fn(),
|
getProcessingStatus: vi.fn(),
|
||||||
getSummary: 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 }),
|
kgStatus: vi.fn().mockResolvedValue({ ok: true }),
|
||||||
kgOverview: vi.fn(),
|
kgOverview: vi.fn(),
|
||||||
searchEntities: vi.fn(),
|
searchEntities: vi.fn(),
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
import { deriveProgressPct, shouldPoll } from '../../../../frontend/src/components/bench/kgPaneCompat.js';
|
import { deriveProgressPct, shouldPoll } from '../../../../ui/src/components/bench/kgPaneCompat.js';
|
||||||
import { useBenchStore } from '../../../../frontend/src/stores/bench.js';
|
import { useBenchStore } from '../../../../ui/src/stores/bench.js';
|
||||||
import ProgressItem from '../../../../frontend/src/components/bench/ProgressItem.vue';
|
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 }),
|
listJobs: vi.fn().mockResolvedValue({ jobs: [], count: 0 }),
|
||||||
getJob: vi.fn(),
|
getJob: vi.fn(),
|
||||||
uploadPaper: vi.fn(),
|
uploadPaper: vi.fn(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// graphLayout.test.js —— B3 单测:手写力导向布局(graphLayout.js)
|
// graphLayout.test.js —— B3 单测:手写力导向布局(graphLayout.js)
|
||||||
import { describe, it, expect } from 'vitest';
|
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)', () => {
|
describe('forceLayout 手写力导向(FR-KG-001)', () => {
|
||||||
const small = {
|
const small = {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// kg.pane.test.js —— B3 单测:KgPane(节点配色稳定性、>50 节点降级、离线态、搜索流程 mock)
|
// kg.pane.test.js —— B3 单测:KgPane(节点配色稳定性、>50 节点降级、离线态、搜索流程 mock)
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { createPinia, setActivePinia, getActivePinia } from 'pinia';
|
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 {
|
import {
|
||||||
isDegradedGraph,
|
isDegradedGraph,
|
||||||
isKgOffline,
|
isKgOffline,
|
||||||
NODE_LIMIT_DESKTOP,
|
NODE_LIMIT_DESKTOP,
|
||||||
NODE_LIMIT_MOBILE,
|
NODE_LIMIT_MOBILE,
|
||||||
} from '../../../../frontend/src/components/bench/kgPaneCompat.js';
|
} from '../../../../ui/src/components/bench/kgPaneCompat.js';
|
||||||
import { useBenchStore } from '../../../../frontend/src/stores/bench.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(),
|
listJobs: vi.fn(),
|
||||||
getJob: vi.fn(),
|
getJob: vi.fn(),
|
||||||
uploadPaper: vi.fn(),
|
uploadPaper: vi.fn(),
|
||||||
@@ -18,7 +18,7 @@ vi.mock('../../../../frontend/src/api/extraction.js', () => ({
|
|||||||
getProcessingStatus: vi.fn(),
|
getProcessingStatus: vi.fn(),
|
||||||
getSummary: vi.fn(),
|
getSummary: vi.fn(),
|
||||||
}));
|
}));
|
||||||
vi.mock('../../../../frontend/src/api/kg.js', () => ({
|
vi.mock('../../../../ui/src/api/kg.js', () => ({
|
||||||
kgStatus: vi.fn(),
|
kgStatus: vi.fn(),
|
||||||
searchEntities: vi.fn().mockResolvedValue({
|
searchEntities: vi.fn().mockResolvedValue({
|
||||||
query: 'OsSPL',
|
query: 'OsSPL',
|
||||||
@@ -87,7 +87,7 @@ describe('KgPane 离线降级(FR-KG-007)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('store.checkKgStatus 失败 → kgReady=false + kgError=true', async () => {
|
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 });
|
kgStatus.mockRejectedValue({ code: 'UPSTREAM_DOWN', message: 'Neo4j 不可用', degraded: true });
|
||||||
setActivePinia(createPinia());
|
setActivePinia(createPinia());
|
||||||
const store = useBenchStore();
|
const store = useBenchStore();
|
||||||
@@ -97,7 +97,7 @@ describe('KgPane 离线降级(FR-KG-007)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('store.checkKgStatus 成功 → kgReady=true', async () => {
|
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' });
|
kgStatus.mockResolvedValue({ ok: true, active: 'neo4j-plant-srt-direct-v1-full' });
|
||||||
setActivePinia(createPinia());
|
setActivePinia(createPinia());
|
||||||
const store = useBenchStore();
|
const store = useBenchStore();
|
||||||
@@ -108,7 +108,7 @@ describe('KgPane 离线降级(FR-KG-007)', () => {
|
|||||||
|
|
||||||
describe('KgPane 搜索流程(mock,FR-KG-004)', () => {
|
describe('KgPane 搜索流程(mock,FR-KG-004)', () => {
|
||||||
it('搜索返回 name_norm 匹配结果(精确/前缀),选中后加载邻域', async () => {
|
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);
|
const data = await searchEntities('OsSPL', 20);
|
||||||
expect(data.results).toHaveLength(2);
|
expect(data.results).toHaveLength(2);
|
||||||
expect(data.results[0].name).toBe('OsSPL14');
|
expect(data.results[0].name).toBe('OsSPL14');
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// retrieval.pane.test.js —— B3 单测:RetrievalPane(相位筛选、空态)
|
// retrieval.pane.test.js —— B3 单测:RetrievalPane(相位筛选、空态)
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { createPinia, setActivePinia, getActivePinia } from 'pinia';
|
import { createPinia, setActivePinia, getActivePinia } from 'pinia';
|
||||||
import { useBenchStore, normalizeStageEvent } from '../../../../frontend/src/stores/bench.js';
|
import { useBenchStore, normalizeStageEvent } from '../../../../ui/src/stores/bench.js';
|
||||||
import { STAGE_PHASE } from '../../../../frontend/src/i18n/labels.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(),
|
listJobs: vi.fn(),
|
||||||
getJob: vi.fn(),
|
getJob: vi.fn(),
|
||||||
uploadPaper: vi.fn(),
|
uploadPaper: vi.fn(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// sources.pane.test.js —— B3 单测:SourcesPane 相关(外链优先级、null 字段不渲染)
|
// sources.pane.test.js —— B3 单测:SourcesPane 相关(外链优先级、null 字段不渲染)
|
||||||
// 组件渲染走 @vue/test-utils,判定规则走 kgPaneCompat 纯函数双保险。
|
// 组件渲染走 @vue/test-utils,判定规则走 kgPaneCompat 纯函数双保险。
|
||||||
import { describe, it, expect } from 'vitest';
|
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)', () => {
|
describe('SourcesPane 外链优先级(FR-SOURCE-003)', () => {
|
||||||
it('url > doi > pmid > pmcid', () => {
|
it('url > doi > pmid > pmcid', () => {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
// vitest.config.js —— B3 单测局部配置(B3 归属,位于 tests/frontend/unit/b3/)
|
// vitest.config.js —— B3 单测局部配置(B3 归属,位于 tests/frontend/unit/b3/)
|
||||||
// B1 尚未交付 frontend/vitest.config.js,此处提供最小可用配置保证 B3 单测可独立运行。
|
// B1 尚未交付 ui/vitest.config.js,此处提供最小可用配置保证 B3 单测可独立运行。
|
||||||
// 因本目录在 frontend/ 之外,node_modules 解析与别名均需显式指到 frontend/node_modules。
|
// 因本目录在 ui/ 之外,node_modules 解析与别名均需显式指到 ui/node_modules。
|
||||||
// 待 B1 总配置就绪后,PM 集成时可删除本文件,改用统一配置(需 include 覆盖本目录)。
|
// 待 B1 总配置就绪后,PM 集成时可删除本文件,改用统一配置(需 include 覆盖本目录)。
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { defineConfig } from '../../../../frontend/node_modules/vitest/dist/config.js';
|
import { defineConfig } from '../../../../ui/node_modules/vitest/dist/config.js';
|
||||||
import vue from '../../../../frontend/node_modules/@vitejs/plugin-vue/dist/index.mjs';
|
import vue from '../../../../ui/node_modules/@vitejs/plugin-vue/dist/index.mjs';
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
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({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
# ALT_URL 回落抽取服务根,默认 http://127.0.0.1:7900
|
# ALT_URL 回落抽取服务根,默认 http://127.0.0.1:7900
|
||||||
# MOCK_URL mock server 根,默认 http://127.0.0.1:8099
|
# 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_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
|
# TIMEOUT curl 超时秒,默认 4
|
||||||
#
|
#
|
||||||
# 注意:不探测 QA /search 与 /chat/stream —— 实测单次 37~92s(DISCOVERY.md §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。
|
# 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 覆盖(默认指向当选实例)。
|
# 凭据经环境变量 NEO4J_USER/NEO4J_PASSWORD 覆盖(默认指向当选实例)。
|
||||||
check_neo4j() {
|
check_neo4j() {
|
||||||
local name="$1" base="$2"
|
local name="$1" base="$2"
|
||||||
local user="${NEO4J_USER:-neo4j}"
|
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
|
local out code
|
||||||
out=$(curl -s -m "$TIMEOUT" -o "$TMP" -w '%{http_code}' \
|
out=$(curl -s -m "$TIMEOUT" -o "$TMP" -w '%{http_code}' \
|
||||||
-u "$user:$pass" \
|
-u "$user:$pass" \
|
||||||
|
|||||||
@@ -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)
|
||||||
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 356 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user