| @@ -13,6 +13,7 @@ from core.model_runtime.utils.encoders import jsonable_encoder | |||
| from core.plugin.impl.exc import PluginDaemonClientSideError | |||
| from libs.login import login_required | |||
| from models.account import TenantPluginPermission | |||
| from services.plugin.plugin_parameter_service import PluginParameterService | |||
| from services.plugin.plugin_permission_service import PluginPermissionService | |||
| from services.plugin.plugin_service import PluginService | |||
| @@ -497,6 +498,42 @@ class PluginFetchPermissionApi(Resource): | |||
| ) | |||
| class PluginFetchDynamicSelectOptionsApi(Resource): | |||
| @setup_required | |||
| @login_required | |||
| @account_initialization_required | |||
| def get(self): | |||
| # check if the user is admin or owner | |||
| if not current_user.is_admin_or_owner: | |||
| raise Forbidden() | |||
| tenant_id = current_user.current_tenant_id | |||
| user_id = current_user.id | |||
| parser = reqparse.RequestParser() | |||
| parser.add_argument("plugin_id", 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("parameter", type=str, required=True, location="args") | |||
| parser.add_argument("provider_type", type=str, required=True, location="args") | |||
| args = parser.parse_args() | |||
| try: | |||
| options = PluginParameterService.get_dynamic_select_options( | |||
| tenant_id, | |||
| user_id, | |||
| args["plugin_id"], | |||
| args["provider"], | |||
| args["action"], | |||
| args["parameter"], | |||
| args["provider_type"], | |||
| ) | |||
| except PluginDaemonClientSideError as e: | |||
| raise ValueError(e) | |||
| return jsonable_encoder({"options": options}) | |||
| api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") | |||
| api.add_resource(PluginListApi, "/workspaces/current/plugin/list") | |||
| api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") | |||
| @@ -521,3 +558,5 @@ api.add_resource(PluginFetchMarketplacePkgApi, "/workspaces/current/plugin/marke | |||
| api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change") | |||
| api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch") | |||
| api.add_resource(PluginFetchDynamicSelectOptionsApi, "/workspaces/current/plugin/parameters/dynamic-options") | |||
| @@ -15,6 +15,11 @@ class CommonParameterType(StrEnum): | |||
| MODEL_SELECTOR = "model-selector" | |||
| TOOLS_SELECTOR = "array[tools]" | |||
| # Dynamic select parameter | |||
| # Once you are not sure about the available options until authorization is done | |||
| # eg: Select a Slack channel from a Slack workspace | |||
| DYNAMIC_SELECT = "dynamic-select" | |||
| # TOOL_SELECTOR = "tool-selector" | |||
| @@ -35,6 +35,7 @@ class PluginParameterType(enum.StrEnum): | |||
| APP_SELECTOR = CommonParameterType.APP_SELECTOR.value | |||
| MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value | |||
| TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value | |||
| DYNAMIC_SELECT = CommonParameterType.DYNAMIC_SELECT.value | |||
| # deprecated, should not use. | |||
| SYSTEM_FILES = CommonParameterType.SYSTEM_FILES.value | |||
| @@ -1,4 +1,4 @@ | |||
| from collections.abc import Mapping | |||
| from collections.abc import Mapping, Sequence | |||
| from datetime import datetime | |||
| from enum import StrEnum | |||
| from typing import Any, Generic, Optional, TypeVar | |||
| @@ -9,6 +9,7 @@ from core.agent.plugin_entities import AgentProviderEntityWithPlugin | |||
| from core.model_runtime.entities.model_entities import AIModelEntity | |||
| from core.model_runtime.entities.provider_entities import ProviderEntity | |||
| from core.plugin.entities.base import BasePluginEntity | |||
| from core.plugin.entities.parameters import PluginParameterOption | |||
| from core.plugin.entities.plugin import PluginDeclaration, PluginEntity | |||
| from core.tools.entities.common_entities import I18nObject | |||
| from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin | |||
| @@ -186,3 +187,7 @@ class PluginOAuthCredentialsResponse(BaseModel): | |||
| class PluginListResponse(BaseModel): | |||
| list: list[PluginEntity] | |||
| total: int | |||
| class PluginDynamicSelectOptionsResponse(BaseModel): | |||
| options: Sequence[PluginParameterOption] = Field(description="The options of the dynamic select.") | |||
| @@ -0,0 +1,45 @@ | |||
| from collections.abc import Mapping | |||
| from typing import Any | |||
| from core.plugin.entities.plugin import GenericProviderID | |||
| from core.plugin.entities.plugin_daemon import PluginDynamicSelectOptionsResponse | |||
| from core.plugin.impl.base import BasePluginClient | |||
| class DynamicSelectClient(BasePluginClient): | |||
| def fetch_dynamic_select_options( | |||
| self, | |||
| tenant_id: str, | |||
| user_id: str, | |||
| plugin_id: str, | |||
| provider: str, | |||
| action: str, | |||
| credentials: Mapping[str, Any], | |||
| parameter: str, | |||
| ) -> PluginDynamicSelectOptionsResponse: | |||
| """ | |||
| Fetch dynamic select options for a plugin parameter. | |||
| """ | |||
| response = self._request_with_plugin_daemon_response_stream( | |||
| "POST", | |||
| f"plugin/{tenant_id}/dispatch/dynamic_select/fetch_parameter_options", | |||
| PluginDynamicSelectOptionsResponse, | |||
| data={ | |||
| "user_id": user_id, | |||
| "data": { | |||
| "provider": GenericProviderID(provider).provider_name, | |||
| "credentials": credentials, | |||
| "provider_action": action, | |||
| "parameter": parameter, | |||
| }, | |||
| }, | |||
| headers={ | |||
| "X-Plugin-ID": plugin_id, | |||
| "Content-Type": "application/json", | |||
| }, | |||
| ) | |||
| for options in response: | |||
| return options | |||
| raise ValueError("Plugin service returned no options") | |||
| @@ -240,6 +240,7 @@ class ToolParameter(PluginParameter): | |||
| FILES = PluginParameterType.FILES.value | |||
| APP_SELECTOR = PluginParameterType.APP_SELECTOR.value | |||
| MODEL_SELECTOR = PluginParameterType.MODEL_SELECTOR.value | |||
| DYNAMIC_SELECT = PluginParameterType.DYNAMIC_SELECT.value | |||
| # deprecated, should not use. | |||
| SYSTEM_FILES = PluginParameterType.SYSTEM_FILES.value | |||
| @@ -86,6 +86,7 @@ class ProviderConfigEncrypter(BaseModel): | |||
| cached_credentials = cache.get() | |||
| if cached_credentials: | |||
| return cached_credentials | |||
| data = self._deep_copy(data) | |||
| # get fields need to be decrypted | |||
| fields = dict[str, BasicProviderConfig]() | |||
| @@ -0,0 +1,74 @@ | |||
| from collections.abc import Mapping, Sequence | |||
| from typing import Any, Literal | |||
| from sqlalchemy.orm import Session | |||
| from core.plugin.entities.parameters import PluginParameterOption | |||
| from core.plugin.impl.dynamic_select import DynamicSelectClient | |||
| from core.tools.tool_manager import ToolManager | |||
| from core.tools.utils.configuration import ProviderConfigEncrypter | |||
| from extensions.ext_database import db | |||
| from models.tools import BuiltinToolProvider | |||
| class PluginParameterService: | |||
| @staticmethod | |||
| def get_dynamic_select_options( | |||
| tenant_id: str, | |||
| user_id: str, | |||
| plugin_id: str, | |||
| provider: str, | |||
| action: str, | |||
| parameter: str, | |||
| provider_type: Literal["tool"], | |||
| ) -> Sequence[PluginParameterOption]: | |||
| """ | |||
| Get dynamic select options for a plugin parameter. | |||
| Args: | |||
| tenant_id: The tenant ID. | |||
| plugin_id: The plugin ID. | |||
| provider: The provider name. | |||
| action: The action name. | |||
| parameter: The parameter name. | |||
| """ | |||
| credentials: Mapping[str, Any] = {} | |||
| match provider_type: | |||
| case "tool": | |||
| provider_controller = ToolManager.get_builtin_provider(provider, tenant_id) | |||
| # init tool configuration | |||
| tool_configuration = ProviderConfigEncrypter( | |||
| tenant_id=tenant_id, | |||
| config=[x.to_basic_provider_config() for x in provider_controller.get_credentials_schema()], | |||
| provider_type=provider_controller.provider_type.value, | |||
| provider_identity=provider_controller.entity.identity.name, | |||
| ) | |||
| # check if credentials are required | |||
| if not provider_controller.need_credentials: | |||
| credentials = {} | |||
| else: | |||
| # fetch credentials from db | |||
| with Session(db.engine) as session: | |||
| db_record = ( | |||
| session.query(BuiltinToolProvider) | |||
| .filter( | |||
| BuiltinToolProvider.tenant_id == tenant_id, | |||
| BuiltinToolProvider.provider == provider, | |||
| ) | |||
| .first() | |||
| ) | |||
| if db_record is None: | |||
| raise ValueError(f"Builtin provider {provider} not found when fetching credentials") | |||
| credentials = tool_configuration.decrypt(db_record.credentials) | |||
| 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) | |||
| .options | |||
| ) | |||
| @@ -1,10 +1,10 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import React, { useEffect, useState } from 'react' | |||
| import React, { useEffect, useRef, useState } from 'react' | |||
| import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions, Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react' | |||
| import { ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid' | |||
| import Badge from '../badge/index' | |||
| import { RiCheckLine } from '@remixicon/react' | |||
| import { RiCheckLine, RiLoader4Line } from '@remixicon/react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import classNames from '@/utils/classnames' | |||
| import { | |||
| @@ -51,6 +51,8 @@ export type ISelectProps = { | |||
| item: Item | |||
| selected: boolean | |||
| }) => React.ReactNode | |||
| isLoading?: boolean | |||
| onOpenChange?: (open: boolean) => void | |||
| } | |||
| const Select: FC<ISelectProps> = ({ | |||
| className, | |||
| @@ -178,17 +180,20 @@ const SimpleSelect: FC<ISelectProps> = ({ | |||
| defaultValue = 1, | |||
| disabled = false, | |||
| onSelect, | |||
| onOpenChange, | |||
| placeholder, | |||
| optionWrapClassName, | |||
| optionClassName, | |||
| hideChecked, | |||
| notClearable, | |||
| renderOption, | |||
| isLoading = false, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const localPlaceholder = placeholder || t('common.placeholder.select') | |||
| const [selectedItem, setSelectedItem] = useState<Item | null>(null) | |||
| useEffect(() => { | |||
| let defaultSelect = null | |||
| const existed = items.find((item: Item) => item.value === defaultValue) | |||
| @@ -199,8 +204,10 @@ const SimpleSelect: FC<ISelectProps> = ({ | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [defaultValue]) | |||
| const listboxRef = useRef<HTMLDivElement>(null) | |||
| return ( | |||
| <Listbox | |||
| <Listbox ref={listboxRef} | |||
| value={selectedItem} | |||
| onChange={(value: Item) => { | |||
| if (!disabled) { | |||
| @@ -212,10 +219,17 @@ const SimpleSelect: FC<ISelectProps> = ({ | |||
| <div className={classNames('group/simple-select relative h-9', wrapperClassName)}> | |||
| {renderTrigger && <ListboxButton className='w-full'>{renderTrigger(selectedItem)}</ListboxButton>} | |||
| {!renderTrigger && ( | |||
| <ListboxButton className={classNames(`flex items-center w-full h-full rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-state-base-hover-alt group-hover/simple-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}> | |||
| <ListboxButton onClick={() => { | |||
| // get data-open, use setTimeout to ensure the attribute is set | |||
| setTimeout(() => { | |||
| if (listboxRef.current) | |||
| onOpenChange?.(listboxRef.current.getAttribute('data-open') !== null) | |||
| }) | |||
| }} className={classNames(`flex items-center w-full h-full rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-state-base-hover-alt group-hover/simple-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}> | |||
| <span className={classNames('block truncate text-left system-sm-regular text-components-input-text-filled', !selectedItem?.name && 'text-components-input-text-placeholder')}>{selectedItem?.name ?? localPlaceholder}</span> | |||
| <span className="absolute inset-y-0 right-0 flex items-center pr-2"> | |||
| {(selectedItem && !notClearable) | |||
| {isLoading ? <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' /> | |||
| : (selectedItem && !notClearable) | |||
| ? ( | |||
| <XMarkIcon | |||
| onClick={(e) => { | |||
| @@ -237,7 +251,7 @@ const SimpleSelect: FC<ISelectProps> = ({ | |||
| </ListboxButton> | |||
| )} | |||
| {!disabled && ( | |||
| {(!disabled) && ( | |||
| <ListboxOptions className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-xl bg-components-panel-bg-blur backdrop-blur-sm py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}> | |||
| {items.map((item: Item) => ( | |||
| <ListboxOption | |||
| @@ -19,12 +19,14 @@ export enum FormTypeEnum { | |||
| toolSelector = 'tool-selector', | |||
| multiToolSelector = 'array[tools]', | |||
| appSelector = 'app-selector', | |||
| dynamicSelect = 'dynamic-select', | |||
| } | |||
| export type FormOption = { | |||
| label: TypeWithI18N | |||
| value: string | |||
| show_on: FormShowOnObject[] | |||
| icon?: string | |||
| } | |||
| export enum ModelTypeEnum { | |||
| @@ -13,6 +13,8 @@ type Props = { | |||
| readonly: boolean | |||
| value: string | |||
| onChange: (value: string | number, varKindType: VarKindType, varInfo?: Var) => void | |||
| onOpenChange?: (open: boolean) => void | |||
| isLoading?: boolean | |||
| } | |||
| const DEFAULT_SCHEMA = {} as CredentialFormSchema | |||
| @@ -22,6 +24,8 @@ const ConstantField: FC<Props> = ({ | |||
| readonly, | |||
| value, | |||
| onChange, | |||
| onOpenChange, | |||
| isLoading, | |||
| }) => { | |||
| const language = useLanguage() | |||
| const placeholder = (schema as CredentialFormSchemaSelect).placeholder | |||
| @@ -36,7 +40,7 @@ const ConstantField: FC<Props> = ({ | |||
| return ( | |||
| <> | |||
| {schema.type === FormTypeEnum.select && ( | |||
| {(schema.type === FormTypeEnum.select || schema.type === FormTypeEnum.dynamicSelect) && ( | |||
| <SimpleSelect | |||
| wrapperClassName='w-full !h-8' | |||
| className='flex items-center' | |||
| @@ -45,6 +49,8 @@ const ConstantField: FC<Props> = ({ | |||
| items={(schema as CredentialFormSchemaSelect).options.map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))} | |||
| onSelect={item => handleSelectChange(item.value)} | |||
| placeholder={placeholder?.[language] || placeholder?.en_US} | |||
| onOpenChange={onOpenChange} | |||
| isLoading={isLoading} | |||
| /> | |||
| )} | |||
| {schema.type === FormTypeEnum.textNumber && ( | |||
| @@ -6,6 +6,7 @@ import { | |||
| RiArrowDownSLine, | |||
| RiCloseLine, | |||
| RiErrorWarningFill, | |||
| RiLoader4Line, | |||
| RiMoreLine, | |||
| } from '@remixicon/react' | |||
| import produce from 'immer' | |||
| @@ -16,8 +17,9 @@ import VarReferencePopup from './var-reference-popup' | |||
| import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils' | |||
| import ConstantField from './constant-field' | |||
| import cn from '@/utils/classnames' | |||
| import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' | |||
| import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import type { Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' | |||
| import type { CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { BlockEnum } from '@/app/components/workflow/types' | |||
| import { VarBlockIcon } from '@/app/components/workflow/block-icon' | |||
| import { Line3 } from '@/app/components/base/icons/src/public/common' | |||
| @@ -40,6 +42,8 @@ import Tooltip from '@/app/components/base/tooltip' | |||
| import { isExceptionVariable } from '@/app/components/workflow/utils' | |||
| import VarFullPathPanel from './var-full-path-panel' | |||
| import { noop } from 'lodash-es' | |||
| import { useFetchDynamicOptions } from '@/service/use-plugins' | |||
| import type { Tool } from '@/app/components/tools/types' | |||
| const TRIGGER_DEFAULT_WIDTH = 227 | |||
| @@ -68,6 +72,8 @@ type Props = { | |||
| minWidth?: number | |||
| popupFor?: 'assigned' | 'toAssigned' | |||
| zIndex?: number | |||
| currentTool?: Tool | |||
| currentProvider?: ToolWithProvider | |||
| } | |||
| const DEFAULT_VALUE_SELECTOR: Props['value'] = [] | |||
| @@ -97,6 +103,8 @@ const VarReferencePicker: FC<Props> = ({ | |||
| minWidth, | |||
| popupFor, | |||
| zIndex, | |||
| currentTool, | |||
| currentProvider, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const store = useStoreApi() | |||
| @@ -316,6 +324,42 @@ const VarReferencePicker: FC<Props> = ({ | |||
| return null | |||
| }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type]) | |||
| const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null) | |||
| const [isLoading, setIsLoading] = useState(false) | |||
| const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions( | |||
| currentProvider?.plugin_id || '', currentProvider?.name || '', currentTool?.name || '', (schema as CredentialFormSchemaSelect)?.variable || '', | |||
| 'tool', | |||
| ) | |||
| const handleFetchDynamicOptions = async () => { | |||
| if (schema?.type !== FormTypeEnum.dynamicSelect || !currentTool || !currentProvider) | |||
| return | |||
| setIsLoading(true) | |||
| try { | |||
| const data = await fetchDynamicOptions() | |||
| setDynamicOptions(data?.options || []) | |||
| } | |||
| finally { | |||
| setIsLoading(false) | |||
| } | |||
| } | |||
| useEffect(() => { | |||
| handleFetchDynamicOptions() | |||
| }, [currentTool, currentProvider, schema]) | |||
| const schemaWithDynamicSelect = useMemo(() => { | |||
| if (schema?.type !== FormTypeEnum.dynamicSelect) | |||
| return schema | |||
| // rewrite schema.options with dynamicOptions | |||
| if (dynamicOptions) { | |||
| return { | |||
| ...schema, | |||
| options: dynamicOptions, | |||
| } | |||
| } | |||
| return schema | |||
| }, [dynamicOptions]) | |||
| return ( | |||
| <div className={cn(className, !readonly && 'cursor-pointer')}> | |||
| <PortalToFollowElem | |||
| @@ -366,8 +410,9 @@ const VarReferencePicker: FC<Props> = ({ | |||
| <ConstantField | |||
| value={value as string} | |||
| onChange={onChange as ((value: string | number, varKindType: VarKindType, varInfo?: Var) => void)} | |||
| schema={schema as CredentialFormSchema} | |||
| schema={schemaWithDynamicSelect as CredentialFormSchema} | |||
| readonly={readonly} | |||
| isLoading={isLoading} | |||
| /> | |||
| ) | |||
| : ( | |||
| @@ -412,6 +457,7 @@ const VarReferencePicker: FC<Props> = ({ | |||
| )} | |||
| <div className='flex items-center text-text-accent'> | |||
| {!hasValue && <Variable02 className='h-3.5 w-3.5' />} | |||
| {isLoading && <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' />} | |||
| {isEnv && <Env className='h-3.5 w-3.5 text-util-colors-violet-violet-600' />} | |||
| {isChatVar && <BubbleX className='h-3.5 w-3.5 text-util-colors-teal-teal-700' />} | |||
| <div className={cn('ml-0.5 truncate text-xs font-medium', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning')} title={varName} style={{ | |||
| @@ -424,7 +470,16 @@ const VarReferencePicker: FC<Props> = ({ | |||
| {!isValidVar && <RiErrorWarningFill className='ml-0.5 h-3 w-3 text-text-destructive' />} | |||
| </> | |||
| ) | |||
| : <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</div>} | |||
| : <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}> | |||
| {isLoading ? ( | |||
| <div className='flex items-center'> | |||
| <RiLoader4Line className='mr-1 h-3.5 w-3.5 animate-spin text-text-secondary' /> | |||
| <span>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</span> | |||
| </div> | |||
| ) : ( | |||
| placeholder ?? t('workflow.common.setVarValuePlaceholder') | |||
| )} | |||
| </div>} | |||
| </div> | |||
| </Tooltip> | |||
| </div> | |||
| @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' | |||
| import type { ToolVarInputs } from '../types' | |||
| import { VarType as VarKindType } from '../types' | |||
| import cn from '@/utils/classnames' | |||
| import type { ValueSelector, Var } from '@/app/components/workflow/types' | |||
| import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' | |||
| import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' | |||
| @@ -17,6 +17,7 @@ import { VarType } from '@/app/components/workflow/types' | |||
| import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' | |||
| import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' | |||
| import { noop } from 'lodash-es' | |||
| import type { Tool } from '@/app/components/tools/types' | |||
| type Props = { | |||
| readOnly: boolean | |||
| @@ -27,6 +28,8 @@ type Props = { | |||
| onOpen?: (index: number) => void | |||
| isSupportConstantValue?: boolean | |||
| filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean | |||
| currentTool?: Tool | |||
| currentProvider?: ToolWithProvider | |||
| } | |||
| const InputVarList: FC<Props> = ({ | |||
| @@ -38,6 +41,8 @@ const InputVarList: FC<Props> = ({ | |||
| onOpen = noop, | |||
| isSupportConstantValue, | |||
| filterVar, | |||
| currentTool, | |||
| currentProvider, | |||
| }) => { | |||
| const language = useLanguage() | |||
| const { t } = useTranslation() | |||
| @@ -58,6 +63,8 @@ const InputVarList: FC<Props> = ({ | |||
| return 'ModelSelector' | |||
| else if (type === FormTypeEnum.toolSelector) | |||
| return 'ToolSelector' | |||
| else if (type === FormTypeEnum.dynamicSelect || type === FormTypeEnum.select) | |||
| return 'Select' | |||
| else | |||
| return 'String' | |||
| } | |||
| @@ -149,6 +156,7 @@ const InputVarList: FC<Props> = ({ | |||
| const handleOpen = useCallback((index: number) => { | |||
| return () => onOpen(index) | |||
| }, [onOpen]) | |||
| return ( | |||
| <div className='space-y-3'> | |||
| { | |||
| @@ -163,7 +171,8 @@ const InputVarList: FC<Props> = ({ | |||
| } = schema | |||
| const varInput = value[variable] | |||
| const isNumber = type === FormTypeEnum.textNumber | |||
| const isSelect = type === FormTypeEnum.select | |||
| const isDynamicSelect = type === FormTypeEnum.dynamicSelect | |||
| const isSelect = type === FormTypeEnum.select || type === FormTypeEnum.dynamicSelect | |||
| const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files | |||
| const isAppSelector = type === FormTypeEnum.appSelector | |||
| const isModelSelector = type === FormTypeEnum.modelSelector | |||
| @@ -198,11 +207,13 @@ const InputVarList: FC<Props> = ({ | |||
| value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])} | |||
| onChange={handleNotMixedTypeChange(variable)} | |||
| onOpen={handleOpen(index)} | |||
| defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)} | |||
| defaultVarKindType={varInput?.type || ((isNumber || isDynamicSelect) ? VarKindType.constant : VarKindType.variable)} | |||
| isSupportConstantValue={isSupportConstantValue} | |||
| filterVar={isNumber ? filterVar : undefined} | |||
| availableVars={isSelect ? availableVars : undefined} | |||
| schema={schema} | |||
| currentTool={currentTool} | |||
| currentProvider={currentProvider} | |||
| /> | |||
| )} | |||
| {isFile && ( | |||
| @@ -42,6 +42,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ | |||
| isLoading, | |||
| outputSchema, | |||
| hasObjectOutput, | |||
| currTool, | |||
| } = useConfig(id, data) | |||
| if (isLoading) { | |||
| @@ -80,6 +81,8 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ | |||
| filterVar={filterVar} | |||
| isSupportConstantValue | |||
| onOpen={handleOnVarOpen} | |||
| currentProvider={currCollection} | |||
| currentTool={currTool} | |||
| /> | |||
| </Field> | |||
| )} | |||
| @@ -1,5 +1,6 @@ | |||
| import { useCallback, useEffect } from 'react' | |||
| import type { | |||
| FormOption, | |||
| ModelProvider, | |||
| } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { fetchModelProviderModelList } from '@/service/common' | |||
| @@ -477,7 +478,7 @@ export const usePluginTaskList = (category?: PluginType) => { | |||
| refreshPluginList(category ? { category } as any : undefined, !category) | |||
| } | |||
| } | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [isRefetching]) | |||
| const handleRefetch = useCallback(() => { | |||
| @@ -571,3 +572,17 @@ export const usePluginInfo = (providerName?: string) => { | |||
| enabled: !!providerName, | |||
| }) | |||
| } | |||
| export const useFetchDynamicOptions = (plugin_id: string, provider: string, action: string, parameter: string, provider_type: 'tool') => { | |||
| return useMutation({ | |||
| mutationFn: () => get<{ options: FormOption[] }>('/workspaces/current/plugin/parameters/dynamic-options', { | |||
| params: { | |||
| plugin_id, | |||
| provider, | |||
| action, | |||
| parameter, | |||
| provider_type, | |||
| }, | |||
| }), | |||
| }) | |||
| } | |||