diff --git a/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx b/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx index cd491eb868..28a07fb09e 100644 --- a/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx @@ -1,5 +1,6 @@ 'use client' import type { FC } from 'react' +import type { ToolSetting } from '../types' import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' @@ -14,12 +15,16 @@ type Props = { readonly: boolean enabled: boolean onChange: (enabled: boolean) => void + nodeId: string + toolSettings?: ToolSetting[] } const ComputerUseConfig: FC = ({ readonly, enabled, onChange, + nodeId, + toolSettings, }) => { const { t } = useTranslation() @@ -54,7 +59,12 @@ const ComputerUseConfig: FC = ({ {t(`${i18nPrefix}.referenceTools`, { ns: 'workflow' })} - + diff --git a/web/app/components/workflow/nodes/llm/components/reference-tool-config.tsx b/web/app/components/workflow/nodes/llm/components/reference-tool-config.tsx index 6b145006b4..f12f5fb3c2 100644 --- a/web/app/components/workflow/nodes/llm/components/reference-tool-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/reference-tool-config.tsx @@ -1,69 +1,113 @@ 'use client' import type { FC } from 'react' +import type { LLMNodeType, ToolSetting } from '../types' import { RiArrowDownSLine } from '@remixicon/react' +import { useQuery } from '@tanstack/react-query' import * as React from 'react' +import { useCallback, useMemo } from 'react' +import { useStore as useAppStore } from '@/app/components/app/store' import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' import Switch from '@/app/components/base/switch' +import { useNodeCurdKit } from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { consoleClient, consoleQuery } from '@/service/client' import { cn } from '@/utils/classnames' -type ToolPermissionAction = { - id: string - label: string - defaultEnabled: boolean -} - -type ToolPermissionProvider = { - id: string - label: string - actions?: ToolPermissionAction[] -} - type ReferenceToolConfigProps = { readonly: boolean enabled: boolean + nodeId: string + toolSettings?: ToolSetting[] +} + +type ToolDependency = { + type: string + provider: string + tool_name: string +} + +type ToolProviderGroup = { + id: string + actions: ToolDependency[] } const ReferenceToolConfig: FC = ({ readonly, enabled, + nodeId, + toolSettings, }) => { const isDisabled = readonly || !enabled - const providers: ToolPermissionProvider[] = [ - { - id: 'duckduckgo', - label: 'DuckDuckGo', - actions: [ - { - id: 'duckduckgo-ai-chat', - label: 'DuckDuckGo AI Chat', - defaultEnabled: true, + const appId = useAppStore(s => s.appDetail?.id) + const { handleNodeDataUpdate } = useNodeCurdKit(nodeId) + + const { data } = useQuery({ + queryKey: consoleQuery.workflowDraft.nodeSkills.queryKey({ + input: { + params: { + appId: appId ?? '', + nodeId, }, - { - id: 'duckduckgo-image-search', - label: 'DuckDuckGo Image Search', - defaultEnabled: true, - }, - { - id: 'duckduckgo-search', - label: 'DuckDuckGo Search', - defaultEnabled: true, - }, - { - id: 'duckduckgo-translate', - label: 'DuckDuckGo Translate', - defaultEnabled: false, - }, - ], - }, - { - id: 'web-search', - label: 'Web Search', - }, - { - id: 'stability', - label: 'Stability', - }, - ] + }, + }), + queryFn: () => consoleClient.workflowDraft.nodeSkills({ + params: { + appId: appId ?? '', + nodeId, + }, + }), + enabled: !!appId && !!nodeId, + }) + + const toolDependencies = useMemo(() => data?.tool_dependencies ?? [], [data?.tool_dependencies]) + + const providers = useMemo(() => { + const map = new Map() + toolDependencies.forEach((tool) => { + const key = tool.provider || tool.tool_name || tool.type + const group = map.get(key) + if (group) + group.push(tool) + else + map.set(key, [tool]) + }) + return Array.from(map.entries()).map(([id, actions]) => ({ + id, + actions, + })) + }, [toolDependencies]) + + const resolveToolEnabled = useCallback((tool: ToolDependency) => { + const matched = toolSettings?.find(setting => + setting.type === tool.type + && setting.provider === tool.provider + && setting.tool_name === tool.tool_name, + ) + return matched?.enabled ?? true + }, [toolSettings]) + + const handleToolEnabledChange = useCallback((tool: ToolDependency, isEnabled: boolean) => { + const nextSettings = [...(toolSettings ?? [])] + const index = nextSettings.findIndex(setting => + setting.type === tool.type + && setting.provider === tool.provider + && setting.tool_name === tool.tool_name, + ) + if (index >= 0) { + nextSettings[index] = { + ...nextSettings[index], + enabled: isEnabled, + } + } + else { + nextSettings.push({ + ...tool, + enabled: isEnabled, + }) + } + handleNodeDataUpdate({ + tool_settings: nextSettings, + }) + }, [handleNodeDataUpdate, toolSettings]) return (
@@ -78,26 +122,27 @@ const ReferenceToolConfig: FC = ({
- {provider.label} + {provider.id}
- {provider.actions?.map(action => ( + {provider.actions.map(action => (
- {action.label} + {action.tool_name}
handleToolEnabledChange(action, value)} />
))} diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index ba9694ce6a..621c891089 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -232,6 +232,8 @@ const Panel: FC> = ({ readonly={readOnly} enabled={!!inputs.computer_use} onChange={handleComputerUseChange} + nodeId={id} + toolSettings={inputs.tool_settings} /> )} diff --git a/web/app/components/workflow/nodes/llm/types.ts b/web/app/components/workflow/nodes/llm/types.ts index 83b9877bc5..23d7a0ced5 100644 --- a/web/app/components/workflow/nodes/llm/types.ts +++ b/web/app/components/workflow/nodes/llm/types.ts @@ -13,6 +13,13 @@ export type Tool = { extra?: Record } +export type ToolSetting = { + type: string + provider: string + tool_name: string + enabled: boolean +} + export type LLMNodeType = CommonNodeType & { model: ModelConfig prompt_template: PromptTemplateItem[] | PromptItem @@ -33,6 +40,7 @@ export type LLMNodeType = CommonNodeType & { structured_output?: StructuredOutput reasoning_format?: 'tagged' | 'separated' tools?: ToolValue[] + tool_settings?: ToolSetting[] max_iterations?: number } diff --git a/web/contract/console/workflow.ts b/web/contract/console/workflow.ts index f2eb76dd39..0a4fe8b811 100644 --- a/web/contract/console/workflow.ts +++ b/web/contract/console/workflow.ts @@ -80,3 +80,23 @@ export const workflowDraftUpdateFeaturesContract = base } }>()) .output(type()) + +export const workflowDraftNodeSkillsContract = base + .route({ + path: '/apps/{appId}/workflows/draft/nodes/{nodeId}/skills', + method: 'GET', + }) + .input(type<{ + params: { + appId: string + nodeId: string + } + }>()) + .output(type<{ + node_id: string + tool_dependencies: { + type: string + provider: string + tool_name: string + }[] + }>()) diff --git a/web/contract/router.ts b/web/contract/router.ts index 29fbd9b2fb..85f3ea1c7e 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -30,6 +30,7 @@ import { systemFeaturesContract } from './console/system' import { trialAppDatasetsContract, trialAppInfoContract, trialAppParametersContract, trialAppWorkflowsContract } from './console/try-app' import { workflowDraftEnvironmentVariablesContract, + workflowDraftNodeSkillsContract, workflowDraftUpdateConversationVariablesContract, workflowDraftUpdateEnvironmentVariablesContract, workflowDraftUpdateFeaturesContract, @@ -89,6 +90,7 @@ export const consoleRouterContract = { }, workflowDraft: { environmentVariables: workflowDraftEnvironmentVariablesContract, + nodeSkills: workflowDraftNodeSkillsContract, updateEnvironmentVariables: workflowDraftUpdateEnvironmentVariablesContract, updateConversationVariables: workflowDraftUpdateConversationVariablesContract, updateFeatures: workflowDraftUpdateFeaturesContract,