fix: Fetch chunk list by @tanstack/react-query #1306 (#1738)

### What problem does this PR solve?

fix: Fetch chunk list by @tanstack/react-query #1306

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
balibabu 2024-07-29 19:20:13 +08:00 committed by GitHub
parent aac460ad29
commit 8468031e39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 240 additions and 101 deletions

View File

@ -1,12 +1,17 @@
import { useCallback } from 'react'; import { ResponseGetType } from '@/interfaces/database/base';
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
import kbService from '@/services/knowledge-service';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { PaginationProps } from 'antd';
import { useCallback, useState } from 'react';
import { useDispatch } from 'umi'; import { useDispatch } from 'umi';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
import { useGetKnowledgeSearchParams } from './route-hook'; import { useGetKnowledgeSearchParams } from './route-hook';
interface PayloadType {
doc_id: string;
keywords?: string;
}
export const useFetchChunkList = () => { export const useFetchChunkList = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { documentId } = useGetKnowledgeSearchParams(); const { documentId } = useGetKnowledgeSearchParams();
@ -22,3 +27,104 @@ export const useFetchChunkList = () => {
return fetchChunkList; return fetchChunkList;
}; };
export interface IChunkListResult {
searchString?: string;
handleInputChange?: React.ChangeEventHandler<HTMLInputElement>;
pagination: PaginationProps;
setPagination?: (pagination: { page: number; pageSize: number }) => void;
available: number | undefined;
handleSetAvailable: (available: number | undefined) => void;
}
export const useFetchNextChunkList = (): ResponseGetType<{
data: IChunk[];
total: number;
documentInfo: IKnowledgeFile;
}> &
IChunkListResult => {
const { pagination, setPagination } = useGetPaginationWithRouter();
const { documentId } = useGetKnowledgeSearchParams();
const { searchString, handleInputChange } = useHandleSearchChange();
const [available, setAvailable] = useState<number | undefined>();
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
const { data, isFetching: loading } = useQuery({
queryKey: [
'fetchChunkList',
documentId,
pagination.current,
pagination.pageSize,
debouncedSearchString,
available,
],
initialData: { data: [], total: 0, documentInfo: {} },
// placeholderData: keepPreviousData,
gcTime: 0,
queryFn: async () => {
const { data } = await kbService.chunk_list({
doc_id: documentId,
page: pagination.current,
size: pagination.pageSize,
available_int: available,
keywords: searchString,
});
if (data.retcode === 0) {
const res = data.data;
return {
data: res.chunks,
total: res.total,
documentInfo: res.doc,
};
}
return (
data?.data ?? {
data: [],
total: 0,
documentInfo: {},
}
);
},
});
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
setPagination({ page: 1 });
handleInputChange(e);
},
[handleInputChange, setPagination],
);
const handleSetAvailable = useCallback(
(a: number | undefined) => {
setPagination({ page: 1 });
setAvailable(a);
},
[setAvailable, setPagination],
);
return {
data,
loading,
pagination,
setPagination,
searchString,
handleInputChange: onInputChange,
available,
handleSetAvailable,
};
};
export const useSelectChunkList = () => {
const queryClient = useQueryClient();
const data = queryClient.getQueriesData<{
data: IChunk[];
total: number;
documentInfo: IKnowledgeFile;
}>({ queryKey: ['fetchChunkList'] });
// console.log('🚀 ~ useSelectChunkList ~ data:', data);
return data?.at(-1)?.[1];
};

View File

@ -17,8 +17,9 @@
.contentEllipsis { .contentEllipsis {
.multipleLineEllipsis(3); .multipleLineEllipsis(3);
} }
.contentText { .contentText {
word-break: break-all; word-break: break-all !important;
} }
.chunkCard { .chunkCard {

View File

@ -1,5 +1,6 @@
import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
import { KnowledgeRouteKey } from '@/constants/knowledge'; import { KnowledgeRouteKey } from '@/constants/knowledge';
import { IChunkListResult, useSelectChunkList } from '@/hooks/chunk-hooks';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useKnowledgeBaseId } from '@/hooks/knowledge-hooks'; import { useKnowledgeBaseId } from '@/hooks/knowledge-hooks';
import { import {
@ -27,16 +28,18 @@ import {
Space, Space,
Typography, Typography,
} from 'antd'; } from 'antd';
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { Link, useDispatch, useSelector } from 'umi'; import { Link } from 'umi';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import { ChunkModelState } from '../../model';
const { Text } = Typography; const { Text } = Typography;
interface IProps { interface IProps
extends Pick<
IChunkListResult,
'searchString' | 'handleInputChange' | 'available' | 'handleSetAvailable'
> {
checked: boolean; checked: boolean;
getChunkList: () => void;
selectAllChunk: (checked: boolean) => void; selectAllChunk: (checked: boolean) => void;
createChunk: () => void; createChunk: () => void;
removeChunk: () => void; removeChunk: () => void;
@ -45,17 +48,19 @@ interface IProps {
} }
const ChunkToolBar = ({ const ChunkToolBar = ({
getChunkList,
selectAllChunk, selectAllChunk,
checked, checked,
createChunk, createChunk,
removeChunk, removeChunk,
switchChunk, switchChunk,
changeChunkTextMode, changeChunkTextMode,
available,
handleSetAvailable,
searchString,
handleInputChange,
}: IProps) => { }: IProps) => {
const { documentInfo, available, searchString }: ChunkModelState = const data = useSelectChunkList();
useSelector((state: any) => state.chunkModel); const documentInfo = data?.documentInfo;
const dispatch = useDispatch();
const knowledgeBaseId = useKnowledgeBaseId(); const knowledgeBaseId = useKnowledgeBaseId();
const [isShowSearchBox, setIsShowSearchBox] = useState(false); const [isShowSearchBox, setIsShowSearchBox] = useState(false);
const { t } = useTranslate('chunk'); const { t } = useTranslate('chunk');
@ -71,17 +76,17 @@ const ChunkToolBar = ({
setIsShowSearchBox(true); setIsShowSearchBox(true);
}; };
const handleSearchChange: ChangeEventHandler<HTMLInputElement> = (e) => { // const handleSearchChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const val = e.target.value; // const val = e.target.value;
dispatch({ type: 'chunkModel/setSearchString', payload: val }); // dispatch({ type: 'chunkModel/setSearchString', payload: val });
dispatch({ // dispatch({
type: 'chunkModel/throttledGetChunkList', // type: 'chunkModel/throttledGetChunkList',
payload: documentInfo.id, // payload: documentInfo.id,
}); // });
}; // };
const handleSearchBlur = () => { const handleSearchBlur = () => {
if (!searchString.trim()) { if (!searchString?.trim()) {
setIsShowSearchBox(false); setIsShowSearchBox(false);
} }
}; };
@ -155,8 +160,7 @@ const ChunkToolBar = ({
const handleFilterChange = (e: RadioChangeEvent) => { const handleFilterChange = (e: RadioChangeEvent) => {
selectAllChunk(false); selectAllChunk(false);
dispatch({ type: 'chunkModel/setAvailable', payload: e.target.value }); handleSetAvailable(e.target.value);
getChunkList();
}; };
const filterContent = ( const filterContent = (
@ -178,8 +182,8 @@ const ChunkToolBar = ({
<ArrowLeftOutlined /> <ArrowLeftOutlined />
</Link> </Link>
<FilePdfOutlined /> <FilePdfOutlined />
<Text ellipsis={{ tooltip: documentInfo.name }} style={{ width: 150 }}> <Text ellipsis={{ tooltip: documentInfo?.name }} style={{ width: 150 }}>
{documentInfo.name} {documentInfo?.name}
</Text> </Text>
</Space> </Space>
<Space> <Space>
@ -202,7 +206,7 @@ const ChunkToolBar = ({
placeholder={t('search')} placeholder={t('search')}
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
allowClear allowClear
onChange={handleSearchChange} onChange={handleInputChange}
onBlur={handleSearchBlur} onBlur={handleSearchBlur}
value={searchString} value={searchString}
/> />

View File

@ -1,5 +1,5 @@
import { Skeleton } from 'antd'; import { Skeleton } from 'antd';
import { useEffect, useRef } from 'react'; import { memo, useEffect, useRef } from 'react';
import { import {
AreaHighlight, AreaHighlight,
Highlight, Highlight,
@ -8,7 +8,6 @@ import {
PdfLoader, PdfLoader,
Popup, Popup,
} from 'react-pdf-highlighter'; } from 'react-pdf-highlighter';
import { useGetChunkHighlights } from '../../hooks';
import { useGetDocumentUrl } from './hooks'; import { useGetDocumentUrl } from './hooks';
import { useCatchDocumentError } from '@/components/pdf-previewer/hooks'; import { useCatchDocumentError } from '@/components/pdf-previewer/hooks';
@ -16,7 +15,8 @@ import FileError from '@/pages/document-viewer/file-error';
import styles from './index.less'; import styles from './index.less';
interface IProps { interface IProps {
selectedChunkId: string; highlights: IHighlight[];
setWidthAndHeight: (width: number, height: number) => void;
} }
const HighlightPopup = ({ const HighlightPopup = ({
comment, comment,
@ -30,11 +30,10 @@ const HighlightPopup = ({
) : null; ) : null;
// TODO: merge with DocumentPreviewer // TODO: merge with DocumentPreviewer
const Preview = ({ selectedChunkId }: IProps) => { const Preview = ({ highlights: state, setWidthAndHeight }: IProps) => {
const url = useGetDocumentUrl(); const url = useGetDocumentUrl();
useCatchDocumentError(url); useCatchDocumentError(url);
const { highlights: state, setWidthAndHeight } =
useGetChunkHighlights(selectedChunkId);
const ref = useRef<(highlight: IHighlight) => void>(() => {}); const ref = useRef<(highlight: IHighlight) => void>(() => {});
const error = useCatchDocumentError(url); const error = useCatchDocumentError(url);
@ -120,4 +119,12 @@ const Preview = ({ selectedChunkId }: IProps) => {
); );
}; };
export default Preview; const compare = (oldProps: IProps, newProps: IProps) => {
const arePropsEqual =
oldProps.highlights === newProps.highlights ||
(oldProps.highlights.length === 0 && newProps.highlights.length === 0);
return arePropsEqual;
};
export default memo(Preview);

View File

@ -1,24 +1,17 @@
import { useSelectChunkList } from '@/hooks/chunk-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks'; import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks';
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge'; import { IChunk } from '@/interfaces/database/knowledge';
import { buildChunkHighlights } from '@/utils/document-util'; import { buildChunkHighlights } from '@/utils/document-util';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter'; import { IHighlight } from 'react-pdf-highlighter';
import { useSelector } from 'umi';
import { ChunkTextMode } from './constant'; import { ChunkTextMode } from './constant';
export const useSelectDocumentInfo = () => { // export const useSelectChunkList = () => {
const documentInfo: IKnowledgeFile = useSelector( // const chunkList: IChunk[] = useSelector(
(state: any) => state.chunkModel.documentInfo, // (state: any) => state.chunkModel.data,
); // );
return documentInfo; // return chunkList;
}; // };
export const useSelectChunkList = () => {
const chunkList: IChunk[] = useSelector(
(state: any) => state.chunkModel.data,
);
return chunkList;
};
export const useHandleChunkCardClick = () => { export const useHandleChunkCardClick = () => {
const [selectedChunkId, setSelectedChunkId] = useState<string>(''); const [selectedChunkId, setSelectedChunkId] = useState<string>('');
@ -31,9 +24,9 @@ export const useHandleChunkCardClick = () => {
}; };
export const useGetSelectedChunk = (selectedChunkId: string) => { export const useGetSelectedChunk = (selectedChunkId: string) => {
const chunkList: IChunk[] = useSelectChunkList(); const data = useSelectChunkList();
return ( return (
chunkList.find((x) => x.chunk_id === selectedChunkId) ?? ({} as IChunk) data?.data?.find((x) => x.chunk_id === selectedChunkId) ?? ({} as IChunk)
); );
}; };
@ -45,14 +38,14 @@ export const useGetChunkHighlights = (selectedChunkId: string) => {
return buildChunkHighlights(selectedChunk, size); return buildChunkHighlights(selectedChunk, size);
}, [selectedChunk, size]); }, [selectedChunk, size]);
const setWidthAndHeight = (width: number, height: number) => { const setWidthAndHeight = useCallback((width: number, height: number) => {
setSize((pre) => { setSize((pre) => {
if (pre.height !== height || pre.width !== width) { if (pre.height !== height || pre.width !== width) {
return { height, width }; return { height, width };
} }
return pre; return pre;
}); });
}; }, []);
return { highlights, setWidthAndHeight }; return { highlights, setWidthAndHeight };
}; };

View File

@ -1,45 +1,46 @@
import { useFetchChunkList } from '@/hooks/chunk-hooks'; import { useFetchNextChunkList } from '@/hooks/chunk-hooks';
import { useDeleteChunkByIds } from '@/hooks/knowledge-hooks'; import { useDeleteChunkByIds } from '@/hooks/knowledge-hooks';
import type { PaginationProps } from 'antd'; import type { PaginationProps } from 'antd';
import { Divider, Flex, Pagination, Space, Spin, message } from 'antd'; import { Divider, Flex, Pagination, Space, Spin, message } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSearchParams, useSelector } from 'umi'; import { useDispatch, useSearchParams } from 'umi';
import ChunkCard from './components/chunk-card'; import ChunkCard from './components/chunk-card';
import CreatingModal from './components/chunk-creating-modal'; import CreatingModal from './components/chunk-creating-modal';
import ChunkToolBar from './components/chunk-toolbar'; import ChunkToolBar from './components/chunk-toolbar';
import DocumentPreview from './components/document-preview/preview'; import DocumentPreview from './components/document-preview/preview';
import { import {
useChangeChunkTextMode, useChangeChunkTextMode,
useGetChunkHighlights,
useHandleChunkCardClick, useHandleChunkCardClick,
useSelectChunkListLoading,
useSelectDocumentInfo,
} from './hooks'; } from './hooks';
import { ChunkModelState } from './model';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from './index.less'; import styles from './index.less';
const Chunk = () => { const Chunk = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const chunkModel: ChunkModelState = useSelector(
(state: any) => state.chunkModel,
);
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]); const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { data = [], total, pagination } = chunkModel; // const loading = useSelectChunkListLoading();
const loading = useSelectChunkListLoading();
const documentId: string = searchParams.get('doc_id') || ''; const documentId: string = searchParams.get('doc_id') || '';
const [chunkId, setChunkId] = useState<string | undefined>(); const [chunkId, setChunkId] = useState<string | undefined>();
const { removeChunk } = useDeleteChunkByIds(); const { removeChunk } = useDeleteChunkByIds();
const documentInfo = useSelectDocumentInfo(); const {
data: { documentInfo, data = [], total },
pagination,
loading,
searchString,
handleInputChange,
available,
handleSetAvailable,
} = useFetchNextChunkList();
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
const isPdf = documentInfo.type === 'pdf'; const isPdf = documentInfo?.type === 'pdf';
const { t } = useTranslation(); const { t } = useTranslation();
const { changeChunkTextMode, textMode } = useChangeChunkTextMode(); const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
const getChunkList = useFetchChunkList();
const handleEditChunk = useCallback( const handleEditChunk = useCallback(
(chunk_id?: string) => { (chunk_id?: string) => {
setChunkId(chunk_id); setChunkId(chunk_id);
@ -57,14 +58,8 @@ const Chunk = () => {
size, size,
) => { ) => {
setSelectedChunkIds([]); setSelectedChunkIds([]);
dispatch({ pagination.onChange?.(page, size);
type: 'chunkModel/setPagination', // getChunkList();
payload: {
current: page,
pageSize: size,
},
});
getChunkList();
}; };
const selectAllChunk = useCallback( const selectAllChunk = useCallback(
@ -125,38 +120,44 @@ const Chunk = () => {
}, },
}); });
if (!chunkIds && resCode === 0) { if (!chunkIds && resCode === 0) {
getChunkList(); // getChunkList();
} }
}, },
[ [
dispatch, dispatch,
documentId, documentId,
getChunkList, // getChunkList,
selectedChunkIds, selectedChunkIds,
showSelectedChunkWarning, showSelectedChunkWarning,
], ],
); );
const { highlights, setWidthAndHeight } =
useGetChunkHighlights(selectedChunkId);
useEffect(() => { useEffect(() => {
getChunkList(); // getChunkList();
return () => { return () => {
dispatch({ dispatch({
type: 'chunkModel/resetFilter', // TODO: need to reset state uniformly type: 'chunkModel/resetFilter', // TODO: need to reset state uniformly
}); });
}; };
}, [dispatch, getChunkList]); }, [dispatch]);
return ( return (
<> <>
<div className={styles.chunkPage}> <div className={styles.chunkPage}>
<ChunkToolBar <ChunkToolBar
getChunkList={getChunkList}
selectAllChunk={selectAllChunk} selectAllChunk={selectAllChunk}
createChunk={handleEditChunk} createChunk={handleEditChunk}
removeChunk={handleRemoveChunk} removeChunk={handleRemoveChunk}
checked={selectedChunkIds.length === data.length} checked={selectedChunkIds.length === data.length}
switchChunk={switchChunk} switchChunk={switchChunk}
changeChunkTextMode={changeChunkTextMode} changeChunkTextMode={changeChunkTextMode}
searchString={searchString}
handleInputChange={handleInputChange}
available={available}
handleSetAvailable={handleSetAvailable}
></ChunkToolBar> ></ChunkToolBar>
<Divider></Divider> <Divider></Divider>
<Flex flex={1} gap={'middle'}> <Flex flex={1} gap={'middle'}>
@ -193,33 +194,22 @@ const Chunk = () => {
</Spin> </Spin>
<div className={styles.pageFooter}> <div className={styles.pageFooter}>
<Pagination <Pagination
responsive {...pagination}
showLessItems
showQuickJumper
showSizeChanger
onChange={onPaginationChange}
pageSize={pagination.pageSize}
pageSizeOptions={[10, 30, 60, 90]}
current={pagination.current}
size={'small'}
total={total} total={total}
showTotal={(total) => ( size={'small'}
<Space> onChange={onPaginationChange}
{t('total', { keyPrefix: 'common' })}
{total}
</Space>
)}
/> />
</div> </div>
</Flex> </Flex>
{isPdf && ( {
<section className={styles.documentPreview}> <section className={styles.documentPreview}>
<DocumentPreview <DocumentPreview
selectedChunkId={selectedChunkId} highlights={highlights}
setWidthAndHeight={setWidthAndHeight}
></DocumentPreview> ></DocumentPreview>
</section> </section>
)} }
</Flex> </Flex>
</div> </div>
<CreatingModal doc_id={documentId} chunkId={chunkId} /> <CreatingModal doc_id={documentId} chunkId={chunkId} />

View File

@ -0,0 +1,33 @@
import { memo, useState } from 'react';
function compare(oldProps, newProps) {
return true;
}
const Greeting = memo(function Greeting({ name }) {
console.log('Greeting was rendered at', new Date().toLocaleTimeString());
return (
<h3>
Hello{name && ', '}
{name}!
</h3>
);
}, compare);
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
<label>
Name{': '}
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
Address{': '}
<input value={address} onChange={(e) => setAddress(e.target.value)} />
</label>
<Greeting name={name} />
</>
);
}

View File

@ -98,6 +98,11 @@ const routes = [
component: '@/pages/document-viewer', component: '@/pages/document-viewer',
layout: false, layout: false,
}, },
{
path: 'force',
component: '@/pages/force-graph',
layout: false,
},
{ {
path: '/*', path: '/*',
component: '@/pages/404', component: '@/pages/404',