import { useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic'; import DynamicFormItemComponent from '@/app/home/components/dynamic-form/DynamicFormItemComponent'; import { extractI18nObject } from '@/i18n/I18nProvider'; /** * N8n认证表单组件 * 根据选择的认证类型动态显示相应的表单项 */ export default function N8nAuthFormComponent({ itemConfigList, onSubmit, initialValues, }: { itemConfigList: IDynamicFormItemSchema[]; onSubmit?: (val: object) => unknown; initialValues?: Record; }) { // 当前选择的认证类型 const [authType, setAuthType] = useState( initialValues?.['auth-type'] || 'none', ); // 根据 itemConfigList 动态生成 zod schema const formSchema = z.object( itemConfigList.reduce( (acc, item) => { let fieldSchema; switch (item.type) { case 'integer': fieldSchema = z.number(); break; case 'float': fieldSchema = z.number(); break; case 'boolean': fieldSchema = z.boolean(); break; case 'string': fieldSchema = z.string(); break; case 'array[string]': fieldSchema = z.array(z.string()); break; case 'select': fieldSchema = z.string(); break; case 'llm-model-selector': fieldSchema = z.string(); break; case 'prompt-editor': fieldSchema = z.array( z.object({ content: z.string(), role: z.string(), }), ); break; default: fieldSchema = z.string(); } if ( item.required && (fieldSchema instanceof z.ZodString || fieldSchema instanceof z.ZodArray) ) { fieldSchema = fieldSchema.min(1, { message: '此字段为必填项' }); } return { ...acc, [item.name]: fieldSchema, }; }, {} as Record, ), ); type FormValues = z.infer; const form = useForm({ resolver: zodResolver(formSchema), defaultValues: itemConfigList.reduce((acc, item) => { // 优先使用 initialValues,如果没有则使用默认值 const value = initialValues?.[item.name] ?? item.default; return { ...acc, [item.name]: value, }; }, {} as FormValues), }); const isInitialMount = useRef(true); const previousInitialValues = useRef(initialValues); // Stable ref for onSubmit to avoid re-triggering the effect when the // parent passes a new closure on every render (matches DynamicFormComponent pattern). const onSubmitRef = useRef(onSubmit); onSubmitRef.current = onSubmit; // 当 initialValues 变化时更新表单值 useEffect(() => { // Skip the first mount — defaultValues already handles it if (isInitialMount.current) { isInitialMount.current = false; previousInitialValues.current = initialValues; return; } // Deep compare to avoid reacting to parent re-renders that pass // the same values back (e.g. after our own onSubmit emission). const hasRealChange = JSON.stringify(previousInitialValues.current) !== JSON.stringify(initialValues); if (initialValues && hasRealChange) { // 合并默认值和初始值 const mergedValues = itemConfigList.reduce( (acc, item) => { acc[item.name] = initialValues[item.name] ?? item.default; return acc; }, {} as Record, ); Object.entries(mergedValues).forEach(([key, value]) => { form.setValue(key as keyof FormValues, value); }); // 更新认证类型 setAuthType((mergedValues['auth-type'] as string) || 'none'); previousInitialValues.current = initialValues; } }, [initialValues, form, itemConfigList]); // 监听表单值变化 useEffect(() => { // Emit initial form values on mount so the parent form's // initializedStagesRef registers this stage (matches DynamicFormComponent). const formValues = form.getValues(); const initialFinalValues = itemConfigList.reduce( (acc, item) => { acc[item.name] = formValues[item.name] ?? item.default; return acc; }, {} as Record, ); onSubmitRef.current?.(initialFinalValues); previousInitialValues.current = initialFinalValues as Record< string, string >; const subscription = form.watch((value, { name }) => { // 如果认证类型变化,更新状态 if (name === 'auth-type') { setAuthType(value['auth-type'] as string); } // 获取完整的表单值,确保包含所有默认值 const formValues = form.getValues(); const finalValues = itemConfigList.reduce( (acc, item) => { acc[item.name] = formValues[item.name] ?? item.default; return acc; }, {} as Record, ); onSubmitRef.current?.(finalValues); previousInitialValues.current = finalValues as Record; }); return () => subscription.unsubscribe(); }, [form, itemConfigList]); // 根据认证类型过滤表单项 const filteredConfigList = itemConfigList.filter((config) => { // 始终显示webhook-url、auth-type、timeout和output-key if ( ['webhook-url', 'auth-type', 'timeout', 'output-key'].includes( config.name, ) ) { return true; } // 根据认证类型显示相应的表单项 if (authType === 'basic' && config.name.startsWith('basic-')) { return true; } if (authType === 'jwt' && config.name.startsWith('jwt-')) { return true; } if (authType === 'header' && config.name.startsWith('header-')) { return true; } return false; }); return (
{filteredConfigList.map((config) => ( ( {extractI18nObject(config.label)}{' '} {config.required && *} {config.description && (

{extractI18nObject(config.description)}

)}
)} /> ))}
); }