// deploy/m2_probe3.js —— 浏览器 fetch chunk 级探针(PM 使用,在远端运行) // 目的:确定 progress 帧丢失发生在哪一层(Vite proxy 缓冲?fetch 读取?parser?) 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(); // 在页面上下文里直接发起 fetch 并记录 chunk 到达时间线与内容 const result = await page.evaluate(async () => { const t0 = Date.now(); const log = []; const ctrl = new AbortController(); const timeout = setTimeout(() => ctrl.abort(), 90000); try { const resp = await fetch('http://127.0.0.1:5180/api/qa/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: 'SPL gene family in rice', use_llm: true, debug: false, chain_top_k: 3, chain_hops: 2, chain_max_edges: 12, chain_max_nodes: 10, native_chain_enabled: true, native_chain_max_hops: 3, native_chain_top_k: 5, }), signal: ctrl.signal, }); log.push(`T+${Math.round((Date.now() - t0) / 1000)}s headers: status=${resp.status} ct=${resp.headers.get('content-type')}`); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let nChunks = 0; for (;;) { const { done, value } = await reader.read(); if (done) break; nChunks++; const text = decoder.decode(value, { stream: true }); const events = (text.match(/event: \w+/g) || []).join(','); log.push(`T+${Math.round((Date.now() - t0) / 1000)}s chunk#${nChunks} len=${value.length} [${events}] preview=${text.slice(0, 80).replace(/\n/g, '↵')}`); } log.push(`T+${Math.round((Date.now() - t0) / 1000)}s stream done, ${nChunks} chunks total`); } catch (e) { log.push(`ERROR: ${e.name}: ${e.message}`); } clearTimeout(timeout); return log; }); result.forEach((l) => console.log(l)); await browser.close(); })();