From a18bcf39570a93bd98fecdc8b00faaec57bd51e9 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 8 Aug 2025 14:22:01 +0800 Subject: [PATCH] feat: pass current value to form input --- .../components/base/action-button/index.css | 4 + .../components/base/action-button/index.tsx | 1 + .../components/base/prompt-editor/index.tsx | 6 +- .../plugins/draggable-plugin/index.tsx | 2 +- .../plugins/hitl-input-block/component-ui.tsx | 96 ++++++++++++++++--- .../plugins/hitl-input-block/component.tsx | 30 +++++- .../hitl-input-block-replacement-block.tsx | 15 +-- .../plugins/hitl-input-block/input-field.tsx | 34 ++++++- .../plugins/hitl-input-block/node.tsx | 46 +++++++-- .../components/base/prompt-editor/types.ts | 4 + .../human-input/components/form-content.tsx | 10 ++ .../workflow/nodes/human-input/panel.tsx | 4 + .../workflow/nodes/human-input/types.ts | 23 +++-- .../nodes/human-input/use-form-content.ts | 10 +- 14 files changed, 240 insertions(+), 45 deletions(-) diff --git a/web/app/components/base/action-button/index.css b/web/app/components/base/action-button/index.css index 3c1a10b86f..4ede34aeb5 100644 --- a/web/app/components/base/action-button/index.css +++ b/web/app/components/base/action-button/index.css @@ -26,6 +26,10 @@ @apply p-0.5 w-6 h-6 rounded-lg } + .action-btn-s { + @apply w-5 h-5 rounded-[6px] + } + .action-btn-xs { @apply p-0 w-4 h-4 rounded } diff --git a/web/app/components/base/action-button/index.tsx b/web/app/components/base/action-button/index.tsx index c90d1a8de8..b03a859436 100644 --- a/web/app/components/base/action-button/index.tsx +++ b/web/app/components/base/action-button/index.tsx @@ -17,6 +17,7 @@ const actionButtonVariants = cva( variants: { size: { xs: 'action-btn-xs', + s: 'action-btn-s', m: 'action-btn-m', l: 'action-btn-l', xl: 'action-btn-xl', diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index a80c15c897..5dd62f321b 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -262,7 +262,11 @@ const PromptEditor: FC = ({ hitlInputBlock?.show && ( <> - + ) } diff --git a/web/app/components/base/prompt-editor/plugins/draggable-plugin/index.tsx b/web/app/components/base/prompt-editor/plugins/draggable-plugin/index.tsx index 692d3f76eb..cba7dcd8a6 100644 --- a/web/app/components/base/prompt-editor/plugins/draggable-plugin/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/draggable-plugin/index.tsx @@ -55,7 +55,7 @@ export default function DraggableBlockPlugin({ menuRef={menuRef as any} targetLineRef={targetLineRef as any} menuComponent={ - isSupportDrag ?
+ isSupportDrag ?
: null } diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/component-ui.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/component-ui.tsx index f694adeabf..8b14c6673c 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/component-ui.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/component-ui.tsx @@ -1,24 +1,63 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useCallback, useEffect, useRef } from 'react' import { VariableX } from '../../../icons/src/vender/workflow' import { VarBlockIcon } from '@/app/components/workflow/block-icon' -import { BlockEnum } from '@/app/components/workflow/types' +import { BlockEnum, InputVarType } from '@/app/components/workflow/types' import { Variable02 } from '../../../icons/src/vender/solid/development' import { useTranslation } from 'react-i18next' +import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' +import ActionButton from '../../../action-button' +import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' +import InputField from './input-field' +import { useBoolean } from 'ahooks' +import Modal from '../../../modal' type Props = { - nodeName: string + nodeTitle: string varName: string isSelected: boolean + formInput?: FormInputItem + onChange: (input: FormInputItem) => void } const ComponentUI: FC = ({ - nodeName, + nodeTitle, varName, // isSelected, + formInput = { + type: InputVarType.textInput, + output_variable_name: varName, + placeholder: { + type: 'const', + selector: [], + value: '', + }, + }, + onChange, }) => { const { t } = useTranslation() + const [isShowEditModal, { + setTrue: showEditModal, + setFalse: hideEditModal, + }] = useBoolean(false) + + const editBtnRef = useRef(null) + useEffect(() => { + const editBtn = editBtnRef.current + if (editBtn) + editBtn.addEventListener('click', showEditModal) + + return () => { + if (editBtn) + editBtn.removeEventListener('click', showEditModal) + } + }, []) + + const handleChange = useCallback((newPayload: FormInputItem) => { + onChange(newPayload) + hideEditModal() + }, [onChange]) return (
= ({
-
-
- -
{nodeName}
+
+ {/* Node info */} +
+
+ +
{nodeTitle}
+
+
/
+
+ +
{varName}
+
-
/
-
- -
{varName}
+ + {/* Actions */} +
+
+ + + +
+ +
+ + + +
+ + {isShowEditModal && ( + + + + )}
) } diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/component.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/component.tsx index 8f1ce5da06..e297dba231 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/component.tsx @@ -1,29 +1,51 @@ -import type { FC } from 'react' +import { type FC, useCallback } from 'react' import { useSelectOrDelete } from '../../hooks' import { DELETE_HITL_INPUT_BLOCK_COMMAND } from './' import ComponentUi from './component-ui' +import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' +import produce from 'immer' type QueryBlockComponentProps = { nodeKey: string - nodeName: string + nodeTitle: string varName: string + formInputs?: FormInputItem[] + onChange: (inputs: FormInputItem[]) => void } const HITLInputComponent: FC = ({ nodeKey, - nodeName, + nodeTitle, varName, + formInputs = [], + onChange, }) => { const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_HITL_INPUT_BLOCK_COMMAND) + const payload = formInputs.find(item => item.output_variable_name === varName) + const handleChange = useCallback((newPayload: FormInputItem) => { + if(!payload) { + onChange([...formInputs, newPayload]) + return + } + if(payload?.output_variable_name !== newPayload.output_variable_name) { + onChange(produce(formInputs, (draft) => { + draft.splice(draft.findIndex(item => item.output_variable_name === payload?.output_variable_name), 1, newPayload) + })) + return + } + onChange(formInputs.map(item => item.output_variable_name === varName ? newPayload : item)) + }, [onChange]) return (
) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx index 14e8f9dd19..158fd09cfb 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx @@ -8,7 +8,7 @@ import { $applyNodeReplacement } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { decoratorTransform } from '../../utils' -import type { QueryBlockType } from '../../types' +import type { HITLInputBlockType } from '../../types' import { $createHITLInputNode } from './node' import { QueryBlockNode, @@ -19,8 +19,11 @@ import { HITL_INPUT_REG } from '@/config' const REGEX = new RegExp(HITL_INPUT_REG) const HITLInputReplacementBlock = ({ - onInsert, -}: QueryBlockType) => { + // onInsert, + nodeTitle, + formInputs, + onFormInputsChange, +}: HITLInputBlockType) => { const [editor] = useLexicalComposerContext() useEffect(() => { @@ -29,11 +32,9 @@ const HITLInputReplacementBlock = ({ }, [editor]) const createHITLInputBlockNode = useCallback((textNode: TextNode): QueryBlockNode => { - if (onInsert) - onInsert() const varName = textNode.getTextContent().split('.')[1].replace(/#}}$/, '') - return $applyNodeReplacement($createHITLInputNode(varName)) - }, [onInsert]) + return $applyNodeReplacement($createHITLInputNode(varName, nodeTitle, formInputs || [], onFormInputsChange!)) + }, [nodeTitle, formInputs, onFormInputsChange]) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx index 057514fb4d..28c8b6a7db 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx @@ -1,14 +1,29 @@ -import React from 'react' +import React, { useCallback, useState } from 'react' import Input from '@/app/components/base/input' import PromptEditor from '@/app/components/base/prompt-editor' import TagLabel from './tag-label' import Button from '../../../button' import { useTranslation } from 'react-i18next' import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' +import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' const i18nPrefix = 'workflow.nodes.humanInput.insertInputField' -const InputField: React.FC = () => { + +type Props = { + payload: FormInputItem + onChange: (newPayload: FormInputItem) => void + onCancel: () => void +} +const InputField: React.FC = ({ + payload, + onChange, + onCancel, +}) => { const { t } = useTranslation() + const [tempPayload, setTempPayload] = useState(payload) + const handleSave = useCallback(() => { + onChange(tempPayload) + }, [tempPayload]) return (
{t(`${i18nPrefix}.title`)}
@@ -19,6 +34,10 @@ const InputField: React.FC = () => { { + setTempPayload(prev => ({ ...prev, output_variable_name: e.target.value })) + }} />
@@ -37,14 +56,21 @@ const InputField: React.FC = () => { {t(`${i18nPrefix}.users`)}
{t(`${i18nPrefix}.prePopulateFieldPlaceholderEnd`)}
-
} +
+ } + onChange={ + (newValue) => { + setTempPayload(prev => ({ ...prev, prePopulateField: newValue })) + } + } />
- +
{/* user actions */} diff --git a/web/app/components/workflow/nodes/human-input/types.ts b/web/app/components/workflow/nodes/human-input/types.ts index 500f4d9f9b..c233c1a46e 100644 --- a/web/app/components/workflow/nodes/human-input/types.ts +++ b/web/app/components/workflow/nodes/human-input/types.ts @@ -3,15 +3,7 @@ import type { CommonNodeType, InputVarType, ValueSelector, Variable } from '@/ap export type HumanInputNodeType = CommonNodeType & { delivery_methods: DeliveryMethod[] form_content: string - form_input: { - type: InputVarType - output_variable_name: string - placeholder?: { // only text-input and paragraph support placeholder - type: 'variable' | 'const', - selector: ValueSelector - value: string - } - }[] + inputs: FormInputItem[] user_actions: UserAction[] timeout: number timeout_unit: 'hour' | 'day' @@ -47,6 +39,19 @@ export type DeliveryMethod = { config?: EmailConfig } +export type FormInputItemPlaceholder = { + type: 'variable' | 'const', + selector: ValueSelector + value: string +} + +export type FormInputItem = { + type: InputVarType + output_variable_name: string + // only text-input and paragraph support placeholder + placeholder?: FormInputItemPlaceholder +} + export enum UserActionButtonType { Primary = 'primary', Default = 'default', diff --git a/web/app/components/workflow/nodes/human-input/use-form-content.ts b/web/app/components/workflow/nodes/human-input/use-form-content.ts index 1fbdf02a32..820241b34f 100644 --- a/web/app/components/workflow/nodes/human-input/use-form-content.ts +++ b/web/app/components/workflow/nodes/human-input/use-form-content.ts @@ -1,5 +1,5 @@ import useNodeCrud from '../_base/hooks/use-node-crud' -import type { HumanInputNodeType } from './types' +import type { FormInputItem, HumanInputNodeType } from './types' const useFormContent = (id: string, payload: HumanInputNodeType) => { const { inputs, setInputs } = useNodeCrud(id, payload) @@ -9,8 +9,16 @@ const useFormContent = (id: string, payload: HumanInputNodeType) => { form_content: value, }) } + + const handleFormInputsChange = (formInputs: FormInputItem[]) => { + setInputs({ + ...inputs, + inputs: formInputs, + }) + } return { handleFormContentChange, + handleFormInputsChange, } }