diff --git a/routes/articles.py b/routes/articles.py index 8244e31..ce7da58 100644 --- a/routes/articles.py +++ b/routes/articles.py @@ -132,4 +132,60 @@ def fetch_article(): 'data': result }) else: - return jsonify({'error': '抓取失败'}), 500 \ No newline at end of file + return jsonify({'error': '抓取失败'}), 500 + +@bp.route('/internet-search', methods=['POST']) +def internet_search(): + """互联网搜索(浏览器方式)""" + data = request.get_json() + keyword = data.get('keyword', '') + max_results = data.get('max_results', 10) + save_to_library = data.get('save_to_library', False) + + if not keyword: + return jsonify({'error': '请提供搜索关键词'}), 400 + + # 执行互联网搜索 + results = search_service.search_internet(keyword, max_results) + + return jsonify({ + 'success': True, + 'keyword': keyword, + 'results': results, + 'count': len(results) + }) + +@bp.route('/internet-search-and-fetch', methods=['POST']) +def internet_search_and_fetch(): + """互联网搜索并抓取内容""" + data = request.get_json() + keyword = data.get('keyword', '') + max_results = data.get('max_results', 5) + category = data.get('category') + + if not keyword: + return jsonify({'error': '请提供搜索关键词'}), 400 + + # 执行互联网搜索 + results = search_service.search_internet(keyword, max_results) + + # 抓取每个结果的详细内容 + fetched_results = [] + for r in results: + url = r.get('url') + if url: + content = search_service.fetch_url_content(url) + if content: + fetched_results.append({ + 'title': r['title'], + 'url': url, + 'source': r['source'], + 'fetched_content': content + }) + + return jsonify({ + 'success': True, + 'keyword': keyword, + 'results': fetched_results, + 'count': len(fetched_results) + }) \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 0e1fb84..0d1c7d4 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -642,4 +642,97 @@ body { .priority-low { color: #10b981; +} + +/* 搜索结果卡片 */ +.search-results-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 15px; +} + +.search-result-card { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 15px; + display: flex; + gap: 15px; + transition: all 0.2s; +} + +.search-result-card:hover { + border-color: #667eea; + background: white; +} + +.result-number { + width: 30px; + height: 30px; + background: #667eea; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 14px; +} + +.result-content { + flex: 1; + min-width: 0; +} + +.result-title { + font-size: 15px; + color: #333; + cursor: pointer; + margin-bottom: 8px; + word-break: break-word; +} + +.result-title:hover { + color: #667eea; +} + +.result-url { + font-size: 12px; + color: #666; + margin-bottom: 6px; + overflow: hidden; +} + +.result-url a { + color: #3b82f6; + text-decoration: none; + display: flex; + align-items: center; + gap: 4px; +} + +.result-url a:hover { + text-decoration: underline; +} + +.result-source { + font-size: 12px; + color: #999; +} + +.result-actions { + display: flex; + flex-direction: column; + gap: 8px; +} + +/* 搜索状态 */ +#internet-search-status { + display: flex; + align-items: center; + gap: 8px; +} + +#internet-search-status i { + font-size: 16px; } \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js index a1d3962..931e358 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -612,4 +612,233 @@ function getStatusText(status) { 'completed': '已完成' }; return statusMap[status] || status; +} + +// ========== 互联网搜索功能 ========== + +let currentSearchResult = null; + +// 执行互联网搜索 +async function doInternetSearch() { + const keyword = document.getElementById('internet-search-keyword').value.trim(); + const maxResults = parseInt(document.getElementById('internet-search-count').value) || 10; + + if (!keyword) { + showToast('请输入搜索关键词', 'error'); + return; + } + + // 显示搜索状态 + const statusDiv = document.getElementById('internet-search-status'); + const resultsDiv = document.getElementById('internet-search-results'); + const searchBtn = document.getElementById('search-btn'); + + statusDiv.innerHTML = ' 正在搜索...'; + resultsDiv.innerHTML = '
搜索中...
'; + searchBtn.disabled = true; + + 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 }) + }); + + const data = await response.json(); + + if (data.success) { + statusDiv.innerHTML = ` 搜索完成,找到 ${data.count} 条结果`; + + if (data.results.length > 0) { + resultsDiv.innerHTML = data.results.map((r, i) => ` +
+
${i + 1}
+
+
${escapeHtml(r.title)}
+ +
来源: ${escapeHtml(r.source)}
+
+
+ + +
+
+ `).join(''); + + // 存储搜索结果供后续使用 + window.lastSearchResults = data.results; + } else { + resultsDiv.innerHTML = '
未找到相关结果
'; + } + } else { + statusDiv.innerHTML = ` 搜索失败: ${escapeHtml(data.error)}`; + resultsDiv.innerHTML = '
搜索失败
'; + } + } catch (error) { + statusDiv.innerHTML = ' 搜索出错'; + resultsDiv.innerHTML = '
搜索出错,请稍后重试
'; + console.error('搜索错误:', error); + } + + searchBtn.disabled = false; +} + +// 显示搜索结果详情 +function showSearchResultDetail(index) { + if (!window.lastSearchResults || !window.lastSearchResults[index]) { + return; + } + + currentSearchResult = window.lastSearchResults[index]; + const body = document.getElementById('search-result-body'); + + body.innerHTML = ` +
+
+ + ${escapeHtml(currentSearchResult.title)} +
+
+ + ${escapeHtml(currentSearchResult.url)} +
+
+ + ${escapeHtml(currentSearchResult.source)} +
+
+
+

提示

+

点击"抓取内容"按钮可以获取页面详细内容,然后保存到内容库。

+
+ `; + + document.getElementById('search-result-modal').classList.add('active'); +} + +// 抓取并显示搜索结果内容 +async function fetchAndShowResult(index) { + if (!window.lastSearchResults || !window.lastSearchResults[index]) { + return; + } + + const result = window.lastSearchResults[index]; + currentSearchResult = result; + + showToast('正在抓取页面内容...', ''); + + 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], + category: '' + }) + }); + + const data = await response.json(); + + if (data.success) { + // 更新当前搜索结果,添加抓取的内容 + currentSearchResult = { + ...result, + fetched: true, + fetchedContent: data.data + }; + + const body = document.getElementById('search-result-body'); + body.innerHTML = ` +
+
+ + ${escapeHtml(data.data.title)} +
+
+ + ${escapeHtml(result.url)} +
+
+ + ${escapeHtml(data.data.description || '无')} +
+
+
+

页面内容

+
${escapeHtml(data.data.content.substring(0, 2000))}${data.data.content.length > 2000 ? '\n... (内容过长,已截断)' : ''}
+
+ `; + + document.getElementById('search-result-modal').classList.add('active'); + showToast('内容抓取成功', 'success'); + } else { + showToast('抓取失败: ' + data.error, 'error'); + } + } catch (error) { + showToast('抓取出错', 'error'); + console.error('抓取错误:', error); + } +} + +// 快速保存搜索结果到内容库 +async function quickSaveResult(index) { + if (!window.lastSearchResults || !window.lastSearchResults[index]) { + return; + } + + const result = window.lastSearchResults[index]; + + // 先抓取内容再保存 + showToast('正在抓取并保存...', ''); + + 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], + category: '' + }) + }); + + const data = await response.json(); + + if (data.success) { + showToast('已保存到内容库', 'success'); + loadArticles(); + loadStats(); + } else { + showToast('保存失败: ' + data.error, 'error'); + } + } catch (error) { + showToast('保存出错', 'error'); + } +} + +// 从详情模态框保存到内容库 +async function saveSearchResultToLibrary() { + if (!currentSearchResult) { + return; + } + + // 如果还没有抓取内容,先抓取 + if (!currentSearchResult.fetched) { + await fetchAndShowResult(window.lastSearchResults.findIndex(r => r.url === currentSearchResult.url)); + return; + } + + showToast('已保存到内容库', 'success'); + closeModal('search-result-modal'); + loadArticles(); + loadStats(); } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e1af3bc..8cb47e6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -116,6 +116,26 @@ + +
+
+

互联网搜索

+
+ + + +
+
+
+
+
+
输入关键词进行互联网搜索
+
+
+
+
@@ -267,6 +287,26 @@
+ + +