diff --git a/web/app/components/base/icons/assets/vender/line/general/assemble-variables.svg b/web/app/components/base/icons/assets/vender/line/general/assemble-variables.svg new file mode 100644 index 0000000000..0575036fa9 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/assemble-variables.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/app/components/base/icons/src/vender/line/general/AssembleVariables.json b/web/app/components/base/icons/src/vender/line/general/AssembleVariables.json new file mode 100644 index 0000000000..5db6132599 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/AssembleVariables.json @@ -0,0 +1,53 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.91992 1.6875C3.23055 1.68754 3.48242 1.93937 3.48242 2.25C3.48242 2.56063 3.23055 2.81246 2.91992 2.8125C2.63855 2.8125 2.41064 3.04041 2.41064 3.32178V5.46436C2.41061 5.61344 2.35148 5.75637 2.24609 5.86182L2.10791 6L2.24609 6.13818C2.35148 6.24363 2.41061 6.38656 2.41064 6.53564V8.67822C2.41064 8.95959 2.63855 9.1875 2.91992 9.1875C3.23055 9.18754 3.48242 9.43937 3.48242 9.75C3.48242 10.0606 3.23055 10.3125 2.91992 10.3125C2.01723 10.3125 1.28564 9.58091 1.28564 8.67822V6.76855L0.914551 6.39795C0.809062 6.29246 0.75 6.14918 0.75 6C0.75 5.85082 0.809062 5.70754 0.914551 5.60205L1.28564 5.23145V3.32178C1.28564 2.41909 2.01723 1.6875 2.91992 1.6875Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M9.08008 1.6875C9.98276 1.68751 10.7144 2.41909 10.7144 3.32178V5.23145L11.085 5.60205C11.1904 5.70754 11.25 5.85082 11.25 6C11.25 6.14918 11.1904 6.29246 11.085 6.39795L10.7144 6.76855V8.67822C10.7144 9.58107 9.98213 10.3125 9.08008 10.3125C8.76942 10.3125 8.51758 10.0607 8.51758 9.75C8.51758 9.43934 8.76942 9.1875 9.08008 9.1875C9.36113 9.18749 9.58936 8.95943 9.58936 8.67822V6.53564C9.58939 6.38654 9.64849 6.24363 9.75391 6.13818L9.89209 6L9.75391 5.86182C9.64849 5.75637 9.58939 5.61346 9.58936 5.46436V3.32178C9.58936 3.04041 9.36144 2.81251 9.08008 2.8125C8.76942 2.8125 8.51758 2.56066 8.51758 2.25C8.51758 1.93934 8.76942 1.6875 9.08008 1.6875Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M5.24707 5.07715C5.36302 5.07715 5.46712 5.14866 5.50879 5.25684L5.8335 6.10059C5.88932 6.24563 6.00388 6.36018 6.14893 6.41602L6.99268 6.74072C7.10086 6.78238 7.17236 6.88648 7.17236 7.00244C7.17229 7.11832 7.10078 7.22202 6.99268 7.26367L6.14893 7.58838C6.00378 7.64424 5.88929 7.75912 5.8335 7.9043L5.50879 8.74756C5.46715 8.8558 5.36307 8.92725 5.24707 8.92725C5.13116 8.92717 5.02746 8.85572 4.98584 8.74756L4.66113 7.9043C4.60526 7.75904 4.49046 7.6442 4.34521 7.58838L3.50195 7.26367C3.39378 7.22205 3.32234 7.11835 3.32227 7.00244C3.32227 6.88645 3.39371 6.78236 3.50195 6.74072L4.34521 6.41602C4.49039 6.36022 4.60523 6.24573 4.66113 6.10059L4.98584 5.25684C5.02749 5.14874 5.13121 5.07723 5.24707 5.07715Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M6.89746 2.87744C6.98013 2.87754 7.05427 2.92822 7.08398 3.00537L7.29053 3.54297C7.34635 3.68816 7.46125 3.80302 7.60645 3.85889L8.14404 4.06543C8.22123 4.0952 8.27246 4.16966 8.27246 4.25244C8.27236 4.33513 8.22116 4.40922 8.14404 4.43896L7.60645 4.64551C7.46125 4.70138 7.34635 4.81624 7.29053 4.96143L7.08398 5.49902C7.05428 5.57614 6.98014 5.62734 6.89746 5.62744C6.81468 5.62744 6.74019 5.57622 6.71045 5.49902L6.50391 4.96143C6.44808 4.81624 6.33318 4.70138 6.18799 4.64551L5.65039 4.43896C5.57328 4.40922 5.52256 4.33513 5.52246 4.25244C5.52246 4.16966 5.5732 4.0952 5.65039 4.06543L6.18799 3.85889C6.33318 3.80302 6.44808 3.68816 6.50391 3.54297L6.71045 3.00537C6.74019 2.92814 6.81469 2.87744 6.89746 2.87744Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "AssembleVariables" +} diff --git a/web/app/components/base/icons/src/vender/line/general/AssembleVariables.tsx b/web/app/components/base/icons/src/vender/line/general/AssembleVariables.tsx new file mode 100644 index 0000000000..40b72561f2 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/AssembleVariables.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import type { IconData } from '@/app/components/base/icons/IconBase' +import * as React from 'react' +import IconBase from '@/app/components/base/icons/IconBase' +import data from './AssembleVariables.json' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject> + }, +) => + +Icon.displayName = 'AssembleVariables' + +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 2409367264..e4d9b39cfe 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 @@ -1,4 +1,5 @@ export { default as AtSign } from './AtSign' +export { default as AssembleVariables } from './AssembleVariables' export { default as Bookmark } from './Bookmark' export { default as Check } from './Check' export { default as CheckDone01 } from './CheckDone01' 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 a2b00a9506..f0e50014c9 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 @@ -9,8 +9,7 @@ import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' -import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' -import { MagicWand } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' +import { AssembleVariables, CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import Input from '@/app/components/base/input' import { @@ -414,8 +413,8 @@ const VarReferenceVars: FC = ({ onClick={handleAssembleVariables} onMouseDown={e => e.preventDefault()} > - - + + {t('nodes.tool.assembleVariables', { ns: 'workflow' })} diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/agent-header-bar.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/agent-header-bar.tsx index dcb46f78d2..ac757431c5 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/agent-header-bar.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/agent-header-bar.tsx @@ -11,6 +11,7 @@ type AgentHeaderBarProps = { onRemove: () => void onViewInternals?: () => void hasWarning?: boolean + showAtPrefix?: boolean } const AgentHeaderBar: FC = ({ @@ -18,6 +19,7 @@ const AgentHeaderBar: FC = ({ onRemove, onViewInternals, hasWarning, + showAtPrefix = true, }) => { const { t } = useTranslation() @@ -36,7 +38,7 @@ const AgentHeaderBar: FC = ({ - @ + {showAtPrefix && '@'} {agentName} - + {onViewInternals && ( + + )} ) } diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx index 84aa94ba87..ecbf509950 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx @@ -2,6 +2,7 @@ import type { AgentNode, WorkflowVariableBlockType } from '@/app/components/base import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types' import type { MentionConfig, VarKindType } from '@/app/components/workflow/nodes/_base/types' import type { AgentNodeType } from '@/app/components/workflow/nodes/agent/types' +import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types' import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types' import type { CommonNodeType, @@ -10,6 +11,7 @@ import type { PromptTemplateItem, ValueSelector, Node as WorkflowNode, + VarType, } from '@/app/components/workflow/types' import { memo, @@ -38,6 +40,11 @@ import Placeholder from './placeholder' * Example: {{@agent-123.context@}} -> captures "agent-123" */ const AGENT_CONTEXT_VAR_PATTERN = /\{\{[@#]([^.@#]+)\.context[@#]\}\}/g +const buildAssemblePlaceholder = (toolNodeId?: string, paramKey?: string) => { + if (!toolNodeId || !paramKey) + return '' + return `{{#${toolNodeId}_ext_${paramKey}.result#}}` +} const DEFAULT_MENTION_CONFIG: MentionConfig = { extractor_node_id: '', output_selector: [], @@ -166,6 +173,16 @@ const MixedVariableTextInput = ({ }, {} as Record) }, [availableNodes]) + const assemblePlaceholder = useMemo(() => { + return buildAssemblePlaceholder(toolNodeId, paramKey) + }, [paramKey, toolNodeId]) + + const isAssembleValue = useMemo(() => { + if (!assemblePlaceholder) + return false + return value.trim() === assemblePlaceholder + }, [assemblePlaceholder, value]) + const contextNodeIds = useMemo(() => { const ids = new Set() availableNodes.forEach((node) => { @@ -182,6 +199,12 @@ const MixedVariableTextInput = ({ }, {} as Record) }, [nodes]) + const assembleExtractorNodeId = useMemo(() => { + if (!toolNodeId || !paramKey) + return '' + return `${toolNodeId}_ext_${paramKey}` + }, [paramKey, toolNodeId]) + type DetectedAgent = { nodeId: string name: string @@ -278,6 +301,12 @@ const MixedVariableTextInput = ({ return agentWarning || extractorWarning }, [detectedAgentFromValue, getNodeWarning, nodesById, paramKey, toolNodeId]) + const hasAssembleWarning = useMemo(() => { + if (!isAssembleValue || !assembleExtractorNodeId) + return false + return getNodeWarning(nodesById[assembleExtractorNodeId]) + }, [assembleExtractorNodeId, getNodeWarning, isAssembleValue, nodesById]) + const syncExtractorPromptFromText = useCallback((text: string) => { if (!toolNodeId || !paramKey) return @@ -408,6 +437,70 @@ const MixedVariableTextInput = ({ setControlPromptEditorRerenderKey(Date.now()) }, [handleSyncWorkflowDraft, nodesMetaDataMap, onChange, paramKey, reactFlowStore, setControlPromptEditorRerenderKey, syncExtractorPromptFromText, toolNodeId, value]) + const handleAssembleSelect = useCallback(() => { + if (!onChange || !toolNodeId || !paramKey || !assemblePlaceholder) + return + + const defaultValue = nodesMetaDataMap?.[BlockEnum.Code]?.defaultValue as Partial | undefined + if (!defaultValue) + return + + const extractorNodeId = `${toolNodeId}_ext_${paramKey}` + const { getNodes, setNodes } = reactFlowStore.getState() + const currentNodes = getNodes() + const existingNode = currentNodes.find(node => node.id === extractorNodeId) + const shouldReplace = existingNode && existingNode.data.type !== BlockEnum.Code + const shouldCreate = !existingNode || shouldReplace + + if (shouldCreate) { + const nextNodes = shouldReplace + ? currentNodes.filter(node => node.id !== extractorNodeId) + : currentNodes + const { newNode } = generateNewNode({ + id: extractorNodeId, + type: getNodeCustomTypeByNodeDataType(BlockEnum.Code), + data: { + ...defaultValue, + type: BlockEnum.Code, + title: defaultValue?.title ?? '', + desc: defaultValue?.desc || '', + parent_node_id: toolNodeId, + outputs: { + result: { + type: VarType.string, + children: null, + }, + }, + }, + position: { + x: 0, + y: 0, + }, + hidden: true, + }) + setNodes([...nextNodes, newNode]) + handleSyncWorkflowDraft() + } + + const mentionConfigWithOutputSelector: MentionConfig = { + ...DEFAULT_MENTION_CONFIG, + extractor_node_id: extractorNodeId, + output_selector: ['result'], + } + onChange(assemblePlaceholder, VarKindTypeEnum.mention, mentionConfigWithOutputSelector) + setControlPromptEditorRerenderKey(Date.now()) + }, [assemblePlaceholder, handleSyncWorkflowDraft, nodesMetaDataMap, onChange, paramKey, reactFlowStore, setControlPromptEditorRerenderKey, toolNodeId]) + + const handleAssembleRemove = useCallback(() => { + if (!onChange || !assemblePlaceholder) + return + + const nextValue = value.replace(assemblePlaceholder, '') + removeExtractorNode() + onChange(nextValue, VarKindTypeEnum.mixed, null) + setControlPromptEditorRerenderKey(Date.now()) + }, [assemblePlaceholder, onChange, removeExtractorNode, setControlPromptEditorRerenderKey, value]) + const handleOpenSubGraphModal = useCallback(() => { setIsSubGraphModalOpen(true) }, []) @@ -427,7 +520,15 @@ const MixedVariableTextInput = ({ 'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs', )} > - {detectedAgentFromValue && ( + {isAssembleValue && ( + + )} + {!isAssembleValue && detectedAgentFromValue && ( )} - 0 && !detectedAgentFromValue, - agentNodes, - onSelect: handleAgentSelect, - }} - placeholder={} - onChange={(text) => { - const hasPlaceholder = new RegExp(AGENT_CONTEXT_VAR_PATTERN.source).test(text) - if (hasPlaceholder) - syncExtractorPromptFromText(text) - if (detectedAgentFromValue && !hasPlaceholder) { - removeExtractorNode() - onChange?.(text, VarKindTypeEnum.mixed, null) - return - } - onChange?.(text) - }} - /> + {!isAssembleValue && ( + 0 && !detectedAgentFromValue, + agentNodes, + onSelect: handleAgentSelect, + }} + placeholder={} + onChange={(text) => { + const hasPlaceholder = new RegExp(AGENT_CONTEXT_VAR_PATTERN.source).test(text) + if (hasPlaceholder) + syncExtractorPromptFromText(text) + if (detectedAgentFromValue && !hasPlaceholder) { + removeExtractorNode() + onChange?.(text, VarKindTypeEnum.mixed, null) + return + } + onChange?.(text) + }} + /> + )} {toolNodeId && detectedAgentFromValue && sourceVariable && (