mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
feat: support choose tools
This commit is contained in:
@ -31,6 +31,13 @@ import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { FileReferenceNode } from '@/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/node'
|
||||
import FileReferenceReplacementBlock from '@/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/replacement-block'
|
||||
import {
|
||||
ToolBlock,
|
||||
ToolBlockNode,
|
||||
ToolBlockReplacementBlock,
|
||||
} from '@/app/components/workflow/skill/editor/skill-editor/plugins/tool-block'
|
||||
import { ToolBlockContextProvider } from '@/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-block-context'
|
||||
import ToolPickerBlock from '@/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-picker-block'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import {
|
||||
@ -97,6 +104,8 @@ export type PromptEditorProps = {
|
||||
onChange?: (text: string) => void
|
||||
onBlur?: () => void
|
||||
onFocus?: () => void
|
||||
toolMetadata?: Record<string, unknown>
|
||||
onToolMetadataChange?: (metadata: Record<string, unknown>) => void
|
||||
contextBlock?: ContextBlockType
|
||||
queryBlock?: QueryBlockType
|
||||
historyBlock?: HistoryBlockType
|
||||
@ -124,6 +133,8 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
onChange,
|
||||
onBlur,
|
||||
onFocus,
|
||||
toolMetadata,
|
||||
onToolMetadataChange,
|
||||
contextBlock,
|
||||
queryBlock,
|
||||
historyBlock,
|
||||
@ -156,7 +167,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
CurrentBlockNode,
|
||||
ErrorMessageBlockNode,
|
||||
LastRunBlockNode, // LastRunBlockNode is used for error message block replacement
|
||||
...(isSupportSandbox ? [FileReferenceNode] : []),
|
||||
...(isSupportSandbox ? [FileReferenceNode, ToolBlockNode] : []),
|
||||
],
|
||||
editorState: textToEditorState(value || ''),
|
||||
onError: (error: Error) => {
|
||||
@ -185,46 +196,45 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
} as any)
|
||||
}, [eventEmitter, historyBlock?.history])
|
||||
|
||||
const toolBlockContextValue = React.useMemo(() => {
|
||||
if (!onToolMetadataChange)
|
||||
return null
|
||||
return {
|
||||
metadata: toolMetadata,
|
||||
onMetadataChange: onToolMetadataChange,
|
||||
useModal: true,
|
||||
}
|
||||
}, [onToolMetadataChange, toolMetadata])
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<div className={cn('relative', wrapperClassName)}>
|
||||
<RichTextPlugin
|
||||
contentEditable={(
|
||||
<ContentEditable
|
||||
className={cn(
|
||||
'text-text-secondary outline-none',
|
||||
compact ? 'text-[13px] leading-5' : 'text-sm leading-6',
|
||||
className,
|
||||
)}
|
||||
style={style || {}}
|
||||
/>
|
||||
)}
|
||||
placeholder={(
|
||||
<Placeholder
|
||||
value={placeholder}
|
||||
className={cn('truncate', placeholderClassName)}
|
||||
compact={compact}
|
||||
/>
|
||||
)}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString="/"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
variableBlock={variableBlock}
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
currentBlock={currentBlock}
|
||||
errorMessageBlock={errorMessageBlock}
|
||||
lastRunBlock={lastRunBlock}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isSupportSandbox={isSupportSandbox}
|
||||
/>
|
||||
{(!agentBlock || agentBlock.show) && (
|
||||
<ToolBlockContextProvider value={toolBlockContextValue}>
|
||||
<div
|
||||
className={cn('relative', wrapperClassName)}
|
||||
data-skill-editor-root={isSupportSandbox ? 'true' : undefined}
|
||||
>
|
||||
<RichTextPlugin
|
||||
contentEditable={(
|
||||
<ContentEditable
|
||||
className={cn(
|
||||
'text-text-secondary outline-none',
|
||||
compact ? 'text-[13px] leading-5' : 'text-sm leading-6',
|
||||
className,
|
||||
)}
|
||||
style={style || {}}
|
||||
/>
|
||||
)}
|
||||
placeholder={(
|
||||
<Placeholder
|
||||
value={placeholder}
|
||||
className={cn('truncate', placeholderClassName)}
|
||||
compact={compact}
|
||||
/>
|
||||
)}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString="@"
|
||||
triggerString="/"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@ -234,100 +244,123 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
currentBlock={currentBlock}
|
||||
errorMessageBlock={errorMessageBlock}
|
||||
lastRunBlock={lastRunBlock}
|
||||
agentBlock={agentBlock}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isSupportSandbox={isSupportSandbox}
|
||||
/>
|
||||
)}
|
||||
<ComponentPickerBlock
|
||||
triggerString="{"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
variableBlock={variableBlock}
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
currentBlock={currentBlock}
|
||||
errorMessageBlock={errorMessageBlock}
|
||||
lastRunBlock={lastRunBlock}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isSupportSandbox={isSupportSandbox}
|
||||
/>
|
||||
{
|
||||
contextBlock?.show && (
|
||||
{!isSupportSandbox && (!agentBlock || agentBlock.show) && (
|
||||
<ComponentPickerBlock
|
||||
triggerString="@"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
variableBlock={variableBlock}
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
currentBlock={currentBlock}
|
||||
errorMessageBlock={errorMessageBlock}
|
||||
lastRunBlock={lastRunBlock}
|
||||
agentBlock={agentBlock}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
/>
|
||||
)}
|
||||
{isSupportSandbox && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
<ToolBlock />
|
||||
<ToolBlockReplacementBlock />
|
||||
{editable && <ToolPickerBlock />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
)}
|
||||
<ComponentPickerBlock
|
||||
triggerString="{"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
variableBlock={variableBlock}
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
currentBlock={currentBlock}
|
||||
errorMessageBlock={errorMessageBlock}
|
||||
lastRunBlock={lastRunBlock}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isSupportSandbox={isSupportSandbox}
|
||||
/>
|
||||
{
|
||||
contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{isSupportSandbox && <FileReferenceReplacementBlock />}
|
||||
{
|
||||
currentBlock?.show && (
|
||||
<>
|
||||
<CurrentBlock {...currentBlock} />
|
||||
<CurrentBlockReplacementBlock {...currentBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
errorMessageBlock?.show && (
|
||||
<>
|
||||
<ErrorMessageBlock {...errorMessageBlock} />
|
||||
<ErrorMessageBlockReplacementBlock {...errorMessageBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
lastRunBlock?.show && (
|
||||
<>
|
||||
<LastRunBlock {...lastRunBlock} />
|
||||
<LastRunReplacementBlock {...lastRunBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
isSupportFileVar && (
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{isSupportSandbox && <FileReferenceReplacementBlock />}
|
||||
{
|
||||
currentBlock?.show && (
|
||||
<>
|
||||
<CurrentBlock {...currentBlock} />
|
||||
<CurrentBlockReplacementBlock {...currentBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
errorMessageBlock?.show && (
|
||||
<>
|
||||
<ErrorMessageBlock {...errorMessageBlock} />
|
||||
<ErrorMessageBlockReplacementBlock {...errorMessageBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
lastRunBlock?.show && (
|
||||
<>
|
||||
<LastRunBlock {...lastRunBlock} />
|
||||
<LastRunReplacementBlock {...lastRunBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
isSupportFileVar && (
|
||||
<VariableValueBlock />
|
||||
)
|
||||
}
|
||||
<OnChangePlugin onChange={handleEditorChange} />
|
||||
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
|
||||
<UpdateBlock instanceId={instanceId} />
|
||||
<HistoryPlugin />
|
||||
{/* <TreeView /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<OnChangePlugin onChange={handleEditorChange} />
|
||||
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
|
||||
<UpdateBlock instanceId={instanceId} />
|
||||
<HistoryPlugin />
|
||||
{/* <TreeView /> */}
|
||||
</div>
|
||||
</ToolBlockContextProvider>
|
||||
</LexicalComposer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -63,6 +63,8 @@ type Props = {
|
||||
availableNodes?: Node[]
|
||||
isSupportFileVar?: boolean
|
||||
isSupportSandbox?: boolean
|
||||
promptMetadata?: Record<string, unknown>
|
||||
onPromptMetadataChange?: (metadata: Record<string, unknown>) => void
|
||||
isSupportPromptGenerator?: boolean
|
||||
onGenerated?: (prompt: string) => void
|
||||
modelConfig?: ModelConfig
|
||||
@ -104,6 +106,8 @@ const Editor: FC<Props> = ({
|
||||
availableNodes = [],
|
||||
isSupportFileVar,
|
||||
isSupportSandbox,
|
||||
promptMetadata,
|
||||
onPromptMetadataChange,
|
||||
isSupportPromptGenerator,
|
||||
isSupportJinja,
|
||||
editionType,
|
||||
@ -298,6 +302,8 @@ const Editor: FC<Props> = ({
|
||||
editable={!readOnly}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isSupportSandbox={isSupportSandbox}
|
||||
toolMetadata={promptMetadata}
|
||||
onToolMetadataChange={onPromptMetadataChange}
|
||||
/>
|
||||
{/* to patch Editor not support dynamic change editable status */}
|
||||
{readOnly && <div className="absolute inset-0 z-10"></div>}
|
||||
|
||||
@ -27,6 +27,7 @@ type Props = {
|
||||
payload: PromptItem
|
||||
handleChatModeMessageRoleChange: (role: PromptRole) => void
|
||||
onPromptChange: (p: string) => void
|
||||
onMetadataChange: (metadata: Record<string, unknown>) => void
|
||||
onEditionTypeChange: (editionType: EditionType) => void
|
||||
onRemove: () => void
|
||||
isShowContext: boolean
|
||||
@ -74,6 +75,7 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
isChatApp,
|
||||
payload,
|
||||
onPromptChange,
|
||||
onMetadataChange,
|
||||
onEditionTypeChange,
|
||||
onRemove,
|
||||
isShowContext,
|
||||
@ -131,6 +133,8 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
)}
|
||||
value={payload.edition_type === EditionType.jinja2 ? (payload.jinja2_text || '') : payload.text}
|
||||
onChange={onPromptChange}
|
||||
promptMetadata={payload.metadata}
|
||||
onPromptMetadataChange={onMetadataChange}
|
||||
readOnly={readOnly}
|
||||
showRemove={canRemove}
|
||||
onRemove={onRemove}
|
||||
|
||||
@ -146,6 +146,17 @@ const ConfigPrompt: FC<Props> = ({
|
||||
}
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleChatModeMetadataChange = useCallback((index: number) => {
|
||||
return (metadata: Record<string, unknown>) => {
|
||||
const newPrompt = produce(payload as PromptTemplateItem[], (draft) => {
|
||||
const item = draft[index]
|
||||
if (!isPromptMessageContext(item))
|
||||
(item as PromptItem).metadata = metadata
|
||||
})
|
||||
onChange(newPrompt)
|
||||
}
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleChatModeEditionTypeChange = useCallback((index: number) => {
|
||||
return (editionType: EditionType) => {
|
||||
const newPrompt = produce(payload as PromptTemplateItem[], (draft) => {
|
||||
@ -246,6 +257,13 @@ const ConfigPrompt: FC<Props> = ({
|
||||
onChange(newPrompt)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleCompletionMetadataChange = useCallback((metadata: Record<string, unknown>) => {
|
||||
const newPrompt = produce(payload as PromptItem, (draft) => {
|
||||
draft.metadata = metadata
|
||||
})
|
||||
onChange(newPrompt)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleGenerated = useCallback((prompt: string) => {
|
||||
handleCompletionPromptChange(prompt)
|
||||
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
|
||||
@ -331,6 +349,7 @@ const ConfigPrompt: FC<Props> = ({
|
||||
isChatApp={isChatApp}
|
||||
payload={item}
|
||||
onPromptChange={handleChatModePromptChange(index)}
|
||||
onMetadataChange={handleChatModeMetadataChange(index)}
|
||||
onEditionTypeChange={handleChatModeEditionTypeChange(index)}
|
||||
onRemove={handleRemove(index)}
|
||||
isShowContext={isShowContext}
|
||||
@ -399,6 +418,8 @@ const ConfigPrompt: FC<Props> = ({
|
||||
title={<span className="capitalize">{t(`${i18nPrefix}.prompt`, { ns: 'workflow' })}</span>}
|
||||
value={((payload as PromptItem).edition_type === EditionType.basic || !(payload as PromptItem).edition_type) ? (payload as PromptItem).text : ((payload as PromptItem).jinja2_text || '')}
|
||||
onChange={handleCompletionPromptChange}
|
||||
promptMetadata={(payload as PromptItem).metadata}
|
||||
onPromptMetadataChange={handleCompletionMetadataChange}
|
||||
readOnly={readOnly}
|
||||
isChatModel={isChatModel}
|
||||
isChatApp={isChatApp}
|
||||
|
||||
@ -6,6 +6,7 @@ import * as React from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
|
||||
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'
|
||||
@ -27,6 +28,7 @@ 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'
|
||||
import ToolHeader from './tool-header'
|
||||
|
||||
type ToolBlockComponentProps = {
|
||||
@ -102,6 +104,9 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_TOOL_BLOCK_COMMAND)
|
||||
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 [toolValue, setToolValue] = useState<ToolValue | null>(null)
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null)
|
||||
@ -154,11 +159,15 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
}, [currentTool?.description, language, toolValue?.tool_description])
|
||||
|
||||
const toolConfigFromMetadata = useMemo(() => {
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = toolBlockContext?.metadata as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[configId]
|
||||
}
|
||||
if (!activeTabId)
|
||||
return undefined
|
||||
const metadata = fileMetadata.get(activeTabId) as SkillFileMetadata | undefined
|
||||
return metadata?.tools?.[configId]
|
||||
}, [activeTabId, configId, fileMetadata])
|
||||
}, [activeTabId, configId, fileMetadata, isUsingExternalMetadata, toolBlockContext?.metadata])
|
||||
|
||||
const defaultToolValue = useMemo(() => {
|
||||
if (!currentProvider || !currentTool)
|
||||
@ -244,16 +253,18 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
}, [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])
|
||||
}, [ref, useModal])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSettingOpen)
|
||||
if (!isSettingOpen || useModal)
|
||||
return
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@ -273,7 +284,7 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [isSettingOpen, portalContainer, ref])
|
||||
}, [isSettingOpen, portalContainer, ref, useModal])
|
||||
|
||||
const displayLabel = label || toolMeta?.label || tool
|
||||
const resolvedIcon = (() => {
|
||||
@ -316,7 +327,39 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
|
||||
const handleToolValueChange = (nextValue: ToolValue) => {
|
||||
setToolValue(nextValue)
|
||||
if (!activeTabId || !currentProvider || !currentTool)
|
||||
if (!currentProvider || !currentTool)
|
||||
return
|
||||
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 || {}),
|
||||
[configId]: {
|
||||
type: toolType,
|
||||
configuration: { fields },
|
||||
},
|
||||
},
|
||||
}
|
||||
toolBlockContext?.onMetadataChange?.(nextMetadata)
|
||||
return
|
||||
}
|
||||
if (!activeTabId)
|
||||
return
|
||||
const metadata = (fileMetadata.get(activeTabId) || {}) as SkillFileMetadata
|
||||
const toolType = currentProvider.type === CollectionType.mcp ? 'mcp' : 'builtin'
|
||||
@ -352,6 +395,30 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
setToolValue(prev => (prev ? { ...prev, credential_id: id } : prev))
|
||||
}
|
||||
|
||||
const toolSettingsContent = currentProvider && currentTool && toolValue && (
|
||||
<>
|
||||
<ToolHeader
|
||||
icon={resolvedIcon}
|
||||
providerLabel={currentProvider.label?.[language] || currentProvider.name || provider}
|
||||
toolLabel={toolValue.tool_label || displayLabel}
|
||||
description={toolDescriptionText}
|
||||
onClose={() => setIsSettingOpen(false)}
|
||||
/>
|
||||
<ToolAuthorizationSection
|
||||
currentProvider={currentProvider}
|
||||
credentialId={toolValue.credential_id}
|
||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||
/>
|
||||
<ToolSettingsSection
|
||||
currentProvider={currentProvider}
|
||||
currentTool={currentTool}
|
||||
value={toolValue}
|
||||
onChange={handleToolValueChange}
|
||||
nodeId={undefined}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
@ -375,35 +442,25 @@ const ToolBlockComponent: FC<ToolBlockComponentProps> = ({
|
||||
{displayLabel}
|
||||
</span>
|
||||
</span>
|
||||
{portalContainer && isSettingOpen && createPortal(
|
||||
{useModal && (
|
||||
<Modal
|
||||
isShow={isSettingOpen}
|
||||
onClose={() => setIsSettingOpen(false)}
|
||||
className="!max-w-[420px] !bg-transparent !p-0"
|
||||
overflowVisible
|
||||
>
|
||||
<div className={cn('relative min-h-20 w-[361px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm', 'overflow-y-auto pb-2')}>
|
||||
{toolSettingsContent}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
{!useModal && portalContainer && isSettingOpen && createPortal(
|
||||
<div
|
||||
className="absolute bottom-4 right-4 top-4 z-[999]"
|
||||
data-tool-setting-panel="true"
|
||||
>
|
||||
<div className={cn('relative h-full min-h-20 w-[361px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-4 shadow-lg backdrop-blur-sm', 'overflow-y-auto pb-2')}>
|
||||
{currentProvider && currentTool && toolValue && (
|
||||
<>
|
||||
<ToolHeader
|
||||
icon={resolvedIcon}
|
||||
providerLabel={currentProvider.label?.[language] || currentProvider.name || provider}
|
||||
toolLabel={toolValue.tool_label || displayLabel}
|
||||
description={toolDescriptionText}
|
||||
onClose={() => setIsSettingOpen(false)}
|
||||
/>
|
||||
<ToolAuthorizationSection
|
||||
currentProvider={currentProvider}
|
||||
credentialId={toolValue.credential_id}
|
||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||
/>
|
||||
<ToolSettingsSection
|
||||
currentProvider={currentProvider}
|
||||
currentTool={currentTool}
|
||||
value={toolValue}
|
||||
onChange={handleToolValueChange}
|
||||
nodeId={undefined}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{toolSettingsContent}
|
||||
</div>
|
||||
</div>,
|
||||
portalContainer,
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
type ToolBlockContextValue = {
|
||||
metadata?: Record<string, unknown>
|
||||
onMetadataChange?: (metadata: Record<string, unknown>) => void
|
||||
useModal?: boolean
|
||||
}
|
||||
|
||||
const ToolBlockContext = createContext<ToolBlockContextValue | null>(null)
|
||||
|
||||
export const ToolBlockContextProvider = ToolBlockContext.Provider
|
||||
|
||||
export const useToolBlockContext = () => useContext(ToolBlockContext)
|
||||
@ -18,6 +18,7 @@ import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-for
|
||||
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { $createToolBlockNode } from './node'
|
||||
import { useToolBlockContext } from './tool-block-context'
|
||||
|
||||
class ToolPickerMenuOption extends MenuOption {
|
||||
constructor() {
|
||||
@ -36,6 +37,8 @@ const ToolPickerBlock: FC<ToolPickerBlockProps> = ({ scope = 'all' }) => {
|
||||
maxLength: 0,
|
||||
})
|
||||
const storeApi = useWorkflowStore()
|
||||
const toolBlockContext = useToolBlockContext()
|
||||
const isUsingExternalMetadata = Boolean(toolBlockContext?.onMetadataChange)
|
||||
|
||||
const options = useMemo(() => [new ToolPickerMenuOption()], [])
|
||||
|
||||
@ -70,6 +73,27 @@ const ToolPickerBlock: FC<ToolPickerBlockProps> = ({ scope = 'all' }) => {
|
||||
$insertNodes(nodes)
|
||||
})
|
||||
|
||||
if (isUsingExternalMetadata) {
|
||||
const metadata = (toolBlockContext?.metadata || {}) as Record<string, unknown>
|
||||
const nextTools = { ...(metadata.tools || {}) } as Record<string, unknown>
|
||||
toolEntries.forEach(({ configId, tool }) => {
|
||||
const schemas = toolParametersToFormSchemas((tool.paramSchemas || []) as ToolParameter[])
|
||||
const fields = schemas.map(schema => ({
|
||||
id: schema.variable,
|
||||
value: schema.default ?? null,
|
||||
auto: schema.form === 'llm',
|
||||
}))
|
||||
nextTools[configId] = {
|
||||
type: tool.provider_type,
|
||||
configuration: { fields },
|
||||
}
|
||||
})
|
||||
toolBlockContext?.onMetadataChange?.({
|
||||
...metadata,
|
||||
tools: nextTools,
|
||||
})
|
||||
return
|
||||
}
|
||||
const { activeTabId, fileMetadata, setDraftMetadata, pinTab } = storeApi.getState()
|
||||
if (!activeTabId)
|
||||
return
|
||||
@ -92,7 +116,7 @@ const ToolPickerBlock: FC<ToolPickerBlockProps> = ({ scope = 'all' }) => {
|
||||
tools: nextTools,
|
||||
})
|
||||
pinTab(activeTabId)
|
||||
}, [checkForTriggerMatch, editor, storeApi])
|
||||
}, [checkForTriggerMatch, editor, isUsingExternalMetadata, storeApi, toolBlockContext])
|
||||
|
||||
const renderMenu = useCallback((
|
||||
anchorElementRef: React.RefObject<HTMLElement | null>,
|
||||
|
||||
Reference in New Issue
Block a user