Compare commits

..
5 Commits
Author SHA1 Message Date
hz4th_coder 96c479916b feat: 步骤4提取产品数据改用智能体hz4th_editor执行
- 步骤4调用 openclaw agent --agent hz4th_editor --message 执行提取任务
- 新增任务文本模板(config/agent_task_template.txt),支持变量填充
- /process页面新增模板编辑面板,可查看/编辑/保存/预览模板
- 模板变量: {{product_name}} {{category}} {{subcategory}} {{library_results}} {{internet_results}}
- 新增API: GET/POST /api/process/agent-template, POST /api/process/agent-template/preview
2026-07-15 11:18:47 +08:00
hz4th_coder c833394f06 fix: 统一处理流程和后台任务显示
- 处理流程抓取失败时记录到失败URL库
- /search页面新增产品处理会话展示区
- 同时显示后台抓取任务和产品处理会话
- 自动刷新同时更新三种数据:任务、会话、失败URL
2026-07-14 15:36:18 +08:00
hz4th_coder 5e3e9e7335 fix: 修复处理流程和后台任务问题
- 步骤简要数值显示在每个步骤圆圈下方
- 处理流程抓取网页后保存到内容库
- 修复后台任务结果解析错误
2026-07-14 15:13:31 +08:00
hz4th_coder 1712fb0756 feat: 处理监控页面显示步骤简要数值
- 显示内容库搜索结果数
- 显示互联网搜索结果数
- 显示抓取成功数
- 显示数据提取/填充状态
- 显示审核ID
2026-07-14 12:42:59 +08:00
hz4th_coder d18e80016b fix: 修复首页产品处理启动问题
- 首页点击处理现在使用新的处理监控流程
- 处理启动后自动跳转到处理监控页面
- 首页添加'处理监控'入口按钮
- 修复错误消息显示 undefined 的问题
2026-07-14 12:33:40 +08:00
11 changed files with 673 additions and 27 deletions
+31
View File
@@ -0,0 +1,31 @@
## 任务背景
### 内容库搜索结果
以下是从内容库中搜索到的相关文章数据位置:
{{library_results}}
### 互联网搜索抓取内容
以下是从互联网搜索并抓取的网页内容数据位置:
{{internet_results}}
## 产品信息
- **产品名称:** {{product_name}}
- **产品类别:** {{category}}
- **子类别:** {{subcategory}}
## 任务要求
请从上述搜索结果和抓取内容中,提取出与产品「{{product_name}}」直接相关的具体内容。
要求:
1. 只提取与该产品直接相关的信息,排除其他无关产品的内容
2. 提取的内容应包括但不限于:产品参数、规格、功能描述、发布信息、技术特点等
3. 注明每条信息的来源(URL或文章标题)
4. 如果某些信息在多个来源中都有提及,请综合整理
5. 严格按照原始数据提取,不要编造或推测任何内容
请将提取结果以JSON格式输出,包含以下字段:
- name: 产品名称
- extracted_fields: 提取到的字段键值对
- sources: 信息来源列表
- confidence: 提取置信度(high/medium/low
+81 -1
View File
@@ -4,10 +4,15 @@
from flask import Blueprint, jsonify, request
from services.process_monitor import process_monitor, PROCESS_STEPS
from models.database import db
import os
import logging
logger = logging.getLogger('process_monitor_api')
# 模板文件路径
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config')
AGENT_TEMPLATE_FILE = os.path.join(TEMPLATE_DIR, 'agent_task_template.txt')
bp = Blueprint('process_monitor', __name__, url_prefix='/api/process')
@@ -177,4 +182,79 @@ def get_step_detail(session_id, step_num):
return jsonify({
'success': True,
'step': step
})
})
@bp.route('/agent-template', methods=['GET'])
def get_agent_template():
"""获取智能体任务文本模板"""
try:
if os.path.exists(AGENT_TEMPLATE_FILE):
with open(AGENT_TEMPLATE_FILE, 'r', encoding='utf-8') as f:
template = f.read()
return jsonify({
'success': True,
'template': template
})
else:
return jsonify({
'success': False,
'error': '模板文件不存在'
}), 404
except Exception as e:
logger.error(f"获取模板失败: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@bp.route('/agent-template', methods=['POST'])
def save_agent_template():
"""保存智能体任务文本模板"""
try:
data = request.get_json()
template = data.get('template', '')
if not template:
return jsonify({'success': False, 'error': '模板内容不能为空'}), 400
os.makedirs(TEMPLATE_DIR, exist_ok=True)
with open(AGENT_TEMPLATE_FILE, 'w', encoding='utf-8') as f:
f.write(template)
return jsonify({
'success': True,
'message': '模板已保存'
})
except Exception as e:
logger.error(f"保存模板失败: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@bp.route('/agent-template/preview', methods=['POST'])
def preview_agent_template():
"""预览填充后的任务文本"""
try:
data = request.get_json()
product_name = data.get('product_name', '示例产品')
category = data.get('category', '示例类别')
subcategory = data.get('subcategory', '示例子类别')
# 读取模板
if os.path.exists(AGENT_TEMPLATE_FILE):
with open(AGENT_TEMPLATE_FILE, 'r', encoding='utf-8') as f:
template = f.read()
else:
return jsonify({'success': False, 'error': '模板文件不存在'}), 404
# 填充示例数据
filled = template.replace('{{product_name}}', product_name)
filled = filled.replace('{{category}}', category)
filled = filled.replace('{{subcategory}}', subcategory or '')
filled = filled.replace('{{library_results}}', '[内容库搜索结果将在此处列出,包含文章标题、URL、摘要等]')
filled = filled.replace('{{internet_results}}', '[互联网抓取内容将在此处列出,包含URL、标题、正文片段等]')
return jsonify({
'success': True,
'preview': filled
})
except Exception as e:
logger.error(f"预览模板失败: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
+201 -14
View File
@@ -1,9 +1,11 @@
"""
处理步骤监控服务 - 记录和监控产品处理流程
"""
import os
import time
import uuid
import json
import subprocess
import threading
import logging
from datetime import datetime
@@ -18,7 +20,7 @@ PROCESS_STEPS = [
{'num': 1, 'name': '搜索内容库', 'description': '从内容库搜索相关文章'},
{'num': 2, 'name': '搜索互联网', 'description': '从互联网搜索最新数据'},
{'num': 3, 'name': '抓取网页内容', 'description': '抓取搜索结果网页的详细内容'},
{'num': 4, 'name': '提取产品数据', 'description': '从抓取内容中提取产品相关数据'},
{'num': 4, 'name': '提取产品数据(智能体)', 'description': '调用hz4th_editor智能体提取产品相关内容'},
{'num': 5, 'name': '填充字段', 'description': '根据分类字段配置填充数据'},
{'num': 6, 'name': '提交审核', 'description': '提交到ParamHub待审核区'},
]
@@ -102,6 +104,7 @@ class ProcessMonitor:
self._start_step(session_id, product_name, 3, '抓取网页内容')
try:
fetched = []
failed_count = 0
urls_to_fetch = [r['url'] for r in all_data['internet_results'][:5]]
for i, url in enumerate(urls_to_fetch):
@@ -110,31 +113,90 @@ class ProcessMonitor:
fetch_result = search_service.fetch_url_content(url)
if fetch_result.get('success'):
title = fetch_result.get('title', '')
content = fetch_result.get('content', '')
fetched.append({
'url': url,
'title': fetch_result.get('title', ''),
'content': fetch_result.get('content', '')[:500]
'title': title,
'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)
all_data['fetched_contents'] = fetched
self._complete_step(session_id, 3, {'count': len(fetched)})
logger.info(f"[{session_id}] 步骤3完成: 抓取 {len(fetched)} 个网页")
self._complete_step(session_id, 3, {'count': len(fetched), 'failed': failed_count})
logger.info(f"[{session_id}] 步骤3完成: 抓取 {len(fetched)} 个网页, 失败 {failed_count}")
except Exception as e:
self._fail_step(session_id, 3, str(e))
# 步骤4: 提取产品数据
# 步骤4: 提取产品数据(调用智能体执行)
if not self._check_pause(session_id):
self._start_step(session_id, product_name, 4, '提取产品数据')
self._start_step(session_id, product_name, 4, '提取产品数据(智能体)')
try:
extracted = self._extract_data(product_name, all_data)
all_data['extracted_data'] = extracted
# 构建任务文本
task_text = self._build_agent_task(
product_name, category, subcategory, all_data
)
if extracted:
self._complete_step(session_id, 4, {'has_data': True})
# 记录任务文本
self._complete_step(session_id, 4, {
'agent': 'hz4th_editor',
'task_text': task_text,
'status': 'calling_agent'
})
# 调用智能体
agent_result = self._call_agent(task_text)
if agent_result.get('success'):
extracted = self._parse_agent_response(agent_result.get('output', ''))
all_data['extracted_data'] = extracted
if extracted:
self._complete_step(session_id, 4, {
'has_data': True,
'agent': 'hz4th_editor',
'task_text': task_text,
'agent_output': agent_result.get('output', '')[:2000]
})
else:
self._complete_step(session_id, 4, {
'has_data': False,
'agent': 'hz4th_editor',
'task_text': task_text,
'agent_output': agent_result.get('output', '')[:2000]
}, status='skipped')
result['message'] = '智能体无法提取有效数据'
else:
self._complete_step(session_id, 4, {'has_data': False}, status='skipped')
result['message'] = '无法提取有效数据'
self._fail_step(session_id, 4, f"智能体调用失败: {agent_result.get('error', '未知错误')}")
result['message'] = f'智能体调用失败: {agent_result.get("error")}'
except Exception as e:
self._fail_step(session_id, 4, str(e))
@@ -271,8 +333,133 @@ class ProcessMonitor:
return {'session': session, 'steps': steps}
return None
def _build_agent_task(self, product_name, category, subcategory, all_data):
"""构建智能体任务文本"""
# 读取模板
template_file = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'config', 'agent_task_template.txt'
)
if os.path.exists(template_file):
with open(template_file, 'r', encoding='utf-8') as f:
template = f.read()
else:
# 默认模板
template = (
"请从以下数据中提取产品「{{product_name}}」的相关内容。\n"
"类别: {{category}} / {{subcategory}}\n\n"
"内容库结果:\n{{library_results}}\n\n"
"互联网抓取内容:\n{{internet_results}}\n\n"
"要求:只提取与该产品信息直接相关的内容,排除无关产品。以JSON格式输出。"
)
# 构建内容库搜索结果
library_lines = []
for i, article in enumerate(all_data.get('library_results', [])[:10], 1):
title = article.get('search_title', article.get('title', '无标题'))
url = article.get('url', article.get('source', '无URL'))
summary = article.get('summary', '')[:200]
library_lines.append(f" [{i}] 标题: {title}\n URL: {url}\n 摘要: {summary}")
library_text = '\n'.join(library_lines) if library_lines else '(无内容库搜索结果)'
# 构建互联网抓取内容
internet_lines = []
for i, item in enumerate(all_data.get('fetched_contents', [])[:10], 1):
title = item.get('title', '无标题')
url = item.get('url', '无URL')
content = item.get('content', '')[:300]
internet_lines.append(f" [{i}] 标题: {title}\n URL: {url}\n 内容片段: {content}")
internet_text = '\n'.join(internet_lines) if internet_lines else '(无互联网抓取内容)'
# 填充模板
task = template.replace('{{product_name}}', product_name or '未知')
task = task.replace('{{category}}', category or '未分类')
task = task.replace('{{subcategory}}', subcategory or '')
task = task.replace('{{library_results}}', library_text)
task = task.replace('{{internet_results}}', internet_text)
return task
def _call_agent(self, task_text):
"""调用智能体执行任务"""
try:
cmd = [
'openclaw', 'agent',
'--agent', 'hz4th_editor',
'--message', task_text
]
logger.info(f"调用智能体命令: openclaw agent --agent hz4th_editor --message '[任务文本 {len(task_text)} 字符]'")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300 # 5分钟超时
)
if result.returncode == 0:
output = result.stdout.strip()
logger.info(f"智能体返回: {output[:500]}...")
return {'success': True, 'output': output}
else:
error = result.stderr.strip() or result.stdout.strip()
logger.error(f"智能体调用失败: {error}")
return {'success': False, 'error': error}
except subprocess.TimeoutExpired:
return {'success': False, 'error': '智能体执行超时(>5分钟)'}
except FileNotFoundError:
return {'success': False, 'error': 'openclaw命令未找到'}
except Exception as e:
return {'success': False, 'error': str(e)}
def _parse_agent_response(self, output):
"""解析智能体返回的结果"""
if not output:
return None
# 尝试从输出中提取JSON
import re
# 查找JSON块
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', output, re.DOTALL)
if json_match:
try:
data = json.loads(json_match.group(1))
return {
'name': data.get('name', ''),
'extracted_fields': data.get('extracted_fields', {}),
'sources': data.get('sources', []),
'confidence': data.get('confidence', 'unknown'),
'raw_output': output
}
except json.JSONDecodeError:
pass
# 尝试直接解析整个输出为JSON
try:
data = json.loads(output)
return {
'name': data.get('name', ''),
'extracted_fields': data.get('extracted_fields', {}),
'sources': data.get('sources', []),
'confidence': data.get('confidence', 'unknown'),
'raw_output': output
}
except json.JSONDecodeError:
pass
# 如果无法解析为JSON,将原始输出作为raw_content保存
return {
'name': '',
'raw_content': output,
'raw_output': output
}
def _extract_data(self, product_name, all_data):
"""提取产品数据"""
"""提取产品数据(备用,已被智能体替代)"""
all_content = []
for article in all_data.get('library_results', []):
+77 -1
View File
@@ -175,6 +175,31 @@
max-width: 80px;
}
.step-value {
font-size: 12px;
font-weight: 600;
color: #10b981;
margin-top: 3px;
padding: 2px 6px;
background: #d1fae5;
border-radius: 3px;
}
.step-node.running .step-value {
color: #667eea;
background: #e0e7ff;
}
.step-node.failed .step-value {
color: #ef4444;
background: #fee2e2;
}
.step-node.skipped .step-value {
color: #6b7280;
background: #f3f4f6;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
@@ -325,4 +350,55 @@
.steps-progress {
flex-wrap: wrap;
}
}
}
/* 智能体任务模板区域 */
.template-section .panel-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.template-actions {
display: flex;
gap: 8px;
}
.template-info {
background: #f0f4ff;
border: 1px solid #c7d2fe;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 15px;
font-size: 13px;
color: #4338ca;
}
.template-info p {
margin: 4px 0;
}
.template-info code {
background: #e0e7ff;
padding: 1px 5px;
border-radius: 3px;
font-size: 12px;
color: #3730a3;
}
.template-editor {
width: 100%;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
resize: vertical;
background: #fafafa;
}
.template-editor:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
+16
View File
@@ -472,6 +472,22 @@
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 {
display: flex;
flex-direction: column;
+13 -8
View File
@@ -396,30 +396,35 @@ async function processProduct(productName, category, subcategory) {
return;
}
showToast('正在启动处理...', '');
try {
const response = await fetch(`${API_BASE}/api/products/process`, {
const response = await fetch(`${API_BASE}/api/process/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
product_name: productName,
category: category,
subcategory: subcategory
category: category || '',
subcategory: subcategory || ''
})
});
const data = await response.json();
if (data.success) {
showToast(data.message, 'success');
if (data.new_products && data.new_products.length > 0) {
showToast(`发现 ${data.new_products.length} 个新产品`, 'success');
}
showToast('处理流程已启动', 'success');
// 跳转到处理监控页面
setTimeout(() => {
window.location.href = '/process';
}, 1000);
} else {
showToast('处理失败: ' + data.error || data.message, 'error');
const errorMsg = data.error || data.message || '未知错误';
showToast('处理失败: ' + errorMsg, 'error');
}
refreshData();
} catch (error) {
console.error('处理产品失败:', error);
showToast('处理产品失败', 'error');
}
}
+109 -2
View File
@@ -9,6 +9,7 @@ document.addEventListener('DOMContentLoaded', () => {
loadStepDefinitions();
loadActiveProcesses();
loadHistory();
loadAgentTemplate();
// 启动自动刷新(每2秒)
startAutoRefresh();
@@ -133,17 +134,42 @@ function displayActiveProcesses(sessions) {
function renderStepsProgress(steps, currentStep) {
const totalSteps = 6;
const stepStatuses = {};
const stepDataMap = {};
// 构建步骤状态映射
// 构建步骤状态和数据映射
steps.forEach(s => {
stepStatuses[s.step_number] = s.step_status;
if (s.step_data) {
try {
stepDataMap[s.step_number] = typeof s.step_data === 'string' ? JSON.parse(s.step_data) : s.step_data;
} catch (e) {
stepDataMap[s.step_number] = s.step_data;
}
}
});
const stepNames = ['搜索内容库', '搜索互联网', '抓取网页', '提取数据', '填充字段', '提交审核'];
let html = '';
// 获取每个步骤的简要数值
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">';
for (let i = 1; i <= totalSteps; i++) {
const status = stepStatuses[i] || (i > currentStep ? 'pending' : '');
const stepValue = getStepValue(i);
let className = '';
if (status === 'completed') className = 'completed';
@@ -155,9 +181,11 @@ function renderStepsProgress(steps, currentStep) {
<div class="step-node ${className}">
<div class="step-circle">${i}</div>
<div class="step-label">${stepNames[i-1]}</div>
${stepValue ? `<div class="step-value">${stepValue}</div>` : ''}
</div>
`;
}
html += '</div>';
return html;
}
@@ -401,4 +429,83 @@ function showToast(message, type = '') {
setTimeout(() => {
toast.classList.remove('active');
}, 3000);
}
// ===== 智能体任务模板 =====
// 加载模板
async function loadAgentTemplate() {
try {
const response = await fetch(`${API_BASE}/api/process/agent-template`);
const data = await response.json();
if (data.success) {
document.getElementById('agent-template-editor').value = data.template;
} else {
document.getElementById('agent-template-editor').value = '// 模板加载失败: ' + (data.error || '未知错误');
}
} catch (error) {
console.error('加载模板失败:', error);
document.getElementById('agent-template-editor').value = '// 加载模板失败: ' + error.message;
}
}
// 保存模板
async function saveTemplate() {
const template = document.getElementById('agent-template-editor').value;
if (!template.trim()) {
showToast('模板内容不能为空', 'error');
return;
}
try {
const response = await fetch(`${API_BASE}/api/process/agent-template`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ template })
});
const data = await response.json();
if (data.success) {
showToast('模板已保存 ✓', 'success');
} else {
showToast('保存失败: ' + data.error, 'error');
}
} catch (error) {
showToast('保存失败: ' + error.message, 'error');
}
}
// 预览模板
function previewTemplate() {
document.getElementById('template-preview-modal').classList.add('active');
doPreview();
}
// 执行预览
async function doPreview() {
const product = document.getElementById('preview-product').value || '示例产品';
const category = document.getElementById('preview-category').value || 'AI模型';
try {
const response = await fetch(`${API_BASE}/api/process/agent-template/preview`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
product_name: product,
category: category,
subcategory: ''
})
});
const data = await response.json();
if (data.success) {
document.getElementById('template-preview-content').textContent = data.preview;
} else {
document.getElementById('template-preview-content').textContent = '预览失败: ' + data.error;
}
} catch (error) {
document.getElementById('template-preview-content').textContent = '预览失败: ' + error.message;
}
}
+70 -1
View File
@@ -22,6 +22,7 @@ document.addEventListener('DOMContentLoaded', () => {
// 加载失败URL和后台任务
loadFailedUrls();
loadBackgroundTasks();
loadProcessSessions();
// 启动自动刷新后台任务列表(每3秒)
startAutoRefreshTasks();
@@ -773,7 +774,12 @@ async function loadBackgroundTasks() {
const progressPercent = task.total > 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 `
<div class="background-task-item ${statusClass}">
@@ -875,6 +881,8 @@ function startAutoRefreshTasks() {
autoRefreshTasksInterval = setInterval(() => {
loadBackgroundTasks();
loadProcessSessions(); // 同时刷新处理会话
loadFailedUrls(); // 同时刷新失败URL
}, 3000); // 每3秒刷新一次
}
@@ -884,4 +892,65 @@ function stopAutoRefreshTasks() {
clearInterval(autoRefreshTasksInterval);
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);
}
}
+3
View File
@@ -13,6 +13,9 @@
<header class="header">
<h1><i class="ri-robot-line"></i> 参数数据自动化管理系统</h1>
<div class="header-actions">
<a href="/process" class="btn btn-primary">
<i class="ri-cpu-line"></i> 处理监控
</a>
<button onclick="refreshData()" class="btn btn-secondary">
<i class="ri-refresh-line"></i> 刷新数据
</button>
+52
View File
@@ -49,6 +49,58 @@
</div>
</div>
<!-- 智能体任务模板区域 -->
<div class="panel template-section">
<div class="panel-header">
<h2><i class="ri-robot-line"></i> 智能体任务文本模板(步骤4:提取产品数据)</h2>
<div class="template-actions">
<button onclick="previewTemplate()" class="btn btn-secondary btn-sm">
<i class="ri-eye-line"></i> 预览
</button>
<button onclick="saveTemplate()" class="btn btn-primary btn-sm">
<i class="ri-save-line"></i> 保存模板
</button>
</div>
</div>
<div class="panel-body">
<div class="template-info">
<p><strong>说明:</strong>此模板用于步骤4「提取产品数据」中调用智能体 <code>hz4th_editor</code> 的任务文本。</p>
<p><strong>可用变量:</strong>
<code>{{product_name}}</code> 产品名称、
<code>{{category}}</code> 类别、
<code>{{subcategory}}</code> 子类别、
<code>{{library_results}}</code> 内容库搜索结果、
<code>{{internet_results}}</code> 互联网抓取内容
</p>
<p><strong>调用命令:</strong><code>openclaw agent --agent hz4th_editor --message "[填充后的任务文本]"</code></p>
</div>
<textarea id="agent-template-editor" class="template-editor" rows="20" placeholder="加载模板中..."></textarea>
</div>
</div>
<!-- 预览模态框 -->
<div id="template-preview-modal" class="modal">
<div class="modal-content large">
<div class="modal-header">
<h3><i class="ri-eye-line"></i> 任务文本预览</h3>
<button onclick="closeModal('template-preview-modal')" class="close-btn">
<i class="ri-close-line"></i>
</button>
</div>
<div class="modal-body">
<div class="preview-meta" style="margin-bottom: 15px;">
<label>产品名称: <input type="text" id="preview-product" value="示例产品" style="padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px;"></label>
<label style="margin-left: 10px;">类别: <input type="text" id="preview-category" value="AI模型" style="padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px;"></label>
<button onclick="doPreview()" class="btn btn-sm btn-secondary" style="margin-left: 10px;"><i class="ri-refresh-line"></i> 刷新预览</button>
</div>
<pre id="template-preview-content" style="background: #f8f9fa; padding: 15px; border-radius: 8px; white-space: pre-wrap; font-size: 13px; max-height: 500px; overflow-y: auto;">加载中...</pre>
</div>
<div class="modal-footer">
<button onclick="closeModal('template-preview-modal')" class="btn btn-secondary">关闭</button>
</div>
</div>
</div>
<!-- 处理历史区域 -->
<div class="panel history-section">
<div class="panel-header">
+20
View File
@@ -111,6 +111,26 @@
</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区域 -->
<div class="panel failed-urls-section">
<div class="panel-header">