'use client' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Label } from '@/components/ui/label' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { ModelDefaults, Model } from '@/lib/types/models' import { useUpdateModelDefaults } from '@/lib/hooks/use-models' import { AlertCircle, X } from 'lucide-react' import { EmbeddingModelChangeDialog } from './EmbeddingModelChangeDialog' interface DefaultModelsSectionProps { models: Model[] defaults: ModelDefaults } interface DefaultConfig { key: keyof ModelDefaults label: string description: string modelType: 'language' | 'embedding' | 'text_to_speech' | 'speech_to_text' required?: boolean } const defaultConfigs: DefaultConfig[] = [ { key: 'default_chat_model', label: 'Chat Model', description: 'Used for chat conversations', modelType: 'language', required: true }, { key: 'default_transformation_model', label: 'Transformation Model', description: 'Used for summaries, insights, and transformations', modelType: 'language', required: true }, { key: 'default_tools_model', label: 'Tools Model', description: 'Used for function calling - OpenAI or Anthropic recommended', modelType: 'language' }, { key: 'large_context_model', label: 'Large Context Model', description: 'Used for processing large documents - Gemini recommended', modelType: 'language' }, { key: 'default_embedding_model', label: 'Embedding Model', description: 'Used for semantic search and vector embeddings', modelType: 'embedding', required: true }, { key: 'default_text_to_speech_model', label: 'Text-to-Speech Model', description: 'Used for podcast generation', modelType: 'text_to_speech' }, { key: 'default_speech_to_text_model', label: 'Speech-to-Text Model', description: 'Used for audio transcription', modelType: 'speech_to_text' } ] export function DefaultModelsSection({ models, defaults }: DefaultModelsSectionProps) { const updateDefaults = useUpdateModelDefaults() const { setValue, watch } = useForm({ defaultValues: defaults }) // State for embedding model change dialog const [showEmbeddingDialog, setShowEmbeddingDialog] = useState(false) const [pendingEmbeddingChange, setPendingEmbeddingChange] = useState<{ key: keyof ModelDefaults value: string oldModelId?: string newModelId?: string } | null>(null) // Update form when defaults change useEffect(() => { if (defaults) { Object.entries(defaults).forEach(([key, value]) => { setValue(key as keyof ModelDefaults, value) }) } }, [defaults, setValue]) const handleChange = (key: keyof ModelDefaults, value: string) => { // Special handling for embedding model changes if (key === 'default_embedding_model') { const currentEmbeddingModel = defaults[key] // Only show dialog if there's an existing embedding model and it's changing if (currentEmbeddingModel && currentEmbeddingModel !== value) { setPendingEmbeddingChange({ key, value, oldModelId: currentEmbeddingModel, newModelId: value }) setShowEmbeddingDialog(true) return } } // For all other changes or new embedding model assignment const newDefaults = { [key]: value || null } updateDefaults.mutate(newDefaults) } const handleConfirmEmbeddingChange = () => { if (pendingEmbeddingChange) { const newDefaults = { [pendingEmbeddingChange.key]: pendingEmbeddingChange.value || null } updateDefaults.mutate(newDefaults) setPendingEmbeddingChange(null) } } const handleCancelEmbeddingChange = () => { setPendingEmbeddingChange(null) setShowEmbeddingDialog(false) } const getModelsForType = (type: 'language' | 'embedding' | 'text_to_speech' | 'speech_to_text') => { return models.filter(model => model.type === type) } const missingRequired = defaultConfigs .filter(config => { if (!config.required) return false const value = defaults[config.key] if (!value) return true // Check if the model still exists const modelsOfType = models.filter(m => m.type === config.modelType) return !modelsOfType.some(m => m.id === value) }) .map(config => config.label) return ( Default Model Assignments Configure which models to use for different purposes across Open Notebook {missingRequired.length > 0 && ( Missing required models: {missingRequired.join(', ')}. Open Notebook may not function properly without these. )}
{defaultConfigs.map((config) => { const availableModels = getModelsForType(config.modelType) const currentValue = watch(config.key) || undefined // Check if the current value exists in available models const isValidModel = currentValue && availableModels.some(m => m.id === currentValue) return (
{!config.required && currentValue && ( )}

{config.description}

) })}
Which model should I choose? →
{/* Embedding Model Change Dialog */} { if (!open) { handleCancelEmbeddingChange() } }} onConfirm={handleConfirmEmbeddingChange} oldModelName={ pendingEmbeddingChange?.oldModelId ? models.find(m => m.id === pendingEmbeddingChange.oldModelId)?.name : undefined } newModelName={ pendingEmbeddingChange?.newModelId ? models.find(m => m.id === pendingEmbeddingChange.newModelId)?.name : undefined } />
) }