/** * RAG管道测试可视化工具 * 用于展示RAG链路各环节输入输出数据流转 */ // API配置 const API_BASE = 'http://localhost:8765'; // 全局数据存储 let pipelineData = null; let currentSelectedStep = null; let serverConnected = false; // DOM元素引用 const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const pipelineOverview = document.getElementById('pipelineOverview'); const pipelineFlow = document.getElementById('pipelineFlow'); const detailPanel = document.getElementById('detailPanel'); const stagesDetail = document.getElementById('stagesDetail'); const flowContainer = document.getElementById('flowContainer'); const stagesAccordion = document.getElementById('stagesAccordion'); // 阶段配置映射 const stageConfig = { '1_query_extract': { icon: '🔍', title: '查询提取', description: '从输入内容中提取查询对' }, '2_entity_enhance_retrieval': { icon: '🎯', title: '实体增强检索', description: '实体召回 + BFP召回' }, '3_parent_doc_enhancement': { icon: '📚', title: '父文档增强', description: '使用父文档增强检索结果' }, '4_extract_first_result': { icon: '✂️', title: '结果提取', description: '提取最终检索结果' }, '1_rag_retrieval': { icon: '🔄', title: 'RAG检索', description: '完整RAG检索流程' }, '2_parameter_compliance_check': { icon: '✅', title: '参数合规检查', description: 'LLM审查参数合规性' } }; // 初始化事件监听 document.addEventListener('DOMContentLoaded', () => { initUploadEvents(); initTabEvents(); checkServerStatus(); // 定时检查服务状态 setInterval(checkServerStatus, 10000); }); /** * 检查服务器状态 */ function checkServerStatus() { fetch(`${API_BASE}/api/health`) .then(response => response.json()) .then(data => { serverConnected = true; updateServerStatus(true, data.milvus_ready); }) .catch(() => { serverConnected = false; updateServerStatus(false, false); }); } /** * 更新服务器状态显示 */ function updateServerStatus(connected, milvusReady) { const statusEl = document.getElementById('serverStatus'); if (!statusEl) return; const dot = statusEl.querySelector('.status-dot'); const text = statusEl.querySelector('.status-text'); dot.className = 'status-dot ' + (connected ? (milvusReady ? 'online' : 'warning') : 'offline'); if (connected) { text.textContent = milvusReady ? '服务已连接 (Milvus就绪)' : '服务已连接 (Milvus未就绪)'; } else { text.textContent = '服务未连接 - 请启动 rag_pipeline_server.py'; } // 保存到全局变量供其他模块使用 window.serverConnected = serverConnected; window.milvusReady = milvusReady; const runRagBtn = document.getElementById('runRagBtn'); if (runRagBtn) { runRagBtn.disabled = !connected; } } /** * 执行RAG检索 */ function runRAG() { const content = document.getElementById('testInput').value.trim(); if (!content) { alert('请输入测试文本'); return; } if (!serverConnected) { alert('服务未连接,请先启动 rag_pipeline_server.py'); return; } // 显示加载状态 const loadingOverlay = document.getElementById('loadingOverlay'); const loadingText = document.getElementById('loadingText'); loadingOverlay.style.display = 'flex'; loadingText.textContent = '正在执行RAG检索...'; document.getElementById('runRagBtn').disabled = true; fetch(`${API_BASE}/api/rag`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: content }) }) .then(response => { if (!response.ok) throw new Error(`请求失败: ${response.status}`); return response.json(); }) .then(data => { pipelineData = data; window.pipelineData = data; // 同时保存到全局 renderPipeline(data); loadingOverlay.style.display = 'none'; document.getElementById('runRagBtn').disabled = false; }) .catch(err => { alert(`RAG执行失败: ${err.message}`); loadingOverlay.style.display = 'none'; document.getElementById('runRagBtn').disabled = false; }); } /** * 初始化上传事件 */ function initUploadEvents() { uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); const file = e.dataTransfer.files[0]; if (file) handleFile(file); }); fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) handleFile(file); }); } /** * 初始化标签页事件 */ function initTabEvents() { document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { const tabId = btn.dataset.tab; switchTab(tabId); }); }); } /** * 切换标签页 */ function switchTab(tabId) { document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabId); }); document.querySelectorAll('.tab-pane').forEach(pane => { pane.classList.toggle('active', pane.id === tabId + 'Pane'); }); } /** * 处理上传的文件 */ function handleFile(file) { if (!file.name.endsWith('.json')) { alert('请上传JSON文件'); return; } const reader = new FileReader(); reader.onload = (e) => { try { const data = JSON.parse(e.target.result); pipelineData = data; renderPipeline(data); } catch (err) { alert('JSON解析失败: ' + err.message); } }; reader.readAsText(file); } /** * 默认数据文件路径 */ const DEFAULT_DATA_PATH = '../../../temp/entity_bfp_recall/rag_pipeline_data.json'; /** * 加载默认数据文件 */ function loadSampleData() { // 优先从服务器API加载 if (serverConnected) { fetch(`${API_BASE}/api/data`) .then(response => { if (!response.ok) throw new Error('数据不存在'); return response.json(); }) .then(data => { pipelineData = data; renderPipeline(data); }) .catch(() => { // 服务器没有数据,尝试本地文件 loadFromPath(DEFAULT_DATA_PATH); }); } else { loadFromPath(DEFAULT_DATA_PATH); } } /** * 从指定路径加载JSON数据 */ function loadFromPath(path) { fetch(path) .then(response => { if (!response.ok) { throw new Error(`文件加载失败: ${response.status} ${response.statusText}`); } return response.json(); }) .then(data => { pipelineData = data; renderPipeline(data); }) .catch(err => { alert(`加载数据失败: ${err.message}\n\n请确保已运行 test_rag_pipeline.py 生成数据文件,或手动上传JSON文件。`); console.error('加载数据失败:', err); }); } /** * 清空数据 */ function clearData() { pipelineData = null; currentSelectedStep = null; document.getElementById('testInput').value = ''; pipelineOverview.style.display = 'none'; pipelineFlow.style.display = 'none'; detailPanel.style.display = 'none'; stagesDetail.style.display = 'none'; flowContainer.innerHTML = ''; stagesAccordion.innerHTML = ''; } /** * 渲染管道数据 */ function renderPipeline(data) { renderOverview(data); renderFlowChart(data); renderStagesDetail(data); pipelineOverview.style.display = 'block'; pipelineFlow.style.display = 'block'; stagesDetail.style.display = 'block'; } /** * 渲染概览信息 */ function renderOverview(data) { const steps = data.steps || {}; const stepCount = Object.keys(steps).length; document.getElementById('stageCount').textContent = stepCount + ' 个'; // 显示总执行时间 if (data.total_execution_time) { document.getElementById('totalTime').textContent = data.total_execution_time + ' 秒'; } else { document.getElementById('totalTime').textContent = formatTimestamp(data.timestamp); } const hasError = data.error || (data.final_result && data.final_result.retrieval_status === 'no_results'); document.getElementById('execStatus').textContent = hasError ? '异常' : '成功'; document.getElementById('execStatus').style.color = hasError ? '#ff5555' : '#00ff88'; } /** * 渲染流程图 */ function renderFlowChart(data) { flowContainer.innerHTML = ''; const steps = data.steps || {}; Object.entries(steps).forEach(([stepKey, stepData], index) => { const config = stageConfig[stepKey] || { icon: '📦', title: stepData.name || stepKey, description: '' }; const node = document.createElement('div'); node.className = 'flow-node'; node.dataset.step = stepKey; const inputCount = stepData.input ? Object.keys(stepData.input).length : 0; const outputCount = stepData.output ? Object.keys(stepData.output).length : 0; const execTime = stepData.execution_time ? `${stepData.execution_time}s` : '-'; node.innerHTML = `
${config.icon} ${stepData.name || config.title} Step ${index + 1}
输入字段 ${inputCount}
输出字段 ${outputCount}
⏱️ ${execTime}
`; node.addEventListener('click', () => selectStep(stepKey, stepData)); flowContainer.appendChild(node); }); } /** * 选择步骤显示详情 */ function selectStep(stepKey, stepData) { currentSelectedStep = stepKey; // 更新节点选中状态 document.querySelectorAll('.flow-node').forEach(node => { node.classList.toggle('active', node.dataset.step === stepKey); }); // 显示详情面板 detailPanel.style.display = 'block'; // 渲染输入输出数据 document.getElementById('inputViewer').innerHTML = formatJson(stepData.input || {}); document.getElementById('outputViewer').innerHTML = formatJson(stepData.output || {}); document.getElementById('rawViewer').innerHTML = formatJson(stepData); // 滚动到详情面板 detailPanel.scrollIntoView({ behavior: 'smooth', block: 'start' }); } /** * 渲染各阶段详情 */ function renderStagesDetail(data) { stagesAccordion.innerHTML = ''; const steps = data.steps || {}; Object.entries(steps).forEach(([stepKey, stepData], index) => { const config = stageConfig[stepKey] || { icon: '📦', title: stepKey, description: '' }; const item = document.createElement('div'); item.className = 'accordion-item'; item.innerHTML = `
${config.icon} Step ${index + 1}: ${config.title}
${renderStepContent(stepKey, stepData)}
`; stagesAccordion.appendChild(item); }); } /** * 渲染步骤内容 */ function renderStepContent(stepKey, stepData) { let html = `

输入数据

${formatJson(stepData.input || {})}

输出数据

${formatJson(stepData.output || {})}
`; // 如果有子步骤(如实体增强检索的process_details) if (stepData.output && stepData.output.process_details) { html += `

🔬 子步骤详情

`; stepData.output.process_details.forEach((detail, idx) => { html += `
查询对 ${detail.index || idx + 1}

输入

${formatJson(detail.input || {})}

子步骤

${formatJson(detail.steps || {})}
`; }); html += `
`; } return html; } /** * 切换手风琴 */ function toggleAccordion(header) { const content = header.nextElementSibling; const isActive = header.classList.contains('active'); // 关闭所有 document.querySelectorAll('.accordion-header').forEach(h => h.classList.remove('active')); document.querySelectorAll('.accordion-content').forEach(c => c.classList.remove('active')); // 如果之前不是激活状态,则打开当前 if (!isActive) { header.classList.add('active'); content.classList.add('active'); } } /** * 格式化JSON为带语法高亮的HTML */ function formatJson(obj) { const json = JSON.stringify(obj, null, 2); return syntaxHighlight(json); } /** * JSON语法高亮 */ function syntaxHighlight(json) { json = json.replace(/&/g, '&').replace(//g, '>'); return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { let cls = 'json-number'; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'json-key'; } else { cls = 'json-string'; } } else if (/true|false/.test(match)) { cls = 'json-boolean'; } else if (/null/.test(match)) { cls = 'json-null'; } return '' + match + ''; }); } /** * 格式化时间戳 */ function formatTimestamp(timestamp) { if (!timestamp) return '-'; const date = new Date(timestamp * 1000); return date.toLocaleString('zh-CN'); } // ==================== 环节调试功能 ==================== let currentDebugStep = 'query_extract'; const stepMeta = { query_extract: { name: '查询提取', icon: '🔍', desc: '从输入内容中提取查询实体和关键词', inputType: 'text' }, entity_enhance: { name: '实体增强检索', icon: '🎯', desc: '实体召回 + BFP召回,需要 query_pairs 输入(JSON)', inputType: 'json' }, multi_stage_recall: { name: '多阶段召回', icon: '🔄', desc: '混合检索 + 重排序', inputType: 'text' }, hybrid_search: { name: '混合检索', icon: '⚡', desc: 'Dense + Sparse 加权融合检索', inputType: 'text' }, parent_doc_enhance: { name: '父文档增强', icon: '📚', desc: '使用父文档增强检索结果,需要 bfp_result_lists 输入(JSON)', inputType: 'json' }, extract_results: { name: '结果提取', icon: '✂️', desc: '按查询对提取高分结果,需要 bfp_result_lists 输入(JSON)', inputType: 'json' } }; function selectDebugStep(stepName) { currentDebugStep = stepName; const meta = stepMeta[stepName]; // 更新按钮状态 document.querySelectorAll('.step-btn').forEach(b => b.classList.remove('active')); document.querySelector(`[data-step="${stepName}"]`).classList.add('active'); // 更新提示 document.getElementById('currentStepInfo').innerHTML = `${meta.icon}` + `当前环节: ${meta.name} — ${meta.desc}`; // 更新输入框占位符 const input = document.getElementById('debugInput'); if (meta.inputType === 'json') { input.placeholder = '粘贴 JSON 数据...\n\n示例: [{"entity": "...", "search_keywords": [...], "background": "..."}]'; } else { input.placeholder = '输入测试文本...\n\n示例:主要部件说明\n1、主梁总成\n主梁总成由主梁和导梁构成。主梁单节长12m,共7节,每节重10.87t...'; } // 显示/隐藏相关参数 updateParamVisibility(stepName); } function updateParamVisibility(stepName) { const visibleParams = { query_extract: [], entity_enhance: [], multi_stage_recall: ['collectionName', 'topK', 'hybridTopK'], hybrid_search: ['collectionName', 'topK', 'denseWeight'], parent_doc_enhance: ['scoreThreshold', 'maxParents'], extract_results: ['scoreThreshold'] }; const allParams = ['paramCollectionName', 'paramTopK', 'paramHybridTopK', 'paramScoreThreshold', 'paramDenseWeight', 'paramMaxParents']; const visible = visibleParams[stepName] || []; allParams.forEach(id => { const el = document.getElementById(id); if (!el) return; const row = el.closest('.param-row'); if (!row) return; const paramKey = { 'paramCollectionName': 'collectionName', 'paramTopK': 'topK', 'paramHybridTopK': 'hybridTopK', 'paramScoreThreshold': 'scoreThreshold', 'paramDenseWeight': 'denseWeight', 'paramMaxParents': 'maxParents' }[id]; row.style.display = visible.includes(paramKey) ? '' : 'none'; }); } function toggleParamsPanel() { const body = document.getElementById('paramsBody'); const icon = document.getElementById('paramsToggleIcon'); if (body.style.display === 'none') { body.style.display = 'block'; icon.textContent = '▲'; } else { body.style.display = 'none'; icon.textContent = '▼'; } } function updateDebugServerStatus() { const statusEl = document.getElementById('debugServerStatus'); if (!statusEl) return; const dot = statusEl.querySelector('.status-dot'); const text = statusEl.querySelector('.status-text'); if (window.serverConnected) { dot.className = 'status-dot ' + (window.milvusReady ? 'online' : 'warning'); text.textContent = window.milvusReady ? '服务已连接 (Milvus就绪)' : '服务已连接 (Milvus未就绪)'; } else { dot.className = 'status-dot offline'; text.textContent = '服务未连接'; } } function getDebugParams() { return { collection_name: document.getElementById('paramCollectionName').value || 'rag_children_hybrid', top_k: parseInt(document.getElementById('paramTopK').value) || 10, hybrid_top_k: parseInt(document.getElementById('paramHybridTopK').value) || 50, score_threshold: parseFloat(document.getElementById('paramScoreThreshold').value) || 0.5, dense_weight: parseFloat(document.getElementById('paramDenseWeight').value) || 0.7, sparse_weight: 1.0 - (parseFloat(document.getElementById('paramDenseWeight').value) || 0.7), max_parents_per_pair: parseInt(document.getElementById('paramMaxParents').value) || 3 }; } function runDebugStep() { const content = document.getElementById('debugInput').value.trim(); if (!content) { alert('请输入测试内容'); return; } if (!window.serverConnected) { alert('服务未连接,请先启动 rag_pipeline_server.py'); return; } const overlay = document.getElementById('debugLoadingOverlay'); const loadingText = document.getElementById('debugLoadingText'); overlay.style.display = 'flex'; loadingText.textContent = `正在执行: ${stepMeta[currentDebugStep].name}...`; document.getElementById('runDebugStepBtn').disabled = true; document.getElementById('runDebugChainBtn').disabled = true; fetch(`${API_BASE}/api/debug/step`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ step: currentDebugStep, content: content, params: getDebugParams() }) }) .then(r => r.json().then(data => ({ status: r.status, data }))) .then(({ status, data }) => { overlay.style.display = 'none'; document.getElementById('runDebugStepBtn').disabled = false; document.getElementById('runDebugChainBtn').disabled = false; renderDebugResult(data, false); }) .catch(err => { overlay.style.display = 'none'; document.getElementById('runDebugStepBtn').disabled = false; document.getElementById('runDebugChainBtn').disabled = false; alert(`执行失败: ${err.message}`); }); } function runDebugChain() { const content = document.getElementById('debugInput').value.trim(); if (!content) { alert('请输入测试内容'); return; } if (!window.serverConnected) { alert('服务未连接,请先启动 rag_pipeline_server.py'); return; } const overlay = document.getElementById('debugLoadingOverlay'); const loadingText = document.getElementById('debugLoadingText'); overlay.style.display = 'flex'; loadingText.textContent = '链式执行中: query_extract → entity_enhance → parent_doc_enhance → extract_results...'; document.getElementById('runDebugStepBtn').disabled = true; document.getElementById('runDebugChainBtn').disabled = true; fetch(`${API_BASE}/api/debug/chain`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: content, params: getDebugParams() }) }) .then(r => r.json()) .then(data => { overlay.style.display = 'none'; document.getElementById('runDebugStepBtn').disabled = false; document.getElementById('runDebugChainBtn').disabled = false; renderChainResult(data); }) .catch(err => { overlay.style.display = 'none'; document.getElementById('runDebugStepBtn').disabled = false; document.getElementById('runDebugChainBtn').disabled = false; alert(`链式执行失败: ${err.message}`); }); } function clearDebugResult() { document.getElementById('debugResultSection').style.display = 'none'; document.getElementById('debugInput').value = ''; document.getElementById('chainFlow').style.display = 'none'; } // ==================== 单环节结果渲染 ==================== function renderDebugResult(data, isChainStep) { const section = document.getElementById('debugResultSection'); const overviewCards = document.getElementById('debugOverviewCards'); const chainFlow = document.getElementById('chainFlow'); const outputArea = document.getElementById('debugOutputArea'); section.style.display = 'block'; if (!isChainStep) { chainFlow.style.display = 'none'; } const stepName = data.step || currentDebugStep; const meta = stepMeta[stepName] || { name: stepName, icon: '📦' }; // 统计卡片 const execTime = data.execution_time || 0; const hasError = data.status === 'error'; overviewCards.innerHTML = `
${meta.icon}
执行环节 ${meta.name}
⏱️
执行时间 ${execTime} 秒
${hasError ? '❌' : '✅'}
状态 ${hasError ? '失败' : '成功'}
`; if (hasError) { outputArea.innerHTML = `
${escapeHtml(data.error || '未知错误')}
`; return; } // 输入摘要 let summaryHtml = '
'; if (data.input_summary) { summaryHtml += '

📥 输入摘要

'; for (const [k, v] of Object.entries(data.input_summary)) { summaryHtml += `
${k}${v}
`; } summaryHtml += '
'; } summaryHtml += '
'; // 输出内容 let outputHtml = '

📤 输出结果

'; const output = data.output; if (stepName === 'query_extract' && Array.isArray(output)) { outputHtml += renderQueryExtractTable(output); } else if ((stepName === 'entity_enhance' || stepName === 'multi_stage_recall' || stepName === 'hybrid_search') && Array.isArray(output)) { outputHtml += renderSearchResultCards(output, stepName); } else if (stepName === 'parent_doc_enhance' && typeof output === 'object') { outputHtml += renderParentDocSummary(output); } else if (stepName === 'extract_results' && Array.isArray(output)) { outputHtml += renderExtractResultsTable(output); } else { outputHtml += `
${formatJson(output)}
`; } outputHtml += '
'; outputArea.innerHTML = summaryHtml + outputHtml; } // ==================== 链式执行结果渲染 ==================== function renderChainResult(data) { const section = document.getElementById('debugResultSection'); const overviewCards = document.getElementById('debugOverviewCards'); const chainFlow = document.getElementById('chainFlow'); const outputArea = document.getElementById('debugOutputArea'); section.style.display = 'block'; chainFlow.style.display = 'block'; // 总体统计 const totalTime = data.execution_time || 0; const hasError = data.status === 'error'; const steps = data.steps || {}; const stepCount = Object.keys(steps).length; overviewCards.innerHTML = `
🔗
链式执行 ${stepCount} 个环节
⏱️
总耗时 ${totalTime} 秒
${hasError ? '❌' : '✅'}
状态 ${hasError ? data.error || '失败' : '成功'}
`; // 链式流程图 const stepNames = ['query_extract', 'entity_enhance', 'parent_doc_enhance', 'extract_results']; let flowHtml = '
'; stepNames.forEach((sn, i) => { const stepData = steps[sn]; const meta = stepMeta[sn] || { name: sn, icon: '📦' }; const statusClass = stepData ? (stepData.status === 'success' ? 'chain-step-success' : 'chain-step-error') : 'chain-step-pending'; const statusIcon = stepData ? (stepData.status === 'success' ? '✅' : '❌') : '⏳'; const timeStr = stepData ? `${stepData.execution_time}s` : '-'; const summary = stepData ? (stepData.summary || '') : '未执行'; flowHtml += `
${meta.icon} ${statusIcon}
${meta.name}
${timeStr}
${escapeHtml(summary)}
`; if (i < stepNames.length - 1) { flowHtml += '
'; } }); flowHtml += '
'; chainFlow.innerHTML = flowHtml; // 最后一步(extract_results)的详情 const lastStep = steps['extract_results']; let outputHtml = ''; if (lastStep && lastStep.status === 'success' && lastStep.output) { outputHtml = '

📤 最终输出 (extract_results)

'; outputHtml += renderExtractResultsTable(lastStep.output); outputHtml += '
'; } // 各步骤折叠详情 outputHtml += '

📋 各环节详情

'; stepNames.forEach(sn => { const stepData = steps[sn]; if (!stepData) return; const meta = stepMeta[sn] || { name: sn, icon: '📦' }; const statusClass = stepData.status === 'success' ? 'status-success' : 'status-error'; outputHtml += `
${meta.icon} ${meta.name} ${stepData.status}

摘要

${stepData.summary || '无'}
`; if (stepData.output) { outputHtml += `

输出数据

${formatJson(stepData.output)}
`; } if (stepData.error) { outputHtml += `

错误

${escapeHtml(stepData.error)}
`; } outputHtml += '
'; }); outputHtml += '
'; outputArea.innerHTML = outputHtml; } // ==================== 差异化渲染函数 ==================== function renderQueryExtractTable(queryPairs) { if (!queryPairs || queryPairs.length === 0) { return '
📭

未提取到查询对

'; } let html = '
' + ''; queryPairs.forEach((qp, i) => { const keywords = Array.isArray(qp.search_keywords) ? qp.search_keywords.join(', ') : (qp.search_keywords || ''); html += ``; }); html += '
#实体 (entity)搜索关键词背景 (background)参数 (parameter)
${i + 1} ${escapeHtml(qp.entity || '')} ${escapeHtml(keywords)} ${escapeHtml(qp.background || '')} ${escapeHtml(qp.parameter || '')}
'; return html; } function renderSearchResultCards(results, stepName) { if (!results || results.length === 0) { return '
📭

未检索到结果

'; } let html = `
共 ${results.length} 个结果
`; results.forEach((r, i) => { const textContent = (r.text_content || r.text || '').substring(0, 300); const fileName = r.file_name || r.metadata?.file_name || r.metadata?.document_id || 'N/A'; const score = r.rerank_score || r.hybrid_similarity || r.bfp_rerank_score || r.distance || 0; const scoreType = r.rerank_score ? 'rerank' : (r.hybrid_similarity ? 'hybrid' : (r.bfp_rerank_score ? 'bfp' : 'score')); html += `
#${i + 1} ${scoreType}: ${typeof score === 'number' ? score.toFixed(4) : score}
📄 ${escapeHtml(String(fileName))}
${escapeHtml(textContent)}${textContent.length >= 300 ? '...' : ''}
`; }); html += '
'; return html; } function renderParentDocSummary(output) { let html = '
'; for (const [k, v] of Object.entries(output)) { html += `
${k}${v}
`; } html += '
'; if (output.parent_docs && output.parent_docs.length > 0) { html += `
父文档列表 (${output.parent_docs.length})
`; output.parent_docs.forEach((p, i) => { const textContent = (p.text_content || '').substring(0, 300); const fileName = p.metadata?.file_name || p.metadata?.document_id || 'N/A'; html += `
📚 父文档 #${i + 1}
📄 ${escapeHtml(String(fileName))}
${escapeHtml(textContent)}${textContent.length >= 300 ? '...' : ''}
`; }); html += '
'; } return html; } function renderExtractResultsTable(entityResults) { if (!entityResults || entityResults.length === 0) { return '
📭

没有通过阈值过滤的结果

'; } let html = '
' + ''; entityResults.forEach((r, i) => { const score = r.final_score || r.bfp_rerank_score || 0; const content = (r.text_content || '').substring(0, 150); html += ``; }); html += '
#实体combined_query分数文件名内容预览
${i + 1} ${escapeHtml(r.entity || '')} ${escapeHtml((r.combined_query || '').substring(0, 80))} ${typeof score === 'number' ? score.toFixed(4) : score} ${escapeHtml((r.file_name || '').substring(0, 50))} ${escapeHtml(content)}${content.length >= 150 ? '...' : ''}
'; return html; } // ==================== 工具函数 ==================== function escapeHtml(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }