PlantAI-rag-new 首次提交:文献知识工作台

- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化
- Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab
- 四档响应式(桌面/笔记本/平板/手机)、深浅主题
- 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测
- 真实凭据已脱敏,运行时配置在 backend/.env(不入 git)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jerry
2026-08-25 20:47:52 +08:00
co-authored by Claude Fable 5
commit 1e94de7464
171 changed files with 26291 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
// deploy/m2_probe6.js —— 卡片枚举探针(PM 使用,在远端运行)
// 枚举文档中全部 .pipeline-card:props 状态 / textContent / 可见性 / 所属容器 / 挂载状态
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
const page = await ctx.newPage();
await page.goto('http://127.0.0.1:5180', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.app-shell', { timeout: 15000 });
await page.click('.new-task-btn');
await page.waitForSelector('.composer textarea', { timeout: 15000 });
await page.fill('.composer textarea', 'SPL gene family in rice');
await page.click('.send-btn');
// 等流结束
await page.waitForFunction(() => {
const rootEl = document.querySelector('#app');
let n = rootEl; let app = null;
while (n && !app) { if (n.__vue_app__) app = n.__vue_app__; n = n.__vueParentComponent?.parent || null; }
if (!app) return false;
const chat = app.config.globalProperties.$pinia._s.get('chat');
return chat && !chat.busy && chat.completedOnce;
}, null, { timeout: 180000 });
await new Promise((r) => setTimeout(r, 3000)); // 揭示动画收尾
const report = await page.evaluate(() => {
const cards = Array.from(document.querySelectorAll('.pipeline-card'));
return cards.map((el, i) => {
let vnode = el.__vueParentComponent;
let props = null, compName = null;
for (let d = 0; d < 6 && vnode; d++) {
if (vnode.props && vnode.props.block) {
props = { status: vnode.props.block.status, steps: vnode.props.block.steps.map((s) => `${s.phase}:${s.state}`).join(',') };
compName = vnode.type?.name || vnode.type?.__name || 'anon';
break;
}
vnode = vnode.parent;
}
const msgEl = el.closest('.msg');
const chatScroll = el.closest('.chat-scroll');
const style = getComputedStyle(el);
const inDrawer = !!el.closest('.drawer');
const inOverlay = !!el.closest('.bench-overlay');
// 每个元素与文档根的连接性 + 是否在视口
const rect = el.getBoundingClientRect();
return {
i,
props,
compName,
text: el.textContent.slice(0, 70),
display: style.display,
visibility: style.visibility,
opacity: style.opacity,
rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
inMsg: !!msgEl,
msgText: msgEl ? msgEl.textContent.slice(0, 30) : null,
inChatScroll: !!chatScroll,
inDrawer,
inOverlay,
isConnected: el.isConnected,
};
});
});
console.log(JSON.stringify(report, null, 2));
await browser.close();
})();