### What problem does this PR solve? fix: use @tanstack/react-query to get knowledge base data #1306 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)tags/v0.9.0
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { useCallback, useEffect } from 'react'; | import { useCallback, useEffect } from 'react'; | ||||
| import { useDispatch, useSearchParams, useSelector } from 'umi'; | import { useDispatch, useSearchParams, useSelector } from 'umi'; | ||||
| import { useGetKnowledgeSearchParams } from './route-hook'; | |||||
| export const useKnowledgeBaseId = (): string => { | export const useKnowledgeBaseId = (): string => { | ||||
| const [searchParams] = useSearchParams(); | const [searchParams] = useSearchParams(); | ||||
| }; | }; | ||||
| }; | }; | ||||
| export const useFetchKnowledgeDetail = () => { | |||||
| const dispatch = useDispatch(); | |||||
| const { knowledgeId } = useGetKnowledgeSearchParams(); | |||||
| const fetchKnowledgeDetail = useCallback( | |||||
| (knowledgeId: string) => { | |||||
| dispatch({ | |||||
| type: 'knowledgeModel/getKnowledgeDetail', | |||||
| payload: { kb_id: knowledgeId }, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| useEffect(() => { | |||||
| fetchKnowledgeDetail(knowledgeId); | |||||
| }, [fetchKnowledgeDetail, knowledgeId]); | |||||
| return fetchKnowledgeDetail; | |||||
| }; | |||||
| export const useSelectKnowledgeDetail = () => { | |||||
| const knowledge: IKnowledge = useSelector( | |||||
| (state: any) => state.knowledgeModel.knowledge, | |||||
| ); | |||||
| return knowledge; | |||||
| }; | |||||
| export const useGetDocumentDefaultParser = () => { | |||||
| const item = useSelectKnowledgeDetail(); | |||||
| return { | |||||
| defaultParserId: item?.parser_id ?? '', | |||||
| parserConfig: item?.parser_config ?? '', | |||||
| }; | |||||
| }; | |||||
| export const useDeleteChunkByIds = (): { | export const useDeleteChunkByIds = (): { | ||||
| removeChunk: (chunkIds: string[], documentId: string) => Promise<number>; | removeChunk: (chunkIds: string[], documentId: string) => Promise<number>; | ||||
| } => { | } => { | ||||
| }; | }; | ||||
| export const useFetchKnowledgeBaseConfiguration = () => { | export const useFetchKnowledgeBaseConfiguration = () => { | ||||
| const dispatch = useDispatch(); | |||||
| const knowledgeBaseId = useKnowledgeBaseId(); | const knowledgeBaseId = useKnowledgeBaseId(); | ||||
| const fetchKnowledgeBaseConfiguration = useCallback(() => { | |||||
| dispatch({ | |||||
| type: 'kSModel/getKbDetail', | |||||
| payload: { | |||||
| const { data, isFetching: loading } = useQuery({ | |||||
| queryKey: ['fetchKnowledgeDetail'], | |||||
| initialData: {}, | |||||
| gcTime: 0, | |||||
| queryFn: async () => { | |||||
| const { data } = await kbService.get_kb_detail({ | |||||
| kb_id: knowledgeBaseId, | kb_id: knowledgeBaseId, | ||||
| }, | |||||
| }); | |||||
| }, [dispatch, knowledgeBaseId]); | |||||
| }); | |||||
| return data?.data ?? {}; | |||||
| }, | |||||
| }); | |||||
| useEffect(() => { | |||||
| fetchKnowledgeBaseConfiguration(); | |||||
| }, [fetchKnowledgeBaseConfiguration]); | |||||
| return { data, loading }; | |||||
| }; | }; | ||||
| export const useNextFetchKnowledgeList = ( | export const useNextFetchKnowledgeList = ( | ||||
| //#region knowledge configuration | //#region knowledge configuration | ||||
| export const useUpdateKnowledge = () => { | export const useUpdateKnowledge = () => { | ||||
| const dispatch = useDispatch(); | |||||
| const saveKnowledgeConfiguration = useCallback( | |||||
| (payload: any) => { | |||||
| dispatch({ | |||||
| type: 'kSModel/updateKb', | |||||
| payload, | |||||
| const knowledgeBaseId = useKnowledgeBaseId(); | |||||
| const queryClient = useQueryClient(); | |||||
| const { | |||||
| data, | |||||
| isPending: loading, | |||||
| mutateAsync, | |||||
| } = useMutation({ | |||||
| mutationKey: ['saveKnowledge'], | |||||
| mutationFn: async (params: Record<string, any>) => { | |||||
| const { data = {} } = await kbService.updateKb({ | |||||
| kb_id: knowledgeBaseId, | |||||
| ...params, | |||||
| }); | }); | ||||
| if (data.retcode === 0) { | |||||
| message.success(i18n.t(`message.updated`)); | |||||
| queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] }); | |||||
| } | |||||
| return data; | |||||
| }, | }, | ||||
| [dispatch], | |||||
| ); | |||||
| }); | |||||
| return saveKnowledgeConfiguration; | |||||
| return { data, loading, saveKnowledgeConfiguration: mutateAsync }; | |||||
| }; | }; | ||||
| export const useSelectKnowledgeDetails = () => { | |||||
| const knowledgeDetails: IKnowledge = useSelector( | |||||
| (state: any) => state.kSModel.knowledgeDetails, | |||||
| ); | |||||
| return knowledgeDetails; | |||||
| }; | |||||
| //#endregion | //#endregion | ||||
| //#region Retrieval testing | //#region Retrieval testing |
| .uploadWrapper { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| padding: 64px 32px 32px; | |||||
| height: 100%; | |||||
| .backToList { | |||||
| display: none; | |||||
| padding-bottom: 60px; | |||||
| } | |||||
| .footer { | |||||
| text-align: center; | |||||
| padding-top: 16px; | |||||
| .nextButton { | |||||
| // background-color: @purple; | |||||
| font-weight: 700; | |||||
| font-size: 24px; | |||||
| display: inline-flex; | |||||
| align-items: center; | |||||
| padding: 26px 40px; | |||||
| justify-content: center; | |||||
| width: 10%; | |||||
| } | |||||
| } | |||||
| .uploadContent { | |||||
| flex: 1; | |||||
| padding-top: 60px; | |||||
| } | |||||
| .uploader { | |||||
| :global(.ant-upload) { | |||||
| height: 126px; | |||||
| } | |||||
| } | |||||
| .hiddenUploader { | |||||
| :global(.ant-upload-drag) { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| .fileIcon { | |||||
| font-size: 40px; | |||||
| align-items: end; | |||||
| } | |||||
| .uploaderButton { | |||||
| padding: 10px; | |||||
| vertical-align: middle; | |||||
| height: 40px; | |||||
| } | |||||
| .uploaderIcon { | |||||
| svg { | |||||
| width: 20px; | |||||
| height: 20px; | |||||
| } | |||||
| } | |||||
| .deleteIcon { | |||||
| font-size: 20px; | |||||
| } | |||||
| .uploaderItem { | |||||
| margin-top: 16px; | |||||
| :global(.ant-card-body) { | |||||
| padding: 16px; | |||||
| } | |||||
| } | |||||
| .uploaderItemProgress { | |||||
| padding-left: 50px; | |||||
| } | |||||
| .uploaderItemTextWrapper { | |||||
| flex: 1; | |||||
| text-align: left; | |||||
| padding-left: 10px; | |||||
| } | |||||
| } | |||||
| .progressWrapper { | |||||
| text-align: center; | |||||
| } | |||||
| .progress { | |||||
| width: 50%; | |||||
| margin: 0; | |||||
| } | |||||
| .selectFilesText { | |||||
| transform: translateX(10%); | |||||
| } | |||||
| .changeSpecificCategoryText { | |||||
| transform: translateX(30%); | |||||
| } |
| import { ReactComponent as SelectFilesEndIcon } from '@/assets/svg/select-files-end.svg'; | |||||
| import { ReactComponent as SelectFilesStartIcon } from '@/assets/svg/select-files-start.svg'; | |||||
| import ChunkMethodModal from '@/components/chunk-method-modal'; | |||||
| import { KnowledgeRouteKey } from '@/constants/knowledge'; | |||||
| import { | |||||
| useRunDocument, | |||||
| useSelectDocumentList, | |||||
| useUploadDocument, | |||||
| } from '@/hooks/document-hooks'; | |||||
| import { | |||||
| useDeleteDocumentById, | |||||
| useFetchKnowledgeDetail, | |||||
| useKnowledgeBaseId, | |||||
| } from '@/hooks/knowledge-hooks'; | |||||
| import { | |||||
| useChangeDocumentParser, | |||||
| useSetSelectedRecord, | |||||
| } from '@/hooks/logic-hooks'; | |||||
| import { useFetchTenantInfo } from '@/hooks/user-setting-hooks'; | |||||
| import { IKnowledgeFile } from '@/interfaces/database/knowledge'; | |||||
| import { getExtension, isFileUploadDone } from '@/utils/documentUtils'; | |||||
| import { | |||||
| ArrowLeftOutlined, | |||||
| CloudUploadOutlined, | |||||
| DeleteOutlined, | |||||
| EditOutlined, | |||||
| FileDoneOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { | |||||
| Button, | |||||
| Card, | |||||
| Flex, | |||||
| Progress, | |||||
| Space, | |||||
| Upload, | |||||
| UploadFile, | |||||
| UploadProps, | |||||
| } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { ReactElement, useCallback, useMemo, useRef, useState } from 'react'; | |||||
| import { Link, useNavigate } from 'umi'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import styles from './index.less'; | |||||
| const { Dragger } = Upload; | |||||
| type UploadRequestOption = Parameters< | |||||
| NonNullable<UploadProps['customRequest']> | |||||
| >[0]; | |||||
| const UploaderItem = ({ | |||||
| file, | |||||
| isUpload, | |||||
| remove, | |||||
| handleEdit, | |||||
| }: { | |||||
| isUpload: boolean; | |||||
| originNode: ReactElement; | |||||
| file: UploadFile; | |||||
| fileList: object[]; | |||||
| showModal: () => void; | |||||
| remove: (id: string) => void; | |||||
| setRecord: (record: IKnowledgeFile) => void; | |||||
| handleEdit: (id: string) => void; | |||||
| }) => { | |||||
| const { removeDocument } = useDeleteDocumentById(); | |||||
| const documentId = file?.response?.id; | |||||
| const handleRemove = async () => { | |||||
| if (file.status === 'error') { | |||||
| remove(documentId); | |||||
| } else { | |||||
| const ret: any = await removeDocument(documentId); | |||||
| if (ret === 0) { | |||||
| remove(documentId); | |||||
| } | |||||
| } | |||||
| }; | |||||
| const handleEditClick = () => { | |||||
| if (file.status === 'done') { | |||||
| handleEdit(documentId); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <Card className={styles.uploaderItem}> | |||||
| <Flex justify="space-between"> | |||||
| <FileDoneOutlined className={styles.fileIcon} /> | |||||
| <section className={styles.uploaderItemTextWrapper}> | |||||
| <div> | |||||
| <b>{file.name}</b> | |||||
| </div> | |||||
| <span>{file.size}</span> | |||||
| </section> | |||||
| {isUpload ? ( | |||||
| <DeleteOutlined | |||||
| className={styles.deleteIcon} | |||||
| onClick={handleRemove} | |||||
| /> | |||||
| ) : ( | |||||
| <EditOutlined onClick={handleEditClick} /> | |||||
| )} | |||||
| </Flex> | |||||
| <Flex> | |||||
| <Progress | |||||
| showInfo={false} | |||||
| percent={100} | |||||
| className={styles.uploaderItemProgress} | |||||
| strokeColor=" | |||||
| rgba(127, 86, 217, 1) | |||||
| " | |||||
| /> | |||||
| <span>100%</span> | |||||
| </Flex> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| const KnowledgeUploadFile = () => { | |||||
| const knowledgeBaseId = useKnowledgeBaseId(); | |||||
| const [isUpload, setIsUpload] = useState(true); | |||||
| const [uploadedFileIds, setUploadedFileIds] = useState<string[]>([]); | |||||
| const fileListRef = useRef<UploadFile[]>([]); | |||||
| const navigate = useNavigate(); | |||||
| const { currentRecord, setRecord } = useSetSelectedRecord(); | |||||
| const { | |||||
| changeParserLoading, | |||||
| onChangeParserOk, | |||||
| changeParserVisible, | |||||
| hideChangeParserModal, | |||||
| showChangeParserModal, | |||||
| } = useChangeDocumentParser(currentRecord.id); | |||||
| const documentList = useSelectDocumentList(); | |||||
| const runDocumentByIds = useRunDocument(); | |||||
| const uploadDocument = useUploadDocument(); | |||||
| const { t } = useTranslate('knowledgeDetails'); | |||||
| const enabled = useMemo(() => { | |||||
| if (isUpload) { | |||||
| return ( | |||||
| uploadedFileIds.length > 0 && | |||||
| fileListRef.current.filter((x) => isFileUploadDone(x)).length === | |||||
| uploadedFileIds.length | |||||
| ); | |||||
| } | |||||
| return true; | |||||
| }, [uploadedFileIds, isUpload]); | |||||
| const createRequest: (props: UploadRequestOption) => void = async function ({ | |||||
| file, | |||||
| onSuccess, | |||||
| onError, | |||||
| // onProgress, | |||||
| }) { | |||||
| const data = await uploadDocument(file as UploadFile); | |||||
| if (data?.retcode === 0) { | |||||
| setUploadedFileIds((pre) => { | |||||
| return pre.concat(data.data.id); | |||||
| }); | |||||
| if (onSuccess) { | |||||
| onSuccess(data.data); | |||||
| } | |||||
| } else { | |||||
| if (onError) { | |||||
| onError(data?.data); | |||||
| } | |||||
| } | |||||
| }; | |||||
| const removeIdFromUploadedIds = useCallback((id: string) => { | |||||
| setUploadedFileIds((pre) => { | |||||
| return pre.filter((x) => x !== id); | |||||
| }); | |||||
| }, []); | |||||
| const handleItemEdit = useCallback( | |||||
| (id: string) => { | |||||
| const document = documentList.find((x) => x.id === id); | |||||
| if (document) { | |||||
| setRecord(document); | |||||
| } | |||||
| showChangeParserModal(); | |||||
| }, | |||||
| [documentList, showChangeParserModal, setRecord], | |||||
| ); | |||||
| const props: UploadProps = { | |||||
| name: 'file', | |||||
| multiple: true, | |||||
| itemRender(originNode, file, fileList, actions) { | |||||
| fileListRef.current = fileList; | |||||
| const remove = (id: string) => { | |||||
| if (isFileUploadDone(file)) { | |||||
| removeIdFromUploadedIds(id); | |||||
| } | |||||
| actions.remove(); | |||||
| }; | |||||
| return ( | |||||
| <UploaderItem | |||||
| isUpload={isUpload} | |||||
| file={file} | |||||
| fileList={fileList} | |||||
| originNode={originNode} | |||||
| remove={remove} | |||||
| showModal={showChangeParserModal} | |||||
| setRecord={setRecord} | |||||
| handleEdit={handleItemEdit} | |||||
| ></UploaderItem> | |||||
| ); | |||||
| }, | |||||
| customRequest: createRequest, | |||||
| onDrop(e) { | |||||
| console.log('Dropped files', e.dataTransfer.files); | |||||
| }, | |||||
| }; | |||||
| const runSelectedDocument = () => { | |||||
| const ids = fileListRef.current.map((x) => x.response.id); | |||||
| runDocumentByIds({ doc_ids: ids, run: 1 }); | |||||
| }; | |||||
| const handleNextClick = () => { | |||||
| if (!isUpload) { | |||||
| runSelectedDocument(); | |||||
| navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`); | |||||
| } else { | |||||
| setIsUpload(false); | |||||
| } | |||||
| }; | |||||
| useFetchTenantInfo(); | |||||
| useFetchKnowledgeDetail(); | |||||
| return ( | |||||
| <> | |||||
| <div className={styles.uploadWrapper}> | |||||
| <section> | |||||
| <Space className={styles.backToList}> | |||||
| <ArrowLeftOutlined /> | |||||
| <Link to={`/knowledge/dataset?id=${knowledgeBaseId}`}> | |||||
| Back to select files | |||||
| </Link> | |||||
| </Space> | |||||
| <div className={styles.progressWrapper}> | |||||
| <Flex align="center" justify="center"> | |||||
| <SelectFilesStartIcon></SelectFilesStartIcon> | |||||
| <Progress | |||||
| percent={100} | |||||
| showInfo={false} | |||||
| className={styles.progress} | |||||
| strokeColor=" | |||||
| rgba(127, 86, 217, 1) | |||||
| " | |||||
| /> | |||||
| <SelectFilesEndIcon></SelectFilesEndIcon> | |||||
| </Flex> | |||||
| <Flex justify="space-around"> | |||||
| <p className={styles.selectFilesText}> | |||||
| <b>{t('selectFiles')}</b> | |||||
| </p> | |||||
| <p className={styles.changeSpecificCategoryText}> | |||||
| <b>{t('changeSpecificCategory')}</b> | |||||
| </p> | |||||
| </Flex> | |||||
| </div> | |||||
| </section> | |||||
| <section className={styles.uploadContent}> | |||||
| <Dragger | |||||
| {...props} | |||||
| className={classNames(styles.uploader, { | |||||
| [styles.hiddenUploader]: !isUpload, | |||||
| })} | |||||
| > | |||||
| <Button className={styles.uploaderButton}> | |||||
| <CloudUploadOutlined className={styles.uploaderIcon} /> | |||||
| </Button> | |||||
| <p className="ant-upload-text">{t('uploadTitle')}</p> | |||||
| <p className="ant-upload-hint">{t('uploadDescription')}</p> | |||||
| </Dragger> | |||||
| </section> | |||||
| <section className={styles.footer}> | |||||
| <Button | |||||
| type="primary" | |||||
| className={styles.nextButton} | |||||
| onClick={handleNextClick} | |||||
| disabled={!enabled} | |||||
| size="large" | |||||
| > | |||||
| {t('next', { keyPrefix: 'common' })} | |||||
| </Button> | |||||
| </section> | |||||
| </div> | |||||
| <ChunkMethodModal | |||||
| documentId={currentRecord.id} | |||||
| parserId={currentRecord.parser_id} | |||||
| parserConfig={currentRecord.parser_config} | |||||
| documentExtension={getExtension(currentRecord.name)} | |||||
| onOk={onChangeParserOk} | |||||
| visible={changeParserVisible} | |||||
| hideModal={hideChangeParserModal} | |||||
| loading={changeParserLoading} | |||||
| /> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default KnowledgeUploadFile; |
| import { | import { | ||||
| useFetchKnowledgeBaseConfiguration, | useFetchKnowledgeBaseConfiguration, | ||||
| useKnowledgeBaseId, | |||||
| useSelectKnowledgeDetails, | |||||
| useUpdateKnowledge, | useUpdateKnowledge, | ||||
| } from '@/hooks/knowledge-hooks'; | } from '@/hooks/knowledge-hooks'; | ||||
| import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llm-hooks'; | import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llm-hooks'; | ||||
| import { useNavigateToDataset } from '@/hooks/route-hook'; | import { useNavigateToDataset } from '@/hooks/route-hook'; | ||||
| import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks'; | |||||
| import { | import { | ||||
| useFetchTenantInfo, | useFetchTenantInfo, | ||||
| useSelectParserList, | useSelectParserList, | ||||
| getBase64FromUploadFileList, | getBase64FromUploadFileList, | ||||
| getUploadFileListFromBase64, | getUploadFileListFromBase64, | ||||
| } from '@/utils/fileUtil'; | } from '@/utils/fileUtil'; | ||||
| import { useIsFetching } from '@tanstack/react-query'; | |||||
| import { Form, UploadFile } from 'antd'; | import { Form, UploadFile } from 'antd'; | ||||
| import { FormInstance } from 'antd/lib'; | import { FormInstance } from 'antd/lib'; | ||||
| import pick from 'lodash/pick'; | import pick from 'lodash/pick'; | ||||
| import { LlmModelType } from '../../constant'; | import { LlmModelType } from '../../constant'; | ||||
| export const useSubmitKnowledgeConfiguration = (form: FormInstance) => { | export const useSubmitKnowledgeConfiguration = (form: FormInstance) => { | ||||
| const save = useUpdateKnowledge(); | |||||
| const knowledgeBaseId = useKnowledgeBaseId(); | |||||
| const submitLoading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']); | |||||
| const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge(); | |||||
| const navigateToDataset = useNavigateToDataset(); | const navigateToDataset = useNavigateToDataset(); | ||||
| const submitKnowledgeConfiguration = useCallback(async () => { | const submitKnowledgeConfiguration = useCallback(async () => { | ||||
| const values = await form.validateFields(); | const values = await form.validateFields(); | ||||
| const avatar = await getBase64FromUploadFileList(values.avatar); | const avatar = await getBase64FromUploadFileList(values.avatar); | ||||
| save({ | |||||
| saveKnowledgeConfiguration({ | |||||
| ...values, | ...values, | ||||
| avatar, | avatar, | ||||
| kb_id: knowledgeBaseId, | |||||
| }); | }); | ||||
| navigateToDataset(); | navigateToDataset(); | ||||
| }, [save, knowledgeBaseId, form, navigateToDataset]); | |||||
| }, [saveKnowledgeConfiguration, form, navigateToDataset]); | |||||
| return { submitKnowledgeConfiguration, submitLoading, navigateToDataset }; | |||||
| return { | |||||
| submitKnowledgeConfiguration, | |||||
| submitLoading: loading, | |||||
| navigateToDataset, | |||||
| }; | |||||
| }; | }; | ||||
| export const useFetchKnowledgeConfigurationOnMount = (form: FormInstance) => { | export const useFetchKnowledgeConfigurationOnMount = (form: FormInstance) => { | ||||
| const knowledgeDetails = useSelectKnowledgeDetails(); | |||||
| const parserList = useSelectParserList(); | const parserList = useSelectParserList(); | ||||
| const embeddingModelOptions = useSelectLlmOptions(); | const embeddingModelOptions = useSelectLlmOptions(); | ||||
| useFetchTenantInfo(); | useFetchTenantInfo(); | ||||
| useFetchKnowledgeBaseConfiguration(); | |||||
| const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration(); | |||||
| useFetchLlmList(LlmModelType.Embedding); | useFetchLlmList(LlmModelType.Embedding); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| }; | }; | ||||
| export const useSelectKnowledgeDetailsLoading = () => | export const useSelectKnowledgeDetailsLoading = () => | ||||
| useOneNamespaceEffectsLoading('kSModel', ['getKbDetail']); | |||||
| useIsFetching({ queryKey: ['fetchKnowledgeDetail'] }) > 0; | |||||
| export const useHandleChunkMethodChange = () => { | export const useHandleChunkMethodChange = () => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); |
| import { IKnowledge } from '@/interfaces/database/knowledge'; | |||||
| import i18n from '@/locales/config'; | |||||
| import kbService from '@/services/knowledge-service'; | |||||
| import { message } from 'antd'; | |||||
| import { DvaModel } from 'umi'; | |||||
| export interface KSModelState { | |||||
| isShowPSwModal: boolean; | |||||
| tenantIfo: any; | |||||
| knowledgeDetails: IKnowledge; | |||||
| } | |||||
| const model: DvaModel<KSModelState> = { | |||||
| namespace: 'kSModel', | |||||
| state: { | |||||
| isShowPSwModal: false, | |||||
| tenantIfo: {}, | |||||
| knowledgeDetails: {} as any, | |||||
| }, | |||||
| reducers: { | |||||
| updateState(state, { payload }) { | |||||
| return { | |||||
| ...state, | |||||
| ...payload, | |||||
| }; | |||||
| }, | |||||
| setKnowledgeDetails(state, { payload }) { | |||||
| return { ...state, knowledgeDetails: payload }; | |||||
| }, | |||||
| }, | |||||
| effects: { | |||||
| *updateKb({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(kbService.updateKb, payload); | |||||
| const { retcode } = data; | |||||
| if (retcode === 0) { | |||||
| yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } }); | |||||
| message.success(i18n.t('message.updated')); | |||||
| } | |||||
| }, | |||||
| *getKbDetail({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(kbService.get_kb_detail, payload); | |||||
| if (data.retcode === 0) { | |||||
| yield put({ type: 'setKnowledgeDetails', payload: data.data }); | |||||
| } | |||||
| return data; | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| export default model; |
| import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg'; | import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg'; | ||||
| import { useFetchKnowledgeBaseConfiguration } from '@/hooks/knowledge-hooks'; | import { useFetchKnowledgeBaseConfiguration } from '@/hooks/knowledge-hooks'; | ||||
| import { useSecondPathName } from '@/hooks/route-hook'; | import { useSecondPathName } from '@/hooks/route-hook'; | ||||
| import { IKnowledge } from '@/interfaces/database/knowledge'; | |||||
| import { getWidth } from '@/utils'; | import { getWidth } from '@/utils'; | ||||
| import { Avatar, Menu, MenuProps, Space } from 'antd'; | import { Avatar, Menu, MenuProps, Space } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useNavigate, useSelector } from 'umi'; | import { useNavigate, useSelector } from 'umi'; | ||||
| import { KnowledgeRouteKey } from '../../constant'; | import { KnowledgeRouteKey } from '../../constant'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const KnowledgeSidebar = () => { | const KnowledgeSidebar = () => { | ||||
| const { id } = kAModel; | const { id } = kAModel; | ||||
| let navigate = useNavigate(); | let navigate = useNavigate(); | ||||
| const activeKey = useSecondPathName(); | const activeKey = useSecondPathName(); | ||||
| const knowledgeDetails: IKnowledge = useSelector( | |||||
| (state: any) => state.kSModel.knowledgeDetails, | |||||
| ); | |||||
| const [windowWidth, setWindowWidth] = useState(getWidth()); | const [windowWidth, setWindowWidth] = useState(getWidth()); | ||||
| const [collapsed, setCollapsed] = useState(false); | const [collapsed, setCollapsed] = useState(false); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration(); | |||||
| const handleSelect: MenuProps['onSelect'] = (e) => { | const handleSelect: MenuProps['onSelect'] = (e) => { | ||||
| navigate(`/knowledge/${e.key}?id=${id}`); | navigate(`/knowledge/${e.key}?id=${id}`); | ||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| useFetchKnowledgeBaseConfiguration(); | |||||
| return ( | return ( | ||||
| <div className={styles.sidebarWrapper}> | <div className={styles.sidebarWrapper}> | ||||
| <div className={styles.sidebarTop}> | <div className={styles.sidebarTop}> |
| import { IKnowledge } from '@/interfaces/database/knowledge'; | |||||
| import kbService from '@/services/knowledge-service'; | |||||
| import { DvaModel } from 'umi'; | |||||
| export interface KnowledgeModelState { | |||||
| data: IKnowledge[]; | |||||
| knowledge: IKnowledge; | |||||
| } | |||||
| const model: DvaModel<KnowledgeModelState> = { | |||||
| namespace: 'knowledgeModel', | |||||
| state: { | |||||
| data: [], | |||||
| knowledge: {} as IKnowledge, | |||||
| }, | |||||
| reducers: { | |||||
| updateState(state, { payload }) { | |||||
| return { | |||||
| ...state, | |||||
| ...payload, | |||||
| }; | |||||
| }, | |||||
| setKnowledge(state, { payload }) { | |||||
| return { | |||||
| ...state, | |||||
| knowledge: payload, | |||||
| }; | |||||
| }, | |||||
| }, | |||||
| effects: { | |||||
| *rmKb({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(kbService.rmKb, payload); | |||||
| const { retcode } = data; | |||||
| if (retcode === 0) { | |||||
| yield put({ | |||||
| type: 'getList', | |||||
| payload: {}, | |||||
| }); | |||||
| } | |||||
| }, | |||||
| *getList({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(kbService.getList, payload); | |||||
| const { retcode, data: res } = data; | |||||
| if (retcode === 0) { | |||||
| yield put({ | |||||
| type: 'updateState', | |||||
| payload: { | |||||
| data: res, | |||||
| }, | |||||
| }); | |||||
| } | |||||
| }, | |||||
| *getKnowledgeDetail({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(kbService.get_kb_detail, payload); | |||||
| if (data.retcode === 0) { | |||||
| yield put({ type: 'setKnowledge', payload: data.data }); | |||||
| } | |||||
| return data.retcode; | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| export default model; |
| path: '/knowledge/dataset', | path: '/knowledge/dataset', | ||||
| component: '@/pages/add-knowledge/components/knowledge-file', | component: '@/pages/add-knowledge/components/knowledge-file', | ||||
| }, | }, | ||||
| { | |||||
| path: '/knowledge/dataset/upload', | |||||
| component: | |||||
| '@/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file', | |||||
| }, | |||||
| { | { | ||||
| path: '/knowledge/dataset/chunk', | path: '/knowledge/dataset/chunk', | ||||
| component: '@/pages/add-knowledge/components/knowledge-chunk', | component: '@/pages/add-knowledge/components/knowledge-chunk', |
| import { ChunkModelState } from '@/pages/add-knowledge/components/knowledge-chunk/model'; | |||||
| import { KFModelState } from '@/pages/add-knowledge/components/knowledge-file/model'; | |||||
| import { KSModelState } from '@/pages/add-knowledge/components/knowledge-setting/model'; | |||||
| import { TestingModelState } from '@/pages/add-knowledge/components/knowledge-testing/model'; | |||||
| import { kAModelState } from '@/pages/add-knowledge/model'; | |||||
| import { ChatModelState } from '@/pages/chat/model'; | |||||
| import { FileManagerModelState } from '@/pages/file-manager/model'; | |||||
| import { KnowledgeModelState } from '@/pages/knowledge/model'; | |||||
| import { LoginModelState } from '@/pages/login/model'; | |||||
| import { SettingModelState } from '@/pages/user-setting/model'; | |||||
| declare module 'lodash'; | |||||
| function useSelector<TState = RootState, TSelected = unknown>( | |||||
| selector: (state: TState) => TSelected, | |||||
| equalityFn?: (left: TSelected, right: TSelected) => boolean, | |||||
| ): TSelected; | |||||
| export interface RootState { | |||||
| // loading: Loading; | |||||
| fileManager: FileManagerModelState; | |||||
| chatModel: ChatModelState; | |||||
| loginModel: LoginModelState; | |||||
| knowledgeModel: KnowledgeModelState; | |||||
| settingModel: SettingModelState; | |||||
| kFModel: KFModelState; | |||||
| kAModel: kAModelState; | |||||
| chunkModel: ChunkModelState; | |||||
| kSModel: KSModelState; | |||||
| testingModel: TestingModelState; | |||||
| } | |||||
| declare global { | |||||
| type Nullable<T> = T | null; | |||||
| } | |||||
| declare module 'umi' { | |||||
| export { useSelector }; | |||||
| } | |||||
| import { ChunkModelState } from '@/pages/add-knowledge/components/knowledge-chunk/model'; | |||||
| import { KFModelState } from '@/pages/add-knowledge/components/knowledge-file/model'; | |||||
| import { TestingModelState } from '@/pages/add-knowledge/components/knowledge-testing/model'; | |||||
| import { kAModelState } from '@/pages/add-knowledge/model'; | |||||
| import { ChatModelState } from '@/pages/chat/model'; | |||||
| import { FileManagerModelState } from '@/pages/file-manager/model'; | |||||
| import { LoginModelState } from '@/pages/login/model'; | |||||
| import { SettingModelState } from '@/pages/user-setting/model'; | |||||
| declare module 'lodash'; | |||||
| function useSelector<TState = RootState, TSelected = unknown>( | |||||
| selector: (state: TState) => TSelected, | |||||
| equalityFn?: (left: TSelected, right: TSelected) => boolean, | |||||
| ): TSelected; | |||||
| export interface RootState { | |||||
| // loading: Loading; | |||||
| fileManager: FileManagerModelState; | |||||
| chatModel: ChatModelState; | |||||
| loginModel: LoginModelState; | |||||
| settingModel: SettingModelState; | |||||
| kFModel: KFModelState; | |||||
| kAModel: kAModelState; | |||||
| chunkModel: ChunkModelState; | |||||
| testingModel: TestingModelState; | |||||
| } | |||||
| declare global { | |||||
| type Nullable<T> = T | null; | |||||
| } | |||||
| declare module 'umi' { | |||||
| export { useSelector }; | |||||
| } |