import React, { useState } from 'react'; import { Input } from '../../ui/input'; import { Textarea } from '../../ui/textarea'; import { LabelWithTooltip } from '../../ui/label-with-tooltip'; import { Button } from '../../ui/button'; import { Collapsible } from '../../ui/collapsible'; import { Plus, Trash2, ChevronDown, ChevronUp } from 'lucide-react'; import { PROMPT_GENERATOR_SOURCES } from '@dexto/core'; import type { ContributorConfig } from '@dexto/core'; // Component works with the object form of SystemPromptConfig (not the string form) type SystemPromptConfigObject = { contributors: ContributorConfig[]; }; interface SystemPromptSectionProps { value: SystemPromptConfigObject; onChange: (value: SystemPromptConfigObject) => void; errors?: Record; open?: boolean; onOpenChange?: (open: boolean) => void; errorCount?: number; sectionErrors?: string[]; } export function SystemPromptSection({ value, onChange, errors = {}, open, onOpenChange, errorCount = 0, sectionErrors = [], }: SystemPromptSectionProps) { const [expandedContributors, setExpandedContributors] = useState>(new Set()); // Local state for file paths (comma-separated editing) const [editingFiles, setEditingFiles] = useState>({}); const contributors = value.contributors || []; const toggleContributor = (id: string) => { setExpandedContributors((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }; const addContributor = () => { const newId = `contributor-${Date.now()}`; const newContributor: ContributorConfig = { id: newId, type: 'static', priority: contributors.length * 10, enabled: true, content: '', }; onChange({ contributors: [...contributors, newContributor], }); setExpandedContributors((prev) => new Set(prev).add(newId)); }; const removeContributor = (id: string) => { onChange({ contributors: contributors.filter((c) => c.id !== id), }); setExpandedContributors((prev) => { const next = new Set(prev); next.delete(id); return next; }); }; const updateContributor = (id: string, updates: Partial) => { onChange({ contributors: contributors.map((c) => { if (c.id === id) { // If ID is changing, handle the ID change if (updates.id && updates.id !== id) { // Update expanded state setExpandedContributors((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); next.add(updates.id!); } return next; }); } // If type is changing, create a new contributor with the new type if (updates.type && updates.type !== c.type) { const baseFields = { id: updates.id || c.id, priority: updates.priority !== undefined ? updates.priority : c.priority, enabled: updates.enabled !== undefined ? updates.enabled : c.enabled, }; if (updates.type === 'static') { return { ...baseFields, type: 'static', content: '', } as ContributorConfig; } else if (updates.type === 'dynamic') { return { ...baseFields, type: 'dynamic', source: 'date', } as ContributorConfig; } else if (updates.type === 'file') { return { ...baseFields, type: 'file', files: [] } as ContributorConfig; } } return { ...c, ...updates } as ContributorConfig; } return c; }), }); }; // Get the current value for file paths (either from editing state or from config) const getFilesValue = (id: string, files: string[]): string => { return editingFiles[id] ?? files.join(', '); }; // Update local editing state while typing const setFilesValue = (id: string, value: string) => { setEditingFiles((prev) => ({ ...prev, [id]: value })); }; // Parse and commit files on blur const commitFiles = (id: string, filesString: string) => { setEditingFiles((prev) => { const next = { ...prev }; delete next[id]; return next; }); const files = filesString .split(',') .map((f) => f.trim()) .filter(Boolean); updateContributor(id, { files: files.length > 0 ? files : [] }); }; return (

Define how the agent should behave using multiple contributors with different priorities.

{contributors.length === 0 ? (

No contributors configured

) : ( contributors.map((contributor) => { const isExpanded = expandedContributors.has(contributor.id); return (
{/* Contributor Header */}
{/* Contributor Details */} {isExpanded && (
{/* Common Fields */}
ID * updateContributor(contributor.id, { id: e.target.value, }) } placeholder="e.g., primary, date" />
Type *
Priority * { const val = e.target.value; const num = Number.parseInt(val, 10); updateContributor(contributor.id, { priority: Number.isNaN(num) ? 0 : num, }); }} placeholder="0" min="0" />
{/* Type-specific Fields */} {contributor.type === 'static' && (
Content *