'use client' import type { AppPagination, AppPartial, GetAppsData, } from '@dify/contracts/api/console/apps/types.gen' import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { RefObject } from 'react' import type { App } from '@/models/explore' import type { TryAppSelection } from '@/types/try-app' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { keepPreviousData, useInfiniteQuery, useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { InfiniteScrollSentinel } from '@/app/components/base/infinite-scroll-sentinel' import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { consoleQuery } from '@/service/client' import { AppModeEnum } from '@/types/app' import { AppCard } from './app-card' import { AppCardSkeleton } from './app-card-skeleton' import { APP_LIST_GRID_CLASS_NAME } from './constants' import Empty from './empty' import FirstEmptyState from './first-empty-state' import { useAppListTour } from './hooks/use-app-list-tour' import { useWorkflowOnlineUsers } from './hooks/use-workflow-online-users' import { ALL_APPS_HEADING_ID, AppListSectionHeading, StarredAppList } from './starred-app-list' const STARRED_APP_LIMIT = 100 const STEP_BY_STEP_TOUR_APP_ROW_CARD_COUNT = 4 const emptyStarredApps: AppPartial[] = [] const getPreloadDistance = (scrollContainer: Element) => Math.max(160, Math.min(scrollContainer.clientHeight * 0.25, 320)) type AppListQuery = NonNullable type AppListCatalogProps = Readonly<{ appListQuery: AppListQuery canCreateApp: boolean dragging: boolean hasActiveFilters: boolean onCreateBlank: () => void onCreateLearnDify?: (app: App) => void onCreateTemplate: () => void onImportDSL: () => void onOpenTagManagement: () => void onTryLearnDify?: (params: TryAppSelection) => void scrollViewportRef: RefObject }> type AppListCatalogContentProps = Omit & Readonly<{ appListPages: AppPagination[] hasNextPage: boolean isFetchNextPageError: boolean isError: boolean isFetching: boolean isFetchingNextPage: boolean isPlaceholderData: boolean onFetchNextPage: () => Promise starredApps: AppPartial[] systemFeatures: GetSystemFeaturesResponse }> function CatalogSkeleton() { const { t } = useTranslation() return (
$.loading, { ns: 'common' })}>
) } function AppListCatalogContent({ appListPages, canCreateApp, dragging, hasActiveFilters, hasNextPage, isFetchNextPageError, isError, isFetching, isFetchingNextPage, isPlaceholderData, onCreateBlank, onCreateLearnDify, onCreateTemplate, onFetchNextPage, onImportDSL, onOpenTagManagement, onTryLearnDify, starredApps, scrollViewportRef, systemFeatures, }: AppListCatalogContentProps) { const { t } = useTranslation() const apps = useMemo(() => appListPages.flatMap(({ data: pageApps }) => pageApps), [appListPages]) const workflowOnlineUserAppIds = useMemo(() => { const appIds = new Set() apps.forEach((app) => { if (app.mode === AppModeEnum.WORKFLOW || app.mode === AppModeEnum.ADVANCED_CHAT) appIds.add(app.id) }) return Array.from(appIds) }, [apps]) const { onlineUsersMap: workflowOnlineUsersMap } = useWorkflowOnlineUsers({ appIds: workflowOnlineUserAppIds, enabled: systemFeatures.enable_collaboration_mode, }) const hasResolvedFirstPage = appListPages.length > 0 const hasAnyApp = (appListPages[0]?.total ?? 0) > 0 const emptyMessage = t(($) => $['filterEmpty.noApps'], { ns: 'app' }) const resultStatusMessage = !isError && !isPlaceholderData && hasResolvedFirstPage && !hasAnyApp ? emptyMessage : '' const showFirstEmptyState = !isPlaceholderData && !hasAnyApp && canCreateApp && hasResolvedFirstPage && !hasActiveFilters const showNoCreateEmptyState = !isPlaceholderData && !hasAnyApp && !canCreateApp && hasResolvedFirstPage && !hasActiveFilters const { shouldHighlightAllAppsRow, shouldHighlightStarredAppRow, shouldOpenFirstAppActionMenu } = useAppListTour({ canCreateApp, hasAnyApp, hasResolvedFirstPage, hasStarredApps: starredApps.length > 0, showFirstEmptyState, showNoCreateEmptyState, }) return ( <> {resultStatusMessage} {showFirstEmptyState ? ( ) : ( <> {starredApps.length > 0 && ( )} {starredApps.length > 0 && ( $['studio.allApps'], { ns: 'app' })} /> )}
{hasAnyApp ? (
    $['studio.allApps'], { ns: 'app' }) : undefined } aria-labelledby={starredApps.length > 0 ? ALL_APPS_HEADING_ID : undefined} className={APP_LIST_GRID_CLASS_NAME} > {apps.map((app, index) => ( ))} {hasNextPage && }
) : (
)} {hasNextPage && ( <> {isFetchNextPageError && (
{t(($) => $['errorBoundary.title'], { ns: 'common' })}
)} { void onFetchNextPage() }} preloadDistance={getPreloadDistance} scrollContainerRef={scrollViewportRef} /> )}
)} {canCreateApp && !showFirstEmptyState && (
{t(($) => $['newApp.dropDSLToCreateApp'], { ns: 'app' })}
)} ) } export function AppListCatalog(props: AppListCatalogProps) { const { appListQuery, canCreateApp, dragging, hasActiveFilters, onCreateBlank, onCreateLearnDify, onCreateTemplate, onImportDSL, onOpenTagManagement, onTryLearnDify, scrollViewportRef, } = props const systemFeaturesQuery = useQuery(systemFeaturesQueryOptions()) const systemFeatures = systemFeaturesQuery.data const appList = useInfiniteQuery( consoleQuery.apps.get.infiniteOptions({ input: (pageParam) => ({ query: { ...appListQuery, page: Number(pageParam), }, }), getNextPageParam: (lastPage) => (lastPage.has_more ? lastPage.page + 1 : undefined), initialPageParam: 1, placeholderData: keepPreviousData, refetchInterval: systemFeatures?.enable_collaboration_mode ? 10000 : false, }), ) const starredAppList = useQuery( consoleQuery.apps.starred.get.queryOptions({ input: { query: { ...appListQuery, page: 1, limit: STARRED_APP_LIMIT, }, }, placeholderData: keepPreviousData, }), ) if (systemFeatures === undefined && systemFeaturesQuery.error) throw systemFeaturesQuery.error if (appList.data === undefined && appList.error) throw appList.error if ( systemFeatures === undefined || appList.data === undefined || (starredAppList.data === undefined && !starredAppList.error) ) return return ( appList.fetchNextPage({ cancelRefetch: false })} onImportDSL={onImportDSL} onOpenTagManagement={onOpenTagManagement} onTryLearnDify={onTryLearnDify} scrollViewportRef={scrollViewportRef} starredApps={starredAppList.data?.data ?? emptyStarredApps} systemFeatures={systemFeatures} /> ) }