From 3160e5e5629d554904266204a5235ab1263f8dd1 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Jul 2025 17:52:11 +0800 Subject: [PATCH] feat: add error message --- .../base/prompt-editor/constants.tsx | 1 + .../components/base/prompt-editor/index.tsx | 21 ++++++ .../plugins/component-picker-block/hooks.tsx | 17 ++++- .../plugins/component-picker-block/index.tsx | 7 ++ .../plugins/current-block/component.tsx | 2 +- .../plugins/error-message-block/component.tsx | 40 +++++++++++ .../error-message-block-replacement-block.tsx | 61 +++++++++++++++++ .../plugins/error-message-block/index.tsx | 65 ++++++++++++++++++ .../plugins/error-message-block/node.tsx | 67 +++++++++++++++++++ ...kflow-variable-block-replacement-block.tsx | 2 - .../components/base/prompt-editor/types.ts | 6 ++ .../variable/var-reference-vars.tsx | 2 + 12 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 web/app/components/base/prompt-editor/plugins/error-message-block/component.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/error-message-block/error-message-block-replacement-block.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/error-message-block/index.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/error-message-block/node.tsx diff --git a/web/app/components/base/prompt-editor/constants.tsx b/web/app/components/base/prompt-editor/constants.tsx index d694cab2de..2fd0a62e95 100644 --- a/web/app/components/base/prompt-editor/constants.tsx +++ b/web/app/components/base/prompt-editor/constants.tsx @@ -4,6 +4,7 @@ export const CONTEXT_PLACEHOLDER_TEXT = '{{#context#}}' export const HISTORY_PLACEHOLDER_TEXT = '{{#histories#}}' export const QUERY_PLACEHOLDER_TEXT = '{{#query#}}' export const CURRENT_PLACEHOLDER_TEXT = '{{#current#}}' +export const ERROR_MESSAGE_PLACEHOLDER_TEXT = '{{#error_message#}}' export const PRE_PROMPT_PLACEHOLDER_TEXT = '{{#pre_prompt#}}' export const UPDATE_DATASETS_EVENT_EMITTER = 'prompt-editor-context-block-update-datasets' diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 7d70f2b10e..0a21587bc6 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -44,6 +44,11 @@ import { CurrentBlockNode, CurrentBlockReplacementBlock, } from './plugins/current-block' +import { + ErrorMessageBlock, + ErrorMessageBlockNode, + ErrorMessageBlockReplacementBlock, +} from './plugins/error-message-block' import VariableBlock from './plugins/variable-block' import VariableValueBlock from './plugins/variable-value-block' import { VariableValueBlockNode } from './plugins/variable-value-block/node' @@ -54,6 +59,7 @@ import { textToEditorState } from './utils' import type { ContextBlockType, CurrentBlockType, + ErrorMessageBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, @@ -88,6 +94,7 @@ export type PromptEditorProps = { externalToolBlock?: ExternalToolBlockType workflowVariableBlock?: WorkflowVariableBlockType currentBlock?: CurrentBlockType + errorMessageBlock?: ErrorMessageBlockType isSupportFileVar?: boolean } @@ -114,6 +121,9 @@ const PromptEditor: FC = ({ show: true, generatorType: GeneratorType.code, }, + errorMessageBlock = { + show: true, + }, isSupportFileVar, }) => { const { eventEmitter } = useEventEmitterContextContext() @@ -132,6 +142,7 @@ const PromptEditor: FC = ({ WorkflowVariableBlockNode, VariableValueBlockNode, CurrentBlockNode, + ErrorMessageBlockNode, ], editorState: textToEditorState(value || ''), onError: (error: Error) => { @@ -192,6 +203,7 @@ const PromptEditor: FC = ({ externalToolBlock={externalToolBlock} workflowVariableBlock={workflowVariableBlock} currentBlock={currentBlock} + errorMessageBlock={errorMessageBlock} isSupportFileVar={isSupportFileVar} /> = ({ externalToolBlock={externalToolBlock} workflowVariableBlock={workflowVariableBlock} currentBlock={currentBlock} + errorMessageBlock={errorMessageBlock} isSupportFileVar={isSupportFileVar} /> { @@ -253,6 +266,14 @@ const PromptEditor: FC = ({ ) } + { + errorMessageBlock?.show && ( + <> + + + + ) + } { isSupportFileVar && ( diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx index 818f850bd2..51689e1790 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx @@ -5,6 +5,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext import type { ContextBlockType, CurrentBlockType, + ErrorMessageBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, @@ -270,6 +271,7 @@ export const useOptions = ( externalToolBlockType?: ExternalToolBlockType, workflowVariableBlockType?: WorkflowVariableBlockType, currentBlockType?: CurrentBlockType, + errorMessageBlockType?: ErrorMessageBlockType, queryString?: string, ) => { const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock) @@ -280,6 +282,19 @@ export const useOptions = ( if (!workflowVariableBlockType?.show) return [] const res = workflowVariableBlockType.variables || [] + if(errorMessageBlockType?.show && res.findIndex(v => v.nodeId === 'error_message') === -1) { + res.unshift({ + nodeId: 'error_message', + title: 'error_message', + isFlat: true, + vars: [ + { + variable: 'error_message', + type: VarType.string, + }, + ], + }) + } if(currentBlockType?.show && res.findIndex(v => v.nodeId === 'current') === -1) { const title = currentBlockType.generatorType === 'prompt' ? 'current_prompt' : 'current_code' res.unshift({ @@ -295,7 +310,7 @@ export const useOptions = ( }) } return res - }, [workflowVariableBlockType, currentBlockType]) + }, [workflowVariableBlockType?.show, workflowVariableBlockType?.variables, errorMessageBlockType?.show, currentBlockType?.show, currentBlockType?.generatorType]) return useMemo(() => { return { diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx index f55db95fb3..73b81f957f 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx @@ -18,6 +18,7 @@ import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuP import type { ContextBlockType, CurrentBlockType, + ErrorMessageBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, @@ -35,6 +36,7 @@ import { useEventEmitterContextContext } from '@/context/event-emitter' import { KEY_ESCAPE_COMMAND } from 'lexical' import { INSERT_CURRENT_BLOCK_COMMAND } from '../current-block' import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' +import { INSERT_ERROR_MESSAGE_BLOCK_COMMAND } from '../error-message-block' type ComponentPickerProps = { triggerString: string @@ -45,6 +47,7 @@ type ComponentPickerProps = { externalToolBlock?: ExternalToolBlockType workflowVariableBlock?: WorkflowVariableBlockType currentBlock?: CurrentBlockType + errorMessageBlock?: ErrorMessageBlockType isSupportFileVar?: boolean } const ComponentPicker = ({ @@ -56,6 +59,7 @@ const ComponentPicker = ({ externalToolBlock, workflowVariableBlock, currentBlock, + errorMessageBlock, isSupportFileVar, }: ComponentPickerProps) => { const { eventEmitter } = useEventEmitterContextContext() @@ -93,6 +97,7 @@ const ComponentPicker = ({ externalToolBlock, workflowVariableBlock, currentBlock, + errorMessageBlock, ) const onSelectOption = useCallback( @@ -122,6 +127,8 @@ const ComponentPicker = ({ if(isFlat) { if(variables[0] === 'current') editor.dispatchCommand(INSERT_CURRENT_BLOCK_COMMAND, currentBlock?.generatorType) + else if (variables[0] === 'error_message') + editor.dispatchCommand(INSERT_ERROR_MESSAGE_BLOCK_COMMAND, null) } else if (variables[1] === 'sys.query' || variables[1] === 'sys.files') { editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]]) diff --git a/web/app/components/base/prompt-editor/plugins/current-block/component.tsx b/web/app/components/base/prompt-editor/plugins/current-block/component.tsx index 788c5fd6c3..7e56ffa6cc 100644 --- a/web/app/components/base/prompt-editor/plugins/current-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/current-block/component.tsx @@ -36,7 +36,7 @@ const CurrentBlockComponent: FC = ({ ref={ref} > -
{generatorType === GeneratorType.prompt ? 'current_prompt' : 'current_code'}
+
{generatorType === GeneratorType.prompt ? 'current_prompt' : 'current_code'}
) } diff --git a/web/app/components/base/prompt-editor/plugins/error-message-block/component.tsx b/web/app/components/base/prompt-editor/plugins/error-message-block/component.tsx new file mode 100644 index 0000000000..e5d113f680 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/error-message-block/component.tsx @@ -0,0 +1,40 @@ +import { type FC, useEffect } from 'react' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { useSelectOrDelete } from '../../hooks' +import { DELETE_ERROR_MESSAGE_COMMAND, ErrorMessageBlockNode } from '.' +import cn from '@/utils/classnames' +import { Variable02 } from '../../../icons/src/vender/solid/development' + +type Props = { + nodeKey: string +} + +const ErrorMessageBlockComponent: FC = ({ + nodeKey, +}) => { + const [editor] = useLexicalComposerContext() + const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_ERROR_MESSAGE_COMMAND) + + useEffect(() => { + if (!editor.hasNodes([ErrorMessageBlockNode])) + throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') + }, [editor]) + + return ( +
{ + e.stopPropagation() + }} + ref={ref} + > + +
error_message
+
+ ) +} + +export default ErrorMessageBlockComponent diff --git a/web/app/components/base/prompt-editor/plugins/error-message-block/error-message-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/error-message-block/error-message-block-replacement-block.tsx new file mode 100644 index 0000000000..80c89c7325 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/error-message-block/error-message-block-replacement-block.tsx @@ -0,0 +1,61 @@ +import { + memo, + useCallback, + useEffect, +} from 'react' +import { $applyNodeReplacement } from 'lexical' +import { mergeRegister } from '@lexical/utils' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { decoratorTransform } from '../../utils' +import { ERROR_MESSAGE_PLACEHOLDER_TEXT } from '../../constants' +import type { ErrorMessageBlockType } from '../../types' +import { + $createErrorMessageBlockNode, + ErrorMessageBlockNode, +} from './node' +import { CustomTextNode } from '../custom-text/node' + +const REGEX = new RegExp(ERROR_MESSAGE_PLACEHOLDER_TEXT) + +const ErrorMessageBlockReplacementBlock = ({ + onInsert, +}: ErrorMessageBlockType) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([ErrorMessageBlockNode])) + throw new Error('ErrorMessageBlockNodePlugin: ErrorMessageBlockNode not registered on editor') + }, [editor]) + + const createErrorMessageBlockNode = useCallback((): ErrorMessageBlockNode => { + if (onInsert) + onInsert() + return $applyNodeReplacement($createErrorMessageBlockNode()) + }, [onInsert]) + + const getMatch = useCallback((text: string) => { + const matchArr = REGEX.exec(text) + + if (matchArr === null) + return null + + const startOffset = matchArr.index + const endOffset = startOffset + ERROR_MESSAGE_PLACEHOLDER_TEXT.length + return { + end: endOffset, + start: startOffset, + } + }, []) + + useEffect(() => { + REGEX.lastIndex = 0 + return mergeRegister( + editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createErrorMessageBlockNode)), + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return null +} + +export default memo(ErrorMessageBlockReplacementBlock) diff --git a/web/app/components/base/prompt-editor/plugins/error-message-block/index.tsx b/web/app/components/base/prompt-editor/plugins/error-message-block/index.tsx new file mode 100644 index 0000000000..25bb55c3f0 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/error-message-block/index.tsx @@ -0,0 +1,65 @@ +import { + memo, + useEffect, +} from 'react' +import { + $insertNodes, + COMMAND_PRIORITY_EDITOR, + createCommand, +} from 'lexical' +import { mergeRegister } from '@lexical/utils' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import type { ErrorMessageBlockType } from '../../types' +import { + $createErrorMessageBlockNode, + ErrorMessageBlockNode, +} from './node' + +export const INSERT_ERROR_MESSAGE_BLOCK_COMMAND = createCommand('INSERT_ERROR_MESSAGE_BLOCK_COMMAND') +export const DELETE_ERROR_MESSAGE_COMMAND = createCommand('DELETE_ERROR_MESSAGE_COMMAND') + +const ErrorMessageBlock = memo(({ + onInsert, + onDelete, +}: ErrorMessageBlockType) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([ErrorMessageBlockNode])) + throw new Error('ERROR_MESSAGEBlockPlugin: ERROR_MESSAGEBlock not registered on editor') + + return mergeRegister( + editor.registerCommand( + INSERT_ERROR_MESSAGE_BLOCK_COMMAND, + () => { + const Node = $createErrorMessageBlockNode() + + $insertNodes([Node]) + + if (onInsert) + onInsert() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerCommand( + DELETE_ERROR_MESSAGE_COMMAND, + () => { + if (onDelete) + onDelete() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + ) + }, [editor, onDelete, onInsert]) + + return null +}) +ErrorMessageBlock.displayName = 'ErrorMessageBlock' + +export { ErrorMessageBlock } +export { ErrorMessageBlockNode } from './node' +export { default as ErrorMessageBlockReplacementBlock } from './error-message-block-replacement-block' diff --git a/web/app/components/base/prompt-editor/plugins/error-message-block/node.tsx b/web/app/components/base/prompt-editor/plugins/error-message-block/node.tsx new file mode 100644 index 0000000000..b8042e5e54 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/error-message-block/node.tsx @@ -0,0 +1,67 @@ +import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical' +import { DecoratorNode } from 'lexical' +import ErrorMessageBlockComponent from './component' + +export type SerializedNode = SerializedLexicalNode + +export class ErrorMessageBlockNode extends DecoratorNode { + static getType(): string { + return 'error-message-block' + } + + static clone(node: ErrorMessageBlockNode): ErrorMessageBlockNode { + return new ErrorMessageBlockNode(node.getKey()) + } + + isInline(): boolean { + return true + } + + constructor(key?: NodeKey) { + super(key) + } + + createDOM(): HTMLElement { + const div = document.createElement('div') + div.classList.add('inline-flex', 'items-center', 'align-middle') + return div + } + + updateDOM(): false { + return false + } + + decorate(): React.JSX.Element { + return ( + + ) + } + + static importJSON(): ErrorMessageBlockNode { + const node = $createErrorMessageBlockNode() + + return node + } + + exportJSON(): SerializedNode { + return { + type: 'error-message-block', + version: 1, + } + } + + getTextContent(): string { + return '{{#error_message#}}' + } +} +export function $createErrorMessageBlockNode(): ErrorMessageBlockNode { + return new ErrorMessageBlockNode() +} + +export function $isErrorMessageBlockNode( + node: ErrorMessageBlockNode | LexicalNode | null | undefined, +): boolean { + return node instanceof ErrorMessageBlockNode +} diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx index 85f0cb8e09..4d0c80f10f 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx @@ -41,8 +41,6 @@ const WorkflowVariableBlockReplacementBlock = ({ if (matchArr === null) return null - console.log(matchArr) - const startOffset = matchArr.index const endOffset = startOffset + matchArr[0].length return { diff --git a/web/app/components/base/prompt-editor/types.ts b/web/app/components/base/prompt-editor/types.ts index 88ae5fc3ab..939d8afd89 100644 --- a/web/app/components/base/prompt-editor/types.ts +++ b/web/app/components/base/prompt-editor/types.ts @@ -83,3 +83,9 @@ export type CurrentBlockType = { onInsert?: () => void onDelete?: () => void } + +export type ErrorMessageBlockType = { + show?: boolean + onInsert?: () => void + onDelete?: () => void +} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 8575aca247..0bdcd529fb 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -83,6 +83,8 @@ const Item: FC = ({ case 'current': Icon = isInCodeGeneratorInstructionEditor ? CodeAssistant : MagicEdit return + case 'error_message': + return } }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable])