import type { AgentCheckConfig, ScheduledJob, ScheduleSpecInput } from "@umt/contracts"; import { History, LoaderCircle, Pencil, Play, RotateCcw, SkipForward, Trash2 } from "lucide-react"; import { useEffect, useRef } from "react"; import { toast } from "sonner"; import { useDeleteSchedule, useRunScheduleNow, useSkipSchedule, useUpdateSchedule } from "~/api/schedules.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Switch } from "~/components/ui/switch.tsx"; import { errorMessage } from "~/lib/api.ts"; import { formatRelative } from "~/lib/labels.ts"; import { cn } from "~/lib/utils.ts"; import { IntervalEditor } from "./interval-editor.tsx"; import { CHECK_TRIGGER_LABEL, countdown, formatMs, RUN_STATUS } from "./labels.ts"; /** 下次运行一栏:运行中 > 排队中 > 已停用 > 倒计时 */ function NextRun({ job, now }: { job: ScheduledJob; now: number }) { if (job.runningSince) return ( 运行中 · 已 {formatMs(Math.max(0, now - new Date(job.runningSince).getTime()))} ); if (job.runRequestedAt) return 排队中; if (!job.available) return 未配置; if (!job.enabled) return 已停用; if (!job.nextRunAt) return 待调度; return {countdown(job.nextRunAt, now)}; } function AgentCheckSummary({ config }: { config: Partial }) { const budget = config.budget ?? {}; const allow = config.capabilityAllowlist ?? []; return (

{config.goalTemplate ?? "—"}

条件:{CHECK_TRIGGER_LABEL[config.triggerCondition ?? "always"]} 预算:{budget.maxToolCalls ? `${budget.maxToolCalls} 次调用` : "默认"} {budget.maxDurationSec ? ` · ${budget.maxDurationSec} 秒` : ""} 能力:{allow.length ? `${allow.length} 项` : "全部只读能力"} {config.allowRecover ? 允许授权内恢复 : null} {config.reportAsDecision ? 写入待处理 : null}
); } export function ScheduleCard({ job, now, onHistory, onEdit, highlight = false, }: { job: ScheduledJob; now: number; onHistory: (job: ScheduledJob) => void; onEdit: (job: ScheduledJob) => void; /** 从任务流跳来时高亮并滚到可见 */ highlight?: boolean; }) { const update = useUpdateSchedule(); const runNow = useRunScheduleNow(); const skip = useSkipSchedule(); const del = useDeleteSchedule(); const ref = useRef(null); useEffect(() => { if (highlight) ref.current?.scrollIntoView({ block: "center" }); }, [highlight]); const busy = !!job.runningSince || !!job.runRequestedAt; const last = job.lastStatus ? RUN_STATUS[job.lastStatus] : null; const patch = (body: Parameters[0]["body"], ok: string) => update.mutate( { id: job.id, body }, { onSuccess: () => toast.success(ok), onError: (e) => toast.error(errorMessage(e)) }, ); return (

{job.name}

{job.description ? (

{job.description}

) : null}
patch({ enabled: v }, v ? "已启用" : "已停用")} />
{job.kind === "agent_check" ? } /> : null}
间隔
patch({ schedule }, "间隔已更新")} />
上次
{last ? {last.label} : 未运行} {job.lastRunAt ? {formatRelative(job.lastRunAt)} : null} {job.lastDurationMs !== null ? 耗时 {formatMs(job.lastDurationMs)} : null}
下次
{job.lastStatus === "failed" && job.lastError ? (
{job.lastError}

{job.lastError}

) : null}
); }