"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 {
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,
PROVIDER_LOGO_MAP,
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"
// 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 (
// biome-ignore lint/performance/noImgElement: External URL from models.dev
)
}
// 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 (
)
}
// 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",
"vertexApiKey",
]
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 || !selectedProviderId) return
// Check credentials based on provider type
const isBedrock = selectedProvider.provider === "bedrock"
const isEdgeOne = selectedProvider.provider === "edgeone"
const isOllama = selectedProvider.provider === "ollama"
const isVertexAI = selectedProvider.provider === "vertexai"
if (isBedrock) {
if (
!selectedProvider.awsAccessKeyId ||
!selectedProvider.awsSecretAccessKey ||
!selectedProvider.awsRegion
) {
return
}
} else if (isVertexAI) {
// Vertex AI requires vertexApiKey for Express Mode
if (!selectedProvider.vertexApiKey) {
return
}
} else if (!isEdgeOne && !isOllama && !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,
// Vertex AI credentials (Express Mode)
vertexApiKey: selectedProvider.vertexApiKey,
}),
})
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 (
)
}