import React, { useState, useEffect } from 'react'; import { LLMConfigSection } from './form-sections/LLMConfigSection'; import { SystemPromptSection } from './form-sections/SystemPromptSection'; import { McpServersSection } from './form-sections/McpServersSection'; import { StorageSection } from './form-sections/StorageSection'; import { ToolConfirmationSection } from './form-sections/ToolConfirmationSection'; import { Collapsible } from '../ui/collapsible'; import { Input } from '../ui/input'; import { LabelWithTooltip } from '../ui/label-with-tooltip'; import { AlertCircle } from 'lucide-react'; import type { AgentConfig, ContributorConfig } from '@dexto/core'; interface FormEditorProps { config: AgentConfig; onChange: (config: AgentConfig) => void; errors?: Record; } type SectionKey = 'basic' | 'llm' | 'systemPrompt' | 'mcpServers' | 'storage' | 'toolConfirmation'; export default function FormEditor({ config, onChange, errors = {} }: FormEditorProps) { // Convert systemPrompt to contributors format for the UI const systemPromptValue = (() => { if (!config.systemPrompt) { return { contributors: [] }; } if (typeof config.systemPrompt === 'string') { // Convert string to contributors array return { contributors: [ { id: 'primary', type: 'static' as const, priority: 0, enabled: true, content: config.systemPrompt, }, ], }; } // Already in object format with contributors - ensure contributors array exists return { contributors: config.systemPrompt.contributors || [], }; })(); // Track which sections are open const [openSections, setOpenSections] = useState>({ basic: true, llm: false, systemPrompt: false, mcpServers: false, storage: false, toolConfirmation: false, }); // Map errors to sections const sectionErrors = mapErrorsToSections(errors); // Auto-expand sections with errors useEffect(() => { // Compute derived value inside effect to avoid stale closures const derivedSectionErrors = mapErrorsToSections(errors); const sectionsWithErrors = Object.keys(derivedSectionErrors).filter( (section) => derivedSectionErrors[section as SectionKey].length > 0 ) as SectionKey[]; if (sectionsWithErrors.length > 0) { setOpenSections((prev) => { const updated = { ...prev }; sectionsWithErrors.forEach((section) => { updated[section] = true; }); return updated; }); } }, [errors]); const toggleSection = (section: SectionKey) => { setOpenSections((prev) => ({ ...prev, [section]: !prev[section], })); }; // Handle section updates const updateLLM = (llm: AgentConfig['llm']) => { onChange({ ...config, llm }); }; const updateSystemPrompt = (value: { contributors: ContributorConfig[] }) => { onChange({ ...config, systemPrompt: value }); }; const updateMcpServers = (mcpServers: AgentConfig['mcpServers']) => { onChange({ ...config, mcpServers }); }; const updateStorage = (storage: AgentConfig['storage']) => { onChange({ ...config, storage }); }; const updateToolConfirmation = (toolConfirmation: AgentConfig['toolConfirmation']) => { onChange({ ...config, toolConfirmation }); }; // Check if config has advanced features that aren't supported in form mode const hasAdvancedFeatures = checkForAdvancedFeatures(config); return (
{/* Advanced Features Warning */} {hasAdvancedFeatures && (

Advanced Configuration Detected

Some advanced features may not be editable in form mode. Switch to YAML editor for full control.

)} {/* Form Sections */}
{/* Basic Info Section */} toggleSection('basic')} errorCount={sectionErrors.basic.length} sectionErrors={sectionErrors.basic} >
Greeting Message onChange({ ...config, greeting: e.target.value })} placeholder="Hello! How can I help you today?" aria-invalid={!!errors.greeting} /> {errors.greeting && (

{errors.greeting}

)}
{/* LLM Configuration */} toggleSection('llm')} errorCount={sectionErrors.llm.length} sectionErrors={sectionErrors.llm} /> {/* System Prompt */} toggleSection('systemPrompt')} errorCount={sectionErrors.systemPrompt.length} sectionErrors={sectionErrors.systemPrompt} /> {/* MCP Servers */} toggleSection('mcpServers')} errorCount={sectionErrors.mcpServers.length} sectionErrors={sectionErrors.mcpServers} /> {/* Storage Configuration */} toggleSection('storage')} errorCount={sectionErrors.storage.length} sectionErrors={sectionErrors.storage} /> {/* Tool Confirmation */} toggleSection('toolConfirmation')} errorCount={sectionErrors.toolConfirmation.length} sectionErrors={sectionErrors.toolConfirmation} />
); } /** * Check if config has advanced features that aren't well-supported in form mode */ function checkForAdvancedFeatures(config: AgentConfig): boolean { // System prompt is now fully supported in form mode via contributors // Check for session config customization if (config.sessions && Object.keys(config.sessions).length > 0) { return true; } // Check for internal tools customization if (config.internalTools) { return true; } return false; } /** * Map error paths to form sections */ function mapErrorsToSections(errors: Record): Record { const sectionErrors: Record = { basic: [], llm: [], systemPrompt: [], mcpServers: [], storage: [], toolConfirmation: [], }; Object.entries(errors).forEach(([path, message]) => { if (path === 'greeting') { sectionErrors.basic.push(message); } else if (path.startsWith('llm.')) { sectionErrors.llm.push(message); } else if (path.startsWith('systemPrompt')) { sectionErrors.systemPrompt.push(message); } else if (path.startsWith('mcpServers')) { sectionErrors.mcpServers.push(message); } else if (path.startsWith('storage.')) { sectionErrors.storage.push(message); } else if (path.startsWith('toolConfirmation.')) { sectionErrors.toolConfirmation.push(message); } }); return sectionErrors; }