mirror of https://github.com/langgenius/dify.git
fix(dynamic_select): implement function
This commit is contained in:
parent
97a9d34e96
commit
dd929dbf0e
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue