diff --git a/web/app/(commonLayout)/plugins/test/other/page.tsx b/web/app/(commonLayout)/plugins/test/other/page.tsx deleted file mode 100644 index 3166e34ba3796..0000000000000 --- a/web/app/(commonLayout)/plugins/test/other/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client' -import { useBoolean } from 'ahooks' -import UpdatePlugin from '@/app/components/plugins/update-plugin' - -const Page = () => { - const [isShowUpdateModal, { - setTrue: showUpdateModal, - setFalse: hideUpdateModal, - }] = useBoolean(false) - return ( -
-
Show Upgrade
- {isShowUpdateModal && ( - - )} -
- ) -} - -export default Page diff --git a/web/app/components/plugins/card/base/card-icon.tsx b/web/app/components/plugins/card/base/card-icon.tsx index 34ff0d8fbc323..3587f3fd0dc81 100644 --- a/web/app/components/plugins/card/base/card-icon.tsx +++ b/web/app/components/plugins/card/base/card-icon.tsx @@ -2,12 +2,19 @@ import { RiCheckLine, RiCloseLine } from '@remixicon/react' import AppIcon from '@/app/components/base/app-icon' import cn from '@/utils/classnames' +const iconSizeMap = { + xs: 'w-4 h-4 text-base', + tiny: 'w-6 h-6 text-base', + small: 'w-8 h-8', + medium: 'w-9 h-9', + large: 'w-10 h-10', +} const Icon = ({ className, src, installed = false, installFailed = false, - size, + size = 'large', }: { className?: string src: string | { @@ -23,7 +30,7 @@ const Icon = ({ return (
) } + return (
= ({ onCancel, }) => { const { t } = useTranslation() - const updateModelProviders = useUpdateModelProviders() const handleClose = () => { onCancel() - if (payload?.category === PluginType.model) - updateModelProviders() } return ( <> diff --git a/web/app/components/plugins/install-plugin/install-bundle/index.tsx b/web/app/components/plugins/install-plugin/install-bundle/index.tsx index da5a9fbd6a592..035de8b7819bc 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/index.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/index.tsx @@ -34,9 +34,7 @@ const InstallBundle: FC = ({ if (step === InstallStep.uploadFailed) return t(`${i18nPrefix}.uploadFailed`) if (step === InstallStep.installed) - return t(`${i18nPrefix}.installedSuccessfully`) - if (step === InstallStep.installFailed) - return t(`${i18nPrefix}.installFailed`) + return t(`${i18nPrefix}.installComplete`) return t(`${i18nPrefix}.installPlugin`) }, [step, t]) diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx index 4ae0bccdab299..07119357aa924 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx @@ -6,7 +6,6 @@ import MarketplaceItem from '../item/marketplace-item' import GithubItem from '../item/github-item' import { useFetchPluginsInMarketPlaceByIds, useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' import produce from 'immer' -import { useGetState } from 'ahooks' import PackageItem from '../item/package-item' import LoadingError from '../../base/loading-error' @@ -25,7 +24,7 @@ const InstallByDSLList: FC = ({ }) => { const { isLoading: isFetchingMarketplaceDataFromDSL, data: marketplaceFromDSLRes } = useFetchPluginsInMarketPlaceByIds(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value.plugin_unique_identifier!)) const { isLoading: isFetchingMarketplaceDataFromLocal, data: marketplaceResFromLocalRes } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!)) - const [plugins, setPlugins, getPlugins] = useGetState<(Plugin | undefined)[]>((() => { + const [plugins, doSetPlugins] = useState<(Plugin | undefined)[]>((() => { const hasLocalPackage = allPlugins.some(d => d.type === 'package') if (!hasLocalPackage) return [] @@ -42,17 +41,23 @@ const InstallByDSLList: FC = ({ }) return _plugins })()) + const pluginsRef = React.useRef<(Plugin | undefined)[]>(plugins) + + const setPlugins = useCallback((p: (Plugin | undefined)[]) => { + doSetPlugins(p) + pluginsRef.current = p + }, []) const [errorIndexes, setErrorIndexes] = useState([]) const handleGitHubPluginFetched = useCallback((index: number) => { return (p: Plugin) => { - const nextPlugins = produce(getPlugins(), (draft) => { + const nextPlugins = produce(pluginsRef.current, (draft) => { draft[index] = p }) setPlugins(nextPlugins) } - }, [getPlugins, setPlugins]) + }, [setPlugins]) const handleGitHubPluginFetchError = useCallback((index: number) => { return () => { @@ -73,7 +78,7 @@ const InstallByDSLList: FC = ({ if (!isFetchingMarketplaceDataFromDSL && marketplaceFromDSLRes?.data.plugins) { const payloads = marketplaceFromDSLRes?.data.plugins const failedIndex: number[] = [] - const nextPlugins = produce(getPlugins(), (draft) => { + const nextPlugins = produce(pluginsRef.current, (draft) => { marketPlaceInDSLIndex.forEach((index, i) => { if (payloads[i]) draft[index] = payloads[i] @@ -82,6 +87,7 @@ const InstallByDSLList: FC = ({ }) }) setPlugins(nextPlugins) + if (failedIndex.length > 0) setErrorIndexes([...errorIndexes, ...failedIndex]) } @@ -92,7 +98,7 @@ const InstallByDSLList: FC = ({ if (!isFetchingMarketplaceDataFromLocal && marketplaceResFromLocalRes?.data.list) { const payloads = marketplaceResFromLocalRes?.data.list const failedIndex: number[] = [] - const nextPlugins = produce(getPlugins(), (draft) => { + const nextPlugins = produce(pluginsRef.current, (draft) => { marketPlaceInDSLIndex.forEach((index, i) => { if (payloads[i]) { const item = payloads[i] diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index c74071e8086f0..d55ea4de857c8 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -7,7 +7,7 @@ import type { InstallState } from '@/app/components/plugins/types' import { useGitHubReleases } from '../hooks' import { convertRepoToUrl, parseGitHubUrl } from '../utils' import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types' -import { InstallStepFromGitHub } from '../../types' +import { InstallStepFromGitHub, PluginType } from '../../types' import Toast from '@/app/components/base/toast' import SetURL from './steps/setURL' import SelectPackage from './steps/selectPackage' @@ -15,6 +15,8 @@ import Installed from '../base/installed' import Loaded from './steps/loaded' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' import { useTranslation } from 'react-i18next' +import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useInvalidateAllToolProviders } from '@/service/use-tools' const i18nPrefix = 'plugin.installFromGitHub' @@ -28,6 +30,8 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on const { t } = useTranslation() const { getIconUrl } = useGetIcon() const { fetchReleases } = useGitHubReleases() + const updateModelProviders = useUpdateModelProviders() + const invalidateAllToolProviders = useInvalidateAllToolProviders() const [state, setState] = useState({ step: updatePayload ? InstallStepFromGitHub.selectPackage : InstallStepFromGitHub.setUrl, repoUrl: updatePayload?.originalPackageInfo?.repo @@ -63,7 +67,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on return t(`${i18nPrefix}.installFailed`) return updatePayload ? t(`${i18nPrefix}.updatePlugin`) : t(`${i18nPrefix}.installPlugin`) - }, [state.step]) + }, [state.step, t, updatePayload]) const handleUrlSubmit = async () => { const { isValid, owner, repo } = parseGitHubUrl(state.repoUrl) @@ -111,8 +115,14 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on const handleInstalled = useCallback(() => { setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) + if (!manifest) + return + if (PluginType.model.includes(manifest.category)) + updateModelProviders() + if (PluginType.tool.includes(manifest.category)) + invalidateAllToolProviders() onSuccess() - }, [onSuccess]) + }, [invalidateAllToolProviders, manifest, onSuccess, updateModelProviders]) const handleFailed = useCallback((errorMsg?: string) => { setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installFailed })) @@ -142,7 +152,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on closable >
-
+
{getTitle()}
diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx index 611a1ad5a155a..c126a38a1da6a 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx @@ -34,13 +34,15 @@ const InstallFromLocalPackage: React.FC = ({ const getTitle = useCallback(() => { if (step === InstallStep.uploadFailed) return t(`${i18nPrefix}.uploadFailed`) + if (isBundle && step === InstallStep.installed) + return t(`${i18nPrefix}.installComplete`) if (step === InstallStep.installed) return t(`${i18nPrefix}.installedSuccessfully`) if (step === InstallStep.installFailed) return t(`${i18nPrefix}.installFailed`) return t(`${i18nPrefix}.installPlugin`) - }, [step, t]) + }, [isBundle, step, t]) const { getIconUrl } = useGetIcon() diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx index 9f81b1e9187a9..f41ecd3469815 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx @@ -2,11 +2,12 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import type { PluginDeclaration } from '../../types' -import { InstallStep } from '../../types' +import { InstallStep, PluginType } from '../../types' import Install from './steps/install' import Installed from '../base/installed' import { useInvalidateInstalledPluginList } from '@/service/use-plugins' - +import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useInvalidateAllToolProviders } from '@/service/use-tools' type Props = { step: InstallStep onStepChange: (step: InstallStep) => void, @@ -27,11 +28,19 @@ const ReadyToInstall: FC = ({ onError, }) => { const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const updateModelProviders = useUpdateModelProviders() + const invalidateAllToolProviders = useInvalidateAllToolProviders() const handleInstalled = useCallback(() => { - invalidateInstalledPluginList() onStepChange(InstallStep.installed) - }, [invalidateInstalledPluginList, onStepChange]) + invalidateInstalledPluginList() + if (!manifest) + return + if (PluginType.model.includes(manifest.category)) + updateModelProviders() + if (PluginType.tool.includes(manifest.category)) + invalidateAllToolProviders() + }, [invalidateAllToolProviders, invalidateInstalledPluginList, manifest, onStepChange, updateModelProviders]) const handleFailed = useCallback((errorMsg?: string) => { onStepChange(InstallStep.installFailed) diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx index ad5b596b7308b..553c30f6be958 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx @@ -3,10 +3,13 @@ import React, { useCallback, useState } from 'react' import Modal from '@/app/components/base/modal' import type { Plugin, PluginManifestInMarket } from '../../types' -import { InstallStep } from '../../types' +import { InstallStep, PluginType } from '../../types' import Install from './steps/install' import Installed from '../base/installed' import { useTranslation } from 'react-i18next' +import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useInvalidateInstalledPluginList } from '@/service/use-plugins' +import { useInvalidateAllToolProviders } from '@/service/use-tools' const i18nPrefix = 'plugin.installModal' @@ -27,7 +30,9 @@ const InstallFromMarketplace: React.FC = ({ // readyToInstall -> check installed -> installed/failed const [step, setStep] = useState(InstallStep.readyToInstall) const [errorMsg, setErrorMsg] = useState(null) - + const updateModelProviders = useUpdateModelProviders() + const invalidateAllToolProviders = useInvalidateAllToolProviders() + const invalidateInstalledPluginList = useInvalidateInstalledPluginList() // TODO: check installed in beta version. const getTitle = useCallback(() => { @@ -40,7 +45,12 @@ const InstallFromMarketplace: React.FC = ({ const handleInstalled = useCallback(() => { setStep(InstallStep.installed) - }, []) + invalidateInstalledPluginList() + if (PluginType.model.includes(manifest.category)) + updateModelProviders() + if (PluginType.tool.includes(manifest.category)) + invalidateAllToolProviders() + }, [invalidateAllToolProviders, invalidateInstalledPluginList, manifest.category, updateModelProviders]) const handleFailed = useCallback((errorMsg?: string) => { setStep(InstallStep.installFailed) diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx index 27ae871d97567..596dc1c05e146 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx @@ -80,7 +80,7 @@ const Installed: FC = ({ return (<>{ payload.latest_version === toInstallVersion || !supportCheckInstalled ? ( - {payload.latest_version} + {payload.version || payload.latest_version} ) : ( <> diff --git a/web/app/components/plugins/plugin-detail-panel/action-list.tsx b/web/app/components/plugins/plugin-detail-panel/action-list.tsx index 609e7d1306329..2d440c2702271 100644 --- a/web/app/components/plugins/plugin-detail-panel/action-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/action-list.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' import { useAppContext } from '@/context/app-context' import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' @@ -14,19 +13,25 @@ import { useRemoveProviderCredentials, useUpdateProviderCredentials, } from '@/service/use-tools' +import type { PluginDetail } from '@/app/components/plugins/types' -const ActionList = () => { +type Props = { + detail: PluginDetail +} + +const ActionList = ({ + detail, +}: Props) => { const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() - const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail) - const { data: provider } = useBuiltinProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`) + const { data: provider } = useBuiltinProviderInfo(`${detail.plugin_id}/${detail.name}`) const invalidateProviderInfo = useInvalidateBuiltinProviderInfo() - const { data } = useBuiltinTools(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`) + const { data } = useBuiltinTools(`${detail.plugin_id}/${detail.name}`) const [showSettingAuth, setShowSettingAuth] = useState(false) const handleCredentialSettingUpdate = () => { - invalidateProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`) + invalidateProviderInfo(`${detail.plugin_id}/${detail.name}`) Toast.notify({ type: 'success', message: t('common.api.actionSuccess'), @@ -74,7 +79,7 @@ const ActionList = () => {
{data.map(tool => ( {verified && } { +const EndpointList = ({ detail }: Props) => { const { t } = useTranslation() - const pluginDetail = usePluginPageContext(v => v.currentPluginDetail) - const pluginUniqueID = pluginDetail.plugin_unique_identifier - const declaration = pluginDetail.declaration.endpoint - const { data } = useEndpointList(pluginDetail.plugin_id) + const pluginUniqueID = detail.plugin_unique_identifier + const declaration = detail.declaration.endpoint + const showTopBorder = detail.declaration.tool + const { data } = useEndpointList(detail.plugin_id) const invalidateEndpointList = useInvalidateEndpointList() const [isShowEndpointModal, { @@ -43,7 +43,7 @@ const EndpointList = ({ showTopBorder }: Props) => { const { mutate: createEndpoint } = useCreateEndpoint({ onSuccess: async () => { - await invalidateEndpointList(pluginDetail.plugin_id) + await invalidateEndpointList(detail.plugin_id) hideEndpointModal() }, onError: () => { @@ -101,7 +101,7 @@ const EndpointList = ({ showTopBorder }: Props) => { invalidateEndpointList(pluginDetail.plugin_id)} + handleChange={() => invalidateEndpointList(detail.plugin_id)} /> ))}
diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index cda554099bfe5..d42304742b502 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -6,51 +6,50 @@ import EndpointList from './endpoint-list' import ActionList from './action-list' import ModelList from './model-list' import Drawer from '@/app/components/base/drawer' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' +import type { PluginDetail } from '@/app/components/plugins/types' import cn from '@/utils/classnames' type Props = { + detail?: PluginDetail onUpdate: () => void + onHide: () => void } const PluginDetailPanel: FC = ({ + detail, onUpdate, + onHide, }) => { - const pluginDetail = usePluginPageContext(v => v.currentPluginDetail) - const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail) - - const handleHide = () => setCurrentPluginDetail(undefined) - const handleUpdate = (isDelete = false) => { if (isDelete) - handleHide() + onHide() onUpdate() } - if (!pluginDetail) + if (!detail) return null return ( - {pluginDetail && ( + {detail && ( <>
- {!!pluginDetail.declaration.tool && } - {!!pluginDetail.declaration.endpoint && } - {!!pluginDetail.declaration.model && } + {!!detail.declaration.tool && } + {!!detail.declaration.endpoint && } + {!!detail.declaration.model && }
)} diff --git a/web/app/components/plugins/plugin-detail-panel/model-list.tsx b/web/app/components/plugins/plugin-detail-panel/model-list.tsx index 7592126867e82..5989a75945a70 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-list.tsx @@ -1,14 +1,19 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon' import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name' import { useModelProviderModelList } from '@/service/use-models' +import type { PluginDetail } from '@/app/components/plugins/types' -const ModelList = () => { +type Props = { + detail: PluginDetail +} + +const ModelList = ({ + detail, +}: Props) => { const { t } = useTranslation() - const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail) - const { data: res } = useModelProviderModelList(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`) + const { data: res } = useModelProviderModelList(`${detail.plugin_id}/${detail.name}`) if (!res) return null diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 13c8797358ae8..430ceae7de1d8 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -22,6 +22,7 @@ import cn from '@/utils/classnames' import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' import { useLanguage } from '../../header/account-setting/model-provider-page/hooks' import { useInvalidateInstalledPluginList } from '@/service/use-plugins' +import { useInvalidateAllToolProviders } from '@/service/use-tools' import { useCategories } from '../hooks' import { useProviderContext } from '@/context/provider-context' @@ -37,9 +38,10 @@ const PluginItem: FC = ({ const locale = useLanguage() const { t } = useTranslation() const { categoriesMap } = useCategories() - const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail) - const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail) + const currentPluginID = usePluginPageContext(v => v.currentPluginID) + const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const invalidateAllToolProviders = useInvalidateAllToolProviders() const { refreshModelProviders } = useProviderContext() const { @@ -59,20 +61,22 @@ const PluginItem: FC = ({ const handleDelete = () => { invalidateInstalledPluginList() - if (category === PluginType.model) + if (PluginType.model.includes(category)) refreshModelProviders() + if (PluginType.tool.includes(category)) + invalidateAllToolProviders() } return (
{ - setCurrentPluginDetail(plugin) + setCurrentPluginID(plugin.plugin_id) }} >
diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index e9e66849e9d56..6363bcae696c5 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -11,15 +11,14 @@ import { useContextSelector, } from 'use-context-selector' import { useSelector as useAppContextSelector } from '@/context/app-context' -import type { PluginDetail } from '../types' import type { FilterState } from './filter-management' import { useTranslation } from 'react-i18next' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' export type PluginPageContextValue = { containerRef: React.RefObject - currentPluginDetail: PluginDetail | undefined - setCurrentPluginDetail: (plugin: PluginDetail) => void + currentPluginID: string | undefined + setCurrentPluginID: (pluginID?: string) => void filters: FilterState setFilters: (filter: FilterState) => void activeTab: string @@ -29,8 +28,8 @@ export type PluginPageContextValue = { export const PluginPageContext = createContext({ containerRef: { current: null }, - currentPluginDetail: undefined, - setCurrentPluginDetail: () => { }, + currentPluginID: undefined, + setCurrentPluginID: () => { }, filters: { categories: [], tags: [], @@ -60,7 +59,7 @@ export const PluginPageContextProvider = ({ tags: [], searchQuery: '', }) - const [currentPluginDetail, setCurrentPluginDetail] = useState() + const [currentPluginID, setCurrentPluginID] = useState() const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) const options = useMemo(() => { @@ -81,8 +80,8 @@ export const PluginPageContextProvider = ({ { const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters]) as [FilterState, (filter: FilterState) => void] const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const currentPluginID = usePluginPageContext(v => v.currentPluginID) + const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) const { run: handleFilterChange } = useDebounceFn((filters: FilterState) => { setFilters(filters) @@ -31,6 +33,13 @@ const PluginsPanel = () => { return filteredList }, [pluginList, filters]) + const currentPluginDetail = useMemo(() => { + const detail = pluginList?.plugins.find(plugin => plugin.plugin_id === currentPluginID) + return detail + }, [currentPluginID, pluginList?.plugins]) + + const handleHide = () => setCurrentPluginID(undefined) + return ( <>
@@ -40,7 +49,7 @@ const PluginsPanel = () => { />
{isPluginListLoading ? : (filteredList?.length ?? 0) > 0 ? ( -
+
@@ -48,7 +57,11 @@ const PluginsPanel = () => { ) : ( )} - invalidateInstalledPluginList()}/> + invalidateInstalledPluginList()} + onHide={handleHide} + /> ) } diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 34cd0c7308284..4bf4f54c6663b 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -79,6 +79,7 @@ export type PluginManifestInMarket = { icon: string label: Record category: PluginType + version: string // TODO: wait api return current plugin version latest_version: string brief: Record introduction: string diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index 20dfc294a7980..071b143115ccf 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -94,11 +94,9 @@ const UpdatePluginModal: FC = ({ } return } - if (uploadStep === UploadStep.installed) { + if (uploadStep === UploadStep.installed) onSave() - onCancel() - } - }, [onCancel, onSave, uploadStep, check, originalPackageInfo.id, handleRefetch, targetPackageInfo.id]) + }, [onSave, uploadStep, check, originalPackageInfo.id, handleRefetch, targetPackageInfo.id]) const usedInAppInfo = useMemo(() => { return (
diff --git a/web/app/components/plugins/update-plugin/plugin-version-picker.tsx b/web/app/components/plugins/update-plugin/plugin-version-picker.tsx index 62f8f85233ec4..b05ddc006217b 100644 --- a/web/app/components/plugins/update-plugin/plugin-version-picker.tsx +++ b/web/app/components/plugins/update-plugin/plugin-version-picker.tsx @@ -67,7 +67,7 @@ const PluginVersionPicker: FC = ({ return onSelect({ version, unique_identifier }) onShowChange(false) - }, [currentVersion, onSelect]) + }, [currentVersion, onSelect, onShowChange]) return ( { const { t } = useTranslation() @@ -50,90 +54,105 @@ const ProviderList = () => { }, [activeTab, tagFilterValue, keywords, collectionList]) const [currentProvider, setCurrentProvider] = useState() + const { data: pluginList } = useInstalledPluginList() + const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const currentPluginDetail = useMemo(() => { + const detail = pluginList?.plugins.find(plugin => plugin.plugin_id === currentProvider?.plugin_id) + return detail + }, [currentProvider?.plugin_id, pluginList?.plugins]) return ( -
-
-
- { - setActiveTab(state) - if (state !== activeTab) - setCurrentProvider(undefined) - }} - options={options} - /> -
- - handleKeywordsChange(e.target.value)} - onClear={() => handleKeywordsChange('')} - /> -
-
- {filteredCollectionList.length > 0 && ( + <> +
+
- {filteredCollectionList.map(collection => ( -
setCurrentProvider(collection)} - > - - } - /> -
- ))} -
- )} - {!filteredCollectionList.length && ( - - )} - { - enable_marketplace && ( - { - containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }) + { + setActiveTab(state) + if (state !== activeTab) + setCurrentProvider(undefined) }} - searchPluginText={keywords} - filterPluginTags={tagFilterValue} + options={options} /> - ) - } +
+ + handleKeywordsChange(e.target.value)} + onClear={() => handleKeywordsChange('')} + /> +
+
+ {(filteredCollectionList.length > 0 || activeTab !== 'builtin') && ( +
+ {activeTab === 'api' && } + {filteredCollectionList.map(collection => ( +
setCurrentProvider(collection)} + > + + } + /> +
+ ))} + {!filteredCollectionList.length && activeTab === 'workflow' &&
} +
+ )} + {!filteredCollectionList.length && activeTab === 'builtin' && ( + + )} + { + enable_marketplace && activeTab === 'builtin' && ( + { + containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }) + }} + searchPluginText={keywords} + filterPluginTags={tagFilterValue} + /> + ) + } +
- {currentProvider && ( + {currentProvider && !currentProvider.plugin_id && ( setCurrentProvider(undefined)} onRefreshData={refetch} /> )} -
+ invalidateInstalledPluginList()} + onHide={() => setCurrentProvider(undefined)} + /> + ) } ProviderList.displayName = 'ToolProviderList' diff --git a/web/app/components/tools/provider/custom-create-card.tsx b/web/app/components/tools/provider/custom-create-card.tsx index d6aa9ab533b02..424a0775274a0 100644 --- a/web/app/components/tools/provider/custom-create-card.tsx +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -45,7 +45,7 @@ const Contribute = ({ onRefreshData }: Props) => { return ( <> {isCurrentWorkspaceManager && ( -
+
setIsShowEditCustomCollectionModal(true)}>
diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index 34cc491481437..27f187d1f7571 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -48,6 +48,7 @@ export type Collection = { is_team_authorization: boolean allow_delete: boolean labels: string[] + plugin_id?: string } export type ToolParameter = { diff --git a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx index 6f0c08eeca967..f4a9668c3ebdf 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useRef } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiMoreFill } from '@remixicon/react' import ActionButton from '@/app/components/base/action-button' @@ -12,12 +12,15 @@ import { } from '@/app/components/base/portal-to-follow-elem' import cn from '@/utils/classnames' import { MARKETPLACE_URL_PREFIX } from '@/config' +import { useDownloadPlugin } from '@/service/use-plugins' +import { downloadFile } from '@/utils/format' type Props = { open: boolean onOpenChange: (v: boolean) => void author: string name: string + version: string } const OperationDropdown: FC = ({ @@ -25,6 +28,7 @@ const OperationDropdown: FC = ({ onOpenChange, author, name, + version, }) => { const { t } = useTranslation() const openRef = useRef(open) @@ -37,6 +41,25 @@ const OperationDropdown: FC = ({ setOpen(!openRef.current) }, [setOpen]) + const [needDownload, setNeedDownload] = useState(false) + const { data: blob, isLoading } = useDownloadPlugin({ + organization: author, + pluginName: name, + version, + }, needDownload) + const handleDownload = useCallback(() => { + if (isLoading) return + setNeedDownload(true) + }, [isLoading]) + + useEffect(() => { + if (blob) { + const fileName = `${author}-${name}_${version}.zip` + downloadFile({ data: blob, fileName }) + setNeedDownload(false) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [blob]) return ( = ({
-
{t('common.operation.download')}
+
{t('common.operation.download')}
{t('common.operation.viewDetails')}
diff --git a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx index 7f2ae3408362f..ebe4da73f8b70 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx @@ -59,6 +59,7 @@ const Item: FC = ({ onOpenChange={setOpen} author={payload.org} name={payload.name} + version={payload.latest_version} />
{isShowInstallModal && ( diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index bcbb1648f412e..88c21c0dd3f74 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -94,6 +94,7 @@ const translation = { }, installModal: { installPlugin: 'Install Plugin', + installComplete: 'Installation complete', installedSuccessfully: 'Installation successful', installedSuccessfullyDesc: 'The plugin has been installed successfully.', uploadFailed: 'Upload failed', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index b293f99f8f0f6..94e1324bedcbc 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -94,6 +94,7 @@ const translation = { }, installModal: { installPlugin: '安装插件', + installComplete: '安装完成', installedSuccessfully: '安装成功', installedSuccessfullyDesc: '插件已成功安装。', uploadFailed: '上传失败', diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 666a3e2336db8..39f91f903f33d 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -12,6 +12,7 @@ export const ContentType = { audio: 'audio/mpeg', form: 'application/x-www-form-urlencoded; charset=UTF-8', download: 'application/octet-stream', // for download + downloadZip: 'application/zip', // for download upload: 'multipart/form-data', // for upload } @@ -193,7 +194,7 @@ async function base(url: string, options: FetchOptionType = {}, otherOptions: const contentType = res.headers.get('content-type') if ( contentType - && [ContentType.download, ContentType.audio].includes(contentType) + && [ContentType.download, ContentType.audio, ContentType.downloadZip].includes(contentType) ) return await res.blob() as T diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 0c05290e0ffff..78c4ff770f27a 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -344,3 +344,12 @@ export const useMutationCheckDependenciesBeforeImportDSL = () => { return mutation } + +export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => { + return useQuery({ + queryKey: [NAME_SPACE, 'downloadPlugin', info], + queryFn: () => getMarketplace(`/plugins/${info.organization}/${info.pluginName}/${info.version}/download`), + enabled: needDownload, + retry: 0, + }) +} diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 337f0bbfb8bad..bf9678332f456 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -21,6 +21,10 @@ export const useAllToolProviders = () => { }) } +export const useInvalidateAllToolProviders = () => { + return useInvalid(useAllToolProvidersKey) +} + const useAllBuiltInToolsKey = [NAME_SPACE, 'builtIn'] export const useAllBuiltInTools = () => { return useQuery({ diff --git a/web/utils/format.ts b/web/utils/format.ts index 1eeb6af807e93..45f0e51878a52 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -34,3 +34,14 @@ export const formatTime = (num: number) => { } return `${num.toFixed(2)} ${units[index]}` } + +export const downloadFile = ({ data, fileName }: { data: Blob; fileName: string }) => { + const url = window.URL.createObjectURL(data) + const a = document.createElement('a') + a.href = url + a.download = fileName + document.body.appendChild(a) + a.click() + a.remove() + window.URL.revokeObjectURL(url) +}