Compare commits

..

6 Commits

16 changed files with 254 additions and 268 deletions

View File

@ -1004,11 +1004,6 @@ class RagPipelineRecommendedPluginApi(Resource):
@login_required @login_required
@account_initialization_required @account_initialization_required
def get(self): def get(self):
parser = reqparse.RequestParser()
parser.add_argument('type', type=str, location='args', required=False, default='all')
args = parser.parse_args()
type = args["type"]
rag_pipeline_service = RagPipelineService() rag_pipeline_service = RagPipelineService()
recommended_plugins = rag_pipeline_service.get_recommended_plugins(type) recommended_plugins = rag_pipeline_service.get_recommended_plugins()
return recommended_plugins return recommended_plugins

View File

@ -1,64 +0,0 @@
"""Alter table pipeline_recommended_plugins add column type
Revision ID: 6bb0832495f0
Revises: 7bb281b7a422
Create Date: 2025-12-15 16:14:38.482072
"""
from alembic import op
import models as models
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '6bb0832495f0'
down_revision = '7bb281b7a422'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('app_triggers', schema=None) as batch_op:
batch_op.alter_column('provider_name',
existing_type=sa.VARCHAR(length=255),
nullable=False,
existing_server_default=sa.text("''::character varying"))
with op.batch_alter_table('operation_logs', schema=None) as batch_op:
batch_op.alter_column('content',
existing_type=postgresql.JSON(astext_type=sa.Text()),
nullable=False)
with op.batch_alter_table('pipeline_recommended_plugins', schema=None) as batch_op:
batch_op.add_column(sa.Column('type', sa.String(length=50), nullable=True))
with op.batch_alter_table('providers', schema=None) as batch_op:
batch_op.alter_column('quota_used',
existing_type=sa.BIGINT(),
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('providers', schema=None) as batch_op:
batch_op.alter_column('quota_used',
existing_type=sa.BIGINT(),
nullable=True)
with op.batch_alter_table('pipeline_recommended_plugins', schema=None) as batch_op:
batch_op.drop_column('type')
with op.batch_alter_table('operation_logs', schema=None) as batch_op:
batch_op.alter_column('content',
existing_type=postgresql.JSON(astext_type=sa.Text()),
nullable=True)
with op.batch_alter_table('app_triggers', schema=None) as batch_op:
batch_op.alter_column('provider_name',
existing_type=sa.VARCHAR(length=255),
nullable=True,
existing_server_default=sa.text("''::character varying"))
# ### end Alembic commands ###

View File

@ -1458,7 +1458,6 @@ class PipelineRecommendedPlugin(TypeBase):
) )
plugin_id: Mapped[str] = mapped_column(LongText, nullable=False) plugin_id: Mapped[str] = mapped_column(LongText, nullable=False)
provider_name: Mapped[str] = mapped_column(LongText, nullable=False) provider_name: Mapped[str] = mapped_column(LongText, nullable=False)
type: Mapped[str] = mapped_column(sa.String(50), nullable=True)
position: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0) position: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
active: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True) active: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True)
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(

View File

@ -907,29 +907,19 @@ class WorkflowNodeExecutionModel(Base): # This model is expected to have `offlo
@property @property
def extras(self) -> dict[str, Any]: def extras(self) -> dict[str, Any]:
from core.tools.tool_manager import ToolManager from core.tools.tool_manager import ToolManager
from core.trigger.trigger_manager import TriggerManager
extras: dict[str, Any] = {} extras: dict[str, Any] = {}
execution_metadata = self.execution_metadata_dict if self.execution_metadata_dict:
if execution_metadata: if self.node_type == NodeType.TOOL and "tool_info" in self.execution_metadata_dict:
if self.node_type == NodeType.TOOL and "tool_info" in execution_metadata: tool_info: dict[str, Any] = self.execution_metadata_dict["tool_info"]
tool_info: dict[str, Any] = execution_metadata["tool_info"]
extras["icon"] = ToolManager.get_tool_icon( extras["icon"] = ToolManager.get_tool_icon(
tenant_id=self.tenant_id, tenant_id=self.tenant_id,
provider_type=tool_info["provider_type"], provider_type=tool_info["provider_type"],
provider_id=tool_info["provider_id"], provider_id=tool_info["provider_id"],
) )
elif self.node_type == NodeType.DATASOURCE and "datasource_info" in execution_metadata: elif self.node_type == NodeType.DATASOURCE and "datasource_info" in self.execution_metadata_dict:
datasource_info = execution_metadata["datasource_info"] datasource_info = self.execution_metadata_dict["datasource_info"]
extras["icon"] = datasource_info.get("icon") extras["icon"] = datasource_info.get("icon")
elif self.node_type == NodeType.TRIGGER_PLUGIN and "trigger_info" in execution_metadata:
trigger_info = execution_metadata["trigger_info"] or {}
provider_id = trigger_info.get("provider_id")
if provider_id:
extras["icon"] = TriggerManager.get_trigger_plugin_icon(
tenant_id=self.tenant_id,
provider_id=provider_id,
)
return extras return extras
def _get_offload_by_type(self, type_: ExecutionOffLoadType) -> Optional["WorkflowNodeExecutionOffload"]: def _get_offload_by_type(self, type_: ExecutionOffLoadType) -> Optional["WorkflowNodeExecutionOffload"]:

View File

@ -1248,14 +1248,12 @@ class RagPipelineService:
session.commit() session.commit()
return workflow_node_execution_db_model return workflow_node_execution_db_model
def get_recommended_plugins(self, type: str) -> dict: def get_recommended_plugins(self) -> dict:
# Query active recommended plugins # Query active recommended plugins
query = db.session.query(PipelineRecommendedPlugin).where(PipelineRecommendedPlugin.active == True)
if type and type != "all":
query = query.where(PipelineRecommendedPlugin.type == type)
pipeline_recommended_plugins = ( pipeline_recommended_plugins = (
query.order_by(PipelineRecommendedPlugin.position.asc()) db.session.query(PipelineRecommendedPlugin)
.where(PipelineRecommendedPlugin.active == True)
.order_by(PipelineRecommendedPlugin.position.asc())
.all() .all()
) )

View File

@ -1,39 +1,28 @@
import { import {
useCallback,
useEffect, useEffect,
useMemo, useMemo,
useState,
} from 'react' } from 'react'
import { import {
useMarketplacePlugins, useMarketplacePlugins,
useMarketplacePluginsByCollectionId,
} from '@/app/components/plugins/marketplace/hooks' } from '@/app/components/plugins/marketplace/hooks'
import type { Plugin } from '@/app/components/plugins/types'
import { PluginCategoryEnum } from '@/app/components/plugins/types' import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils'
export const useMarketplaceAllPlugins = (providers: any[], searchText: string) => { export const useMarketplaceAllPlugins = (providers: any[], searchText: string) => {
const exclude = useMemo(() => { const exclude = useMemo(() => {
return providers.map(provider => provider.plugin_id) return providers.map(provider => provider.plugin_id)
}, [providers]) }, [providers])
const [collectionPlugins, setCollectionPlugins] = useState<Plugin[]>([]) const {
plugins: collectionPlugins = [],
isLoading: isCollectionLoading,
} = useMarketplacePluginsByCollectionId('__datasource-settings-pinned-datasources')
const { const {
plugins, plugins,
queryPlugins, queryPlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
isLoading, isLoading: isPluginsLoading,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const getCollectionPlugins = useCallback(async () => {
const collectionPlugins = await getMarketplacePluginsByCollectionId('__datasource-settings-pinned-datasources')
setCollectionPlugins(collectionPlugins)
}, [])
useEffect(() => {
getCollectionPlugins()
}, [getCollectionPlugins])
useEffect(() => { useEffect(() => {
if (searchText) { if (searchText) {
queryPluginsWithDebounced({ queryPluginsWithDebounced({
@ -75,6 +64,6 @@ export const useMarketplaceAllPlugins = (providers: any[], searchText: string) =
return { return {
plugins: allPlugins, plugins: allPlugins,
isLoading, isLoading: isCollectionLoading || isPluginsLoading,
} }
} }

View File

@ -33,10 +33,9 @@ import {
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { import {
useMarketplacePlugins, useMarketplacePlugins,
useMarketplacePluginsByCollectionId,
} from '@/app/components/plugins/marketplace/hooks' } from '@/app/components/plugins/marketplace/hooks'
import type { Plugin } from '@/app/components/plugins/types'
import { PluginCategoryEnum } from '@/app/components/plugins/types' import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils'
import { useModalContextSelector } from '@/context/modal-context' import { useModalContextSelector } from '@/context/modal-context'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card' import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
@ -255,25 +254,17 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText:
const exclude = useMemo(() => { const exclude = useMemo(() => {
return providers.map(provider => provider.provider.replace(/(.+)\/([^/]+)$/, '$1')) return providers.map(provider => provider.provider.replace(/(.+)\/([^/]+)$/, '$1'))
}, [providers]) }, [providers])
const [collectionPlugins, setCollectionPlugins] = useState<Plugin[]>([]) const {
plugins: collectionPlugins = [],
isLoading: isCollectionLoading,
} = useMarketplacePluginsByCollectionId('__model-settings-pinned-models')
const { const {
plugins, plugins,
queryPlugins, queryPlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
isLoading, isLoading: isPluginsLoading,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const getCollectionPlugins = useCallback(async () => {
const collectionPlugins = await getMarketplacePluginsByCollectionId('__model-settings-pinned-models')
setCollectionPlugins(collectionPlugins)
}, [])
useEffect(() => {
getCollectionPlugins()
}, [getCollectionPlugins])
useEffect(() => { useEffect(() => {
if (searchText) { if (searchText) {
queryPluginsWithDebounced({ queryPluginsWithDebounced({
@ -315,7 +306,7 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText:
return { return {
plugins: allPlugins, plugins: allPlugins,
isLoading, isLoading: isCollectionLoading || isPluginsLoading,
} }
} }

View File

@ -2,3 +2,5 @@ export const DEFAULT_SORT = {
sortBy: 'install_count', sortBy: 'install_count',
sortOrder: 'DESC', sortOrder: 'DESC',
} }
export const SCROLL_BOTTOM_THRESHOLD = 100

View File

@ -50,7 +50,7 @@ export type MarketplaceContextValue = {
activePluginType: string activePluginType: string
handleActivePluginTypeChange: (type: string) => void handleActivePluginTypeChange: (type: string) => void
page: number page: number
handlePageChange: (page: number) => void handlePageChange: () => void
plugins?: Plugin[] plugins?: Plugin[]
pluginsTotal?: number pluginsTotal?: number
resetPlugins: () => void resetPlugins: () => void
@ -128,8 +128,6 @@ export const MarketplaceContextProvider = ({
const filterPluginTagsRef = useRef(filterPluginTags) const filterPluginTagsRef = useRef(filterPluginTags)
const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams) const [activePluginType, setActivePluginType] = useState(categoryFromSearchParams)
const activePluginTypeRef = useRef(activePluginType) const activePluginTypeRef = useRef(activePluginType)
const [page, setPage] = useState(1)
const pageRef = useRef(page)
const [sort, setSort] = useState(DEFAULT_SORT) const [sort, setSort] = useState(DEFAULT_SORT)
const sortRef = useRef(sort) const sortRef = useRef(sort)
const { const {
@ -149,7 +147,11 @@ export const MarketplaceContextProvider = ({
queryPluginsWithDebounced, queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced, cancelQueryPluginsWithDebounced,
isLoading: isPluginsLoading, isLoading: isPluginsLoading,
fetchNextPage: fetchNextPluginsPage,
hasNextPage: hasNextPluginsPage,
page: pluginsPage,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const page = Math.max(pluginsPage || 0, 1)
useEffect(() => { useEffect(() => {
if (queryFromSearchParams || hasValidTags || hasValidCategory) { if (queryFromSearchParams || hasValidTags || hasValidCategory) {
@ -160,7 +162,6 @@ export const MarketplaceContextProvider = ({
sortBy: sortRef.current.sortBy, sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
const url = new URL(window.location.href) const url = new URL(window.location.href)
if (searchParams?.language) if (searchParams?.language)
@ -221,7 +222,6 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
} }
else { else {
@ -233,7 +233,6 @@ export const MarketplaceContextProvider = ({
sortOrder: sortRef.current.sortOrder, sortOrder: sortRef.current.sortOrder,
exclude, exclude,
type: getMarketplaceListFilterType(activePluginTypeRef.current), type: getMarketplaceListFilterType(activePluginTypeRef.current),
page: pageRef.current,
}) })
} }
}, [exclude, queryPluginsWithDebounced, queryPlugins, handleUpdateSearchParams]) }, [exclude, queryPluginsWithDebounced, queryPlugins, handleUpdateSearchParams])
@ -252,8 +251,6 @@ export const MarketplaceContextProvider = ({
const handleSearchPluginTextChange = useCallback((text: string) => { const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text) setSearchPluginText(text)
searchPluginTextRef.current = text searchPluginTextRef.current = text
setPage(1)
pageRef.current = 1
handleQuery(true) handleQuery(true)
}, [handleQuery]) }, [handleQuery])
@ -261,8 +258,6 @@ export const MarketplaceContextProvider = ({
const handleFilterPluginTagsChange = useCallback((tags: string[]) => { const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags) setFilterPluginTags(tags)
filterPluginTagsRef.current = tags filterPluginTagsRef.current = tags
setPage(1)
pageRef.current = 1
handleQuery() handleQuery()
}, [handleQuery]) }, [handleQuery])
@ -270,8 +265,6 @@ export const MarketplaceContextProvider = ({
const handleActivePluginTypeChange = useCallback((type: string) => { const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type) setActivePluginType(type)
activePluginTypeRef.current = type activePluginTypeRef.current = type
setPage(1)
pageRef.current = 1
handleQuery() handleQuery()
}, [handleQuery]) }, [handleQuery])
@ -279,20 +272,14 @@ export const MarketplaceContextProvider = ({
const handleSortChange = useCallback((sort: PluginsSort) => { const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort) setSort(sort)
sortRef.current = sort sortRef.current = sort
setPage(1)
pageRef.current = 1
handleQueryPlugins() handleQueryPlugins()
}, [handleQueryPlugins]) }, [handleQueryPlugins])
const handlePageChange = useCallback(() => { const handlePageChange = useCallback(() => {
if (pluginsTotal && plugins && pluginsTotal > plugins.length) { if (hasNextPluginsPage)
setPage(pageRef.current + 1) fetchNextPluginsPage()
pageRef.current++ }, [fetchNextPluginsPage, hasNextPluginsPage])
handleQueryPlugins()
}
}, [handleQueryPlugins, plugins, pluginsTotal])
const handleMoreClick = useCallback((searchParams: SearchParamsFromCollection) => { const handleMoreClick = useCallback((searchParams: SearchParamsFromCollection) => {
setSearchPluginText(searchParams?.query || '') setSearchPluginText(searchParams?.query || '')
@ -305,9 +292,6 @@ export const MarketplaceContextProvider = ({
sortBy: searchParams?.sort_by || DEFAULT_SORT.sortBy, sortBy: searchParams?.sort_by || DEFAULT_SORT.sortBy,
sortOrder: searchParams?.sort_order || DEFAULT_SORT.sortOrder, sortOrder: searchParams?.sort_order || DEFAULT_SORT.sortOrder,
} }
setPage(1)
pageRef.current = 1
handleQueryPlugins() handleQueryPlugins()
}, [handleQueryPlugins]) }, [handleQueryPlugins])

View File

@ -3,6 +3,11 @@ import {
useEffect, useEffect,
useState, useState,
} from 'react' } from 'react'
import {
useInfiniteQuery,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks' import { useDebounceFn } from 'ahooks'
import type { import type {
@ -16,39 +21,41 @@ import type {
import { import {
getFormattedPlugin, getFormattedPlugin,
getMarketplaceCollectionsAndPlugins, getMarketplaceCollectionsAndPlugins,
getMarketplacePluginsByCollectionId,
} from './utils' } from './utils'
import { SCROLL_BOTTOM_THRESHOLD } from './constants'
import i18n from '@/i18n-config/i18next-config' import i18n from '@/i18n-config/i18next-config'
import { import { postMarketplace } from '@/service/base'
useMutationPluginsFromMarketplace, import type { PluginsFromMarketplaceResponse } from '@/app/components/plugins/types'
} from '@/service/use-plugins'
export const useMarketplaceCollectionsAndPlugins = () => { export const useMarketplaceCollectionsAndPlugins = () => {
const [isLoading, setIsLoading] = useState(false) const [queryParams, setQueryParams] = useState<CollectionsAndPluginsSearchParams>()
const [isSuccess, setIsSuccess] = useState(false) const [marketplaceCollectionsOverride, setMarketplaceCollections] = useState<MarketplaceCollection[]>()
const [marketplaceCollections, setMarketplaceCollections] = useState<MarketplaceCollection[]>() const [marketplaceCollectionPluginsMapOverride, setMarketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>()
const [marketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>()
const queryMarketplaceCollectionsAndPlugins = useCallback(async (query?: CollectionsAndPluginsSearchParams) => { const {
try { data,
setIsLoading(true) isFetching,
setIsSuccess(false) isSuccess,
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins(query) isPending,
setIsLoading(false) } = useQuery({
setIsSuccess(true) queryKey: ['marketplaceCollectionsAndPlugins', queryParams],
setMarketplaceCollections(marketplaceCollections) queryFn: ({ signal }) => getMarketplaceCollectionsAndPlugins(queryParams, { signal }),
setMarketplaceCollectionPluginsMap(marketplaceCollectionPluginsMap) enabled: queryParams !== undefined,
} staleTime: 1000 * 60 * 5,
// eslint-disable-next-line unused-imports/no-unused-vars gcTime: 1000 * 60 * 10,
catch (e) { retry: false,
setIsLoading(false) })
setIsSuccess(false)
} const queryMarketplaceCollectionsAndPlugins = useCallback((query?: CollectionsAndPluginsSearchParams) => {
setQueryParams(query ? { ...query } : {})
}, []) }, [])
const isLoading = !!queryParams && (isFetching || isPending)
return { return {
marketplaceCollections, marketplaceCollections: marketplaceCollectionsOverride ?? data?.marketplaceCollections,
setMarketplaceCollections, setMarketplaceCollections,
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap: marketplaceCollectionPluginsMapOverride ?? data?.marketplaceCollectionPluginsMap,
setMarketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap,
queryMarketplaceCollectionsAndPlugins, queryMarketplaceCollectionsAndPlugins,
isLoading, isLoading,
@ -56,37 +63,128 @@ export const useMarketplaceCollectionsAndPlugins = () => {
} }
} }
export const useMarketplacePlugins = () => { export const useMarketplacePluginsByCollectionId = (
collectionId?: string,
query?: CollectionsAndPluginsSearchParams,
) => {
const { const {
data, data,
mutateAsync, isFetching,
reset, isSuccess,
isPending, isPending,
} = useMutationPluginsFromMarketplace() } = useQuery({
queryKey: ['marketplaceCollectionPlugins', collectionId, query],
queryFn: ({ signal }) => {
if (!collectionId)
return Promise.resolve<Plugin[]>([])
return getMarketplacePluginsByCollectionId(collectionId, query, { signal })
},
enabled: !!collectionId,
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 10,
retry: false,
})
const [prevPlugins, setPrevPlugins] = useState<Plugin[] | undefined>() return {
plugins: data || [],
isLoading: !!collectionId && (isFetching || isPending),
isSuccess,
}
}
export const useMarketplacePlugins = () => {
const queryClient = useQueryClient()
const [queryParams, setQueryParams] = useState<PluginsSearchParams>()
const normalizeParams = useCallback((pluginsSearchParams: PluginsSearchParams) => {
const pageSize = pluginsSearchParams.pageSize || 40
return {
...pluginsSearchParams,
pageSize,
}
}, [])
const marketplacePluginsQuery = useInfiniteQuery({
queryKey: ['marketplacePlugins', queryParams],
queryFn: async ({ pageParam = 1, signal }) => {
if (!queryParams) {
return {
plugins: [] as Plugin[],
total: 0,
page: 1,
pageSize: 40,
}
}
const params = normalizeParams(queryParams)
const {
query,
sortBy,
sortOrder,
category,
tags,
exclude,
type,
pageSize,
} = params
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
try {
const res = await postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
body: {
page: pageParam,
page_size: pageSize,
query,
sort_by: sortBy,
sort_order: sortOrder,
category: category !== 'all' ? category : '',
tags,
exclude,
type,
},
signal,
})
const resPlugins = res.data.bundles || res.data.plugins || []
return {
plugins: resPlugins.map(plugin => getFormattedPlugin(plugin)),
total: res.data.total,
page: pageParam,
pageSize,
}
}
catch {
return {
plugins: [],
total: 0,
page: pageParam,
pageSize,
}
}
},
getNextPageParam: (lastPage) => {
const nextPage = lastPage.page + 1
const loaded = lastPage.page * lastPage.pageSize
return loaded < (lastPage.total || 0) ? nextPage : undefined
},
initialPageParam: 1,
enabled: !!queryParams,
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 10,
retry: false,
})
const resetPlugins = useCallback(() => { const resetPlugins = useCallback(() => {
reset() setQueryParams(undefined)
setPrevPlugins(undefined) queryClient.removeQueries({
}, [reset]) queryKey: ['marketplacePlugins'],
})
}, [queryClient])
const handleUpdatePlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => { const handleUpdatePlugins = useCallback((pluginsSearchParams: PluginsSearchParams) => {
mutateAsync(pluginsSearchParams).then((res) => { setQueryParams(normalizeParams(pluginsSearchParams))
const currentPage = pluginsSearchParams.page || 1 }, [normalizeParams])
const resPlugins = res.data.bundles || res.data.plugins
if (currentPage > 1) {
setPrevPlugins(prevPlugins => [...(prevPlugins || []), ...resPlugins.map((plugin) => {
return getFormattedPlugin(plugin)
})])
}
else {
setPrevPlugins(resPlugins.map((plugin) => {
return getFormattedPlugin(plugin)
}))
}
})
}, [mutateAsync])
const { run: queryPluginsWithDebounced, cancel: cancelQueryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => { const { run: queryPluginsWithDebounced, cancel: cancelQueryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => {
handleUpdatePlugins(pluginsSearchParams) handleUpdatePlugins(pluginsSearchParams)
@ -94,14 +192,29 @@ export const useMarketplacePlugins = () => {
wait: 500, wait: 500,
}) })
const hasQuery = !!queryParams
const hasData = marketplacePluginsQuery.data !== undefined
const plugins = hasQuery && hasData
? marketplacePluginsQuery.data.pages.flatMap(page => page.plugins)
: undefined
const total = hasQuery && hasData ? marketplacePluginsQuery.data.pages?.[0]?.total : undefined
const isPluginsLoading = hasQuery && (
marketplacePluginsQuery.isPending
|| (marketplacePluginsQuery.isFetching && !marketplacePluginsQuery.data)
)
return { return {
plugins: prevPlugins, plugins,
total: data?.data?.total, total,
resetPlugins, resetPlugins,
queryPlugins: handleUpdatePlugins, queryPlugins: handleUpdatePlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced, cancelQueryPluginsWithDebounced,
isLoading: isPending, isLoading: isPluginsLoading,
isFetchingNextPage: marketplacePluginsQuery.isFetchingNextPage,
hasNextPage: marketplacePluginsQuery.hasNextPage,
fetchNextPage: marketplacePluginsQuery.fetchNextPage,
page: marketplacePluginsQuery.data?.pages?.length || (marketplacePluginsQuery.isPending && hasQuery ? 1 : 0),
} }
} }
@ -131,7 +244,7 @@ export const useMarketplaceContainerScroll = (
scrollHeight, scrollHeight,
clientHeight, clientHeight,
} = target } = target
if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0) if (scrollTop + clientHeight >= scrollHeight - SCROLL_BOTTOM_THRESHOLD && scrollTop > 0)
callback() callback()
}, [callback]) }, [callback])

View File

@ -4,7 +4,8 @@ import IntersectionLine from './intersection-line'
import SearchBoxWrapper from './search-box/search-box-wrapper' import SearchBoxWrapper from './search-box/search-box-wrapper'
import PluginTypeSwitch from './plugin-type-switch' import PluginTypeSwitch from './plugin-type-switch'
import ListWrapper from './list/list-wrapper' import ListWrapper from './list/list-wrapper'
import type { SearchParams } from './types' import type { MarketplaceCollection, SearchParams } from './types'
import type { Plugin } from '@/app/components/plugins/types'
import { getMarketplaceCollectionsAndPlugins } from './utils' import { getMarketplaceCollectionsAndPlugins } from './utils'
import { TanstackQueryInitializer } from '@/context/query-client' import { TanstackQueryInitializer } from '@/context/query-client'
@ -30,8 +31,8 @@ const Marketplace = async ({
scrollContainerId, scrollContainerId,
showSearchParams = true, showSearchParams = true,
}: MarketplaceProps) => { }: MarketplaceProps) => {
let marketplaceCollections: any = [] let marketplaceCollections: MarketplaceCollection[] = []
let marketplaceCollectionPluginsMap = {} let marketplaceCollectionPluginsMap: Record<string, Plugin[]> = {}
if (!shouldExclude) { if (!shouldExclude) {
const marketplaceCollectionsAndPluginsData = await getMarketplaceCollectionsAndPlugins() const marketplaceCollectionsAndPluginsData = await getMarketplaceCollectionsAndPlugins()
marketplaceCollections = marketplaceCollectionsAndPluginsData.marketplaceCollections marketplaceCollections = marketplaceCollectionsAndPluginsData.marketplaceCollections

View File

@ -28,13 +28,20 @@ const ListWrapper = ({
const isLoading = useMarketplaceContext(v => v.isLoading) const isLoading = useMarketplaceContext(v => v.isLoading)
const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections) const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections)
const handleQueryPlugins = useMarketplaceContext(v => v.handleQueryPlugins) const handleQueryPlugins = useMarketplaceContext(v => v.handleQueryPlugins)
const searchPluginText = useMarketplaceContext(v => v.searchPluginText)
const filterPluginTags = useMarketplaceContext(v => v.filterPluginTags)
const page = useMarketplaceContext(v => v.page) const page = useMarketplaceContext(v => v.page)
const handleMoreClick = useMarketplaceContext(v => v.handleMoreClick) const handleMoreClick = useMarketplaceContext(v => v.handleMoreClick)
useEffect(() => { useEffect(() => {
if (!marketplaceCollectionsFromClient?.length && isSuccessCollections) if (
!marketplaceCollectionsFromClient?.length
&& isSuccessCollections
&& !searchPluginText
&& !filterPluginTags.length
)
handleQueryPlugins() handleQueryPlugins()
}, [handleQueryPlugins, marketplaceCollections, marketplaceCollectionsFromClient, isSuccessCollections]) }, [handleQueryPlugins, marketplaceCollections, marketplaceCollectionsFromClient, isSuccessCollections, searchPluginText, filterPluginTags])
return ( return (
<div <div

View File

@ -13,6 +13,14 @@ import {
} from '@/config' } from '@/config'
import { getMarketplaceUrl } from '@/utils/var' import { getMarketplaceUrl } from '@/utils/var'
type MarketplaceFetchOptions = {
signal?: AbortSignal
}
const getMarketplaceHeaders = () => new Headers({
'X-Dify-Version': !IS_MARKETPLACE ? APP_VERSION : '999.0.0',
})
export const getPluginIconInMarketplace = (plugin: Plugin) => { export const getPluginIconInMarketplace = (plugin: Plugin) => {
if (plugin.type === 'bundle') if (plugin.type === 'bundle')
return `${MARKETPLACE_API_PREFIX}/bundles/${plugin.org}/${plugin.name}/icon` return `${MARKETPLACE_API_PREFIX}/bundles/${plugin.org}/${plugin.name}/icon`
@ -46,20 +54,23 @@ export const getPluginDetailLinkInMarketplace = (plugin: Plugin) => {
return `/plugins/${plugin.org}/${plugin.name}` return `/plugins/${plugin.org}/${plugin.name}`
} }
export const getMarketplacePluginsByCollectionId = async (collectionId: string, query?: CollectionsAndPluginsSearchParams) => { export const getMarketplacePluginsByCollectionId = async (
let plugins: Plugin[] collectionId: string,
query?: CollectionsAndPluginsSearchParams,
options?: MarketplaceFetchOptions,
) => {
let plugins: Plugin[] = []
try { try {
const url = `${MARKETPLACE_API_PREFIX}/collections/${collectionId}/plugins` const url = `${MARKETPLACE_API_PREFIX}/collections/${collectionId}/plugins`
const headers = new Headers({ const headers = getMarketplaceHeaders()
'X-Dify-Version': !IS_MARKETPLACE ? APP_VERSION : '999.0.0',
})
const marketplaceCollectionPluginsData = await globalThis.fetch( const marketplaceCollectionPluginsData = await globalThis.fetch(
url, url,
{ {
cache: 'no-store', cache: 'no-store',
method: 'POST', method: 'POST',
headers, headers,
signal: options?.signal,
body: JSON.stringify({ body: JSON.stringify({
category: query?.category, category: query?.category,
exclude: query?.exclude, exclude: query?.exclude,
@ -68,9 +79,7 @@ export const getMarketplacePluginsByCollectionId = async (collectionId: string,
}, },
) )
const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json() const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json()
plugins = marketplaceCollectionPluginsDataJson.data.plugins.map((plugin: Plugin) => { plugins = (marketplaceCollectionPluginsDataJson.data.plugins || []).map((plugin: Plugin) => getFormattedPlugin(plugin))
return getFormattedPlugin(plugin)
})
} }
// eslint-disable-next-line unused-imports/no-unused-vars // eslint-disable-next-line unused-imports/no-unused-vars
catch (e) { catch (e) {
@ -80,23 +89,31 @@ export const getMarketplacePluginsByCollectionId = async (collectionId: string,
return plugins return plugins
} }
export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAndPluginsSearchParams) => { export const getMarketplaceCollectionsAndPlugins = async (
let marketplaceCollections = [] as MarketplaceCollection[] query?: CollectionsAndPluginsSearchParams,
let marketplaceCollectionPluginsMap = {} as Record<string, Plugin[]> options?: MarketplaceFetchOptions,
) => {
let marketplaceCollections: MarketplaceCollection[] = []
let marketplaceCollectionPluginsMap: Record<string, Plugin[]> = {}
try { try {
let marketplaceUrl = `${MARKETPLACE_API_PREFIX}/collections?page=1&page_size=100` let marketplaceUrl = `${MARKETPLACE_API_PREFIX}/collections?page=1&page_size=100`
if (query?.condition) if (query?.condition)
marketplaceUrl += `&condition=${query.condition}` marketplaceUrl += `&condition=${query.condition}`
if (query?.type) if (query?.type)
marketplaceUrl += `&type=${query.type}` marketplaceUrl += `&type=${query.type}`
const headers = new Headers({ const headers = getMarketplaceHeaders()
'X-Dify-Version': !IS_MARKETPLACE ? APP_VERSION : '999.0.0', const marketplaceCollectionsData = await globalThis.fetch(
}) marketplaceUrl,
const marketplaceCollectionsData = await globalThis.fetch(marketplaceUrl, { headers, cache: 'no-store' }) {
headers,
cache: 'no-store',
signal: options?.signal,
},
)
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json() const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
marketplaceCollections = marketplaceCollectionsDataJson.data.collections marketplaceCollections = marketplaceCollectionsDataJson.data.collections || []
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => { await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
const plugins = await getMarketplacePluginsByCollectionId(collection.name, query) const plugins = await getMarketplacePluginsByCollectionId(collection.name, query, options)
marketplaceCollectionPluginsMap[collection.name] = plugins marketplaceCollectionPluginsMap[collection.name] = plugins
})) }))

View File

@ -3,12 +3,12 @@ import {
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState,
} from 'react' } from 'react'
import { import {
useMarketplaceCollectionsAndPlugins, useMarketplaceCollectionsAndPlugins,
useMarketplacePlugins, useMarketplacePlugins,
} from '@/app/components/plugins/marketplace/hooks' } from '@/app/components/plugins/marketplace/hooks'
import { SCROLL_BOTTOM_THRESHOLD } from '@/app/components/plugins/marketplace/constants'
import { PluginCategoryEnum } from '@/app/components/plugins/types' import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils' import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils'
import { useAllToolProviders } from '@/service/use-tools' import { useAllToolProviders } from '@/service/use-tools'
@ -31,10 +31,10 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
queryPlugins, queryPlugins,
queryPluginsWithDebounced, queryPluginsWithDebounced,
isLoading: isPluginsLoading, isLoading: isPluginsLoading,
total: pluginsTotal, fetchNextPage,
hasNextPage,
page: pluginsPage,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const [page, setPage] = useState(1)
const pageRef = useRef(page)
const searchPluginTextRef = useRef(searchPluginText) const searchPluginTextRef = useRef(searchPluginText)
const filterPluginTagsRef = useRef(filterPluginTags) const filterPluginTagsRef = useRef(filterPluginTags)
@ -44,9 +44,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
}, [searchPluginText, filterPluginTags]) }, [searchPluginText, filterPluginTags])
useEffect(() => { useEffect(() => {
if ((searchPluginText || filterPluginTags.length) && isSuccess) { if ((searchPluginText || filterPluginTags.length) && isSuccess) {
setPage(1)
pageRef.current = 1
if (searchPluginText) { if (searchPluginText) {
queryPluginsWithDebounced({ queryPluginsWithDebounced({
category: PluginCategoryEnum.tool, category: PluginCategoryEnum.tool,
@ -54,7 +51,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
tags: filterPluginTags, tags: filterPluginTags,
exclude, exclude,
type: 'plugin', type: 'plugin',
page: pageRef.current,
}) })
return return
} }
@ -64,7 +60,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
tags: filterPluginTags, tags: filterPluginTags,
exclude, exclude,
type: 'plugin', type: 'plugin',
page: pageRef.current,
}) })
} }
else { else {
@ -87,24 +82,13 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
scrollHeight, scrollHeight,
clientHeight, clientHeight,
} = target } = target
if (scrollTop + clientHeight >= scrollHeight - 5 && scrollTop > 0) { if (scrollTop + clientHeight >= scrollHeight - SCROLL_BOTTOM_THRESHOLD && scrollTop > 0) {
const searchPluginText = searchPluginTextRef.current const searchPluginText = searchPluginTextRef.current
const filterPluginTags = filterPluginTagsRef.current const filterPluginTags = filterPluginTagsRef.current
if (pluginsTotal && plugins && pluginsTotal > plugins.length && (!!searchPluginText || !!filterPluginTags.length)) { if (hasNextPage && (!!searchPluginText || !!filterPluginTags.length))
setPage(pageRef.current + 1) fetchNextPage()
pageRef.current++
queryPlugins({
category: PluginCategoryEnum.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
type: 'plugin',
page: pageRef.current,
})
} }
} }, [exclude, fetchNextPage, hasNextPage, plugins, queryPlugins])
}, [exclude, plugins, pluginsTotal, queryPlugins])
return { return {
isLoading: isLoading || isPluginsLoading, isLoading: isLoading || isPluginsLoading,
@ -112,6 +96,6 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
marketplaceCollectionPluginsMap, marketplaceCollectionPluginsMap,
plugins, plugins,
handleScroll, handleScroll,
page, page: Math.max(pluginsPage || 0, 1),
} }
} }

View File

@ -21,9 +21,6 @@ export type ConversationListResponse = {
logs: Conversation[] logs: Conversation[]
} }
export const fetchLogs = (url: string) =>
fetch(url).then<ConversationListResponse>(r => r.json())
export const CompletionParams = ['temperature', 'top_p', 'presence_penalty', 'max_token', 'stop', 'frequency_penalty'] as const export const CompletionParams = ['temperature', 'top_p', 'presence_penalty', 'max_token', 'stop', 'frequency_penalty'] as const
export type CompletionParamType = typeof CompletionParams[number] export type CompletionParamType = typeof CompletionParams[number]

View File

@ -1,17 +0,0 @@
export type User = {
id: string
firstName: string
lastName: string
name: string
phone: string
username: string
email: string
avatar: string
}
export type UserResponse = {
users: User[]
}
export const fetchUsers = (url: string) =>
fetch(url).then<UserResponse>(r => r.json())