-
+
{t('common.modelProvider.modelsNum', { num: models.length })}
onCollapse()}
>
{t('common.modelProvider.modelsNum', { num: models.length })}
diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx
index da5357d87d..b48922bdb9 100644
--- a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx
+++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx
@@ -8,7 +8,7 @@ import Button from '@/app/components/base/button'
import { Trans, useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
-import { installPackageFromLocal } from '@/service/plugins'
+import { useInstallPackageFromLocal } from '@/service/use-plugins'
import checkTaskStatus from '../../base/check-task-status'
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
@@ -33,6 +33,8 @@ const Installed: FC = ({
}) => {
const { t } = useTranslation()
const [isInstalling, setIsInstalling] = React.useState(false)
+ const { mutateAsync: installPackageFromLocal } = useInstallPackageFromLocal()
+
const {
check,
stop,
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 bc32e642a5..27ae871d97 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
@@ -9,7 +9,7 @@ import Button from '@/app/components/base/button'
import { useTranslation } from 'react-i18next'
import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
-import { installPackageFromMarketPlace } from '@/service/plugins'
+import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
import checkTaskStatus from '../../base/check-task-status'
const i18nPrefix = 'plugin.installModal'
@@ -32,6 +32,7 @@ const Installed: FC = ({
onFailed,
}) => {
const { t } = useTranslation()
+ const { mutateAsync: installPackageFromMarketPlace } = useInstallPackageFromMarketPlace()
const [isInstalling, setIsInstalling] = React.useState(false)
const {
check,
diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx
index 0c87e32919..4c5752d45b 100644
--- a/web/app/components/plugins/marketplace/context.tsx
+++ b/web/app/components/plugins/marketplace/context.tsx
@@ -34,7 +34,7 @@ export type MarketplaceContextValue = {
activePluginType: string
handleActivePluginTypeChange: (type: string) => void
plugins?: Plugin[]
- setPlugins: (plugins: Plugin[]) => void
+ resetPlugins: () => void
sort: PluginsSort
handleSortChange: (sort: PluginsSort) => void
marketplaceCollectionsFromClient?: MarketplaceCollection[]
@@ -53,7 +53,7 @@ export const MarketplaceContext = createContext({
activePluginType: PLUGIN_TYPE_SEARCH_MAP.all,
handleActivePluginTypeChange: () => {},
plugins: undefined,
- setPlugins: () => {},
+ resetPlugins: () => {},
sort: DEFAULT_SORT,
handleSortChange: () => {},
marketplaceCollectionsFromClient: [],
@@ -91,7 +91,7 @@ export const MarketplaceContextProvider = ({
} = useMarketplaceCollectionsAndPlugins()
const {
plugins,
- setPlugins,
+ resetPlugins,
queryPlugins,
queryPluginsWithDebounced,
} = useMarketplacePlugins()
@@ -104,7 +104,7 @@ export const MarketplaceContextProvider = ({
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
})
- setPlugins(undefined)
+ resetPlugins()
return
}
@@ -116,7 +116,7 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
})
- }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, setPlugins])
+ }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins])
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags)
@@ -126,7 +126,7 @@ export const MarketplaceContextProvider = ({
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
})
- setPlugins(undefined)
+ resetPlugins()
return
}
@@ -138,7 +138,7 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
})
- }, [queryPlugins, setPlugins, queryMarketplaceCollectionsAndPlugins])
+ }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type)
@@ -148,7 +148,7 @@ export const MarketplaceContextProvider = ({
queryMarketplaceCollectionsAndPlugins({
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
})
- setPlugins(undefined)
+ resetPlugins()
return
}
@@ -160,7 +160,7 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
})
- }, [queryPlugins, setPlugins, queryMarketplaceCollectionsAndPlugins])
+ }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort)
@@ -187,7 +187,7 @@ export const MarketplaceContextProvider = ({
activePluginType,
handleActivePluginTypeChange,
plugins,
- setPlugins,
+ resetPlugins,
sort,
handleSortChange,
marketplaceCollectionsFromClient,
diff --git a/web/app/components/plugins/marketplace/description/index.tsx b/web/app/components/plugins/marketplace/description/index.tsx
index 3b0454c3c6..9e3f9774b5 100644
--- a/web/app/components/plugins/marketplace/description/index.tsx
+++ b/web/app/components/plugins/marketplace/description/index.tsx
@@ -15,10 +15,10 @@ const Description = async ({
return (
<>
- Empower your AI development
+ {t('marketplace.empower')}
- Discover
+ {t('marketplace.discover')}
{t('category.models')}
@@ -30,11 +30,11 @@ const Description = async ({
{t('category.extensions')}
- and
+ {t('marketplace.and')}
{t('category.bundles')}
- in Dify Marketplace
+ {t('marketplace.inDifyMarketplace')}
>
)
diff --git a/web/app/components/plugins/marketplace/hooks.ts b/web/app/components/plugins/marketplace/hooks.ts
index 47ad603276..1cbc035972 100644
--- a/web/app/components/plugins/marketplace/hooks.ts
+++ b/web/app/components/plugins/marketplace/hooks.ts
@@ -4,7 +4,9 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks'
-import type { Plugin } from '../types'
+import type {
+ Plugin,
+} from '../types'
import type {
CollectionsAndPluginsSearchParams,
MarketplaceCollection,
@@ -12,9 +14,9 @@ import type {
} from './types'
import {
getMarketplaceCollectionsAndPlugins,
- getMarketplacePlugins,
} from './utils'
import i18n from '@/i18n/i18next-config'
+import { useMutationPluginsFromMarketplace } from '@/service/use-plugins'
export const useMarketplaceCollectionsAndPlugins = () => {
const [isLoading, setIsLoading] = useState(false)
@@ -41,28 +43,29 @@ export const useMarketplaceCollectionsAndPlugins = () => {
}
export const useMarketplacePlugins = () => {
- const [isLoading, setIsLoading] = useState(false)
- const [plugins, setPlugins] = useState()
+ const {
+ data,
+ mutate,
+ reset,
+ isPending,
+ } = useMutationPluginsFromMarketplace()
- const queryPlugins = useCallback(async (query: PluginsSearchParams) => {
- setIsLoading(true)
- const { marketplacePlugins } = await getMarketplacePlugins(query)
- setIsLoading(false)
+ const queryPlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
+ mutate(pluginsSearchParams)
+ }, [mutate])
- setPlugins(marketplacePlugins)
- }, [])
-
- const { run: queryPluginsWithDebounced } = useDebounceFn(queryPlugins, {
+ const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams) => {
+ mutate(pluginsSearchParams)
+ }, {
wait: 500,
})
return {
- plugins,
- setPlugins,
+ plugins: data?.data?.plugins,
+ resetPlugins: reset,
queryPlugins,
queryPluginsWithDebounced,
- isLoading,
- setIsLoading,
+ isLoading: isPending,
}
}
diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx
index 742df86ea0..10e623710e 100644
--- a/web/app/components/plugins/marketplace/index.tsx
+++ b/web/app/components/plugins/marketplace/index.tsx
@@ -5,6 +5,7 @@ import SearchBoxWrapper from './search-box/search-box-wrapper'
import PluginTypeSwitch from './plugin-type-switch'
import ListWrapper from './list/list-wrapper'
import { getMarketplaceCollectionsAndPlugins } from './utils'
+import { TanstackQueryIniter } from '@/context/query-client'
type MarketplaceProps = {
locale?: string
@@ -17,18 +18,20 @@ const Marketplace = async ({
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
)
}
diff --git a/web/app/components/plugins/marketplace/plugin-type-switch.tsx b/web/app/components/plugins/marketplace/plugin-type-switch.tsx
index 6a44524a0c..82758ad87d 100644
--- a/web/app/components/plugins/marketplace/plugin-type-switch.tsx
+++ b/web/app/components/plugins/marketplace/plugin-type-switch.tsx
@@ -30,7 +30,7 @@ const PluginTypeSwitch = ({
const options = [
{
value: PLUGIN_TYPE_SEARCH_MAP.all,
- text: 'All',
+ text: t('plugin.category.all'),
icon: null,
},
{
diff --git a/web/app/components/plugins/permission-setting-modal/modal.tsx b/web/app/components/plugins/permission-setting-modal/modal.tsx
index c9e2d2da61..b5eaa4765c 100644
--- a/web/app/components/plugins/permission-setting-modal/modal.tsx
+++ b/web/app/components/plugins/permission-setting-modal/modal.tsx
@@ -34,7 +34,7 @@ const PluginSettingModal: FC = ({
const handleSave = useCallback(async () => {
await onSave(tempPrivilege)
onHide()
- }, [tempPrivilege])
+ }, [onHide, onSave, tempPrivilege])
return (
{
const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext()
const currentPluginDetail = usePluginPageContext(v => v.currentPluginDetail)
- const { data: provider } = useSWR(
- `builtin/${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
- fetchCollectionDetail,
- )
- const { data } = useSWR(
- `${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`,
- fetchBuiltInToolList,
- )
+ const { data: provider } = useBuiltinProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
+ const invalidateProviderInfo = useInvalidateBuiltinProviderInfo()
+ const { data } = useBuiltinTools(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
const [showSettingAuth, setShowSettingAuth] = useState(false)
- const handleCredentialSettingUpdate = () => {}
+ const handleCredentialSettingUpdate = () => {
+ invalidateProviderInfo(`${currentPluginDetail.plugin_id}/${currentPluginDetail.name}`)
+ Toast.notify({
+ type: 'success',
+ message: t('common.api.actionSuccess'),
+ })
+ setShowSettingAuth(false)
+ }
+
+ const { mutate: updatePermission } = useUpdateProviderCredentials({
+ onSuccess: handleCredentialSettingUpdate,
+ })
+
+ const { mutate: removePermission } = useRemoveProviderCredentials({
+ onSuccess: handleCredentialSettingUpdate,
+ })
if (!data || !provider)
return null
@@ -77,24 +87,11 @@ const ActionList = () => {
setShowSettingAuth(false)}
- onSaved={async (value) => {
- await updateBuiltInToolCredential(provider.name, value)
- Toast.notify({
- type: 'success',
- message: t('common.api.actionSuccess'),
- })
- handleCredentialSettingUpdate()
- setShowSettingAuth(false)
- }}
- onRemove={async () => {
- await removeBuiltInToolCredential(provider.name)
- Toast.notify({
- type: 'success',
- message: t('common.api.actionSuccess'),
- })
- handleCredentialSettingUpdate()
- setShowSettingAuth(false)
- }}
+ onSaved={async value => updatePermission({
+ providerName: provider.name,
+ credentials: value,
+ })}
+ onRemove={async () => removePermission(provider.name)}
/>
)}
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 ad9760feff..b40f264967 100644
--- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx
@@ -13,6 +13,8 @@ import Description from '../card/base/description'
import Icon from '../card/base/card-icon'
import Title from '../card/base/title'
import OrgInfo from '../card/base/org-info'
+import { useGitHubReleases } from '../install-plugin/hooks'
+import { compareVersion, getLatestVersion } from '@/utils/semver'
import OperationDropdown from './operation-dropdown'
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
import ActionButton from '@/app/components/base/action-button'
@@ -20,10 +22,13 @@ import Button from '@/app/components/base/button'
import Badge from '@/app/components/base/badge'
import Confirm from '@/app/components/base/confirm'
import Tooltip from '@/app/components/base/tooltip'
+import Toast from '@/app/components/base/toast'
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
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 { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
import cn from '@/utils/classnames'
@@ -32,16 +37,18 @@ const i18nPrefix = 'plugin.action'
type Props = {
detail: PluginDetail
onHide: () => void
- onDelete: () => void
+ onUpdate: () => void
}
const DetailHeader = ({
detail,
onHide,
- onDelete,
+ onUpdate,
}: Props) => {
const { t } = useTranslation()
const locale = useGetLanguage()
+ const { fetchReleases } = useGitHubReleases()
+ const { setShowUpdatePluginModal } = useModalContext()
const {
installation_id,
@@ -53,13 +60,51 @@ const DetailHeader = ({
} = detail
const { author, name, label, description, icon, verified } = detail.declaration
const isFromGitHub = source === PluginSource.github
- // Only plugin installed from GitHub need to check if it's the new version
+
const hasNewVersion = useMemo(() => {
return source === PluginSource.github && latest_version !== version
}, [source, latest_version, version])
- // #plugin TODO# update plugin
- const handleUpdate = () => { }
+ const handleUpdate = async () => {
+ try {
+ const fetchedReleases = await fetchReleases(author, name)
+ if (fetchedReleases.length === 0)
+ return
+ const versions = fetchedReleases.map(release => release.tag_name)
+ const latestVersion = getLatestVersion(versions)
+ if (compareVersion(latestVersion, version) === 1) {
+ setShowUpdatePluginModal({
+ onSaveCallback: () => {
+ onUpdate()
+ },
+ payload: {
+ type: PluginSource.github,
+ github: {
+ originalPackageInfo: {
+ id: installation_id,
+ repo: meta!.repo,
+ version: meta!.version,
+ package: meta!.package,
+ releases: fetchedReleases,
+ },
+ },
+ },
+ })
+ }
+ else {
+ Toast.notify({
+ type: 'info',
+ message: 'No new version available',
+ })
+ }
+ }
+ catch {
+ Toast.notify({
+ type: 'error',
+ message: 'Failed to compare versions',
+ })
+ }
+ }
const [isShowPluginInfo, {
setTrue: showPluginInfo,
@@ -82,9 +127,9 @@ const DetailHeader = ({
hideDeleting()
if (res.success) {
hideDeleteConfirm()
- onDelete()
+ onUpdate()
}
- }, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onDelete])
+ }, [hideDeleteConfirm, hideDeleting, installation_id, showDeleting, onUpdate])
// #plugin TODO# used in apps
// const usedInApps = 3
@@ -141,6 +186,7 @@ const DetailHeader = ({