import React, { useCallback, useMemo, useState } from "react"; import { Check, ChevronDown } from "lucide-react"; import { Trans, useTranslation } from "react-i18next"; import type { ProjectSession, LLMProvider, ProviderModelsDefinition, } from "../../../../types/app"; import SessionProviderLogo from "../../../llm-logo-provider/SessionProviderLogo"; import { NextTaskBanner } from "../../../task-master"; import { Dialog, DialogTrigger, DialogContent, DialogTitle, Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Card, } from "../../../../shared/view/ui"; const PROVIDER_META: { id: LLMProvider; name: string }[] = [ { id: "claude", name: "Anthropic" }, { id: "codex", name: "OpenAI" }, { id: "gemini", name: "Google" }, { id: "cursor", name: "Cursor" }, { id: "opencode", name: "OpenCode" }, ]; const MOD_KEY = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘" : "Ctrl"; type ProviderSelectionEmptyStateProps = { selectedSession: ProjectSession | null; currentSessionId: string | null; provider: LLMProvider; setProvider: (next: LLMProvider) => void; textareaRef: React.RefObject; claudeModel: string; setClaudeModel: (model: string) => void; cursorModel: string; setCursorModel: (model: string) => void; codexModel: string; setCodexModel: (model: string) => void; geminiModel: string; setGeminiModel: (model: string) => void; opencodeModel: string; setOpenCodeModel: (model: string) => void; providerModelCatalog: Partial>; providerModelsLoading: boolean; tasksEnabled: boolean; isTaskMasterInstalled: boolean | null; onShowAllTasks?: (() => void) | null; setInput: React.Dispatch>; }; type ProviderGroup = { id: LLMProvider; name: string; models: { value: string; label: string; description?: string }[]; }; function getModelConfig( p: LLMProvider, catalog: Partial>, ): ProviderModelsDefinition { const entry = catalog[p]; return entry ?? { OPTIONS: [], DEFAULT: "" }; } function getCurrentModel( p: LLMProvider, c: string, cu: string, co: string, g: string, o: string, ) { if (p === "claude") return c; if (p === "codex") return co; if (p === "gemini") return g; if (p === "opencode") return o; return cu; } function getProviderDisplayName(p: LLMProvider) { if (p === "claude") return "Claude"; if (p === "cursor") return "Cursor"; if (p === "codex") return "Codex"; if (p === "opencode") return "OpenCode"; return "Gemini"; } export default function ProviderSelectionEmptyState({ selectedSession, currentSessionId, provider, setProvider, textareaRef, claudeModel, setClaudeModel, cursorModel, setCursorModel, codexModel, setCodexModel, geminiModel, setGeminiModel, opencodeModel, setOpenCodeModel, providerModelCatalog, providerModelsLoading, tasksEnabled, isTaskMasterInstalled, onShowAllTasks, setInput, }: ProviderSelectionEmptyStateProps) { const { t } = useTranslation("chat"); const [dialogOpen, setDialogOpen] = useState(false); const visibleProviderGroups = useMemo(() => { return PROVIDER_META.map((p) => ({ id: p.id, name: p.name, models: providerModelCatalog[p.id]?.OPTIONS ?? [], })); }, [providerModelCatalog]); const nextTaskPrompt = t("tasks.nextTaskPrompt", { defaultValue: "Start the next task", }); const currentModel = getCurrentModel( provider, claudeModel, cursorModel, codexModel, geminiModel, opencodeModel, ); const currentModelLabel = useMemo(() => { const config = getModelConfig(provider, providerModelCatalog); const found = config.OPTIONS.find( (o: { value: string; label: string }) => o.value === currentModel, ); return found?.label || currentModel; }, [provider, currentModel, providerModelCatalog]); const setModelForProvider = useCallback( (providerId: LLMProvider, modelValue: string) => { if (providerId === "claude") { setClaudeModel(modelValue); localStorage.setItem("claude-model", modelValue); } else if (providerId === "codex") { setCodexModel(modelValue); localStorage.setItem("codex-model", modelValue); } else if (providerId === "gemini") { setGeminiModel(modelValue); localStorage.setItem("gemini-model", modelValue); } else if (providerId === "opencode") { setOpenCodeModel(modelValue); localStorage.setItem("opencode-model", modelValue); } else { setCursorModel(modelValue); localStorage.setItem("cursor-model", modelValue); } }, [setClaudeModel, setCursorModel, setCodexModel, setGeminiModel, setOpenCodeModel], ); const handleModelSelect = useCallback( (providerId: LLMProvider, modelValue: string) => { setProvider(providerId); localStorage.setItem("selected-provider", providerId); setModelForProvider(providerId, modelValue); setDialogOpen(false); setTimeout(() => textareaRef.current?.focus(), 100); }, [setProvider, setModelForProvider, textareaRef], ); if (!selectedSession && !currentSessionId) { return (

{t("providerSelection.title")}

{t("providerSelection.description")}

{getProviderDisplayName(provider)} · {currentModelLabel}

{t("providerSelection.clickToChange", { defaultValue: "Click to change model", })}

Model Selector

Choose a model

{t("providerSelection.noModelsFound", { defaultValue: "No models found.", })} {visibleProviderGroups.map((group, idx) => ( 0 ? "border-t border-border/40 [&_[cmdk-group-heading]]:mt-1 [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider" : "[&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider" } heading={ {group.name} } > {group.models.length === 0 && providerModelsLoading ? ( {t("providerSelection.loadingModels", { defaultValue: "Loading models…" })} ) : null} {group.models.map((model) => { const isSelected = provider === group.id && currentModel === model.value; return ( handleModelSelect(group.id, model.value)} className="ml-4 border-l border-border/40 pl-4" >
{model.label}
{model.description && (
{model.description}
)}
{isSelected && ( )}
); })}
))}

{ { claude: t("providerSelection.readyPrompt.claude", { model: claudeModel, }), cursor: t("providerSelection.readyPrompt.cursor", { model: cursorModel, }), codex: t("providerSelection.readyPrompt.codex", { model: codexModel, }), gemini: t("providerSelection.readyPrompt.gemini", { model: geminiModel, }), opencode: t("providerSelection.readyPrompt.opencode", { model: opencodeModel, defaultValue: "Ready with OpenCode {{model}}", }), }[provider] }

), }} />

{provider && tasksEnabled && isTaskMasterInstalled && (
setInput(nextTaskPrompt)} onShowAllTasks={onShowAllTasks} />
)}
); } if (selectedSession) { return (

{t("session.continue.title")}

{t("session.continue.description")}

{tasksEnabled && isTaskMasterInstalled && (
setInput(nextTaskPrompt)} onShowAllTasks={onShowAllTasks} />
)}
); } return null; }