feat: fetch tools and set tools enabled from api

This commit is contained in:
Joel
2026-01-28 14:11:10 +08:00
parent ca95b6684f
commit 0ae02938e6
6 changed files with 140 additions and 53 deletions

View File

@ -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<Props> = ({
readonly,
enabled,
onChange,
nodeId,
toolSettings,
}) => {
const { t } = useTranslation()
@ -54,7 +59,12 @@ const ComputerUseConfig: FC<Props> = ({
{t(`${i18nPrefix}.referenceTools`, { ns: 'workflow' })}
</div>
</div>
<ReferenceToolConfig readonly={readonly} enabled={enabled} />
<ReferenceToolConfig
readonly={readonly}
enabled={enabled}
nodeId={nodeId}
toolSettings={toolSettings}
/>
</div>
</FieldCollapse>
<Split />

View File

@ -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<ReferenceToolConfigProps> = ({
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<LLMNodeType>(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<ToolDependency[]>(() => data?.tool_dependencies ?? [], [data?.tool_dependencies])
const providers = useMemo<ToolProviderGroup[]>(() => {
const map = new Map<string, ToolDependency[]>()
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 (
<div className={cn('flex flex-col gap-2', isDisabled && 'opacity-50')}>
@ -78,26 +122,27 @@ const ReferenceToolConfig: FC<ReferenceToolConfigProps> = ({
<DefaultToolIcon className="h-4 w-4 text-text-primary" />
</div>
<div className="system-sm-medium truncate text-text-primary">
{provider.label}
{provider.id}
</div>
<RiArrowDownSLine className="h-4 w-4 text-text-tertiary" />
</div>
</div>
{provider.actions?.map(action => (
{provider.actions.map(action => (
<div
key={action.id}
key={`${action.type}-${action.provider}-${action.tool_name}`}
className="relative flex items-center gap-2 rounded-md px-2 py-1"
>
<div className="absolute left-3 top-0 h-full w-px bg-divider-subtle" />
<div className="flex min-w-0 flex-1 items-center pl-5">
<span className="system-sm-regular truncate text-text-secondary">
{action.label}
{action.tool_name}
</span>
</div>
<Switch
size="md"
disabled={isDisabled}
defaultValue={action.defaultEnabled}
defaultValue={resolveToolEnabled(action)}
onChange={value => handleToolEnabledChange(action, value)}
/>
</div>
))}

View File

@ -232,6 +232,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
readonly={readOnly}
enabled={!!inputs.computer_use}
onChange={handleComputerUseChange}
nodeId={id}
toolSettings={inputs.tool_settings}
/>
</>
)}

View File

@ -13,6 +13,13 @@ export type Tool = {
extra?: Record<string, any>
}
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
}

View File

@ -80,3 +80,23 @@ export const workflowDraftUpdateFeaturesContract = base
}
}>())
.output(type<CommonResponse>())
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
}[]
}>())

View File

@ -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,