diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index fd5421fa64..499bfdbcac 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -516,6 +516,7 @@ class PluginFetchDynamicSelectOptionsApi(Resource): parser.add_argument("provider", type=str, required=True, location="args") parser.add_argument("action", type=str, required=True, location="args") parser.add_argument("parameter", type=str, required=True, location="args") + parser.add_argument("extra", type=dict, required=False, location="args") parser.add_argument("provider_type", type=str, required=True, location="args") args = parser.parse_args() @@ -527,6 +528,7 @@ class PluginFetchDynamicSelectOptionsApi(Resource): args["provider"], args["action"], args["parameter"], + args["extra"], args["provider_type"], ) except PluginDaemonClientSideError as e: diff --git a/api/core/plugin/impl/dynamic_select.py b/api/core/plugin/impl/dynamic_select.py index 004412afd7..93d533193c 100644 --- a/api/core/plugin/impl/dynamic_select.py +++ b/api/core/plugin/impl/dynamic_select.py @@ -15,6 +15,7 @@ class DynamicSelectClient(BasePluginClient): provider: str, action: str, credentials: Mapping[str, Any], + credential_type: str, parameter: str, ) -> PluginDynamicSelectOptionsResponse: """ @@ -29,6 +30,7 @@ class DynamicSelectClient(BasePluginClient): "data": { "provider": GenericProviderID(provider).provider_name, "credentials": credentials, + "credential_type": credential_type, "provider_action": action, "parameter": parameter, }, diff --git a/api/services/plugin/plugin_parameter_service.py b/api/services/plugin/plugin_parameter_service.py index 00b59dacb3..6dd8ebf7ce 100644 --- a/api/services/plugin/plugin_parameter_service.py +++ b/api/services/plugin/plugin_parameter_service.py @@ -4,9 +4,12 @@ from typing import Any, Literal from sqlalchemy.orm import Session from core.plugin.entities.parameters import PluginParameterOption +from core.plugin.entities.plugin import TriggerProviderID +from core.plugin.entities.plugin_daemon import CredentialType from core.plugin.impl.dynamic_select import DynamicSelectClient from core.tools.tool_manager import ToolManager from core.tools.utils.encryption import create_tool_provider_encrypter +from core.trigger.trigger_manager import TriggerManager from extensions.ext_database import db from models.tools import BuiltinToolProvider @@ -20,7 +23,8 @@ class PluginParameterService: provider: str, action: str, parameter: str, - provider_type: Literal["tool"], + extra: dict | None, + provider_type: Literal["tool", "trigger"], ) -> Sequence[PluginParameterOption]: """ Get dynamic select options for a plugin parameter. @@ -33,7 +37,7 @@ class PluginParameterService: parameter: The parameter name. """ credentials: Mapping[str, Any] = {} - + credential_type: str = CredentialType.API_KEY.value match provider_type: case "tool": provider_controller = ToolManager.get_builtin_provider(provider, tenant_id) @@ -49,24 +53,43 @@ class PluginParameterService: else: # fetch credentials from db with Session(db.engine) as session: - db_record = ( - session.query(BuiltinToolProvider) - .where( - BuiltinToolProvider.tenant_id == tenant_id, - BuiltinToolProvider.provider == provider, + if extra and "credential_id" in extra: + credential_id = extra["credential_id"] + db_record = ( + session.query(BuiltinToolProvider) + .where( + BuiltinToolProvider.tenant_id == tenant_id, + BuiltinToolProvider.provider == provider, + BuiltinToolProvider.id == credential_id, + ) + .first() + ) + else: + db_record = ( + session.query(BuiltinToolProvider) + .where( + BuiltinToolProvider.tenant_id == tenant_id, + BuiltinToolProvider.provider == provider, + ) + .order_by(BuiltinToolProvider.is_default.desc(), BuiltinToolProvider.created_at.asc()) + .first() ) - .first() - ) if db_record is None: raise ValueError(f"Builtin provider {provider} not found when fetching credentials") credentials = encrypter.decrypt(db_record.credentials) + credential_type = db_record.credential_type + case "trigger": + provider_controller = TriggerManager.get_trigger_provider(tenant_id, TriggerProviderID(provider)) + case _: raise ValueError(f"Invalid provider type: {provider_type}") return ( DynamicSelectClient() - .fetch_dynamic_select_options(tenant_id, user_id, plugin_id, provider, action, credentials, parameter) + .fetch_dynamic_select_options( + tenant_id, user_id, plugin_id, provider, action, credentials, credential_type, parameter + ) .options ) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index d624130317..68d979eadc 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -1,11 +1,13 @@ 'use client' import type { FC } from 'react' +import { useEffect, useState } from 'react' import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' -import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations' 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 { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { VarType } from '@/app/components/workflow/types' +import { useFetchDynamicOptions } from '@/service/use-plugins' import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import FormInputTypeSwitch from './form-input-type-switch' @@ -31,6 +33,7 @@ type Props = { inPanel?: boolean currentTool?: Tool currentProvider?: ToolWithProvider + extraParams?: Record } const FormInputItem: FC = ({ @@ -42,8 +45,11 @@ const FormInputItem: FC = ({ inPanel, currentTool, currentProvider, + extraParams, }) => { const language = useLanguage() + const [dynamicOptions, setDynamicOptions] = useState(null) + const [isLoadingOptions, setIsLoadingOptions] = useState(false) const { placeholder, @@ -61,7 +67,8 @@ const FormInputItem: FC = ({ const isShowJSONEditor = isObject || isArray const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const isBoolean = type === FormTypeEnum.boolean - const isSelect = type === FormTypeEnum.select || type === FormTypeEnum.dynamicSelect + const isSelect = type === FormTypeEnum.select + const isDynamicSelect = type === FormTypeEnum.dynamicSelect const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector const showTypeSwitch = isNumber || isBoolean || isObject || isArray @@ -119,12 +126,44 @@ const FormInputItem: FC = ({ const getVarKindType = () => { if (isFile) return VarKindType.variable - if (isSelect || isBoolean || isNumber || isArray || isObject) + if (isSelect || isDynamicSelect || isBoolean || isNumber || isArray || isObject) return VarKindType.constant if (isString) return VarKindType.mixed } + // Fetch dynamic options hook + const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions( + currentProvider?.plugin_id || '', + currentProvider?.name || '', + currentTool?.name || '', + variable || '', + 'tool', + extraParams, + ) + + // Fetch dynamic options when component mounts or dependencies change + useEffect(() => { + const fetchOptions = async () => { + if (isDynamicSelect && currentTool && currentProvider) { + setIsLoadingOptions(true) + try { + const data = await fetchDynamicOptions() + setDynamicOptions(data?.options || []) + } + catch (error) { + console.error('Failed to fetch dynamic options:', error) + setDynamicOptions([]) + } + finally { + setIsLoadingOptions(false) + } + } + } + + fetchOptions() + }, [isDynamicSelect, currentTool?.name, currentProvider?.name, variable, extraParams]) + const handleTypeChange = (newType: string) => { if (newType === VarKindType.variable) { onChange({ @@ -219,9 +258,48 @@ const FormInputItem: FC = ({ return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) return true - }).map((option: { value: any; label: { [x: string]: any; en_US: any } }) => ({ value: option.value, name: option.label[language] || option.label.en_US }))} + }).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ({ + value: option.value, + name: option.label[language] || option.label.en_US, + icon: option.icon, + }))} onSelect={item => handleValueChange(item.value as string)} placeholder={placeholder?.[language] || placeholder?.en_US} + renderOption={options.some((opt: any) => opt.icon) ? ({ item }) => ( +
+ {item.icon && ( + + )} + {item.name} +
+ ) : undefined} + /> + )} + {isDynamicSelect && ( + { + if (option.show_on?.length) + return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + + return true + }).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ({ + value: option.value, + name: option.label[language] || option.label.en_US, + icon: option.icon, + }))} + onSelect={item => handleValueChange(item.value as string)} + placeholder={isLoadingOptions ? 'Loading...' : (placeholder?.[language] || placeholder?.en_US)} + renderOption={({ item }) => ( +
+ {item.icon && ( + + )} + {item.name} +
+ )} /> )} {isShowJSONEditor && isConstant && ( diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 2877ef15f2..3ede69c2af 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -613,7 +613,7 @@ export const usePluginInfo = (providerName?: string) => { }) } -export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type: 'tool') => { +export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type: 'tool', extra?: Record) => { return useMutation({ mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', { params: { @@ -622,6 +622,7 @@ export const useFetchDynamicOptions = (plugin_id: string, provider: string, acti action, parameter, provider_type, + ...extra, }, }), })