diff --git a/.nvmrc b/.nvmrc index 7af24b7ddb..a45fd52cc5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.11.0 +24 diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 99e3a3325c..05853d1b4d 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -212,6 +212,19 @@ const PromptEditor: FC = ({ lastRunBlock={lastRunBlock} isSupportFileVar={isSupportFileVar} /> + void +} + +const Item: FC = ({ node, onSelect }) => { + const [isHovering, setIsHovering] = useState(false) + + return ( + + ) +} + +type Props = { + nodes: AgentNode[] + onSelect: (node: AgentNode) => void + onClose?: () => void + onBlur?: () => void + hideSearch?: boolean + searchBoxClassName?: string + maxHeightClass?: string + autoFocus?: boolean +} + +const AgentNodeList: FC = ({ + nodes, + onSelect, + onClose, + onBlur, + hideSearch, + searchBoxClassName, + maxHeightClass, + autoFocus = true, +}) => { + const { t } = useTranslation() + const [searchText, setSearchText] = useState('') + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault() + onClose?.() + } + } + + const filteredNodes = nodes.filter((node) => { + if (!searchText) + return true + return node.title.toLowerCase().includes(searchText.toLowerCase()) + }) + + return ( + <> + {!hideSearch && ( + <> +
+ setSearchText(e.target.value)} + onClick={e => e.stopPropagation()} + onKeyDown={handleKeyDown} + onClear={() => setSearchText('')} + onBlur={onBlur} + autoFocus={autoFocus} + /> +
+
+ + )} + + {filteredNodes.length > 0 + ? ( +
+ {filteredNodes.map(node => ( + + ))} +
+ ) + : ( +
+ {t('common.noAgentNodes', { ns: 'workflow' })} +
+ )} + + ) +} + +export default React.memo(AgentNodeList) 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 new file mode 100644 index 0000000000..f956188474 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/agent-header-bar.tsx @@ -0,0 +1,52 @@ +import type { FC } from 'react' +import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import { Agent } from '@/app/components/base/icons/src/vender/workflow' + +type AgentHeaderBarProps = { + agentName: string + onRemove: () => void + onViewInternals?: () => void +} + +const AgentHeaderBar: FC = ({ + agentName, + onRemove, + onViewInternals, +}) => { + const { t } = useTranslation() + + return ( +
+
+
+
+ +
+ + @ + {agentName} + + +
+
+ +
+ ) +} + +export default memo(AgentHeaderBar) 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 ceef2c3489..8cd98bd3a3 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 @@ -4,14 +4,23 @@ import type { } from '@/app/components/workflow/types' import { memo, + useCallback, + useMemo, } from 'react' import { useTranslation } from 'react-i18next' import PromptEditor from '@/app/components/base/prompt-editor' import { useStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import AgentHeaderBar from './agent-header-bar' import Placeholder from './placeholder' +/** + * Matches workflow variable syntax: {{#nodeId.varName#}} + * Example: {{#agent-123.text#}} -> captures "agent-123.text" + */ +const WORKFLOW_VAR_PATTERN = /\{\{#([^#]+)#\}\}/g + type MixedVariableTextInputProps = { readOnly?: boolean nodesOutputVars?: NodeOutPutVar[] @@ -21,7 +30,9 @@ type MixedVariableTextInputProps = { showManageInputField?: boolean onManageInputField?: () => void disableVariableInsertion?: boolean + onViewInternals?: () => void } + const MixedVariableTextInput = ({ readOnly = false, nodesOutputVars, @@ -31,43 +42,95 @@ const MixedVariableTextInput = ({ showManageInputField, onManageInputField, disableVariableInsertion = false, + onViewInternals, }: MixedVariableTextInputProps) => { const { t } = useTranslation() const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) + const setControlPromptEditorRerenderKey = useStore(s => s.setControlPromptEditorRerenderKey) + + const nodesByIdMap = useMemo(() => { + return availableNodes.reduce((acc, node) => { + acc[node.id] = node + return acc + }, {} as Record) + }, [availableNodes]) + + const detectedAgentFromValue = useMemo(() => { + if (!value) + return null + + const matches = value.matchAll(WORKFLOW_VAR_PATTERN) + for (const match of matches) { + const variablePath = match[1] + const nodeId = variablePath.split('.')[0] + const node = nodesByIdMap[nodeId] + if (node?.data.type === BlockEnum.Agent) { + return { + nodeId, + name: node.data.title, + } + } + } + return null + }, [value, nodesByIdMap]) + + const handleAgentRemove = useCallback(() => { + if (!detectedAgentFromValue || !onChange) + return + + const pattern = /\{\{#([^#]+)#\}\}/g + const valueWithoutAgentVars = value.replace(pattern, (match, variablePath) => { + const nodeId = variablePath.split('.')[0] + return nodeId === detectedAgentFromValue.nodeId ? '' : match + }).trim() + + onChange(valueWithoutAgentVars) + setControlPromptEditorRerenderKey(Date.now()) + }, [detectedAgentFromValue, value, onChange, setControlPromptEditorRerenderKey]) return ( - + {detectedAgentFromValue && ( + )} - className="caret:text-text-accent" - editable={!readOnly} - value={value} - workflowVariableBlock={{ - show: !disableVariableInsertion, - variables: nodesOutputVars || [], - workflowNodesMap: availableNodes.reduce((acc, node) => { - acc[node.id] = { - title: node.data.title, - type: node.data.type, - } - if (node.data.type === BlockEnum.Start) { - acc.sys = { - title: t('blocks.start', { ns: 'workflow' }), - type: BlockEnum.Start, + { + acc[node.id] = { + title: node.data.title, + type: node.data.type, } - } - return acc - }, {} as any), - showManageInputField, - onManageInputField, - }} - placeholder={} - onChange={onChange} - /> + if (node.data.type === BlockEnum.Start) { + acc.sys = { + title: t('blocks.start', { ns: 'workflow' }), + type: BlockEnum.Start, + } + } + return acc + }, {} as any), + showManageInputField, + onManageInputField, + }} + placeholder={} + onChange={onChange} + /> +
) } diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx index 6e999975f1..03a623b1bc 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx @@ -44,6 +44,17 @@ const Placeholder = ({ disableVariableInsertion = false }: PlaceholderProps) => > {t('nodes.tool.insertPlaceholder2', { ns: 'workflow' })} +
@
+
{ + e.preventDefault() + e.stopPropagation() + handleInsert('@') + })} + > + {t('nodes.tool.insertPlaceholder3', { ns: 'workflow' })} +
)} diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 11700722d3..03d42cd1df 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -204,6 +204,7 @@ "common.runApp": "Run App", "common.runHistory": "Run History", "common.running": "Running", + "common.searchAgent": "Search agent", "common.searchVar": "Search variable", "common.setVarValuePlaceholder": "Set variable", "common.showRunHistory": "Show Run History", @@ -215,6 +216,7 @@ "common.variableNamePlaceholder": "Variable name", "common.versionHistory": "Version History", "common.viewDetailInTracingPanel": "View details", + "common.viewInternals": "View internals", "common.viewOnly": "View Only", "common.viewRunHistory": "View run history", "common.workflowAsTool": "Workflow as Tool", @@ -764,6 +766,7 @@ "nodes.tool.inputVars": "Input Variables", "nodes.tool.insertPlaceholder1": "Type or press", "nodes.tool.insertPlaceholder2": "insert variable", + "nodes.tool.insertPlaceholder3": "add agent", "nodes.tool.outputVars.files.title": "tool generated files", "nodes.tool.outputVars.files.transfer_method": "Transfer method.Value is remote_url or local_file", "nodes.tool.outputVars.files.type": "Support type. Now only support image", diff --git a/web/i18n/ja-JP/workflow.json b/web/i18n/ja-JP/workflow.json index dd811a3858..7669d378e1 100644 --- a/web/i18n/ja-JP/workflow.json +++ b/web/i18n/ja-JP/workflow.json @@ -202,6 +202,7 @@ "common.runApp": "アプリを実行", "common.runHistory": "実行履歴", "common.running": "実行中", + "common.searchAgent": "エージェントを検索", "common.searchVar": "変数を検索", "common.setVarValuePlaceholder": "変数値を設定", "common.showRunHistory": "実行履歴を表示", @@ -213,6 +214,7 @@ "common.variableNamePlaceholder": "変数名を入力", "common.versionHistory": "バージョン履歴", "common.viewDetailInTracingPanel": "詳細を表示", + "common.viewInternals": "内部を表示", "common.viewOnly": "閲覧のみ", "common.viewRunHistory": "実行履歴を表示", "common.workflowAsTool": "ワークフローをツールとして公開する", @@ -762,6 +764,7 @@ "nodes.tool.inputVars": "入力変数", "nodes.tool.insertPlaceholder1": "タイプするか押してください", "nodes.tool.insertPlaceholder2": "変数を挿入する", + "nodes.tool.insertPlaceholder3": "エージェントを追加", "nodes.tool.outputVars.files.title": "ツールが生成したファイル", "nodes.tool.outputVars.files.transfer_method": "転送方法。値は remote_url または local_file です", "nodes.tool.outputVars.files.type": "サポートタイプ。現在は画像のみサポートされています", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 569407aafa..1579da0620 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -202,6 +202,7 @@ "common.runApp": "运行", "common.runHistory": "运行历史", "common.running": "运行中", + "common.searchAgent": "搜索代理", "common.searchVar": "搜索变量", "common.setVarValuePlaceholder": "设置变量值", "common.showRunHistory": "显示运行历史", @@ -213,6 +214,7 @@ "common.variableNamePlaceholder": "变量名", "common.versionHistory": "版本历史", "common.viewDetailInTracingPanel": "查看详细信息", + "common.viewInternals": "查看内部", "common.viewOnly": "只读", "common.viewRunHistory": "查看运行历史", "common.workflowAsTool": "发布为工具", @@ -762,6 +764,7 @@ "nodes.tool.inputVars": "输入变量", "nodes.tool.insertPlaceholder1": "键入", "nodes.tool.insertPlaceholder2": "插入变量", + "nodes.tool.insertPlaceholder3": "添加代理", "nodes.tool.outputVars.files.title": "工具生成的文件", "nodes.tool.outputVars.files.transfer_method": "传输方式。值为 remote_url 或 local_file", "nodes.tool.outputVars.files.type": "支持类型。现在只支持图片", diff --git a/web/i18n/zh-Hant/workflow.json b/web/i18n/zh-Hant/workflow.json index 80e72e42ff..bc9a3008b4 100644 --- a/web/i18n/zh-Hant/workflow.json +++ b/web/i18n/zh-Hant/workflow.json @@ -202,6 +202,7 @@ "common.runApp": "運行", "common.runHistory": "運行歷史", "common.running": "運行中", + "common.searchAgent": "搜索代理", "common.searchVar": "搜索變數", "common.setVarValuePlaceholder": "設置變數值", "common.showRunHistory": "顯示運行歷史", @@ -213,6 +214,7 @@ "common.variableNamePlaceholder": "變數名", "common.versionHistory": "版本歷史", "common.viewDetailInTracingPanel": "查看詳細信息", + "common.viewInternals": "查看內部", "common.viewOnly": "只讀", "common.viewRunHistory": "查看運行歷史", "common.workflowAsTool": "發佈為工具", @@ -762,6 +764,7 @@ "nodes.tool.inputVars": "輸入變數", "nodes.tool.insertPlaceholder1": "輸入或按壓", "nodes.tool.insertPlaceholder2": "插入變數", + "nodes.tool.insertPlaceholder3": "添加代理", "nodes.tool.outputVars.files.title": "工具生成的文件", "nodes.tool.outputVars.files.transfer_method": "傳輸方式。值為 remote_url 或 local_file", "nodes.tool.outputVars.files.type": "支持類型。現在只支持圖片",