From ba16cbccf0f86924a31e523ee262cab1f6e1eb3b Mon Sep 17 00:00:00 2001 From: Yi Date: Mon, 30 Dec 2024 12:55:28 +0800 Subject: [PATCH] feat: add install options for model configuration --- .../components/base/install-button/index.tsx | 27 ++++ .../model-provider-page/model-icon/index.tsx | 8 +- .../agent-model-trigger.tsx | 139 +++++++++--------- .../configuration-button.tsx | 32 ++++ .../model-parameter-modal/model-display.tsx | 25 ++++ .../status-indicators.tsx | 44 ++++++ web/app/components/plugins/types.ts | 7 +- web/i18n/en-US/workflow.ts | 5 + web/i18n/zh-Hans/workflow.ts | 5 + web/service/plugins.ts | 8 + 10 files changed, 230 insertions(+), 70 deletions(-) create mode 100644 web/app/components/base/install-button/index.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-parameter-modal/configuration-button.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx create mode 100644 web/app/components/header/account-setting/model-provider-page/model-parameter-modal/status-indicators.tsx 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 0000000000..f9ad238fb2 --- /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 d8af591904..9c81d99675 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 4e03264ffd..68cdf2230e 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 && ( - - )} - {!currentModel && ( -
-
- {modelId} -
-
- )} + + {needsConfiguration && ( - + + )} + + {!installed && !modelProvider && pluginInfo && ( + { + setLoading(true) + const { all_installed } = await installPackageFromMarketPlace(pluginInfo.latest_package_identifier) + if (all_installed) + setInstalled(true) + }} + 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 0000000000..0cc0f1be8e --- /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 0000000000..6f586c1f6f --- /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 0000000000..2f7ee0e5f7 --- /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/types.ts b/web/app/components/plugins/types.ts index d36ba4109a..18282b730d 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/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index f58bc3fdd0..64ea5b9317 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/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 82c92dc983..fce1fd74b2 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 2857948860..0a880b865f 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) }