diff --git a/web/app/components/header/license-env/index.tsx b/web/app/components/header/license-env/index.tsx
new file mode 100644
index 0000000000..800d86d2b8
--- /dev/null
+++ b/web/app/components/header/license-env/index.tsx
@@ -0,0 +1,29 @@
+'use client'
+
+import AppContext from '@/context/app-context'
+import { LicenseStatus } from '@/types/feature'
+import { useTranslation } from 'react-i18next'
+import { useContextSelector } from 'use-context-selector'
+import dayjs from 'dayjs'
+
+const LicenseNav = () => {
+ const { t } = useTranslation()
+ const systemFeatures = useContextSelector(AppContext, s => s.systemFeatures)
+
+ if (systemFeatures.license?.status === LicenseStatus.EXPIRING) {
+ const expiredAt = systemFeatures.license?.expired_at
+ const count = dayjs(expiredAt).diff(dayjs(), 'days')
+ return
+ {count <= 1 && {t('common.license.expiring', { count })}}
+ {count > 1 && {t('common.license.expiring_plural', { count })}}
+
+ }
+ if (systemFeatures.license.status === LicenseStatus.ACTIVE) {
+ return
+ Enterprise
+
+ }
+ return null
+}
+
+export default LicenseNav
diff --git a/web/app/components/plugins/install-plugin/base/installed.tsx b/web/app/components/plugins/install-plugin/base/installed.tsx
index 442a61e372..eba50a6b21 100644
--- a/web/app/components/plugins/install-plugin/base/installed.tsx
+++ b/web/app/components/plugins/install-plugin/base/installed.tsx
@@ -1,11 +1,13 @@
'use client'
import type { FC } from 'react'
import React from 'react'
-import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
+import { useTranslation } from 'react-i18next'
import Card from '../../card'
import Button from '@/app/components/base/button'
+import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks'
+import { PluginType } from '../../types'
+import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
import { pluginManifestInMarketToPluginProps, pluginManifestToCardPluginProps } from '../utils'
-import { useTranslation } from 'react-i18next'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
type Props = {
@@ -24,6 +26,13 @@ const Installed: FC
= ({
onCancel,
}) => {
const { t } = useTranslation()
+ const updateModelProviders = useUpdateModelProviders()
+
+ const handleClose = () => {
+ onCancel()
+ if (payload?.category === PluginType.model)
+ updateModelProviders()
+ }
return (
<>
@@ -45,7 +54,7 @@ const Installed: FC
= ({
diff --git a/web/app/components/plugins/install-plugin/base/loading-error.tsx b/web/app/components/plugins/install-plugin/base/loading-error.tsx
new file mode 100644
index 0000000000..eb698bb573
--- /dev/null
+++ b/web/app/components/plugins/install-plugin/base/loading-error.tsx
@@ -0,0 +1,45 @@
+'use client'
+import type { FC } from 'react'
+import React from 'react'
+import { Group } from '../../../base/icons/src/vender/other'
+import { LoadingPlaceholder } from '@/app/components/plugins/card/base/placeholder'
+import Checkbox from '@/app/components/base/checkbox'
+import { RiCloseLine } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+
+const LoadingError: FC = () => {
+ const { t } = useTranslation()
+ return (
+
+
+
+
+
+
+
+ {t('plugin.installModal.pluginLoadError')}
+
+
+ {t('plugin.installModal.pluginLoadErrorDesc')}
+
+
+
+
+
+
+ )
+}
+export default React.memo(LoadingError)
diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/loading.tsx b/web/app/components/plugins/install-plugin/base/loading.tsx
similarity index 91%
rename from web/app/components/plugins/install-plugin/install-bundle/item/loading.tsx
rename to web/app/components/plugins/install-plugin/base/loading.tsx
index 5e33363ecf..52cccc2cd0 100644
--- a/web/app/components/plugins/install-plugin/install-bundle/item/loading.tsx
+++ b/web/app/components/plugins/install-plugin/base/loading.tsx
@@ -1,6 +1,6 @@
'use client'
import React from 'react'
-import Placeholder from '../../../card/base/placeholder'
+import Placeholder from '../../card/base/placeholder'
import Checkbox from '@/app/components/base/checkbox'
const Loading = () => {
diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx
index cfbe05ca5d..8440b488b2 100644
--- a/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx
+++ b/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx
@@ -4,7 +4,7 @@ import React, { useEffect } from 'react'
import type { GitHubItemAndMarketPlaceDependency, Plugin } from '../../../types'
import { pluginManifestToCardPluginProps } from '../../utils'
import { useUploadGitHub } from '@/service/use-plugins'
-import Loading from './loading'
+import Loading from '../../base/loading'
import LoadedItem from './loaded-item'
type Props = {
@@ -25,8 +25,8 @@ const Item: FC = ({
const info = dependency.value
const { data, error } = useUploadGitHub({
repo: info.repo!,
- version: info.version!,
- package: info.package!,
+ version: info.release! || info.version!,
+ package: info.packages! || info.package!,
})
const [payload, setPayload] = React.useState(null)
useEffect(() => {
diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx
index 836869df63..f7de4d09bc 100644
--- a/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx
+++ b/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx
@@ -2,7 +2,7 @@
import type { FC } from 'react'
import React from 'react'
import type { Plugin } from '../../../types'
-import Loading from './loading'
+import Loading from '../../base/loading'
import LoadedItem from './loaded-item'
type Props = {
diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx
index bcdc72a1ce..b649aada8f 100644
--- a/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx
+++ b/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx
@@ -5,6 +5,7 @@ import type { Plugin } from '../../../types'
import type { PackageDependency } from '../../../types'
import { pluginManifestToCardPluginProps } from '../../utils'
import LoadedItem from './loaded-item'
+import LoadingError from '../../base/loading-error'
type Props = {
checked: boolean
@@ -17,6 +18,9 @@ const PackageItem: FC = ({
checked,
onCheckedChange,
}) => {
+ if (!payload.value?.manifest)
+ return
+
const plugin = pluginManifestToCardPluginProps(payload.value.manifest)
return (
= ({
onSelect,
onLoadedAllPlugin,
}) => {
- const { isLoading: isFetchingMarketplaceData, data: marketplaceRes } = useFetchPluginsInMarketPlaceByIds(allPlugins.filter(d => d.type === 'marketplace').map(d => d.value.plugin_unique_identifier!))
+ 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 hasLocalPackage = allPlugins.some(d => d.type === 'package')
+ if (!hasLocalPackage)
+ return []
- const [plugins, setPlugins, getPlugins] = useGetState([])
+ const _plugins = allPlugins.map((d) => {
+ if (d.type === 'package') {
+ return {
+ ...(d as any).value.manifest,
+ plugin_id: (d as any).value.unique_identifier,
+ }
+ }
+
+ return undefined
+ })
+ return _plugins
+ })())
const [errorIndexes, setErrorIndexes] = useState([])
@@ -53,21 +70,50 @@ const InstallByDSLList: FC = ({
}, [allPlugins])
useEffect(() => {
- if (!isFetchingMarketplaceData && marketplaceRes?.data.plugins && marketplaceRes?.data.plugins.length > 0) {
- const payloads = marketplaceRes?.data.plugins
-
+ if (!isFetchingMarketplaceDataFromDSL && marketplaceFromDSLRes?.data.plugins) {
+ const payloads = marketplaceFromDSLRes?.data.plugins
+ const failedIndex: number[] = []
const nextPlugins = produce(getPlugins(), (draft) => {
marketPlaceInDSLIndex.forEach((index, i) => {
- draft[index] = payloads[i]
+ if (payloads[i])
+ draft[index] = payloads[i]
+ else
+ failedIndex.push(index)
})
})
setPlugins(nextPlugins)
- // marketplaceRes?.data.plugins
+ if (failedIndex.length > 0)
+ setErrorIndexes([...errorIndexes, ...failedIndex])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isFetchingMarketplaceData])
+ }, [isFetchingMarketplaceDataFromDSL])
- const isLoadedAllData = allPlugins.length === plugins.length && plugins.every(p => !!p)
+ useEffect(() => {
+ if (!isFetchingMarketplaceDataFromLocal && marketplaceResFromLocalRes?.data.list) {
+ const payloads = marketplaceResFromLocalRes?.data.list
+ const failedIndex: number[] = []
+ const nextPlugins = produce(getPlugins(), (draft) => {
+ marketPlaceInDSLIndex.forEach((index, i) => {
+ if (payloads[i]) {
+ const item = payloads[i]
+ draft[index] = {
+ ...item.plugin,
+ plugin_id: item.version.unique_identifier,
+ }
+ }
+ else {
+ failedIndex.push(index)
+ }
+ })
+ })
+ setPlugins(nextPlugins)
+ if (failedIndex.length > 0)
+ setErrorIndexes([...errorIndexes, ...failedIndex])
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isFetchingMarketplaceDataFromLocal])
+
+ const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length
useEffect(() => {
if (isLoadedAllData)
onLoadedAllPlugin()
@@ -76,7 +122,7 @@ const InstallByDSLList: FC = ({
const handleSelect = useCallback((index: number) => {
return () => {
- onSelect(plugins[index], index)
+ onSelect(plugins[index]!, index)
}
}, [onSelect, plugins])
return (
@@ -84,7 +130,7 @@ const InstallByDSLList: FC = ({
{allPlugins.map((d, index) => {
if (errorIndexes.includes(index)) {
return (
- error
+
)
}
if (d.type === 'github') {
diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx
index 9c361d4e4b..389cb3d9ca 100644
--- a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx
+++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx
@@ -5,7 +5,7 @@ import type { Dependency, InstallStatusResponse, Plugin } from '../../../types'
import Button from '@/app/components/base/button'
import { RiLoader2Line } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
-import InstallByDSLList from './install-multi'
+import InstallMulti from './install-multi'
import { useInstallFromMarketplaceAndGitHub } from '@/service/use-plugins'
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
const i18nPrefix = 'plugin.installModal'
@@ -58,7 +58,10 @@ const Install: FC = ({
},
})
const handleInstall = () => {
- installFromMarketplaceAndGitHub(allPlugins.filter((_d, index) => selectedIndexes.includes(index)))
+ installFromMarketplaceAndGitHub({
+ payload: allPlugins.filter((_d, index) => selectedIndexes.includes(index)),
+ plugin: selectedPlugins,
+ })
}
return (
<>
@@ -67,7 +70,7 @@ const Install: FC = ({
{t(`${i18nPrefix}.${selectedPluginsNum > 1 ? 'readyToInstallPackages' : 'readyToInstallPackage'}`, { num: selectedPluginsNum })}
-
= ({
const [uniqueIdentifier, setUniqueIdentifier] = useState(null)
const [manifest, setManifest] = useState(null)
const [errorMsg, setErrorMsg] = useState(null)
- const isBundle = file.name.endsWith('.bundle')
+ const isBundle = file.name.endsWith('.difybndl')
const [dependencies, setDependencies] = useState([])
const getTitle = useCallback(() => {
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 b721a84454..ad5b596b73 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
@@ -52,6 +52,7 @@ const InstallFromMarketplace: React.FC = ({
diff --git a/web/app/components/plugins/marketplace/empty/index.tsx b/web/app/components/plugins/marketplace/empty/index.tsx
index 25f8efc504..32b706a291 100644
--- a/web/app/components/plugins/marketplace/empty/index.tsx
+++ b/web/app/components/plugins/marketplace/empty/index.tsx
@@ -4,12 +4,22 @@ import { Group } from '@/app/components/base/icons/src/vender/other'
import Line from './line'
import cn from '@/utils/classnames'
-const Empty = () => {
+type Props = {
+ text?: string
+ lightCard?: boolean
+ className?: string
+}
+
+const Empty = ({
+ text,
+ lightCard,
+ className,
+}: Props) => {
const { t } = useTranslation()
return (
{
Array.from({ length: 16 }).map((_, index) => (
@@ -19,6 +29,7 @@ const Empty = () => {
'mr-3 mb-3 h-[144px] w-[calc((100%-36px)/4)] rounded-xl bg-background-section-burn',
index % 4 === 3 && 'mr-0',
index > 11 && 'mb-0',
+ lightCard && 'bg-background-default-lighter',
)}
>
@@ -28,7 +39,7 @@ const Empty = () => {
className='absolute inset-0 bg-marketplace-plugin-empty z-[1]'
>
-
+
@@ -36,7 +47,7 @@ const Empty = () => {
- {t('plugin.marketplace.noPluginFound')}
+ {text || t('plugin.marketplace.noPluginFound')}
diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx
index 364a1b2b58..06541fe6d6 100644
--- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx
+++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx
@@ -48,19 +48,19 @@ const CardWrapper = ({
)
}
@@ -94,27 +94,6 @@ const CardWrapper = ({
/>
}
/>
- {
- showInstallButton && (
-
- )
- }
)
}
diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx
index 5e1b0770f7..0127c0059f 100644
--- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx
@@ -9,7 +9,7 @@ import {
RiVerifiedBadgeLine,
} from '@remixicon/react'
import type { PluginDetail } from '../types'
-import { PluginSource } from '../types'
+import { PluginSource, PluginType } from '../types'
import Description from '../card/base/description'
import Icon from '../card/base/card-icon'
import Title from '../card/base/title'
@@ -30,6 +30,7 @@ import { Github } from '@/app/components/base/icons/src/public/common'
import { uninstallPlugin } from '@/service/plugins'
import { useGetLanguage } from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
+import { useProviderContext } from '@/context/provider-context'
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
import cn from '@/utils/classnames'
@@ -38,7 +39,7 @@ const i18nPrefix = 'plugin.action'
type Props = {
detail: PluginDetail
onHide: () => void
- onUpdate: () => void
+ onUpdate: (isDelete?: boolean) => void
}
const DetailHeader = ({
@@ -50,6 +51,7 @@ const DetailHeader = ({
const locale = useGetLanguage()
const { checkForUpdates, fetchReleases } = useGitHubReleases()
const { setShowUpdatePluginModal } = useModalContext()
+ const { refreshModelProviders } = useProviderContext()
const {
installation_id,
@@ -61,7 +63,7 @@ const DetailHeader = ({
meta,
plugin_id,
} = detail
- const { author, name, label, description, icon, verified } = detail.declaration
+ const { author, category, name, label, description, icon, verified } = detail.declaration
const isFromGitHub = source === PluginSource.github
const isFromMarketplace = source === PluginSource.marketplace
@@ -77,6 +79,14 @@ const DetailHeader = ({
return false
}, [isFromMarketplace, latest_version, version])
+ const detailUrl = useMemo(() => {
+ if (isFromGitHub)
+ return `https://github.com/${meta!.repo}`
+ if (isFromMarketplace)
+ return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}`
+ return ''
+ }, [author, isFromGitHub, isFromMarketplace, meta, name])
+
const [isShowUpdateModal, {
setTrue: showUpdateModal,
setFalse: hideUpdateModal,
@@ -139,9 +149,11 @@ const DetailHeader = ({
hideDeleting()
if (res.success) {
hideDeleteConfirm()
- onUpdate()
+ onUpdate(true)
+ if (category === PluginType.model)
+ refreshModelProviders()
}
- }, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onUpdate])
+ }, [showDeleting, installation_id, hideDeleting, hideDeleteConfirm, onUpdate, category, refreshModelProviders])
// #plugin TODO# used in apps
// const usedInApps = 3
@@ -225,7 +237,7 @@ const DetailHeader = ({
onInfo={showPluginInfo}
onCheckVersion={handleUpdate}
onRemove={showDeleteConfirm}
- detailUrl={`${MARKETPLACE_URL_PREFIX}/plugin/${author}/${name}`}
+ detailUrl={detailUrl}
/>
diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx
index da8c23a93f..cda554099b 100644
--- a/web/app/components/plugins/plugin-detail-panel/index.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/index.tsx
@@ -21,6 +21,12 @@ const PluginDetailPanel: FC = ({
const handleHide = () => setCurrentPluginDetail(undefined)
+ const handleUpdate = (isDelete = false) => {
+ if (isDelete)
+ handleHide()
+ onUpdate()
+ }
+
if (!pluginDetail)
return null
@@ -39,7 +45,7 @@ const PluginDetailPanel: FC = ({
{!!pluginDetail.declaration.tool &&
}
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 7980920119..7592126867 100644
--- a/web/app/components/plugins/plugin-detail-panel/model-list.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/model-list.tsx
@@ -21,7 +21,7 @@ const ModelList = () => {
= ({
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
>{t('plugin.detailPanel.operation.checkUpdate')}
)}
- {source === PluginSource.marketplace && (
+ {(source === PluginSource.marketplace || source === PluginSource.github) && (
{t('plugin.detailPanel.operation.viewDetail')}
diff --git a/web/app/components/plugins/plugin-item/action.tsx b/web/app/components/plugins/plugin-item/action.tsx
index a387727b4f..1bc34c9928 100644
--- a/web/app/components/plugins/plugin-item/action.tsx
+++ b/web/app/components/plugins/plugin-item/action.tsx
@@ -55,7 +55,7 @@ const Action: FC = ({
const handleFetchNewVersion = async () => {
const fetchedReleases = await fetchReleases(author, pluginName)
- if (fetchReleases.length === 0) return
+ if (fetchedReleases.length === 0) return
const { needUpdate, toastProps } = checkForUpdates(fetchedReleases, meta!.version)
Toast.notify(toastProps)
if (needUpdate) {
diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx
index eb833e0781..13c8797358 100644
--- a/web/app/components/plugins/plugin-item/index.tsx
+++ b/web/app/components/plugins/plugin-item/index.tsx
@@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next'
import { usePluginPageContext } from '../plugin-page/context'
import { Github } from '../../base/icons/src/public/common'
import Badge from '../../base/badge'
-import { type PluginDetail, PluginSource } from '../types'
+import { type PluginDetail, PluginSource, PluginType } from '../types'
import CornerMark from '../card/base/corner-mark'
import Description from '../card/base/description'
import OrgInfo from '../card/base/org-info'
@@ -23,6 +23,7 @@ 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 { useCategories } from '../hooks'
+import { useProviderContext } from '@/context/provider-context'
type Props = {
className?: string
@@ -39,6 +40,7 @@ const PluginItem: FC = ({
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
const setCurrentPluginDetail = usePluginPageContext(v => v.setCurrentPluginDetail)
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
+ const { refreshModelProviders } = useProviderContext()
const {
source,
@@ -54,6 +56,12 @@ const PluginItem: FC = ({
const orgName = useMemo(() => {
return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
}, [source, author])
+
+ const handleDelete = () => {
+ invalidateInstalledPluginList()
+ if (category === PluginType.model)
+ refreshModelProviders()
+ }
return (
= ({
isShowInfo={source === PluginSource.github}
isShowDelete
meta={meta}
- onDelete={() => {
- invalidateInstalledPluginList()
- }}
+ onDelete={handleDelete}
/>
@@ -136,7 +142,7 @@ const PluginItem: FC = ({
}
{source === PluginSource.marketplace
&& <>
-
+
{t('plugin.from')} marketplace
diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx
index 2d7ae46874..e7fd8ad4ec 100644
--- a/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx
+++ b/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx
@@ -119,7 +119,7 @@ const PluginTasks = () => {