import { appPath } from "@agent-native/core/client/api-path"; import { writeClipboardText } from "@agent-native/core/client/clipboard"; import { useActionQuery } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconAlertTriangle, IconCheck, IconCopy, IconClock, IconExternalLink, IconPlayerPause, IconPlayerPlay, } from "@tabler/icons-react"; import { useState } from "react"; import { Link } from "react-router"; import { automationScopeLabel, automationStatus, automationTarget, } from "../lib/automation-display"; import { automationRunScope, type DispatchAutomationItem, } from "../lib/automations"; import { ActionQueryError } from "./action-query-error"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { Skeleton } from "./ui/skeleton"; interface AutomationRun { id: string; runId: string | null; threadId: string | null; status: "running" | "success" | "error" | "interrupted"; startedAt: number; finishedAt: number | null; error: string | null; } export interface AutomationDetailsPanelProps { automation: DispatchAutomationItem; isToggling?: boolean; onToggle: () => void; } function formatTimestamp(value: number | string | null | undefined): string { if (value == null) return "Not recorded"; const date = new Date(value); return Number.isNaN(date.getTime()) ? "Unknown time" : date.toLocaleString(); } function formatDuration(run: AutomationRun): string | null { if (!run.finishedAt) return null; const seconds = Math.max( 0, Math.round((run.finishedAt - run.startedAt) / 1000), ); return `${seconds}s`; } function runDebugPath(run: AutomationRun): string | null { const params = new URLSearchParams(); if (run.runId) params.set("runId", run.runId); else if (run.threadId) params.set("threadId", run.threadId); else return null; return `/admin/thread-debug?${params.toString()}`; } function runStatusVariant( status: AutomationRun["status"], ): "default" | "destructive" | "outline" { if (status === "error") return "destructive"; if (status === "success") return "default"; return "outline"; } function DetailField({ label, value, mono = false, }: { label: string; value: string; mono?: boolean; }) { return (
{label}
{value}
); } function runAsLabel(value: DispatchAutomationItem["runAs"]): string { if (value === "creator") return "Creator identity"; if (value === "shared") return "Shared identity"; return "Default identity"; } export function AutomationDetailsPanel({ automation, isToggling = false, onToggle, }: AutomationDetailsPanelProps) { const t = useT(); const runsQuery = useActionQuery( "list-automation-runs", { name: automation.name, scope: automationRunScope(automation), appId: automation.appId ?? "dispatch", }, { staleTime: 5_000 }, ); const runs = runsQuery.data ?? []; const status = automationStatus(automation); const isScheduled = automation.triggerType === "schedule"; const [copied, setCopied] = useState(false); const webhookUrl = automation.webhookPath && typeof window !== "undefined" ? window.location.origin + appPath(automation.webhookPath) : null; const triggerLabel = automation.triggerType === "webhook" ? t("jobs.webhook", { defaultValue: "Webhook" }) : isScheduled ? "Schedule" : "Event"; const prompt = automation.body?.trim(); const hasExecutionTarget = Boolean( automation.executionHostId || automation.executionEngine || automation.executionCwd, ); const hasDelivery = Boolean( automation.deliveryPlatform || automation.deliveryDestination || automation.deliveryThreadRef, ); return ( ); }