"use client"; import { forwardRef, useCallback, useState } from "react"; import { IconBox, IconLoader2 } from "@tabler/icons-react"; import { HoverCard, HoverCardContent, HoverCardTrigger } from "@kandev/ui/hover-card"; import { Button } from "@kandev/ui/button"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@kandev/ui/drawer"; import { getExecutorStatusIcon } from "@/lib/executor-icons"; import { TaskResetEnvConfirmDialog } from "./task-reset-env-confirm-dialog"; import type { StatusTone } from "./executor-environment-status"; import { usePrepareSummary } from "@/hooks/domains/session/use-prepare-summary"; import { useTaskEnvironment } from "@/hooks/domains/session/use-task-environment"; import { isPreparingPhase } from "@/lib/prepare/summarize"; import { useTouchDrawer } from "@/hooks/use-compact-task-chrome"; import { PrepareStatusSection } from "./executor-prepare-status"; import { ExecutorEnvironmentDisclosure } from "./executor-environment-disclosure"; import { useTranslation } from "react-i18next"; import { t } from "@/lib/i18n"; type ExecutorSettingsButtonProps = { taskId?: string | null; sessionId?: string | null; disabled?: boolean; }; export function ExecutorSettingsButton({ taskId, sessionId, disabled, }: ExecutorSettingsButtonProps) { const usesTouchDrawer = useTouchDrawer(); const [open, setOpen] = useState(false); const [resetDialogOpen, setResetDialogOpen] = useState(false); const prepare = usePrepareSummary(sessionId ?? null); const isPreparing = isPreparingPhase(prepare.phase); // Promote the foreground polling cadence while preparing so the icon flips // to "ready" without the user hovering over the trigger. const { env, container, ssh, kubernetes, kubernetesLoaded, kubernetesError, loading, refreshing, isResetting, reset, refresh, status, } = useTaskEnvironment(taskId, sessionId, open || isPreparing); const hasWorktreePath = Boolean(env?.worktree_path); const handleReset = useCallback( async (opts: { pushBranch: boolean }) => { const ok = await reset(opts); if (ok) { setResetDialogOpen(false); setOpen(false); } }, [reset], ); if (!taskId) return null; const executorType = env?.executor_type ?? null; const ariaLabel = computeAriaLabel(isPreparing, status); const content = ( <> setResetDialogOpen(true)} /> > ); return ( ); } type ExecutorDisclosureSurfaceProps = { touch: boolean; open: boolean; onOpenChange: (open: boolean) => void; content: React.ReactNode; trigger: Omit; resetDialog: React.ComponentProps; }; function ExecutorDisclosureSurface({ touch, open, onOpenChange, content, trigger, resetDialog, }: ExecutorDisclosureSurfaceProps) { const { t } = useTranslation(); if (touch) { return ( <> {t("task:executorSettings")} {trigger.status ? t("task:environmentStateCurrent", { state: trigger.status.label }) : t("task:executorEnvironmentIsUnavailable")} {t("common:close")} {content} > ); } return ( <> {content} > ); } type ExecutorTriggerProps = { ariaLabel: string; disabled?: boolean; executorType: string | null; loading: boolean; preparing: boolean; status: { label: string; tone: StatusTone } | null; touch?: boolean; expanded: boolean; } & Omit, "children">; const ExecutorTrigger = forwardRef( function ExecutorTrigger( { ariaLabel, disabled, executorType, loading, preparing, status, touch = false, expanded, ...triggerProps }, ref, ) { return ( ); }, ); function computeAriaLabel( preparing: boolean, status: { label: string; tone: StatusTone } | null, ): string { if (preparing) return t("task:executorSettingsPreparing"); if (status) return t("task:executorSettingsWithStatus", { status: status.label }); return t("task:executorSettings"); } function ExecutorButtonIcon({ executorType, preparing, hasError, }: { executorType: string | null; preparing: boolean; hasError: boolean; }) { if (preparing) { return ( ); } if (!executorType) { return ; } const { Icon, testId } = getExecutorStatusIcon(executorType, hasError); return ; } const DOT_CLASSES: Record = { running: "bg-green-500", stopped: "bg-zinc-500", warn: "bg-amber-500", error: "bg-red-500", neutral: "bg-muted-foreground", }; function ExecutorStatusDot({ status, loading, }: { status: { label: string; tone: StatusTone } | null; loading: boolean; }) { const { t } = useTranslation(); const tone = status?.tone ?? "neutral"; const label = status?.label ?? "not created"; return ( ); }