diff --git a/web/app/components/base/install-button/index.tsx b/web/app/components/base/install-button/index.tsx new file mode 100644 index 00000000000000..f9ad238fb24130 --- /dev/null +++ b/web/app/components/base/install-button/index.tsx @@ -0,0 +1,27 @@ +import Button from '../button' +import { RiInstallLine, RiLoader2Line } from '@remixicon/react' + +type InstallButtonProps = { + loading: boolean + onInstall: () => void + t: any +} + +const InstallButton = ({ loading, onInstall, t }: InstallButtonProps) => { + return ( + + ) +} + +export default InstallButton diff --git a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx index d8af591904d301..9c81d9967594e6 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx @@ -4,7 +4,7 @@ import type { ModelProvider, } from '../declarations' import { useLanguage } from '../hooks' -import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes' +import { Group } from '@/app/components/base/icons/src/vender/other' import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm' import cn from '@/utils/classnames' @@ -41,10 +41,12 @@ const ModelIcon: FC = ({ return (
- +
+ +
) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx index 4e03264ffded13..68cdf2230ee8bd 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx @@ -1,4 +1,5 @@ import type { FC } from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import type { CustomConfigurationModelFixedFields, @@ -10,20 +11,24 @@ import { CustomConfigurationStatusEnum, } from '../declarations' import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from '../provider-added-card' -import { ModelStatusEnum } from '../declarations' +import type { PluginInfoFromMarketPlace } from '@/app/components/plugins/types' +import { useInstallPackageFromMarketPlace } from '@/service/use-plugins' +import ConfigurationButton from './configuration-button' +import { PluginType } from '@/app/components/plugins/types' import { useUpdateModelList, useUpdateModelProviders, } from '../hooks' import ModelIcon from '../model-icon' -import ModelName from '../model-name' -import Button from '@/app/components/base/button' +import ModelDisplay from './model-display' +import InstallButton from '@/app/components/base/install-button' +import StatusIndicators from './status-indicators' import cn from '@/utils/classnames' import { useProviderContext } from '@/context/provider-context' import { useModalContextSelector } from '@/context/modal-context' import { useEventEmitterContextContext } from '@/context/event-emitter' -import Tooltip from '@/app/components/base/tooltip' -import { RiEqualizer2Line, RiErrorWarningFill } from '@remixicon/react' +import { RiEqualizer2Line } from '@remixicon/react' +import { fetchPluginInfoFromMarketPlace } from '@/service/plugins' export type AgentModelTriggerProps = { open?: boolean @@ -56,6 +61,36 @@ const AgentModelTrigger: FC = ({ item => item.quota_type === modelProvider.system_configuration.current_quota_type, ) ) + const [pluginInfo, setPluginInfo] = useState(null) + const [isPluginChecked, setIsPluginChecked] = useState(false) + const [loading, setLoading] = useState(false) + const [installed, setInstalled] = useState(false) + const { mutateAsync: installPackageFromMarketPlace } = useInstallPackageFromMarketPlace() + + useEffect(() => { + (async () => { + if (providerName && !modelProvider) { + const parts = providerName.split('/') + const org = parts[0] + const name = parts[1] + try { + const pluginInfo = await fetchPluginInfoFromMarketPlace({ org, name }) + if (pluginInfo.data.plugin.category === PluginType.model) + setPluginInfo(pluginInfo.data.plugin) + } + catch (error) { + // pass + } + setIsPluginChecked(true) + } + else { + setIsPluginChecked(true) + } + })() + }, [providerName, modelProvider]) + + if (modelId && !isPluginChecked) + return null const handleOpenModal = ( provider: ModelProvider, @@ -97,64 +132,41 @@ const AgentModelTrigger: FC = ({ > {modelId ? ( <> - {currentProvider && ( - - )} - {!currentProvider && ( - - )} - {currentModel && ( - + + {needsConfiguration && ( + )} - {!currentModel && ( -
-
- {modelId} -
-
- )} - {needsConfiguration && ( - + t={t} + /> )} - {!needsConfiguration && disabled && ( - - - - ) - } ) : ( <> @@ -168,11 +180,6 @@ const AgentModelTrigger: FC = ({ )} - {currentProvider && currentModel && currentModel.status === ModelStatusEnum.active && ( -
- -
- )} ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/configuration-button.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/configuration-button.tsx new file mode 100644 index 00000000000000..0cc0f1be8ef6fb --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/configuration-button.tsx @@ -0,0 +1,32 @@ +import Button from '@/app/components/base/button' +import { ConfigurationMethodEnum } from '../declarations' +import { useTranslation } from 'react-i18next' + +type ConfigurationButtonProps = { + modelProvider: any + handleOpenModal: any +} + +const ConfigurationButton = ({ modelProvider, handleOpenModal }: ConfigurationButtonProps) => { + const { t } = useTranslation() + return ( + + ) +} + +export default ConfigurationButton diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx new file mode 100644 index 00000000000000..6f586c1f6fd978 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx @@ -0,0 +1,25 @@ +import ModelName from '../model-name' + +type ModelDisplayProps = { + currentModel: any + modelId: string +} + +const ModelDisplay = ({ currentModel, modelId }: ModelDisplayProps) => { + return currentModel ? ( + + ) : ( +
+
+ {modelId} +
+
+ ) +} + +export default ModelDisplay diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.tsx new file mode 100644 index 00000000000000..2f7ee0e5f7710e --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.tsx @@ -0,0 +1,44 @@ +import Tooltip from '@/app/components/base/tooltip' +import { RiErrorWarningFill } from '@remixicon/react' + +type StatusIndicatorsProps = { + needsConfiguration: boolean + modelProvider: boolean + disabled: boolean + pluginInfo: any + t: any +} + +const StatusIndicators = ({ needsConfiguration, modelProvider, disabled, pluginInfo, t }: StatusIndicatorsProps) => { + return ( + <> + {!needsConfiguration && modelProvider && disabled && ( + + + + )} + {!modelProvider && !pluginInfo && ( + +
{t('workflow.nodes.agent.modelNotInMarketplace.title')}
+
+ {t('workflow.nodes.agent.modelNotInMarketplace.desc')} +
+
{t('workflow.nodes.agent.modelNotInMarketplace.manageInPlugins')}
+ + } + asChild={false} + needsDelay + > + +
+ )} + + ) +} + +export default StatusIndicators diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index 57aa24bf848d32..4d20c0877d923e 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -7,7 +7,6 @@ import ActionList from './action-list' import ModelList from './model-list' import AgentStrategyList from './agent-strategy-list' import Drawer from '@/app/components/base/drawer' -import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' import type { PluginDetail } from '@/app/components/plugins/types' import cn from '@/utils/classnames' @@ -28,12 +27,6 @@ const PluginDetailPanel: FC = ({ onUpdate() } - const [value, setValue] = React.useState(undefined) - const testChange = (val: any) => { - console.log('tool change', val) - setValue(val) - } - if (!detail) return null @@ -59,15 +52,6 @@ const PluginDetailPanel: FC = ({ {!!detail.declaration.agent_strategy && } {!!detail.declaration.endpoint && } {!!detail.declaration.model && } - {false && ( -
- -
- )} )} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/hooks.ts b/web/app/components/plugins/plugin-detail-panel/tool-selector/hooks.ts new file mode 100644 index 00000000000000..57c1fbd7c3ceb4 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/hooks.ts @@ -0,0 +1,14 @@ +import { + usePluginManifestInfo, +} from '@/service/use-plugins' + +export const usePluginInstalledCheck = (providerName = '') => { + const pluginID = providerName?.split('/').splice(0, 2).join('/') + + const { data: manifest } = usePluginManifestInfo(pluginID) + + return { + inMarketPlace: !!manifest, + manifest: manifest?.data.plugin, + } +} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index d21a0a1ded18d0..a5b96e4eeaf660 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -32,12 +32,15 @@ import { useInvalidateAllBuiltInTools, useUpdateProviderCredentials, } from '@/service/use-tools' +import { useInstallPackageFromMarketPlace } from '@/service/use-plugins' +import { usePluginInstalledCheck } from '@/app/components/plugins/plugin-detail-panel/tool-selector/hooks' import { CollectionType } from '@/app/components/tools/types' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' import type { OffsetOptions, Placement, } from '@floating-ui/react' +import { MARKETPLACE_API_PREFIX } from '@/config' import cn from '@/utils/classnames' export type ToolValue = { @@ -92,6 +95,9 @@ const ToolSelector: FC = ({ const { data: workflowTools } = useAllWorkflowTools() const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools() + // plugin info check + const { inMarketPlace, manifest } = usePluginInstalledCheck(value?.provider_name) + const currentProvider = useMemo(() => { const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] return mergedTools.find((toolWithProvider) => { @@ -164,6 +170,25 @@ const ToolSelector: FC = ({ onSuccess: handleCredentialSettingUpdate, }) + // install from marketplace + const { mutateAsync: installPackageFromMarketPlace, isPending } = useInstallPackageFromMarketPlace() + const manifestIcon = useMemo(() => { + if (!manifest) + return '' + return `${MARKETPLACE_API_PREFIX}/plugins/${(manifest as any).plugin_id}/icon` + }, [manifest]) + const handleInstall = async () => { + if (!manifest) + return + try { + await installPackageFromMarketPlace(manifest.latest_package_identifier) + invalidateAllBuiltinTools() + } + catch (e: any) { + Toast.notify({ type: 'error', message: `${e.message || e}` }) + } + } + return ( <> = ({ {!trigger && value?.provider_name && ( = ({ onDelete={onDelete} noAuth={currentProvider && !currentProvider.is_team_authorization} onAuth={() => setShowSettingAuth(true)} - // uninstalled TODO - // isError TODO - errorTip={
-

{t('workflow.nodes.agent.pluginNotInstalled')}

-

{t('workflow.nodes.agent.pluginNotInstalledDesc')}

+ uninstalled={!currentProvider && inMarketPlace} + isInstalling={isPending} + onInstall={() => handleInstall()} + isError={!currentProvider && !inMarketPlace} + errorTip={
+

{t('plugin.detailPanel.toolSelector.uninstalledTitle')}

+

{t('plugin.detailPanel.toolSelector.uninstalledContent')}

- {t('workflow.nodes.agent.linkToPlugin')} + {t('plugin.detailPanel.toolSelector.uninstalledLink')}

} /> diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx index f2ff56d07bd71f..c393d70a2513ba 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx @@ -6,6 +6,7 @@ import { RiEqualizer2Line, RiErrorWarningFill, } from '@remixicon/react' +import { Group } from '@/app/components/base/icons/src/vender/other' import AppIcon from '@/app/components/base/app-icon' import Switch from '@/app/components/base/switch' import Button from '@/app/components/base/button' @@ -61,10 +62,21 @@ const ToolItem = ({ open && 'bg-components-panel-on-panel-item-bg-hover shadow-sm', isDeleting && 'hover:bg-state-destructive-hover border-state-destructive-border shadow-xs', )}> -
- {typeof icon === 'string' &&
} - {typeof icon !== 'string' && } -
+ {icon && ( +
+ {typeof icon === 'string' &&
} + {typeof icon !== 'string' && } +
+ )} + {!icon && ( +
+
+ +
+
+ )}
{providerNameText}
{toolName}
diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index d36ba4109a8ced..18282b730d01b9 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -81,7 +81,7 @@ export type PluginManifestInMarket = { icon: string label: Record category: PluginType - version: string // conbine the other place to it + version: string // combine the other place to it latest_version: string brief: Record introduction: string @@ -108,6 +108,11 @@ export type PluginDetail = { meta?: MetaData } +export type PluginInfoFromMarketPlace = { + category: PluginType + latest_package_identifier: string +} + export type Plugin = { type: 'plugin' | 'bundle' | 'model' | 'extension' | 'tool' | 'agent_strategy' org: string diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index 4b38111b1d82f2..638f5b7259aec2 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -83,7 +83,6 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => { }, [query, list]) // TODO: should be replaced by real data const isExternalInstalled = true - // TODO: 验证这玩意写对了没 const icon = list?.find( coll => coll.tools?.find(tool => tool.name === value?.agent_strategy_name), )?.icon as string | undefined @@ -125,9 +124,10 @@ export const AgentStrategySelector = (props: AgentStrategySelectorProps) => { onChange({ agent_strategy_name: tool!.tool_name, agent_strategy_provider_name: tool!.provider_name, - agent_parameters: tool!.params, agent_strategy_label: tool!.tool_label, agent_output_schema: tool!.output_schema, + agent_configurations: {}, + agent_parameters: {}, }) setOpen(false) }} diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index ce0613dedf1f05..8f10d91f9e289e 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -19,7 +19,8 @@ export type Strategy = { agent_strategy_provider_name: string agent_strategy_name: string agent_strategy_label: string - agent_parameters?: ToolVarInputs + agent_configurations?: Record + agent_parameters?: Record agent_output_schema: Record } diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index aca1a75f5d77cf..893472af5e3cd0 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -1,30 +1,40 @@ import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' import classNames from '@/utils/classnames' -import { useRef } from 'react' +import { useMemo, useRef } from 'react' +import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools' export type ToolIconProps = { - src: string - alt?: string status?: 'error' | 'warning' tooltip?: string + providerName: string } -export const ToolIcon = ({ src, status, tooltip, alt }: ToolIconProps) => { +export const ToolIcon = ({ status, tooltip, providerName }: ToolIconProps) => { const indicator = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined const containerRef = useRef(null) const notSuccess = (['error', 'warning'] as Array).includes(status) + const { data: buildInTools } = useAllBuiltInTools() + const { data: customTools } = useAllCustomTools() + const { data: workflowTools } = useAllWorkflowTools() + const currentProvider = useMemo(() => { + const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] + return mergedTools.find((toolWithProvider) => { + return toolWithProvider.name === providerName + }) + }, [providerName, buildInTools, customTools, workflowTools]) return
+ {/* eslint-disable-next-line @next/next/no-img-element */} {alt} diff --git a/web/app/components/workflow/nodes/agent/node.tsx b/web/app/components/workflow/nodes/agent/node.tsx index 626f38b11f03a9..fa0513229528f0 100644 --- a/web/app/components/workflow/nodes/agent/node.tsx +++ b/web/app/components/workflow/nodes/agent/node.tsx @@ -8,14 +8,11 @@ import type { ToolIconProps } from './components/tool-icon' import { ToolIcon } from './components/tool-icon' import useConfig from './use-config' import { useTranslation } from 'react-i18next' -import { useInstalledPluginList } from '@/service/use-plugins' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' const AgentNode: FC> = (props) => { const { inputs, currentStrategy } = useConfig(props.id, props.data) const { t } = useTranslation() - const pluginList = useInstalledPluginList() - // TODO: Implement models const models = useMemo(() => { if (!inputs) return [] // if selected, show in node @@ -24,7 +21,7 @@ const AgentNode: FC> = (props) => { const models = currentStrategy?.parameters .filter(param => param.type === FormTypeEnum.modelSelector) .reduce((acc, param) => { - const item = inputs.agent_parameters?.[param.name] + const item = inputs.agent_configurations?.[param.name] if (!item) { if (param.required) { acc.push({ param: param.name }) @@ -41,18 +38,29 @@ const AgentNode: FC> = (props) => { const tools = useMemo(() => { const tools: Array = [] currentStrategy?.parameters.forEach((param) => { - if (['array[tool]', 'tool'].includes(param.type)) { - const vari = inputs.agent_parameters?.[param.name] - if (!vari) return - if (Array.isArray(vari.value)) { - // TODO: Implement array of tools + if (param.type === FormTypeEnum.toolSelector) { + const field = param.name + const value = inputs.agent_configurations?.[field] + if (value) { + tools.push({ + providerName: value.provider_name as any, + }) } - else { - // TODO: Implement single tool + } + if (param.type === FormTypeEnum.multiToolSelector) { + const field = param.name + const value = inputs.agent_configurations?.[field] + if (value) { + (value as unknown as any[]).forEach((item) => { + tools.push({ + providerName: item.provider_name, + }) + }) } } }) - }, [currentStrategy, inputs.agent_parameters]) + return tools + }, [currentStrategy?.parameters, inputs.agent_configurations]) return
{inputs.agent_strategy_name ? > = (props) => { {inputs.agent_strategy_label} : } - {models.length && 0 && {t('workflow.nodes.agent.model')} } @@ -85,25 +93,13 @@ const AgentNode: FC> = (props) => { /> })} } - + {tools.length > 0 && {t('workflow.nodes.agent.toolbox')} }>
- - - + {tools.map(tool => )}
-
+
}
} diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 4f1d2089489d9c..af4bf9dc88a74d 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -28,25 +28,27 @@ const AgentPanel: FC> = (props) => { strategy={inputs.agent_strategy_name ? { agent_strategy_provider_name: inputs.agent_strategy_provider_name!, agent_strategy_name: inputs.agent_strategy_name!, - agent_parameters: inputs.agent_parameters, + agent_configurations: inputs.agent_configurations, agent_strategy_label: inputs.agent_strategy_label!, agent_output_schema: inputs.output_schema, + agent_parameters: inputs.agent_parameters, } : undefined} onStrategyChange={(strategy) => { setInputs({ ...inputs, agent_strategy_provider_name: strategy?.agent_strategy_provider_name, agent_strategy_name: strategy?.agent_strategy_name, + agent_configurations: strategy?.agent_configurations, agent_parameters: strategy?.agent_parameters, agent_strategy_label: strategy?.agent_strategy_label, output_schema: strategy!.agent_output_schema, }) }} formSchema={currentStrategy?.parameters?.map(strategyParamToCredientialForm) || []} - formValue={inputs.agent_parameters || {}} + formValue={inputs.agent_configurations || {}} onFormValueChange={value => setInputs({ ...inputs, - agent_parameters: value, + agent_configurations: value, })} /> diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index c3351c1d9d21a8..66e576cb9c53e2 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -5,7 +5,7 @@ export type AgentNodeType = CommonNodeType & { agent_strategy_provider_name?: string agent_strategy_name?: string agent_strategy_label?: string - agent_parameters?: Record - agent_configurations?: Record + agent_parameters?: Record + agent_configurations?: Record output_schema: Record } diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index a706f8f0420cea..f3807baf53be10 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -74,6 +74,9 @@ const translation = { auth: 'AUTHORIZATION', settings: 'TOOL SETTINGS', empty: 'Click the \'+\' button to add tools. You can add multiple tools.', + uninstalledTitle: 'Tool not installed', + uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.', + uninstalledLink: 'Manage in Plugins', }, configureApp: 'Configure App', configureModel: 'Configure model', diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index f58bc3fdd03422..64ea5b93171cd6 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -714,6 +714,11 @@ const translation = { install: 'Install', installing: 'Installing', }, + modelNotInMarketplace: { + title: 'Model not installed', + desc: 'This model is not installed from the marketplace. Please go to Plugins to reinstall.', + manageInPlugins: 'Manage in Plugins', + }, configureModel: 'Configure Model', notAuthorized: 'Not Authorized', model: 'model', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index 4b7c764d9f18eb..f1eb6e954117d3 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -74,6 +74,9 @@ const translation = { auth: '授权', settings: '工具设置', empty: '点击 "+" 按钮添加工具。您可以添加多个工具。', + uninstalledTitle: '工具未安装', + uninstalledContent: '此插件安装自 本地 / GitHub 仓库,请安装后使用。', + uninstalledLink: '在插件中管理', }, configureApp: '应用设置', configureModel: '模型设置', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 82c92dc983608c..fce1fd74b2ec1e 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -714,6 +714,11 @@ const translation = { install: '安装', installing: '安装中', }, + modelNotInMarketplace: { + title: '模型未安装', + desc: '此模型未从市场安装。请转到插件重新安装。', + manageInPlugins: '在插件中管理', + }, model: '模型', toolbox: '工具箱', strategyNotSet: '代理策略未设置', diff --git a/web/service/plugins.ts b/web/service/plugins.ts index 28579488609df7..0a880b865fd432 100644 --- a/web/service/plugins.ts +++ b/web/service/plugins.ts @@ -5,6 +5,7 @@ import type { InstallPackageResponse, Permissions, PluginDeclaration, + PluginInfoFromMarketPlace, PluginManifestInMarket, PluginTasksResponse, TaskStatusResponse, @@ -75,6 +76,13 @@ export const fetchBundleInfoFromMarketPlace = async ({ return getMarketplace<{ data: { version: { dependencies: Dependency[] } } }>(`/bundles/${org}/${name}/${version}`) } +export const fetchPluginInfoFromMarketPlace = async ({ + org, + name, +}: Record) => { + return getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${org}/${name}`) +} + export const fetchMarketplaceCollections: Fetcher = ({ url }) => { return get(url) } diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index a901d98c8e4ef4..5ad7d831d938ec 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -9,6 +9,7 @@ import type { Permissions, Plugin, PluginDetail, + PluginInfoFromMarketPlace, PluginTask, PluginsFromMarketplaceByInfoResponse, PluginsFromMarketplaceResponse, @@ -91,6 +92,7 @@ export const useUpdatePackageFromMarketPlace = () => { export const useVersionListOfPlugin = (pluginID: string) => { return useQuery<{ data: VersionListResponse }>({ + enabled: !!pluginID, queryKey: [NAME_SPACE, 'versions', pluginID], queryFn: () => getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, { params: { page: 1, page_size: 100 } }), }) @@ -399,6 +401,15 @@ export const useMutationClearAllTaskPlugin = () => { }) } +export const usePluginManifestInfo = (pluginUID: string) => { + return useQuery({ + enabled: !!pluginUID, + queryKey: [[NAME_SPACE, 'manifest', pluginUID]], + queryFn: () => getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${pluginUID}`), + retry: 0, + }) +} + export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => { return useQuery({ queryKey: [NAME_SPACE, 'downloadPlugin', info],