diff --git a/packages/builtin-tool-user-interaction/package.json b/packages/builtin-tool-user-interaction/package.json index 99a6a8634a..f129fe230e 100644 --- a/packages/builtin-tool-user-interaction/package.json +++ b/packages/builtin-tool-user-interaction/package.json @@ -11,5 +11,10 @@ "main": "./src/index.ts", "devDependencies": { "@lobechat/types": "workspace:*" + }, + "peerDependencies": { + "@lobehub/ui": "^5", + "antd": "^6", + "react": "^19" } } diff --git a/packages/builtin-tool-user-interaction/src/client/Intervention/AskUserQuestion/index.tsx b/packages/builtin-tool-user-interaction/src/client/Intervention/AskUserQuestion/index.tsx index 163199665b..d708f920d0 100644 --- a/packages/builtin-tool-user-interaction/src/client/Intervention/AskUserQuestion/index.tsx +++ b/packages/builtin-tool-user-interaction/src/client/Intervention/AskUserQuestion/index.tsx @@ -1,33 +1,154 @@ 'use client'; import type { BuiltinInterventionProps } from '@lobechat/types'; -import { memo } from 'react'; +import { Flexbox, Text } from '@lobehub/ui'; +import { Button, Input, Select } from 'antd'; +import { memo, useCallback, useState } from 'react'; -import type { AskUserQuestionArgs } from '../../../types'; +import type { AskUserQuestionArgs, InteractionField } from '../../../types'; + +const FieldInput = memo<{ + field: InteractionField; + onChange: (key: string, value: string | string[]) => void; + value?: string | string[]; +}>(({ field, value, onChange }) => { + switch (field.kind) { + case 'textarea': { + return ( + onChange(field.key, e.target.value)} + /> + ); + } + case 'select': { + return ( + ({ label: o.label, value: o.value }))} + placeholder={field.placeholder} + style={{ width: '100%' }} + value={value as string[]} + onChange={(v) => onChange(field.key, v)} + /> + ); + } + default: { + return ( + onChange(field.key, e.target.value)} + /> + ); + } + } +}); const AskUserQuestionIntervention = memo>( - ({ args }) => { + ({ args, interactionMode, onInteractionAction }) => { const { question } = args; + const isCustom = interactionMode === 'custom'; + + const initialValues: Record = {}; + if (question.fields) { + for (const field of question.fields) { + if (field.value !== undefined) initialValues[field.key] = field.value; + } + } + + const [formData, setFormData] = useState>(initialValues); + const [submitting, setSubmitting] = useState(false); + + const handleFieldChange = useCallback((key: string, value: string | string[]) => { + setFormData((prev) => ({ ...prev, [key]: value })); + }, []); + + const handleSubmit = useCallback(async () => { + if (!onInteractionAction) return; + setSubmitting(true); + try { + await onInteractionAction({ payload: formData, type: 'submit' }); + } finally { + setSubmitting(false); + } + }, [formData, onInteractionAction]); + + const handleSkip = useCallback(async () => { + if (!onInteractionAction) return; + await onInteractionAction({ type: 'skip' }); + }, [onInteractionAction]); + + const isSubmitDisabled = question.fields?.some((f) => f.required && !formData[f.key]) ?? false; + + if (!isCustom) { + return ( + + {question.prompt} + {question.fields && question.fields.length > 0 && ( +
    + {question.fields.map((field) => ( +
  • + {field.label} + {field.required && ' *'} +
  • + ))} +
+ )} +
+ ); + } return ( -
-

{question.prompt}

+ + {question.prompt} {question.description && ( -

+ {question.description} -

+ )} {question.fields && question.fields.length > 0 && ( -
    + {question.fields.map((field) => ( -
  • - {field.label} - {field.required && ' *'} -
  • + + + {field.label} + {field.required && *} + + + ))} -
+
)} -
+ + + + + ); }, ); diff --git a/packages/builtin-tool-user-interaction/src/manifest.ts b/packages/builtin-tool-user-interaction/src/manifest.ts index a09a297195..c051918768 100644 --- a/packages/builtin-tool-user-interaction/src/manifest.ts +++ b/packages/builtin-tool-user-interaction/src/manifest.ts @@ -8,6 +8,7 @@ export const UserInteractionManifest: BuiltinToolManifest = { { description: 'Present a question to the user with either structured form fields or freeform input. Returns the interaction request in pending state.', + humanIntervention: 'required', name: UserInteractionApiName.askUserQuestion, parameters: { properties: {