From 89963ecf597c0181d722281c2a4b22990e6bcfb4 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 8 Aug 2025 15:08:02 +0800 Subject: [PATCH] feat: can remove --- .../components/base/prompt-editor/index.tsx | 1 + .../plugins/hitl-input-block/component-ui.tsx | 17 +++++++- .../plugins/hitl-input-block/component.tsx | 7 +++- .../hitl-input-block-replacement-block.tsx | 11 +++++- .../plugins/hitl-input-block/node.tsx | 39 ++++++++++++++++--- .../components/base/prompt-editor/types.ts | 1 + .../human-input/components/form-content.tsx | 6 +++ .../workflow/nodes/human-input/panel.tsx | 4 ++ .../nodes/human-input/use-form-content.ts | 28 +++++++++++-- web/app/test/page.tsx | 11 ------ 10 files changed, 100 insertions(+), 25 deletions(-) delete mode 100644 web/app/test/page.tsx diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 5dd62f321b..340e671489 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -266,6 +266,7 @@ const PromptEditor: FC = ({ nodeTitle={hitlInputBlock.nodeTitle} formInputs={hitlInputBlock.formInputs} onFormInputsChange={hitlInputBlock.onFormInputsChange} + onFormInputItemRemove={hitlInputBlock.onFormInputItemRemove} /> ) 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 8b14c6673c..0c99274209 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 @@ -19,6 +19,7 @@ type Props = { isSelected: boolean formInput?: FormInputItem onChange: (input: FormInputItem) => void + onRemove: (varName: string) => void } const ComponentUI: FC = ({ @@ -35,6 +36,7 @@ const ComponentUI: FC = ({ }, }, onChange, + onRemove, }) => { const { t } = useTranslation() const [isShowEditModal, { @@ -42,6 +44,7 @@ const ComponentUI: FC = ({ setFalse: hideEditModal, }] = useBoolean(false) + // Lexical delegate the click make it unable to add click by the method of react const editBtnRef = useRef(null) useEffect(() => { const editBtn = editBtnRef.current @@ -54,6 +57,18 @@ const ComponentUI: FC = ({ } }, []) + const removeBtnRef = useRef(null) + useEffect(() => { + const removeBtn = removeBtnRef.current + if (removeBtn) + removeBtn.addEventListener('click', () => onRemove(varName)) + + return () => { + if (removeBtn) + removeBtn.removeEventListener('click', () => onRemove(varName)) + } + }, [onRemove, varName]) + const handleChange = useCallback((newPayload: FormInputItem) => { onChange(newPayload) hideEditModal() @@ -93,7 +108,7 @@ const ComponentUI: FC = ({ -
+
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 e297dba231..ac1fcccb3b 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 @@ -5,20 +5,22 @@ import ComponentUi from './component-ui' import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' import produce from 'immer' -type QueryBlockComponentProps = { +type Props = { nodeKey: string nodeTitle: string varName: string formInputs?: FormInputItem[] onChange: (inputs: FormInputItem[]) => void + onRemove: (varName: string) => void } -const HITLInputComponent: FC = ({ +const HITLInputComponent: FC = ({ nodeKey, nodeTitle, varName, formInputs = [], onChange, + onRemove, }) => { const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_HITL_INPUT_BLOCK_COMMAND) const payload = formInputs.find(item => item.output_variable_name === varName) @@ -46,6 +48,7 @@ const HITLInputComponent: FC = ({ isSelected={isSelected} formInput={payload} onChange={handleChange} + onRemove={onRemove} />
) 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 158fd09cfb..a5fc453c33 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 @@ -23,6 +23,7 @@ const HITLInputReplacementBlock = ({ nodeTitle, formInputs, onFormInputsChange, + onFormInputItemRemove, }: HITLInputBlockType) => { const [editor] = useLexicalComposerContext() @@ -33,8 +34,14 @@ const HITLInputReplacementBlock = ({ const createHITLInputBlockNode = useCallback((textNode: TextNode): QueryBlockNode => { const varName = textNode.getTextContent().split('.')[1].replace(/#}}$/, '') - return $applyNodeReplacement($createHITLInputNode(varName, nodeTitle, formInputs || [], onFormInputsChange!)) - }, [nodeTitle, formInputs, onFormInputsChange]) + return $applyNodeReplacement($createHITLInputNode( + varName, + nodeTitle, + formInputs || [], + onFormInputsChange!, + onFormInputItemRemove!, + )) + }, [nodeTitle, formInputs, onFormInputsChange, onFormInputItemRemove]) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/node.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/node.tsx index 7c12aa2117..2a98b3fd03 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/node.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/node.tsx @@ -8,6 +8,7 @@ export type SerializedNode = SerializedLexicalNode & { nodeTitle: string formInputs: FormInputItem[] onFormInputsChange: (inputs: FormInputItem[]) => void + onFormInputItemRemove: (varName: string) => void } export class HITLInputNode extends DecoratorNode { @@ -15,6 +16,7 @@ export class HITLInputNode extends DecoratorNode { __nodeTitle: string __formInputs?: FormInputItem[] __onFormInputsChange: (inputs: FormInputItem[]) => void + __onFormInputItemRemove: (varName: string) => void isIsolated(): boolean { return true // This is necessary for drag-and-drop to work correctly @@ -48,21 +50,34 @@ export class HITLInputNode extends DecoratorNode { return self.__onFormInputsChange } + getOnFormInputItemRemove(): (varName: string) => void { + const self = this.getLatest() + return self.__onFormInputItemRemove + } + static clone(node: HITLInputNode): HITLInputNode { - return new HITLInputNode(node.__variableName, node.__nodeTitle, node.__formInputs || [], node.__onFormInputsChange, node.__key) + return new HITLInputNode( + node.__variableName, + node.__nodeTitle, + node.__formInputs || [], + node.__onFormInputsChange, + node.__onFormInputItemRemove, + node.__key, + ) } isInline(): boolean { return true } - constructor(varName: string, nodeTitle: string, formInputs: FormInputItem[], onFormInputsChange: (inputs: FormInputItem[]) => void, key?: NodeKey) { + constructor(varName: string, nodeTitle: string, formInputs: FormInputItem[], onFormInputsChange: (inputs: FormInputItem[]) => void, onFormInputItemRemove: (varName: string) => void, key?: NodeKey) { super(key) this.__variableName = varName this.__nodeTitle = nodeTitle this.__formInputs = formInputs this.__onFormInputsChange = onFormInputsChange + this.__onFormInputItemRemove = onFormInputItemRemove } createDOM(): HTMLElement { @@ -82,11 +97,18 @@ export class HITLInputNode extends DecoratorNode { nodeTitle={this.getNodeTitle()} formInputs={this.getFormInputs()} onChange={this.getOnFormInputsChange()} + onRemove={this.getOnFormInputItemRemove()} /> } static importJSON(serializedNode: SerializedNode): HITLInputNode { - const node = $createHITLInputNode(serializedNode.variableName, serializedNode.nodeTitle, serializedNode.formInputs, serializedNode.onFormInputsChange) + const node = $createHITLInputNode( + serializedNode.variableName, + serializedNode.nodeTitle, + serializedNode.formInputs, + serializedNode.onFormInputsChange, + serializedNode.onFormInputItemRemove, + ) return node } @@ -99,6 +121,7 @@ export class HITLInputNode extends DecoratorNode { nodeTitle: this.getNodeTitle(), formInputs: this.getFormInputs(), onFormInputsChange: this.getOnFormInputsChange(), + onFormInputItemRemove: this.getOnFormInputItemRemove(), } } @@ -107,8 +130,14 @@ export class HITLInputNode extends DecoratorNode { } } -export function $createHITLInputNode(variableName: string, nodeTitle: string, formInputs: FormInputItem[], onFormInputsChange: (inputs: FormInputItem[]) => void): HITLInputNode { - return new HITLInputNode(variableName, nodeTitle, formInputs, onFormInputsChange) +export function $createHITLInputNode(variableName: string, nodeTitle: string, formInputs: FormInputItem[], onFormInputsChange: (inputs: FormInputItem[]) => void, onFormInputItemRemove: (varName: string) => void): HITLInputNode { + return new HITLInputNode( + variableName, + nodeTitle, + formInputs, + onFormInputsChange, + onFormInputItemRemove, + ) } export function $isHITLInputNode( diff --git a/web/app/components/base/prompt-editor/types.ts b/web/app/components/base/prompt-editor/types.ts index f8bb2bc2e8..841808c1a9 100644 --- a/web/app/components/base/prompt-editor/types.ts +++ b/web/app/components/base/prompt-editor/types.ts @@ -83,6 +83,7 @@ export type HITLInputBlockType = { nodeTitle: string formInputs?: FormInputItem[] onFormInputsChange?: (inputs: FormInputItem[]) => void + onFormInputItemRemove: (varName: string) => void } export type MenuTextMatch = { diff --git a/web/app/components/workflow/nodes/human-input/components/form-content.tsx b/web/app/components/workflow/nodes/human-input/components/form-content.tsx index 986c311fcf..50891ddcb3 100644 --- a/web/app/components/workflow/nodes/human-input/components/form-content.tsx +++ b/web/app/components/workflow/nodes/human-input/components/form-content.tsx @@ -14,7 +14,9 @@ type Props = { onChange: (value: string) => void formInputs: FormInputItem[] onFormInputsChange: (payload: FormInputItem[]) => void + onFormInputItemRemove: (varName: string) => void nodeTitle: string + editorKey: number } const FormContent: FC = ({ @@ -23,7 +25,9 @@ const FormContent: FC = ({ onChange, formInputs, onFormInputsChange, + onFormInputItemRemove, nodeTitle, + editorKey, }) => { const { t } = useTranslation() const filterVar = () => true @@ -40,6 +44,7 @@ const FormContent: FC = ({ return (
= ({ formInputs, nodeTitle, onFormInputsChange, + onFormInputItemRemove, }} workflowVariableBlock={{ show: true, diff --git a/web/app/components/workflow/nodes/human-input/panel.tsx b/web/app/components/workflow/nodes/human-input/panel.tsx index c8a61c51a2..90cc0f021a 100644 --- a/web/app/components/workflow/nodes/human-input/panel.tsx +++ b/web/app/components/workflow/nodes/human-input/panel.tsx @@ -36,6 +36,8 @@ const Panel: FC> = ({ handleTimeoutChange, handleFormContentChange, handleFormInputsChange, + handleFormInputItemRemove, + editorKey, } = useConfig(id, data) const { availableVars, availableNodesWithParent } = useAvailableVarList(id, { @@ -68,12 +70,14 @@ const Panel: FC> = ({
{/* user actions */} 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 820241b34f..2db1868787 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,24 +1,44 @@ +import { useCallback, useEffect, useRef, useState } from 'react' import useNodeCrud from '../_base/hooks/use-node-crud' import type { FormInputItem, HumanInputNodeType } from './types' +import produce from 'immer' const useFormContent = (id: string, payload: HumanInputNodeType) => { + const [editorKey, setEditorKey] = useState(0) const { inputs, setInputs } = useNodeCrud(id, payload) - const handleFormContentChange = (value: string) => { + const inputsRef = useRef(inputs) + useEffect(() => { + inputsRef.current = inputs + }, [inputs]) + const handleFormContentChange = useCallback((value: string) => { setInputs({ ...inputs, form_content: value, }) - } + }, [inputs, setInputs]) - const handleFormInputsChange = (formInputs: FormInputItem[]) => { + const handleFormInputsChange = useCallback((formInputs: FormInputItem[]) => { setInputs({ ...inputs, inputs: formInputs, }) - } + }, [inputs, setInputs]) + + const handleFormInputItemRemove = useCallback((varName: string) => { + const inputs = inputsRef.current + const newInputs = produce(inputs, (draft) => { + draft.form_content = draft.form_content.replaceAll(`{{#$output.${varName}#}}`, '') + draft.inputs = draft.inputs.filter(item => item.output_variable_name !== varName) + }) + setInputs(newInputs) + setEditorKey(editorKey + 1) + }, [inputs, setInputs, editorKey]) + return { + editorKey, handleFormContentChange, handleFormInputsChange, + handleFormInputItemRemove, } } diff --git a/web/app/test/page.tsx b/web/app/test/page.tsx deleted file mode 100644 index bb46f27ae6..0000000000 --- a/web/app/test/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client' - -import InputField from '@/app/components/base/prompt-editor/plugins/hitl-input-block/input-field' - -const Page = () => { - return ( - - ) -} - -export default Page