"use client"; import { useTranslation } from "react-i18next"; import type { TFunction } from "i18next"; import { IconAlertTriangle, IconLoader2, IconRefresh } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@kandev/ui/dialog"; import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@kandev/ui/drawer"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint"; import { canApproveAgentRuntimeUpdate, resolveRuntimeVersionPair, } from "@/lib/agent-runtime-update"; import type { AgentUpdateJob, AgentUpdatePreview, InstallJob } from "@/lib/api"; import type { RuntimeUpdate } from "@/lib/types/http"; import { useAgentUpdateDialogState } from "./use-agent-update-dialog-state"; const UPDATE_AGENT_KEY = "agents:updateAgent"; const ACTIVE_UPDATE_STATUSES = new Set([ "queued", "resolving", "updating", "refreshing", ]); // The job `status` values are the wire enum; only the phase labels are copy, so // they travel as catalog keys and resolve at render. const UPDATE_PHASE_KEYS: Partial> = { queued: "agents:updatePhaseQueued", resolving: "agents:updatePhaseResolving", updating: "agents:updatePhaseUpdating", refreshing: "agents:updatePhaseRefreshing", }; function updatePhase(t: TFunction, status: AgentUpdateJob["status"] | undefined): string | null { const key = status ? UPDATE_PHASE_KEYS[status] : undefined; return key ? t(key) : null; } function UpdateResult({ agentName, job }: { agentName: string; job?: AgentUpdateJob }) { const { t } = useTranslation(); if (!job) return null; const { versionsMatch } = resolveRuntimeVersionPair(null, job); if (job.status === "succeeded" && job.refresh_error) { return (

{t("agents:runtimeUpdatedRefreshFailed", { error: job.refresh_error })}

); } if (job.status === "succeeded") { return (

{versionsMatch ? t("agents:runtimeAlreadyUpToDate") : t("agents:runtimeUpdatedSuccess")}

); } if (job.status === "failed") { return (

{job.error || t("agents:runtimeUpdateFailed")}

); } return null; } type UpdateBodyProps = { agentName: string; preview: AgentUpdatePreview | null; loading: boolean; previewError: string | null; approveError: string | null; job?: AgentUpdateJob; onRetryPreview: () => void; }; function RuntimeVersionSummary({ preview, job, }: { preview: AgentUpdatePreview; job?: AgentUpdateJob; }) { const { t } = useTranslation(); const { currentVersion, targetVersion, versionsMatch } = resolveRuntimeVersionPair(preview, job); return (

{t("agents:runtimeVersion")}

{versionsMatch ? currentVersion : `${currentVersion} → ${targetVersion}`}

{versionsMatch && (

{t("agents:upToDate")}

)}
); } function UpdateBody({ agentName, preview, loading, previewError, approveError, job, onRetryPreview, }: UpdateBodyProps) { const { t } = useTranslation(); const phase = updatePhase(t, job?.status); return (
{loading && (

{t("agents:checkingLatestRuntimeVersion")}

)} {previewError && (

{previewError}

)} {approveError && (

{t("agents:unableToStartUpdate", { error: approveError })}

)} {preview && ( <>

{t("agents:runtimeUpdateExplainer")}

{t("agents:runtimeUpdateSessionsNote")}

{t("agents:commandThatWillRun")}

              {preview.command_string}
            
)} {phase && (

{phase}

)} {job?.output && (
          {job.output}
        
)}
); } type UpdateFooterProps = { agentName: string; preview: AgentUpdatePreview | null; previewError: string | null; job?: AgentUpdateJob; loading: boolean; starting: boolean; installInFlight: boolean; onApprove: () => void; onClose: () => void; mobile?: boolean; }; function UpdateFooter({ agentName, preview, previewError, job, loading, starting, installInFlight, onApprove, onClose, mobile, }: UpdateFooterProps) { const { t } = useTranslation(); const updateInFlight = Boolean(job && ACTIVE_UPDATE_STATUSES.has(job.status)); const canRetry = job?.status === "failed"; const canApprove = canApproveAgentRuntimeUpdate({ preview, job, previewError, loading, updateInFlight, starting, installInFlight, }); const showApprove = !job || canRetry; const content = ( <> {showApprove && ( )} ); if (mobile) { return ( {content} ); } return {content}; } function UpdateTrigger({ agentName, displayName, installInFlight, onOpen, }: { agentName: string; displayName: string; installInFlight: boolean; onOpen: () => void; }) { const { t } = useTranslation(); return ( {installInFlight ? t("agents:agentInstallationInProgress") : t(UPDATE_AGENT_KEY, { name: displayName })} ); } export function AgentRuntimeUpdateControl({ agentName, displayName, runtimeUpdate, job, installJob, onPreview, onUpdate, }: { agentName: string; displayName: string; runtimeUpdate: RuntimeUpdate; job?: AgentUpdateJob; installJob?: InstallJob; onPreview: (agentName: string) => Promise; onUpdate: (agentName: string) => Promise; }) { const { t } = useTranslation(); const { isMobile } = useResponsiveBreakpoint(); const { activeJob, approve, approveError, handleOpenChange, loading, loadPreview, open, preview, previewError, starting, } = useAgentUpdateDialogState({ agentName, job, onPreview, onUpdate }); const installInFlight = installJob?.status === "queued" || installJob?.status === "running"; if (!runtimeUpdate.supported) return null; const body = ( void loadPreview()} /> ); const footer = (mobile = false) => ( void approve()} onClose={() => handleOpenChange(false)} mobile={mobile} /> ); return ( <> handleOpenChange(true)} /> {isMobile ? ( {t(UPDATE_AGENT_KEY, { name: displayName })} {t("agents:reviewUpdateBeforeApplying")} {body} {footer(true)} ) : ( {t(UPDATE_AGENT_KEY, { name: displayName })} {t("agents:reviewUpdateBeforeApplying")} {body} {footer()} )} ); }