// State let currentEventSource = null; let currentTab = 'observation'; // Track which tab we're writing to let currentSection = 'reasoning'; // 'reasoning' | 'conclusion', 由 SSE section 事件驱动 // DOM elements const scopeSelect = document.getElementById('scopeSelect'); const questionInput = document.getElementById('questionInput'); const startBtn = document.getElementById('startBtn'); const presetBtns = document.querySelectorAll('.preset-btn'); const observationContent = document.getElementById('observationContent'); const promptContent = document.getElementById('promptContent'); const reasoningContent = document.getElementById('reasoningContent'); const conclusionContent = document.getElementById('conclusionContent'); // Programmatic tab switching (Bootstrap tabs) function switchTab(name) { const btn = document.getElementById(name + '-tab'); if (btn) btn.click(); } // Tab switching document.querySelectorAll('#outputTabs button').forEach(button => { button.addEventListener('click', (e) => { const tabId = e.target.id.replace('-tab', ''); currentTab = tabId; }); }); // Preset button click presetBtns.forEach(btn => { btn.addEventListener('click', () => { const preset = btn.dataset.preset; startAnalysis(null, preset); }); }); // Start button click startBtn.addEventListener('click', () => { const question = questionInput.value.trim(); if (!question) { alert('请输入自定义问题或选择预设分析'); return; } startAnalysis(question, null); }); function startAnalysis(question, preset) { // Close existing connection if (currentEventSource) { currentEventSource.close(); currentEventSource = null; } // Reset content observationContent.innerHTML = '
正在收集上下文...
'; promptContent.innerHTML = '
等待...
'; reasoningContent.innerHTML = '
等待...
'; conclusionContent.innerHTML = '
等待...
'; currentSection = 'reasoning'; // Build URL const scope = scopeSelect.value; const params = new URLSearchParams({scope}); if (question) params.append('question', question); if (preset) params.append('preset', preset); const url = `/api/analyze/stream?${params.toString()}`; // Start SSE currentEventSource = new EventSource(url); // Handle step events currentEventSource.addEventListener('step', (e) => { const data = JSON.parse(e.data); const step = data.step; const content = data.content; if (step === 'context') { observationContent.innerHTML = `
${escapeHtml(content)}
`; } else if (step === 'prompt') { promptContent.innerHTML = `
${escapeHtml(content)}
`; } else if (step === 'complete') { // 追加提示, 不覆盖已流式写入的结论内容 conclusionContent.innerHTML += `
${escapeHtml(content)}
`; currentEventSource.close(); currentEventSource = null; } }); // Section 事件: 后端识别 ===REASONING=== / ===CONCLUSION=== 后显式下发 currentEventSource.addEventListener('reasoning_start', () => { currentSection = 'reasoning'; reasoningContent.innerHTML = ''; switchTab('reasoning'); }); currentEventSource.addEventListener('conclusion_start', () => { currentSection = 'conclusion'; conclusionContent.innerHTML = ''; switchTab('conclusion'); }); // Handle token events (streaming LLM output, 分隔符已被后端剥离) currentEventSource.addEventListener('token', (e) => { const data = JSON.parse(e.data); const text = data.text; const pane = currentSection === 'conclusion' ? conclusionContent : reasoningContent; if (pane.querySelector('.text-muted')) pane.innerHTML = ''; pane.innerHTML += escapeHtml(text); pane.scrollTop = pane.scrollHeight; }); // Handle errors currentEventSource.addEventListener('error', (e) => { const data = JSON.parse(e.data); const errorMsg = data.error || '未知错误'; observationContent.innerHTML = `
错误: ${escapeHtml(errorMsg)}
`; currentEventSource.close(); currentEventSource = null; }); currentEventSource.onerror = (e) => { observationContent.innerHTML = `
连接错误, 请检查后端日志
`; currentEventSource.close(); currentEventSource = null; }; } // Helper: escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }