mirror of
https://github.com/langgenius/dify.git
synced 2026-02-22 19:15:47 +08:00
feat: show provider config
This commit is contained in:
@ -1,10 +1,29 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ToolToken } from './utils'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import type { ToolParameter } from '@/app/components/tools/types'
|
||||
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section'
|
||||
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
|
||||
import { ReadmeShowType } from '@/app/components/plugins/readme-panel/store'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { generateFormValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import { VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import { START_TAB_ID } from '@/app/components/workflow/skill/constants'
|
||||
import ToolSettingsSection from '@/app/components/workflow/skill/editor/skill-editor/tool-setting/tool-settings-section'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import {
|
||||
@ -18,12 +37,60 @@ import { canFindTool } from '@/utils'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { basePath } from '@/utils/var'
|
||||
import { DELETE_TOOL_BLOCK_COMMAND } from './index'
|
||||
import { useToolBlockContext } from './tool-block-context'
|
||||
|
||||
type ToolGroupBlockComponentProps = {
|
||||
nodeKey: string
|
||||
tools: ToolToken[]
|
||||
}
|
||||
|
||||
type ToolConfigField = {
|
||||
id: string
|
||||
value: unknown
|
||||
auto: boolean
|
||||
}
|
||||
|
||||
type ToolConfigMetadata = {
|
||||
type: 'mcp' | 'builtin'
|
||||
configuration: {
|
||||
fields: ToolConfigField[]
|
||||
}
|
||||
}
|
||||
|
||||
type SkillFileMetadata = {
|
||||
tools?: Record<string, ToolConfigMetadata>
|
||||
}
|
||||
|
||||
type ToolFormSchema = {
|
||||
variable: string
|
||||
type: string
|
||||
default?: unknown
|
||||
}
|
||||
|
||||
type ToolConfigValueItem = {
|
||||
auto?: 0 | 1
|
||||
value?: {
|
||||
type: VarKindType
|
||||
value?: unknown
|
||||
} | null
|
||||
}
|
||||
|
||||
type ToolConfigValueMap = Record<string, ToolConfigValueItem>
|
||||
|
||||
type ToolItem = {
|
||||
configId: string
|
||||
providerId: string
|
||||
toolName: string
|
||||
toolLabel: string
|
||||
toolDescription: string
|
||||
providerIcon?: ToolWithProvider['icon']
|
||||
providerIconDark?: ToolWithProvider['icon']
|
||||
providerType?: ToolWithProvider['type']
|
||||
providerName?: string
|
||||
providerLabel?: string
|
||||
toolParams?: ToolParameter[]
|
||||
}
|
||||
|
||||
const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
|
||||
if (!icon)
|
||||
return icon
|
||||
@ -37,12 +104,24 @@ const ToolGroupBlockComponent: FC<ToolGroupBlockComponentProps> = ({
|
||||
tools,
|
||||
}) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_TOOL_BLOCK_COMMAND)
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const { theme } = useTheme()
|
||||
const toolBlockContext = useToolBlockContext()
|
||||
const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange)
|
||||
const useModal = Boolean(toolBlockContext?.useModal)
|
||||
const [isSettingOpen, setIsSettingOpen] = useState(false)
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null)
|
||||
const [expandedToolId, setExpandedToolId] = useState<string | null>(null)
|
||||
const [toolValue, setToolValue] = useState<ToolValue | null>(null)
|
||||
const [enabledByConfigId, setEnabledByConfigId] = useState<Record<string, boolean>>({})
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
const { data: mcpTools } = useAllMCPTools()
|
||||
const activeTabId = useStore(s => s.activeTabId)
|
||||
const fileMetadata = useStore(s => s.fileMetadata)
|
||||
const storeApi = useWorkflowStore()
|
||||
|
||||
const mergedTools = useMemo(() => {
|
||||
return [buildInTools, customTools, workflowTools, mcpTools].filter(Boolean) as ToolWithProvider[][]
|
||||
@ -61,11 +140,294 @@ const ToolGroupBlockComponent: FC<ToolGroupBlockComponentProps> = ({
|
||||
}, [mergedTools, providerId])
|
||||
|
||||
const providerLabel = currentProvider?.label?.[language] || currentProvider?.name || providerId
|
||||
const providerAuthor = currentProvider?.author
|
||||
const providerDescription = useMemo(() => {
|
||||
if (!currentProvider?.description)
|
||||
return ''
|
||||
return currentProvider.description?.[language] || currentProvider.description?.['en-US'] || Object.values(currentProvider.description).find(Boolean) || ''
|
||||
}, [currentProvider?.description, language])
|
||||
const resolvedIcon = (() => {
|
||||
const fromMeta = theme === Theme.dark ? currentProvider?.icon_dark : currentProvider?.icon
|
||||
return normalizeProviderIcon(fromMeta)
|
||||
})()
|
||||
|
||||
const toolItems = useMemo<ToolItem[]>(() => {
|
||||
if (!currentProvider)
|
||||
return []
|
||||
return tools.map((toolToken) => {
|
||||
const tool = currentProvider.tools?.find(item => item.name === toolToken.tool)
|
||||
const toolLabel = tool?.label?.[language] || toolToken.tool
|
||||
const toolDescription = tool?.description?.[language] || ''
|
||||
return {
|
||||
configId: toolToken.configId,
|
||||
providerId: toolToken.provider,
|
||||
toolName: toolToken.tool,
|
||||
toolLabel,
|
||||
toolDescription,
|
||||
providerIcon: currentProvider.icon,
|
||||
providerIconDark: currentProvider.icon_dark,
|
||||
providerType: currentProvider.type,
|
||||
providerName: currentProvider.name,
|
||||
providerLabel: currentProvider.label?.[language] || currentProvider.name,
|
||||
toolParams: tool?.parameters,
|
||||
}
|
||||
})
|
||||
}, [currentProvider, language, tools])
|
||||
|
||||
const activeToolItem = useMemo(() => {
|
||||
if (!expandedToolId)
|
||||
return undefined
|
||||
return toolItems.find(item => item.configId === expandedToolId)
|
||||
}, [expandedToolId, toolItems])
|
||||
|
||||
const currentTool = useMemo(() => {
|
||||
if (!activeToolItem || !currentProvider)
|
||||
return undefined
|
||||
return currentProvider.tools?.find(item => item.name === activeToolItem.toolName)
|
||||
}, [activeToolItem, currentProvider])
|
||||
|
||||
const toolConfigFromMetadata = useMemo(() => {
|
||||
if (!activeToolItem)
|
||||
return undefined
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = toolBlockContext?.metadata as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[activeToolItem.configId]
|
||||
}
|
||||
if (!activeTabId)
|
||||
return undefined
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[activeToolItem.configId]
|
||||
}, [activeTabId, activeToolItem, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata])
|
||||
|
||||
const getVarKindType = (type: FormTypeEnum | string) => {
|
||||
if (type === FormTypeEnum.file || type === FormTypeEnum.files)
|
||||
return VarKindType.variable
|
||||
if (type === FormTypeEnum.select || type === FormTypeEnum.checkbox || type === FormTypeEnum.textNumber || type === FormTypeEnum.array || type === FormTypeEnum.object)
|
||||
return VarKindType.constant
|
||||
if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput)
|
||||
return VarKindType.mixed
|
||||
return VarKindType.constant
|
||||
}
|
||||
|
||||
const defaultToolValue = useMemo(() => {
|
||||
if (!currentProvider || !currentTool || !activeToolItem)
|
||||
return null
|
||||
const settingsSchemas = toolParametersToFormSchemas(currentTool.parameters?.filter(param => param.form !== 'llm') || []) as ToolFormSchema[]
|
||||
const paramsSchemas = toolParametersToFormSchemas(currentTool.parameters?.filter(param => param.form === 'llm') || []) as ToolFormSchema[]
|
||||
const toolLabel = currentTool.label?.[language] || activeToolItem.toolName
|
||||
const toolDescription = typeof currentTool.description === 'object'
|
||||
? (currentTool.description?.[language] || '')
|
||||
: (currentTool.description || '')
|
||||
return {
|
||||
provider_name: currentProvider.id,
|
||||
provider_show_name: currentProvider.name,
|
||||
tool_name: currentTool.name,
|
||||
tool_label: toolLabel,
|
||||
tool_description: toolDescription,
|
||||
settings: generateFormValue({}, settingsSchemas),
|
||||
parameters: generateFormValue({}, paramsSchemas, true),
|
||||
enabled: true,
|
||||
extra: { description: toolDescription },
|
||||
} as ToolValue
|
||||
}, [activeToolItem, currentProvider, currentTool, language])
|
||||
|
||||
const configuredToolValue = useMemo(() => {
|
||||
if (!defaultToolValue || !currentTool)
|
||||
return defaultToolValue
|
||||
const fields = toolConfigFromMetadata?.configuration?.fields ?? []
|
||||
if (!fields.length)
|
||||
return defaultToolValue
|
||||
const fieldsById = new Map(fields.map(field => [field.id, field]))
|
||||
const settingsSchemas = toolParametersToFormSchemas(currentTool.parameters?.filter(param => param.form !== 'llm') || []) as ToolFormSchema[]
|
||||
const paramsSchemas = toolParametersToFormSchemas(currentTool.parameters?.filter(param => param.form === 'llm') || []) as ToolFormSchema[]
|
||||
const applyFields = (schemas: ToolFormSchema[]) => {
|
||||
const nextValue: ToolConfigValueMap = {}
|
||||
schemas.forEach((schema) => {
|
||||
const field = fieldsById.get(schema.variable)
|
||||
if (!field)
|
||||
return
|
||||
const isAuto = Boolean(field.auto)
|
||||
if (isAuto) {
|
||||
nextValue[schema.variable] = { auto: 1, value: null }
|
||||
return
|
||||
}
|
||||
nextValue[schema.variable] = {
|
||||
auto: 0,
|
||||
value: {
|
||||
type: getVarKindType(schema.type),
|
||||
value: field.value ?? null,
|
||||
},
|
||||
}
|
||||
})
|
||||
return nextValue
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultToolValue,
|
||||
settings: {
|
||||
...(defaultToolValue.settings || {}),
|
||||
...applyFields(settingsSchemas),
|
||||
},
|
||||
parameters: {
|
||||
...(defaultToolValue.parameters || {}),
|
||||
...applyFields(paramsSchemas),
|
||||
},
|
||||
}
|
||||
}, [currentTool, defaultToolValue, toolConfigFromMetadata])
|
||||
|
||||
const needAuthorization = useMemo(() => {
|
||||
return !currentProvider?.is_team_authorization
|
||||
}, [currentProvider])
|
||||
|
||||
const readmeEntrance = useMemo(() => {
|
||||
if (!currentProvider)
|
||||
return null
|
||||
return <ReadmeEntrance pluginDetail={currentProvider as unknown as PluginDetail} showType={ReadmeShowType.drawer} position="right" className="mt-auto" />
|
||||
}, [currentProvider])
|
||||
|
||||
useEffect(() => {
|
||||
if (!configuredToolValue)
|
||||
return
|
||||
if (!toolValue || toolValue.tool_name !== configuredToolValue.tool_name || toolValue.provider_name !== configuredToolValue.provider_name)
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
setToolValue(configuredToolValue)
|
||||
}, [configuredToolValue, toolValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (expandedToolId)
|
||||
return
|
||||
if (toolValue)
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
setToolValue(null)
|
||||
}, [expandedToolId, toolValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingOpen || !configuredToolValue)
|
||||
return
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
setToolValue(configuredToolValue)
|
||||
}, [configuredToolValue, isSettingOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (useModal)
|
||||
return
|
||||
const containerFromRef = ref.current?.closest('[data-skill-editor-root="true"]') as HTMLElement | null
|
||||
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
|
||||
const container = containerFromRef || fallbackContainer
|
||||
if (container)
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
|
||||
setPortalContainer(container)
|
||||
}, [ref, useModal])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingOpen || useModal)
|
||||
return
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Node | null
|
||||
const triggerEl = ref.current
|
||||
const panelEl = portalContainer?.querySelector('[data-tool-group-setting-panel="true"]')
|
||||
if (!target || !panelEl)
|
||||
return
|
||||
if (target instanceof Element && target.closest('[data-modal-root="true"]'))
|
||||
return
|
||||
if (panelEl.contains(target))
|
||||
return
|
||||
if (triggerEl && triggerEl.contains(target))
|
||||
return
|
||||
setIsSettingOpen(false)
|
||||
setExpandedToolId(null)
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [isSettingOpen, portalContainer, ref, useModal])
|
||||
|
||||
const resolvedEnabledByConfigId = useMemo(() => {
|
||||
const next = { ...enabledByConfigId }
|
||||
toolItems.forEach((item) => {
|
||||
if (next[item.configId] === undefined)
|
||||
next[item.configId] = true
|
||||
})
|
||||
return next
|
||||
}, [enabledByConfigId, toolItems])
|
||||
|
||||
const enabledCount = useMemo(() => {
|
||||
if (!toolItems.length)
|
||||
return 0
|
||||
return toolItems.reduce((count, item) => count + (resolvedEnabledByConfigId[item.configId] === false ? 0 : 1), 0)
|
||||
}, [resolvedEnabledByConfigId, toolItems])
|
||||
|
||||
const handleToolValueChange = (nextValue: ToolValue) => {
|
||||
if (!activeToolItem || !currentProvider || !currentTool)
|
||||
return
|
||||
setToolValue(nextValue)
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = (toolBlockContext?.metadata || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
const buildFields = (value: Record<string, unknown> | undefined) => {
|
||||
if (!value)
|
||||
return []
|
||||
return Object.entries(value).map(([id, field]) => {
|
||||
const fieldValue = field as ToolConfigValueItem | undefined
|
||||
const auto = Boolean(fieldValue?.auto)
|
||||
const rawValue = auto ? null : fieldValue?.value?.value ?? null
|
||||
return { id, value: rawValue, auto }
|
||||
})
|
||||
}
|
||||
const fields = [
|
||||
...buildFields(nextValue.settings),
|
||||
...buildFields(nextValue.parameters),
|
||||
]
|
||||
const nextMetadata: SkillFileMetadata = {
|
||||
...metadata,
|
||||
tools: {
|
||||
...(metadata.tools || {}),
|
||||
[activeToolItem.configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
},
|
||||
},
|
||||
}
|
||||
toolBlockContext?.onMetadataChange?.(nextMetadata)
|
||||
return
|
||||
}
|
||||
if (!activeTabId || activeTabId === START_TAB_ID)
|
||||
return
|
||||
const metadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
const buildFields = (value: Record<string, unknown> | undefined) => {
|
||||
if (!value)
|
||||
return []
|
||||
return Object.entries(value).map(([id, field]) => {
|
||||
const fieldValue = field as ToolConfigValueItem | undefined
|
||||
const auto = Boolean(fieldValue?.auto)
|
||||
const rawValue = auto ? null : fieldValue?.value?.value ?? null
|
||||
return { id, value: rawValue, auto }
|
||||
})
|
||||
}
|
||||
const fields = [
|
||||
...buildFields(nextValue.settings),
|
||||
...buildFields(nextValue.parameters),
|
||||
]
|
||||
const nextMetadata: SkillFileMetadata = {
|
||||
...metadata,
|
||||
tools: {
|
||||
...(metadata.tools || {}),
|
||||
[activeToolItem.configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
},
|
||||
},
|
||||
}
|
||||
storeApi.getState().setDraftMetadata(activeTabId, nextMetadata)
|
||||
storeApi.getState().pinTab(activeTabId)
|
||||
}
|
||||
|
||||
const handleToggleTool = useCallback((configId: string, nextValue: boolean) => {
|
||||
setEnabledByConfigId(prev => ({ ...prev, [configId]: nextValue }))
|
||||
}, [])
|
||||
|
||||
const renderIcon = () => {
|
||||
if (!resolvedIcon)
|
||||
return null
|
||||
@ -96,23 +458,203 @@ const ToolGroupBlockComponent: FC<ToolGroupBlockComponentProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const renderProviderHeaderIcon = () => {
|
||||
if (!resolvedIcon)
|
||||
return null
|
||||
if (typeof resolvedIcon === 'string') {
|
||||
if (resolvedIcon.startsWith('http') || resolvedIcon.startsWith('/')) {
|
||||
return (
|
||||
<span className="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-[10px] border border-divider-subtle bg-background-default-dodge">
|
||||
<span
|
||||
className="h-full w-full bg-cover bg-center"
|
||||
style={{ backgroundImage: `url(${resolvedIcon})` }}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<AppIcon
|
||||
size="large"
|
||||
icon={resolvedIcon}
|
||||
className="!h-10 !w-10 shrink-0 !rounded-[10px] !border border-divider-subtle bg-background-default-dodge"
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<AppIcon
|
||||
size="large"
|
||||
icon={resolvedIcon.content}
|
||||
background={resolvedIcon.background}
|
||||
className="!h-10 !w-10 shrink-0 !rounded-[10px] !border border-divider-subtle bg-background-default-dodge"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const toolSettingsContent = currentProvider && currentTool && toolValue && (
|
||||
<div className="px-3 pb-2">
|
||||
<ToolSettingsSection
|
||||
currentProvider={currentProvider}
|
||||
currentTool={currentTool}
|
||||
value={toolValue}
|
||||
onChange={handleToolValueChange}
|
||||
nodeId={undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const groupPanelContent = (
|
||||
<div className="flex min-h-full flex-col">
|
||||
<div className="border-b border-divider-subtle px-4 pb-3 pt-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{renderProviderHeaderIcon()}
|
||||
<div className="flex flex-col">
|
||||
<span className="system-md-semibold text-text-secondary">{providerLabel}</span>
|
||||
{providerAuthor && (
|
||||
<span className="system-xs-regular text-text-tertiary">{t('toolGroup.byAuthor', { ns: 'workflow', author: providerAuthor })}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-[6px] text-text-tertiary hover:bg-state-base-hover"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
setIsSettingOpen(false)
|
||||
setExpandedToolId(null)
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">{t('operation.close', { ns: 'common' })}</span>
|
||||
<RiCloseLine className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{providerDescription && (
|
||||
<div className="system-xs-regular mt-2 text-text-tertiary">
|
||||
{providerDescription}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<ToolAuthorizationSection
|
||||
currentProvider={currentProvider}
|
||||
credentialId={toolValue?.credential_id}
|
||||
onAuthorizationItemClick={(id) => {
|
||||
setToolValue(prev => (prev ? { ...prev, credential_id: id } : prev))
|
||||
}}
|
||||
/>
|
||||
{needAuthorization && (
|
||||
<div className="flex min-h-[120px] flex-1 flex-col items-center justify-center px-4 py-6 text-text-tertiary">
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{t('skillEditor.authorizationRequired', { ns: 'workflow' })}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 px-4 pb-4 pt-1">
|
||||
<div className="system-sm-semibold-uppercase text-text-secondary">
|
||||
{t('toolGroup.actionsEnabled', { ns: 'workflow', num: enabledCount })}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{toolItems.map(item => (
|
||||
<div
|
||||
key={item.configId}
|
||||
className={cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border-subtle px-3 py-2 shadow-xs',
|
||||
expandedToolId === item.configId ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-components-panel-item-bg',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="system-md-semibold flex-1 text-text-secondary">
|
||||
{item.toolLabel}
|
||||
</div>
|
||||
{item.toolParams?.length
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded-md px-2 py-1 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
|
||||
onClick={() => {
|
||||
setExpandedToolId(prev => (prev === item.configId ? null : item.configId))
|
||||
}}
|
||||
>
|
||||
<Settings01 className="h-3 w-3" />
|
||||
<span className="system-xs-medium">{t('operation.settings', { ns: 'common' })}</span>
|
||||
</button>
|
||||
)
|
||||
: null}
|
||||
<div className="pl-1">
|
||||
<Switch
|
||||
size="md"
|
||||
defaultValue={resolvedEnabledByConfigId[item.configId] !== false}
|
||||
onChange={(value) => {
|
||||
handleToggleTool(item.configId, value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{item.toolDescription && (
|
||||
<div className="system-xs-regular mt-1 text-text-tertiary">
|
||||
{item.toolDescription}
|
||||
</div>
|
||||
)}
|
||||
{expandedToolId === item.configId && toolSettingsContent}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{readmeEntrance}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-[2px] rounded-[5px] border border-state-accent-hover-alt bg-state-accent-hover px-[4px] py-[1px] shadow-xs',
|
||||
isSelected && 'border-text-accent',
|
||||
<>
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-[2px] rounded-[5px] border border-state-accent-hover-alt bg-state-accent-hover px-[4px] py-[1px] shadow-xs',
|
||||
isSelected && 'border-text-accent',
|
||||
)}
|
||||
title={providerLabel}
|
||||
onMouseDown={() => {
|
||||
if (!toolItems.length)
|
||||
return
|
||||
setIsSettingOpen(true)
|
||||
}}
|
||||
>
|
||||
{renderIcon()}
|
||||
<span className="system-xs-medium max-w-[160px] truncate text-text-accent">
|
||||
{providerLabel}
|
||||
</span>
|
||||
<span className="system-2xs-medium-uppercase rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-[4px] py-[2px] text-text-accent-secondary">
|
||||
{tools.length}
|
||||
</span>
|
||||
</span>
|
||||
{useModal && (
|
||||
<Modal
|
||||
isShow={isSettingOpen}
|
||||
onClose={() => {
|
||||
setIsSettingOpen(false)
|
||||
setExpandedToolId(null)
|
||||
}}
|
||||
className="!max-w-[420px] !bg-transparent !p-0"
|
||||
overflowVisible
|
||||
>
|
||||
<div className={cn('relative min-h-20 w-[420px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm')}>
|
||||
{groupPanelContent}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
title={providerLabel}
|
||||
>
|
||||
{renderIcon()}
|
||||
<span className="system-xs-medium max-w-[160px] truncate text-text-accent">
|
||||
{providerLabel}
|
||||
</span>
|
||||
<span className="system-2xs-medium-uppercase rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-[4px] py-[2px] text-text-accent-secondary">
|
||||
{tools.length}
|
||||
</span>
|
||||
</span>
|
||||
{!useModal && portalContainer && isSettingOpen && createPortal(
|
||||
<div
|
||||
className="absolute bottom-4 right-4 top-4 z-[999]"
|
||||
data-tool-group-setting-panel="true"
|
||||
>
|
||||
<div className={cn('relative h-full min-h-20 w-[420px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm')}>
|
||||
{groupPanelContent}
|
||||
</div>
|
||||
</div>,
|
||||
portalContainer,
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1185,6 +1185,8 @@
|
||||
"tabs.usePlugin": "Select tool",
|
||||
"tabs.utilities": "Utilities",
|
||||
"tabs.workflowTool": "Workflow",
|
||||
"toolGroup.actionsEnabled": "{{num}} ACTIONS ENABLED",
|
||||
"toolGroup.byAuthor": "by {{author}}",
|
||||
"tracing.stopBy": "Stop by {{user}}",
|
||||
"triggerStatus.disabled": "TRIGGER • DISABLED",
|
||||
"triggerStatus.enabled": "TRIGGER",
|
||||
|
||||
@ -1175,6 +1175,8 @@
|
||||
"tabs.usePlugin": "选择工具",
|
||||
"tabs.utilities": "工具",
|
||||
"tabs.workflowTool": "工作流",
|
||||
"toolGroup.actionsEnabled": "{{num}} 个操作已启用",
|
||||
"toolGroup.byAuthor": "由 {{author}} 提供",
|
||||
"tracing.stopBy": "由{{user}}终止",
|
||||
"triggerStatus.disabled": "触发器 • 已禁用",
|
||||
"triggerStatus.enabled": "触发器",
|
||||
|
||||
@ -1084,6 +1084,8 @@
|
||||
"tabs.usePlugin": "選取工具",
|
||||
"tabs.utilities": "工具",
|
||||
"tabs.workflowTool": "工作流",
|
||||
"toolGroup.actionsEnabled": "{{num}} 個操作已啟用",
|
||||
"toolGroup.byAuthor": "由 {{author}} 提供",
|
||||
"tracing.stopBy": "由{{user}}終止",
|
||||
"triggerStatus.disabled": "觸發器 • 已停用",
|
||||
"triggerStatus.enabled": "觸發",
|
||||
|
||||
Reference in New Issue
Block a user