From 7d80cb6d9593188b2f8e764cef5a244d8c0217f9 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Jul 2025 11:24:10 +0800 Subject: [PATCH 01/36] feat: current block --- .../configuration/config/automatic/types.ts | 4 + .../base/prompt-editor/constants.tsx | 2 + .../plugins/current-block/component.tsx | 24 ++++++ .../current-block-replacement-block.tsx | 62 +++++++++++++++ .../plugins/current-block/index.tsx | 66 ++++++++++++++++ .../plugins/current-block/node.tsx | 78 +++++++++++++++++++ .../components/base/prompt-editor/types.ts | 8 ++ 7 files changed, 244 insertions(+) create mode 100644 web/app/components/app/configuration/config/automatic/types.ts create mode 100644 web/app/components/base/prompt-editor/plugins/current-block/component.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/current-block/current-block-replacement-block.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/current-block/index.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/current-block/node.tsx diff --git a/web/app/components/app/configuration/config/automatic/types.ts b/web/app/components/app/configuration/config/automatic/types.ts new file mode 100644 index 0000000000..cc97569499 --- /dev/null +++ b/web/app/components/app/configuration/config/automatic/types.ts @@ -0,0 +1,4 @@ +export enum GeneratorType { + prompt = 'prompt', + code = 'code', +} diff --git a/web/app/components/base/prompt-editor/constants.tsx b/web/app/components/base/prompt-editor/constants.tsx index 31fbc0abb4..d694cab2de 100644 --- a/web/app/components/base/prompt-editor/constants.tsx +++ b/web/app/components/base/prompt-editor/constants.tsx @@ -3,6 +3,8 @@ import { SupportUploadFileTypes, type ValueSelector } from '../../workflow/types 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 PRE_PROMPT_PLACEHOLDER_TEXT = '{{#pre_prompt#}}' export const UPDATE_DATASETS_EVENT_EMITTER = 'prompt-editor-context-block-update-datasets' export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-role' 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 new file mode 100644 index 0000000000..2c4b678d0d --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/current-block/component.tsx @@ -0,0 +1,24 @@ +import type { FC } from 'react' +import { File05 } from '@/app/components/base/icons/src/vender/solid/files' +import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' + +type CurrentBlockComponentProps = { + nodeKey: string + generatorType: GeneratorType +} + +const CurrentBlockComponent: FC = ({ + generatorType, +}) => { + return ( +
+ +
{generatorType === GeneratorType.prompt ? 'current_prompt' : 'current_code'}
+
+ ) +} + +export default CurrentBlockComponent diff --git a/web/app/components/base/prompt-editor/plugins/current-block/current-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/current-block/current-block-replacement-block.tsx new file mode 100644 index 0000000000..8ca56b0cf4 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/current-block/current-block-replacement-block.tsx @@ -0,0 +1,62 @@ +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 { CURRENT_PLACEHOLDER_TEXT } from '../../constants' +import type { CurrentBlockType } from '../../types' +import { + $createCurrentBlockNode, + CurrentBlockNode, +} from './node' +import { CustomTextNode } from '../custom-text/node' + +const REGEX = new RegExp(CURRENT_PLACEHOLDER_TEXT) + +const CurrentBlockReplacementBlock = ({ + generatorType, + onInsert, +}: CurrentBlockType) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([CurrentBlockNode])) + throw new Error('CurrentBlockNodePlugin: CurrentBlockNode not registered on editor') + }, [editor]) + + const createCurrentBlockNode = useCallback((): CurrentBlockNode => { + if (onInsert) + onInsert() + return $applyNodeReplacement($createCurrentBlockNode(generatorType)) + }, [onInsert, generatorType]) + + const getMatch = useCallback((text: string) => { + const matchArr = REGEX.exec(text) + + if (matchArr === null) + return null + + const startOffset = matchArr.index + const endOffset = startOffset + CURRENT_PLACEHOLDER_TEXT.length + return { + end: endOffset, + start: startOffset, + } + }, []) + + useEffect(() => { + REGEX.lastIndex = 0 + return mergeRegister( + editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createCurrentBlockNode)), + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return null +} + +export default memo(CurrentBlockReplacementBlock) diff --git a/web/app/components/base/prompt-editor/plugins/current-block/index.tsx b/web/app/components/base/prompt-editor/plugins/current-block/index.tsx new file mode 100644 index 0000000000..0b9ed6c5a4 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/current-block/index.tsx @@ -0,0 +1,66 @@ +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 { CurrentBlockType } from '../../types' +import { + $createCurrentBlockNode, + CurrentBlockNode, +} from './node' + +export const INSERT_CURRENT_BLOCK_COMMAND = createCommand('INSERT_CURRENT_BLOCK_COMMAND') +export const DELETE_CURRENT_BLOCK_COMMAND = createCommand('DELETE_CURRENT_BLOCK_COMMAND') + +const CurrentBlock = memo(({ + generatorType, + onInsert, + onDelete, +}: CurrentBlockType) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([CurrentBlockNode])) + throw new Error('CURRENTBlockPlugin: CURRENTBlock not registered on editor') + + return mergeRegister( + editor.registerCommand( + INSERT_CURRENT_BLOCK_COMMAND, + () => { + const currentBlockNode = $createCurrentBlockNode(generatorType) + + $insertNodes([currentBlockNode]) + + if (onInsert) + onInsert() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerCommand( + DELETE_CURRENT_BLOCK_COMMAND, + () => { + if (onDelete) + onDelete() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + ) + }, [editor, generatorType, onDelete, onInsert]) + + return null +}) +CurrentBlock.displayName = 'CurrentBlock' + +export { CurrentBlock } +export { CurrentBlockNode } from './node' +export { default as CurrentBlockReplacementBlock } from './current-block-replacement-block' diff --git a/web/app/components/base/prompt-editor/plugins/current-block/node.tsx b/web/app/components/base/prompt-editor/plugins/current-block/node.tsx new file mode 100644 index 0000000000..6f2a3a1751 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/current-block/node.tsx @@ -0,0 +1,78 @@ +import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical' +import { DecoratorNode } from 'lexical' +import CurrentBlockComponent from './component' +import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' + +export type SerializedNode = SerializedLexicalNode & { generatorType: GeneratorType; } + +export class CurrentBlockNode extends DecoratorNode { + __generatorType: GeneratorType + static getType(): string { + return 'current-block' + } + + static clone(node: CurrentBlockNode): CurrentBlockNode { + return new CurrentBlockNode(node.__generatorType) + } + + isInline(): boolean { + return true + } + + constructor(generatorType: GeneratorType, key?: NodeKey) { + super(key) + + this.__generatorType = generatorType + } + + 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 ( + + ) + } + + getGeneratorType(): GeneratorType { + const self = this.getLatest() + return self.__generatorType + } + + static importJSON(serializedNode: SerializedNode): CurrentBlockNode { + const node = $createCurrentBlockNode(serializedNode.generatorType) + + return node + } + + exportJSON(): SerializedNode { + return { + type: 'current-block', + version: 1, + generatorType: this.getGeneratorType(), + } + } + + getTextContent(): string { + return '{{#current#}}' + } +} +export function $createCurrentBlockNode(type: GeneratorType): CurrentBlockNode { + return new CurrentBlockNode(type) +} + +export function $isCurrentBlockNode( + node: CurrentBlockNode | LexicalNode | null | undefined, +): boolean { + return node instanceof CurrentBlockNode +} diff --git a/web/app/components/base/prompt-editor/types.ts b/web/app/components/base/prompt-editor/types.ts index e82ec1da16..88ae5fc3ab 100644 --- a/web/app/components/base/prompt-editor/types.ts +++ b/web/app/components/base/prompt-editor/types.ts @@ -1,3 +1,4 @@ +import type { GeneratorType } from '../../app/configuration/config/automatic/types' import type { Type } from '../../workflow/nodes/llm/types' import type { Dataset } from './plugins/context-block' import type { RoleName } from './plugins/history-block' @@ -75,3 +76,10 @@ export type MenuTextMatch = { matchingString: string replaceableString: string } + +export type CurrentBlockType = { + show?: boolean + generatorType: GeneratorType + onInsert?: () => void + onDelete?: () => void +} From 6075ca5f5958378e64053252f69d51422ae95597 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Jul 2025 16:09:07 +0800 Subject: [PATCH 02/36] feat: can choose current and show current --- .../vender/line/general/code-assistant.svg | 6 ++ .../assets/vender/line/general/magic-edit.svg | 6 ++ .../vender/line/general/CodeAssistant.json | 53 ++++++++++++++++++ .../src/vender/line/general/CodeAssistant.tsx | 20 +++++++ .../src/vender/line/general/MagicEdit.json | 55 +++++++++++++++++++ .../src/vender/line/general/MagicEdit.tsx | 20 +++++++ .../icons/src/vender/line/general/index.ts | 2 + .../components/base/prompt-editor/index.tsx | 28 ++++++++++ .../plugins/component-picker-block/hooks.tsx | 24 +++++++- .../plugins/component-picker-block/index.tsx | 21 +++++-- .../plugins/current-block/component.tsx | 34 +++++++++--- .../plugins/current-block/node.tsx | 2 +- ...kflow-variable-block-replacement-block.tsx | 2 + .../variable/var-reference-vars.tsx | 38 +++++++++++-- web/app/components/workflow/types.ts | 1 + 15 files changed, 291 insertions(+), 21 deletions(-) create mode 100644 web/app/components/base/icons/assets/vender/line/general/code-assistant.svg create mode 100644 web/app/components/base/icons/assets/vender/line/general/magic-edit.svg create mode 100644 web/app/components/base/icons/src/vender/line/general/CodeAssistant.json create mode 100644 web/app/components/base/icons/src/vender/line/general/CodeAssistant.tsx create mode 100644 web/app/components/base/icons/src/vender/line/general/MagicEdit.json create mode 100644 web/app/components/base/icons/src/vender/line/general/MagicEdit.tsx diff --git a/web/app/components/base/icons/assets/vender/line/general/code-assistant.svg b/web/app/components/base/icons/assets/vender/line/general/code-assistant.svg new file mode 100644 index 0000000000..0051dfc271 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/code-assistant.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/app/components/base/icons/assets/vender/line/general/magic-edit.svg b/web/app/components/base/icons/assets/vender/line/general/magic-edit.svg new file mode 100644 index 0000000000..9bc82f4853 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/magic-edit.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/app/components/base/icons/src/vender/line/general/CodeAssistant.json b/web/app/components/base/icons/src/vender/line/general/CodeAssistant.json new file mode 100644 index 0000000000..03dca1bd11 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/CodeAssistant.json @@ -0,0 +1,53 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M12 3C12.5523 3 13 3.44772 13 4C13 4.55228 12.5523 5 12 5H5.59962C5.30336 5 5.14096 5.00122 5.02443 5.01074C5.01998 5.01111 5.01573 5.01135 5.01173 5.01172C5.01136 5.01572 5.01112 5.01996 5.01076 5.02441C5.00124 5.14095 5.00001 5.30334 5.00001 5.59961V18.4004C5.00001 18.6967 5.00124 18.8591 5.01076 18.9756C5.01109 18.9797 5.01139 18.9836 5.01173 18.9873C5.01575 18.9877 5.01995 18.9889 5.02443 18.9893C5.14096 18.9988 5.30336 19 5.59962 19H18.4004C18.6967 19 18.8591 18.9988 18.9756 18.9893C18.9797 18.9889 18.9836 18.9876 18.9873 18.9873C18.9877 18.9836 18.9889 18.9797 18.9893 18.9756C18.9988 18.8591 19 18.6967 19 18.4004V12C19 11.4477 19.4477 11 20 11C20.5523 11 21 11.4477 21 12V18.4004C21 18.6638 21.0011 18.9219 20.9834 19.1387C20.9647 19.3672 20.9205 19.6369 20.7822 19.9082C20.5905 20.2845 20.2845 20.5905 19.9082 20.7822C19.6369 20.9205 19.3672 20.9647 19.1387 20.9834C18.9219 21.0011 18.6638 21 18.4004 21H5.59962C5.33625 21 5.07815 21.0011 4.86134 20.9834C4.63284 20.9647 4.36307 20.9204 4.09181 20.7822C3.71579 20.5906 3.40963 20.2847 3.21779 19.9082C3.07958 19.6369 3.03531 19.3672 3.01662 19.1387C2.9989 18.9219 3.00001 18.6638 3.00001 18.4004V5.59961C3.00001 5.33623 2.9989 5.07814 3.01662 4.86133C3.03531 4.63283 3.07958 4.36305 3.21779 4.0918C3.40953 3.71555 3.71557 3.40952 4.09181 3.21777C4.36306 3.07957 4.63285 3.0353 4.86134 3.0166C5.07815 2.99889 5.33624 3 5.59962 3H12Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M13.293 9.29297C13.6835 8.90244 14.3165 8.90244 14.707 9.29297L16.707 11.293C17.0975 11.6835 17.0975 12.3165 16.707 12.707L14.707 14.707C14.3165 15.0975 13.6835 15.0975 13.293 14.707C12.9025 14.3165 12.9025 13.6835 13.293 13.293L14.586 12L13.293 10.707C12.9025 10.3165 12.9025 9.68349 13.293 9.29297Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M9.29298 9.29297C9.68351 8.90244 10.3165 8.90244 10.707 9.29297C11.0975 9.6835 11.0975 10.3165 10.707 10.707L9.41408 12L10.707 13.293L10.7754 13.3691C11.0957 13.7619 11.0731 14.3409 10.707 14.707C10.341 15.0731 9.76192 15.0957 9.36915 14.7754L9.29298 14.707L7.29298 12.707C6.90246 12.3165 6.90246 11.6835 7.29298 11.293L9.29298 9.29297Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M18.501 2C18.7487 2.00048 18.9564 2.18654 18.9844 2.43262C19.1561 3.94883 20.0165 4.87694 21.5557 5.01367C21.8075 5.03614 22.0003 5.24816 22 5.50098C21.9995 5.75356 21.8063 5.96437 21.5547 5.98633C20.0372 6.11768 19.1176 7.03726 18.9863 8.55469C18.9644 8.80629 18.7536 8.99948 18.501 9C18.2483 9.00028 18.0362 8.80743 18.0137 8.55566C17.877 7.01647 16.9488 6.15613 15.4326 5.98438C15.1866 5.95634 15.0006 5.74863 15 5.50098C14.9997 5.25311 15.1855 5.04417 15.4317 5.01563C16.9697 4.83823 17.8382 3.96966 18.0156 2.43164C18.0442 2.18545 18.2531 1.99975 18.501 2Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "CodeAssistant" +} diff --git a/web/app/components/base/icons/src/vender/line/general/CodeAssistant.tsx b/web/app/components/base/icons/src/vender/line/general/CodeAssistant.tsx new file mode 100644 index 0000000000..71adb145fb --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/CodeAssistant.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './CodeAssistant.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'CodeAssistant' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/MagicEdit.json b/web/app/components/base/icons/src/vender/line/general/MagicEdit.json new file mode 100644 index 0000000000..3c2be09743 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/MagicEdit.json @@ -0,0 +1,55 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M15.3691 3.22448C15.7619 2.90422 16.3409 2.92682 16.707 3.29284L20.707 7.29284C21.0975 7.68334 21.0975 8.31637 20.707 8.7069L8.70703 20.7069C8.5195 20.8944 8.26521 20.9999 8 20.9999H4C3.44771 20.9999 3 20.5522 3 19.9999V15.9999L3.00488 15.9012C3.0276 15.6723 3.12886 15.4569 3.29297 15.2928L15.293 3.29284L15.3691 3.22448ZM5 16.4139V18.9999H7.58593L18.5859 7.99987L16 5.41394L5 16.4139Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M18.2451 15.1581C18.3502 14.9478 18.6498 14.9478 18.7549 15.1581L19.4082 16.4637C19.4358 16.5189 19.4809 16.5641 19.5361 16.5917L20.8428 17.245C21.0525 17.3502 21.0524 17.6495 20.8428 17.7548L19.5361 18.4081C19.4809 18.4357 19.4358 18.4808 19.4082 18.536L18.7549 19.8426C18.6497 20.0525 18.3503 20.0525 18.2451 19.8426L17.5918 18.536C17.5642 18.4808 17.5191 18.4357 17.4639 18.4081L16.1572 17.7548C15.9476 17.6495 15.9475 17.3502 16.1572 17.245L17.4639 16.5917C17.5191 16.5641 17.5642 16.5189 17.5918 16.4637L18.2451 15.1581Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M4.24511 5.15808C4.35024 4.94783 4.64975 4.94783 4.75488 5.15808L5.4082 6.46374C5.4358 6.51895 5.48092 6.56406 5.53613 6.59167L6.84277 7.24499C7.05248 7.3502 7.05238 7.64946 6.84277 7.75476L5.53613 8.40808C5.48092 8.43568 5.4358 8.4808 5.4082 8.53601L4.75488 9.84265C4.64966 10.0525 4.35033 10.0525 4.24511 9.84265L3.59179 8.53601C3.56418 8.4808 3.51907 8.43568 3.46386 8.40808L2.15722 7.75476C1.94764 7.64945 1.94755 7.35021 2.15722 7.24499L3.46386 6.59167C3.51907 6.56406 3.56418 6.51895 3.59179 6.46374L4.24511 5.15808Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M8.73535 2.16394C8.84432 1.94601 9.15568 1.94601 9.26465 2.16394L9.74414 3.12292C9.77275 3.18014 9.81973 3.22712 9.87695 3.25573L10.8369 3.73522C11.0546 3.84424 11.0545 4.15544 10.8369 4.26452L9.87695 4.74401C9.81973 4.77262 9.77275 4.81961 9.74414 4.87683L9.26465 5.83679C9.15565 6.05458 8.84435 6.05457 8.73535 5.83679L8.25586 4.87683C8.22725 4.81961 8.18026 4.77262 8.12304 4.74401L7.16308 4.26452C6.94547 4.15546 6.94539 3.84422 7.16308 3.73522L8.12304 3.25573C8.18026 3.22712 8.22725 3.18014 8.25586 3.12292L8.73535 2.16394Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "MagicEdit" +} diff --git a/web/app/components/base/icons/src/vender/line/general/MagicEdit.tsx b/web/app/components/base/icons/src/vender/line/general/MagicEdit.tsx new file mode 100644 index 0000000000..4e49c55277 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/MagicEdit.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './MagicEdit.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'MagicEdit' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/index.ts b/web/app/components/base/icons/src/vender/line/general/index.ts index b5c7a7bbc1..f0ee776c60 100644 --- a/web/app/components/base/icons/src/vender/line/general/index.ts +++ b/web/app/components/base/icons/src/vender/line/general/index.ts @@ -3,6 +3,7 @@ export { default as Bookmark } from './Bookmark' export { default as CheckDone01 } from './CheckDone01' export { default as Check } from './Check' export { default as ChecklistSquare } from './ChecklistSquare' +export { default as CodeAssistant } from './CodeAssistant' export { default as DotsGrid } from './DotsGrid' export { default as Edit02 } from './Edit02' export { default as Edit04 } from './Edit04' @@ -14,6 +15,7 @@ export { default as LinkExternal02 } from './LinkExternal02' export { default as LogIn04 } from './LogIn04' export { default as LogOut01 } from './LogOut01' export { default as LogOut04 } from './LogOut04' +export { default as MagicEdit } from './MagicEdit' export { default as Menu01 } from './Menu01' export { default as Pin01 } from './Pin01' export { default as Pin02 } from './Pin02' diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index a87a51cd50..4288ad1e26 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -39,6 +39,11 @@ import { WorkflowVariableBlockNode, WorkflowVariableBlockReplacementBlock, } from './plugins/workflow-variable-block' +import { + CurrentBlock, + CurrentBlockNode, + CurrentBlockReplacementBlock, +} from './plugins/current-block' import VariableBlock from './plugins/variable-block' import VariableValueBlock from './plugins/variable-value-block' import { VariableValueBlockNode } from './plugins/variable-value-block/node' @@ -48,6 +53,7 @@ import UpdateBlock from './plugins/update-block' import { textToEditorState } from './utils' import type { ContextBlockType, + CurrentBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, @@ -60,6 +66,7 @@ import { } from './constants' import { useEventEmitterContextContext } from '@/context/event-emitter' import cn from '@/utils/classnames' +import { GeneratorType } from '../../app/configuration/config/automatic/types' export type PromptEditorProps = { instanceId?: string @@ -80,6 +87,7 @@ export type PromptEditorProps = { variableBlock?: VariableBlockType externalToolBlock?: ExternalToolBlockType workflowVariableBlock?: WorkflowVariableBlockType + currentBlock?: CurrentBlockType isSupportFileVar?: boolean } @@ -102,6 +110,10 @@ const PromptEditor: FC = ({ variableBlock, externalToolBlock, workflowVariableBlock, + currentBlock = { + show: true, + generatorType: GeneratorType.prompt, + }, isSupportFileVar, }) => { const { eventEmitter } = useEventEmitterContextContext() @@ -119,6 +131,7 @@ const PromptEditor: FC = ({ QueryBlockNode, WorkflowVariableBlockNode, VariableValueBlockNode, + CurrentBlockNode, ], editorState: textToEditorState(value || ''), onError: (error: Error) => { @@ -178,6 +191,7 @@ const PromptEditor: FC = ({ variableBlock={variableBlock} externalToolBlock={externalToolBlock} workflowVariableBlock={workflowVariableBlock} + currentBlock={currentBlock} isSupportFileVar={isSupportFileVar} /> = ({ variableBlock={variableBlock} externalToolBlock={externalToolBlock} workflowVariableBlock={workflowVariableBlock} + currentBlock={currentBlock} isSupportFileVar={isSupportFileVar} /> { @@ -230,6 +245,19 @@ const PromptEditor: FC = ({ ) } + { + currentBlock?.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 7332a0d39b..818f850bd2 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 @@ -4,6 +4,7 @@ import { $insertNodes } from 'lexical' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import type { ContextBlockType, + CurrentBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, @@ -27,6 +28,7 @@ import { BracketsX } from '@/app/components/base/icons/src/vender/line/developme import { UserEdit02 } from '@/app/components/base/icons/src/vender/solid/users' import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' import AppIcon from '@/app/components/base/app-icon' +import { VarType } from '@/app/components/workflow/types' export const usePromptOptions = ( contextBlock?: ContextBlockType, @@ -267,17 +269,33 @@ export const useOptions = ( variableBlock?: VariableBlockType, externalToolBlockType?: ExternalToolBlockType, workflowVariableBlockType?: WorkflowVariableBlockType, + currentBlockType?: CurrentBlockType, queryString?: string, ) => { const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock) const variableOptions = useVariableOptions(variableBlock, queryString) const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString) + const workflowVariableOptions = useMemo(() => { if (!workflowVariableBlockType?.show) return [] - - return workflowVariableBlockType.variables || [] - }, [workflowVariableBlockType]) + const res = workflowVariableBlockType.variables || [] + if(currentBlockType?.show && res.findIndex(v => v.nodeId === 'current') === -1) { + const title = currentBlockType.generatorType === 'prompt' ? 'current_prompt' : 'current_code' + res.unshift({ + nodeId: 'current', + title, + isFlat: true, + vars: [ + { + variable: 'current', + type: VarType.string, + }, + ], + }) + } + return res + }, [workflowVariableBlockType, currentBlockType]) 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 bffcdc60d2..f55db95fb3 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 @@ -17,6 +17,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin' import type { ContextBlockType, + CurrentBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, @@ -32,6 +33,8 @@ import type { PickerBlockMenuOption } from './menu' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' 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' type ComponentPickerProps = { triggerString: string @@ -41,6 +44,7 @@ type ComponentPickerProps = { variableBlock?: VariableBlockType externalToolBlock?: ExternalToolBlockType workflowVariableBlock?: WorkflowVariableBlockType + currentBlock?: CurrentBlockType isSupportFileVar?: boolean } const ComponentPicker = ({ @@ -51,6 +55,7 @@ const ComponentPicker = ({ variableBlock, externalToolBlock, workflowVariableBlock, + currentBlock, isSupportFileVar, }: ComponentPickerProps) => { const { eventEmitter } = useEventEmitterContextContext() @@ -87,6 +92,7 @@ const ComponentPicker = ({ variableBlock, externalToolBlock, workflowVariableBlock, + currentBlock, ) const onSelectOption = useCallback( @@ -112,12 +118,18 @@ const ComponentPicker = ({ if (needRemove) needRemove.remove() }) - - if (variables[1] === 'sys.query' || variables[1] === 'sys.files') + const isFlat = variables.length === 1 + if(isFlat) { + if(variables[0] === 'current') + editor.dispatchCommand(INSERT_CURRENT_BLOCK_COMMAND, currentBlock?.generatorType) + } + else if (variables[1] === 'sys.query' || variables[1] === 'sys.files') { editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]]) - else + } + else { editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) - }, [editor, checkForTriggerMatch, triggerString]) + } + }, [editor, currentBlock?.generatorType, checkForTriggerMatch, triggerString]) const handleClose = useCallback(() => { const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }) @@ -166,6 +178,7 @@ const ComponentPicker = ({ onClose={handleClose} onBlur={handleClose} autoFocus={false} + isInCodeGeneratorInstructionEditor={currentBlock?.generatorType === GeneratorType.code} /> ) 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 2c4b678d0d..788c5fd6c3 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 @@ -1,6 +1,10 @@ -import type { FC } from 'react' -import { File05 } from '@/app/components/base/icons/src/vender/solid/files' +import { type FC, useEffect } from 'react' import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { useSelectOrDelete } from '../../hooks' +import { CurrentBlockNode, DELETE_CURRENT_BLOCK_COMMAND } from '.' +import cn from '@/utils/classnames' +import { CodeAssistant, MagicEdit } from '../../../icons/src/vender/line/general' type CurrentBlockComponentProps = { nodeKey: string @@ -8,14 +12,30 @@ type CurrentBlockComponentProps = { } const CurrentBlockComponent: FC = ({ + nodeKey, generatorType, }) => { + const [editor] = useLexicalComposerContext() + const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_CURRENT_BLOCK_COMMAND) + + const Icon = generatorType === GeneratorType.prompt ? MagicEdit : CodeAssistant + useEffect(() => { + if (!editor.hasNodes([CurrentBlockNode])) + throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') + }, [editor]) + return ( -
- +
{ + e.stopPropagation() + }} + ref={ref} + > +
{generatorType === GeneratorType.prompt ? 'current_prompt' : 'current_code'}
) diff --git a/web/app/components/base/prompt-editor/plugins/current-block/node.tsx b/web/app/components/base/prompt-editor/plugins/current-block/node.tsx index 6f2a3a1751..eb0239f613 100644 --- a/web/app/components/base/prompt-editor/plugins/current-block/node.tsx +++ b/web/app/components/base/prompt-editor/plugins/current-block/node.tsx @@ -12,7 +12,7 @@ export class CurrentBlockNode extends DecoratorNode { } static clone(node: CurrentBlockNode): CurrentBlockNode { - return new CurrentBlockNode(node.__generatorType) + return new CurrentBlockNode(node.__generatorType, node.getKey()) } isInline(): boolean { 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 4d0c80f10f..85f0cb8e09 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,6 +41,8 @@ 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/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 303840d8e7..3414903318 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 @@ -23,6 +23,7 @@ import type { Field } from '@/app/components/workflow/nodes/llm/types' import { FILE_STRUCT } from '@/app/components/workflow/constants' import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { noop } from 'lodash-es' +import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' type ObjectChildrenProps = { nodeId: string @@ -46,6 +47,8 @@ type ItemProps = { isSupportFileVar?: boolean isException?: boolean isLoopVar?: boolean + isFlat?: boolean + isInCodeGeneratorInstructionEditor?: boolean zIndex?: number } @@ -61,6 +64,8 @@ const Item: FC = ({ isSupportFileVar, isException, isLoopVar, + isFlat, + isInCodeGeneratorInstructionEditor, zIndex, }) => { const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties @@ -69,6 +74,17 @@ const Item: FC = ({ const isSys = itemData.variable.startsWith('sys.') const isEnv = itemData.variable.startsWith('env.') const isChatVar = itemData.variable.startsWith('conversation.') + const flatVarIcon = useMemo(() => { + if(!isFlat) + return null + const variable = itemData.variable + let Icon + switch (variable) { + case 'current': + Icon = isInCodeGeneratorInstructionEditor ? CodeAssistant : MagicEdit + return + } + }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable]) const objStructuredOutput: StructuredOutput | null = useMemo(() => { if (!isObj) return null @@ -125,7 +141,10 @@ const Item: FC = ({ if (!isSupportFileVar && isFile) return - if (isSys || isEnv || isChatVar) { // system variable | environment variable | conversation variable + if(isFlat) { + onChange([itemData.variable], itemData) + } + else if (isSys || isEnv || isChatVar) { // system variable | environment variable | conversation variable onChange([...objPath, ...itemData.variable.split('.')], itemData) } else { @@ -150,10 +169,11 @@ const Item: FC = ({ onMouseDown={e => e.preventDefault()} >
- {!isEnv && !isChatVar && !isLoopVar && } + {!isFlat && !isEnv && !isChatVar && !isLoopVar && } {isEnv && } {isChatVar && } {isLoopVar && } + {isFlat && flatVarIcon} {!isEnv && !isChatVar && (
{itemData.variable}
)} @@ -263,6 +283,7 @@ type Props = { onClose?: () => void onBlur?: () => void zIndex?: number + isInCodeGeneratorInstructionEditor?: boolean autoFocus?: boolean } const VarReferenceVars: FC = ({ @@ -276,6 +297,7 @@ const VarReferenceVars: FC = ({ onClose, onBlur, zIndex, + isInCodeGeneratorInstructionEditor, autoFocus = true, }) => { const { t } = useTranslation() @@ -345,10 +367,12 @@ const VarReferenceVars: FC = ({ { filteredVars.map((item, i) => (
-
{item.title}
+ {!item.isFlat && ( +
{item.title}
+ )} {item.vars.map((v, j) => ( = ({ isSupportFileVar={isSupportFileVar} isException={v.isException} isLoopVar={item.isLoop} + isFlat={item.isFlat} + isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor} zIndex={zIndex} /> ))} diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 507d494c74..2592759351 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -293,6 +293,7 @@ export type NodeOutPutVar = { vars: Var[] isStartNode?: boolean isLoop?: boolean + isFlat?: boolean } export type Block = { From da560e595033fe7ca410400e94a14bce3c1c9391 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Jul 2025 16:16:30 +0800 Subject: [PATCH 03/36] chore: current ui fix --- web/app/components/base/prompt-editor/index.tsx | 2 +- .../_base/components/variable/var-reference-vars.tsx | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 4288ad1e26..7d70f2b10e 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -112,7 +112,7 @@ const PromptEditor: FC = ({ workflowVariableBlock, currentBlock = { show: true, - generatorType: GeneratorType.prompt, + generatorType: GeneratorType.code, }, isSupportFileVar, }) => { 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 3414903318..8575aca247 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 @@ -86,6 +86,14 @@ const Item: FC = ({ } }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable]) + const varName = useMemo(() => { + if(!isFlat) + return itemData.variable + if (itemData.variable === 'current') + return isInCodeGeneratorInstructionEditor ? 'current_code' : 'current_prompt' + return itemData.variable + }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable]) + const objStructuredOutput: StructuredOutput | null = useMemo(() => { if (!isObj) return null const properties: Record = {}; @@ -175,7 +183,7 @@ const Item: FC = ({ {isLoopVar && } {isFlat && flatVarIcon} {!isEnv && !isChatVar && ( -
{itemData.variable}
+
{varName}
)} {isEnv && (
{itemData.variable.replace('env.', '')}
From 3160e5e5629d554904266204a5235ab1263f8dd1 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Jul 2025 17:52:11 +0800 Subject: [PATCH 04/36] 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]) From 4c76a5f57bc85b682acc6a5aea9de3ff4dba34f4 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Jul 2025 18:23:59 +0800 Subject: [PATCH 05/36] feat: support choose last run --- .../base/prompt-editor/constants.tsx | 1 + .../components/base/prompt-editor/index.tsx | 22 ++++++ .../plugins/component-picker-block/hooks.tsx | 17 ++++- .../plugins/component-picker-block/index.tsx | 14 +++- .../plugins/last-run-block/component.tsx | 40 +++++++++++ .../plugins/last-run-block/index.tsx | 65 ++++++++++++++++++ .../last-run-block-replacement-block.tsx | 61 +++++++++++++++++ .../plugins/last-run-block/node.tsx | 67 +++++++++++++++++++ .../components/base/prompt-editor/types.ts | 6 ++ .../variable/var-reference-vars.tsx | 2 + 10 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 web/app/components/base/prompt-editor/plugins/last-run-block/component.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/last-run-block/index.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/last-run-block/last-run-block-replacement-block.tsx create mode 100644 web/app/components/base/prompt-editor/plugins/last-run-block/node.tsx diff --git a/web/app/components/base/prompt-editor/constants.tsx b/web/app/components/base/prompt-editor/constants.tsx index 2fd0a62e95..c3313ba406 100644 --- a/web/app/components/base/prompt-editor/constants.tsx +++ b/web/app/components/base/prompt-editor/constants.tsx @@ -5,6 +5,7 @@ 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 LAST_RUN_PLACEHOLDER_TEXT = '{{#last_run#}}' 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 0a21587bc6..de83a29f7f 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -49,6 +49,12 @@ import { ErrorMessageBlockNode, ErrorMessageBlockReplacementBlock, } from './plugins/error-message-block' +import { + LastRunBlock, + LastRunBlockNode, + LastRunReplacementBlock, +} from './plugins/last-run-block' + import VariableBlock from './plugins/variable-block' import VariableValueBlock from './plugins/variable-value-block' import { VariableValueBlockNode } from './plugins/variable-value-block/node' @@ -62,6 +68,7 @@ import type { ErrorMessageBlockType, ExternalToolBlockType, HistoryBlockType, + LastRunBlockType, QueryBlockType, VariableBlockType, WorkflowVariableBlockType, @@ -95,6 +102,7 @@ export type PromptEditorProps = { workflowVariableBlock?: WorkflowVariableBlockType currentBlock?: CurrentBlockType errorMessageBlock?: ErrorMessageBlockType + lastRunBlock?: LastRunBlockType isSupportFileVar?: boolean } @@ -124,6 +132,9 @@ const PromptEditor: FC = ({ errorMessageBlock = { show: true, }, + lastRunBlock = { + show: true, + }, isSupportFileVar, }) => { const { eventEmitter } = useEventEmitterContextContext() @@ -143,6 +154,7 @@ const PromptEditor: FC = ({ VariableValueBlockNode, CurrentBlockNode, ErrorMessageBlockNode, + LastRunBlockNode, // LastRunBlockNode is used for error message block replacement ], editorState: textToEditorState(value || ''), onError: (error: Error) => { @@ -204,6 +216,7 @@ const PromptEditor: FC = ({ workflowVariableBlock={workflowVariableBlock} currentBlock={currentBlock} errorMessageBlock={errorMessageBlock} + lastRunBlock={lastRunBlock} isSupportFileVar={isSupportFileVar} /> = ({ workflowVariableBlock={workflowVariableBlock} currentBlock={currentBlock} errorMessageBlock={errorMessageBlock} + lastRunBlock={lastRunBlock} isSupportFileVar={isSupportFileVar} /> { @@ -274,6 +288,14 @@ const PromptEditor: FC = ({ ) } + { + lastRunBlock?.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 51689e1790..2032f22ce9 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 @@ -8,6 +8,7 @@ import type { ErrorMessageBlockType, ExternalToolBlockType, HistoryBlockType, + LastRunBlockType, QueryBlockType, VariableBlockType, WorkflowVariableBlockType, @@ -272,6 +273,7 @@ export const useOptions = ( workflowVariableBlockType?: WorkflowVariableBlockType, currentBlockType?: CurrentBlockType, errorMessageBlockType?: ErrorMessageBlockType, + lastRunBlockType?: LastRunBlockType, queryString?: string, ) => { const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock) @@ -295,6 +297,19 @@ export const useOptions = ( ], }) } + if(lastRunBlockType?.show && res.findIndex(v => v.nodeId === 'last_run') === -1) { + res.unshift({ + nodeId: 'last_run', + title: 'last_run', + isFlat: true, + vars: [ + { + variable: 'last_run', + type: VarType.object, + }, + ], + }) + } if(currentBlockType?.show && res.findIndex(v => v.nodeId === 'current') === -1) { const title = currentBlockType.generatorType === 'prompt' ? 'current_prompt' : 'current_code' res.unshift({ @@ -310,7 +325,7 @@ export const useOptions = ( }) } return res - }, [workflowVariableBlockType?.show, workflowVariableBlockType?.variables, errorMessageBlockType?.show, currentBlockType?.show, currentBlockType?.generatorType]) + }, [workflowVariableBlockType?.show, workflowVariableBlockType?.variables, errorMessageBlockType?.show, lastRunBlockType?.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 73b81f957f..cffb2762c2 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 @@ -21,6 +21,7 @@ import type { ErrorMessageBlockType, ExternalToolBlockType, HistoryBlockType, + LastRunBlockType, QueryBlockType, VariableBlockType, WorkflowVariableBlockType, @@ -37,6 +38,7 @@ 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' +import { INSERT_LAST_RUN_BLOCK_COMMAND } from '../last-run-block' type ComponentPickerProps = { triggerString: string @@ -48,6 +50,7 @@ type ComponentPickerProps = { workflowVariableBlock?: WorkflowVariableBlockType currentBlock?: CurrentBlockType errorMessageBlock?: ErrorMessageBlockType + lastRunBlock?: LastRunBlockType isSupportFileVar?: boolean } const ComponentPicker = ({ @@ -60,6 +63,7 @@ const ComponentPicker = ({ workflowVariableBlock, currentBlock, errorMessageBlock, + lastRunBlock, isSupportFileVar, }: ComponentPickerProps) => { const { eventEmitter } = useEventEmitterContextContext() @@ -98,6 +102,7 @@ const ComponentPicker = ({ workflowVariableBlock, currentBlock, errorMessageBlock, + lastRunBlock, ) const onSelectOption = useCallback( @@ -125,10 +130,13 @@ const ComponentPicker = ({ }) const isFlat = variables.length === 1 if(isFlat) { - if(variables[0] === 'current') + const varName = variables[0] + if(varName === 'current') editor.dispatchCommand(INSERT_CURRENT_BLOCK_COMMAND, currentBlock?.generatorType) - else if (variables[0] === 'error_message') + else if (varName === 'error_message') editor.dispatchCommand(INSERT_ERROR_MESSAGE_BLOCK_COMMAND, null) + else if (varName === 'last_run') + editor.dispatchCommand(INSERT_LAST_RUN_BLOCK_COMMAND, null) } else if (variables[1] === 'sys.query' || variables[1] === 'sys.files') { editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]]) @@ -226,7 +234,7 @@ const ComponentPicker = ({ } ) - }, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable, handleClose, isSupportFileVar]) + }, [allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, workflowVariableOptions, isSupportFileVar, handleClose, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString]) return ( = ({ + nodeKey, +}) => { + const [editor] = useLexicalComposerContext() + const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_LAST_RUN_COMMAND) + + useEffect(() => { + if (!editor.hasNodes([LastRunBlockNode])) + throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') + }, [editor]) + + return ( +
{ + e.stopPropagation() + }} + ref={ref} + > + +
last_run
+
+ ) +} + +export default LastRunBlockComponent diff --git a/web/app/components/base/prompt-editor/plugins/last-run-block/index.tsx b/web/app/components/base/prompt-editor/plugins/last-run-block/index.tsx new file mode 100644 index 0000000000..8bbbb8d4dd --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/last-run-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 { LastRunBlockType } from '../../types' +import { + $createLastRunBlockNode, + LastRunBlockNode, +} from './node' + +export const INSERT_LAST_RUN_BLOCK_COMMAND = createCommand('INSERT_LAST_RUN_BLOCK_COMMAND') +export const DELETE_LAST_RUN_COMMAND = createCommand('DELETE_LAST_RUN_COMMAND') + +const LastRunBlock = memo(({ + onInsert, + onDelete, +}: LastRunBlockType) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([LastRunBlockNode])) + throw new Error('Last_RunBlockPlugin: Last_RunBlock not registered on editor') + + return mergeRegister( + editor.registerCommand( + INSERT_LAST_RUN_BLOCK_COMMAND, + () => { + const Node = $createLastRunBlockNode() + + $insertNodes([Node]) + + if (onInsert) + onInsert() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerCommand( + DELETE_LAST_RUN_COMMAND, + () => { + if (onDelete) + onDelete() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + ) + }, [editor, onDelete, onInsert]) + + return null +}) +LastRunBlock.displayName = 'LastRunBlock' + +export { LastRunBlock } +export { LastRunBlockNode } from './node' +export { default as LastRunReplacementBlock } from './last-run-block-replacement-block' diff --git a/web/app/components/base/prompt-editor/plugins/last-run-block/last-run-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/last-run-block/last-run-block-replacement-block.tsx new file mode 100644 index 0000000000..9d28828016 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/last-run-block/last-run-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 { LAST_RUN_PLACEHOLDER_TEXT } from '../../constants' +import type { LastRunBlockType } from '../../types' +import { + $createLastRunBlockNode, + LastRunBlockNode, +} from './node' +import { CustomTextNode } from '../custom-text/node' + +const REGEX = new RegExp(LAST_RUN_PLACEHOLDER_TEXT) + +const LastRunReplacementBlock = ({ + onInsert, +}: LastRunBlockType) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([LastRunBlockNode])) + throw new Error('LastRunMessageBlockNodePlugin: LastRunMessageBlockNode not registered on editor') + }, [editor]) + + const createLastRunBlockNode = useCallback((): LastRunBlockNode => { + if (onInsert) + onInsert() + return $applyNodeReplacement($createLastRunBlockNode()) + }, [onInsert]) + + const getMatch = useCallback((text: string) => { + const matchArr = REGEX.exec(text) + + if (matchArr === null) + return null + + const startOffset = matchArr.index + const endOffset = startOffset + LAST_RUN_PLACEHOLDER_TEXT.length + return { + end: endOffset, + start: startOffset, + } + }, []) + + useEffect(() => { + REGEX.lastIndex = 0 + return mergeRegister( + editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createLastRunBlockNode)), + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return null +} + +export default memo(LastRunReplacementBlock) diff --git a/web/app/components/base/prompt-editor/plugins/last-run-block/node.tsx b/web/app/components/base/prompt-editor/plugins/last-run-block/node.tsx new file mode 100644 index 0000000000..5f61c3138b --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/last-run-block/node.tsx @@ -0,0 +1,67 @@ +import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical' +import { DecoratorNode } from 'lexical' +import LastRunBlockComponent from './component' + +export type SerializedNode = SerializedLexicalNode + +export class LastRunBlockNode extends DecoratorNode { + static getType(): string { + return 'last-run-block' + } + + static clone(node: LastRunBlockNode): LastRunBlockNode { + return new LastRunBlockNode(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(): LastRunBlockNode { + const node = $createLastRunBlockNode() + + return node + } + + exportJSON(): SerializedNode { + return { + type: 'last-run-block', + version: 1, + } + } + + getTextContent(): string { + return '{{#last_run#}}' + } +} +export function $createLastRunBlockNode(): LastRunBlockNode { + return new LastRunBlockNode() +} + +export function $isLastRunBlockNode( + node: LastRunBlockNode | LexicalNode | null | undefined, +): boolean { + return node instanceof LastRunBlockNode +} diff --git a/web/app/components/base/prompt-editor/types.ts b/web/app/components/base/prompt-editor/types.ts index 939d8afd89..45ae2e2af7 100644 --- a/web/app/components/base/prompt-editor/types.ts +++ b/web/app/components/base/prompt-editor/types.ts @@ -89,3 +89,9 @@ export type ErrorMessageBlockType = { onInsert?: () => void onDelete?: () => void } + +export type LastRunBlockType = { + 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 0bdcd529fb..688ffc6f9e 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 @@ -85,6 +85,8 @@ const Item: FC = ({ return case 'error_message': return + default: + return } }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable]) From 5d232ac1bc330c4f567d279a3ca56643a1cb10b5 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 17 Jul 2025 14:39:00 +0800 Subject: [PATCH 06/36] chore: last run divide --- .../_base/components/variable/var-reference-vars.tsx | 12 ++++++++++-- web/i18n/en-US/workflow.ts | 1 + web/i18n/zh-Hans/workflow.ts | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) 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 688ffc6f9e..e828d86b6f 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 @@ -175,7 +175,8 @@ const Item: FC = ({ className={cn( (isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]', isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'), - 'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3') + 'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3', + ) } onClick={handleChosen} onMouseDown={e => e.preventDefault()} @@ -378,7 +379,7 @@ const VarReferenceVars: FC = ({ { filteredVars.map((item, i) => ( -
+
{!item.isFlat && (
= ({ zIndex={zIndex} /> ))} + {item.isFlat && !filteredVars[i + 1]?.isFlat && ( +
+
+
{t('workflow.debug.lastOutput')}
+
+
+ )}
)) }
diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index c56b497ac2..23fd382acf 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -965,6 +965,7 @@ const translation = { chatNode: 'Conversation', systemNode: 'System', }, + lastOutput: 'Last Output', }, } diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 6bd202d58f..d6eeac13b6 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -966,6 +966,7 @@ const translation = { chatNode: '会话变量', systemNode: '系统变量', }, + lastOutput: '上次输出', }, } From 7907235124c6d805f6899b423a947a18fc7cea9f Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 17 Jul 2025 16:43:56 +0800 Subject: [PATCH 07/36] feat: gener instrument left --- .../config-prompt/simple-prompt-input.tsx | 3 + .../config/automatic/get-automatic-res.tsx | 108 ++++++++++++------ .../config/automatic/instruction-editor.tsx | 94 +++++++++++++++ .../components/base/prompt-editor/index.tsx | 14 +-- .../workflow/hooks/use-workflow-variables.ts | 3 +- .../nodes/_base/components/prompt/editor.tsx | 8 +- .../llm/components/prompt-generator-btn.tsx | 11 +- 7 files changed, 191 insertions(+), 50 deletions(-) create mode 100644 web/app/components/app/configuration/config/automatic/instruction-editor.tsx diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx index eb0f524386..51fa0c173d 100644 --- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -27,6 +27,7 @@ import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/ba import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { noop } from 'lodash-es' +import { GeneratorType } from '../config/automatic/types' export type ISimplePromptInput = { mode: AppType @@ -276,6 +277,8 @@ const Prompt: FC = ({ isShow={showAutomatic} onClose={showAutomaticFalse} onFinished={handleAutomaticRes} + isBasicMode + generatorType={GeneratorType.prompt} /> )}
diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index aacaa81ac2..c8f801663a 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -39,13 +39,20 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import type { ModelModeType } from '@/types/app' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' +import InstructionEditor from './instruction-editor' +import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' +import type { GeneratorType } from './types' +import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' export type IGetAutomaticResProps = { mode: AppType isShow: boolean onClose: () => void onFinished: (res: AutomaticRes) => void - isInLLMNode?: boolean + nodesOutputVars?: NodeOutPutVar[] + availableNodes?: Node[] + generatorType: GeneratorType + isBasicMode?: boolean } const TryLabel: FC<{ @@ -68,7 +75,10 @@ const GetAutomaticRes: FC = ({ mode, isShow, onClose, - isInLLMNode, + nodesOutputVars, + availableNodes, + generatorType, + isBasicMode, onFinished, }) => { const { t } = useTranslation() @@ -124,6 +134,11 @@ const GetAutomaticRes: FC = ({ ] const [instruction, setInstruction] = useState('') + const [ideaOutput, setIdeaOutput] = useState('') + const [isFoldIdeaOutput, { + toggle: toggleFoldIdeaOutput, + }] = useBoolean(true) + const handleChooseTemplate = useCallback((key: string) => { return () => { const template = t(`appDebug.generate.template.${key}.instruction`) @@ -210,7 +225,6 @@ const GetAutomaticRes: FC = ({ const { error, ...res } = await generateRule({ instruction, model_config: model, - no_variable: !!isInLLMNode, }) setRes(res) if (error) { @@ -240,11 +254,11 @@ const GetAutomaticRes: FC = ({ >
-
+
{t('appDebug.generate.title')}
{t('appDebug.generate.description')}
-
+
= ({ hideDebugWithMultipleModel />
-
-
-
{t('appDebug.generate.tryIt')}
-
-
-
- {tryList.map(item => ( - - ))} + {isBasicMode && ( +
+
+
{t('appDebug.generate.tryIt')}
+
+
+
+ {tryList.map(item => ( + + ))} +
-
+ )} + {/* inputs */} -
-
-
{t('appDebug.generate.instruction')}
-