diff --git a/web/src/hooks/chunk-hooks.ts b/web/src/hooks/chunk-hooks.ts index 65433e029..efdd9fdaa 100644 --- a/web/src/hooks/chunk-hooks.ts +++ b/web/src/hooks/chunk-hooks.ts @@ -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 => { + const { data } = useQuery({ + queryKey: ['fetchChunk'], + enabled: !!chunkId, + initialData: {}, + queryFn: async () => { + const data = await kbService.get_chunk({ + chunk_id: chunkId, + }); + + return data; + }, + }); + + return data; +}; diff --git a/web/src/hooks/knowledge-hooks.ts b/web/src/hooks/knowledge-hooks.ts index aab1bc355..80ae8fdea 100644 --- a/web/src/hooks/knowledge-hooks.ts +++ b/web/src/hooks/knowledge-hooks.ts @@ -48,37 +48,6 @@ export const useDeleteDocumentById = (): { }; }; -export const useDeleteChunkByIds = (): { - removeChunk: (chunkIds: string[], documentId: string) => Promise; -} => { - 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 => { - return showDeleteConfirm({ onOk: removeChunk(chunkIds, documentId) }); - }, - [removeChunk, showDeleteConfirm], - ); - - return { - removeChunk: onRemoveChunk, - }; -}; - export const useFetchKnowledgeBaseConfiguration = () => { const knowledgeBaseId = useKnowledgeBaseId(); diff --git a/web/src/interfaces/common.ts b/web/src/interfaces/common.ts index a26c610e0..666e60705 100644 --- a/web/src/interfaces/common.ts +++ b/web/src/interfaces/common.ts @@ -12,7 +12,7 @@ export interface BaseState { export interface IModalProps { showModal?(): void; hideModal(): void; - visible: boolean; + visible?: boolean; loading?: boolean; onOk?(payload?: T): Promise | void; } diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx index a7e1f7620..130f2905a 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx @@ -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 = ({ doc_id, chunkId }) => { - const dispatch = useDispatch(); +const ChunkCreatingModal: React.FC & 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([]); - 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({ - 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 = ({ doc_id, chunkId }) => { return ( -
+ label={t('chunk.chunk')} name="content" diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx index 18d8c76f8..6a513ba78 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx @@ -76,15 +76,6 @@ const ChunkToolBar = ({ setIsShowSearchBox(true); }; - // const handleSearchChange: ChangeEventHandler = (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); diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts index 7f66e2721..0eef37987 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/hooks.ts @@ -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(''); @@ -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.Full); @@ -68,3 +58,73 @@ export const useChangeChunkTextMode = () => { return { textMode, changeChunkTextMode }; }; + +export const useDeleteChunkByIds = (): { + removeChunk: (chunkIds: string[], documentId: string) => Promise; +} => { + 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 => { + return showDeleteConfirm({ onOk: removeChunk(chunkIds, documentId) }); + }, + [removeChunk, showDeleteConfirm], + ); + + return { + removeChunk: onRemoveChunk, + }; +}; + +export const useUpdateChunk = () => { + const [chunkId, setChunkId] = useState(''); + 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, + }; +}; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx index b7ade5e5b..351f3f668 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx @@ -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([]); - const [searchParams] = useSearchParams(); - // const loading = useSelectChunkListLoading(); - const documentId: string = searchParams.get('doc_id') || ''; - const [chunkId, setChunkId] = useState(); 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({ - 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 ( <>
{ 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 = () => { }
- + {chunkUpdatingVisible && ( + + )} ); }; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts deleted file mode 100644 index 8cc127a90..000000000 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts +++ /dev/null @@ -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 = { - 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; diff --git a/web/typings.d.ts b/web/typings.d.ts index 1ba6ecc89..d4c44aaf7 100644 --- a/web/typings.d.ts +++ b/web/typings.d.ts @@ -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( export interface RootState { chatModel: ChatModelState; kFModel: KFModelState; - chunkModel: ChunkModelState; } declare global {