mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 04:26:30 +08:00
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("provider", type=str, required=True, location="args")
|
||||||
parser.add_argument("action", 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("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")
|
parser.add_argument("provider_type", type=str, required=True, location="args")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@ -527,6 +528,7 @@ class PluginFetchDynamicSelectOptionsApi(Resource):
|
|||||||
args["provider"],
|
args["provider"],
|
||||||
args["action"],
|
args["action"],
|
||||||
args["parameter"],
|
args["parameter"],
|
||||||
|
args["extra"],
|
||||||
args["provider_type"],
|
args["provider_type"],
|
||||||
)
|
)
|
||||||
except PluginDaemonClientSideError as e:
|
except PluginDaemonClientSideError as e:
|
||||||
|
|||||||
@ -15,6 +15,7 @@ class DynamicSelectClient(BasePluginClient):
|
|||||||
provider: str,
|
provider: str,
|
||||||
action: str,
|
action: str,
|
||||||
credentials: Mapping[str, Any],
|
credentials: Mapping[str, Any],
|
||||||
|
credential_type: str,
|
||||||
parameter: str,
|
parameter: str,
|
||||||
) -> PluginDynamicSelectOptionsResponse:
|
) -> PluginDynamicSelectOptionsResponse:
|
||||||
"""
|
"""
|
||||||
@ -29,6 +30,7 @@ class DynamicSelectClient(BasePluginClient):
|
|||||||
"data": {
|
"data": {
|
||||||
"provider": GenericProviderID(provider).provider_name,
|
"provider": GenericProviderID(provider).provider_name,
|
||||||
"credentials": credentials,
|
"credentials": credentials,
|
||||||
|
"credential_type": credential_type,
|
||||||
"provider_action": action,
|
"provider_action": action,
|
||||||
"parameter": parameter,
|
"parameter": parameter,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,9 +4,12 @@ from typing import Any, Literal
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from core.plugin.entities.parameters import PluginParameterOption
|
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.plugin.impl.dynamic_select import DynamicSelectClient
|
||||||
from core.tools.tool_manager import ToolManager
|
from core.tools.tool_manager import ToolManager
|
||||||
from core.tools.utils.encryption import create_tool_provider_encrypter
|
from core.tools.utils.encryption import create_tool_provider_encrypter
|
||||||
|
from core.trigger.trigger_manager import TriggerManager
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from models.tools import BuiltinToolProvider
|
from models.tools import BuiltinToolProvider
|
||||||
|
|
||||||
@ -20,7 +23,8 @@ class PluginParameterService:
|
|||||||
provider: str,
|
provider: str,
|
||||||
action: str,
|
action: str,
|
||||||
parameter: str,
|
parameter: str,
|
||||||
provider_type: Literal["tool"],
|
extra: dict | None,
|
||||||
|
provider_type: Literal["tool", "trigger"],
|
||||||
) -> Sequence[PluginParameterOption]:
|
) -> Sequence[PluginParameterOption]:
|
||||||
"""
|
"""
|
||||||
Get dynamic select options for a plugin parameter.
|
Get dynamic select options for a plugin parameter.
|
||||||
@ -33,7 +37,7 @@ class PluginParameterService:
|
|||||||
parameter: The parameter name.
|
parameter: The parameter name.
|
||||||
"""
|
"""
|
||||||
credentials: Mapping[str, Any] = {}
|
credentials: Mapping[str, Any] = {}
|
||||||
|
credential_type: str = CredentialType.API_KEY.value
|
||||||
match provider_type:
|
match provider_type:
|
||||||
case "tool":
|
case "tool":
|
||||||
provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
|
provider_controller = ToolManager.get_builtin_provider(provider, tenant_id)
|
||||||
@ -49,24 +53,43 @@ class PluginParameterService:
|
|||||||
else:
|
else:
|
||||||
# fetch credentials from db
|
# fetch credentials from db
|
||||||
with Session(db.engine) as session:
|
with Session(db.engine) as session:
|
||||||
db_record = (
|
if extra and "credential_id" in extra:
|
||||||
session.query(BuiltinToolProvider)
|
credential_id = extra["credential_id"]
|
||||||
.where(
|
db_record = (
|
||||||
BuiltinToolProvider.tenant_id == tenant_id,
|
session.query(BuiltinToolProvider)
|
||||||
BuiltinToolProvider.provider == provider,
|
.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:
|
if db_record is None:
|
||||||
raise ValueError(f"Builtin provider {provider} not found when fetching credentials")
|
raise ValueError(f"Builtin provider {provider} not found when fetching credentials")
|
||||||
|
|
||||||
credentials = encrypter.decrypt(db_record.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 _:
|
case _:
|
||||||
raise ValueError(f"Invalid provider type: {provider_type}")
|
raise ValueError(f"Invalid provider type: {provider_type}")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
DynamicSelectClient()
|
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
|
.options
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types'
|
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 { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
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 as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||||
import { VarType } from '@/app/components/workflow/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 type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||||
import FormInputTypeSwitch from './form-input-type-switch'
|
import FormInputTypeSwitch from './form-input-type-switch'
|
||||||
@ -31,6 +33,7 @@ type Props = {
|
|||||||
inPanel?: boolean
|
inPanel?: boolean
|
||||||
currentTool?: Tool
|
currentTool?: Tool
|
||||||
currentProvider?: ToolWithProvider
|
currentProvider?: ToolWithProvider
|
||||||
|
extraParams?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormInputItem: FC<Props> = ({
|
const FormInputItem: FC<Props> = ({
|
||||||
@ -42,8 +45,11 @@ const FormInputItem: FC<Props> = ({
|
|||||||
inPanel,
|
inPanel,
|
||||||
currentTool,
|
currentTool,
|
||||||
currentProvider,
|
currentProvider,
|
||||||
|
extraParams,
|
||||||
}) => {
|
}) => {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null)
|
||||||
|
const [isLoadingOptions, setIsLoadingOptions] = useState(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
placeholder,
|
placeholder,
|
||||||
@ -61,7 +67,8 @@ const FormInputItem: FC<Props> = ({
|
|||||||
const isShowJSONEditor = isObject || isArray
|
const isShowJSONEditor = isObject || isArray
|
||||||
const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files
|
const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files
|
||||||
const isBoolean = type === FormTypeEnum.boolean
|
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 isAppSelector = type === FormTypeEnum.appSelector
|
||||||
const isModelSelector = type === FormTypeEnum.modelSelector
|
const isModelSelector = type === FormTypeEnum.modelSelector
|
||||||
const showTypeSwitch = isNumber || isBoolean || isObject || isArray
|
const showTypeSwitch = isNumber || isBoolean || isObject || isArray
|
||||||
@ -119,12 +126,44 @@ const FormInputItem: FC<Props> = ({
|
|||||||
const getVarKindType = () => {
|
const getVarKindType = () => {
|
||||||
if (isFile)
|
if (isFile)
|
||||||
return VarKindType.variable
|
return VarKindType.variable
|
||||||
if (isSelect || isBoolean || isNumber || isArray || isObject)
|
if (isSelect || isDynamicSelect || isBoolean || isNumber || isArray || isObject)
|
||||||
return VarKindType.constant
|
return VarKindType.constant
|
||||||
if (isString)
|
if (isString)
|
||||||
return VarKindType.mixed
|
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) => {
|
const handleTypeChange = (newType: string) => {
|
||||||
if (newType === VarKindType.variable) {
|
if (newType === VarKindType.variable) {
|
||||||
onChange({
|
onChange({
|
||||||
@ -219,9 +258,48 @@ const FormInputItem: FC<Props> = ({
|
|||||||
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
|
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
|
||||||
|
|
||||||
return true
|
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)}
|
onSelect={item => handleValueChange(item.value as string)}
|
||||||
placeholder={placeholder?.[language] || placeholder?.en_US}
|
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 && (
|
{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({
|
return useMutation({
|
||||||
mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', {
|
mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', {
|
||||||
params: {
|
params: {
|
||||||
@ -622,6 +622,7 @@ export const useFetchDynamicOptions = (plugin_id: string, provider: string, acti
|
|||||||
action,
|
action,
|
||||||
parameter,
|
parameter,
|
||||||
provider_type,
|
provider_type,
|
||||||
|
...extra,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user