From d1d83f8a2ac12ef6b3a5567bd6cd10e1877b4a89 Mon Sep 17 00:00:00 2001 From: twwu Date: Sun, 27 Apr 2025 20:17:50 +0800 Subject: [PATCH] feat: Enhance form components with hidden fields and popup properties for improved configuration --- .../base/form/form-scenarios/base/field.tsx | 25 +++++-- .../base/form/form-scenarios/base/types.ts | 6 ++ .../form/form-scenarios/input-field/field.tsx | 12 +-- .../input-field/editor/form/hidden-fields.tsx | 39 ++++++++++ .../input-field/editor/form/hooks.ts | 74 ++++++++++--------- .../input-field/editor/form/index.tsx | 52 ++++--------- .../editor/form/initial-fields.tsx | 44 +++++++++++ .../editor/form/show-all-settings.tsx | 47 +++++++----- 8 files changed, 200 insertions(+), 99 deletions(-) create mode 100644 web/app/components/rag-pipeline/components/input-field/editor/form/hidden-fields.tsx create mode 100644 web/app/components/rag-pipeline/components/input-field/editor/form/initial-fields.tsx diff --git a/web/app/components/base/form/form-scenarios/base/field.tsx b/web/app/components/base/form/form-scenarios/base/field.tsx index 9fcbd03d7d..f763ffd00c 100644 --- a/web/app/components/base/form/form-scenarios/base/field.tsx +++ b/web/app/components/base/form/form-scenarios/base/field.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React from 'react' import { type BaseConfiguration, BaseFieldType } from './types' import { withForm } from '../..' import { useStore } from '@tanstack/react-form' @@ -16,18 +16,30 @@ const BaseField = ({ render: function Render({ form, }) { - const { type, label, placeholder, variable, tooltip, showConditions, max, min, options, required, showOptional } = config + const { + type, + label, + placeholder, + variable, + tooltip, + showConditions, + max, + min, + options, + required, + showOptional, + popupProps, + } = config - const fieldValues = useStore(form.store, state => state.values) - - const isAllConditionsMet = useMemo(() => { + const isAllConditionsMet = useStore(form.store, (state) => { + const fieldValues = state.values if (!showConditions.length) return true return showConditions.every((condition) => { const { variable, value } = condition const fieldValue = fieldValues[variable as keyof typeof fieldValues] return fieldValue === value }) - }, [fieldValues, showConditions]) + }) if (!isAllConditionsMet) return <> @@ -98,6 +110,7 @@ const BaseField = ({ showOptional, }} options={options!} + popupProps={popupProps} /> )} /> diff --git a/web/app/components/base/form/form-scenarios/base/types.ts b/web/app/components/base/form/form-scenarios/base/types.ts index f66f37a8bb..3c4e50b723 100644 --- a/web/app/components/base/form/form-scenarios/base/types.ts +++ b/web/app/components/base/form/form-scenarios/base/types.ts @@ -21,6 +21,12 @@ export type NumberConfiguration = { export type SelectConfiguration = { options: Option[] // Options for select field + popupProps?: { + wrapperClassName?: string + className?: string + itemClassName?: string + title?: string + } } export type BaseConfiguration = { diff --git a/web/app/components/base/form/form-scenarios/input-field/field.tsx b/web/app/components/base/form/form-scenarios/input-field/field.tsx index e360a8d4e9..125622fa4d 100644 --- a/web/app/components/base/form/form-scenarios/input-field/field.tsx +++ b/web/app/components/base/form/form-scenarios/input-field/field.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React from 'react' import { type InputFieldConfiguration, InputFieldType } from './types' import { withForm } from '../..' import { useStore } from '@tanstack/react-form' @@ -31,18 +31,17 @@ const InputField = ({ description, options, listeners, + popupProps, } = config - const fieldValues = useStore(form.store, state => state.values) - - const isAllConditionsMet = useMemo(() => { - if (!showConditions.length) return true + const isAllConditionsMet = useStore(form.store, (state) => { + const fieldValues = state.values return showConditions.every((condition) => { const { variable, value } = condition const fieldValue = fieldValues[variable as keyof typeof fieldValues] return fieldValue === value }) - }, [fieldValues, showConditions]) + }) if (!isAllConditionsMet) return <> @@ -134,6 +133,7 @@ const InputField = ({ showOptional, }} options={options!} + popupProps={popupProps} /> )} /> diff --git a/web/app/components/rag-pipeline/components/input-field/editor/form/hidden-fields.tsx b/web/app/components/rag-pipeline/components/input-field/editor/form/hidden-fields.tsx new file mode 100644 index 0000000000..6a9c04a154 --- /dev/null +++ b/web/app/components/rag-pipeline/components/input-field/editor/form/hidden-fields.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { withForm } from '@/app/components/base/form' +import type { FormData } from './types' +import InputField from '@/app/components/base/form/form-scenarios/input-field/field' +import { useStore } from '@tanstack/react-form' +import { useHiddenConfigurations } from './hooks' + +type HiddenFieldsProps = { + initialData?: FormData +} + +const HiddenFields = ({ + initialData, +}: HiddenFieldsProps) => withForm({ + defaultValues: initialData, + render: function Render({ + form, + }) { + const options = useStore(form.store, state => state.values.options) + + const hiddenConfigurations = useHiddenConfigurations({ + options, + }) + + return ( + <> + {hiddenConfigurations.map((config, index) => { + const FieldComponent = InputField({ + initialData, + config, + }) + return + })} + + ) + }, +}) + +export default HiddenFields diff --git a/web/app/components/rag-pipeline/components/input-field/editor/form/hooks.ts b/web/app/components/rag-pipeline/components/input-field/editor/form/hooks.ts index 47fb9f0632..e88b0ff9be 100644 --- a/web/app/components/rag-pipeline/components/input-field/editor/form/hooks.ts +++ b/web/app/components/rag-pipeline/components/input-field/editor/form/hooks.ts @@ -64,40 +64,11 @@ export const useHiddenFieldNames = (type: InputVarType) => { } export const useConfigurations = (props: { - type: string, - options: string[] | undefined, setFieldValue: (fieldName: DeepKeys, value: any) => void, supportFile: boolean }) => { const { t } = useTranslation() - const { type, options, setFieldValue, supportFile } = props - - const { data: fileUploadConfigResponse } = useFileUploadConfig() - const { - imgSizeLimit, - docSizeLimit, - audioSizeLimit, - videoSizeLimit, - } = useFileSizeLimit(fileUploadConfigResponse) - - const isSelectInput = type === InputVarType.select - - const defaultSelectOptions = useMemo(() => { - if (isSelectInput && options) { - const defaultOptions = [ - { - value: '', - label: t('appDebug.variableConfig.noDefaultSelected'), - }, - ] - const otherOptions = options.map((option: string) => ({ - value: option, - label: option, - })) - return [...defaultOptions, ...otherOptions] - } - return [] - }, [isSelectInput, options, t]) + const { setFieldValue, supportFile } = props const handleTypeChange = useCallback((type: string) => { if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type as InputVarType)) { @@ -186,6 +157,41 @@ export const useConfigurations = (props: { }] }, [handleTypeChange, supportFile, t]) + return initialConfigurations +} + +export const useHiddenConfigurations = (props: { + options: string[] | undefined, +}) => { + const { t } = useTranslation() + + const { options } = props + + const { data: fileUploadConfigResponse } = useFileUploadConfig() + const { + imgSizeLimit, + docSizeLimit, + audioSizeLimit, + videoSizeLimit, + } = useFileSizeLimit(fileUploadConfigResponse) + + const defaultSelectOptions = useMemo(() => { + if (options) { + const defaultOptions = [ + { + value: '', + label: t('appDebug.variableConfig.noDefaultSelected'), + }, + ] + const otherOptions = options.map((option: string) => ({ + value: option, + label: option, + })) + return [...defaultOptions, ...otherOptions] + } + return [] + }, [options, t]) + const hiddenConfigurations = useMemo((): InputFieldConfiguration[] => { return [{ type: InputFieldType.textInput, @@ -220,6 +226,9 @@ export const useConfigurations = (props: { }], showOptional: true, options: defaultSelectOptions, + popupProps: { + wrapperClassName: 'z-40', + }, }, { type: InputFieldType.textInput, label: t('appDebug.variableConfig.placeholder'), @@ -296,8 +305,5 @@ export const useConfigurations = (props: { }] }, [defaultSelectOptions, imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit, t]) - return { - initialConfigurations, - hiddenConfigurations, - } + return hiddenConfigurations } diff --git a/web/app/components/rag-pipeline/components/input-field/editor/form/index.tsx b/web/app/components/rag-pipeline/components/input-field/editor/form/index.tsx index f5ef6d0822..8679637e4e 100644 --- a/web/app/components/rag-pipeline/components/input-field/editor/form/index.tsx +++ b/web/app/components/rag-pipeline/components/input-field/editor/form/index.tsx @@ -1,19 +1,17 @@ import { useTranslation } from 'react-i18next' import { useCallback, useState } from 'react' -import type { DeepKeys } from '@tanstack/react-form' -import { useStore } from '@tanstack/react-form' import { ChangeType } from '@/app/components/workflow/types' import { useFileUploadConfig } from '@/service/use-common' -import type { FormData, InputFieldFormProps } from './types' +import type { InputFieldFormProps } from './types' import { useAppForm } from '@/app/components/base/form' import { createInputFieldSchema } from './schema' import Toast from '@/app/components/base/toast' import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks' -import { useConfigurations, useHiddenFieldNames } from './hooks' import Divider from '@/app/components/base/divider' import ShowAllSettings from './show-all-settings' import Button from '@/app/components/base/button' -import InputField from '@/app/components/base/form/form-scenarios/input-field/field' +import InitialFields from './initial-fields' +import HiddenFields from './hidden-fields' const InputFieldForm = ({ initialData, @@ -59,25 +57,24 @@ const InputFieldForm = ({ }) const [showAllSettings, setShowAllSettings] = useState(false) - const type = useStore(inputFieldForm.store, state => state.values.type) - const options = useStore(inputFieldForm.store, state => state.values.options) - const setFieldValue = useCallback((fieldName: DeepKeys, value: any) => { - inputFieldForm.setFieldValue(fieldName, value) - }, [inputFieldForm]) - - const hiddenFieldNames = useHiddenFieldNames(type) - const { initialConfigurations, hiddenConfigurations } = useConfigurations({ - type, - options, - setFieldValue, + const InitialFieldsComp = InitialFields({ + initialData, supportFile, }) + const HiddenFieldsComp = HiddenFields({ + initialData, + }) const handleShowAllSettings = useCallback(() => { setShowAllSettings(true) }, []) + const ShowAllSettingComp = ShowAllSettings({ + initialData, + handleShowAllSettings, + }) + return (
- {initialConfigurations.map((config, index) => { - const FieldComponent = InputField({ - initialData, - config, - }) - return - })} + {!showAllSettings && ( - + )} {showAllSettings && ( - <> - {hiddenConfigurations.map((config, index) => { - const FieldComponent = InputField({ - initialData, - config, - }) - return - })} - + )}
diff --git a/web/app/components/rag-pipeline/components/input-field/editor/form/initial-fields.tsx b/web/app/components/rag-pipeline/components/input-field/editor/form/initial-fields.tsx new file mode 100644 index 0000000000..85181188af --- /dev/null +++ b/web/app/components/rag-pipeline/components/input-field/editor/form/initial-fields.tsx @@ -0,0 +1,44 @@ +import React, { useCallback } from 'react' +import { withForm } from '@/app/components/base/form' +import type { FormData } from './types' +import InputField from '@/app/components/base/form/form-scenarios/input-field/field' +import type { DeepKeys } from '@tanstack/react-form' +import { useConfigurations } from './hooks' + +type InitialFieldsProps = { + initialData?: FormData + supportFile: boolean +} + +const InitialFields = ({ + initialData, + supportFile, +}: InitialFieldsProps) => withForm({ + defaultValues: initialData, + render: function Render({ + form, + }) { + const setFieldValue = useCallback((fieldName: DeepKeys, value: any) => { + form.setFieldValue(fieldName, value) + }, [form]) + + const initialConfigurations = useConfigurations({ + setFieldValue, + supportFile, + }) + + return ( + <> + {initialConfigurations.map((config, index) => { + const FieldComponent = InputField({ + initialData, + config, + }) + return + })} + + ) + }, +}) + +export default InitialFields diff --git a/web/app/components/rag-pipeline/components/input-field/editor/form/show-all-settings.tsx b/web/app/components/rag-pipeline/components/input-field/editor/form/show-all-settings.tsx index 0a55c70ef1..752f694a73 100644 --- a/web/app/components/rag-pipeline/components/input-field/editor/form/show-all-settings.tsx +++ b/web/app/components/rag-pipeline/components/input-field/editor/form/show-all-settings.tsx @@ -1,30 +1,43 @@ +import React from 'react' +import { withForm } from '@/app/components/base/form' +import type { FormData } from './types' +import { useStore } from '@tanstack/react-form' +import { useHiddenFieldNames } from './hooks' import { useTranslation } from 'react-i18next' import { RiArrowRightSLine } from '@remixicon/react' type ShowAllSettingsProps = { - description: string + initialData?: FormData handleShowAllSettings: () => void } const ShowAllSettings = ({ - description, + initialData, handleShowAllSettings, -}: ShowAllSettingsProps) => { - const { t } = useTranslation() +}: ShowAllSettingsProps) => withForm({ + defaultValues: initialData, + render: function Render({ + form, + }) { + const { t } = useTranslation() + const type = useStore(form.store, state => state.values.type) - return ( -
-
- - {t('appDebug.variableConfig.showAllSettings')} - - - {description} - + const hiddenFieldNames = useHiddenFieldNames(type) + + return ( +
+
+ + {t('appDebug.variableConfig.showAllSettings')} + + + {hiddenFieldNames} + +
+
- -
- ) -} + ) + }, +}) export default ShowAllSettings