From 31c17e637876fcb667f2b678f5b02d52486d9a7d Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 11 Feb 2025 14:08:43 +0800 Subject: [PATCH 1/5] fix: installed plugin not show upgrade (#13523) --- .../install-bundle/steps/install-multi.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx index 803be4abd4..40be3e65e6 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx @@ -87,10 +87,13 @@ const InstallByDSLList: FC = ({ const failedIndex: number[] = [] const nextPlugins = produce(pluginsRef.current, (draft) => { marketPlaceInDSLIndex.forEach((index, i) => { - if (payloads[i]) - draft[index] = payloads[i] - else - failedIndex.push(index) + if (payloads[i]) { + draft[index] = { + ...payloads[i], + version: payloads[i].version || payloads[i].latest_version, + } + } + else { failedIndex.push(index) } }) }) setPlugins(nextPlugins) @@ -192,8 +195,8 @@ const InstallByDSLList: FC = ({ key={index} checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)} onCheckedChange={handleSelect(index)} - payload={plugins[index] as Plugin} - version={(d as GitHubItemAndMarketPlaceDependency).value.version!} + payload={plugin} + version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''} versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)} /> ) From 824f8d899488432b239739a26fad849c1619aaf4 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 11 Feb 2025 18:32:01 +0800 Subject: [PATCH 2/5] chore: add debug doc link (#13537) --- web/app/components/plugins/plugin-page/debug-info.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/plugins/plugin-page/debug-info.tsx b/web/app/components/plugins/plugin-page/debug-info.tsx index e4d249f3e2..4361c5b37b 100644 --- a/web/app/components/plugins/plugin-page/debug-info.tsx +++ b/web/app/components/plugins/plugin-page/debug-info.tsx @@ -29,8 +29,8 @@ const DebugInfo: FC = () => { popupContent={ <>
- {t(`${i18nPrefix}.title`)} - + {t(`${i18nPrefix}.title`)} + {t(`${i18nPrefix}.viewDocs`)} From 2ea3b64a451c9071d2465d94e1f0c9d84cc2cff5 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Wed, 12 Feb 2025 12:54:10 +0800 Subject: [PATCH 3/5] Feat: tool setting support variable (#13465) Co-authored-by: zxhlyh --- web/app/components/base/switch/index.tsx | 7 +- .../base/tab-slider-plain/index.tsx | 16 +- .../model-provider-page/model-modal/Form.tsx | 16 + .../multiple-tool-selector/index.tsx | 14 + .../tool-selector/index.tsx | 158 +++++++--- .../tool-selector/reasoning-config-form.tsx | 275 ++++++++++++++++++ .../components/tools/utils/to-form-schema.ts | 31 ++ .../workflow/block-selector/types.ts | 1 + .../workflow/hooks/use-checklist.ts | 2 +- .../nodes/_base/components/agent-strategy.tsx | 16 +- .../nodes/_base/components/variable/utils.ts | 2 +- .../variable/var-reference-picker.tsx | 4 +- .../workflow/nodes/agent/default.ts | 89 ++++++ .../components/workflow/nodes/agent/panel.tsx | 1 + .../components/workflow/nodes/constants.ts | 16 + .../components/condition-files-list-value.tsx | 2 +- .../condition-list/condition-item.tsx | 4 +- .../if-else/components/condition-value.tsx | 4 +- .../if-else/components/condition-wrap.tsx | 2 +- .../workflow/nodes/if-else/default.ts | 16 - .../components/filter-condition.tsx | 2 +- .../components/sub-variable-picker.tsx | 2 +- web/i18n/en-US/plugin.ts | 7 +- web/i18n/en-US/workflow.ts | 2 + web/i18n/zh-Hans/plugin.ts | 7 +- web/i18n/zh-Hans/workflow.ts | 3 + 26 files changed, 620 insertions(+), 79 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..baf6b194ce 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 @@ -22,6 +22,10 @@ import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-sele import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import RadioE from '@/app/components/base/radio/ui' +import type { + NodeOutPutVar, +} from '@/app/components/workflow/types' +import type { Node } from 'reactflow' type FormProps< CustomFormSchema extends Omit & { type: string } = never, @@ -47,6 +51,9 @@ type FormProps< ) => ReactNode // If return falsy value, this field will fallback to default render override?: [Array, (formSchema: CredentialFormSchema, props: Omit, 'override' | 'customRenderField'>) => ReactNode] + nodeId?: string + nodeOutputVars?: NodeOutPutVar[], + availableNodes?: Node[], } function Form< @@ -69,6 +76,9 @@ function Form< fieldMoreInfo, customRenderField, override, + nodeId, + nodeOutputVars, + availableNodes, }: FormProps) { const language = useLanguage() const [changeKey, setChangeKey] = useState('') @@ -326,6 +336,9 @@ function Form<
void + nodeOutputVars: NodeOutPutVar[], + availableNodes: Node[], + nodeId?: string } const MultipleToolSelector = ({ @@ -32,6 +37,9 @@ const MultipleToolSelector = ({ supportCollapse, scope, onChange, + nodeOutputVars, + availableNodes, + nodeId, }: 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 + nodeOutputVars: NodeOutPutVar[], + availableNodes: Node[], + nodeId?: string, } const ToolSelector: FC = ({ value, @@ -81,6 +89,9 @@ const ToolSelector: FC = ({ onControlledStateChange, panelShowState, onPanelShowStateChange, + nodeOutputVars, + availableNodes, + nodeId = '', }) => { const { t } = useTranslation() const [isShow, onShowChange] = useState(false) @@ -107,17 +118,20 @@ 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: { description: '', }, + schemas: tool.paramSchemas, } onSelect(toolValue) // setIsShowChooseTool(false) @@ -133,14 +147,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 +314,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 */} + {nodeId && showTabSlider && ( + { + setCurrType(value) + }} + options={[ + { value: 'settings', text: t('plugin.detailPanel.toolSelector.settings')! }, + { value: 'params', text: t('plugin.detailPanel.toolSelector.params')! }, + ]} /> -
-
+ )} + {nodeId && 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 */} + {nodeId && 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 */} + {nodeId && (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..6ba8207856 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -0,0 +1,275 @@ +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 Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +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, + Var, +} 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 { VarType } from '@/app/components/workflow/types' +import cn from '@/utils/classnames' + +type Props = { + value: Record + onChange: (val: Record) => void + schemas: any[] + nodeOutputVars: NodeOutPutVar[], + availableNodes: Node[], + nodeId: string +} + +const ReasoningConfigForm: React.FC = ({ + value, + onChange, + schemas, + nodeOutputVars, + availableNodes, + nodeId, +}) => { + const { t } = useTranslation() + const language = useLanguage() + const handleAutomatic = (key: string, val: any) => { + onChange({ + ...value, + [key]: { + value: val ? null : value[key]?.value, + 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].value + if (target) { + target.type = varKindType + target.value = varValue + } + else { + draft[variable].value = { + 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].value + if (target) { + target.value = itemValue + } + else { + draft[variable].value = { + 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].value = { + 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].value = app as any + }) + onChange(newValue) + } + }, [onChange, value]) + const handleModelChange = useCallback((variable: string) => { + return (model: any) => { + const newValue = produce(value, (draft: ToolVarInputs) => { + draft[variable].value = { + ...draft[variable].value, + ...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)}> + {t('plugin.detailPanel.toolSelector.auto')} + handleAutomatic(variable, val)} + /> +
+
+ {auto === 0 && ( + <> + {isString && ( + + )} + {/* {isString && ( + varPayload.type === VarType.number || varPayload.type === VarType.secret || varPayload.type === VarType.string} + /> + )} */} + {(isNumber || isSelect) && ( + varPayload.type === schema._type : undefined} + availableVars={isSelect ? nodeOutputVars : undefined} + schema={schema} + /> + )} + {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/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index 561c70fc30..0ef374493c 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -184,7 +184,7 @@ export const useChecklistBeforePublish = () => { } return true - }, [nodesExtraData, notify, t, store, isChatMode, buildInTools, customTools, workflowTools, language]) + }, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders]) return { handleCheckBeforePublish, 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..a07686a8f5 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -36,6 +36,7 @@ export type AgentStrategyProps = { onFormValueChange: (value: ToolVarInputs) => void nodeOutputVars?: NodeOutPutVar[], availableNodes?: Node[], + nodeId?: string } type CustomSchema = Omit & { type: Type } & Field @@ -46,7 +47,7 @@ type MultipleToolSelectorSchema = CustomSchema<'array[tools]'> type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema export const AgentStrategy = memo((props: AgentStrategyProps) => { - const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes } = props + const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props const { t } = useTranslation() const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration) const renderI18nObject = useRenderI18nObject() @@ -141,7 +142,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { ] const renderField: ComponentProps>['customRenderField'] = (schema, props) => { switch (schema.type) { - case 'tool-selector': { + case FormTypeEnum.toolSelector: { const value = props.value[schema.variable] const onChange = (value: any) => { props.onChange({ ...props.value, [schema.variable]: value }) @@ -154,6 +155,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { tooltip={schema.tooltip && renderI18nObject(schema.tooltip)} > onChange(item)} @@ -162,13 +166,16 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { ) } - case 'array[tools]': { + case FormTypeEnum.multiToolSelector: { const value = props.value[schema.variable] const onChange = (value: any) => { props.onChange({ ...props.value, [schema.variable]: value }) } return ( { fieldLabelClassName='uppercase' customRenderField={renderField} override={override} + nodeId={nodeId} + nodeOutputVars={nodeOutputVars || []} + availableNodes={availableNodes || []} /> : = ({ @@ -90,6 +91,7 @@ const VarReferencePicker: FC = ({ placeholder, minWidth, popupFor, + zIndex, }) => { const { t } = useTranslation() const store = useStoreApi() @@ -386,7 +388,7 @@ const VarReferencePicker: FC = ({ {!isConstant && ( = { @@ -37,6 +38,94 @@ const nodeDefault: NodeDefault = { } } for (const param of strategy.parameters) { + // single tool + if (param.required && param.type === FormTypeEnum.toolSelector) { + // no value + const toolValue = payload.agent_parameters?.[param.name]?.value + if (!toolValue) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.fieldRequired', { field: renderI18nObject(param.label, language) }), + } + } + // not enabled + else if (!toolValue.enabled) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.noValidTool', { field: renderI18nObject(param.label, language) }), + } + } + // check form of tool + else { + const schemas = toolValue.schemas || [] + const userSettings = toolValue.settings + const reasoningConfig = toolValue.parameters + schemas.forEach((schema: any) => { + if (schema?.required) { + if (schema.form === 'form' && !userSettings[schema.name]?.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + if (schema.form === 'llm' && reasoningConfig[schema.name].auto === 0 && !userSettings[schema.name]?.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + } + }) + } + } + // multiple tools + if (param.required && param.type === FormTypeEnum.multiToolSelector) { + const tools = payload.agent_parameters?.[param.name]?.value || [] + // no value + if (!tools.length) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.fieldRequired', { field: renderI18nObject(param.label, language) }), + } + } + // not enabled + else if (tools.every((tool: any) => !tool.enabled)) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.noValidTool', { field: renderI18nObject(param.label, language) }), + } + } + // check form of tools + else { + let validState = { + isValid: true, + errorMessage: '', + } + for (const tool of tools) { + const schemas = tool.schemas || [] + const userSettings = tool.settings + const reasoningConfig = tool.parameters + schemas.forEach((schema: any) => { + if (schema?.required) { + if (schema.form === 'form' && !userSettings[schema.name]?.value) { + return validState = { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + if (schema.form === 'llm' && reasoningConfig[schema.name]?.auto === 0 && !reasoningConfig[schema.name]?.value) { + return validState = { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + } + }) + } + return validState + } + } + // common params if (param.required && !payload.agent_parameters?.[param.name]?.value) { return { isValid: false, diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 75e3522ab9..c3912c293e 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -103,6 +103,7 @@ const AgentPanel: FC> = (props) => { onFormValueChange={onFormChange} nodeOutputVars={availableVars} availableNodes={availableNodesWithParent} + nodeId={props.id} />
diff --git a/web/app/components/workflow/nodes/constants.ts b/web/app/components/workflow/nodes/constants.ts index dc202acc28..d765b89f86 100644 --- a/web/app/components/workflow/nodes/constants.ts +++ b/web/app/components/workflow/nodes/constants.ts @@ -36,6 +36,7 @@ import ListFilterNode from './list-operator/node' import ListFilterPanel from './list-operator/panel' import AgentNode from './agent/node' import AgentPanel from './agent/panel' +import { TransferMethod } from '@/types/app' export const NodeComponentMap: Record> = { [BlockEnum.Start]: StartNode, @@ -82,3 +83,18 @@ export const PanelComponentMap: Record> = { } export const CUSTOM_NODE_TYPE = 'custom' + +export const FILE_TYPE_OPTIONS = [ + { value: 'image', i18nKey: 'image' }, + { value: 'document', i18nKey: 'doc' }, + { value: 'audio', i18nKey: 'audio' }, + { value: 'video', i18nKey: 'video' }, +] + +export const TRANSFER_METHOD = [ + { value: TransferMethod.local_file, i18nKey: 'localUpload' }, + { value: TransferMethod.remote_url, i18nKey: 'url' }, +] + +export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method'] +export const OUTPUT_FILE_SUB_VARIABLES = SUB_VARIABLES.filter(key => key !== 'transfer_method') diff --git a/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx index f21a3fac10..b0cfdeaf28 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx @@ -9,7 +9,7 @@ import { isComparisonOperatorNeedTranslate, isEmptyRelatedOperator, } from '../utils' -import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../default' +import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' import type { ValueSelector } from '../../../types' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx index 2e89c73074..670f766bea 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx @@ -21,7 +21,7 @@ import { } from '../../types' import { comparisonOperatorNotRequireValue, getOperators } from '../../utils' import ConditionNumberInput from '../condition-number-input' -import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from '../../default' +import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from '../../../constants' import ConditionWrap from '../condition-wrap' import ConditionOperator from './condition-operator' import ConditionInput from './condition-input' @@ -39,7 +39,7 @@ import { SimpleSelect as Select } from '@/app/components/base/select' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' -interface ConditionItemProps { +type ConditionItemProps = { className?: string disabled?: boolean caseId: string diff --git a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx index 3a71b85a0e..8bfa77acc1 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx @@ -9,7 +9,7 @@ import { comparisonOperatorNotRequireValue, isComparisonOperatorNeedTranslate, } from '../utils' -import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../default' +import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import cn from '@/utils/classnames' @@ -20,7 +20,7 @@ import type { Node, } from '@/app/components/workflow/types' -interface ConditionValueProps { +type ConditionValueProps = { variableSelector: string[] labelName?: string operator: ComparisonOperator diff --git a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx index 39c03c9b38..a09cb5fa29 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx @@ -12,7 +12,7 @@ import type { CaseItem, HandleAddCondition, HandleAddSubVariableCondition, Handl import type { Node, NodeOutPutVar, Var } from '../../../types' import { VarType } from '../../../types' import { useGetAvailableVars } from '../../variable-assigner/hooks' -import { SUB_VARIABLES } from '../default' +import { SUB_VARIABLES } from '../../constants' import ConditionList from './condition-list' import ConditionAdd from './condition-add' import cn from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/if-else/default.ts b/web/app/components/workflow/nodes/if-else/default.ts index 8d98f694bd..1be80592e5 100644 --- a/web/app/components/workflow/nodes/if-else/default.ts +++ b/web/app/components/workflow/nodes/if-else/default.ts @@ -1,7 +1,6 @@ import { BlockEnum, type NodeDefault } from '../../types' import { type IfElseNodeType, LogicalOperator } from './types' import { isEmptyRelatedOperator } from './utils' -import { TransferMethod } from '@/types/app' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -79,18 +78,3 @@ const nodeDefault: NodeDefault = { } export default nodeDefault - -export const FILE_TYPE_OPTIONS = [ - { value: 'image', i18nKey: 'image' }, - { value: 'document', i18nKey: 'doc' }, - { value: 'audio', i18nKey: 'audio' }, - { value: 'video', i18nKey: 'video' }, -] - -export const TRANSFER_METHOD = [ - { value: TransferMethod.local_file, i18nKey: 'localUpload' }, - { value: TransferMethod.remote_url, i18nKey: 'url' }, -] - -export const SUB_VARIABLES = ['type', 'size', 'name', 'url', 'extension', 'mime_type', 'transfer_method'] -export const OUTPUT_FILE_SUB_VARIABLES = SUB_VARIABLES.filter(key => key !== 'transfer_method') diff --git a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx index b64f753514..0c261a70d6 100644 --- a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx @@ -9,7 +9,7 @@ import { ComparisonOperator } from '../../if-else/types' import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils' import SubVariablePicker from './sub-variable-picker' import Input from '@/app/components/base/input' -import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/if-else/default' +import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants' import { SimpleSelect as Select } from '@/app/components/base/select' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' diff --git a/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx b/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx index 0a210504cf..c3a8708603 100644 --- a/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { SUB_VARIABLES } from '../../if-else/default' +import { SUB_VARIABLES } from '../../constants' import type { Item } from '@/app/components/base/select' import { SimpleSelect as Select } from '@/app/components/base/select' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index ea93b8812d..f302d81c47 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -78,8 +78,11 @@ const translation = { descriptionLabel: 'Tool description', descriptionPlaceholder: 'Brief description of the tool\'s purpose, e.g., get the temperature for a specific location.', placeholder: 'Select a tool...', - auth: 'AUTHORIZATION', - settings: 'TOOL SETTINGS', + settings: 'USER SETTINGS', + params: 'REASONING CONFIG', + paramsTip1: 'Controls LLM inference parameters.', + paramsTip2: 'When \'Automatic\' is off, the default value is used.', + auto: 'Automatic', empty: 'Click the \'+\' button to add tools. You can add multiple tools.', uninstalledTitle: 'Tool not installed', uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.', diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 53c50073c0..89344f01a6 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -195,6 +195,8 @@ const translation = { visionVariable: 'Vision Variable', }, invalidVariable: 'Invalid variable', + noValidTool: '{{field}} no valid tool selected', + toolParameterRequired: '{{field}}: parameter [{{param}}] is required', }, singleRun: { testRun: 'Test Run ', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index 53f2ef8e5c..c13f7b84f1 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -78,8 +78,11 @@ const translation = { descriptionLabel: '工具描述', descriptionPlaceholder: '简要描述工具目的,例如,获取特定位置的温度。', placeholder: '选择工具', - auth: '授权', - settings: '工具设置', + settings: '用户设置', + params: '推理配置', + paramsTip1: '控制 LLM 推理参数。', + paramsTip2: '当“自动”关闭时,使用默认值。', + auto: '自动', empty: '点击 "+" 按钮添加工具。您可以添加多个工具。', uninstalledTitle: '工具未安装', uninstalledContent: '此插件安装自 本地 / GitHub 仓库,请安装后使用。', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index c622581e2d..679626cff8 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -195,6 +195,9 @@ const translation = { visionVariable: '视觉变量', }, invalidVariable: '无效的变量', + noValidTool: '{{field}} 无可用工具', + toolParameterRequired: '{{field}}: 参数 [{{param}}] 不能为空', + }, singleRun: { testRun: '测试运行 ', From 7c1d842cfe089ac6d9275c896d03a49d3c64af36 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:21:58 +0800 Subject: [PATCH 4/5] (1.0) fix: invalid default model provider (#13572) --- .../model_providers/model_provider_factory.py | 6 +++++- api/core/plugin/entities/plugin.py | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/core/model_runtime/model_providers/model_provider_factory.py b/api/core/model_runtime/model_providers/model_provider_factory.py index 23596558db..b311f069a8 100644 --- a/api/core/model_runtime/model_providers/model_provider_factory.py +++ b/api/core/model_runtime/model_providers/model_provider_factory.py @@ -20,6 +20,7 @@ from core.model_runtime.model_providers.__base.text_embedding_model import TextE from core.model_runtime.model_providers.__base.tts_model import TTSModel from core.model_runtime.schema_validators.model_credential_schema_validator import ModelCredentialSchemaValidator from core.model_runtime.schema_validators.provider_credential_schema_validator import ProviderCredentialSchemaValidator +from core.plugin.entities.plugin import ModelProviderID from core.plugin.entities.plugin_daemon import PluginModelProviderEntity from core.plugin.manager.asset import PluginAssetManager from core.plugin.manager.model import PluginModelManager @@ -112,6 +113,9 @@ class ModelProviderFactory: :param provider: provider name :return: provider schema """ + if "/" not in provider: + provider = str(ModelProviderID(provider)) + # fetch plugin model providers plugin_model_provider_entities = self.get_plugin_model_providers() @@ -363,4 +367,4 @@ class ModelProviderFactory: plugin_id = "/".join(provider.split("/")[:-1]) provider_name = provider.split("/")[-1] - return plugin_id, provider_name + return str(plugin_id), provider_name diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index ee65e86826..aa78eb919c 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -169,6 +169,13 @@ class GenericProviderID: return f"{self.organization}/{self.plugin_name}" +class ModelProviderID(GenericProviderID): + def __init__(self, value: str, is_hardcoded: bool = False) -> None: + super().__init__(value, is_hardcoded) + if self.organization == "langgenius" and self.provider_name == "google": + self.provider_name = "gemini" + + class PluginDependency(BaseModel): class Type(enum.StrEnum): Github = PluginInstallationSource.Github.value From 56c7f496255fe6ad7272fad71361805d2ee3a1d4 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:37:10 +0800 Subject: [PATCH 5/5] fix: add langgenius to list tool api (#13578) --- api/core/plugin/entities/plugin.py | 8 ++++++++ api/services/plugin/plugin_migration.py | 3 --- api/services/tools/builtin_tools_manage_service.py | 7 ++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index aa78eb919c..958a4b69e4 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -176,6 +176,14 @@ class ModelProviderID(GenericProviderID): self.provider_name = "gemini" +class ToolProviderID(GenericProviderID): + def __init__(self, value: str, is_hardcoded: bool = False) -> None: + super().__init__(value, is_hardcoded) + if self.organization == "langgenius": + if self.provider_name in ["jina", "siliconflow"]: + self.provider_name = f"{self.provider_name}_tool" + + class PluginDependency(BaseModel): class Type(enum.StrEnum): Github = PluginInstallationSource.Github.value diff --git a/api/services/plugin/plugin_migration.py b/api/services/plugin/plugin_migration.py index fd1bef98b7..63cd08a900 100644 --- a/api/services/plugin/plugin_migration.py +++ b/api/services/plugin/plugin_migration.py @@ -1,7 +1,6 @@ import datetime import json import logging -import sys import time from collections.abc import Mapping, Sequence from concurrent.futures import ThreadPoolExecutor @@ -418,8 +417,6 @@ class PluginMigration: logger.info("Uninstall plugins") - sys.exit(-1) - # get installation try: installation = manager.list_plugins(fake_tenant_id) diff --git a/api/services/tools/builtin_tools_manage_service.py b/api/services/tools/builtin_tools_manage_service.py index aa6f3493fe..5865aaf77a 100644 --- a/api/services/tools/builtin_tools_manage_service.py +++ b/api/services/tools/builtin_tools_manage_service.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import Session from configs import dify_config from core.helper.position_helper import is_filtered from core.model_runtime.utils.encoders import jsonable_encoder -from core.plugin.entities.plugin import GenericProviderID +from core.plugin.entities.plugin import GenericProviderID, ToolProviderID from core.plugin.manager.exc import PluginDaemonClientSideError from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity @@ -240,10 +240,7 @@ class BuiltinToolManageService: # rewrite db_providers for db_provider in db_providers: - try: - GenericProviderID(db_provider.provider) - except Exception: - db_provider.provider = f"langgenius/{db_provider.provider}/{db_provider.provider}" + db_provider.provider = str(ToolProviderID(db_provider.provider)) # find provider def find_provider(provider):