// 专业软件任务中心:用户级轮询、跨对话状态、取消与终态通知。 import { api } from "./api.js"; import { state } from "./state.js"; import { $ } from "./dom.js"; import { escapeHtml } from "./format.js"; import { selectTask } from "./chat.js"; import { dialogConfirm, message } from "./dialog.js"; const ACTIVE = new Set(["queued", "offered", "dispatched", "running", "disconnected", "cancelling"]); const TERMINAL = new Set(["succeeded", "failed", "cancelled"]); const POLL_ACTIVE_MS = 4000; const POLL_IDLE_MS = 30000; let timer = null; let known = new Map(); let jobs = []; let expanded = false; let initialized = false; const statusLabel = { queued: "等待计算节点", offered: "正在分配节点", dispatched: "节点已接收", running: "正在执行", disconnected: "节点连接中断", cancelling: "正在停止", succeeded: "已完成", failed: "失败", cancelled: "已取消", }; const stageLabel = { accepted: "节点已接收", waiting_input: "正在下载输入文件", ready_to_run: "准备软件环境", origin_running: "Origin 正在生成图表", uploading_outputs: "正在上传结果", cancel_requested: "停止请求已发送", terminal: "任务已结束", }; export function initSoftwareJobs() { if (initialized || !$("software-job-center")) return; initialized = true; $("software-job-toggle").onclick = () => { expanded = !expanded; render(); }; document.addEventListener("click", (event) => { const center = $("software-job-center"); if (expanded && center && !center.contains(event.target)) { expanded = false; render(); } }); refreshSoftwareJobs(); } export async function refreshSoftwareJobs() { if (!state.token) return; try { const data = await api("GET", "/v1/software-jobs?limit=50"); const next = data.results || []; next.forEach((job) => { const previous = known.get(job.job_id); if (previous && ACTIVE.has(previous) && TERMINAL.has(job.status)) notifyTerminal(job); known.set(job.job_id, job.status); }); jobs = next; render(); } catch (_) { /* 后台刷新失败静默,下轮恢复 */ } schedule(); } function schedule() { if (timer) clearTimeout(timer); const delay = jobs.some((job) => ACTIVE.has(job.status)) ? POLL_ACTIVE_MS : POLL_IDLE_MS; timer = setTimeout(refreshSoftwareJobs, document.hidden ? Math.max(delay, 30000) : delay); } document.addEventListener("visibilitychange", () => { if (!document.hidden && state.token) refreshSoftwareJobs(); else schedule(); }); function render() { const center = $("software-job-center"); const panel = $("software-job-panel"); const active = jobs.filter((job) => ACTIVE.has(job.status)); const recent = jobs.slice(0, 10); center.classList.toggle("show", active.length > 0 || recent.length > 0); center.classList.toggle("expanded", expanded); $("software-job-count").textContent = active.length ? `${active.length} 运行中` : `${jobs.length} 条`; panel.hidden = !expanded; if (!expanded) return; panel.innerHTML = recent.length ? recent.map(jobCard).join("") : '