import React, { useCallback, useId, useMemo, useState } from 'react'; import { MessageCircleQuestion } from 'lucide-react'; import { useI18n } from '../../application/i18n/I18nProvider'; import { buildCodebuddyElicitationContent, initialCodebuddyElicitationValues, parseCodebuddyElicitationFields, selectedCodebuddyOptionKey, toggleCodebuddyArrayOption, validateCodebuddyElicitationValues, type CodebuddyElicitationField, type CodebuddyElicitationValidationIssue, } from '../../domain/codebuddyElicitationForm'; import type { CodebuddyElicitation, CodebuddyElicitationAction, } from '../../infrastructure/ai/shared/codebuddyElicitations'; import { Button } from '../ui/button'; function validationMessage( validationIssue: CodebuddyElicitationValidationIssue, t: ReturnType['t'], ): string { const values = { field: validationIssue.fieldTitle, limit: validationIssue.limit, format: validationIssue.format, }; return t(`ai.codebuddy.elicitation.validation.${validationIssue.code}`, values); } function inputType(field: CodebuddyElicitationField): React.HTMLInputTypeAttribute { if (field.type === 'number' || field.type === 'integer') return 'number'; if (field.format === 'email') return 'email'; if (field.format === 'uri') return 'url'; if (field.format === 'date') return 'date'; return 'text'; } export const CodebuddyElicitationCard: React.FC<{ elicitation: CodebuddyElicitation; onRespond: ( action: CodebuddyElicitationAction, content?: Record, ) => Promise; }> = ({ elicitation, onRespond }) => { const { t } = useI18n(); const formId = useId(); const fields = useMemo( () => parseCodebuddyElicitationFields(elicitation.request.requestedSchema), [elicitation.request.requestedSchema], ); const [values, setValues] = useState>( () => initialCodebuddyElicitationValues(fields), ); const [touchedFields, setTouchedFields] = useState>(() => new Set()); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const validationIssues = validateCodebuddyElicitationValues(fields, values); const validationByField = new Map( validationIssues.map((validationIssue) => [validationIssue.fieldId, validationIssue]), ); const complete = validationIssues.length === 0; const markFieldTouched = useCallback((fieldId: string) => { setTouchedFields((current) => { if (current.has(fieldId)) return current; const next = new Set(current); next.add(fieldId); return next; }); }, []); const respond = async ( action: CodebuddyElicitationAction, content?: Record, ) => { if (submitting) return; setSubmitting(true); setError(''); try { await onRespond(action, content); } catch (responseError) { setError(responseError instanceof Error ? responseError.message : String(responseError)); setSubmitting(false); } }; return (
{t('ai.codebuddy.elicitation.title')}
{elicitation.request.message || t('ai.codebuddy.elicitation.description')}
{fields.map((field, fieldIndex) => { const validationIssue = validationByField.get(field.id); const visibleValidationIssue = validationIssue && (values[field.id] !== undefined || touchedFields.has(field.id)) ? validationIssue : undefined; const errorId = visibleValidationIssue ? `${formId}-field-${fieldIndex}-error` : undefined; return (
{field.title}{field.required ? ' *' : ''} {field.description ? ( {field.description} ) : null} {field.type === 'array' && field.options.length > 0 ? (
{field.options.map((option) => { const selected = Array.isArray(values[field.id]) ? values[field.id] as unknown[] : []; const checked = selected.some((value) => Object.is(value, option.value)); const maximumReached = field.maxItems !== undefined && selected.length >= field.maxItems; return ( ); })}
) : field.options.length > 0 ? ( ) : field.type === 'boolean' ? ( ) : ( { markFieldTouched(field.id); const raw = event.target.value; const value = raw === '' ? field.type === 'string' && field.required ? '' : undefined : field.type === 'number' || field.type === 'integer' ? Number(raw) : raw; setValues((current) => ({ ...current, [field.id]: value })); }} className="h-8 w-full rounded-md border border-input bg-background px-2.5 text-xs outline-none focus-visible:ring-1 focus-visible:ring-ring" /> )} {visibleValidationIssue ? ( ) : null}
); })} {error ?

{error}

: null}
); };