// API基础地址 const API_BASE = ''; // 搜索结果存储 let searchResults = []; let currentResultIndex = -1; // 自动流程控制 let shouldStop = false; // 页面加载初始化 document.addEventListener('DOMContentLoaded', () => { // 回车搜索 document.getElementById('search-keyword').addEventListener('keypress', (e) => { if (e.key === 'Enter') { doSearch(); } }); // 加载失败URL loadFailedUrls(); }); // 执行搜索 async function doSearch() { const keyword = document.getElementById('search-keyword').value.trim(); const maxResults = parseInt(document.getElementById('search-count').value) || 10; const engine = document.getElementById('search-engine').value; const useCache = document.getElementById('use-cache').checked; const autoFetch = document.getElementById('auto-fetch').checked; const autoSave = document.getElementById('auto-save').checked; if (!keyword) { showToast('请输入搜索关键词', 'error'); return; } // 更新状态 const engineNames = { 'bing_cn': 'Bing 中国', 'bing_global': 'Bing 国际', 'google': 'Google', 'baidu': '百度' }; updateStatus('searching', `正在通过 ${engineNames[engine]} 搜索...`); document.getElementById('search-btn').disabled = true; // 显示进度 const progress = document.getElementById('search-progress'); progress.style.display = 'block'; document.getElementById('progress-fill').style.width = '0%'; document.getElementById('progress-text').textContent = '正在搜索...'; try { const response = await fetch(`${API_BASE}/api/articles/internet-search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ keyword, max_results: maxResults, engine, use_cache: useCache }) }); const data = await response.json(); if (data.success) { searchResults = data.results.map(r => ({ ...r, fetched: false, saved: false, content: '' })); displayResults(); // 显示缓存状态 if (data.cached) { updateStatus('success', `找到 ${searchResults.length} 条结果(使用缓存)`); showToast('使用缓存结果', 'success'); } else { updateStatus('success', `找到 ${searchResults.length} 条结果`); } document.getElementById('results-count').textContent = `${searchResults.length} 条结果`; document.getElementById('save-all-btn').disabled = searchResults.length === 0; document.getElementById('progress-fill').style.width = '100%'; document.getElementById('progress-text').textContent = '搜索完成!'; // 自动抓取和保存 if (autoFetch && searchResults.length > 0) { setTimeout(() => fetchAllResults(autoSave), 500); } } else { updateStatus('error', '搜索失败'); showToast('搜索失败: ' + data.error, 'error'); } } catch (error) { updateStatus('error', '搜索出错'); showToast('搜索出错', 'error'); console.error(error); } setTimeout(() => { progress.style.display = 'none'; }, 1000); document.getElementById('search-btn').disabled = false; } // 显示搜索结果 function displayResults() { const container = document.getElementById('search-results'); if (searchResults.length === 0) { container.innerHTML = '
未找到相关结果
'; return; } container.innerHTML = searchResults.map((r, i) => `
${i + 1}
${r.saved ? '' : ''} ${escapeHtml(r.title)}
${r.content ? `
${escapeHtml(r.content.substring(0, 100))}...
` : ''}
`).join(''); } // 检查URL是否已在内容库 async function checkUrlExists(url) { try { const response = await fetch(`${API_BASE}/api/articles/search?q=${encodeURIComponent(url)}`); const data = await response.json(); if (data.success && data.articles && data.articles.length > 0) { // 检查是否有完全匹配的 URL return data.articles.some(a => a.url === url); } } catch (error) { console.error('检查URL失败:', error); } return false; } // 抓取单个结果 async function fetchResult(index) { const result = searchResults[index]; if (!result || result.fetched) return; const btn = document.getElementById(`fetch-btn-${index}`); btn.disabled = true; btn.innerHTML = ' 检查中...'; // 先检查内容库是否已存在 const exists = await checkUrlExists(result.url); if (exists) { searchResults[index].fetched = true; searchResults[index].saved = true; searchResults[index].content = '[内容库中已存在此链接]'; btn.innerHTML = ' 已存在'; btn.className = 'btn btn-sm btn-warning'; const saveBtn = document.getElementById(`save-btn-${index}`); saveBtn.disabled = true; saveBtn.innerHTML = ' 已存在'; const item = document.getElementById(`result-${index}`); item.classList.add('saved'); showToast('该链接已在内容库中', 'warning'); return; } btn.innerHTML = ' 抓取中...'; try { const response = await fetch(`${API_BASE}/api/articles/fetch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: result.url, product_names: [result.title] }) }); const data = await response.json(); if (data.success) { searchResults[index].fetched = true; searchResults[index].content = data.data.content; searchResults[index].title = data.data.title || result.title; btn.innerHTML = ' 已抓取'; // 更新结果显示 const item = document.getElementById(`result-${index}`); item.classList.add('fetched'); showToast('抓取成功', 'success'); displayResults(); } else { // 记录失败URL await recordFailedUrl(result.url, result.title, data.error); btn.disabled = false; btn.innerHTML = ' 抓取'; btn.className = 'btn btn-sm btn-danger'; showToast('抓取失败: ' + data.error, 'error'); } } catch (error) { // 记录失败URL await recordFailedUrl(result.url, result.title, '抓取出错'); btn.disabled = false; btn.innerHTML = ' 抓取'; btn.className = 'btn btn-sm btn-danger'; showToast('抓取出错', 'error'); } } // 记录失败的URL async function recordFailedUrl(url, title, errorMessage) { try { await fetch(`${API_BASE}/api/articles/failed-urls`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, title, error_message: errorMessage }) }); } catch (error) { console.error('记录失败URL出错:', error); } } // 保存单个结果 async function saveResult(index) { const result = searchResults[index]; if (!result) return; // 如果已经保存(可能是内容库中已存在),直接返回 if (result.saved) { return; } // 如果未抓取,先抓取 if (!result.fetched) { await fetchResult(index); // 抓取后如果已标记为 saved(内容库已存在),不继续保存 if (searchResults[index].saved) return; } const btn = document.getElementById(`save-btn-${index}`); btn.disabled = true; btn.innerHTML = ' 保存中...'; try { const response = await fetch(`${API_BASE}/api/articles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ product_names: [searchResults[index].title], category: '', keywords: [], summary: searchResults[index].content.substring(0, 200), content: searchResults[index].content, source: searchResults[index].url, url: searchResults[index].url }) }); const data = await response.json(); if (data.success) { searchResults[index].saved = true; btn.innerHTML = ' 已保存'; const item = document.getElementById(`result-${index}`); item.classList.add('saved'); showToast('保存成功', 'success'); displayResults(); } else { btn.disabled = false; btn.innerHTML = ' 保存'; showToast('保存失败: ' + data.error, 'error'); } } catch (error) { btn.disabled = false; btn.innerHTML = ' 保存'; showToast('保存出错', 'error'); } } // 一键保存全部 async function saveAllResults() { const unsaved = searchResults.filter(r => !r.saved); if (unsaved.length === 0) { showToast('没有需要保存的结果', 'error'); return; } const progress = document.getElementById('save-progress'); progress.style.display = 'block'; let saved = 0; const total = unsaved.length; for (let i = 0; i < searchResults.length; i++) { if (searchResults[i].saved) continue; document.getElementById('save-progress-text').textContent = `正在保存 ${saved + 1}/${total}...`; document.getElementById('save-fill').style.width = `${(saved / total) * 100}%`; await saveResult(i); saved++; } document.getElementById('save-fill').style.width = '100%'; document.getElementById('save-progress-text').textContent = '保存完成!'; setTimeout(() => { progress.style.display = 'none'; }, 1500); showToast(`成功保存 ${saved} 条结果`, 'success'); } // 抓取所有结果 async function fetchAllResults(autoSave) { shouldStop = false; // 重置停止标志 const progress = document.getElementById('search-progress'); progress.style.display = 'block'; document.getElementById('stop-btn').style.display = 'inline-flex'; // 显示停止按钮 const total = searchResults.length; let fetched = 0; for (let i = 0; i < searchResults.length; i++) { // 检查是否停止 if (shouldStop) { document.getElementById('progress-text').textContent = `已停止(已抓取 ${fetched}/${total})`; document.getElementById('stop-btn').style.display = 'none'; setTimeout(() => { progress.style.display = 'none'; }, 2000); showToast('自动抓取已停止', 'warning'); return; } if (searchResults[i].fetched) { fetched++; continue; } document.getElementById('progress-text').textContent = `正在抓取 ${fetched + 1}/${total}...`; document.getElementById('progress-fill').style.width = `${(fetched / total) * 100}%`; await fetchResult(i); fetched++; } document.getElementById('progress-fill').style.width = '100%'; document.getElementById('progress-text').textContent = '抓取完成!'; document.getElementById('stop-btn').style.display = 'none'; setTimeout(() => { progress.style.display = 'none'; }, 1000); // 自动保存 if (autoSave) { setTimeout(() => saveAllResults(), 500); } } // 停止自动处理 function stopAutoProcess() { shouldStop = true; } // 显示结果详情 function showResultDetail(index) { currentResultIndex = index; const result = searchResults[index]; const body = document.getElementById('result-detail-body'); body.innerHTML = `
${escapeHtml(result.title)}
${escapeHtml(result.url)}
${result.fetched ? '已抓取' : '未抓取'} / ${result.saved ? '已保存' : '未保存'}
${result.content ? `

页面内容

${escapeHtml(result.content.substring(0, 3000))}${result.content.length > 3000 ? '\n...(内容过长,已截断)' : ''}
` : '

尚未抓取内容,点击"保存到内容库"将自动抓取并保存

'} `; document.getElementById('result-detail-modal').classList.add('active'); } // 保存当前结果 async function saveCurrentResult() { if (currentResultIndex >= 0) { await saveResult(currentResultIndex); closeModal('result-detail-modal'); } } // 清空结果 function clearResults() { searchResults = []; displayResults(); document.getElementById('results-count').textContent = '0 条结果'; document.getElementById('save-all-btn').disabled = true; updateStatus('ready', '就绪'); } // 更新状态 function updateStatus(status, text) { const dot = document.getElementById('search-status'); const statusText = document.getElementById('status-text'); dot.className = 'status-dot'; if (status === 'searching') { dot.style.background = '#f59e0b'; } else if (status === 'success') { dot.style.background = '#10b981'; } else if (status === 'error') { dot.style.background = '#ef4444'; } else { dot.style.background = '#10b981'; } statusText.textContent = text; } // 关闭模态框 function closeModal(modalId) { document.getElementById(modalId).classList.remove('active'); } // 显示提示 function showToast(message, type = '') { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = `toast active ${type}`; setTimeout(() => { toast.classList.remove('active'); }, 3000); } // HTML转义 function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // ========== 失败URL管理 ========== // 加载失败URL列表 async function loadFailedUrls() { try { const response = await fetch(`${API_BASE}/api/articles/failed-urls`); const data = await response.json(); if (data.success) { document.getElementById('failed-count').textContent = `${data.count} 条`; const container = document.getElementById('failed-urls-list'); if (data.urls.length === 0) { container.innerHTML = '
暂无失败记录
'; } else { container.innerHTML = data.urls.map(url => `
${escapeHtml(url.title || url.url.substring(0, 50))}
${escapeHtml(url.url.substring(0, 60))}${url.url.length > 60 ? '...' : ''} ${escapeHtml(url.error_message || '未知错误')}
重试: ${url.retry_count || 0} 次 ${url.created_at || ''}
`).join(''); } } } catch (error) { console.error('加载失败URL出错:', error); } } // 重试单个失败URL async function retryFailedUrl(urlId, url) { const item = document.getElementById(`failed-${urlId}`); item.classList.add('loading'); try { const response = await fetch(`${API_BASE}/api/articles/failed-urls/retry`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const data = await response.json(); if (data.success) { showToast('重试成功', 'success'); loadFailedUrls(); } else { showToast('重试失败: ' + data.error, 'error'); item.classList.remove('loading'); } } catch (error) { showToast('重试出错', 'error'); item.classList.remove('loading'); } } // 全部重试 async function retryAllFailed() { showToast('正在重试所有失败URL...', ''); const response = await fetch(`${API_BASE}/api/articles/failed-urls`); const data = await response.json(); if (data.success && data.urls.length > 0) { for (const url of data.urls) { await retryFailedUrl(url.id, url.url); await new Promise(r => setTimeout(r, 500)); // 避免太快 } } } // 删除失败URL记录 async function deleteFailedUrl(urlId) { try { await fetch(`${API_BASE}/api/articles/failed-urls/${urlId}`, { method: 'DELETE' }); loadFailedUrls(); } catch (error) { showToast('删除失败', 'error'); } } // 清空所有失败URL async function clearFailedUrls() { if (!confirm('确定要清空所有失败记录吗?')) return; try { await fetch(`${API_BASE}/api/articles/failed-urls/clear`, { method: 'POST' }); showToast('已清空', 'success'); loadFailedUrls(); } catch (error) { showToast('清空失败', 'error'); } }