"use client"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { getDefaultConfigurationsApiV1UserConfigurationsDefaultsGet } from '@/client/sdk.gen'; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useUserConfig } from "@/context/UserConfigContext"; type ServiceSegment = "llm" | "tts" | "stt"; interface SchemaProperty { type?: string; default?: string | number | boolean; enum?: string[]; $ref?: string; description?: string; format?: string; } interface ProviderSchema { properties: Record; required?: string[]; $defs?: Record; [key: string]: unknown; } interface FormValues { [key: string]: string | number | boolean; } const TAB_CONFIG: { key: ServiceSegment; label: string }[] = [ { key: "llm", label: "LLM" }, { key: "tts", label: "Voice" }, { key: "stt", label: "Transcriber" }, ]; export default function ServiceConfiguration() { const [apiError, setApiError] = useState(null); const [isSaving, setIsSaving] = useState(false); const { userConfig, saveUserConfig } = useUserConfig(); const [schemas, setSchemas] = useState>>({ llm: {}, tts: {}, stt: {} }); const [serviceProviders, setServiceProviders] = useState>({ llm: "", tts: "", stt: "" }); const { register, handleSubmit, formState: { errors }, reset, getValues, setValue, watch } = useForm(); useEffect(() => { const fetchConfigurations = async () => { const response = await getDefaultConfigurationsApiV1UserConfigurationsDefaultsGet(); if (response.data) { setSchemas({ llm: response.data.llm as Record, tts: response.data.tts as Record, stt: response.data.stt as Record }); } else { console.error("Failed to fetch configurations"); return; } const defaultValues: Record = {}; const selectedProviders: Record = { llm: response.data.default_providers.llm, tts: response.data.default_providers.tts, stt: response.data.default_providers.stt }; const setServicePropertyValues = (service: ServiceSegment) => { if (userConfig?.[service]?.provider) { Object.entries(userConfig?.[service]).forEach(([field, value]) => { if (field !== "provider") { defaultValues[`${service}_${field}`] = value; } }); selectedProviders[service] = userConfig?.[service]?.provider as string; } else { const properties = response.data[service]?.[selectedProviders[service]]?.properties as Record; if (properties) { Object.entries(properties).forEach(([field, schema]) => { if (field !== "provider" && schema.default) { defaultValues[`${service}_${field}`] = schema.default; } }); } } } setServicePropertyValues("llm"); setServicePropertyValues("tts"); setServicePropertyValues("stt"); setServiceProviders(selectedProviders); reset(defaultValues); }; fetchConfigurations(); }, [reset, userConfig]); const handleProviderChange = (service: ServiceSegment, providerName: string) => { if (!providerName) { return; } const currentValues = getValues(); const preservedValues: Record = {}; // Preserve values from other services Object.keys(currentValues).forEach(key => { if (!key.startsWith(`${service}_`)) { preservedValues[key] = currentValues[key]; } }); // Set default values from schema if (schemas?.[service]?.[providerName]) { const providerSchema = schemas[service][providerName]; Object.entries(providerSchema.properties).forEach(([field, schema]: [string, SchemaProperty]) => { if (field !== "provider" && schema.default !== undefined) { preservedValues[`${service}_${field}`] = schema.default; } }); } preservedValues[`${service}_provider`] = providerName; reset(preservedValues); setServiceProviders(prev => ({ ...prev, [service]: providerName })); } const onSubmit = async (data: FormValues) => { setApiError(null); setIsSaving(true); const userConfig = { llm: { provider: serviceProviders.llm, api_key: data.llm_api_key as string, model: data.llm_model as string }, tts: { provider: serviceProviders.tts, api_key: data.tts_api_key as string }, stt: { provider: serviceProviders.stt, api_key: data.stt_api_key as string } }; // Add any extra properties in the payload Object.entries(data).forEach(([property, value]) => { const parts = property.split('_'); const service = parts[0] as ServiceSegment; const field = parts.slice(1).join('_'); if (userConfig[service] && !(field in userConfig[service])) { (userConfig[service] as Record)[field] = value as string; } }); try { await saveUserConfig({ llm: userConfig.llm, tts: userConfig.tts, stt: userConfig.stt }); setApiError(null); } catch (error: unknown) { if (error instanceof Error) { setApiError(error.message); } else { setApiError('An unknown error occurred'); } } finally { setIsSaving(false); } }; const getConfigFields = (service: ServiceSegment): string[] => { const currentProvider = serviceProviders[service]; const providerSchema = schemas?.[service]?.[currentProvider]; if (!providerSchema) return []; // Find all config fields (not provider, not api_key) return Object.keys(providerSchema.properties).filter( field => field !== "provider" && field !== "api_key" ); }; const renderServiceFields = (service: ServiceSegment) => { const currentProvider = serviceProviders[service]; const providerSchema = schemas?.[service]?.[currentProvider]; const availableProviders = schemas?.[service] ? Object.keys(schemas[service]) : []; const configFields = getConfigFields(service); return (
{/* Provider and first config field in one row */}
{currentProvider && providerSchema && configFields[0] && (
{renderField(service, configFields[0], providerSchema)}
)}
{/* Additional config fields (like voice for TTS) */} {currentProvider && providerSchema && configFields.length > 1 && (
{configFields.slice(1).map((field) => (
{renderField(service, field, providerSchema)}
))}
)} {/* API Key in bottom row */} {currentProvider && providerSchema && providerSchema.properties.api_key && (
{errors[`${service}_api_key`] && (

{typeof errors[`${service}_api_key`]?.message === 'string' ? String(errors[`${service}_api_key`]?.message) : "This field is required"}

)}
)}
); }; const renderField = (service: ServiceSegment, field: string, providerSchema: ProviderSchema) => { const schema = providerSchema.properties[field]; const actualSchema = schema.$ref && providerSchema.$defs ? providerSchema.$defs[schema.$ref.split('/').pop() || ''] : schema; if (actualSchema?.enum) { return ( ); } return ( ); }; return (

AI Models Configuration

Configure your AI model, voice, and transcription services.

{TAB_CONFIG.map(({ key, label }) => ( {label} ))} {TAB_CONFIG.map(({ key }) => ( {renderServiceFields(key)} ))} {apiError &&

{apiError}

}
); }