diff --git a/web/src/components/settings/agent-definition-form.tsx b/web/src/components/settings/agent-definition-editor.tsx similarity index 61% rename from web/src/components/settings/agent-definition-form.tsx rename to web/src/components/settings/agent-definition-editor.tsx index 152ccab1..a1f12ac1 100644 --- a/web/src/components/settings/agent-definition-form.tsx +++ b/web/src/components/settings/agent-definition-editor.tsx @@ -1,6 +1,5 @@ import type { AppRouter } from '@/../../src/api/router.js'; import { Badge } from '@/components/ui/badge.js'; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog.js'; import { Input } from '@/components/ui/input.js'; import { Label } from '@/components/ui/label.js'; import { @@ -15,7 +14,8 @@ import { Textarea } from '@/components/ui/textarea.js'; import { trpc, trpcClient } from '@/lib/trpc.js'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import type { inferRouterOutputs } from '@trpc/server'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { ReferencePanel } from './prompt-editor.js'; // ───────────────────────────────────────────────────────────────────────────── // Types @@ -25,10 +25,10 @@ type RouterOutput = inferRouterOutputs; type DefinitionRow = RouterOutput['agentDefinitions']['list'][number]; type AgentDefinition = DefinitionRow['definition']; -interface AgentDefinitionFormDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; +export interface AgentDefinitionEditorProps { + /** When provided, we are editing an existing definition. When undefined, we are creating a new one. */ existing?: DefinitionRow; + onClose: () => void; } interface SchemaData { @@ -41,7 +41,7 @@ interface SchemaData { } // ───────────────────────────────────────────────────────────────────────────── -// Helper components +// Helper components (shared with form dialog) // ───────────────────────────────────────────────────────────────────────────── function Toggle({ @@ -506,6 +506,164 @@ function IntegrationsSection({ ); } +// ───────────────────────────────────────────────────────────────────────────── +// System Prompt panel (edit mode only) +// ───────────────────────────────────────────────────────────────────────────── + +function SystemPromptPanel({ agentType }: { agentType: string }) { + const queryClient = useQueryClient(); + const [content, setContent] = useState(''); + const [validationStatus, setValidationStatus] = useState(null); + + const definitionQuery = useQuery(trpc.agentDefinitions.get.queryOptions({ agentType })); + const defaultQuery = useQuery(trpc.prompts.getDefault.queryOptions({ agentType })); + const variablesQuery = useQuery(trpc.prompts.variables.queryOptions()); + const partialsQuery = useQuery(trpc.prompts.listPartials.queryOptions()); + + const definition = definitionQuery.data?.definition; + const hasCustom = !!definition?.prompts?.systemPrompt; + + useEffect(() => { + if (definition?.prompts?.systemPrompt) { + setContent(definition.prompts.systemPrompt); + } else if (defaultQuery.data) { + setContent(defaultQuery.data.content); + } + }, [definition?.prompts?.systemPrompt, defaultQuery.data]); + + const saveMutation = useMutation({ + mutationFn: async () => { + await trpcClient.agentDefinitions.updatePrompt.mutate({ + agentType, + systemPrompt: content, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: trpc.agentDefinitions.get.queryOptions({ agentType }).queryKey, + }); + queryClient.invalidateQueries({ + queryKey: trpc.agentDefinitions.list.queryOptions().queryKey, + }); + setValidationStatus('Saved.'); + }, + }); + + const resetMutation = useMutation({ + mutationFn: async () => { + await trpcClient.agentDefinitions.resetPrompt.mutate({ agentType }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: trpc.agentDefinitions.get.queryOptions({ agentType }).queryKey, + }); + queryClient.invalidateQueries({ + queryKey: trpc.agentDefinitions.list.queryOptions().queryKey, + }); + if (defaultQuery.data) { + setContent(defaultQuery.data.content); + } + setValidationStatus('Reset to default.'); + }, + }); + + const validateMutation = useMutation({ + mutationFn: () => trpcClient.prompts.validate.mutate({ template: content }), + onSuccess: (result) => { + if (result.valid) { + setValidationStatus('Valid.'); + } else { + setValidationStatus(`Invalid: ${result.error}`); + } + }, + }); + + function loadDefault() { + if (defaultQuery.data) { + setContent(defaultQuery.data.content); + setValidationStatus(null); + } + } + + return ( +
+
+
+ + System prompt for {agentType} + + {hasCustom && custom} +
+
+ + +
+
+ +
+
+