/** * 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 = `
${formatJson(stepData.input || {})}
${formatJson(stepData.output || {})}
${formatJson(detail.input || {})}
${formatJson(detail.steps || {})}