// DOM Elements const uploadZone = document.getElementById('uploadZone'); const fileInput = document.getElementById('fileInput'); const fileList = document.getElementById('fileList'); const startBtn = document.getElementById('startBtn'); const requirementInput = document.getElementById('requirementInput'); const statusDot = document.getElementById('statusDot'); const statusText = document.getElementById('statusText'); const logOutput = document.getElementById('logOutput'); const reportContainer = document.getElementById('reportContainer'); const downloadScriptBtn = document.getElementById('downloadScriptBtn'); let isRunning = false; let pollingInterval = null; let currentSessionId = null; // 报告段落数据(用于润色功能) let reportParagraphs = []; // --- Upload Logic --- if (uploadZone) { uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.classList.add('dragover'); }); uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('dragover')); uploadZone.addEventListener('drop', (e) => { e.preventDefault(); uploadZone.classList.remove('dragover'); handleFiles(e.dataTransfer.files); }); uploadZone.addEventListener('click', () => fileInput.click()); } if (fileInput) { fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); fileInput.addEventListener('click', (e) => e.stopPropagation()); } async function handleFiles(files) { if (files.length === 0) return; fileList.innerHTML = ''; const formData = new FormData(); for (const file of files) { formData.append('files', file); const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` ${file.name}`; fileList.appendChild(fileItem); } try { const res = await fetch('/api/upload', { method: 'POST', body: formData }); if (!res.ok) alert('Upload failed'); } catch (e) { console.error(e); alert('Upload failed'); } } // --- Analysis Logic --- if (startBtn) { startBtn.addEventListener('click', startAnalysis); } async function startAnalysis() { if (isRunning) return; const requirement = requirementInput.value.trim(); if (!requirement) { alert('Please enter analysis requirement'); return; } setRunningState(true); try { const res = await fetch('/api/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ requirement }) }); if (res.ok) { const data = await res.json(); currentSessionId = data.session_id; startPolling(); switchTab('logs'); } else { const err = await res.json(); alert('Failed to start: ' + err.detail); setRunningState(false); } } catch (e) { console.error(e); alert('Error starting analysis'); setRunningState(false); } } function setRunningState(running) { isRunning = running; startBtn.disabled = running; if (running) { startBtn.innerHTML = ' Analysis in Progress...'; statusDot.className = 'status-dot running'; statusText.innerText = 'Analyzing'; statusText.style.color = 'var(--primary-color)'; const followUpSection = document.getElementById('followUpSection'); if (followUpSection) followUpSection.classList.add('hidden'); if (downloadScriptBtn) downloadScriptBtn.classList.add('hidden'); } else { startBtn.innerHTML = ' Start Analysis'; statusDot.className = 'status-dot'; statusText.innerText = 'Completed'; statusText.style.color = 'var(--text-secondary)'; const followUpSection = document.getElementById('followUpSection'); if (currentSessionId && followUpSection) followUpSection.classList.remove('hidden'); } } function startPolling() { if (pollingInterval) clearInterval(pollingInterval); if (!currentSessionId) return; pollingInterval = setInterval(async () => { try { const res = await fetch(`/api/status?session_id=${currentSessionId}`); if (!res.ok) return; const data = await res.json(); logOutput.innerText = data.log || "Waiting for output..."; const logTab = document.getElementById('logsTab'); if (logTab) logTab.scrollTop = logTab.scrollHeight; if (!data.is_running && isRunning) { setRunningState(false); clearInterval(pollingInterval); if (data.has_report) { await loadReport(); switchTab('report'); } if (data.script_path && downloadScriptBtn) { downloadScriptBtn.classList.remove('hidden'); downloadScriptBtn.style.display = 'inline-flex'; } } } catch (e) { console.error('Polling error', e); } }, 2000); } // --- Report Logic (with paragraph-level polishing) --- async function loadReport() { if (!currentSessionId) return; try { const res = await fetch(`/api/report?session_id=${currentSessionId}`); const data = await res.json(); if (!data.content || data.content === "Report not ready.") { reportContainer.innerHTML = '

Analysis in progress or no report generated yet.

'; reportParagraphs = []; return; } // 保存段落数据 reportParagraphs = data.paragraphs || []; // 渲染段落化的报告(支持点击润色) renderParagraphReport(reportParagraphs); } catch (e) { reportContainer.innerHTML = '

Failed to load report.

'; } } function renderParagraphReport(paragraphs) { if (!paragraphs || paragraphs.length === 0) { reportContainer.innerHTML = '

No report content.

'; return; } let html = ''; for (const p of paragraphs) { const renderedContent = marked.parse(p.content); const typeClass = `para-${p.type}`; html += `
${renderedContent}
`; } reportContainer.innerHTML = html; } window.selectParagraph = function(paraId) { // 取消所有选中 document.querySelectorAll('.report-paragraph').forEach(el => { el.classList.remove('selected'); el.querySelector('.para-actions')?.classList.add('hidden'); }); // 选中当前段落 const target = document.querySelector(`[data-para-id="${paraId}"]`); if (target) { target.classList.add('selected'); target.querySelector('.para-actions')?.classList.remove('hidden'); } } window.polishParagraph = async function(paraId, mode, customInstruction = '') { if (!currentSessionId) return; const target = document.querySelector(`[data-para-id="${paraId}"]`); if (!target) return; // 显示加载状态 const actionsEl = target.querySelector('.para-actions'); const originalActions = actionsEl.innerHTML; actionsEl.innerHTML = ' AI 润色中...'; try { const res = await fetch('/api/report/polish', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId, paragraph_id: paraId, mode: mode, custom_instruction: customInstruction, }) }); if (!res.ok) { const err = await res.json(); alert('润色失败: ' + (err.detail || 'Unknown error')); actionsEl.innerHTML = originalActions; return; } const data = await res.json(); // 显示对比视图 showPolishDiff(target, paraId, data.original, data.polished); } catch (e) { console.error(e); alert('润色请求失败'); actionsEl.innerHTML = originalActions; } } function showPolishDiff(targetEl, paraId, original, polished) { const polishedHtml = marked.parse(polished); targetEl.innerHTML = `
润色结果预览
原文
${marked.parse(original)}
润色后
${polishedHtml}
`; // 用 addEventListener 绑定,避免内联 onclick 中特殊字符破坏 HTML document.getElementById(`acceptBtn-${paraId}`).addEventListener('click', (e) => { e.stopPropagation(); applyPolish(paraId, polished); }); document.getElementById(`rejectBtn-${paraId}`).addEventListener('click', (e) => { e.stopPropagation(); rejectPolish(paraId); }); } window.applyPolish = async function(paraId, newContent) { if (!currentSessionId) return; try { const res = await fetch('/api/report/apply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId, paragraph_id: paraId, new_content: newContent, }) }); if (res.ok) { // 重新加载报告 await loadReport(); } else { alert('应用失败'); } } catch (e) { console.error(e); alert('应用失败'); } } window.rejectPolish = function(paraId) { // 重新加载报告恢复原状 loadReport(); } window.showCustomPolish = function(paraId) { const target = document.querySelector(`[data-para-id="${paraId}"]`); if (!target) return; const actionsEl = target.querySelector('.para-actions'); if (!actionsEl) return; actionsEl.innerHTML = `
`; document.getElementById(`customInput-${paraId}`)?.focus(); } window.submitCustomPolish = function(paraId) { const input = document.getElementById(`customInput-${paraId}`); if (!input) return; const instruction = input.value.trim(); if (!instruction) { alert('请输入润色指令'); return; } polishParagraph(paraId, 'custom', instruction); } // --- Gallery Logic --- let galleryImages = []; let currentImageIndex = 0; async function loadGallery() { if (!currentSessionId) return; try { const res = await fetch(`/api/figures?session_id=${currentSessionId}`); const data = await res.json(); galleryImages = data.figures || []; currentImageIndex = 0; renderGalleryImage(); } catch (e) { console.error("Gallery load failed", e); document.getElementById('carouselSlide').innerHTML = '

Failed to load images.

'; } } function renderGalleryImage() { const slide = document.getElementById('carouselSlide'); const info = document.getElementById('imageInfo'); if (galleryImages.length === 0) { slide.innerHTML = '

No images generated in this session.

'; info.innerHTML = ''; return; } const img = galleryImages[currentImageIndex]; slide.innerHTML = `${img.filename}`; info.innerHTML = `
${img.filename} (${currentImageIndex + 1}/${galleryImages.length})
${img.description || 'No description available.'}
${img.analysis ? `
${img.analysis}
` : ''} `; } window.prevImage = function () { if (galleryImages.length === 0) return; currentImageIndex = (currentImageIndex - 1 + galleryImages.length) % galleryImages.length; renderGalleryImage(); } window.nextImage = function () { if (galleryImages.length === 0) return; currentImageIndex = (currentImageIndex + 1) % galleryImages.length; renderGalleryImage(); } // --- Download / Export --- window.downloadScript = async function () { if (!currentSessionId) return; const link = document.createElement('a'); link.href = `/api/download_script?session_id=${currentSessionId}`; link.download = ''; document.body.appendChild(link); link.click(); document.body.removeChild(link); } window.triggerExport = async function () { if (!currentSessionId) { alert("No active session to export."); return; } const btn = document.getElementById('exportBtn'); const originalContent = btn.innerHTML; btn.innerHTML = ' Zipping...'; btn.disabled = true; try { window.open(`/api/export?session_id=${currentSessionId}`, '_blank'); } catch (e) { alert("Export failed: " + e.message); } finally { setTimeout(() => { btn.innerHTML = originalContent; btn.disabled = false; }, 2000); } } // --- Follow-up Chat --- window.sendFollowUp = async function () { if (!currentSessionId || isRunning) return; const input = document.getElementById('followUpInput'); const message = input.value.trim(); if (!message) return; input.disabled = true; try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId, message: message }) }); if (res.ok) { input.value = ''; setRunningState(true); startPolling(); switchTab('logs'); } else { alert('Failed to send request'); } } catch (e) { console.error(e); } finally { input.disabled = false; } } // --- History Logic --- async function loadHistory() { const list = document.getElementById('historyList'); if (!list) return; try { const res = await fetch('/api/history'); const data = await res.json(); if (data.history.length === 0) { list.innerHTML = '
No history yet
'; return; } let html = ''; data.history.forEach(item => { html += `
${item.id}
`; }); list.innerHTML = html; } catch (e) { console.error("Failed to load history", e); } } window.loadSession = async function (sessionId) { if (isRunning) { alert("Analysis in progress, please wait."); return; } currentSessionId = sessionId; document.querySelectorAll('.history-item').forEach(el => el.classList.remove('active')); const activeItem = document.getElementById(`hist-${sessionId}`); if (activeItem) activeItem.classList.add('active'); logOutput.innerText = "Loading session data..."; reportContainer.innerHTML = ""; if (downloadScriptBtn) downloadScriptBtn.classList.add('hidden'); try { const res = await fetch(`/api/status?session_id=${sessionId}`); if (res.ok) { const data = await res.json(); logOutput.innerText = data.log || "No logs available."; const logTab = document.getElementById('logsTab'); if (logTab) logTab.scrollTop = logTab.scrollHeight; if (data.has_report) { await loadReport(); if (data.script_path && downloadScriptBtn) { downloadScriptBtn.classList.remove('hidden'); downloadScriptBtn.style.display = 'inline-flex'; } switchTab('report'); } else { switchTab('logs'); } } } catch (e) { logOutput.innerText = "Error loading session."; } } // --- Init & Navigation --- document.addEventListener('DOMContentLoaded', () => { loadHistory(); }); window.switchView = function (viewName) { console.log("View switch requested:", viewName); } window.switchTab = function (tabName) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); ['logs', 'report', 'gallery'].forEach(name => { const content = document.getElementById(`${name}Tab`); if (content) content.classList.add('hidden'); }); document.querySelectorAll('.tab').forEach(btn => { if (btn.getAttribute('onclick') && btn.getAttribute('onclick').includes(`'${tabName}'`)) { btn.classList.add('active'); } }); if (tabName === 'logs') { document.getElementById('logsTab').classList.remove('hidden'); } else if (tabName === 'report') { document.getElementById('reportTab').classList.remove('hidden'); } else if (tabName === 'gallery') { document.getElementById('galleryTab').classList.remove('hidden'); if (currentSessionId) loadGallery(); } }