/* Search Hub — Deepseek 风格 UI */ (function () { 'use strict'; const searchInput = document.getElementById('search-input'); const searchBtn = document.getElementById('search-btn'); const summarizeBtn = document.getElementById('summarize-btn'); const statusBar = document.getElementById('status-bar'); const historyList = document.getElementById('history-list'); const loading = document.getElementById('loading'); const resultsSection = document.getElementById('results-section'); const emptyState = document.getElementById('empty-state'); const summarySection = document.getElementById('summary-section'); const summaryContent = document.getElementById('summary-content'); const summaryMeta = document.getElementById('summary-meta'); const searchSection = document.getElementById('search-section'); const searchTitle = document.getElementById('search-title'); let currentResults = []; let currentQuery = ''; let currentSource = 'auto'; let isSummarizing = false; // 初始隐藏总结区域 summarySection.style.display = 'none'; loadHistory(); setupSourceSelector(); searchBtn.addEventListener('click', doSearch); searchInput.addEventListener('keydown', function (e) { if (e.key === 'Enter') doSearch(); }); summarizeBtn.addEventListener('click', doSummarize); // ===== 搜索源选择 ===== function setupSourceSelector() { document.querySelectorAll('.source-tag').forEach(function (el) { el.addEventListener('click', function () { var source = el.dataset.source; if (!source) return; if (el.classList.contains('source-disabled')) return; document.querySelectorAll('.source-tag').forEach(function (t) { t.classList.remove('source-active'); }); el.classList.add('source-active'); currentSource = source; if (searchInput.value.trim()) { doSearch(); } }); }); } // ===== 搜索(无总结) ===== function doSearch() { const query = searchInput.value.trim(); if (!query) return; hideSummary(); currentQuery = query; currentResults = []; showLoading(true); setStatus(''); searchSection.classList.add('has-results'); searchTitle.textContent = query; fetch('/api/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: query, source: currentSource, }), }) .then(function (r) { return r.json(); }) .then(function (data) { showLoading(false); if (data.error) { setStatus(data.error); resultsSection.innerHTML = ''; return; } currentResults = data.results || []; renderResults(currentResults, data); loadHistory(); }) .catch(function (err) { showLoading(false); setStatus('请求失败: ' + err.message); }); } // ===== 渲染结果 ===== function renderResults(results, data) { if (!results || results.length === 0) { emptyState.style.display = 'block'; resultsSection.innerHTML = ''; return; } emptyState.style.display = 'none'; var sourceLabel = data.source || currentSource; var elapsed = data.elapsed || 0; setStatus('共找到 ' + results.length + ' 条结果(' + sourceLabel + ' | ' + elapsed + 's)'); document.querySelectorAll('.source-tag').forEach(function (t) { t.classList.remove('source-active'); }); var match = document.querySelector('.source-tag[data-source="' + sourceLabel + '"]'); if (match) { match.classList.add('source-active'); } else { var autoTag = document.querySelector('.source-tag[data-source="auto"]'); if (autoTag) autoTag.classList.add('source-active'); } var html = ''; results.forEach(function (item) { var dateStr = formatDate(item.published_date || ''); var sourceTag = item.source ? '' + item.source + '' : ''; html += '
'; html += '
' + escapeHtml(item.title) + '
'; html += '
' + escapeHtml(item.content) + '
'; html += '
'; html += ' ' + escapeHtml(item.url) + ''; if (dateStr) html += ' ' + dateStr + ''; html += ' ' + sourceTag; html += '
'; html += '
'; }); resultsSection.innerHTML = html; } // ===== AI 总结 ===== function doSummarize() { if (isSummarizing) return; // 无搜索词时取输入框内容 if (!currentQuery) { var q = searchInput.value.trim(); if (!q) return; currentQuery = q; currentSource = document.querySelector('.source-tag.source-active')?.dataset.source || 'auto'; } // 无搜索结果 → 先搜索再总结 if (!currentResults || currentResults.length === 0) { doSearchThenSummarize(); return; } startSummarize(); } function doSearchThenSummarize() { var query = currentQuery || searchInput.value.trim(); if (!query) return; hideSummary(); currentQuery = query; currentResults = []; showLoading(true); setStatus(''); searchSection.classList.add('has-results'); searchTitle.textContent = query; summarizeBtn.disabled = true; summarizeBtn.textContent = '搜索中...'; fetch('/api/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: query, source: currentSource, }), }) .then(function (r) { return r.json(); }) .then(function (data) { showLoading(false); summarizeBtn.disabled = false; summarizeBtn.textContent = 'AI 总结'; if (data.error) { setStatus(data.error); resultsSection.innerHTML = ''; return; } currentResults = data.results || []; renderResults(currentResults, data); loadHistory(); if (currentResults.length > 0) { startSummarize(); } }) .catch(function (err) { showLoading(false); summarizeBtn.disabled = false; summarizeBtn.textContent = 'AI 总结'; setStatus('请求失败: ' + err.message); }); } function startSummarize() { if (!currentQuery || !currentResults || currentResults.length === 0) return; if (isSummarizing) return; isSummarizing = true; summaryContent.innerHTML = '
' + '
' + '  AI 总结中...' + '
'; summaryMeta.textContent = ''; summarySection.style.display = 'block'; summarySection.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); summarizeBtn.disabled = true; summarizeBtn.textContent = '总结中...'; var fullSummary = ''; fetch('/api/ai-summarize/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: currentQuery, results: currentResults, }), }) .then(function (r) { if (!r.ok) return r.json().then(function (d) { throw new Error(d.error || '请求失败'); }); var reader = r.body.getReader(); var decoder = new TextDecoder(); var buffer = ''; function read() { return reader.read().then(function (result) { if (result.done) return; buffer += decoder.decode(result.value, { stream: true }); var parts = buffer.split('\n\n'); buffer = parts.pop() || ''; parts.forEach(function (part) { var lines = part.trim().split('\n'); var eventType = ''; var dataStr = ''; lines.forEach(function (line) { if (line.startsWith('event: ')) eventType = line.slice(7).trim(); else if (line.startsWith('data: ')) dataStr = line.slice(6).trim(); }); if (!dataStr) return; try { var parsed = JSON.parse(dataStr); if (eventType === 'delta') { fullSummary += parsed.content || ''; summaryContent.innerHTML = markedSummary(fullSummary); } else if (eventType === 'meta') { summaryMeta.textContent = '模型: ' + (parsed.model || '') + ' | 耗时 ' + parsed.elapsed + 's'; } else if (eventType === 'error') { setStatus('AI 总结失败: ' + (parsed.error || '')); } } catch (e) {} }); return read(); }); } return read(); }) .then(function () { isSummarizing = false; summarizeBtn.disabled = false; summarizeBtn.textContent = 'AI 总结'; }) .catch(function (err) { isSummarizing = false; summarizeBtn.disabled = false; summarizeBtn.textContent = 'AI 总结'; summaryContent.innerHTML = '
总结失败: ' + escapeHtml(err.message) + '
'; }); } function hideSummary() { summarySection.style.display = 'none'; summaryContent.innerHTML = ''; summaryMeta.textContent = ''; isSummarizing = false; } // ===== 搜索历史 ===== function loadHistory() { fetch('/api/history') .then(function (r) { return r.json(); }) .then(function (data) { var list = data.history || []; if (list.length === 0) { historyList.innerHTML = ''; return; } var html = ''; list.forEach(function (item) { html += '' + escapeHtml(item) + ''; }); historyList.innerHTML = html; document.querySelectorAll('.history-tag').forEach(function (el) { el.addEventListener('click', function () { searchInput.value = el.textContent; doSearch(); }); }); }); } // ===== 辅助 ===== function showLoading(v) { loading.style.display = v ? 'block' : 'none'; } function setStatus(msg) { statusBar.textContent = msg; statusBar.style.display = msg ? 'block' : 'none'; } function formatDate(dateStr) { if (!dateStr) return ''; try { var d = new Date(dateStr); if (isNaN(d.getTime())) return dateStr; return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); } catch (e) { return dateStr; } } function escapeHtml(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } function markedSummary(text) { if (!text) return ''; var lines = text.split('\n'); var html = ''; var inList = false; lines.forEach(function (line) { line = escapeHtml(line); var hMatch = line.match(/^(#{1,3})\s+(.+)/); if (hMatch) { if (inList) { html += ''; inList = false; } html += '' + hMatch[2] + ''; return; } var lMatch = line.match(/^[\s]*[-*+]\s+(.+)/); if (lMatch) { if (!inList) { html += ''; inList = false; } return; } if (inList) { html += ''; inList = false; } line = line.replace(/\*\*(.+?)\*\*/g, '$1'); html += '

' + line + '

'; }); if (inList) html += ''; return html; } })();