import { useState, useEffect, useCallback } from "react"; import { fetchModels, updateTask } from "../api"; import type { ModelInfo } from "../api"; import type { Task, TaskDetail } from "@kb/core"; import type { ToastType } from "../hooks/useToast"; import { CustomModelDropdown } from "./CustomModelDropdown"; import { ProviderIcon } from "./ProviderIcon"; interface ModelSelectorTabProps { task: Task | TaskDetail; addToast: (message: string, type?: ToastType) => void; } export function ModelSelectorTab({ task, addToast }: ModelSelectorTabProps) { const [availableModels, setAvailableModels] = useState([]); const [modelsLoading, setModelsLoading] = useState(false); const [modelsError, setModelsError] = useState(null); // Local state for selections (not saved until user clicks Save) const [executorProvider, setExecutorProvider] = useState(task.modelProvider); const [executorModelId, setExecutorModelId] = useState(task.modelId); const [validatorProvider, setValidatorProvider] = useState(task.validatorModelProvider); const [validatorModelId, setValidatorModelId] = useState(task.validatorModelId); const [isSaving, setIsSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); // Load available models on mount useEffect(() => { setModelsLoading(true); setModelsError(null); fetchModels() .then((models) => { setAvailableModels(models); }) .catch((err) => { setModelsError(err.message || "Failed to load models"); }) .finally(() => { setModelsLoading(false); }); }, []); // Track if selections differ from task's saved values useEffect(() => { const executorChanged = executorProvider !== task.modelProvider || executorModelId !== task.modelId; const validatorChanged = validatorProvider !== task.validatorModelProvider || validatorModelId !== task.validatorModelId; setHasChanges(executorChanged || validatorChanged); }, [executorProvider, executorModelId, validatorProvider, validatorModelId, task]); // Build combobox values (provider/id combination or empty for default) const executorValue = executorProvider && executorModelId ? `${executorProvider}/${executorModelId}` : ""; const validatorValue = validatorProvider && validatorModelId ? `${validatorProvider}/${validatorModelId}` : ""; const handleExecutorChange = useCallback((value: string) => { if (!value) { setExecutorProvider(undefined); setExecutorModelId(undefined); } else { const slashIdx = value.indexOf("/"); setExecutorProvider(value.slice(0, slashIdx)); setExecutorModelId(value.slice(slashIdx + 1)); } }, []); const handleValidatorChange = useCallback((value: string) => { if (!value) { setValidatorProvider(undefined); setValidatorModelId(undefined); } else { const slashIdx = value.indexOf("/"); setValidatorProvider(value.slice(0, slashIdx)); setValidatorModelId(value.slice(slashIdx + 1)); } }, []); const handleSave = useCallback(async () => { setIsSaving(true); try { await updateTask(task.id, { modelProvider: executorProvider, modelId: executorModelId, validatorModelProvider: validatorProvider, validatorModelId: validatorModelId, }); addToast("Model settings saved", "success"); setHasChanges(false); } catch (err: any) { addToast(err.message || "Failed to save model settings", "error"); } finally { setIsSaving(false); } }, [task.id, executorProvider, executorModelId, validatorProvider, validatorModelId, addToast]); const handleReset = useCallback(() => { setExecutorProvider(task.modelProvider); setExecutorModelId(task.modelId); setValidatorProvider(task.validatorModelProvider); setValidatorModelId(task.validatorModelId); }, [task]); // Check if using defaults (both provider and modelId are undefined) const executorUsingDefault = !task.modelProvider && !task.modelId; const validatorUsingDefault = !task.validatorModelProvider && !task.validatorModelId; return (

Model Configuration

Override the AI models used for this task. When not specified, global default settings are used.

{modelsLoading ? (
Loading available models…
) : modelsError ? (
Error loading models: {modelsError}
) : availableModels.length === 0 ? (
No models available. Configure authentication in Settings to enable model selection.
) : ( <> {/* Executor Model Selector */}
{executorUsingDefault ? ( Using default ) : ( {task.modelProvider && } {task.modelProvider}/{task.modelId} )}
The AI model used to implement this task.
{/* Validator Model Selector */}
{validatorUsingDefault ? ( Using default ) : ( {task.validatorModelProvider && } {task.validatorModelProvider}/{task.validatorModelId} )}
The AI model used to review code and plans for this task.
{/* Action buttons */}
{!hasChanges && (
{executorUsingDefault && validatorUsingDefault ? "Using global default models." : "Model settings are up to date."}
)} )}
); }