diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index a10b0c67d6..e77dce4b66 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -26,6 +26,8 @@ import { HistoryBlockNode } from './plugins/history-block/node' import HistoryBlockReplacementBlock from './plugins/history-block-replacement-block' import QueryBlock from './plugins/query-block' import { QueryBlockNode } from './plugins/query-block/node' +import WorkflowVariableBlock from './plugins/workflow-variable-block' +import { WorkflowVariableBlockNode } from './plugins/workflow-variable-block/node' import QueryBlockReplacementBlock from './plugins/query-block-replacement-block' import VariableBlock from './plugins/variable-block' import VariableValueBlock from './plugins/variable-value-block' @@ -42,6 +44,10 @@ import { UPDATE_HISTORY_EVENT_EMITTER, } from './constants' import { useEventEmitterContextContext } from '@/context/event-emitter' +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' export type PromptEditorProps = { className?: string @@ -81,6 +87,14 @@ export type PromptEditorProps = { onInsert?: () => void onDelete?: () => void } + workflowVariableBlock?: { + show?: boolean + selectable?: boolean + variables: NodeOutPutVar[] + getWorkflowNode: (nodeId: string) => Node | undefined + onInsert?: () => void + onDelete?: () => void + } } const PromptEditor: FC = ({ @@ -121,6 +135,14 @@ const PromptEditor: FC = ({ onInsert: () => { }, onDelete: () => { }, }, + workflowVariableBlock = { + show: true, + selectable: true, + variables: [], + getWorkflowNode: () => {}, + onInsert: () => { }, + onDelete: () => { }, + }, }) => { const { eventEmitter } = useEventEmitterContextContext() const initialConfig = { @@ -134,6 +156,7 @@ const PromptEditor: FC = ({ ContextBlockNode, HistoryBlockNode, QueryBlockNode, + WorkflowVariableBlockNode, VariableValueBlockNode, ], editorState: value ? textToEditorState(value as string) : null, @@ -177,6 +200,8 @@ const PromptEditor: FC = ({ queryDisabled={!queryBlock.selectable} queryShow={queryBlock.show} outToolDisabled={outToolDisabled} + workflowVariableShow={workflowVariableBlock.show} + workflowVariables={workflowVariableBlock.variables} /> = ({ ) } + { + workflowVariableBlock.show && ( + <> + + + ) + } diff --git a/web/app/components/base/prompt-editor/plugins/component-picker.tsx b/web/app/components/base/prompt-editor/plugins/component-picker.tsx index a21f6fe8ba..1e8ab2d8ee 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker.tsx @@ -13,10 +13,13 @@ import { INSERT_CONTEXT_BLOCK_COMMAND } from './context-block' import { INSERT_VARIABLE_BLOCK_COMMAND } from './variable-block' import { INSERT_HISTORY_BLOCK_COMMAND } from './history-block' import { INSERT_QUERY_BLOCK_COMMAND } from './query-block' +import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from './workflow-variable-block' import { File05 } from '@/app/components/base/icons/src/vender/solid/files' import { Variable } from '@/app/components/base/icons/src/vender/line/development' import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general' import { UserEdit02 } from '@/app/components/base/icons/src/vender/solid/users' +import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' +import type { NodeOutPutVar } from '@/app/components/workflow/types' class ComponentPickerOption extends MenuOption { title: string @@ -97,6 +100,8 @@ type ComponentPickerProps = { historyShow?: boolean queryShow?: boolean outToolDisabled?: boolean + workflowVariableShow?: boolean + workflowVariables?: NodeOutPutVar[] } const ComponentPicker: FC = ({ contextDisabled, @@ -106,6 +111,8 @@ const ComponentPicker: FC = ({ historyShow, queryShow, outToolDisabled, + workflowVariableShow, + workflowVariables, }) => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() @@ -184,6 +191,10 @@ const ComponentPicker: FC = ({ [editor], ) + const handleSelectWorkflowVariable = useCallback((variables: string[]) => { + editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) + }, [editor]) + return ( = ({ option={option} /> ))} + { + workflowVariableShow && !!workflowVariables?.length && ( + + ) + } , anchorElementRef.current, ) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx new file mode 100644 index 0000000000..725a5c7a6d --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -0,0 +1,38 @@ +import type { FC } from 'react' +import { useSelectOrDelete } from '../../hooks' +import { DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND } from './index' +import type { Node } from '@/app/components/workflow/types' + +type WorkflowVariableBlockComponentProps = { + nodeKey: string + variables: string[] + getWorkflowNode: (nodeId: string) => Node +} + +const WorkflowVariableBlockComponent: FC = ({ + nodeKey, + variables, + getWorkflowNode, +}) => { + const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND) + const node = getWorkflowNode(variables[0]) + const variablesLength = variables.length + const lastVariable = variables[variablesLength - 1] + + return ( +
+
{node.data.title}
+ / +
{lastVariable}
+
+ ) +} + +export default WorkflowVariableBlockComponent diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx new file mode 100644 index 0000000000..1cb1130469 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx @@ -0,0 +1,65 @@ +import type { FC } from 'react' +import { useEffect } from 'react' +import { + $insertNodes, + COMMAND_PRIORITY_EDITOR, + createCommand, +} from 'lexical' +import { mergeRegister } from '@lexical/utils' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { + $createWorkflowVariableBlockNode, + WorkflowVariableBlockNode, +} from './node' +import type { Node } from '@/app/components/workflow/types' + +export const INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND') +export const DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND') + +export type WorkflowVariableBlockProps = { + getWorkflowNode: (nodeId: string) => Node + onInsert?: () => void + onDelete?: () => void +} +const WorkflowVariableBlock: FC = ({ + getWorkflowNode, + onInsert, + onDelete, +}) => { + const [editor] = useLexicalComposerContext() + + useEffect(() => { + if (!editor.hasNodes([WorkflowVariableBlockNode])) + throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') + + return mergeRegister( + editor.registerCommand( + INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, + (variables: string[]) => { + const contextBlockNode = $createWorkflowVariableBlockNode(variables, getWorkflowNode) + + $insertNodes([contextBlockNode]) + if (onInsert) + onInsert() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerCommand( + DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, + () => { + if (onDelete) + onDelete() + + return true + }, + COMMAND_PRIORITY_EDITOR, + ), + ) + }, [editor, onInsert, onDelete, getWorkflowNode]) + + return null +} + +export default WorkflowVariableBlock diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx new file mode 100644 index 0000000000..89fff18b83 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx @@ -0,0 +1,81 @@ +import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical' +import { DecoratorNode } from 'lexical' +import WorkflowVariableBlockComponent from './component' +import type { Node } from '@/app/components/workflow/types' + +export type SerializedNode = SerializedLexicalNode & { + variables: string[] + getWorkflowNode: (nodeId: string) => Node +} + +export class WorkflowVariableBlockNode extends DecoratorNode { + __variables: string[] + __getWorkflowNode: (nodeId: string) => Node + + static getType(): string { + return 'workflow-variable-block' + } + + static clone(node: WorkflowVariableBlockNode): WorkflowVariableBlockNode { + return new WorkflowVariableBlockNode(node.__variables, node.__getWorkflowNode) + } + + isInline(): boolean { + return true + } + + constructor(variables: string[], getWorkflowNode: (nodeId: string) => Node, key?: NodeKey) { + super(key) + + this.__variables = variables + this.__getWorkflowNode = getWorkflowNode + } + + createDOM(): HTMLElement { + const div = document.createElement('div') + div.classList.add('inline-flex', 'items-center', 'align-middle') + return div + } + + updateDOM(): false { + return false + } + + decorate(): JSX.Element { + return ( + + ) + } + + static importJSON(serializedNode: SerializedNode): WorkflowVariableBlockNode { + const node = $createWorkflowVariableBlockNode(serializedNode.variables, serializedNode.getWorkflowNode) + + return node + } + + exportJSON(): SerializedNode { + return { + type: 'workflow-variable-block', + version: 1, + variables: this.__variables, + getWorkflowNode: this.__getWorkflowNode, + } + } + + getTextContent(): string { + return `{{#${this.__variables.join('.')}#}}` + } +} +export function $createWorkflowVariableBlockNode(variables: string[], getWorkflowNodeName: (nodeId: string) => Node): WorkflowVariableBlockNode { + return new WorkflowVariableBlockNode(variables, getWorkflowNodeName) +} + +export function $isWorkflowVariableBlockNode( + node: WorkflowVariableBlockNode | LexicalNode | null | undefined, +): node is WorkflowVariableBlockNode { + return node instanceof WorkflowVariableBlockNode +} diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 9e2cf3bea4..f179a441b3 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -342,6 +342,12 @@ export const useWorkflow = () => { setViewport(viewport) }, [store, reactflow]) + const getNode = useCallback((nodeId: string) => { + const { getNodes } = store.getState() + + return getNodes().find(node => node.id === nodeId) + }, [store]) + return { handleLayout, getTreeLeafNodes, @@ -355,6 +361,7 @@ export const useWorkflow = () => { formatTimeFromNow, getValidTreeNodes, renderTreeFromRecord, + getNode, } } diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index 1772b005fa..58e44efa59 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -5,6 +5,8 @@ import cn from 'classnames' import copy from 'copy-to-clipboard' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' +import { useWorkflow } from '../../../../hooks' +import type { NodeOutPutVar } from '../../../../types' import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn' import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend' import PromptEditorHeightResizeWrap from '@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap' @@ -30,6 +32,7 @@ type Props = { history: boolean query: boolean } + nodesOutputVars?: NodeOutPutVar[] } const Editor: FC = ({ @@ -45,8 +48,12 @@ const Editor: FC = ({ isChatApp, isShowContext, hasSetBlockStatus, + nodesOutputVars, }) => { const { t } = useTranslation() + const { getNode } = useWorkflow() + + console.log(nodesOutputVars, '2') const isShowHistory = !isChatModel && isChatApp const isShowQuery = isShowHistory @@ -146,6 +153,12 @@ const Editor: FC = ({ show: justVar ? false : isShowQuery, selectable: !hasSetBlockStatus?.query, }} + workflowVariableBlock={{ + show: true, + selectable: true, + variables: nodesOutputVars || [], + getWorkflowNode: getNode, + }} onChange={onChange} onBlur={setBlur} onFocus={setFocus} diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx index c8add8c467..a928f3fe47 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -146,6 +146,7 @@ const ConfigPrompt: FC = ({ isChatApp={isChatApp} isShowContext={isShowContext} hasSetBlockStatus={hasSetBlockStatus} + nodesOutputVars={availableVarList} /> ) })