initial: blog-app snapshot
This commit is contained in:
137
.review-tmp/analyze-inline.js
Normal file
137
.review-tmp/analyze-inline.js
Normal file
@@ -0,0 +1,137 @@
|
||||
|
||||
// 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 = '<div class="text-muted">正在收集上下文...</div>';
|
||||
promptContent.innerHTML = '<div class="text-muted">等待...</div>';
|
||||
reasoningContent.innerHTML = '<div class="text-muted">等待...</div>';
|
||||
conclusionContent.innerHTML = '<div class="text-muted">等待...</div>';
|
||||
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 = `<div class="alert alert-info">${escapeHtml(content)}</div>`;
|
||||
} else if (step === 'prompt') {
|
||||
promptContent.innerHTML = `<pre class="bg-light p-3 rounded" style="white-space: pre-wrap;">${escapeHtml(content)}</pre>`;
|
||||
} else if (step === 'complete') {
|
||||
// 追加提示, 不覆盖已流式写入的结论内容
|
||||
conclusionContent.innerHTML += `<div class="alert alert-success">${escapeHtml(content)}</div>`;
|
||||
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 = `<div class="alert alert-danger">错误: ${escapeHtml(errorMsg)}</div>`;
|
||||
currentEventSource.close();
|
||||
currentEventSource = null;
|
||||
});
|
||||
|
||||
currentEventSource.onerror = (e) => {
|
||||
observationContent.innerHTML = `<div class="alert alert-danger">连接错误, 请检查后端日志</div>`;
|
||||
currentEventSource.close();
|
||||
currentEventSource = null;
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: escape HTML
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
Reference in New Issue
Block a user