import type { EngineBusinessKind, EngineRun, EngineTriggeredBy } from "@umt/contracts"; import { consolePath } from "~/components/console/nav.ts"; import type { StatusTone } from "~/components/console/status-badge.tsx"; // 统一任务流的标签:引擎 run + business(W9-I)。一条 run = 一条任务。 export const BUSINESS_KIND_LABEL: Record = { subscription: "订阅", download: "下载", organization: "整理", agent: "智能体", schedule: "定时任务", feed: "RSS", site: "站点", decision: "待处理事项", media: "作品", }; export const BUSINESS_KINDS = Object.keys(BUSINESS_KIND_LABEL) as EngineBusinessKind[]; /** 来源:四类(定时/事件/手动/Agent);API Key 调用算手动 */ export type TaskSource = "schedule" | "event" | "manual" | "agent"; export const SOURCE_LABEL: Record = { schedule: "定时", event: "事件", manual: "手动", agent: "智能体", }; /** 来源 → 服务端 triggeredBy 筛选值(逗号分隔多值) */ export const SOURCE_TRIGGERED_BY: Record = { schedule: "schedule", event: "event", manual: "user,api_key", agent: "agent", }; export function sourceOf(t: EngineTriggeredBy | null | undefined): TaskSource | null { if (!t) return null; if (t === "user" || t === "api_key") return "manual"; return t; } /** 引擎工作流名 → 做什么 */ export const WORKFLOW_LABEL: Record = { ingestFeed: "抓取 Feed", resolveObservation: "识别资源", evaluateAcquisition: "评估候选", submitDownload: "提交下载", verifyAndImport: "核验入库", refreshMetadata: "刷新元数据", reconcileLibrary: "文件核对", cleanupDownloadCache: "清理缓存", resolveDecision: "处理待处理事项", runAgent: "智能体会话", snapshotSite: "站点抓取", refreshSiteAccount: "刷新站点账号", searchSubscription: "订阅搜索", purgeTrash: "清理回收站", refreshPiTokens: "刷新模型服务登录", }; export const workflowLabel = (name: string) => WORKFLOW_LABEL[name] ?? name; /** 引擎状态 → 任务状态 */ const RUN_STATUS: Record = { pending: { label: "排队", tone: "neutral" }, running: { label: "进行中", tone: "neutral" }, sleeping: { label: "等待", tone: "neutral" }, completed: { label: "完成", tone: "success" }, succeeded: { label: "完成", tone: "success" }, failed: { label: "失败", tone: "destructive" }, canceled: { label: "已取消", tone: "warning" }, }; export const runStatus = (s: string) => RUN_STATUS[s] ?? { label: s, tone: "neutral" as StatusTone }; /** * 任务行状态:引擎 run 正常结束但业务结果不是成功时以业务结果为准(Agent 会话超出预算 / 失败 / 已停止), * 不把"引擎跑完"显示成「完成」 */ const OUTCOME_STATUS: Record = { budget_exceeded: { label: "超出预算", tone: "warning" }, failed: { label: "失败", tone: "destructive" }, cancelled: { label: "已停止", tone: "warning" }, waiting: { label: "等待", tone: "neutral" }, }; export function engineRunStatus(r: Pick) { const outcome = r.business?.outcome; if (outcome && (r.status === "completed" || r.status === "succeeded") && OUTCOME_STATUS[outcome]) return OUTCOME_STATUS[outcome]; return runStatus(r.status); } export const RUN_STATUS_OPTIONS = ["pending", "running", "completed", "failed", "canceled"].map((value) => ({ value, label: runStatus(value).label, })); /** 行标题:业务对象名 > 业务类型 + 短 id > 工作流做什么 */ export function runTitle(r: Pick): string { const b = r.business; if (b?.title) return b.title; if (b) return BUSINESS_KIND_LABEL[b.kind]; return workflowLabel(r.workflowName); } /** 业务对象的"家"页面 */ export function businessLink(b: EngineRun["business"], input?: unknown): { to: string; label: string } | null { if (!b) return null; const id = encodeURIComponent(b.refId); switch (b.kind) { case "subscription": return { to: consolePath("subscriptions"), label: "订阅" }; case "download": { // 还没提交到下载器时 refId 是计划 id(下载面板按 ?plan= 打开对应行) const i = (input ?? {}) as { downloadTaskId?: unknown; acquisitionPlanId?: unknown }; if (!i.downloadTaskId && i.acquisitionPlanId === b.refId) return { to: `${consolePath("downloads")}?plan=${id}`, label: "下载" }; return { to: `/console/downloads/${id}`, label: "下载任务" }; } case "organization": return { to: `/console/organize/${id}`, label: "整理记录" }; case "agent": return { to: `/console/agent/${id}`, label: "智能体会话" }; case "schedule": return { to: `/console/schedules?job=${id}`, label: "定时任务" }; case "feed": // RSS 属于站点(IA-2):旧 RSS 页按 ?feed= 查所属站点后跳到站点的 RSS Tab return { to: `${consolePath("sources")}?feed=${encodeURIComponent(id)}`, label: "RSS" }; case "site": return { to: `/console/sites/${id}`, label: "站点" }; case "decision": return { to: consolePath("inbox"), label: "待处理" }; case "media": return { to: `/console/library/${id}`, label: "作品" }; default: return null; } } export const ENGINE_ACTIVE = new Set(["pending", "running", "sleeping"]);