From c980f1b2ac44fff7e3c7542967627a9c2bb8b268 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Feb 2026 17:01:07 +0800 Subject: [PATCH] feat: support enable agent mode show tip in prompt editor --- .../components/base/prompt-editor/index.tsx | 3 +- .../nodes/_base/components/prompt/editor.tsx | 7 ++ .../nodes/llm/components/computer-use-tip.tsx | 56 +++++++++++++ .../llm/components/config-prompt-item.tsx | 19 ++++- .../nodes/llm/components/config-prompt.tsx | 20 ++++- .../components/workflow/nodes/llm/panel.tsx | 3 + .../use-structured-output-mutual-exclusion.ts | 8 +- .../plugins/tool-block/component.tsx | 26 +++--- .../plugins/tool-block/tool-block-context.tsx | 1 + .../tool-block/tool-group-block-component.tsx | 79 +++++++++++-------- web/i18n/en-US/workflow.json | 3 + web/i18n/zh-Hans/workflow.json | 3 + web/i18n/zh-Hant/workflow.json | 4 + 13 files changed, 186 insertions(+), 46 deletions(-) create mode 100644 web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index e74c88a3f1..e8c1506b6c 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -327,11 +327,12 @@ const PromptEditorContent: FC = ({ metadata: toolMetadata, onMetadataChange: onToolMetadataChange, useModal: true, + disableToolBlocks, nodeId, nodesOutputVars: workflowVariableBlock?.variables, availableNodes, } - }, [availableNodes, nodeId, onToolMetadataChange, toolMetadata, workflowVariableBlock?.variables]) + }, [availableNodes, disableToolBlocks, nodeId, onToolMetadataChange, toolMetadata, workflowVariableBlock?.variables]) const filePreviewContextValue = React.useMemo(() => ({ enabled: Boolean(isSupportSandbox), 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 1887e2394e..81a95e825b 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -86,6 +86,7 @@ type Props = { onBlur?: () => void onFocus?: () => void disableToolBlocks?: boolean + footer?: ReactNode } const Editor: FC = ({ @@ -131,6 +132,7 @@ const Editor: FC = ({ onBlur, onFocus, disableToolBlocks, + footer, }) => { const { t } = useTranslation() const { eventEmitter } = useEventEmitterContextContext() @@ -351,6 +353,11 @@ const Editor: FC = ({ /> )} + {!!footer && ( +
+ {footer} +
+ )} diff --git a/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx b/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx new file mode 100644 index 0000000000..0e6af3a548 --- /dev/null +++ b/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx @@ -0,0 +1,56 @@ +import type { FC } from 'react' +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' + +type Props = { + visible: boolean + onEnable: () => void +} + +const ComputerUseTip: FC = ({ + visible, + onEnable, +}) => { + const { t } = useTranslation() + const [dismissed, setDismissed] = React.useState(false) + + React.useEffect(() => { + if (!visible) + // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect + setDismissed(false) + }, [visible]) + + if (!visible || dismissed) + return null + + return ( +
+
+ +
+
+ {t('nodes.llm.computerUse.enableForPromptTools', { ns: 'workflow' })} +
+
+ + +
+
+
+
+ ) +} + +export default React.memo(ComputerUseTip) diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx index 1c50ef31fc..6a5fb7300d 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx @@ -7,9 +7,11 @@ import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' +import { extractToolConfigIds } from '@/app/components/workflow/utils' import { PromptRole } from '@/models/debug' import { useWorkflowStore } from '../../../store' import { EditionType } from '../../../types' +import ComputerUseTip from './computer-use-tip' const i18nPrefix = 'nodes.llm' @@ -44,6 +46,8 @@ type Props = { isSupportSandbox?: boolean onPromptEditorBlur?: () => void disableToolBlocks?: boolean + showComputerUseTip?: boolean + onEnableComputerUse?: () => void } const roleOptions = [ @@ -90,6 +94,8 @@ const ConfigPromptItem: FC = ({ isSupportSandbox, onPromptEditorBlur, disableToolBlocks, + showComputerUseTip, + onEnableComputerUse, }) => { const { t } = useTranslation() const workflowStore = useWorkflowStore() @@ -101,6 +107,11 @@ const ConfigPromptItem: FC = ({ onPromptChange(prompt) setTimeout(() => setControlPromptEditorRerenderKey(Date.now())) }, [onPromptChange, setControlPromptEditorRerenderKey]) + const editorValue = payload.edition_type === EditionType.jinja2 + ? (payload.jinja2_text || '') + : payload.text + const shouldShowComputerUseTip = !!showComputerUseTip + && extractToolConfigIds(editorValue || '').size > 0 return ( = ({ /> )} - value={payload.edition_type === EditionType.jinja2 ? (payload.jinja2_text || '') : payload.text} + value={editorValue} onChange={onPromptChange} promptMetadata={payload.metadata} onPromptMetadataChange={onMetadataChange} @@ -162,6 +173,12 @@ const ConfigPromptItem: FC = ({ isSupportSandbox={isSupportSandbox} disableToolBlocks={disableToolBlocks} onBlur={onPromptEditorBlur} + footer={( + onEnableComputerUse?.()} + /> + )} /> ) } 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 09cc23a257..ea7b3a9515 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -22,6 +22,7 @@ import { cn } from '@/utils/classnames' import { useWorkflowStore } from '../../../store' import { BlockEnum, EditionType, isPromptMessageContext, PromptRole, VarType } from '../../../types' import useAvailableVarList from '../../_base/hooks/use-available-var-list' +import ComputerUseTip from './computer-use-tip' import ConfigContextItem from './config-context-item' import ConfigPromptItem from './config-prompt-item' @@ -67,6 +68,8 @@ type Props = { modelConfig: ModelConfig onPromptEditorBlur?: () => void disableToolBlocks?: boolean + showComputerUseTip?: boolean + onEnableComputerUse?: () => void } const ConfigPrompt: FC = ({ @@ -84,6 +87,8 @@ const ConfigPrompt: FC = ({ modelConfig, onPromptEditorBlur, disableToolBlocks, + showComputerUseTip, + onEnableComputerUse, }) => { const { t } = useTranslation() const workflowStore = useWorkflowStore() @@ -284,6 +289,11 @@ const ConfigPrompt: FC = ({ } return false })() + const completionEditorValue = ((payload as PromptItem).edition_type === EditionType.basic || !(payload as PromptItem).edition_type) + ? (payload as PromptItem).text + : ((payload as PromptItem).jinja2_text || '') + const shouldShowCompletionComputerUseTip = !!showComputerUseTip + && extractToolConfigIds(completionEditorValue || '').size > 0 return (
@@ -364,6 +374,8 @@ const ConfigPrompt: FC = ({ isSupportSandbox={isSupportSandbox} onPromptEditorBlur={onPromptEditorBlur} disableToolBlocks={disableToolBlocks} + showComputerUseTip={showComputerUseTip} + onEnableComputerUse={onEnableComputerUse} />
) @@ -421,7 +433,7 @@ const ConfigPrompt: FC = ({ instanceId={`${nodeId}-chat-workflow-llm-prompt-editor`} nodeId={nodeId} title={{t(`${i18nPrefix}.prompt`, { ns: 'workflow' })}} - value={((payload as PromptItem).edition_type === EditionType.basic || !(payload as PromptItem).edition_type) ? (payload as PromptItem).text : ((payload as PromptItem).jinja2_text || '')} + value={completionEditorValue} onChange={handleCompletionPromptChange} promptMetadata={(payload as PromptItem).metadata} onPromptMetadataChange={handleCompletionMetadataChange} @@ -443,6 +455,12 @@ const ConfigPrompt: FC = ({ isSupportSandbox={isSupportSandbox} onBlur={onPromptEditorBlur} disableToolBlocks={disableToolBlocks} + footer={( + onEnableComputerUse?.()} + /> + )} /> )} diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index 985ae57777..7b39dcd055 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -102,6 +102,7 @@ const Panel: FC> = ({ isComputerUseBlocked, isToolsBlocked, disableToolBlocks, + shouldEnableComputerUseForPromptTools, structuredOutputDisabledTip, computerUseDisabledTip, toolsDisabledTip, @@ -185,6 +186,8 @@ const Panel: FC> = ({ modelConfig={model} onPromptEditorBlur={handlePromptEditorBlur} disableToolBlocks={disableToolBlocks} + showComputerUseTip={shouldEnableComputerUseForPromptTools} + onEnableComputerUse={() => handleComputerUseChange(true)} /> )} diff --git a/web/app/components/workflow/nodes/llm/use-structured-output-mutual-exclusion.ts b/web/app/components/workflow/nodes/llm/use-structured-output-mutual-exclusion.ts index 60923cd151..aa92b769dd 100644 --- a/web/app/components/workflow/nodes/llm/use-structured-output-mutual-exclusion.ts +++ b/web/app/components/workflow/nodes/llm/use-structured-output-mutual-exclusion.ts @@ -49,7 +49,12 @@ export const useStructuredOutputMutualExclusion = ({ const isStructuredOutputBlocked = readOnly || (hasToolConflict && !isStructuredOutputEnabled) const isComputerUseBlocked = readOnly || (isStructuredOutputEnabled && !inputs.computer_use) const isToolsBlocked = readOnly || isStructuredOutputEnabled - const disableToolBlocks = isStructuredOutputEnabled + const shouldEnableComputerUseForPromptTools = isSupportSandbox + && !readOnly + && !inputs.computer_use + && hasToolDependencies + && !isComputerUseBlocked + const disableToolBlocks = isStructuredOutputEnabled || shouldEnableComputerUseForPromptTools const structuredOutputDisabledTip = useMemo(() => { if (readOnly || !isStructuredOutputBlocked) @@ -76,6 +81,7 @@ export const useStructuredOutputMutualExclusion = ({ isComputerUseBlocked, isToolsBlocked, disableToolBlocks, + shouldEnableComputerUseForPromptTools, structuredOutputDisabledTip, computerUseDisabledTip, toolsDisabledTip, diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx index 79c9757cf7..bd0effd451 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx @@ -133,6 +133,7 @@ const ToolBlockComponent = ({ metadata, onMetadataChange, useModal, + disableToolBlocks, nodeId: contextNodeId, nodesOutputVars, availableNodes, @@ -141,6 +142,7 @@ const ToolBlockComponent = ({ metadata: context?.metadata, onMetadataChange: context?.onMetadataChange, useModal: context?.useModal, + disableToolBlocks: context?.disableToolBlocks, nodeId: context?.nodeId, nodesOutputVars: context?.nodesOutputVars, availableNodes: context?.availableNodes, @@ -210,8 +212,9 @@ const ToolBlockComponent = ({ return resultMetadata?.tools?.[configId] }, [activeTabId, configId, fileMetadata, isUsingExternalMetadata, metadata]) const isToolMissing = !currentProvider || !currentTool - + const isToolDisabled = Boolean(disableToolBlocks) const isInteractive = editor.isEditable() + const isTriggerInteractive = isInteractive && !isToolDisabled const defaultToolValue = useMemo(() => { if (!currentProvider || !currentTool) @@ -357,6 +360,7 @@ const ToolBlockComponent = ({ return false return !currentProvider.is_team_authorization }, [currentProvider]) + const isWarningStyle = needAuthorization || isToolMissing || isToolDisabled const renderIcon = () => { if (isToolMissing) { @@ -400,19 +404,19 @@ const ToolBlockComponent = ({ className={cn( 'i-ri-equalizer-2-line hidden size-[14px]', needAuthorization ? 'text-text-warning' : 'text-text-accent', - isInteractive && 'group-hover/tool:block', + isTriggerInteractive && 'group-hover/tool:block', )} /> ) const normalIcon = ( - + {iconNode} ) const iconContent = ( {normalIcon} - {isInteractive && hoverIcon} + {isTriggerInteractive && hoverIcon} ) if (!needAuthorization) @@ -601,7 +605,7 @@ const ToolBlockComponent = ({ <> { - if (!isInteractive) + if (!isTriggerInteractive) return if (!currentProvider || !currentTool) return @@ -627,17 +631,17 @@ const ToolBlockComponent = ({ }} > {renderIcon()} - + {isToolMissing ? missingDisplayLabel : displayLabel} - {isToolMissing && ( + {(isToolMissing || isToolDisabled) && ( <> )} - {!isToolMissing && needAuthorization && ( + {!isToolMissing && !isToolDisabled && needAuthorization && ( {authBadgeLabel} diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-block-context.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-block-context.tsx index a91b1cd1d8..1d1797d01b 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-block-context.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-block-context.tsx @@ -8,6 +8,7 @@ export type ToolBlockContextValue = { metadata?: Record onMetadataChange?: (metadata: Record) => void useModal?: boolean + disableToolBlocks?: boolean nodeId?: string nodesOutputVars?: NodeOutPutVar[] availableNodes?: Node[] diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx index 80bfe28aee..a16b89cebe 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx @@ -118,6 +118,7 @@ const ToolGroupBlockComponent = ({ metadata, onMetadataChange, useModal, + disableToolBlocks, nodeId: contextNodeId, nodesOutputVars, availableNodes, @@ -126,6 +127,7 @@ const ToolGroupBlockComponent = ({ metadata: context?.metadata, onMetadataChange: context?.onMetadataChange, useModal: context?.useModal, + disableToolBlocks: context?.disableToolBlocks, nodeId: context?.nodeId, nodesOutputVars: context?.nodesOutputVars, availableNodes: context?.availableNodes, @@ -134,6 +136,8 @@ const ToolGroupBlockComponent = ({ const isUsingExternalMetadata = Boolean(onMetadataChange) const useModalValue = Boolean(useModal) const isInteractive = editor.isEditable() + const isToolDisabled = Boolean(disableToolBlocks) + const isTriggerInteractive = isInteractive && !isToolDisabled const [isSettingOpen, setIsSettingOpen] = useState(false) const [portalContainer, setPortalContainer] = useState(null) const [expandedToolId, setExpandedToolId] = useState(null) @@ -339,6 +343,7 @@ const ToolGroupBlockComponent = ({ return false return !currentProvider.is_team_authorization }, [currentProvider]) + const isWarningStyle = needAuthorization || isToolMissing || isToolDisabled const readmeEntrance = useMemo(() => { if (!currentProvider) @@ -588,19 +593,19 @@ const ToolGroupBlockComponent = ({ className={cn( 'i-ri-equalizer-2-line hidden size-[14px]', (needAuthorization || isToolMissing) ? 'text-text-warning' : 'text-text-accent', - isInteractive && 'group-hover:block', + isTriggerInteractive && 'group-hover:block', )} /> ) const normalIcon = ( - + {iconNode} ) const iconContent = ( {normalIcon} - {isInteractive && hoverIcon} + {isTriggerInteractive && hoverIcon} ) if (!needAuthorization) @@ -865,12 +870,46 @@ const ToolGroupBlockComponent = ({ ) const groupPanelContent = expandedToolId && toolDetailContent ? toolDetailContent : groupListContent + const toolStatusBadge = (() => { + if (isToolMissing) { + return ( + <> + + {missingToolCount} + + + + + + ) + } + if (isToolDisabled) { + return ( + + + + ) + } + if (needAuthorization) { + return ( + + {authBadgeLabel} + + + ) + } + return ( + + {displayEnabledCount} + + ) + })() return ( <> { - if (!isInteractive) + if (!isTriggerInteractive) return if (!toolItems.length) return @@ -893,32 +932,10 @@ const ToolGroupBlockComponent = ({ }} > {renderIcon()} - + {isToolMissing ? missingDisplayLabel : providerLabel} - {isToolMissing - ? ( - <> - - {missingToolCount} - - - - - - ) - : needAuthorization - ? ( - - {authBadgeLabel} - - - ) - : ( - - {displayEnabledCount} - - )} + {toolStatusBadge} diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 7b46982c79..d5d8ee110a 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -778,6 +778,9 @@ "nodes.llm.advancedSettings": "Advanced Settings", "nodes.llm.chatHistorry": "Chat History", "nodes.llm.computerUse.disabledByStructuredOutput": "Agent mode is disabled when Structured Output is enabled.", + "nodes.llm.computerUse.dismiss": "Dismiss", + "nodes.llm.computerUse.enable": "Enable", + "nodes.llm.computerUse.enableForPromptTools": "Enable Agent mode to reference tools in prompts", "nodes.llm.computerUse.referenceTools": "Reference Tools", "nodes.llm.computerUse.referenceToolsEmpty": "Tools referenced in the prompt will appear here", "nodes.llm.computerUse.title": "Agent mode", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 82a886c7a0..4ecbcea2e4 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -771,6 +771,9 @@ "nodes.llm.addMessage": "添加消息", "nodes.llm.chatHistorry": "对话历史", "nodes.llm.computerUse.disabledByStructuredOutput": "启用结构化输出时,将禁用 Agent 模式。", + "nodes.llm.computerUse.dismiss": "关闭", + "nodes.llm.computerUse.enable": "启用", + "nodes.llm.computerUse.enableForPromptTools": "若要在提示词中引用工具,请先启用 Agent 模式", "nodes.llm.computerUse.referenceTools": "引用工具", "nodes.llm.computerUse.referenceToolsEmpty": "提示词中引用的工具会显示在这里", "nodes.llm.computerUse.title": "Agent 模式", diff --git a/web/i18n/zh-Hant/workflow.json b/web/i18n/zh-Hant/workflow.json index fc76da27be..6b6fbeceaf 100644 --- a/web/i18n/zh-Hant/workflow.json +++ b/web/i18n/zh-Hant/workflow.json @@ -753,6 +753,10 @@ "nodes.llm.addMessage": "新增消息", "nodes.llm.chatHistorry": "對話歷史", "nodes.llm.computerUse.disabledByStructuredOutput": "啟用結構化輸出時,將停用 Agent 模式。", + "nodes.llm.computerUse.dismiss": "關閉", + "nodes.llm.computerUse.enable": "啟用", + "nodes.llm.computerUse.enableForPromptTools": "若要在提示詞中引用工具,請先啟用 Agent 模式", + "nodes.llm.computerUse.referenceTools": "引用工具", "nodes.llm.computerUse.referenceToolsEmpty": "提示詞中引用的工具會顯示在這裡", "nodes.llm.computerUse.title": "Agent 模式", "nodes.llm.computerUse.tooltip": "管理 Agent 模式下的執行階段檔案系統與工具存取權限。",