import { useCallback, useEffect, useRef, useState } from "react";
import { api, JobRun } from "../api/haixun";
import { Badge, Button } from "./ui";
import { formatNano } from "../lib/format";
import { canCancelJob, canRetryJob } from "../lib/jobActions";
import { jobTemplateLabel } from "../lib/jobLabels";
import { jobStatusBadgeClass, jobStatusLabel } from "../lib/jobStatus";
import { AcIcon } from "./AcIcon";
import type { JobCreatedDetail } from "../lib/jobEvents";
const ACTIVE_STATUSES = ["running", "queued", "pending", "waiting_worker", "cancel_requested"];
const PANEL_STATUSES = [...ACTIVE_STATUSES, "failed"];
function nowNano() {
return Date.now() * 1_000_000;
}
function optimisticJob(detail: JobCreatedDetail): JobRun {
return {
id: detail.jobId,
template_type: detail.templateType || detail.label || "任務",
status: detail.status || "queued",
progress: { percentage: 0, summary: "剛建立,等待執行…" },
create_at: nowNano(),
update_at: nowNano()
};
}
function JobMonitorItem({
job,
actionLoading,
onCancel,
onRetry,
showError
}: {
job: JobRun;
actionLoading: boolean;
onCancel: () => void;
onRetry: () => void;
showError: boolean;
}) {
return (
{jobTemplateLabel(job.template_type)}
{jobStatusLabel(job.status)}
{job.progress ? (
{job.progress.percentage ?? 0}%
{job.progress.summary ? ` · ${job.progress.summary}` : ""}
) : null}
{showError && job.error?.trim() ? (
{job.error.trim()}
) : null}
{formatNano(job.update_at)}
{canCancelJob(job) ? (
) : null}
{canRetryJob(job) ? (
) : null}
);
}
export function JobMonitor() {
const [jobs, setJobs] = useState([]);
const [open, setOpen] = useState(false);
const [actionJobId, setActionJobId] = useState("");
const [hot, setHot] = useState(false);
const pendingRef = useRef