Skip to content

Commit 18601d8

Browse files
lyzno1WTW0313
authored andcommitted
Refactor datasets service toward TanStack Query (#29008)
Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com>
1 parent 57d244d commit 18601d8

12 files changed

Lines changed: 270 additions & 152 deletions

File tree

web/app/components/app/configuration/dataset-config/select-dataset/index.tsx

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
'use client'
22
import type { FC } from 'react'
3-
import React, { useRef, useState } from 'react'
4-
import { useGetState, useInfiniteScroll } from 'ahooks'
3+
import React, { useEffect, useMemo, useRef, useState } from 'react'
4+
import { useInfiniteScroll } from 'ahooks'
55
import { useTranslation } from 'react-i18next'
66
import Link from 'next/link'
77
import Modal from '@/app/components/base/modal'
88
import type { DataSet } from '@/models/datasets'
99
import Button from '@/app/components/base/button'
10-
import { fetchDatasets } from '@/service/datasets'
1110
import Loading from '@/app/components/base/loading'
1211
import Badge from '@/app/components/base/badge'
1312
import { useKnowledge } from '@/hooks/use-knowledge'
1413
import cn from '@/utils/classnames'
1514
import AppIcon from '@/app/components/base/app-icon'
15+
import { useInfiniteDatasets } from '@/service/knowledge/use-dataset'
1616
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
1717
import FeatureIcon from '@/app/components/header/account-setting/model-provider-page/model-selector/feature-icon'
1818

@@ -30,51 +30,70 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
3030
onSelect,
3131
}) => {
3232
const { t } = useTranslation()
33-
const [selected, setSelected] = React.useState<DataSet[]>([])
34-
const [loaded, setLoaded] = React.useState(false)
35-
const [datasets, setDataSets] = React.useState<DataSet[] | null>(null)
36-
const [hasInitialized, setHasInitialized] = React.useState(false)
37-
const hasNoData = !datasets || datasets?.length === 0
33+
const [selected, setSelected] = useState<DataSet[]>([])
3834
const canSelectMulti = true
35+
const { formatIndexingTechniqueAndMethod } = useKnowledge()
36+
const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage } = useInfiniteDatasets(
37+
{ page: 1 },
38+
{ enabled: isShow, staleTime: 0, refetchOnMount: 'always' },
39+
)
40+
const pages = data?.pages || []
41+
const datasets = useMemo(() => {
42+
return pages.flatMap(page => page.data.filter(item => item.indexing_technique || item.provider === 'external'))
43+
}, [pages])
44+
const hasNoData = !isLoading && datasets.length === 0
3945

4046
const listRef = useRef<HTMLDivElement>(null)
41-
const [page, setPage, getPage] = useGetState(1)
42-
const [isNoMore, setIsNoMore] = useState(false)
43-
const { formatIndexingTechniqueAndMethod } = useKnowledge()
47+
const isNoMore = hasNextPage === false
4448

4549
useInfiniteScroll(
4650
async () => {
47-
if (!isNoMore) {
48-
const { data, has_more } = await fetchDatasets({ url: '/datasets', params: { page } })
49-
setPage(getPage() + 1)
50-
setIsNoMore(!has_more)
51-
const newList = [...(datasets || []), ...data.filter(item => item.indexing_technique || item.provider === 'external')]
52-
setDataSets(newList)
53-
setLoaded(true)
54-
55-
// Initialize selected datasets based on selectedIds and available datasets
56-
if (!hasInitialized) {
57-
if (selectedIds.length > 0) {
58-
const validSelectedDatasets = selectedIds
59-
.map(id => newList.find(item => item.id === id))
60-
.filter(Boolean) as DataSet[]
61-
setSelected(validSelectedDatasets)
62-
}
63-
setHasInitialized(true)
64-
}
65-
}
51+
if (!hasNextPage || isFetchingNextPage)
52+
return { list: [] }
53+
await fetchNextPage()
6654
return { list: [] }
6755
},
6856
{
6957
target: listRef,
70-
isNoMore: () => {
71-
return isNoMore
72-
},
73-
reloadDeps: [isNoMore],
58+
isNoMore: () => isNoMore,
59+
reloadDeps: [isNoMore, isFetchingNextPage],
7460
},
7561
)
7662

63+
const prevSelectedIdsRef = useRef<string[]>([])
64+
const hasUserModifiedSelectionRef = useRef(false)
65+
useEffect(() => {
66+
if (isShow)
67+
hasUserModifiedSelectionRef.current = false
68+
}, [isShow])
69+
useEffect(() => {
70+
const prevSelectedIds = prevSelectedIdsRef.current
71+
const idsChanged = selectedIds.length !== prevSelectedIds.length
72+
|| selectedIds.some((id, idx) => id !== prevSelectedIds[idx])
73+
74+
if (!selectedIds.length && (!hasUserModifiedSelectionRef.current || idsChanged)) {
75+
setSelected([])
76+
prevSelectedIdsRef.current = selectedIds
77+
hasUserModifiedSelectionRef.current = false
78+
return
79+
}
80+
81+
if (!idsChanged && hasUserModifiedSelectionRef.current)
82+
return
83+
84+
setSelected((prev) => {
85+
const prevMap = new Map(prev.map(item => [item.id, item]))
86+
const nextSelected = selectedIds
87+
.map(id => datasets.find(item => item.id === id) || prevMap.get(id))
88+
.filter(Boolean) as DataSet[]
89+
return nextSelected
90+
})
91+
prevSelectedIdsRef.current = selectedIds
92+
hasUserModifiedSelectionRef.current = false
93+
}, [datasets, selectedIds])
94+
7795
const toggleSelect = (dataSet: DataSet) => {
96+
hasUserModifiedSelectionRef.current = true
7897
const isSelected = selected.some(item => item.id === dataSet.id)
7998
if (isSelected) {
8099
setSelected(selected.filter(item => item.id !== dataSet.id))
@@ -98,13 +117,13 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
98117
className='w-[400px]'
99118
title={t('appDebug.feature.dataSet.selectTitle')}
100119
>
101-
{!loaded && (
120+
{(isLoading && datasets.length === 0) && (
102121
<div className='flex h-[200px]'>
103122
<Loading type='area' />
104123
</div>
105124
)}
106125

107-
{(loaded && hasNoData) && (
126+
{hasNoData && (
108127
<div className='mt-6 flex h-[128px] items-center justify-center space-x-1 rounded-lg border text-[13px]'
109128
style={{
110129
background: 'rgba(0, 0, 0, 0.02)',
@@ -116,7 +135,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
116135
</div>
117136
)}
118137

119-
{datasets && datasets?.length > 0 && (
138+
{datasets.length > 0 && (
120139
<>
121140
<div ref={listRef} className='mt-7 max-h-[286px] space-y-1 overflow-y-auto'>
122141
{datasets.map(item => (
@@ -171,7 +190,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
171190
</div>
172191
</>
173192
)}
174-
{loaded && (
193+
{!isLoading && (
175194
<div className='mt-8 flex items-center justify-between'>
176195
<div className='text-sm font-medium text-text-secondary'>
177196
{selected.length > 0 && `${selected.length} ${t('appDebug.feature.dataSet.selected')}`}

web/app/components/datasets/common/document-status-with-action/index-failed.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import type { FC } from 'react'
33
import React, { useEffect, useReducer } from 'react'
44
import { useTranslation } from 'react-i18next'
5-
import useSWR from 'swr'
65
import StatusWithAction from './status-with-action'
7-
import { getErrorDocs, retryErrorDocs } from '@/service/datasets'
6+
import { retryErrorDocs } from '@/service/datasets'
87
import type { IndexingStatusResponse } from '@/models/datasets'
98
import { noop } from 'lodash-es'
9+
import { useDatasetErrorDocs } from '@/service/knowledge/use-dataset'
1010

1111
type Props = {
1212
datasetId: string
@@ -35,16 +35,19 @@ const indexStateReducer = (state: IIndexState, action: IAction) => {
3535
const RetryButton: FC<Props> = ({ datasetId }) => {
3636
const { t } = useTranslation()
3737
const [indexState, dispatch] = useReducer(indexStateReducer, { value: 'success' })
38-
const { data: errorDocs, isLoading } = useSWR({ datasetId }, getErrorDocs)
38+
const { data: errorDocs, isLoading, refetch: refetchErrorDocs } = useDatasetErrorDocs(datasetId)
3939

4040
const onRetryErrorDocs = async () => {
4141
dispatch({ type: 'retry' })
4242
const document_ids = errorDocs?.data.map((doc: IndexingStatusResponse) => doc.id) || []
4343
const res = await retryErrorDocs({ datasetId, document_ids })
44-
if (res.result === 'success')
44+
if (res.result === 'success') {
45+
refetchErrorDocs()
4546
dispatch({ type: 'success' })
46-
else
47+
}
48+
else {
4749
dispatch({ type: 'error' })
50+
}
4851
}
4952

5053
useEffect(() => {

web/app/components/datasets/create/embedding-process/index.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import type { FC } from 'react'
22
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3-
import useSWR from 'swr'
43
import { useRouter } from 'next/navigation'
54
import { useTranslation } from 'react-i18next'
6-
import { omit } from 'lodash-es'
75
import {
86
RiArrowRightLine,
97
RiCheckboxCircleFill,
@@ -25,7 +23,7 @@ import type {
2523
LegacyDataSourceInfo,
2624
ProcessRuleResponse,
2725
} from '@/models/datasets'
28-
import { fetchIndexingStatusBatch as doFetchIndexingStatus, fetchProcessRule } from '@/service/datasets'
26+
import { fetchIndexingStatusBatch as doFetchIndexingStatus } from '@/service/datasets'
2927
import { DataSourceType, ProcessMode } from '@/models/datasets'
3028
import NotionIcon from '@/app/components/base/notion-icon'
3129
import PriorityLabel from '@/app/components/billing/priority-label'
@@ -40,6 +38,7 @@ import { useInvalidDocumentList } from '@/service/knowledge/use-document'
4038
import Divider from '@/app/components/base/divider'
4139
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
4240
import Link from 'next/link'
41+
import { useProcessRule } from '@/service/knowledge/use-dataset'
4342

4443
type Props = {
4544
datasetId: string
@@ -207,12 +206,7 @@ const EmbeddingProcess: FC<Props> = ({ datasetId, batchId, documents = [], index
207206
}, [])
208207

209208
// get rule
210-
const { data: ruleDetail } = useSWR({
211-
action: 'fetchProcessRule',
212-
params: { documentId: getFirstDocument.id },
213-
}, apiParams => fetchProcessRule(omit(apiParams, 'action')), {
214-
revalidateOnFocus: false,
215-
})
209+
const { data: ruleDetail } = useProcessRule(getFirstDocument?.id)
216210

217211
const router = useRouter()
218212
const invalidDocumentList = useInvalidDocumentList()

web/app/components/datasets/create/file-uploader/index.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
33
import { useTranslation } from 'react-i18next'
44
import { useContext } from 'use-context-selector'
5-
import useSWR from 'swr'
65
import { RiDeleteBinLine, RiUploadCloud2Line } from '@remixicon/react'
76
import DocumentFileIcon from '../../common/document-file-icon'
87
import cn from '@/utils/classnames'
@@ -11,8 +10,7 @@ import { ToastContext } from '@/app/components/base/toast'
1110
import SimplePieChart from '@/app/components/base/simple-pie-chart'
1211

1312
import { upload } from '@/service/base'
14-
import { fetchFileUploadConfig } from '@/service/common'
15-
import { fetchSupportFileTypes } from '@/service/datasets'
13+
import { useFileSupportTypes, useFileUploadConfig } from '@/service/use-common'
1614
import I18n from '@/context/i18n'
1715
import { LanguagesSupported } from '@/i18n-config/language'
1816
import { IS_CE_EDITION } from '@/config'
@@ -48,8 +46,8 @@ const FileUploader = ({
4846
const fileUploader = useRef<HTMLInputElement>(null)
4947
const hideUpload = notSupportBatchUpload && fileList.length > 0
5048

51-
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
52-
const { data: supportFileTypesResponse } = useSWR({ url: '/files/support-type' }, fetchSupportFileTypes)
49+
const { data: fileUploadConfigResponse } = useFileUploadConfig()
50+
const { data: supportFileTypesResponse } = useFileSupportTypes()
5351
const supportTypes = supportFileTypesResponse?.allowed_extensions || []
5452
const supportTypesShowNames = (() => {
5553
const extensionMap: { [key: string]: string } = {

web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ import Button from '@/app/components/base/button'
1313
import type { FileItem } from '@/models/datasets'
1414
import { upload } from '@/service/base'
1515
import { getFileUploadErrorMessage } from '@/app/components/base/file-uploader/utils'
16-
import useSWR from 'swr'
17-
import { fetchFileUploadConfig } from '@/service/common'
1816
import SimplePieChart from '@/app/components/base/simple-pie-chart'
1917
import { Theme } from '@/types/app'
2018
import useTheme from '@/hooks/use-theme'
19+
import { useFileUploadConfig } from '@/service/use-common'
2120

2221
export type Props = {
2322
file: FileItem | undefined
@@ -34,7 +33,7 @@ const CSVUploader: FC<Props> = ({
3433
const dropRef = useRef<HTMLDivElement>(null)
3534
const dragRef = useRef<HTMLDivElement>(null)
3635
const fileUploader = useRef<HTMLInputElement>(null)
37-
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
36+
const { data: fileUploadConfigResponse } = useFileUploadConfig()
3837
const fileUploadConfig = useMemo(() => fileUploadConfigResponse ?? {
3938
file_size_limit: 15,
4039
}, [fileUploadConfigResponse])

web/app/components/datasets/documents/detail/embedding/index.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import type { FC } from 'react'
22
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3-
import useSWR from 'swr'
43
import { useContext } from 'use-context-selector'
54
import { useTranslation } from 'react-i18next'
6-
import { omit } from 'lodash-es'
75
import { RiLoader2Line, RiPauseCircleLine, RiPlayCircleLine } from '@remixicon/react'
86
import Image from 'next/image'
97
import { FieldInfo } from '../metadata'
@@ -21,10 +19,10 @@ import type { CommonResponse } from '@/models/common'
2119
import { asyncRunSafe, sleep } from '@/utils'
2220
import {
2321
fetchIndexingStatus as doFetchIndexingStatus,
24-
fetchProcessRule,
2522
pauseDocIndexing,
2623
resumeDocIndexing,
2724
} from '@/service/datasets'
25+
import { useProcessRule } from '@/service/knowledge/use-dataset'
2826

2927
type IEmbeddingDetailProps = {
3028
datasetId?: string
@@ -207,12 +205,7 @@ const EmbeddingDetail: FC<IEmbeddingDetailProps> = ({
207205
}
208206
}, [startQueryStatus, stopQueryStatus])
209207

210-
const { data: ruleDetail } = useSWR({
211-
action: 'fetchProcessRule',
212-
params: { documentId: localDocumentId },
213-
}, apiParams => fetchProcessRule(omit(apiParams, 'action')), {
214-
revalidateOnFocus: false,
215-
})
208+
const { data: ruleDetail } = useProcessRule(localDocumentId)
216209

217210
const isEmbedding = useMemo(() => ['indexing', 'splitting', 'parsing', 'cleaning'].includes(indexingStatusDetail?.indexing_status || ''), [indexingStatusDetail])
218211
const isEmbeddingCompleted = useMemo(() => ['completed'].includes(indexingStatusDetail?.indexing_status || ''), [indexingStatusDetail])

web/app/components/datasets/hit-testing/index.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ import Records from './components/records'
3232
import {
3333
useExternalKnowledgeBaseHitTesting,
3434
useHitTesting,
35-
useHitTestingRecords,
36-
useInvalidateHitTestingRecords,
3735
} from '@/service/knowledge/use-hit-testing'
36+
import { useDatasetTestingRecords } from '@/service/knowledge/use-dataset'
3837

3938
const limit = 10
4039

@@ -48,14 +47,13 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
4847
const media = useBreakpoints()
4948
const isMobile = media === MediaType.mobile
5049

51-
const [hitResult, setHitResult] = useState<HitTestingResponse | undefined>() // 初始化记录为空数组
50+
const [hitResult, setHitResult] = useState<HitTestingResponse | undefined>()
5251
const [externalHitResult, setExternalHitResult] = useState<ExternalKnowledgeBaseHitTestingResponse | undefined>()
5352
const [queries, setQueries] = useState<Query[]>([])
5453
const [queryInputKey, setQueryInputKey] = useState(Date.now())
5554

5655
const [currPage, setCurrPage] = useState<number>(0)
57-
const { data: recordsRes, isLoading: isRecordsLoading } = useHitTestingRecords({ datasetId, page: currPage + 1, limit })
58-
const invalidateHitTestingRecords = useInvalidateHitTestingRecords(datasetId)
56+
const { data: recordsRes, refetch: recordsRefetch, isLoading: isRecordsLoading } = useDatasetTestingRecords(datasetId, { limit, page: currPage + 1 })
5957

6058
const total = recordsRes?.total || 0
6159

@@ -107,8 +105,7 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
107105
)
108106

109107
const handleClickRecord = useCallback((record: HitTestingRecord) => {
110-
const { queries } = record
111-
setQueries(queries)
108+
setQueries(record.queries)
112109
setQueryInputKey(Date.now())
113110
}, [])
114111

@@ -128,7 +125,7 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
128125
setHitResult={setHitResult}
129126
setExternalHitResult={setExternalHitResult}
130127
onSubmit={showRightPanel}
131-
onUpdateList={invalidateHitTestingRecords}
128+
onUpdateList={recordsRefetch}
132129
loading={isRetrievalLoading}
133130
queries={queries}
134131
setQueries={setQueries}
@@ -140,11 +137,9 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
140137
externalKnowledgeBaseHitTestingMutation={externalKnowledgeBaseHitTestingMutation}
141138
/>
142139
<div className='mb-3 mt-6 text-base font-semibold text-text-primary'>{t('datasetHitTesting.records')}</div>
143-
{isRecordsLoading
144-
&& (
145-
<div className='flex-1'><Loading type='app' /></div>
146-
)
147-
}
140+
{isRecordsLoading && (
141+
<div className='flex-1'><Loading type='app' /></div>
142+
)}
148143
{!isRecordsLoading && recordsRes?.data && recordsRes.data.length > 0 && (
149144
<>
150145
<Records records={recordsRes?.data} onClickRecord={handleClickRecord}/>

0 commit comments

Comments
 (0)