dify/web/app/components/datasets/list/datasets.tsx
Jingyi cb64446fa3
feat(web): add step-by-step tour shell (#38785)
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-07-20 09:23:25 +00:00

105 lines
3.7 KiB
TypeScript

'use client'
import type { ReactNode } from 'react'
import type { useDatasetList } from '@/service/knowledge/use-dataset'
import { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import DatasetCard from './dataset-card'
import DatasetCardSkeleton from './dataset-card-skeleton'
type Props = Readonly<{
datasetList: ReturnType<typeof useDatasetList>['data'] | null
fetchNextPage: ReturnType<typeof useDatasetList>['fetchNextPage']
hasNextPage: ReturnType<typeof useDatasetList>['hasNextPage']
isFetching: ReturnType<typeof useDatasetList>['isFetching']
isFetchingNextPage: ReturnType<typeof useDatasetList>['isFetchingNextPage']
isLoading: ReturnType<typeof useDatasetList>['isLoading']
isPlaceholderData: ReturnType<typeof useDatasetList>['isPlaceholderData']
emptyElement?: ReactNode
onOpenTagManagement?: () => void
stepByStepTourActionMenuHighlightPart?: string
stepByStepTourActionMenuOpen?: boolean
stepByStepTourCardTarget?: string
}>
const Datasets = ({
datasetList,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
isLoading,
isPlaceholderData,
emptyElement,
onOpenTagManagement = () => {},
stepByStepTourActionMenuHighlightPart,
stepByStepTourActionMenuOpen,
stepByStepTourCardTarget,
}: Props) => {
const { t } = useTranslation()
const invalidDatasetList = useInvalidDatasetList()
const anchorRef = useRef<HTMLDivElement>(null)
const observerRef = useRef<IntersectionObserver>(null)
const pages = datasetList?.pages ?? []
const datasets = pages.flatMap(({ data }) => data)
const showDatasetSkeleton =
!isFetchingNextPage && (isLoading || (isPlaceholderData && isFetching && datasets.length === 0))
useEffect(() => {
document.title = `${t(($) => $.knowledge, { ns: 'dataset' })} - Dify`
}, [t])
useEffect(() => {
if (anchorRef.current) {
observerRef.current = new IntersectionObserver(
(entries) => {
if (entries[0]!.isIntersecting && hasNextPage && !isFetching && !isPlaceholderData)
fetchNextPage()
},
{
rootMargin: '100px',
},
)
observerRef.current.observe(anchorRef.current)
}
return () => observerRef.current?.disconnect()
}, [anchorRef, hasNextPage, isFetching, isPlaceholderData, fetchNextPage])
const hasAnyDataset =
(datasetList?.pages[0]?.total ?? 0) > 0 ||
!!datasetList?.pages.some(({ data }) => data.length > 0)
return (
<>
<nav className="relative grid grow grid-cols-[repeat(auto-fill,minmax(296px,1fr))] content-start gap-3 px-8 pt-2">
{showDatasetSkeleton ? (
<DatasetCardSkeleton label={t(($) => $.loading, { ns: 'common' })} />
) : (
datasets.map((dataset, index) => (
<DatasetCard
key={dataset.id}
dataset={dataset}
onSuccess={invalidDatasetList}
onOpenTagManagement={onOpenTagManagement}
stepByStepTourActionMenuHighlightPart={
index === 0 && stepByStepTourActionMenuOpen
? stepByStepTourActionMenuHighlightPart
: undefined
}
stepByStepTourActionMenuOpen={index === 0 ? stepByStepTourActionMenuOpen : undefined}
stepByStepTourCardTarget={index === 0 ? stepByStepTourCardTarget : undefined}
/>
))
)}
{!showDatasetSkeleton && !hasAnyDataset && emptyElement}
{isFetchingNextPage && <Loading />}
<div ref={anchorRef} className="h-0" />
</nav>
</>
)
}
export default Datasets