"use client" import { AlertCircle, Check, ChevronRight, Clock, Cloud, Eye, EyeOff, Key, Link2, Loader2, Plus, Server, Sparkles, Tag, Trash2, X, Zap, } from "lucide-react" import { useCallback, useEffect, useRef, useState } from "react" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { ScrollArea } from "@/components/ui/scroll-area" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { useDictionary } from "@/hooks/use-dictionary" import type { UseModelConfigReturn } from "@/hooks/use-model-config" import type { ProviderConfig, ProviderName } from "@/lib/types/model-config" import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config" import { cn } from "@/lib/utils" interface ModelConfigDialogProps { open: boolean onOpenChange: (open: boolean) => void modelConfig: UseModelConfigReturn } type ValidationStatus = "idle" | "validating" | "success" | "error" // Map provider names to models.dev logo names const PROVIDER_LOGO_MAP: Record = { openai: "openai", anthropic: "anthropic", google: "google", azure: "azure", bedrock: "amazon-bedrock", openrouter: "openrouter", deepseek: "deepseek", siliconflow: "siliconflow", gateway: "vercel", } // Provider logo component function ProviderLogo({ provider, className, }: { provider: ProviderName className?: string }) { // Use Lucide icon for bedrock since models.dev doesn't have a good AWS icon if (provider === "bedrock") { return } const logoName = PROVIDER_LOGO_MAP[provider] || provider return ( {`${provider} ) } 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 [scrollState, setScrollState] = useState({ top: false, bottom: true }) const [customModelInput, setCustomModelInput] = useState("") const scrollRef = useRef(null) const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false) const [validatingModelIndex, setValidatingModelIndex] = useState< number | null >(null) const { config, addProvider, updateProvider, deleteProvider, addModel, updateModel, deleteModel, } = modelConfig // Get selected provider const selectedProvider = config.providers.find( (p) => p.id === selectedProviderId, ) // Track scroll position for gradient shadows useEffect(() => { const scrollEl = scrollRef.current?.querySelector( "[data-radix-scroll-area-viewport]", ) as HTMLElement | null if (!scrollEl) return const handleScroll = () => { const { scrollTop, scrollHeight, clientHeight } = scrollEl setScrollState({ top: scrollTop > 10, bottom: scrollTop < scrollHeight - clientHeight - 10, }) } handleScroll() // Initial check scrollEl.addEventListener("scroll", handleScroll) return () => scrollEl.removeEventListener("scroll", handleScroll) }, [selectedProvider]) // 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 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") setDeleteConfirmOpen(false) } // Validate all models 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("") let allValid = true let errorCount = 0 // Validate each model for (let i = 0; i < selectedProvider.models.length; i++) { const model = selectedProvider.models[i] setValidatingModelIndex(i) 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: model.modelId, }), }) const data = await response.json() if (data.valid) { updateModel(selectedProviderId!, model.id, { validated: true, validationError: undefined, }) } else { allValid = false errorCount++ updateModel(selectedProviderId!, model.id, { validated: false, validationError: data.error || "Validation failed", }) } } catch { allValid = false errorCount++ updateModel(selectedProviderId!, model.id, { validated: false, validationError: "Network error", }) } } setValidatingModelIndex(null) if (allValid) { setValidationStatus("success") updateProvider(selectedProviderId!, { validated: true }) } else { setValidationStatus("error") setValidationError(`${errorCount} model(s) failed validation`) } }, [selectedProvider, selectedProviderId, updateProvider, updateModel]) // Get all available provider types const availableProviders = Object.keys(PROVIDER_INFO) as ProviderName[] // Get display name for provider 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 for your workspace"}
{/* Provider List (Left Sidebar) */}
Providers
{config.providers.length === 0 ? (

Add a provider to get started

) : (
{config.providers.map((provider) => ( ))}
)}
{/* Add Provider */}
{/* Provider Details (Right Panel) */}
{selectedProvider ? ( <> {/* Top gradient shadow */}
{/* Bottom gradient shadow */}
{/* Provider Header */}

{ PROVIDER_INFO[ selectedProvider .provider ].label }

{selectedProvider.models .length === 0 ? "No models configured" : `${selectedProvider.models.length} model${selectedProvider.models.length > 1 ? "s" : ""} configured`}

{selectedProvider.validated && (
Verified
)}
{/* Configuration Section */}
Configuration
{/* Display Name */}
handleProviderUpdate( "name", e.target.value, ) } placeholder={ PROVIDER_INFO[ selectedProvider .provider ].label } className="h-9" />
{/* API Key */}
handleProviderUpdate( "apiKey", e.target .value, ) } placeholder="Enter your API key" className="h-9 pr-10 font-mono text-xs" />
{validationStatus === "error" && validationError && (

{ validationError }

)}
{/* Base URL */}
handleProviderUpdate( "baseUrl", e.target.value, ) } placeholder={ PROVIDER_INFO[ selectedProvider .provider ].defaultBaseUrl || "Custom endpoint URL" } className="h-9 font-mono text-xs" />
{/* Models Section */}
Models
setCustomModelInput( e.target.value, ) } onKeyDown={(e) => { if ( e.key === "Enter" && customModelInput.trim() ) { handleAddModel( customModelInput.trim(), ) setCustomModelInput( "", ) } }} className="h-8 w-48 font-mono text-xs" />
{/* Model List */}
{selectedProvider.models .length === 0 ? (

No models configured

) : (
{selectedProvider.models.map( (model, index) => (
{/* Status icon */}
{validatingModelIndex !== null && index === validatingModelIndex ? ( // Currently validating
) : validatingModelIndex !== null && index > validatingModelIndex && model.validated === undefined ? ( // Queued
) : model.validated === true ? ( // Valid
) : model.validated === false ? ( // Invalid
) : ( // Not validated yet
)}
{ updateModel( selectedProviderId!, model.id, { modelId: e .target .value, validated: undefined, validationError: undefined, }, ) }} className="flex-1 font-mono text-sm h-8 border-0 bg-transparent focus-visible:bg-background focus-visible:ring-1" />
), )}
)}
{/* Danger Zone */}
) : (

Configure AI Providers

Select a provider from the list or add a new one to configure API keys and models

)}
{/* Footer */}

API keys are stored locally in your browser

{/* Delete Confirmation Dialog */} Delete Provider Are you sure you want to delete{" "} {selectedProvider ? selectedProvider.name || PROVIDER_INFO[selectedProvider.provider] .label : "this provider"} ? This will remove all configured models and cannot be undone. Cancel Delete
) }