From 26838eb42a4d2382c241d158f5f7ec26c93cf438 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Sat, 8 Feb 2025 17:11:24 +0800 Subject: [PATCH] tool setting support variable --- web/app/components/base/switch/index.tsx | 7 +- .../base/tab-slider-plain/index.tsx | 16 +- .../model-provider-page/model-modal/Form.tsx | 4 + .../multiple-tool-selector/index.tsx | 14 + .../tool-selector/index.tsx | 155 ++++++++--- .../tool-selector/reasoning-config-form.tsx | 258 ++++++++++++++++++ .../components/tools/utils/to-form-schema.ts | 31 +++ .../workflow/block-selector/types.ts | 1 + .../nodes/_base/components/agent-strategy.tsx | 6 + web/i18n/en-US/plugin.ts | 6 +- web/i18n/zh-Hans/plugin.ts | 6 +- 11 files changed, 455 insertions(+), 49 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx diff --git a/web/app/components/base/switch/index.tsx b/web/app/components/base/switch/index.tsx index 48e5c0cd8c..cbff726c76 100644 --- a/web/app/components/base/switch/index.tsx +++ b/web/app/components/base/switch/index.tsx @@ -5,7 +5,7 @@ import classNames from '@/utils/classnames' type SwitchProps = { onChange?: (value: boolean) => void - size?: 'sm' | 'md' | 'lg' | 'l' + size?: 'xs' | 'sm' | 'md' | 'lg' | 'l' defaultValue?: boolean disabled?: boolean className?: string @@ -23,6 +23,7 @@ const Switch = React.forwardRef( l: 'h-5 w-9', md: 'h-4 w-7', sm: 'h-3 w-5', + xs: 'h-2.5 w-3.5', } const circleStyle = { @@ -30,6 +31,7 @@ const Switch = React.forwardRef( l: 'h-4 w-4', md: 'h-3 w-3', sm: 'h-2 w-2', + xs: 'h-1.5 w-1', } const translateLeft = { @@ -37,6 +39,7 @@ const Switch = React.forwardRef( l: 'translate-x-4', md: 'translate-x-3', sm: 'translate-x-2', + xs: 'translate-x-1.5', } return ( @@ -61,6 +65,7 @@ const Switch = React.forwardRef( className={classNames( circleStyle[size], enabled ? translateLeft[size] : 'translate-x-0', + size === 'xs' && 'rounded-[1px]', 'pointer-events-none inline-block transform rounded-[3px] bg-components-toggle-knob shadow ring-0 transition duration-200 ease-in-out', )} /> diff --git a/web/app/components/base/tab-slider-plain/index.tsx b/web/app/components/base/tab-slider-plain/index.tsx index 194b6ad650..22a6c197d8 100644 --- a/web/app/components/base/tab-slider-plain/index.tsx +++ b/web/app/components/base/tab-slider-plain/index.tsx @@ -3,47 +3,51 @@ import type { FC } from 'react' import React from 'react' import cn from '@/utils/classnames' -interface Option { +type Option = { value: string text: string | JSX.Element } -interface ItemProps { +type ItemProps = { className?: string isActive: boolean onClick: (v: string) => void option: Option + smallItem?: boolean } const Item: FC = ({ className, isActive, onClick, option, + smallItem, }) => { return (
!isActive && onClick(option.value)} >
{option.text}
{isActive && ( -
+
)}
) } -interface Props { +type Props = { className?: string value: string onChange: (v: string) => void options: Option[] noBorderBottom?: boolean + smallItem?: boolean itemClassName?: string } @@ -54,6 +58,7 @@ const TabSlider: FC = ({ options, noBorderBottom, itemClassName, + smallItem, }) => { return (
@@ -64,6 +69,7 @@ const TabSlider: FC = ({ onClick={onChange} key={option.value} className={itemClassName} + smallItem={smallItem} /> ))}
diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index 74e295cf0e..d3b61a8d0f 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -326,6 +326,8 @@ function Form< void + supportVariables?: boolean + nodeOutputVars: NodeOutPutVar[], + availableNodes: Node[], } const MultipleToolSelector = ({ @@ -32,6 +37,9 @@ const MultipleToolSelector = ({ supportCollapse, scope, onChange, + supportVariables, + nodeOutputVars, + availableNodes, }: Props) => { const { t } = useTranslation() const enabledCount = value.filter(item => item.enabled).length @@ -121,6 +129,9 @@ const MultipleToolSelector = ({ {!collapse && ( <> 0 && value.map((item, index) => (
parameters?: Record extra?: Record }) => void @@ -65,6 +70,9 @@ type Props = { onControlledStateChange?: (state: boolean) => void panelShowState?: boolean onPanelShowStateChange?: (state: boolean) => void + supportVariables?: boolean + nodeOutputVars: NodeOutPutVar[], + availableNodes: Node[], } const ToolSelector: FC = ({ value, @@ -81,6 +89,8 @@ const ToolSelector: FC = ({ onControlledStateChange, panelShowState, onPanelShowStateChange, + nodeOutputVars, + availableNodes, }) => { const { t } = useTranslation() const [isShow, onShowChange] = useState(false) @@ -107,12 +117,14 @@ const ToolSelector: FC = ({ const [isShowChooseTool, setIsShowChooseTool] = useState(false) const handleSelectTool = (tool: ToolDefaultValue) => { - const paramValues = addDefaultValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any)) + const settingValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any)) + const paramValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form === 'llm') as any), true) const toolValue = { provider_name: tool.provider_id, type: tool.provider_type, tool_name: tool.tool_name, tool_label: tool.tool_label, + settings: settingValues, parameters: paramValues, enabled: tool.is_team_authorization, extra: { @@ -133,14 +145,33 @@ const ToolSelector: FC = ({ } as any) } - const currentToolParams = useMemo(() => { + // tool settings & params + const currentToolSettings = useMemo(() => { if (!currentProvider) return [] return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form !== 'llm') || [] }, [currentProvider, value]) + const currentToolParams = useMemo(() => { + if (!currentProvider) return [] + return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form === 'llm') || [] + }, [currentProvider, value]) + const [currType, setCurrType] = useState('settings') + const showTabSlider = currentToolSettings.length > 0 && currentToolParams.length > 0 + const userSettingsOnly = currentToolSettings.length > 0 && !currentToolParams.length + const reasoningConfigOnly = currentToolParams.length > 0 && !currentToolSettings.length - const formSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams]) + const settingsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolSettings), [currentToolSettings]) + const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams]) - const handleFormChange = (v: Record) => { + const handleSettingsFormChange = (v: Record) => { + const newValue = getStructureValue(v) + + const toolValue = { + ...value, + settings: newValue, + } + onSelect(toolValue as any) + } + const handleParamsFormChange = (v: Record) => { const toolValue = { ...value, parameters: v, @@ -281,12 +312,9 @@ const ToolSelector: FC = ({
{/* authorization */} {currentProvider && currentProvider.type === CollectionType.builtIn && currentProvider.allow_delete && ( -
-
-
{t('plugin.detailPanel.toolSelector.auth')}
- -
-
+ <> + +
{!currentProvider.is_team_authorization && ( )}
-
+ )} {/* tool settings */} - {currentToolParams.length > 0 && currentProvider?.is_team_authorization && ( -
-
-
{t('plugin.detailPanel.toolSelector.settings')}
- -
-
-
item.url - ? ( - {t('tools.howToGet')} - - ) - : null} + {(currentToolSettings.length > 0 || currentToolParams.length > 0) && currentProvider?.is_team_authorization && ( + <> + + {/* tabs */} + {showTabSlider && ( + { + setCurrType(value) + }} + options={[ + { value: 'settings', text: t('plugin.detailPanel.toolSelector.settings')! }, + { value: 'params', text: t('plugin.detailPanel.toolSelector.params')! }, + ]} /> -
-
+ )} + {showTabSlider && currType === 'params' && ( +
+
{t('plugin.detailPanel.toolSelector.paramsTip1')}
+
{t('plugin.detailPanel.toolSelector.paramsTip2')}
+
+ )} + {/* user settings only */} + {userSettingsOnly && ( +
+
{t('plugin.detailPanel.toolSelector.settings')}
+
+ )} + {/* reasoning config only */} + {reasoningConfigOnly && ( +
+
{t('plugin.detailPanel.toolSelector.params')}
+
+
{t('plugin.detailPanel.toolSelector.paramsTip1')}
+
{t('plugin.detailPanel.toolSelector.paramsTip2')}
+
+
+ )} + {/* user settings form */} + {(currType === 'settings' || userSettingsOnly) && ( +
+ item.url + ? ( + {t('tools.howToGet')} + + ) + : null} + /> +
+ )} + {/* reasoning config form */} + {(currType === 'params' || reasoningConfigOnly) && ( + + )} + )} )} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx new file mode 100644 index 0000000000..38d83542e6 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -0,0 +1,258 @@ +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { + RiArrowRightUpLine, +} from '@remixicon/react' +import Tooltip from '@/app/components/base/tooltip' +import Switch from '@/app/components/base/switch' +import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' +import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Node } from 'reactflow' +import type { NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types' +import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import cn from '@/utils/classnames' + +type Props = { + value: Record + onChange: (val: Record) => void + schemas: any[] + nodeOutputVars: NodeOutPutVar[], + availableNodes: Node[], +} + +const ReasoningConfigForm: React.FC = ({ + value, + onChange, + schemas, + nodeOutputVars, + availableNodes, +}) => { + const { t } = useTranslation() + const language = useLanguage() + const handleFormChange = (key: string, val: string | boolean) => { + onChange({ ...value, [key]: val }) + } + const handleAutomatic = (key: string, val: any) => { + onChange({ + ...value, + [key]: { + ...value[key], + auto: val ? 1 : 0, + }, + }) + } + + const [inputsIsFocus, setInputsIsFocus] = useState>({}) + const handleInputFocus = useCallback((variable: string) => { + return (value: boolean) => { + setInputsIsFocus((prev) => { + return { + ...prev, + [variable]: value, + } + }) + } + }, []) + const handleNotMixedTypeChange = useCallback((variable: string) => { + return (varValue: ValueSelector | string, varKindType: VarKindType) => { + const newValue = produce(value, (draft: ToolVarInputs) => { + const target = draft[variable] + if (target) { + target.type = varKindType + target.value = varValue + } + else { + draft[variable] = { + type: varKindType, + value: varValue, + } + } + }) + onChange(newValue) + } + }, [value, onChange]) + const handleMixedTypeChange = useCallback((variable: string) => { + return (itemValue: string) => { + const newValue = produce(value, (draft: ToolVarInputs) => { + const target = draft[variable] + if (target) { + target.value = itemValue + } + else { + draft[variable] = { + type: VarKindType.mixed, + value: itemValue, + } + } + }) + onChange(newValue) + } + }, [value, onChange]) + const handleFileChange = useCallback((variable: string) => { + return (varValue: ValueSelector | string) => { + const newValue = produce(value, (draft: ToolVarInputs) => { + draft[variable] = { + type: VarKindType.variable, + value: varValue, + } + }) + onChange(newValue) + } + }, [value, onChange]) + + const handleAppChange = useCallback((variable: string) => { + return (app: { + app_id: string + inputs: Record + files?: any[] + }) => { + const newValue = produce(value, (draft: ToolVarInputs) => { + draft[variable] = app as any + }) + onChange(newValue) + } + }, [onChange, value]) + const handleModelChange = useCallback((variable: string) => { + return (model: any) => { + const newValue = produce(value, (draft: ToolVarInputs) => { + draft[variable] = { + ...draft[variable], + ...model, + } as any + }) + onChange(newValue) + } + }, [onChange, value]) + + const renderField = (schema: any) => { + const { + variable, + label, + required, + tooltip, + type, + scope, + url, + } = schema + const auto = value[variable]?.auto + const tooltipContent = (tooltip && ( + + {tooltip[language] || tooltip.en_US} +
} + triggerClassName='ml-1 w-4 h-4' + asChild={false} /> + )) + const varInput = value[variable].value + const isNumber = type === FormTypeEnum.textNumber + const isSelect = type === FormTypeEnum.select + const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files + const isAppSelector = type === FormTypeEnum.appSelector + const isModelSelector = type === FormTypeEnum.modelSelector + // const isToolSelector = type === FormTypeEnum.toolSelector + const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector + return ( +
+
+
+ {label[language] || label.en_US} + {required && ( + * + )} + {tooltipContent} +
+
handleAutomatic(variable, !auto)}> + automatic + handleAutomatic(variable, val)} + /> +
+
+ {auto === 0 && ( + <> + {/* {isString && ( + + )} */} + {/* {(isNumber || isSelect) && ( + + )} */} + {/* {isFile && ( + varPayload.type === VarType.file || varPayload.type === VarType.arrayFile} + /> + )} */} + {isAppSelector && ( + + )} + {isModelSelector && ( + + )} + + )} + {url && ( + + {t('tools.howToGet')} + + + )} +
+ ) + } + return ( +
+ {schemas.map(schema => renderField(schema))} +
+ ) +} + +export default ReasoningConfigForm diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index 7086c903d1..6dc51e16ad 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -63,3 +63,34 @@ export const addDefaultValue = (value: Record, formSchemas: { varia }) return newValues } + +export const generateFormValue = (value: Record, formSchemas: { variable: string; default?: any }[], isReasoning = false) => { + const newValues = {} as any + formSchemas.forEach((formSchema) => { + const itemValue = value[formSchema.variable] + if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) { + newValues[formSchema.variable] = { + ...(isReasoning ? { value: null, auto: 1 } : { value: formSchema.default }), + } + } + }) + return newValues +} + +export const getPlainValue = (value: Record) => { + const plainValue = { ...value } as any + Object.keys(plainValue).forEach((key) => { + plainValue[key] = value[key].value + }) + return plainValue +} + +export const getStructureValue = (value: Record) => { + const newValue = { ...value } as any + Object.keys(newValue).forEach((key) => { + newValue[key] = { + value: value[key], + } + }) + return newValue +} diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 48679801ec..93a3242222 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -35,6 +35,7 @@ export type ToolValue = { provider_name: string tool_name: string tool_label: string + settings?: Record parameters?: Record enabled?: boolean extra?: Record diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index 01a1aba24e..6be841b9f0 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -154,6 +154,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { tooltip={schema.tooltip && renderI18nObject(schema.tooltip)} > onChange(item)} @@ -169,6 +172,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { } return (