Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c833394f06 | ||
|
|
5e3e9e7335 |
@@ -102,6 +102,7 @@ class ProcessMonitor:
|
|||||||
self._start_step(session_id, product_name, 3, '抓取网页内容')
|
self._start_step(session_id, product_name, 3, '抓取网页内容')
|
||||||
try:
|
try:
|
||||||
fetched = []
|
fetched = []
|
||||||
|
failed_count = 0
|
||||||
urls_to_fetch = [r['url'] for r in all_data['internet_results'][:5]]
|
urls_to_fetch = [r['url'] for r in all_data['internet_results'][:5]]
|
||||||
|
|
||||||
for i, url in enumerate(urls_to_fetch):
|
for i, url in enumerate(urls_to_fetch):
|
||||||
@@ -110,16 +111,46 @@ class ProcessMonitor:
|
|||||||
|
|
||||||
fetch_result = search_service.fetch_url_content(url)
|
fetch_result = search_service.fetch_url_content(url)
|
||||||
if fetch_result.get('success'):
|
if fetch_result.get('success'):
|
||||||
|
title = fetch_result.get('title', '')
|
||||||
|
content = fetch_result.get('content', '')
|
||||||
fetched.append({
|
fetched.append({
|
||||||
'url': url,
|
'url': url,
|
||||||
'title': fetch_result.get('title', ''),
|
'title': title,
|
||||||
'content': fetch_result.get('content', '')[:500]
|
'content': content[:500]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 保存到内容库
|
||||||
|
try:
|
||||||
|
existing = db.search_articles(url)
|
||||||
|
if not any(a.get('url') == url for a in existing):
|
||||||
|
db.add_article(
|
||||||
|
product_names=[],
|
||||||
|
category=category or '',
|
||||||
|
keywords=[],
|
||||||
|
summary=content[:200] if content else '',
|
||||||
|
content=content,
|
||||||
|
source=url,
|
||||||
|
url=url,
|
||||||
|
search_title=title
|
||||||
|
)
|
||||||
|
logger.info(f"[{session_id}] 已保存到内容库: {title[:30]}")
|
||||||
|
except Exception as save_error:
|
||||||
|
logger.warning(f"[{session_id}] 保存内容库失败: {save_error}")
|
||||||
|
else:
|
||||||
|
# 记录失败URL
|
||||||
|
failed_count += 1
|
||||||
|
error_msg = fetch_result.get('error', '抓取失败')
|
||||||
|
try:
|
||||||
|
db.add_failed_url(url, product_name, error_msg, source='process_monitor')
|
||||||
|
logger.warning(f"[{session_id}] 抓取失败,已记录: {url}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[{session_id}] 记录失败URL出错: {e}")
|
||||||
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
all_data['fetched_contents'] = fetched
|
all_data['fetched_contents'] = fetched
|
||||||
self._complete_step(session_id, 3, {'count': len(fetched)})
|
self._complete_step(session_id, 3, {'count': len(fetched), 'failed': failed_count})
|
||||||
logger.info(f"[{session_id}] 步骤3完成: 抓取 {len(fetched)} 个网页")
|
logger.info(f"[{session_id}] 步骤3完成: 抓取 {len(fetched)} 个网页, 失败 {failed_count} 个")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._fail_step(session_id, 3, str(e))
|
self._fail_step(session_id, 3, str(e))
|
||||||
|
|
||||||
|
|||||||
+16
-37
@@ -175,55 +175,34 @@
|
|||||||
max-width: 80px;
|
max-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
.step-value {
|
||||||
0%, 100% { transform: scale(1); }
|
font-size: 12px;
|
||||||
50% { transform: scale(1.1); }
|
font-weight: 600;
|
||||||
}
|
color: #10b981;
|
||||||
|
margin-top: 3px;
|
||||||
/* 步骤简要数值 */
|
padding: 2px 6px;
|
||||||
.steps-summary {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 15px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-summary-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-summary-item.completed {
|
|
||||||
background: #d1fae5;
|
background: #d1fae5;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-summary-item.running {
|
.step-node.running .step-value {
|
||||||
|
color: #667eea;
|
||||||
background: #e0e7ff;
|
background: #e0e7ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-summary-item.failed {
|
.step-node.failed .step-value {
|
||||||
|
color: #ef4444;
|
||||||
background: #fee2e2;
|
background: #fee2e2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-summary-item.skipped {
|
.step-node.skipped .step-value {
|
||||||
|
color: #6b7280;
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-summary-label {
|
@keyframes pulse {
|
||||||
color: #666;
|
0%, 100% { transform: scale(1); }
|
||||||
}
|
50% { transform: scale(1.1); }
|
||||||
|
|
||||||
.step-summary-value {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 操作按钮 */
|
/* 操作按钮 */
|
||||||
|
|||||||
@@ -472,6 +472,22 @@
|
|||||||
color: #3730a3;
|
color: #3730a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 产品处理会话区域 */
|
||||||
|
.process-sessions-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-sessions-section .panel-header {
|
||||||
|
background: #fef3c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-sessions-section .panel-header h2 {
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
.background-tasks-list {
|
.background-tasks-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
+18
-65
@@ -149,9 +149,26 @@ function renderStepsProgress(steps, currentStep) {
|
|||||||
|
|
||||||
const stepNames = ['搜索内容库', '搜索互联网', '抓取网页', '提取数据', '填充字段', '提交审核'];
|
const stepNames = ['搜索内容库', '搜索互联网', '抓取网页', '提取数据', '填充字段', '提交审核'];
|
||||||
|
|
||||||
|
// 获取每个步骤的简要数值
|
||||||
|
function getStepValue(stepNum) {
|
||||||
|
const data = stepDataMap[stepNum];
|
||||||
|
if (!data) return '';
|
||||||
|
|
||||||
|
switch (stepNum) {
|
||||||
|
case 1: return data.count !== undefined ? `${data.count}篇` : '';
|
||||||
|
case 2: return data.count !== undefined ? `${data.count}条` : '';
|
||||||
|
case 3: return data.count !== undefined ? `${data.count}个` : '';
|
||||||
|
case 4: return data.has_data !== undefined ? (data.has_data ? '✓' : '✗') : (data.extracted ? '✓' : '');
|
||||||
|
case 5: return data.filled !== undefined ? (data.filled ? '✓' : '✗') : '';
|
||||||
|
case 6: return data.review_id ? '✓' : '';
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let html = '<div class="steps-progress">';
|
let html = '<div class="steps-progress">';
|
||||||
for (let i = 1; i <= totalSteps; i++) {
|
for (let i = 1; i <= totalSteps; i++) {
|
||||||
const status = stepStatuses[i] || (i > currentStep ? 'pending' : '');
|
const status = stepStatuses[i] || (i > currentStep ? 'pending' : '');
|
||||||
|
const stepValue = getStepValue(i);
|
||||||
let className = '';
|
let className = '';
|
||||||
|
|
||||||
if (status === 'completed') className = 'completed';
|
if (status === 'completed') className = 'completed';
|
||||||
@@ -163,76 +180,12 @@ function renderStepsProgress(steps, currentStep) {
|
|||||||
<div class="step-node ${className}">
|
<div class="step-node ${className}">
|
||||||
<div class="step-circle">${i}</div>
|
<div class="step-circle">${i}</div>
|
||||||
<div class="step-label">${stepNames[i-1]}</div>
|
<div class="step-label">${stepNames[i-1]}</div>
|
||||||
|
${stepValue ? `<div class="step-value">${stepValue}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
// 添加简要数值显示
|
|
||||||
html += '<div class="steps-summary">';
|
|
||||||
|
|
||||||
// 步骤1:内容库搜索结果数
|
|
||||||
if (stepDataMap[1]) {
|
|
||||||
const count = stepDataMap[1].count || 0;
|
|
||||||
const status = stepStatuses[1];
|
|
||||||
html += `<div class="step-summary-item ${status || ''}">
|
|
||||||
<span class="step-summary-label">内容库:</span>
|
|
||||||
<span class="step-summary-value">${count} 篇</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步骤2:互联网搜索结果数
|
|
||||||
if (stepDataMap[2]) {
|
|
||||||
const count = stepDataMap[2].count || 0;
|
|
||||||
const status = stepStatuses[2];
|
|
||||||
html += `<div class="step-summary-item ${status || ''}">
|
|
||||||
<span class="step-summary-label">互联网:</span>
|
|
||||||
<span class="step-summary-value">${count} 条</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步骤3:抓取成功数
|
|
||||||
if (stepDataMap[3]) {
|
|
||||||
const count = stepDataMap[3].count || 0;
|
|
||||||
const status = stepStatuses[3];
|
|
||||||
html += `<div class="step-summary-item ${status || ''}">
|
|
||||||
<span class="step-summary-label">抓取成功:</span>
|
|
||||||
<span class="step-summary-value">${count} 个</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步骤4:提取状态
|
|
||||||
if (stepDataMap[4]) {
|
|
||||||
const hasData = stepDataMap[4].has_data || stepDataMap[4].extracted;
|
|
||||||
const status = stepStatuses[4];
|
|
||||||
html += `<div class="step-summary-item ${status || ''}">
|
|
||||||
<span class="step-summary-label">数据提取:</span>
|
|
||||||
<span class="step-summary-value">${hasData ? '成功' : '无数据'}</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步骤5:填充状态
|
|
||||||
if (stepDataMap[5]) {
|
|
||||||
const filled = stepDataMap[5].filled;
|
|
||||||
const status = stepStatuses[5];
|
|
||||||
html += `<div class="step-summary-item ${status || ''}">
|
|
||||||
<span class="step-summary-label">字段填充:</span>
|
|
||||||
<span class="step-summary-value">${filled ? '完成' : '失败'}</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步骤6:审核ID
|
|
||||||
if (stepDataMap[6]) {
|
|
||||||
const reviewId = stepDataMap[6].review_id;
|
|
||||||
const status = stepStatuses[6];
|
|
||||||
html += `<div class="step-summary-item ${status || ''}">
|
|
||||||
<span class="step-summary-label">审核ID:</span>
|
|
||||||
<span class="step-summary-value">${reviewId || '-'}</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+70
-1
@@ -22,6 +22,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// 加载失败URL和后台任务
|
// 加载失败URL和后台任务
|
||||||
loadFailedUrls();
|
loadFailedUrls();
|
||||||
loadBackgroundTasks();
|
loadBackgroundTasks();
|
||||||
|
loadProcessSessions();
|
||||||
|
|
||||||
// 启动自动刷新后台任务列表(每3秒)
|
// 启动自动刷新后台任务列表(每3秒)
|
||||||
startAutoRefreshTasks();
|
startAutoRefreshTasks();
|
||||||
@@ -773,7 +774,12 @@ async function loadBackgroundTasks() {
|
|||||||
const progressPercent = task.total > 0 ?
|
const progressPercent = task.total > 0 ?
|
||||||
Math.round((task.progress / task.total) * 100) : 0;
|
Math.round((task.progress / task.total) * 100) : 0;
|
||||||
|
|
||||||
const result = task.result ? JSON.parse(task.result) : {};
|
let result = {};
|
||||||
|
try {
|
||||||
|
result = task.result ? JSON.parse(task.result) : {};
|
||||||
|
} catch (e) {
|
||||||
|
result = {};
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="background-task-item ${statusClass}">
|
<div class="background-task-item ${statusClass}">
|
||||||
@@ -875,6 +881,8 @@ function startAutoRefreshTasks() {
|
|||||||
|
|
||||||
autoRefreshTasksInterval = setInterval(() => {
|
autoRefreshTasksInterval = setInterval(() => {
|
||||||
loadBackgroundTasks();
|
loadBackgroundTasks();
|
||||||
|
loadProcessSessions(); // 同时刷新处理会话
|
||||||
|
loadFailedUrls(); // 同时刷新失败URL
|
||||||
}, 3000); // 每3秒刷新一次
|
}, 3000); // 每3秒刷新一次
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,4 +892,65 @@ function stopAutoRefreshTasks() {
|
|||||||
clearInterval(autoRefreshTasksInterval);
|
clearInterval(autoRefreshTasksInterval);
|
||||||
autoRefreshTasksInterval = null;
|
autoRefreshTasksInterval = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载产品处理会话
|
||||||
|
async function loadProcessSessions() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/process/recent?limit=10`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
const container = document.getElementById('process-sessions-list');
|
||||||
|
|
||||||
|
if (data.sessions.length === 0) {
|
||||||
|
container.innerHTML = '<div class="empty-text">暂无处理会话</div>';
|
||||||
|
} else {
|
||||||
|
container.innerHTML = data.sessions.map(session => {
|
||||||
|
const statusClass = {
|
||||||
|
'pending': 'status-pending',
|
||||||
|
'running': 'status-running',
|
||||||
|
'paused': 'status-warning',
|
||||||
|
'completed': 'status-success',
|
||||||
|
'failed': 'status-error',
|
||||||
|
'stopped': 'status-warning'
|
||||||
|
}[session.status] || '';
|
||||||
|
|
||||||
|
const statusText = {
|
||||||
|
'pending': '等待中',
|
||||||
|
'running': '处理中',
|
||||||
|
'paused': '已暂停',
|
||||||
|
'completed': '已完成',
|
||||||
|
'failed': '失败',
|
||||||
|
'stopped': '已停止'
|
||||||
|
}[session.status] || session.status;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="background-task-item ${statusClass}">
|
||||||
|
<div class="task-info">
|
||||||
|
<div class="task-id">${escapeHtml(session.product_name)}</div>
|
||||||
|
<div class="task-status">
|
||||||
|
<span class="status-badge ${statusClass}">${statusText}</span>
|
||||||
|
${session.status === 'running' || session.status === 'paused' ?
|
||||||
|
`<span class="task-progress">步骤 ${session.current_step || 0}/6</span>` : ''}
|
||||||
|
${session.category ? `<span>分类: ${escapeHtml(session.category)}</span>` : ''}
|
||||||
|
${session.review_id ? `<span>审核ID: ${escapeHtml(session.review_id)}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="task-time">${session.created_at || ''}
|
||||||
|
${session.finished_at ? ' → ' + session.finished_at : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="task-actions">
|
||||||
|
<a href="/process" class="btn btn-sm btn-secondary">
|
||||||
|
<i class="ri-eye-line"></i> 监控
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载处理会话出错:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +111,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 产品处理会话区域 -->
|
||||||
|
<div class="panel process-sessions-section">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2><i class="ri-play-circle-line"></i> 产品处理会话</h2>
|
||||||
|
<div class="panel-actions">
|
||||||
|
<a href="/process" class="btn btn-primary btn-sm">
|
||||||
|
<i class="ri-external-link-line"></i> 监控页面
|
||||||
|
</a>
|
||||||
|
<button onclick="loadProcessSessions()" class="btn btn-secondary btn-sm">
|
||||||
|
<i class="ri-refresh-line"></i> 刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div id="process-sessions-list" class="background-tasks-list">
|
||||||
|
<div class="empty-text">暂无处理会话</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 失败URL区域 -->
|
<!-- 失败URL区域 -->
|
||||||
<div class="panel failed-urls-section">
|
<div class="panel failed-urls-section">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
|||||||
Reference in New Issue
Block a user