import React, { useEffect, useMemo } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Select } from '@/components/ui/Select'; import { useToast } from '@/components/ui/Toast'; import { useCreateAgent, useUpdateAgent, useTestRunAgent, useAgentToolsFull, useAgentSkills, } from '@/api/automation'; import type { AgentDefinitionFull } from '@/types/automation'; import { Play, Save, X, Loader2 } from 'lucide-react'; const agentEditorSchema = z.object({ name: z.string().min(1, 'required').max(120), description: z.string().max(500).default(''), system_prompt: z.string().default(''), llm_model: z.string().min(1, 'required').max(100), tool_ids: z.array(z.string()).default([]), skill_ids: z.array(z.string()).default([]), max_steps: z.coerce.number().int().min(1).max(100).default(20), max_duration_seconds: z.coerce.number().int().min(1).max(86400).default(300), budget_limit_usd: z.coerce.number().min(0).max(10000).default(1.0), temperature: z.coerce.number().min(0).max(2).default(0.3), max_tokens: z.coerce.number().int().min(1).max(100000).default(1000), trace_mode: z.enum(['standard', 'extended']).default('standard'), mode: z.enum(['proactive', 'reactive']).default('reactive'), is_active: z.boolean().default(true), trigger_config: z.record(z.string(), z.unknown()).default({}), ai_use_case_metadata: z.record(z.string(), z.unknown()).default({}), }); type AgentEditorFormData = z.infer; export interface AgentEditorProps { agent?: AgentDefinitionFull | null; onSaved?: (agent: AgentDefinitionFull) => void; onCancel?: () => void; } const modeOptions = [ { value: 'reactive', label: 'Reactive' }, { value: 'proactive', label: 'Proactive' }, ]; const traceModeOptions = [ { value: 'standard', label: 'Standard' }, { value: 'extended', label: 'Extended' }, ]; const commonModels = [ 'ollama/deepseek-v4-flash', 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku', 'llama-3-70b', 'llama-3-8b', 'mistral-large', 'mixtral-8x7b', ]; export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) { const { t } = useTranslation(); const toast = useToast(); const { data: tools = [] } = useAgentToolsFull(); const { data: skills = [] } = useAgentSkills(); const createAgent = useCreateAgent(); const updateAgent = useUpdateAgent(); const testRunAgent = useTestRunAgent(); const defaultValues = useMemo(() => { if (agent) { return { name: agent.name, description: agent.description || '', system_prompt: agent.system_prompt || '', llm_model: agent.llm_model || agent.model || '', tool_ids: agent.tool_ids || [], skill_ids: agent.skill_ids || [], max_steps: agent.max_steps ?? 20, max_duration_seconds: agent.max_duration_seconds ?? 300, budget_limit_usd: agent.budget_limit_usd ?? agent.budget_limit ?? 1.0, temperature: agent.temperature ?? 0.3, max_tokens: agent.max_tokens ?? 1000, trace_mode: (agent.trace_mode === 'extended' ? 'extended' : 'standard') as 'standard' | 'extended', mode: agent.mode, is_active: agent.is_active ?? agent.active ?? true, trigger_config: agent.trigger_config || {}, ai_use_case_metadata: agent.ai_use_case_metadata || {}, }; } return { name: '', description: '', system_prompt: '', llm_model: 'ollama/deepseek-v4-flash', tool_ids: [], skill_ids: [], max_steps: 20, max_duration_seconds: 300, budget_limit_usd: 1.0, temperature: 0.3, max_tokens: 1000, trace_mode: 'standard', mode: 'reactive', is_active: true, trigger_config: {}, ai_use_case_metadata: {}, }; }, [agent]); const { register, handleSubmit, control, reset, watch, setValue, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(agentEditorSchema), defaultValues, }); const selectedToolIds = watch('tool_ids'); const selectedSkillIds = watch('skill_ids'); useEffect(() => { reset(defaultValues); }, [reset, defaultValues]); const onSubmit = async (data: AgentEditorFormData) => { try { if (agent) { const updated = await updateAgent.mutateAsync({ id: agent.id, data }); toast.success(t('agent.saved')); onSaved?.(updated); } else { const created = await createAgent.mutateAsync(data); toast.success(t('agent.created')); onSaved?.(created); } } catch { toast.error(t('agent.saveFailed')); } }; const handleTestRun = async () => { if (!agent) { toast.warning(t('agent.saveBeforeTest')); return; } try { await testRunAgent.mutateAsync(agent.id); toast.success(t('agent.testRunOk')); } catch { toast.error(t('agent.testRunFailed')); } }; const toggleArrayValue = (field: 'tool_ids' | 'skill_ids', value: string) => { const current = field === 'tool_ids' ? selectedToolIds : selectedSkillIds; const next = (current || []).includes(value) ? (current || []).filter((v) => v !== value) : [...(current || []), value]; setValue(field, next, { shouldDirty: true, shouldValidate: true }); }; return (
{/* Basic info */}
{/* System prompt */}