import { type ReactNode, useEffect } from 'react' import { IconCircleCheckFilled } from '@tabler/icons-react' import { Button } from '@/client/components/ui/button' import { DialogDescription, DialogTitle } from '@/client/components/ui/dialog' import { Tooltip, TooltipContent, TooltipTrigger } from '@/client/components/ui/tooltip' import { workspaceProviderIcon, workspaceTypeLabel } from '@/client/features/home/workspace-presentation' import { cn } from '@/client/lib/cn' import { useUiStore } from '@/client/store/ui' import { WORKSPACE_TYPE_ORDER } from '@/lib/workspace-types' import type { HarnessAvailability, WorkspaceType } from '@/lib/types' const WORKSPACE_AGENT_DESCRIPTION = 'Choose which agent moi will use to build your workspace' // Backends whose workspaces belong to an agent installed on the machine: moi // imports them (via discovery or `moi init`) and never creates one. const IMPORT_ONLY_DESCRIPTION: Partial> = { openclaw: 'To connect an OpenClaw agent, run', hermes: 'To connect a Hermes agent, run' } const IMPORT_ONLY_COMMAND: Partial> = { openclaw: 'moi openclaw init', hermes: 'moi hermes init' } const workspaceAgentDescription: Record = { 'claude-code': 'Anthropic', codex: 'OpenAI', openclaw: 'Open-source', hermes: 'Nous Research' } export type WorkspaceAgentOption = { type: WorkspaceType description: string disabled: boolean disabledReason?: string disabledCommand?: string } type WorkspaceAgentOptionsInput = { availability?: Partial> detectedTypes?: WorkspaceType[] } export function getWorkspaceAgentOptions({ availability, detectedTypes = [] }: WorkspaceAgentOptionsInput): WorkspaceAgentOption[] { return WORKSPACE_TYPE_ORDER.map(type => { const state = availability?.[type] const unavailableReason = state?.available === false ? state.reason : undefined const importOnlyReason = !detectedTypes.includes(type) ? IMPORT_ONLY_DESCRIPTION[type] : undefined const importOnlyCommand = !detectedTypes.includes(type) ? IMPORT_ONLY_COMMAND[type] : undefined const disabledReason = unavailableReason ?? importOnlyReason return { type, description: workspaceAgentDescription[type], disabled: Boolean(disabledReason), ...(disabledReason ? { disabledReason } : {}), ...(!unavailableReason && importOnlyCommand ? { disabledCommand: importOnlyCommand } : {}) } }) } export function resolveWorkspaceAgentSelection( options: WorkspaceAgentOption[], selectedType: WorkspaceType ): WorkspaceType | undefined { const selected = options.find(option => option.type === selectedType) if (selected && !selected.disabled) return selected.type return options.find(option => !option.disabled)?.type } type WorkspaceAgentStepProps = { title: string selectedType: WorkspaceType detectedTypes?: WorkspaceType[] availability?: Partial> isPending: boolean errorMessage?: string secondaryAction: ReactNode primaryLabel: string pendingLabel?: string onTypeChange: (type: WorkspaceType) => void onSubmit: () => void } export function WorkspaceAgentStep({ title, selectedType, detectedTypes, availability, isPending, errorMessage, secondaryAction, primaryLabel, pendingLabel, onTypeChange, onSubmit }: WorkspaceAgentStepProps) { const hasSentMessageFromMoi = useUiStore(state => state.hasSentMessageFromMoi) const options = getWorkspaceAgentOptions({ availability, detectedTypes }) const selectableType = resolveWorkspaceAgentSelection(options, selectedType) const selectedUnavailable = selectableType !== selectedType const subscriptionTip = selectableType === 'claude-code' ? 'Tip: you can use your existing Claude subscription in moi' : selectableType === 'codex' ? 'Tip: you can use your existing ChatGPT subscription in moi' : undefined useEffect(() => { if (selectableType && selectedUnavailable) onTypeChange(selectableType) }, [onTypeChange, selectableType, selectedUnavailable]) return (
{title} {WORKSPACE_AGENT_DESCRIPTION}
{!hasSentMessageFromMoi && subscriptionTip && (

{subscriptionTip}

)}
{errorMessage && (

{errorMessage}

)}
{secondaryAction}
) } type WorkspaceAgentSelectorProps = { options: WorkspaceAgentOption[] selectedType?: WorkspaceType onTypeChange: (type: WorkspaceType) => void } function WorkspaceAgentSelector({ options, selectedType, onTypeChange }: WorkspaceAgentSelectorProps) { return (
2 ? 'grid-cols-4' : 'grid-cols-2')} > {options.map(option => ( ))}
) } type WorkspaceAgentOptionButtonProps = { option: WorkspaceAgentOption selected: boolean onSelect: (type: WorkspaceType) => void } function WorkspaceAgentOptionButton({ option, selected, onSelect }: WorkspaceAgentOptionButtonProps) { const showDisabledTooltip = Boolean(option.disabledReason) const button = ( ) if (!showDisabledTooltip) return button return ( {option.disabledReason} {option.disabledCommand && ( <>
{option.disabledCommand} )}
) } type WorkspaceAgentIconProps = { type: WorkspaceType } function WorkspaceAgentIcon({ type }: WorkspaceAgentIconProps) { return }