"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { toast } from "sonner"; type Provider = "openai" | "anthropic" | "openrouter" | "ollama" | ""; type UseCase = { key: "text" | "vision" | "mealPlan"; providerSetting: "DEFAULT_TEXT_PROVIDER" | "DEFAULT_VISION_PROVIDER" | "DEFAULT_MEAL_PLAN_PROVIDER"; modelSetting: "DEFAULT_TEXT_MODEL" | "DEFAULT_VISION_MODEL" | "DEFAULT_MEAL_PLAN_MODEL"; label: string; description: string; }; const USE_CASES: UseCase[] = [ { key: "text", providerSetting: "DEFAULT_TEXT_PROVIDER", modelSetting: "DEFAULT_TEXT_MODEL", label: "Text generation", description: "Recipe generation, chat, substitutions, pairings, and other text-based AI features.", }, { key: "vision", providerSetting: "DEFAULT_VISION_PROVIDER", modelSetting: "DEFAULT_VISION_MODEL", label: "Vision (photo import/scan)", description: "Importing a recipe from a photo and pantry barcode/photo scanning.", }, { key: "mealPlan", providerSetting: "DEFAULT_MEAL_PLAN_PROVIDER", modelSetting: "DEFAULT_MEAL_PLAN_MODEL", label: "Meal plan generation", description: "Generating a full week's meal plan.", }, ]; const PRESET_MODELS: Record = { openai: { label: "OpenAI", models: [ { value: "gpt-4o", label: "GPT-4o" }, { value: "gpt-4o-mini", label: "GPT-4o mini (faster)" }, { value: "o3-mini", label: "o3-mini (reasoning)" }, { value: "gpt-4-turbo", label: "GPT-4 Turbo" }, ], }, anthropic: { label: "Anthropic", models: [ { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" }, { value: "claude-opus-4-8", label: "Claude Opus 4.8 (most capable)" }, { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5 (fastest)" }, ], }, }; type Values = Partial>; export function AdminDefaultModelForm({ initialValues }: { initialValues: Values }) { const [values, setValues] = useState(initialValues); const [saving, setSaving] = useState(false); function setField(key: UseCase["providerSetting"] | UseCase["modelSetting"], value: string | null) { setValues((prev) => ({ ...prev, [key]: value || null })); } async function handleSave() { setSaving(true); try { const res = await fetch("/api/v1/admin/settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(values), }); if (!res.ok) throw new Error("Save failed"); toast.success("Default AI providers saved"); } catch { toast.error("Failed to save defaults"); } finally { setSaving(false); } } return (

Default AI Providers

What to use for a given feature when a user hasn't set their own model preference and has no personal API key. Leave a provider unset to fall back to whichever provider key is configured above.

{USE_CASES.map(({ key, providerSetting, modelSetting, label, description }) => { const provider = (values[providerSetting] ?? "") as Provider; const model = (values[modelSetting] ?? "") as string; const presets = provider && PRESET_MODELS[provider]?.models; return (

{label}

{description}

{presets ? ( ) : ( setField(modelSetting, e.target.value)} disabled={!provider} /> )}
); })}
); }