Files
param-auto-manager/static/js/process.js
T
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

511 lines
17 KiB
JavaScript

// API基础地址
const API_BASE = '';
// 自动刷新定时器
let autoRefreshInterval = null;
// 页面加载
document.addEventListener('DOMContentLoaded', () => {
loadStepDefinitions();
loadActiveProcesses();
loadHistory();
loadAgentTemplate();
// 启动自动刷新(每2秒)
startAutoRefresh();
});
// 启动自动刷新
function startAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
autoRefreshInterval = setInterval(() => {
loadActiveProcesses();
}, 2000);
}
// 加载步骤定义
async function loadStepDefinitions() {
try {
const response = await fetch(`${API_BASE}/api/process/steps`);
const data = await response.json();
if (data.success) {
displayStepDefinitions(data.steps);
}
} catch (error) {
console.error('加载步骤定义失败:', error);
}
}
// 显示步骤定义
function displayStepDefinitions(steps) {
const container = document.getElementById('steps-flow');
container.innerHTML = steps.map((step, i) => `
<div class="step-card">
<div class="step-number">${step.num}</div>
<div class="step-title">${escapeHtml(step.name)}</div>
<div class="step-desc">${escapeHtml(step.description)}</div>
</div>
${i < steps.length - 1 ? '<i class="ri-arrow-right-line" style="color: #ccc;"></i>' : ''}
`).join('');
}
// 加载活动处理
async function loadActiveProcesses() {
try {
const response = await fetch(`${API_BASE}/api/process/active`);
const data = await response.json();
if (data.success) {
displayActiveProcesses(data.sessions);
}
} catch (error) {
console.error('加载活动处理失败:', error);
}
}
// 显示活动处理
function displayActiveProcesses(sessions) {
const container = document.getElementById('active-list');
if (sessions.length === 0) {
container.innerHTML = '<div class="empty-text">暂无正在进行的处理</div>';
return;
}
container.innerHTML = sessions.map(item => {
const session = item.session;
const steps = item.steps || [];
const isPaused = session.paused || session.status === 'paused';
return `
<div class="active-process-item ${isPaused ? 'paused' : ''}" id="process-${session.session_id}">
<div class="process-info">
<div>
<div class="process-name">${escapeHtml(session.product_name)}</div>
<div class="process-meta">
${session.category ? `分类: ${escapeHtml(session.category)} | ` : ''}
会话ID: ${escapeHtml(session.session_id)}
</div>
</div>
<div>
<span class="process-status-badge status-${session.status}">
${getStatusText(session.status)}
</span>
</div>
</div>
<!-- 步骤进度 -->
<div class="steps-progress">
${renderStepsProgress(steps, session.current_step)}
</div>
<!-- 操作按钮 -->
<div class="process-actions">
${session.status === 'running' ? `
<button onclick="pauseProcess('${session.session_id}')" class="btn btn-warning btn-sm">
<i class="ri-pause-line"></i> 暂停
</button>
` : ''}
${session.status === 'paused' ? `
<button onclick="resumeProcess('${session.session_id}')" class="btn btn-success btn-sm">
<i class="ri-play-line"></i> 继续
</button>
` : ''}
${session.status !== 'completed' ? `
<button onclick="stopProcess('${session.session_id}')" class="btn btn-danger btn-sm">
<i class="ri-stop-line"></i> 停止
</button>
` : ''}
<button onclick="showProcessDetail('${session.session_id}')" class="btn btn-secondary btn-sm">
<i class="ri-eye-line"></i> 详情
</button>
</div>
</div>
`;
}).join('');
}
// 渲染步骤进度
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 = ['搜索内容库', '搜索互联网', '抓取网页', '提取数据', '填充字段', '提交审核'];
// 获取每个步骤的简要数值
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';
else if (status === 'running') className = 'running';
else if (status === 'failed') className = 'failed';
else if (status === 'skipped') className = 'skipped';
html += `
<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;
}
// 加载历史
async function loadHistory() {
try {
const response = await fetch(`${API_BASE}/api/process/recent?limit=20`);
const data = await response.json();
if (data.success) {
displayHistory(data.sessions);
}
} catch (error) {
console.error('加载历史失败:', error);
}
}
// 显示历史
function displayHistory(sessions) {
const container = document.getElementById('history-table');
if (sessions.length === 0) {
container.innerHTML = '<tr><td colspan="6" class="empty-text">暂无处理历史</td></tr>';
return;
}
container.innerHTML = sessions.map(session => `
<tr>
<td>${escapeHtml(session.product_name)}</td>
<td>
<span class="process-status-badge status-${session.status}">
${getStatusText(session.status)}
</span>
</td>
<td>
<div class="step-indicator">
<span class="dot ${session.status}"></span>
步骤 ${session.current_step || 0}/6
</div>
</td>
<td>${formatDate(session.started_at || session.created_at)}</td>
<td>${formatDate(session.finished_at) || '-'}</td>
<td>
<button onclick="showProcessDetail('${session.session_id}')" class="btn btn-sm btn-secondary">
<i class="ri-eye-line"></i>
</button>
</td>
</tr>
`).join('');
}
// 暂停处理
async function pauseProcess(sessionId) {
try {
const response = await fetch(`${API_BASE}/api/process/${sessionId}/pause`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
showToast('处理已暂停', 'success');
loadActiveProcesses();
} else {
showToast('暂停失败: ' + data.error, 'error');
}
} catch (error) {
showToast('暂停失败', 'error');
}
}
// 继续处理
async function resumeProcess(sessionId) {
try {
const response = await fetch(`${API_BASE}/api/process/${sessionId}/resume`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
showToast('处理已继续', 'success');
loadActiveProcesses();
} else {
showToast('继续失败: ' + data.error, 'error');
}
} catch (error) {
showToast('继续失败', 'error');
}
}
// 停止处理
async function stopProcess(sessionId) {
if (!confirm('确定要停止处理吗?')) return;
try {
const response = await fetch(`${API_BASE}/api/process/${sessionId}/stop`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
showToast('处理已停止', 'success');
loadActiveProcesses();
loadHistory();
} else {
showToast('停止失败: ' + data.error, 'error');
}
} catch (error) {
showToast('停止失败', 'error');
}
}
// 显示处理详情
async function showProcessDetail(sessionId) {
try {
const response = await fetch(`${API_BASE}/api/process/${sessionId}/status`);
const data = await response.json();
if (data.success) {
const session = data.data.session;
const steps = data.data.steps;
document.getElementById('detail-title').innerHTML =
`<i class="ri-file-list-3-line"></i> ${escapeHtml(session.product_name)} - 处理详情`;
const body = document.getElementById('detail-body');
body.innerHTML = `
<div class="detail-meta" style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<div><strong>状态:</strong> ${getStatusText(session.status)}</div>
<div><strong>分类:</strong> ${escapeHtml(session.category || '未分类')}</div>
<div><strong>开始时间:</strong> ${formatDate(session.started_at) || '未开始'}</div>
<div><strong>完成时间:</strong> ${formatDate(session.finished_at) || '-'}</div>
${session.review_id ? `<div><strong>审核ID:</strong> ${escapeHtml(session.review_id)}</div>` : ''}
</div>
<h4 style="margin-bottom: 15px;"><i class="ri-list-check"></i> 处理步骤</h4>
<div class="detail-steps">
${steps.map(step => renderDetailStep(step)).join('')}
</div>
`;
document.getElementById('process-detail-modal').classList.add('active');
}
} catch (error) {
showToast('获取详情失败', 'error');
}
}
// 渲染详情步骤
function renderDetailStep(step) {
const statusColors = {
'completed': '#10b981',
'running': '#667eea',
'failed': '#ef4444',
'skipped': '#9ca3af',
'pending': '#e9ecef'
};
let stepDataHtml = '';
if (step.step_data) {
try {
const data = typeof step.step_data === 'string' ? JSON.parse(step.step_data) : step.step_data;
stepDataHtml = `<pre style="margin: 0; white-space: pre-wrap;">${escapeHtml(JSON.stringify(data, null, 2))}</pre>`;
} catch (e) {
stepDataHtml = escapeHtml(step.step_data);
}
}
return `
<div class="detail-step">
<div class="detail-step-header">
<div class="detail-step-name">
<span style="color: ${statusColors[step.step_status] || '#999'}; font-size: 18px;">●</span>
步骤${step.step_number}: ${escapeHtml(step.step_name)}
</div>
<span class="detail-step-status" style="background: ${statusColors[step.step_status] || '#e9ecef'}; color: white;">
${step.step_status}
</span>
</div>
${stepDataHtml ? `<div class="detail-step-content">${stepDataHtml}</div>` : ''}
${step.error_message ? `<div style="color: #ef4444; font-size: 13px;"><i class="ri-error-warning-line"></i> ${escapeHtml(step.error_message)}</div>` : ''}
<div class="detail-step-time">
${step.started_at ? `开始: ${formatDate(step.started_at)}` : ''}
${step.finished_at ? ` | 完成: ${formatDate(step.finished_at)}` : ''}
${step.duration_ms ? ` | 耗时: ${step.duration_ms}ms` : ''}
</div>
</div>
`;
}
// 刷新数据
function refreshData() {
loadActiveProcesses();
loadHistory();
}
// 关闭模态框
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
// 获取状态文本
function getStatusText(status) {
const statusMap = {
'pending': '等待中',
'running': '处理中',
'paused': '已暂停',
'completed': '已完成',
'failed': '失败',
'stopped': '已停止'
};
return statusMap[status] || status;
}
// HTML转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 日期格式化
function formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
// 显示提示
function showToast(message, type = '') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast active ${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;
}
}