'use strict'; (function () { const state = { initialized: false, loading: false, root: '', tasks: [], filter: 'all', timer: null, }; const STATUS_LABELS = { queued: '排队中', submitted: '已提交', running: '进行中', success: '已完成', failed: '失败', canceled: '已取消', timeout: '已超时', }; const KIND_LABELS = { logo: 'LOGO', banner: '海报', signage: '招牌', sticker: '贴纸', dish: '菜品', }; function el(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function formatTime(value) { if (!value) return ''; const date = new Date(value); const pad = (n) => String(n).padStart(2, '0'); return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; } function formatBytes(size) { if (!Number.isFinite(size) || size <= 0) return ''; if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`; return `${(size / 1024 / 1024).toFixed(1)} MB`; } function progressValue(task) { const parsed = Number.parseInt(String(task.progress || '').replace(/[^\d]/g, ''), 10); if (task.status === 'success') return 100; if (Number.isFinite(parsed)) return Math.min(100, Math.max(0, parsed)); return task.status === 'queued' ? 0 : 35; } function getFilteredTasks() { if (state.filter === 'all') return state.tasks; if (state.filter === 'running') { return state.tasks.filter((task) => task.status === 'running' || task.status === 'submitted'); } return state.tasks.filter((task) => task.status === state.filter); } function setCount(id, value) { document.getElementById(id).textContent = String(value || 0); } function renderSummary() { const counts = { all: 0, running: 0, queued: 0, success: 0, failed: 0 }; state.tasks.forEach((task) => { counts.all += 1; if (task.status === 'running' || task.status === 'submitted') counts.running += 1; else if (task.status === 'queued') counts.queued += 1; else if (task.status === 'success') counts.success += 1; else if (task.status === 'failed' || task.status === 'timeout') counts.failed += 1; }); setCount('taskCountAll', counts.all); setCount('taskCountRunning', counts.running); setCount('taskCountQueued', counts.queued); setCount('taskCountSuccess', counts.success); setCount('taskCountFailed', counts.failed); document.getElementById('taskSummary').textContent = state.loading ? '正在加载任务…' : `共 ${counts.all} 个任务 · ${counts.running} 个进行中 · ${counts.failed} 个失败`; } function buildTaskCard(task) { const card = el('article', `task-card status-${task.status}`); const main = el('div', 'task-main'); const titleRow = el('div', 'task-title-row'); const title = el('div', 'task-title'); title.textContent = `${task.shopName || '未知门店'} · ${KIND_LABELS[task.kind] || task.name || task.kind}`; const badge = el('span', `badge task-badge ${task.status}`); badge.textContent = STATUS_LABELS[task.status] || task.status; titleRow.append(title, badge); const meta = el('div', 'task-meta'); meta.textContent = [ task.model || '默认模型', formatTime(task.createdAt), task.cost !== null && task.cost !== undefined ? `费用 ${task.cost}` : '', task.providerTaskId ? `任务 ${String(task.providerTaskId).slice(0, 10)}…` : '', ].filter(Boolean).join(' · '); const progress = el('div', 'task-progress'); const bar = el('span', 'task-progress-bar'); bar.style.width = `${progressValue(task)}%`; progress.appendChild(bar); main.append(titleRow, progress, meta); if (task.warning) main.appendChild(el('div', 'task-notice warning', task.warning)); if (task.error) main.appendChild(el('div', 'task-notice error', task.error)); const actions = el('div', 'task-actions'); const right = el('div', 'task-status-detail'); right.textContent = `${task.progress || progressValue(task) + '%'}${task.fileSizeBytes ? ` · ${formatBytes(task.fileSizeBytes)}` : ''}`; actions.appendChild(right); if (task.resultUrl) { const open = el('a', 'btn btn-ghost btn-small', '查看结果'); open.href = task.resultUrl; open.target = '_blank'; open.rel = 'noopener noreferrer'; actions.appendChild(open); } card.append(main, actions); return card; } function renderTasks() { const list = document.getElementById('taskList'); const tasks = getFilteredTasks(); list.innerHTML = ''; list.classList.toggle('empty', tasks.length === 0); if (!tasks.length) { const empty = el('div', 'task-empty'); const icon = el('div', 'task-empty-icon'); icon.innerHTML = ''; const text = el('p', '', state.loading ? '正在加载任务' : '暂无匹配任务'); const help = el('small', '', state.loading ? '请稍候' : '提交生成任务后会自动显示在这里'); empty.append(icon, text, help); list.appendChild(empty); return; } tasks.forEach((task) => list.appendChild(buildTaskCard(task))); } function render() { renderSummary(); renderTasks(); } async function loadTasks() { if (state.loading) return; state.loading = true; render(); try { if (!state.root) { const rootResponse = await fetch('/api/default-root'); const rootData = await rootResponse.json(); state.root = rootData.root; } const url = `/api/tasks?root=${encodeURIComponent(state.root)}`; const response = await fetch(url); const data = await response.json(); if (!response.ok) throw new Error(data.error || '任务列表加载失败'); state.tasks = data.tasks || []; } catch (error) { state.tasks = []; document.getElementById('taskSummary').textContent = error.message; } finally { state.loading = false; render(); } } function bindEvents() { document.getElementById('btnRefreshTasks').addEventListener('click', loadTasks); document.getElementById('taskFilters').addEventListener('click', (event) => { const button = event.target.closest('[data-filter]'); if (!button) return; state.filter = button.dataset.filter; document.querySelectorAll('#taskFilters .history-filter').forEach((item) => { item.classList.toggle('active', item === button); }); renderTasks(); }); } window.shanTasksInit = function () { if (!state.initialized) { state.initialized = true; bindEvents(); } loadTasks(); if (!state.timer) { state.timer = setInterval(() => { if (document.getElementById('viewTasks').classList.contains('active')) loadTasks(); }, 3000); } }; })();