import { useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; import type { TFunction } from "i18next"; import type { ModelPreset } from "@fusion/core"; import type { ModelInfo } from "../api"; import { applyPresetToSelection } from "../utils/modelPresets"; import { CustomModelDropdown } from "./CustomModelDropdown"; import { Brain, X } from "lucide-react"; interface ModelSelectionModalProps { isOpen: boolean; onClose: () => void; models: ModelInfo[]; executorValue: string; validatorValue: string; planningValue?: string; onExecutorChange: (value: string) => void; onValidatorChange: (value: string) => void; onPlanningChange?: (value: string) => void; modelsLoading: boolean; modelsError: string | null; onRetry: () => void; favoriteProviders?: string[]; onToggleFavorite?: (provider: string) => void; favoriteModels?: string[]; onToggleModelFavorite?: (modelId: string) => void; /** Available model presets for quick selection. When provided, a preset selector is shown. */ presets?: ModelPreset[]; /** Currently selected preset ID, or undefined if no preset is active. */ selectedPresetId?: string; /** Called when the user selects a preset or reverts to default/custom mode. */ onPresetChange?: (presetId: string | undefined) => void; } function getModelBadgeLabel(models: ModelInfo[], value: string, t: TFunction<"app">): string { if (!value) return t("modelSelection.usingDefault", "Using default"); const slashIdx = value.indexOf("/"); if (slashIdx === -1) return value; const provider = value.slice(0, slashIdx); const modelId = value.slice(slashIdx + 1); const matched = models.find((m) => m.provider === provider && m.id === modelId); return matched ? `${matched.provider}/${matched.id}` : `${provider}/${modelId}`; } export function ModelSelectionModal({ isOpen, onClose, models, executorValue, validatorValue, planningValue = "", onExecutorChange, onValidatorChange, onPlanningChange, modelsLoading, modelsError, onRetry, favoriteProviders = [], onToggleFavorite, favoriteModels = [], onToggleModelFavorite, presets, selectedPresetId, onPresetChange, }: ModelSelectionModalProps) { const { t } = useTranslation("app"); // Handle Escape key useEffect(() => { if (!isOpen) return; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { event.preventDefault(); onClose(); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [isOpen, onClose]); // Handle overlay click const handleOverlayClick = useCallback( (event: React.MouseEvent) => { if (event.target === event.currentTarget) { onClose(); } }, [onClose], ); const showPresets = !!(presets && presets.length > 0 && onPresetChange); const selectedPreset = presets?.find((p) => p.id === selectedPresetId); const handlePresetSelect = useCallback( (value: string) => { if (!onPresetChange) return; if (value === "default") { onPresetChange(undefined); onExecutorChange(""); onValidatorChange(""); onPlanningChange?.(""); return; } if (value === "custom") { onPresetChange(undefined); return; } const preset = presets?.find((p) => p.id === value); if (preset) { const selection = applyPresetToSelection(preset); onExecutorChange(selection.executorValue); onValidatorChange(selection.validatorValue); onPresetChange(preset.id); } }, [onPresetChange, presets, onExecutorChange, onValidatorChange], ); const handleExecutorChange = useCallback( (value: string) => { // Manual model selection clears preset mode if (onPresetChange && selectedPresetId) { onPresetChange(undefined); } onExecutorChange(value); }, [onPresetChange, selectedPresetId, onExecutorChange], ); const handleValidatorChange = useCallback( (value: string) => { // Manual model selection clears preset mode if (onPresetChange && selectedPresetId) { onPresetChange(undefined); } onValidatorChange(value); }, [onPresetChange, selectedPresetId, onValidatorChange], ); const handlePlanningChange = useCallback( (value: string) => { // Manual model selection clears preset mode if (onPresetChange && selectedPresetId) { onPresetChange(undefined); } onPlanningChange?.(value); }, [onPresetChange, selectedPresetId, onPlanningChange], ); if (!isOpen) return null; const hasExecutorOverride = Boolean(executorValue); const hasValidatorOverride = Boolean(validatorValue); const hasPlanningOverride = Boolean(planningValue); return (

{t("modelSelection.title", "Select Models")}

{modelsLoading ? (

{t("modelSelection.loading", "Loading models…")}

) : modelsError ? (
{modelsError}
) : models.length === 0 ? (
{t("modelSelection.noModels", "No models available. Configure authentication in Settings to enable model selection.")}
) : (

{t("modelSelection.choose", "Choose models for this task. If not selected, default models will be used.")}

{showPresets && (
{selectedPreset ? selectedPreset.name : t("modelSelection.useDefault", "Use default")}
)} {onPlanningChange ? (
{getModelBadgeLabel(models, planningValue, t)}
) : null}
{getModelBadgeLabel(models, executorValue, t)}
{getModelBadgeLabel(models, validatorValue, t)}
)}
); }