import { useCallback, useEffect, useState } from 'react' import { Button } from '@/components/ui/button' import { getTerminalBackends, selectTerminalBackend } from '@/hermes' import { useI18n } from '@/i18n' import { AlertTriangle, Check, Loader2, RefreshCw } from '@/lib/icons' import { cn } from '@/lib/utils' import { notify, notifyError } from '@/store/notifications' import type { TerminalBackendInfo, TerminalBackendsResponse } from '@/types/hermes' import { Pill } from './primitives' interface TerminalBackendPanelProps { /** Re-read the parent toolset list after a backend change so any derived * pills stay in sync. */ onConfiguredChange?: () => void } function StatusPill({ backend }: { backend: TerminalBackendInfo }) { const { t } = useI18n() const copy = t.settings.toolsets.terminalBackend if (backend.status === 'ready') { return ( {copy.ready} ) } return ( {backend.status === 'needs_setup' ? copy.needsSetup : copy.unavailable} ) } /** * Terminal execution backend picker — the Capabilities-tab counterpart of the * `terminal.backend` config enum. Each backend row carries a live health probe * (Docker daemon reachable, SSH host configured, Modal/Daytona credentials * present) so users see Ready / Needs-setup guidance instead of a bare * dropdown. Selecting a needs-setup backend is allowed — the row shows what's * missing rather than blocking, matching the CLI configurator. */ export function TerminalBackendPanel({ onConfiguredChange }: TerminalBackendPanelProps) { const { t } = useI18n() const copy = t.settings.toolsets.terminalBackend const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [selecting, setSelecting] = useState(null) const refresh = useCallback(async () => { setLoading(true) try { setData(await getTerminalBackends()) } catch (err) { notifyError(err, copy.failedLoad) } finally { setLoading(false) } }, [copy.failedLoad]) useEffect(() => { void refresh() }, [refresh]) async function handleSelect(backend: TerminalBackendInfo) { if (backend.active || selecting) { return } setSelecting(backend.name) try { await selectTerminalBackend(backend.name) // Mirror the backend write locally so the active highlight tracks the // new selection without a refetch (probes are unchanged by a select). setData(current => current ? { ...current, active: backend.name, backends: current.backends.map(b => ({ ...b, active: b.name === backend.name })) } : current ) notify({ kind: 'success', title: copy.selectedTitle, message: copy.selectedMessage(backend.label) }) onConfiguredChange?.() } catch (err) { notifyError(err, copy.failedSelect(backend.label)) } finally { setSelecting(null) } } if (loading && !data) { return (
{copy.loading}
) } if (!data) { return null } return (
{copy.sectionTitle}
{data.backends.map(backend => ( ))}
) }