"use client" import { fieldsToJsonSchema } from "@/ai/schema" import { saveSettingsAction } from "@/app/(app)/settings/actions" import { FormError } from "@/components/forms/error" import { FormTextarea } from "@/components/forms/simple" import { Button } from "@/components/ui/button" import { Card, CardTitle } from "@/components/ui/card" import { Field } from "@/prisma/client" import { CircleCheckBig, Edit, GripVertical } from "lucide-react" import Link from "next/link" import { useState, useActionState } from "react" import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from "@dnd-kit/core" import type { DragEndEvent } from "@dnd-kit/core"; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable" import { PROVIDERS } from "@/lib/llm-providers"; function getInitialProviderOrder(settings: Record) { let order: string[] = [] if (!settings.llm_providers) { order = ['openai', 'google', 'mistral'] } else { order = settings.llm_providers.split(",").map(p => p.trim()) } // Remove duplicates and keep only valid providers return order.filter((key, idx) => PROVIDERS.some(p => p.key === key) && order.indexOf(key) === idx) } export default function LLMSettingsForm({ settings, fields, }: { settings: Record fields: Field[] showApiKey?: boolean }) { const [saveState, saveAction, pending] = useActionState(saveSettingsAction, null) const [providerOrder, setProviderOrder] = useState(getInitialProviderOrder(settings)) // Controlled values for each provider const [providerValues, setProviderValues] = useState(() => { const values: Record = {} PROVIDERS.forEach((provider) => { values[provider.key] = { apiKey: settings[provider.apiKeyName], model: settings[provider.modelName] || provider.defaultModelName, } }) return values }) function handleProviderValueChange(providerKey: string, field: "apiKey" | "model", value: string) { setProviderValues((prev) => ({ ...prev, [providerKey]: { ...prev[providerKey], [field]: value, }, })) } return ( <>
Drag provider blocks to reorder. First is highest priority.
{saveState?.success && (

Saved!

)}
{saveState?.error && {saveState.error}} Current JSON Schema for{" "} structured output Edit Fields
          {JSON.stringify(fieldsToJsonSchema(fields), null, 2)}
        
) } type DndProviderBlocksProps = { providerOrder: string[]; setProviderOrder: React.Dispatch>; providerValues: Record; handleProviderValueChange: (providerKey: string, field: "apiKey" | "model", value: string) => void; }; function DndProviderBlocks({ providerOrder, setProviderOrder, providerValues, handleProviderValueChange }: DndProviderBlocksProps) { const sensors = useSensors(useSensor(PointerSensor)) function handleDragEnd(event: DragEndEvent) { const { active, over } = event if (!over || active.id === over.id) return const oldIndex = providerOrder.indexOf(active.id as string) const newIndex = providerOrder.indexOf(over.id as string) setProviderOrder(arrayMove(providerOrder, oldIndex, newIndex)) } return ( {providerOrder.map((providerKey, idx) => ( ))} ) } type SortableProviderBlockProps = { id: string; idx: number; providerKey: string; value: { apiKey: string; model: string }; handleValueChange: (providerKey: string, field: "apiKey" | "model", value: string) => void; }; function SortableProviderBlock({ id, idx, providerKey, value, handleValueChange }: SortableProviderBlockProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }) const provider = PROVIDERS.find(p => p.key === providerKey) if (!provider) return null return (
{/* Drag handle */} {provider.label}
handleValueChange(provider.key, "apiKey", e.target.value)} className="flex-1 border rounded px-2 py-1" placeholder="API key" /> handleValueChange(provider.key, "model", e.target.value)} className="flex-1 border rounded px-2 py-1" placeholder="Model name" />
{provider.apiDoc && ( Get your API key from{" "} {provider.apiDocLabel} )}
) }