fix: delete chunk by @tanstack/react-query #1306 (#1749)

### What problem does this PR solve?
fix: delete chunk 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-30 16:55:00 +08:00 committed by GitHub
parent 74ebc497c1
commit ceb0419fe5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 239 additions and 341 deletions

View File

@ -1,32 +1,19 @@
import { ResponseGetType } from '@/interfaces/database/base';
import { ResponseGetType, ResponseType } 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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { PaginationProps } from 'antd';
import { PaginationProps, message } from 'antd';
import { useCallback, useState } from 'react';
import { useDispatch } from 'umi';
import { useTranslation } from 'react-i18next';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
import { useGetKnowledgeSearchParams } from './route-hook';
export const useFetchChunkList = () => {
const dispatch = useDispatch();
const { documentId } = useGetKnowledgeSearchParams();
const fetchChunkList = useCallback(() => {
dispatch({
type: 'chunkModel/chunk_list',
payload: {
doc_id: documentId,
},
});
}, [dispatch, documentId]);
return fetchChunkList;
};
import {
useGetKnowledgeSearchParams,
useSetPaginationParams,
} from './route-hook';
export interface IChunkListResult {
searchString?: string;
@ -125,6 +112,97 @@ export const useSelectChunkList = () => {
documentInfo: IKnowledgeFile;
}>({ queryKey: ['fetchChunkList'] });
// console.log('🚀 ~ useSelectChunkList ~ data:', data);
return data?.at(-1)?.[1];
};
export const useDeleteChunk = () => {
const queryClient = useQueryClient();
const { setPaginationParams } = useSetPaginationParams();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['deleteChunk'],
mutationFn: async (params: { chunkIds: string[]; documentId: string }) => {
const { data } = await kbService.rm_chunk(params);
if (data.retcode === 0) {
setPaginationParams(1);
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
}
return data?.retcode;
},
});
return { data, loading, deleteChunk: mutateAsync };
};
export const useSwitchChunk = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['switchChunk'],
mutationFn: async (params: {
chunk_ids?: string[];
available_int?: number;
doc_id: string;
}) => {
const { data } = await kbService.switch_chunk(params);
if (data.retcode === 0) {
message.success(t('message.modified'));
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
}
return data?.retcode;
},
});
return { data, loading, switchChunk: mutateAsync };
};
export const useCreateChunk = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['createChunk'],
mutationFn: async (payload: any) => {
let service = kbService.create_chunk;
if (payload.chunk_id) {
service = kbService.set_chunk;
}
const { data } = await service(payload);
if (data.retcode === 0) {
message.success(t('message.created'));
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
}
return data?.retcode;
},
});
return { data, loading, createChunk: mutateAsync };
};
export const useFetchChunk = (chunkId?: string): ResponseType<any> => {
const { data } = useQuery({
queryKey: ['fetchChunk'],
enabled: !!chunkId,
initialData: {},
queryFn: async () => {
const data = await kbService.get_chunk({
chunk_id: chunkId,
});
return data;
},
});
return data;
};

View File

@ -48,37 +48,6 @@ export const useDeleteDocumentById = (): {
};
};
export const useDeleteChunkByIds = (): {
removeChunk: (chunkIds: string[], documentId: string) => Promise<number>;
} => {
const dispatch = useDispatch();
const showDeleteConfirm = useShowDeleteConfirm();
const removeChunk = useCallback(
(chunkIds: string[], documentId: string) => () => {
return dispatch({
type: 'chunkModel/rm_chunk',
payload: {
chunk_ids: chunkIds,
doc_id: documentId,
},
});
},
[dispatch],
);
const onRemoveChunk = useCallback(
(chunkIds: string[], documentId: string): Promise<number> => {
return showDeleteConfirm({ onOk: removeChunk(chunkIds, documentId) });
},
[removeChunk, showDeleteConfirm],
);
return {
removeChunk: onRemoveChunk,
};
};
export const useFetchKnowledgeBaseConfiguration = () => {
const knowledgeBaseId = useKnowledgeBaseId();

View File

@ -12,7 +12,7 @@ export interface BaseState {
export interface IModalProps<T> {
showModal?(): void;
hideModal(): void;
visible: boolean;
visible?: boolean;
loading?: boolean;
onOk?(payload?: T): Promise<any> | void;
}

View File

@ -1,10 +1,10 @@
import { useDeleteChunkByIds } from '@/hooks/knowledge-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks';
import { useFetchChunk } from '@/hooks/chunk-hooks';
import { IModalProps } from '@/interfaces/common';
import { DeleteOutlined } from '@ant-design/icons';
import { Checkbox, Divider, Form, Input, Modal, Space } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'umi';
import { useDeleteChunkByIds } from '../../hooks';
import EditTag from '../edit-tag';
type FieldType = {
@ -15,63 +15,39 @@ interface kFProps {
chunkId: string | undefined;
}
const ChunkCreatingModal: React.FC<kFProps> = ({ doc_id, chunkId }) => {
const dispatch = useDispatch();
const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
doc_id,
chunkId,
hideModal,
onOk,
loading,
}) => {
const [form] = Form.useForm();
const isShowCreateModal: boolean = useSelector(
(state: any) => state.chunkModel.isShowCreateModal,
);
const [checked, setChecked] = useState(false);
const [keywords, setKeywords] = useState<string[]>([]);
const loading = useOneNamespaceEffectsLoading('chunkModel', ['create_chunk']);
const { removeChunk } = useDeleteChunkByIds();
const { data } = useFetchChunk(chunkId);
const { t } = useTranslation();
const handleCancel = () => {
dispatch({
type: 'chunkModel/setIsShowCreateModal',
payload: false,
});
};
const getChunk = useCallback(async () => {
console.info(chunkId);
if (chunkId && isShowCreateModal) {
const data = await dispatch<any>({
type: 'chunkModel/get_chunk',
payload: {
chunk_id: chunkId,
},
});
if (data?.retcode === 0) {
const { content_with_weight, important_kwd = [] } = data.data;
form.setFieldsValue({ content: content_with_weight });
setKeywords(important_kwd);
}
useEffect(() => {
if (data?.retcode === 0) {
const { content_with_weight, important_kwd = [] } = data.data;
form.setFieldsValue({ content: content_with_weight });
setKeywords(important_kwd);
}
if (!chunkId) {
setKeywords([]);
form.setFieldsValue({ content: undefined });
}
}, [chunkId, isShowCreateModal, dispatch, form]);
useEffect(() => {
getChunk();
}, [getChunk]);
}, [data, form, chunkId]);
const handleOk = async () => {
try {
const values = await form.validateFields();
dispatch({
type: 'chunkModel/create_chunk',
payload: {
content_with_weight: values.content,
doc_id,
chunk_id: chunkId,
important_kwd: keywords, // keywords
},
onOk?.({
content: values.content,
keywords, // keywords
});
} catch (errorInfo) {
console.log('Failed:', errorInfo);
@ -90,17 +66,13 @@ const ChunkCreatingModal: React.FC<kFProps> = ({ doc_id, chunkId }) => {
return (
<Modal
title={`${chunkId ? t('common.edit') : t('common.create')} ${t('chunk.chunk')}`}
open={isShowCreateModal}
open={true}
onOk={handleOk}
onCancel={handleCancel}
onCancel={hideModal}
okButtonProps={{ loading }}
destroyOnClose
>
<Form
form={form}
// name="validateOnly"
autoComplete="off"
layout={'vertical'}
>
<Form form={form} autoComplete="off" layout={'vertical'}>
<Form.Item<FieldType>
label={t('chunk.chunk')}
name="content"

View File

@ -76,15 +76,6 @@ 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 handleSearchBlur = () => {
if (!searchString?.trim()) {
setIsShowSearchBox(false);

View File

@ -1,18 +1,16 @@
import { useSelectChunkList } from '@/hooks/chunk-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks';
import {
useCreateChunk,
useDeleteChunk,
useSelectChunkList,
} from '@/hooks/chunk-hooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
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 { ChunkTextMode } from './constant';
// export const useSelectChunkList = () => {
// const chunkList: IChunk[] = useSelector(
// (state: any) => state.chunkModel.data,
// );
// return chunkList;
// };
export const useHandleChunkCardClick = () => {
const [selectedChunkId, setSelectedChunkId] = useState<string>('');
@ -50,14 +48,6 @@ export const useGetChunkHighlights = (selectedChunkId: string) => {
return { highlights, setWidthAndHeight };
};
export const useSelectChunkListLoading = () => {
return useOneNamespaceEffectsLoading('chunkModel', [
'create_hunk',
'chunk_list',
'switch_chunk',
]);
};
// Switch chunk text to be fully displayed or ellipse
export const useChangeChunkTextMode = () => {
const [textMode, setTextMode] = useState<ChunkTextMode>(ChunkTextMode.Full);
@ -68,3 +58,73 @@ export const useChangeChunkTextMode = () => {
return { textMode, changeChunkTextMode };
};
export const useDeleteChunkByIds = (): {
removeChunk: (chunkIds: string[], documentId: string) => Promise<number>;
} => {
const { deleteChunk } = useDeleteChunk();
const showDeleteConfirm = useShowDeleteConfirm();
const removeChunk = useCallback(
(chunkIds: string[], documentId: string) => () => {
return deleteChunk({ chunkIds, documentId });
},
[deleteChunk],
);
const onRemoveChunk = useCallback(
(chunkIds: string[], documentId: string): Promise<number> => {
return showDeleteConfirm({ onOk: removeChunk(chunkIds, documentId) });
},
[removeChunk, showDeleteConfirm],
);
return {
removeChunk: onRemoveChunk,
};
};
export const useUpdateChunk = () => {
const [chunkId, setChunkId] = useState<string | undefined>('');
const {
visible: chunkUpdatingVisible,
hideModal: hideChunkUpdatingModal,
showModal,
} = useSetModalState();
const { createChunk, loading } = useCreateChunk();
const { documentId } = useGetKnowledgeSearchParams();
const onChunkUpdatingOk = useCallback(
async ({ content, keywords }: { content: string; keywords: string }) => {
const retcode = await createChunk({
content_with_weight: content,
doc_id: documentId,
chunk_id: chunkId,
important_kwd: keywords, // keywords
});
if (retcode === 0) {
hideChunkUpdatingModal();
}
},
[createChunk, hideChunkUpdatingModal, chunkId, documentId],
);
const handleShowChunkUpdatingModal = useCallback(
async (id?: string) => {
setChunkId(id);
showModal();
},
[showModal],
);
return {
chunkUpdatingLoading: loading,
onChunkUpdatingOk,
chunkUpdatingVisible,
hideChunkUpdatingModal,
showChunkUpdatingModal: handleShowChunkUpdatingModal,
chunkId,
documentId,
};
};

View File

@ -1,31 +1,25 @@
import { useFetchNextChunkList } from '@/hooks/chunk-hooks';
import { useDeleteChunkByIds } from '@/hooks/knowledge-hooks';
import { useFetchNextChunkList, useSwitchChunk } from '@/hooks/chunk-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 } from 'umi';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
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,
useDeleteChunkByIds,
useGetChunkHighlights,
useHandleChunkCardClick,
useUpdateChunk,
} from './hooks';
import { useTranslation } from 'react-i18next';
import styles from './index.less';
const Chunk = () => {
const dispatch = useDispatch();
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
const [searchParams] = useSearchParams();
// const loading = useSelectChunkListLoading();
const documentId: string = searchParams.get('doc_id') || '';
const [chunkId, setChunkId] = useState<string | undefined>();
const { removeChunk } = useDeleteChunkByIds();
const {
data: { documentInfo, data = [], total },
@ -38,20 +32,19 @@ const Chunk = () => {
} = useFetchNextChunkList();
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
const isPdf = documentInfo?.type === 'pdf';
const { t } = useTranslation();
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
const handleEditChunk = useCallback(
(chunk_id?: string) => {
setChunkId(chunk_id);
dispatch({
type: 'chunkModel/setIsShowCreateModal',
payload: true,
});
},
[dispatch],
);
const { switchChunk } = useSwitchChunk();
const {
chunkUpdatingLoading,
onChunkUpdatingOk,
showChunkUpdatingModal,
hideChunkUpdatingModal,
chunkId,
chunkUpdatingVisible,
documentId,
} = useUpdateChunk();
const onPaginationChange: PaginationProps['onShowSizeChange'] = (
page,
@ -100,7 +93,7 @@ const Chunk = () => {
}
}, [selectedChunkIds, documentId, removeChunk, showSelectedChunkWarning]);
const switchChunk = useCallback(
const handleSwitchChunk = useCallback(
async (available?: number, chunkIds?: string[]) => {
let ids = chunkIds;
if (!chunkIds) {
@ -111,20 +104,17 @@ const Chunk = () => {
}
}
const resCode: number = await dispatch<any>({
type: 'chunkModel/switch_chunk',
payload: {
chunk_ids: ids,
available_int: available,
doc_id: documentId,
},
const resCode: number = await switchChunk({
chunk_ids: ids,
available_int: available,
doc_id: documentId,
});
if (!chunkIds && resCode === 0) {
// getChunkList();
}
},
[
dispatch,
switchChunk,
documentId,
// getChunkList,
selectedChunkIds,
@ -135,24 +125,15 @@ const Chunk = () => {
const { highlights, setWidthAndHeight } =
useGetChunkHighlights(selectedChunkId);
useEffect(() => {
// getChunkList();
return () => {
dispatch({
type: 'chunkModel/resetFilter', // TODO: need to reset state uniformly
});
};
}, [dispatch]);
return (
<>
<div className={styles.chunkPage}>
<ChunkToolBar
selectAllChunk={selectAllChunk}
createChunk={handleEditChunk}
createChunk={showChunkUpdatingModal}
removeChunk={handleRemoveChunk}
checked={selectedChunkIds.length === data.length}
switchChunk={switchChunk}
switchChunk={handleSwitchChunk}
changeChunkTextMode={changeChunkTextMode}
searchString={searchString}
handleInputChange={handleInputChange}
@ -178,12 +159,12 @@ const Chunk = () => {
<ChunkCard
item={item}
key={item.chunk_id}
editChunk={handleEditChunk}
editChunk={showChunkUpdatingModal}
checked={selectedChunkIds.some(
(x) => x === item.chunk_id,
)}
handleCheckboxClick={handleSingleCheckboxClick}
switchChunk={switchChunk}
switchChunk={handleSwitchChunk}
clickChunkCard={handleChunkCardClick}
selected={item.chunk_id === selectedChunkId}
textMode={textMode}
@ -212,7 +193,16 @@ const Chunk = () => {
}
</Flex>
</div>
<CreatingModal doc_id={documentId} chunkId={chunkId} />
{chunkUpdatingVisible && (
<CreatingModal
doc_id={documentId}
chunkId={chunkId}
hideModal={hideChunkUpdatingModal}
visible={chunkUpdatingVisible}
loading={chunkUpdatingLoading}
onOk={onChunkUpdatingOk}
/>
)}
</>
);
};

View File

@ -1,160 +0,0 @@
import { BaseState } from '@/interfaces/common';
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
import kbService from '@/services/knowledge-service';
import { message } from 'antd';
import { pick } from 'lodash';
// import { delay } from '@/utils/store-util';
import i18n from '@/locales/config';
import { DvaModel } from 'umi';
export interface ChunkModelState extends BaseState {
data: IChunk[];
total: number;
isShowCreateModal: boolean;
chunk_id: string;
doc_id: string;
chunkInfo: any;
documentInfo: IKnowledgeFile;
available?: number;
}
const model: DvaModel<ChunkModelState> = {
namespace: 'chunkModel',
state: {
data: [],
total: 0,
isShowCreateModal: false,
chunk_id: '',
doc_id: '',
chunkInfo: {},
documentInfo: {} as IKnowledgeFile,
pagination: {
total: 0,
current: 1,
pageSize: 10,
},
searchString: '',
available: undefined, // set to undefined to select all
},
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
setIsShowCreateModal(state, { payload }) {
return {
...state,
isShowCreateModal:
typeof payload === 'boolean' ? payload : !state.isShowCreateModal,
};
},
setAvailable(state, { payload }) {
return { ...state, available: payload };
},
setSearchString(state, { payload }) {
return {
...state,
pagination: { ...state.pagination, current: 1 },
searchString: payload,
};
},
setPagination(state, { payload }) {
return { ...state, pagination: { ...state.pagination, ...payload } };
},
resetFilter(state, {}) {
return {
...state,
pagination: {
current: 1,
pageSize: 10,
total: 0,
},
searchString: '',
available: undefined,
};
},
},
effects: {
*chunk_list({ payload = {} }, { call, put, select }) {
const { available, searchString, pagination }: ChunkModelState =
yield select((state: any) => state.chunkModel);
const { data } = yield call(kbService.chunk_list, {
...payload,
available_int: available,
keywords: searchString,
page: pagination.current,
size: pagination.pageSize,
});
const { retcode, data: res } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
data: res.chunks,
total: res.total,
documentInfo: res.doc,
},
});
}
},
throttledGetChunkList: [
function* ({ payload }, { put }) {
yield put({ type: 'chunk_list', payload: { doc_id: payload } });
},
{ type: 'throttle', ms: 1000 }, // TODO: Provide type support for this effect
],
*switch_chunk({ payload = {} }, { call }) {
const { data } = yield call(kbService.switch_chunk, payload);
const { retcode } = data;
if (retcode === 0) {
message.success(i18n.t('message.modified'));
}
return retcode;
},
*rm_chunk({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.rm_chunk, payload);
const { retcode } = data;
if (retcode === 0) {
yield put({
type: 'setIsShowCreateModal',
payload: false,
});
yield put({ type: 'setPagination', payload: { current: 1 } });
yield put({ type: 'chunk_list', payload: pick(payload, ['doc_id']) });
}
return retcode;
},
*get_chunk({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.get_chunk, payload);
const { retcode, data: res } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
chunkInfo: res,
},
});
}
return data;
},
*create_chunk({ payload = {} }, { call, put }) {
let service = kbService.create_chunk;
if (payload.chunk_id) {
service = kbService.set_chunk;
}
const { data } = yield call(service, payload);
const { retcode } = data;
if (retcode === 0) {
yield put({
type: 'setIsShowCreateModal',
payload: false,
});
yield put({ type: 'chunk_list', payload: pick(payload, ['doc_id']) });
}
},
},
};
export default model;

2
web/typings.d.ts vendored
View File

@ -1,4 +1,3 @@
import { ChunkModelState } from '@/pages/add-knowledge/components/knowledge-chunk/model';
import { KFModelState } from '@/pages/add-knowledge/components/knowledge-file/model';
import { ChatModelState } from '@/pages/chat/model';
@ -12,7 +11,6 @@ function useSelector<TState = RootState, TSelected = unknown>(
export interface RootState {
chatModel: ChatModelState;
kFModel: KFModelState;
chunkModel: ChunkModelState;
}
declare global {