mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
feat: fetch tools and set tools enabled from api
This commit is contained in:
@ -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 />
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -232,6 +232,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
readonly={readOnly}
|
||||
enabled={!!inputs.computer_use}
|
||||
onChange={handleComputerUseChange}
|
||||
nodeId={id}
|
||||
toolSettings={inputs.tool_settings}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}[]
|
||||
}>())
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user