mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 10:48:59 +08:00
feat: add pagination for plugin page (#20151)
This commit is contained in:
parent
55503ce771
commit
756f35f480
@ -41,12 +41,16 @@ class PluginListApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def get(self):
|
def get(self):
|
||||||
tenant_id = current_user.current_tenant_id
|
tenant_id = current_user.current_tenant_id
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("page", type=int, required=False, location="args", default=1)
|
||||||
|
parser.add_argument("page_size", type=int, required=False, location="args", default=256)
|
||||||
|
args = parser.parse_args()
|
||||||
try:
|
try:
|
||||||
plugins = PluginService.list(tenant_id)
|
plugins_with_total = PluginService.list_with_total(tenant_id, args["page"], args["page_size"])
|
||||||
except PluginDaemonClientSideError as e:
|
except PluginDaemonClientSideError as e:
|
||||||
raise ValueError(e)
|
raise ValueError(e)
|
||||||
|
|
||||||
return jsonable_encoder({"plugins": plugins})
|
return jsonable_encoder({"plugins": plugins_with_total.list, "total": plugins_with_total.total})
|
||||||
|
|
||||||
|
|
||||||
class PluginListLatestVersionsApi(Resource):
|
class PluginListLatestVersionsApi(Resource):
|
||||||
|
@ -9,7 +9,7 @@ from core.agent.plugin_entities import AgentProviderEntityWithPlugin
|
|||||||
from core.model_runtime.entities.model_entities import AIModelEntity
|
from core.model_runtime.entities.model_entities import AIModelEntity
|
||||||
from core.model_runtime.entities.provider_entities import ProviderEntity
|
from core.model_runtime.entities.provider_entities import ProviderEntity
|
||||||
from core.plugin.entities.base import BasePluginEntity
|
from core.plugin.entities.base import BasePluginEntity
|
||||||
from core.plugin.entities.plugin import PluginDeclaration
|
from core.plugin.entities.plugin import PluginDeclaration, PluginEntity
|
||||||
from core.tools.entities.common_entities import I18nObject
|
from core.tools.entities.common_entities import I18nObject
|
||||||
from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin
|
from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin
|
||||||
|
|
||||||
@ -167,3 +167,8 @@ class PluginOAuthAuthorizationUrlResponse(BaseModel):
|
|||||||
|
|
||||||
class PluginOAuthCredentialsResponse(BaseModel):
|
class PluginOAuthCredentialsResponse(BaseModel):
|
||||||
credentials: Mapping[str, Any] = Field(description="The credentials of the OAuth.")
|
credentials: Mapping[str, Any] = Field(description="The credentials of the OAuth.")
|
||||||
|
|
||||||
|
|
||||||
|
class PluginListResponse(BaseModel):
|
||||||
|
list: list[PluginEntity]
|
||||||
|
total: int
|
||||||
|
@ -9,7 +9,12 @@ from core.plugin.entities.plugin import (
|
|||||||
PluginInstallation,
|
PluginInstallation,
|
||||||
PluginInstallationSource,
|
PluginInstallationSource,
|
||||||
)
|
)
|
||||||
from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginInstallTaskStartResponse, PluginUploadResponse
|
from core.plugin.entities.plugin_daemon import (
|
||||||
|
PluginInstallTask,
|
||||||
|
PluginInstallTaskStartResponse,
|
||||||
|
PluginListResponse,
|
||||||
|
PluginUploadResponse,
|
||||||
|
)
|
||||||
from core.plugin.impl.base import BasePluginClient
|
from core.plugin.impl.base import BasePluginClient
|
||||||
|
|
||||||
|
|
||||||
@ -27,11 +32,20 @@ class PluginInstaller(BasePluginClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def list_plugins(self, tenant_id: str) -> list[PluginEntity]:
|
def list_plugins(self, tenant_id: str) -> list[PluginEntity]:
|
||||||
|
result = self._request_with_plugin_daemon_response(
|
||||||
|
"GET",
|
||||||
|
f"plugin/{tenant_id}/management/list",
|
||||||
|
PluginListResponse,
|
||||||
|
params={"page": 1, "page_size": 256},
|
||||||
|
)
|
||||||
|
return result.list
|
||||||
|
|
||||||
|
def list_plugins_with_total(self, tenant_id: str, page: int, page_size: int) -> PluginListResponse:
|
||||||
return self._request_with_plugin_daemon_response(
|
return self._request_with_plugin_daemon_response(
|
||||||
"GET",
|
"GET",
|
||||||
f"plugin/{tenant_id}/management/list",
|
f"plugin/{tenant_id}/management/list",
|
||||||
list[PluginEntity],
|
PluginListResponse,
|
||||||
params={"page": 1, "page_size": 256},
|
params={"page": page, "page_size": page_size},
|
||||||
)
|
)
|
||||||
|
|
||||||
def upload_pkg(
|
def upload_pkg(
|
||||||
|
@ -17,7 +17,7 @@ from core.plugin.entities.plugin import (
|
|||||||
PluginInstallation,
|
PluginInstallation,
|
||||||
PluginInstallationSource,
|
PluginInstallationSource,
|
||||||
)
|
)
|
||||||
from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginUploadResponse
|
from core.plugin.entities.plugin_daemon import PluginInstallTask, PluginListResponse, PluginUploadResponse
|
||||||
from core.plugin.impl.asset import PluginAssetManager
|
from core.plugin.impl.asset import PluginAssetManager
|
||||||
from core.plugin.impl.debugging import PluginDebuggingClient
|
from core.plugin.impl.debugging import PluginDebuggingClient
|
||||||
from core.plugin.impl.plugin import PluginInstaller
|
from core.plugin.impl.plugin import PluginInstaller
|
||||||
@ -110,6 +110,15 @@ class PluginService:
|
|||||||
plugins = manager.list_plugins(tenant_id)
|
plugins = manager.list_plugins(tenant_id)
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def list_with_total(tenant_id: str, page: int, page_size: int) -> PluginListResponse:
|
||||||
|
"""
|
||||||
|
list all plugins of the tenant
|
||||||
|
"""
|
||||||
|
manager = PluginInstaller()
|
||||||
|
plugins = manager.list_plugins_with_total(tenant_id, page, page_size)
|
||||||
|
return plugins
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def list_installations_from_ids(tenant_id: str, ids: Sequence[str]) -> Sequence[PluginInstallation]:
|
def list_installations_from_ids(tenant_id: str, ids: Sequence[str]) -> Sequence[PluginInstallation]:
|
||||||
"""
|
"""
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { FilterState } from './filter-management'
|
import type { FilterState } from './filter-management'
|
||||||
import FilterManagement from './filter-management'
|
import FilterManagement from './filter-management'
|
||||||
import List from './list'
|
import List from './list'
|
||||||
import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
import { useInstalledLatestVersion, useInstalledPluginListWithPagination, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||||
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
||||||
import { usePluginPageContext } from './context'
|
import { usePluginPageContext } from './context'
|
||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
import Empty from './empty'
|
import Empty from './empty'
|
||||||
import Loading from '../../base/loading'
|
import Loading from '../../base/loading'
|
||||||
import { PluginSource } from '../types'
|
import { PluginSource } from '../types'
|
||||||
|
|
||||||
const PluginsPanel = () => {
|
const PluginsPanel = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const filters = usePluginPageContext(v => v.filters) as FilterState
|
const filters = usePluginPageContext(v => v.filters) as FilterState
|
||||||
const setFilters = usePluginPageContext(v => v.setFilters)
|
const setFilters = usePluginPageContext(v => v.setFilters)
|
||||||
const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList()
|
const { data: pluginList, isLoading: isPluginListLoading, isFetching, isLastPage, loadNextPage } = useInstalledPluginListWithPagination()
|
||||||
const { data: installedLatestVersion } = useInstalledLatestVersion(
|
const { data: installedLatestVersion } = useInstalledLatestVersion(
|
||||||
pluginList?.plugins
|
pluginList?.plugins
|
||||||
.filter(plugin => plugin.source === PluginSource.marketplace)
|
.filter(plugin => plugin.source === PluginSource.marketplace)
|
||||||
@ -64,10 +67,16 @@ const PluginsPanel = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isPluginListLoading ? <Loading type='app' /> : (filteredList?.length ?? 0) > 0 ? (
|
{isPluginListLoading ? <Loading type='app' /> : (filteredList?.length ?? 0) > 0 ? (
|
||||||
<div className='flex grow flex-wrap content-start items-start gap-2 self-stretch px-12'>
|
<div className='flex grow flex-wrap content-start items-start justify-center gap-2 self-stretch px-12'>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<List pluginList={filteredList || []} />
|
<List pluginList={filteredList || []} />
|
||||||
</div>
|
</div>
|
||||||
|
{!isLastPage && !isFetching && (
|
||||||
|
<Button onClick={loadNextPage}>
|
||||||
|
{t('workflow.common.loadMore')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isFetching && <div className='system-md-semibold text-text-secondary'>{t('appLog.detail.loading')}</div>}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Empty />
|
<Empty />
|
||||||
|
@ -325,6 +325,11 @@ export type InstalledPluginListResponse = {
|
|||||||
plugins: PluginDetail[]
|
plugins: PluginDetail[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InstalledPluginListWithTotalResponse = {
|
||||||
|
plugins: PluginDetail[]
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
export type InstalledLatestVersionResponse = {
|
export type InstalledLatestVersionResponse = {
|
||||||
versions: {
|
versions: {
|
||||||
[plugin_id: string]: {
|
[plugin_id: string]: {
|
||||||
|
@ -11,6 +11,7 @@ import type {
|
|||||||
InstallPackageResponse,
|
InstallPackageResponse,
|
||||||
InstalledLatestVersionResponse,
|
InstalledLatestVersionResponse,
|
||||||
InstalledPluginListResponse,
|
InstalledPluginListResponse,
|
||||||
|
InstalledPluginListWithTotalResponse,
|
||||||
PackageDependency,
|
PackageDependency,
|
||||||
Permissions,
|
Permissions,
|
||||||
Plugin,
|
Plugin,
|
||||||
@ -33,6 +34,7 @@ import type {
|
|||||||
import { get, getMarketplace, post, postMarketplace } from './base'
|
import { get, getMarketplace, post, postMarketplace } from './base'
|
||||||
import type { MutateOptions, QueryOptions } from '@tanstack/react-query'
|
import type { MutateOptions, QueryOptions } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
|
useInfiniteQuery,
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
@ -74,6 +76,53 @@ export const useInstalledPluginList = (disable?: boolean) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useInstalledPluginListWithPagination = (pageSize = 100) => {
|
||||||
|
const fetchPlugins = async ({ pageParam = 1 }) => {
|
||||||
|
const response = await get<InstalledPluginListWithTotalResponse>(
|
||||||
|
`/workspaces/current/plugin/list?page=${pageParam}&page_size=${pageSize}`,
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetchingNextPage,
|
||||||
|
isLoading,
|
||||||
|
} = useInfiniteQuery({
|
||||||
|
queryKey: ['installed-plugins', pageSize],
|
||||||
|
queryFn: fetchPlugins,
|
||||||
|
getNextPageParam: (lastPage, pages) => {
|
||||||
|
const totalItems = lastPage.total
|
||||||
|
const currentPage = pages.length
|
||||||
|
const itemsLoaded = currentPage * pageSize
|
||||||
|
|
||||||
|
if (itemsLoaded >= totalItems)
|
||||||
|
return
|
||||||
|
|
||||||
|
return currentPage + 1
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const plugins = data?.pages.flatMap(page => page.plugins) ?? []
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
plugins,
|
||||||
|
},
|
||||||
|
isLastPage: !hasNextPage,
|
||||||
|
loadNextPage: () => {
|
||||||
|
fetchNextPage()
|
||||||
|
},
|
||||||
|
isLoading,
|
||||||
|
isFetching: isFetchingNextPage,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useInstalledLatestVersion = (pluginIds: string[]) => {
|
export const useInstalledLatestVersion = (pluginIds: string[]) => {
|
||||||
return useQuery<InstalledLatestVersionResponse>({
|
return useQuery<InstalledLatestVersionResponse>({
|
||||||
queryKey: [NAME_SPACE, 'installedLatestVersion', pluginIds],
|
queryKey: [NAME_SPACE, 'installedLatestVersion', pluginIds],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user