"use client" import { AlertCircle, Check, ChevronRight, Clock, Cloud, Eye, EyeOff, Key, Link2, Loader2, Plus, Server, Settings2, Sparkles, Tag, Trash2, X, Zap, } from "lucide-react" import { useCallback, useEffect, useRef, useState } from "react" import { toast } from "sonner" 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 { Switch } from "@/components/ui/switch" import { useDictionary } from "@/hooks/use-dictionary" import type { UseModelConfigReturn } from "@/hooks/use-model-config" import { formatMessage } from "@/lib/i18n/utils" 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", sglang: "openai", // SGLang is OpenAI-compatible gateway: "vercel", edgeone: "tencent-cloud", doubao: "bytedance", } // Provider logo component function ProviderLogo({ provider, className, }: { provider: ProviderName className?: string }) { // Use Lucide icons for providers without models.dev logos if (provider === "bedrock") { return } if (provider === "sglang") { return } if (provider === "doubao") { return } const logoName = PROVIDER_LOGO_MAP[provider] || provider return ( {`${provider} ) } // Configuration section with title and optional action function ConfigSection({ title, icon: Icon, action, children, }: { title: string icon: React.ComponentType<{ className?: string }> action?: React.ReactNode children: React.ReactNode }) { return (
{title}
{action}
{children}
) } // Card wrapper with subtle depth function ConfigCard({ children }: { children: React.ReactNode }) { return (
{children}
) } 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 [customModelInput, setCustomModelInput] = useState("") const scrollRef = useRef(null) const validationResetTimeoutRef = useRef | null>(null) const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false) const [deleteConfirmText, setDeleteConfirmText] = useState("") const [validatingModelIndex, setValidatingModelIndex] = useState< number | null >(null) const [duplicateError, setDuplicateError] = useState("") const [editError, setEditError] = useState<{ modelId: string message: string } | null>(null) const { config, addProvider, updateProvider, deleteProvider, addModel, updateModel, deleteModel, } = modelConfig // Get selected provider const selectedProvider = config.providers.find( (p) => p.id === selectedProviderId, ) // Cleanup validation reset timeout on unmount useEffect(() => { return () => { if (validationResetTimeoutRef.current) { clearTimeout(validationResetTimeoutRef.current) } } }, []) // Get suggested models for current provider const suggestedModels = selectedProvider ? SUGGESTED_MODELS[selectedProvider.provider] || [] : [] // Filter out already-added models from suggestions const existingModelIds = selectedProvider?.models.map((m) => m.modelId) || [] const availableSuggestions = suggestedModels.filter( (modelId) => !existingModelIds.includes(modelId), ) // 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 credentials change const credentialFields = [ "apiKey", "baseUrl", "awsAccessKeyId", "awsSecretAccessKey", "awsRegion", ] if (credentialFields.includes(field)) { setValidationStatus("idle") updateProvider(selectedProviderId, { validated: false }) } } // Handle adding a model to current provider // Returns true if model was added successfully, false otherwise const handleAddModel = (modelId: string): boolean => { if (!selectedProviderId || !selectedProvider) return false // Prevent duplicate model IDs if (existingModelIds.includes(modelId)) { setDuplicateError(`Model "${modelId}" already exists`) return false } setDuplicateError("") addModel(selectedProviderId, modelId) return true } // 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) return // Check credentials based on provider type const isBedrock = selectedProvider.provider === "bedrock" const isEdgeOne = selectedProvider.provider === "edgeone" if (isBedrock) { if ( !selectedProvider.awsAccessKeyId || !selectedProvider.awsSecretAccessKey || !selectedProvider.awsRegion ) { return } } else if (!isEdgeOne && !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 { // For EdgeOne, construct baseUrl from current origin const baseUrl = isEdgeOne ? `${window.location.origin}/api/edgeai` : selectedProvider.baseUrl const response = await fetch("/api/validate-model", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: selectedProvider.provider, apiKey: selectedProvider.apiKey, baseUrl, modelId: model.modelId, // AWS Bedrock credentials awsAccessKeyId: selectedProvider.awsAccessKeyId, awsSecretAccessKey: selectedProvider.awsSecretAccessKey, awsRegion: selectedProvider.awsRegion, }), }) 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 }) // Reset to idle after showing success briefly (with cleanup) if (validationResetTimeoutRef.current) { clearTimeout(validationResetTimeoutRef.current) } validationResetTimeoutRef.current = setTimeout(() => { setValidationStatus("idle") validationResetTimeoutRef.current = null }, 1500) } 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 ( {/* Header */}
{dict.modelConfig?.title || "AI Model Configuration"}
{dict.modelConfig?.description || "Configure multiple AI providers and models for your workspace"}
{/* Provider List (Left Sidebar) */}
{dict.modelConfig.providers}
{config.providers.length === 0 ? (

{dict.modelConfig.addProviderHint}

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

{ PROVIDER_INFO[ selectedProvider .provider ].label }

{selectedProvider.models .length === 0 ? dict.modelConfig .noModelsConfigured : formatMessage( dict.modelConfig .modelsConfiguredCount, { count: selectedProvider .models .length, }, )}

{selectedProvider.validated && (
{ dict.modelConfig .verified }
)}
{/* Configuration Section */} {/* Display Name */}
handleProviderUpdate( "name", e.target.value, ) } placeholder={ PROVIDER_INFO[ selectedProvider .provider ].label } className="h-9" />
{/* Credentials - different for Bedrock vs other providers */} {selectedProvider.provider === "bedrock" ? ( <> {/* AWS Access Key ID */}
handleProviderUpdate( "awsAccessKeyId", e.target .value, ) } placeholder="AKIA..." className="h-9 font-mono text-xs" />
{/* AWS Secret Access Key */}
handleProviderUpdate( "awsSecretAccessKey", e .target .value, ) } placeholder={ dict .modelConfig .enterSecretKey } className="h-9 pr-10 font-mono text-xs" />
{/* AWS Region */}
{/* Test Button for Bedrock */}
{validationStatus === "error" && validationError && (

{ validationError }

)}
) : selectedProvider.provider === "edgeone" ? (
{validationStatus === "error" && validationError && (

{ validationError }

)}
) : ( <> {/* API Key */}
handleProviderUpdate( "apiKey", e .target .value, ) } placeholder={ dict .modelConfig .enterApiKey } 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 || dict .modelConfig .customEndpoint } className="h-9 rounded-xl font-mono text-xs" />
)}
{/* Models Section */}
{ setCustomModelInput( e.target .value, ) if ( duplicateError ) { setDuplicateError( "", ) } }} onKeyDown={(e) => { if ( e.key === "Enter" && customModelInput.trim() ) { const success = handleAddModel( customModelInput.trim(), ) if ( success ) { setCustomModelInput( "", ) } } }} className={cn( "h-8 w-44 rounded-lg font-mono text-xs", duplicateError && "border-destructive focus-visible:ring-destructive", )} /> {duplicateError && (

{duplicateError}

)}
} > {/* Model List */}
{selectedProvider.models .length === 0 ? (

{ dict.modelConfig .noModelsConfigured }

) : (
{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
)}
{ // Allow free typing - validation happens on blur // Clear edit error when typing if ( editError?.modelId === model.id ) { setEditError( null, ) } updateModel( selectedProviderId!, model.id, { modelId: e .target .value, validated: undefined, validationError: undefined, }, ) }} onKeyDown={( e, ) => { if ( e.key === "Enter" ) { e.currentTarget.blur() } }} onBlur={( e, ) => { const newModelId = e.target.value.trim() // Helper to show error with shake const showError = ( message: string, ) => { setEditError( { modelId: model.id, message, }, ) e.target.animate( [ { transform: "translateX(0)", }, { transform: "translateX(-4px)", }, { transform: "translateX(4px)", }, { transform: "translateX(-4px)", }, { transform: "translateX(4px)", }, { transform: "translateX(0)", }, ], { duration: 400, easing: "ease-in-out", }, ) e.target.focus() } // Check for empty model name if ( !newModelId ) { showError( dict .modelConfig .modelIdEmpty, ) return } // Check for duplicate const otherModelIds = selectedProvider?.models .filter( ( m, ) => m.id !== model.id, ) .map( ( m, ) => m.modelId, ) || [] if ( otherModelIds.includes( newModelId, ) ) { showError( dict .modelConfig .modelIdExists, ) return } // Clear error on valid blur setEditError( null, ) }} className="flex-1 min-w-0 font-mono text-sm h-8 border-0 bg-transparent focus-visible:bg-background focus-visible:ring-1" />
{/* Show validation error inline */} {model.validated === false && model.validationError && (

{ model.validationError }

)} {/* Show edit error inline */} {editError?.modelId === model.id && (

{ editError.message }

)}
), )}
)}
) : (

{dict.modelConfig.configureProviders}

{dict.modelConfig.selectProviderHint}

)}
{/* Footer */}

{dict.modelConfig.apiKeyStored}

{/* Delete Confirmation Dialog */} { setDeleteConfirmOpen(open) if (!open) setDeleteConfirmText("") }} >
{dict.modelConfig.deleteProvider} {formatMessage(dict.modelConfig.deleteConfirmDesc, { name: selectedProvider ? selectedProvider.name || PROVIDER_INFO[selectedProvider.provider] .label : "this provider", })}
{selectedProvider && selectedProvider.models.length >= 3 && (
setDeleteConfirmText(e.target.value) } placeholder={ dict.modelConfig.typeProviderName } className="h-9" />
)} {dict.modelConfig.cancel} = 3 && deleteConfirmText !== (selectedProvider.name || PROVIDER_INFO[selectedProvider.provider] .label) } className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50" > {dict.modelConfig.delete}
) }