From b4a2a6b7f8e278800ab228fcf55cb82c3051808a Mon Sep 17 00:00:00 2001 From: Alex Andru Date: Fri, 4 Jul 2025 04:48:55 +0200 Subject: [PATCH] feat: add elicitation request --- client/src/components/ElicitationRequest.tsx | 173 +++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 client/src/components/ElicitationRequest.tsx diff --git a/client/src/components/ElicitationRequest.tsx b/client/src/components/ElicitationRequest.tsx new file mode 100644 index 00000000..dc3f5962 --- /dev/null +++ b/client/src/components/ElicitationRequest.tsx @@ -0,0 +1,173 @@ +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import DynamicJsonForm from "./DynamicJsonForm"; +import JsonView from "./JsonView"; +import { JsonSchemaType, JsonValue } from "@/utils/jsonUtils"; +import { generateDefaultValue } from "@/utils/schemaUtils"; +import { + PendingElicitationRequest, + ElicitationResponse, +} from "./ElicitationTab"; +import Ajv from "ajv"; + +export type ElicitationRequestProps = { + request: PendingElicitationRequest; + onResolve: (id: number, response: ElicitationResponse) => void; +}; + +const ElicitationRequest = ({ + request, + onResolve, +}: ElicitationRequestProps) => { + const [formData, setFormData] = useState({}); + const [validationError, setValidationError] = useState(null); + + useEffect(() => { + const defaultValue = generateDefaultValue(request.request.requestedSchema); + setFormData(defaultValue); + setValidationError(null); + }, [request.request.requestedSchema]); + + const validateEmailFormat = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const validateFormData = ( + data: JsonValue, + schema: JsonSchemaType, + ): boolean => { + if ( + schema.type === "object" && + schema.properties && + typeof data === "object" && + data !== null + ) { + const dataObj = data as Record; + + if (Array.isArray(schema.required)) { + for (const field of schema.required) { + const value = dataObj[field]; + if (value === undefined || value === null || value === "") { + setValidationError(`Required field missing: ${field}`); + return false; + } + } + } + + for (const [fieldName, fieldValue] of Object.entries(dataObj)) { + const fieldSchema = schema.properties[fieldName]; + if ( + fieldSchema && + fieldSchema.format === "email" && + typeof fieldValue === "string" + ) { + if (!validateEmailFormat(fieldValue)) { + setValidationError(`Invalid email format: ${fieldName}`); + return false; + } + } + } + } + + return true; + }; + + const handleAccept = () => { + try { + if (!validateFormData(formData, request.request.requestedSchema)) { + return; + } + + const ajv = new Ajv(); + const validate = ajv.compile(request.request.requestedSchema); + const isValid = validate(formData); + + if (!isValid) { + const errorMessage = ajv.errorsText(validate.errors); + setValidationError(errorMessage); + return; + } + + onResolve(request.id, { + action: "accept", + content: formData as Record, + }); + } catch (error) { + setValidationError( + error instanceof Error ? error.message : "Validation failed", + ); + } + }; + + const handleReject = () => { + onResolve(request.id, { action: "reject" }); + }; + + const handleCancel = () => { + onResolve(request.id, { action: "cancel" }); + }; + + const schemaTitle = + request.request.requestedSchema.title || "Information Request"; + const schemaDescription = request.request.requestedSchema.description; + + return ( +
+
+
+

{schemaTitle}

+

{request.request.message}

+ {schemaDescription && ( +

{schemaDescription}

+ )} +
+
Request Schema:
+ +
+
+
+ +
+
+

Response Form

+ { + setFormData(newValue); + setValidationError(null); + }} + /> + + {validationError && ( +
+
+ Validation Error: {validationError} +
+
+ )} +
+ +
+ + + +
+
+
+ ); +}; + +export default ElicitationRequest;