Files
jerryandClaude Fable 5 1e94de7464 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>
2026-08-25 20:47:52 +08:00

83 lines
3.6 KiB
JavaScript

// deploy/m2_probe2.js —— 精确诊断探针(PM 使用,在远端运行)
// 观察完整流式生命周期:网络请求 + SSE 帧消费 + 流水线状态 + 答案块
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();
const netLog = [];
page.on('request', (r) => {
if (r.url().includes('/api/')) netLog.push(`REQ ${r.method()} ${r.url()}`);
});
page.on('response', (r) => {
if (r.url().includes('/api/')) netLog.push(`RES ${r.status()} ${r.url()}`);
});
const jsErrors = [];
page.on('pageerror', (e) => jsErrors.push(e.message.slice(0, 300)));
page.on('console', (m) => {
if (m.type() === 'error') jsErrors.push('[console] ' + m.text().slice(0, 300));
});
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');
const t0 = Date.now();
let lastKey = '';
let lastBlockCount = -1;
while (Date.now() - t0 < 150000) {
const snap = await page.evaluate(() => {
const cards = Array.from(document.querySelectorAll('.pipeline-card'));
const card = cards[0];
const steps = card ? Array.from(card.querySelectorAll('.p-step')) : [];
const msg = document.querySelector('.msg.assistant');
// 只统计 blocks 区域,排除流水线卡文本
const blocks = msg ? Array.from(msg.querySelectorAll('.msg-body > *')) : [];
const nonPipelineText = blocks
.filter((b) => !b.classList.contains('pipeline-card'))
.map((b) => b.textContent || '')
.join(' ');
return {
cards: cards.length,
states: steps.map((s) => (s.className.includes('done') ? 'D' : s.className.includes('running') ? 'R' : 'W')).join(''),
stepCount: steps.length,
blockCount: blocks.length,
blockTypes: blocks.map((b) => b.className || b.tagName).join(','),
nonPipelineLen: nonPipelineText.length,
streamCursor: !!document.querySelector('.stream-cursor'),
};
});
const key = `${snap.cards}|${snap.states}|${snap.blockCount}|${snap.nonPipelineLen}|${snap.streamCursor}`;
if (key !== lastKey) {
lastKey = key;
console.log(`T+${Math.round((Date.now() - t0) / 1000)}s`, JSON.stringify(snap));
}
if (snap.states === 'DDDDD' && !snap.streamCursor && snap.nonPipelineLen > 100) break;
if (snap.states === 'DDDDD' && snap.blockCount > 1 && snap.nonPipelineLen > 100) break;
if (snap.blockCount > 3 && snap.nonPipelineLen > 100 && !snap.streamCursor) break;
await new Promise((r) => setTimeout(r, 2000));
}
console.log('\n--- 网络日志 ---');
netLog.forEach((l) => console.log(l));
console.log('\n--- JS 错误 ---');
(jsErrors.length ? jsErrors : ['无']).forEach((l) => console.log(l.slice(0, 400)));
// 最终:检查 store 状态(Vue app 无法直接访问,检查 DOM 证据)
const fin = await page.evaluate(() => {
const msg = document.querySelector('.msg.assistant');
const blocks = msg ? Array.from(msg.querySelectorAll('.msg-body > *')) : [];
return {
blockClasses: blocks.map((b) => (b.className || b.tagName).slice(0, 40)),
text: (msg ? msg.textContent : '').slice(0, 400),
};
});
console.log('\n--- 最终块结构 ---');
console.log(JSON.stringify(fin, null, 2));
await browser.close();
})();