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 {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
import { useGetKnowledgeSearchParams } from './route-hook';
interface PayloadType {
doc_id: string;
keywords?: string;
}
export const useFetchChunkList = () => {
const dispatch = useDispatch();
const { documentId } = useGetKnowledgeSearchParams();
@ -22,3 +27,104 @@ export const useFetchChunkList = () => {
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 {
.multipleLineEllipsis(3);
}
.contentText {
word-break: break-all;
word-break: break-all !important;
}
.chunkCard {

View File

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

View File

@ -1,5 +1,5 @@
import { Skeleton } from 'antd';
import { useEffect, useRef } from 'react';
import { memo, useEffect, useRef } from 'react';
import {
AreaHighlight,
Highlight,
@ -8,7 +8,6 @@ import {
PdfLoader,
Popup,
} from 'react-pdf-highlighter';
import { useGetChunkHighlights } from '../../hooks';
import { useGetDocumentUrl } from './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';
interface IProps {
selectedChunkId: string;
highlights: IHighlight[];
setWidthAndHeight: (width: number, height: number) => void;
}
const HighlightPopup = ({
comment,
@ -30,11 +30,10 @@ const HighlightPopup = ({
) : null;
// TODO: merge with DocumentPreviewer
const Preview = ({ selectedChunkId }: IProps) => {
const Preview = ({ highlights: state, setWidthAndHeight }: IProps) => {
const url = useGetDocumentUrl();
useCatchDocumentError(url);
const { highlights: state, setWidthAndHeight } =
useGetChunkHighlights(selectedChunkId);
const ref = useRef<(highlight: IHighlight) => void>(() => {});
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 { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
import { IChunk } from '@/interfaces/database/knowledge';
import { buildChunkHighlights } from '@/utils/document-util';
import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter';
import { useSelector } from 'umi';
import { ChunkTextMode } from './constant';
export const useSelectDocumentInfo = () => {
const documentInfo: IKnowledgeFile = useSelector(
(state: any) => state.chunkModel.documentInfo,
);
return documentInfo;
};
export const useSelectChunkList = () => {
const chunkList: IChunk[] = useSelector(
(state: any) => state.chunkModel.data,
);
return chunkList;
};
// export const useSelectChunkList = () => {
// const chunkList: IChunk[] = useSelector(
// (state: any) => state.chunkModel.data,
// );
// return chunkList;
// };
export const useHandleChunkCardClick = () => {
const [selectedChunkId, setSelectedChunkId] = useState<string>('');
@ -31,9 +24,9 @@ export const useHandleChunkCardClick = () => {
};
export const useGetSelectedChunk = (selectedChunkId: string) => {
const chunkList: IChunk[] = useSelectChunkList();
const data = useSelectChunkList();
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);
}, [selectedChunk, size]);
const setWidthAndHeight = (width: number, height: number) => {
const setWidthAndHeight = useCallback((width: number, height: number) => {
setSize((pre) => {
if (pre.height !== height || pre.width !== width) {
return { height, width };
}
return pre;
});
};
}, []);
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 type { PaginationProps } from 'antd';
import { Divider, Flex, Pagination, Space, Spin, message } from 'antd';
import classNames from 'classnames';
import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSearchParams, useSelector } from 'umi';
import { useDispatch, useSearchParams } from 'umi';
import ChunkCard from './components/chunk-card';
import CreatingModal from './components/chunk-creating-modal';
import ChunkToolBar from './components/chunk-toolbar';
import DocumentPreview from './components/document-preview/preview';
import {
useChangeChunkTextMode,
useGetChunkHighlights,
useHandleChunkCardClick,
useSelectChunkListLoading,
useSelectDocumentInfo,
} from './hooks';
import { ChunkModelState } from './model';
import { useTranslation } from 'react-i18next';
import styles from './index.less';
const Chunk = () => {
const dispatch = useDispatch();
const chunkModel: ChunkModelState = useSelector(
(state: any) => state.chunkModel,
);
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
const [searchParams] = useSearchParams();
const { data = [], total, pagination } = chunkModel;
const loading = useSelectChunkListLoading();
// const loading = useSelectChunkListLoading();
const documentId: string = searchParams.get('doc_id') || '';
const [chunkId, setChunkId] = useState<string | undefined>();
const { removeChunk } = useDeleteChunkByIds();
const documentInfo = useSelectDocumentInfo();
const {
data: { documentInfo, data = [], total },
pagination,
loading,
searchString,
handleInputChange,
available,
handleSetAvailable,
} = useFetchNextChunkList();
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
const isPdf = documentInfo.type === 'pdf';
const isPdf = documentInfo?.type === 'pdf';
const { t } = useTranslation();
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
const getChunkList = useFetchChunkList();
const handleEditChunk = useCallback(
(chunk_id?: string) => {
setChunkId(chunk_id);
@ -57,14 +58,8 @@ const Chunk = () => {
size,
) => {
setSelectedChunkIds([]);
dispatch({
type: 'chunkModel/setPagination',
payload: {
current: page,
pageSize: size,
},
});
getChunkList();
pagination.onChange?.(page, size);
// getChunkList();
};
const selectAllChunk = useCallback(
@ -125,38 +120,44 @@ const Chunk = () => {
},
});
if (!chunkIds && resCode === 0) {
getChunkList();
// getChunkList();
}
},
[
dispatch,
documentId,
getChunkList,
// getChunkList,
selectedChunkIds,
showSelectedChunkWarning,
],
);
const { highlights, setWidthAndHeight } =
useGetChunkHighlights(selectedChunkId);
useEffect(() => {
getChunkList();
// getChunkList();
return () => {
dispatch({
type: 'chunkModel/resetFilter', // TODO: need to reset state uniformly
});
};
}, [dispatch, getChunkList]);
}, [dispatch]);
return (
<>
<div className={styles.chunkPage}>
<ChunkToolBar
getChunkList={getChunkList}
selectAllChunk={selectAllChunk}
createChunk={handleEditChunk}
removeChunk={handleRemoveChunk}
checked={selectedChunkIds.length === data.length}
switchChunk={switchChunk}
changeChunkTextMode={changeChunkTextMode}
searchString={searchString}
handleInputChange={handleInputChange}
available={available}
handleSetAvailable={handleSetAvailable}
></ChunkToolBar>
<Divider></Divider>
<Flex flex={1} gap={'middle'}>
@ -193,33 +194,22 @@ const Chunk = () => {
</Spin>
<div className={styles.pageFooter}>
<Pagination
responsive
showLessItems
showQuickJumper
showSizeChanger
onChange={onPaginationChange}
pageSize={pagination.pageSize}
pageSizeOptions={[10, 30, 60, 90]}
current={pagination.current}
size={'small'}
{...pagination}
total={total}
showTotal={(total) => (
<Space>
{t('total', { keyPrefix: 'common' })}
{total}
</Space>
)}
size={'small'}
onChange={onPaginationChange}
/>
</div>
</Flex>
{isPdf && (
{
<section className={styles.documentPreview}>
<DocumentPreview
selectedChunkId={selectedChunkId}
highlights={highlights}
setWidthAndHeight={setWidthAndHeight}
></DocumentPreview>
</section>
)}
}
</Flex>
</div>
<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',
layout: false,
},
{
path: 'force',
component: '@/pages/force-graph',
layout: false,
},
{
path: '/*',
component: '@/pages/404',