import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { ArrowUpRight } from "lucide-react"; import * as z from "zod/v4"; import { Button } from "@/src/components/ui/button"; import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/src/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription, } from "@/src/components/ui/form"; import { Input } from "@/src/components/ui/input"; import { Textarea } from "@/src/components/ui/textarea"; import { LLMSchemaNameSchema } from "@/src/features/llm-schemas/validation"; import { api } from "@/src/utils/api"; import { JSONSchemaFormSchema, type LlmSchema } from "@langfuse/shared"; import { CodeMirrorEditor } from "@/src/components/editor"; import { showErrorToast } from "@/src/features/notifications/showErrorToast"; const formSchema = z.object({ name: LLMSchemaNameSchema, description: z.string().min(1, "Description is required"), schema: JSONSchemaFormSchema, }); type FormValues = z.infer; type CreateOrEditLLMSchemaDialog = { children: React.ReactNode; projectId: string; onSave: (llmSchema: LlmSchema) => void; onDelete?: (llmSchema: LlmSchema) => void; existingLlmSchema?: LlmSchema; defaultValues?: { name: string; description: string; schema: string; }; }; export const CreateOrEditLLMSchemaDialog: React.FC< CreateOrEditLLMSchemaDialog > = (props) => { const { children, projectId, onSave, existingLlmSchema } = props; const utils = api.useUtils(); const createLlmSchema = api.llmSchemas.create.useMutation(); const updateLlmSchema = api.llmSchemas.update.useMutation(); const deleteLlmSchema = api.llmSchemas.delete.useMutation(); const [open, setOpen] = useState(false); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: props.defaultValues ?? { name: "", description: "", schema: JSON.stringify( { type: "object", properties: {}, required: [], additionalProperties: false, }, null, 2, ), }, }); // Populate form when in edit mode useEffect(() => { if (existingLlmSchema && !props.defaultValues) { form.reset({ name: existingLlmSchema.name, description: existingLlmSchema.description, schema: JSON.stringify(existingLlmSchema.schema, null, 2), }); } }, [existingLlmSchema, form, props.defaultValues]); async function onSubmit(values: FormValues) { let result; if (existingLlmSchema) { result = await updateLlmSchema.mutateAsync({ id: existingLlmSchema.id, projectId, name: values.name, description: values.description, schema: JSON.parse(values.schema), }); } else { result = await createLlmSchema.mutateAsync({ projectId, name: values.name, description: values.description, schema: JSON.parse(values.schema), }); } await utils.llmSchemas.getAll.invalidate({ projectId }); onSave(result); setOpen(false); } async function handleDelete() { if (!existingLlmSchema) return; await deleteLlmSchema.mutateAsync({ id: existingLlmSchema.id, projectId, }); props.onDelete?.(existingLlmSchema); await utils.llmSchemas.getAll.invalidate({ projectId }); setOpen(false); } const prettifyJson = () => { try { const currentValue = form.getValues("schema"); const parsedJson = JSON.parse(currentValue); const prettified = JSON.stringify(parsedJson, null, 2); form.setValue("schema", prettified); } catch { showErrorToast( "Failed to prettify JSON", "Please verify your input is valid JSON", "WARNING", ); } }; return ( {children} {existingLlmSchema ? "Edit LLM Schema" : "Create LLM Schema"} Define a JSON Schema for structured outputs
{ e.preventDefault(); e.stopPropagation(); form.handleSubmit(onSubmit)(); }} className="grid max-h-full min-h-0 overflow-hidden" >
( Name )} /> ( Description