"use client" import { Check, Eye, EyeOff, Loader2, Plus, Trash2, X } from "lucide-react" import { useCallback, useState } from "react" import { Button } from "@/components/ui/button" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { ScrollArea } from "@/components/ui/scroll-area" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Switch } from "@/components/ui/switch" import { useDictionary } from "@/hooks/use-dictionary" import type { UseModelConfigReturn } from "@/hooks/use-model-config" import type { ModelConfig, ProviderConfig, ProviderName, } from "@/lib/types/model-config" import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config" interface ModelConfigDialogProps { open: boolean onOpenChange: (open: boolean) => void modelConfig: UseModelConfigReturn } type ValidationStatus = "idle" | "validating" | "success" | "error" export function ModelConfigDialog({ open, onOpenChange, modelConfig, }: ModelConfigDialogProps) { const dict = useDictionary() const [selectedProviderId, setSelectedProviderId] = useState( null, ) const [showApiKey, setShowApiKey] = useState(false) const [validationStatus, setValidationStatus] = useState("idle") const [validationError, setValidationError] = useState("") const [modelPopoverOpen, setModelPopoverOpen] = useState(false) const [modelSearchValue, setModelSearchValue] = useState("") const { config, addProvider, updateProvider, deleteProvider, addModel, updateModel, deleteModel, } = modelConfig // Get selected provider const selectedProvider = config.providers.find( (p) => p.id === selectedProviderId, ) // Get suggested models for current provider const suggestedModels = selectedProvider ? SUGGESTED_MODELS[selectedProvider.provider] || [] : [] // Handle adding a new provider const handleAddProvider = (providerType: ProviderName) => { const newProvider = addProvider(providerType) setSelectedProviderId(newProvider.id) setValidationStatus("idle") } // Handle provider field updates const handleProviderUpdate = ( field: keyof ProviderConfig, value: string | boolean, ) => { if (!selectedProviderId) return updateProvider(selectedProviderId, { [field]: value }) // Reset validation when API key or base URL changes if (field === "apiKey" || field === "baseUrl") { setValidationStatus("idle") updateProvider(selectedProviderId, { validated: false }) } } // Handle adding a model to current provider const handleAddModel = (modelId: string) => { if (!selectedProviderId) return addModel(selectedProviderId, modelId) } // Handle model field updates const handleModelUpdate = ( modelConfigId: string, field: keyof ModelConfig, value: string | boolean, ) => { if (!selectedProviderId) return updateModel(selectedProviderId, modelConfigId, { [field]: value }) } // Handle deleting a model const handleDeleteModel = (modelConfigId: string) => { if (!selectedProviderId) return deleteModel(selectedProviderId, modelConfigId) } // Handle deleting the provider const handleDeleteProvider = () => { if (!selectedProviderId) return deleteProvider(selectedProviderId) setSelectedProviderId(null) setValidationStatus("idle") } // Validate API key const handleValidate = useCallback(async () => { if (!selectedProvider || !selectedProvider.apiKey) return // Need at least one model to validate if (selectedProvider.models.length === 0) { setValidationError("Add at least one model to validate") setValidationStatus("error") return } setValidationStatus("validating") setValidationError("") try { const response = await fetch("/api/validate-model", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: selectedProvider.provider, apiKey: selectedProvider.apiKey, baseUrl: selectedProvider.baseUrl, modelId: selectedProvider.models[0].modelId, }), }) const data = await response.json() if (data.valid) { setValidationStatus("success") updateProvider(selectedProviderId!, { validated: true }) } else { setValidationStatus("error") setValidationError(data.error || "Validation failed") } } catch { setValidationStatus("error") setValidationError("Network error") } }, [selectedProvider, selectedProviderId, updateProvider]) // Get all available provider types (allow duplicates for different base URLs) const availableProviders = Object.keys(PROVIDER_INFO) as ProviderName[] // Get display name for provider (use custom name if set) const getProviderDisplayName = (provider: ProviderConfig) => { return provider.name || PROVIDER_INFO[provider.provider].label } return ( {dict.modelConfig?.title || "AI Model Configuration"} {dict.modelConfig?.description || "Configure multiple AI providers and models"}
{/* Provider List (Left Sidebar) */}
{config.providers.map((provider) => ( ))}
{/* Add Provider */} {availableProviders.length > 0 && ( )}
{/* Provider Details (Right Panel) */} {selectedProvider ? (
{/* Provider Header */}

{ PROVIDER_INFO[ selectedProvider.provider ].label }

{/* Provider Name */}
handleProviderUpdate( "name", e.target.value, ) } placeholder={ PROVIDER_INFO[ selectedProvider.provider ].label } />

Custom name to identify this provider (e.g., "OpenAI Production")

{/* API Key */}
handleProviderUpdate( "apiKey", e.target.value, ) } placeholder="Enter API key" className="pr-10" />
{validationStatus === "error" && validationError && (

{validationError}

)}
{/* Base URL */}
handleProviderUpdate( "baseUrl", e.target.value, ) } placeholder={ PROVIDER_INFO[ selectedProvider.provider ].defaultBaseUrl || "Custom endpoint URL" } />
{/* Models Section */}
{ setModelPopoverOpen(open) if (!open) setModelSearchValue("") }} > {modelSearchValue.trim() ? "Press Enter to add custom model" : "Type a model ID..."} {/* Custom model option - appears when search doesn't match suggestions */} {modelSearchValue.trim() && !suggestedModels.some( (m) => m .toLowerCase() .includes( modelSearchValue.toLowerCase(), ), ) && ( { handleAddModel( modelSearchValue.trim(), ) setModelSearchValue( "", ) setModelPopoverOpen( false, ) }} className="text-xs cursor-pointer" > Add " {modelSearchValue.trim()} " )} {suggestedModels.map( (modelId) => ( { handleAddModel( modelId, ) setModelSearchValue( "", ) setModelPopoverOpen( false, ) }} className="text-xs cursor-pointer" > { modelId } ), )}
{/* Model List */}
{selectedProvider.models.length === 0 ? (

No models configured. Add a model to get started.

) : ( selectedProvider.models.map( (model) => (
handleModelUpdate( model.id, "modelId", e.target .value, ) } placeholder="Model ID (e.g., gpt-4o)" className="h-8 text-xs flex-1" />
handleModelUpdate( model.id, "streaming", checked, ) } /> Stream
), ) )}
{/* Delete Provider */}
) : (

Select a provider or add a new one

Configure multiple AI providers and switch between them easily

)}
{/* Footer */}
API keys are stored locally in your browser
) }