fix(dynamic_select): implement function

This commit is contained in:
Harry 2025-09-02 20:11:07 +08:00
parent 97a9d34e96
commit dd929dbf0e
5 changed files with 121 additions and 15 deletions

View File

@ -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:

View File

@ -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,
},

View File

@ -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
)

View File

@ -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<string, any>
}
const FormInputItem: FC<Props> = ({
@ -42,8 +45,11 @@ const FormInputItem: FC<Props> = ({
inPanel,
currentTool,
currentProvider,
extraParams,
}) => {
const language = useLanguage()
const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null)
const [isLoadingOptions, setIsLoadingOptions] = useState(false)
const {
placeholder,
@ -61,7 +67,8 @@ const FormInputItem: FC<Props> = ({
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<Props> = ({
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<Props> = ({
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 }) => (
<div className="flex items-center">
{item.icon && (
<img src={item.icon} alt="" className="mr-2 h-4 w-4" />
)}
<span>{item.name}</span>
</div>
) : undefined}
/>
)}
{isDynamicSelect && (
<SimpleSelect
wrapperClassName='h-8 grow'
disabled={readOnly || isLoadingOptions}
defaultValue={varInput?.value}
items={(dynamicOptions || options || []).filter((option: { show_on?: any[] }) => {
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 }) => (
<div className="flex items-center">
{item.icon && (
<img src={item.icon} alt="" className="mr-2 h-4 w-4" />
)}
<span>{item.name}</span>
</div>
)}
/>
)}
{isShowJSONEditor && isConstant && (

View File

@ -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<string, any>) => {
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,
},
}),
})