import React, { useState, useCallback, useMemo } from 'react'; import Form from '@rjsf/core'; import { customizeValidator } from '@rjsf/validator-ajv8'; import { rjsfTheme, FieldInteractionProvider } from './RjsfTheme'; import type { RJSFSchema, UiSchema } from '@rjsf/utils'; import { Button } from '../ui/Button'; import type { FormMessage } from '../../stores/agentStore'; import { useAgentStore } from '../../stores/agentStore'; import SubmitButtonContent from './SubmitButtonContent'; import { FiCheckCircle, FiXCircle, FiLoader } from 'react-icons/fi'; const typingSelector = (state: any) => state.isTyping; interface FormMessageBubbleProps { formMessage: FormMessage; className?: string; } interface RJSFFormProps { message: FormMessage; onSubmit: (formData: JsonRecord) => Promise; onCancel: () => void; jsonSchema: RJSFSchema; uiSchema: UiSchema; } type JsonRecord = Record; type RJSFEvent = { formData?: JsonRecord }; /** * Converts FormMessage field configuration to JSON Schema format */ const convertToJsonSchema = (formMessage: FormMessage): RJSFSchema => { const properties: Record = {}; const required: string[] = []; formMessage.formConfig.fields.forEach((field) => { if (field.required) { required.push(field.name); } const fieldSchema: any = { title: field.label, description: field.helpText || field.placeholder, }; switch (field.type) { case 'text': fieldSchema.type = 'string'; break; case 'number': case 'currency': case 'percentage': fieldSchema.type = 'number'; if (field.validation?.min !== undefined) fieldSchema.minimum = field.validation.min; if (field.validation?.max !== undefined) fieldSchema.maximum = field.validation.max; break; case 'textarea': fieldSchema.type = 'string'; break; case 'checkbox': fieldSchema.type = 'boolean'; break; case 'select': fieldSchema.type = 'string'; if (field.options) { fieldSchema.enum = field.options.map((opt) => opt.value); fieldSchema.enumNames = field.options.map((opt) => opt.label); } break; default: fieldSchema.type = 'string'; } if (field.validation?.minLength) fieldSchema.minLength = field.validation.minLength; if (field.validation?.maxLength) fieldSchema.maxLength = field.validation.maxLength; if (field.validation?.pattern) fieldSchema.pattern = field.validation.pattern; if (field.defaultValue !== undefined) fieldSchema.default = field.defaultValue; const errorMessage: Record = {}; if (field.required) { errorMessage.required = `${field.label} is required`; } if (field.validation?.minLength) { errorMessage.minLength = `${field.label} must be at least ${field.validation.minLength} characters`; } if (field.validation?.maxLength) { errorMessage.maxLength = `${field.label} must be no more than ${field.validation.maxLength} characters`; } if (field.validation?.pattern) { errorMessage.pattern = `${field.label} has invalid format`; } if (field.validation?.min !== undefined) { if (field.validation?.max !== undefined) { errorMessage.minimum = `${field.label} must be between ${field.validation.min} and ${field.validation.max}`; } else { errorMessage.minimum = `${field.label} must be at least ${field.validation.min}`; } } if ( field.validation?.max !== undefined && field.validation?.min === undefined ) { errorMessage.maximum = `${field.label} must be no more than ${field.validation.max}`; } if (Object.keys(errorMessage).length > 0) { fieldSchema.errorMessage = errorMessage; } properties[field.name] = fieldSchema; }); const schema = { type: 'object' as const, title: formMessage.formConfig.title, description: formMessage.formConfig.description, properties, required: required.length > 0 ? required : undefined, additionalProperties: true, }; return schema; }; /** * Converts FormMessage field configuration to UI Schema format */ const convertToUiSchema = (formMessage: FormMessage): UiSchema => { const uiSchema: UiSchema = {}; formMessage.formConfig.fields.forEach((field) => { switch (field.type) { case 'textarea': uiSchema[field.name] = { 'ui:widget': 'TextareaWidget' }; break; case 'checkbox': uiSchema[field.name] = { 'ui:widget': 'CheckboxWidget' }; break; case 'select': uiSchema[field.name] = { 'ui:widget': 'SelectWidget' }; break; default: uiSchema[field.name] = { 'ui:widget': 'TextWidget' }; } if (field.placeholder) { uiSchema[field.name] = { ...uiSchema[field.name], 'ui:placeholder': field.placeholder, }; } }); return uiSchema; }; /** * RJSF form component using our custom theme */ function RJSFForm({ message, onSubmit, onCancel, jsonSchema, uiSchema, }: RJSFFormProps) { const [formData, setFormData] = useState( (message.partialInput as JsonRecord) || {} ); const [isSubmitting, setIsSubmitting] = useState(false); const isTyping = useAgentStore(typingSelector); const completionState = message.completionState || 'active'; const isCompleted = completionState === 'completed'; const isFailed = completionState === 'failed'; const isFormSubmitting = completionState === 'submitting'; const isProcessing = isSubmitting || isTyping || isFormSubmitting; const validator = useMemo( () => customizeValidator({ ajvOptionsOverrides: { removeAdditional: 'all', strict: false, allErrors: true, }, }), [] ); const handleSubmit = useCallback( (data: RJSFEvent, event: React.FormEvent): void => { event.preventDefault(); setIsSubmitting(true); Promise.resolve(onSubmit((data.formData || {}) as JsonRecord)).finally( () => { setIsSubmitting(false); } ); }, [onSubmit] ); const handleChange = useCallback( (data: RJSFEvent): void => { if (!isCompleted && !isFailed) { setFormData((data.formData || {}) as JsonRecord); } }, [isCompleted, isFailed] ); const formatTimestamp = useCallback((timestamp: number) => { return new Date(timestamp).toLocaleString(); }, []); if (isCompleted) { return (

{message.formConfig?.title || 'Complete Form'}

{message.formConfig?.description && (

{message.formConfig.description}

)}
Form Submitted Successfully {message.completedAt && (

{formatTimestamp(message.completedAt)}

)}
{message.completionResult?.message && (

{message.completionResult.message}

)}
); } if (isFailed) { return (

{message.formConfig?.title || 'Complete Form'}

{message.formConfig?.description && (

{message.formConfig.description}

)}

Form Submission Failed

There was an issue processing your form submission. Please review the details below and try again.

{message.completionResult?.message && (

{message.completionResult.message}

)}
You can modify your responses and try submitting again
Retry Form Submission
); } return (

{message.formConfig?.title || 'Complete Form'}

{message.formConfig?.description && (

{message.formConfig.description}

)} {isFormSubmitting && (
)}
); } /** * Wrapper component that maintains backwards compatibility with existing usage */ export function FormMessageBubble({ formMessage, className, }: FormMessageBubbleProps) { const { processFormSubmission } = useAgentStore(); const handleSubmit = useCallback( async (formData: any) => { try { await processFormSubmission(formMessage.id, formData); } catch (error) {} }, [processFormSubmission, formMessage.id] ); const handleCancel = useCallback(() => {}, []); const jsonSchema = useMemo( () => formMessage.jsonSchema || convertToJsonSchema(formMessage), [formMessage] ); const uiSchema = useMemo( () => formMessage.uiSchema || convertToUiSchema(formMessage), [formMessage] ); return (
); }