import { useEffect, useState, type ReactElement } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Layout } from "@/components/layout/Layout"; import { FormField, FormSelect, FormTextArea, PrimaryButton, SecondaryButton, FormSlider, FormTagInput } from "@/components/shared"; import { Plus, Trash2, ArrowLeft } from "lucide-react"; import { aiService } from "@/services/ai-service"; import { showToast } from "@/utils/toast"; import { useForm, useFieldArray, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { cn } from "@/lib/utils"; import type { AIProviderInfo } from "@/types/ai"; const variableSchema = z.object({ name: z.string().min(1, "Variable name is required").max(100), type: z.enum(["string", "number", "boolean", "array"]), required: z.boolean(), default: z.string().optional(), }); const promptSchema = z.object({ name: z.string().min(1, "Name is required").max(255), description: z.string().optional(), use_case: z.string().min(1, "Use case is required").max(100), system_message: z.string().optional(), user_template: z.string().min(1, "User template is required").max(50000), provider: z.string().optional(), model: z.string().optional(), temperature: z.number().min(0).max(2), max_tokens: z.number().int().min(1).max(128000), tags: z.array(z.string()), is_default: z.boolean(), variables: z.array(variableSchema), change_notes: z.string().optional(), }); type PromptFormData = z.infer; const PromptEdit = (): ReactElement => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [apiProviders, setApiProviders] = useState([]); const [currentVersion, setCurrentVersion] = useState(1); const { control, handleSubmit, setValue, watch, reset, formState: { errors }, } = useForm({ resolver: zodResolver(promptSchema), defaultValues: { name: "", description: "", use_case: "", system_message: "", user_template: "", provider: "", model: "", temperature: 0.7, max_tokens: 2048, tags: [], is_default: false, variables: [], change_notes: "", }, }); const { fields, append, remove } = useFieldArray({ control, name: "variables", }); const selectedProvider = watch("provider"); const providersOptions = apiProviders.map((p) => ({ value: p.name, label: p.displayName || p.name, })); const providerDetail = apiProviders.find((p) => p.name === selectedProvider); const modelsOptions = (providerDetail?.models || []).map((m) => ({ value: m, label: m, })); useEffect(() => { const loadData = async (): Promise => { if (!id) return; try { setIsLoading(true); const [providerData, promptData] = await Promise.all([ aiService.getProviders(), aiService.getPrompt(id), ]); setApiProviders(providerData); reset({ name: promptData.name, description: promptData.description || "", use_case: promptData.useCase, system_message: promptData.systemMessage || "", user_template: promptData.template, provider: promptData.defaultParameters?.provider || "", model: promptData.defaultParameters?.model || "", temperature: promptData.defaultParameters?.temperature ?? 0.7, max_tokens: promptData.defaultParameters?.max_tokens ?? 2048, tags: promptData.tags || [], is_default: promptData.isDefault || false, variables: (promptData.variables || []).map((v) => ({ name: v.name, type: v.type || "string", required: !!v.required, default: v.default ? String(v.default) : "", })), change_notes: "", }); setCurrentVersion(promptData.version || 1); } catch (err: unknown) { showToast.error( (err as { response?: { data?: { error?: { message?: string } } } }) ?.response?.data?.error?.message || "Failed to load prompt data", ); navigate("/tenant/ai/prompts"); } finally { setIsLoading(false); } }; void loadData(); }, [id, reset, navigate]); const onFormSubmit = async (data: PromptFormData): Promise => { if (!id) return; const parsedTags = data.tags || []; const sanitizedVariables = data.variables ?.filter((v) => v.name.trim()) .map((v) => ({ name: v.name.trim(), type: v.type, required: v.required, ...(v.default?.trim() ? { default: v.default.trim() } : {}), })); setIsSubmitting(true); try { await aiService.updatePrompt(id, { name: data.name.trim(), description: data.description?.trim() || undefined, use_case: data.use_case.trim(), system_message: data.system_message?.trim() || undefined, user_template: data.user_template, model: data.model || undefined, provider: data.provider || undefined, temperature: data.temperature, max_tokens: data.max_tokens, variables: sanitizedVariables, tags: parsedTags, is_default: data.is_default, change_notes: data.change_notes, }); showToast.success("Prompt updated successfully"); navigate("/tenant/ai/prompts"); } catch (err: unknown) { showToast.error( (err as { response?: { data?: { error?: { message?: string } } } }) ?.response?.data?.error?.message || "Failed to update prompt", ); } finally { setIsSubmitting(false); } }; if (isLoading) { return (

Loading prompt details...

); } return ( navigate("/tenant/ai/prompts")}> Cancel {isSubmitting ? "Updating..." : "Update Prompt"} ), }} >
{/* LEFT SIDE */}
{/* General Information */}

General Information

( )} /> ( )} />
{/* Prompt Content */}

Prompt Content

( )} /> ( Use {"{{variable_name}}"} for dynamic inputs.

Updating this template will create a new version (v{currentVersion + 1})

} /> )} /> ( )} /> {/* Variables Section */}

Variables Configuration

append({ name: "", type: "string", required: false, default: "", }) } size="small" className="h-8 py-0" > Add Variable
{fields.length > 0 && (
Variable Name Type Default Value Required
)}
{fields.map((field, index) => (
(
{errors.variables?.[index]?.name && (

{errors.variables?.[index]?.name?.message}

)}
)} /> (
)} /> (
{errors.variables?.[index]?.default && (

{errors.variables?.[index]?.default?.message}

)}
)} />
(
field.onChange(!field.value)} >
)} />
))}
{/* RIGHT SIDE (Sidebar) */}
{/* Settings */}

Settings

( )} />
(
field.onChange(!field.value)} >
)} /> Make default for this use case
{/* Model Config */}

Model Configuration

( { field.onChange(val); const pDetail = apiProviders.find((p) => p.name === val); if (pDetail && pDetail.defaultModel) { setValue("model", pDetail.defaultModel); } else if (pDetail && pDetail.models && pDetail.models.length > 0) { setValue("model", pDetail.models[0]); } else { setValue("model", ""); } }} options={providersOptions} error={errors.provider?.message} /> )} /> ( )} /> ( )} /> ( )} />
{/* Organization */}

Organization

( )} />
); }; export default PromptEdit;