mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-14 04:36:01 +08:00
### What problem does this PR solve? fix: #567 use modal to upload files in the knowledge base ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
parent
6874c6f3a7
commit
38f0cc016f
8
web/src/components/file-upload-modal/index.less
Normal file
8
web/src/components/file-upload-modal/index.less
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.uploader {
|
||||||
|
:global {
|
||||||
|
.ant-upload-list {
|
||||||
|
max-height: 40vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
web/src/components/file-upload-modal/index.tsx
Normal file
135
web/src/components/file-upload-modal/index.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { useTranslate } from '@/hooks/commonHooks';
|
||||||
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { InboxOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
Segmented,
|
||||||
|
Tabs,
|
||||||
|
TabsProps,
|
||||||
|
Upload,
|
||||||
|
UploadFile,
|
||||||
|
UploadProps,
|
||||||
|
} from 'antd';
|
||||||
|
import { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const { Dragger } = Upload;
|
||||||
|
|
||||||
|
const FileUpload = ({
|
||||||
|
directory,
|
||||||
|
fileList,
|
||||||
|
setFileList,
|
||||||
|
}: {
|
||||||
|
directory: boolean;
|
||||||
|
fileList: UploadFile[];
|
||||||
|
setFileList: Dispatch<SetStateAction<UploadFile[]>>;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslate('fileManager');
|
||||||
|
const props: UploadProps = {
|
||||||
|
multiple: true,
|
||||||
|
onRemove: (file) => {
|
||||||
|
const index = fileList.indexOf(file);
|
||||||
|
const newFileList = fileList.slice();
|
||||||
|
newFileList.splice(index, 1);
|
||||||
|
setFileList(newFileList);
|
||||||
|
},
|
||||||
|
beforeUpload: (file) => {
|
||||||
|
setFileList((pre) => {
|
||||||
|
return [...pre, file];
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
directory,
|
||||||
|
fileList,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dragger {...props} className={styles.uploader}>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<InboxOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">{t('uploadTitle')}</p>
|
||||||
|
<p className="ant-upload-hint">{t('uploadDescription')}</p>
|
||||||
|
</Dragger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileUploadModal = ({
|
||||||
|
visible,
|
||||||
|
hideModal,
|
||||||
|
loading,
|
||||||
|
onOk: onFileUploadOk,
|
||||||
|
}: IModalProps<UploadFile[]>) => {
|
||||||
|
const { t } = useTranslate('fileManager');
|
||||||
|
const [value, setValue] = useState<string | number>('local');
|
||||||
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||||
|
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
|
||||||
|
if (ret !== undefined && ret === 0) {
|
||||||
|
setFileList([]);
|
||||||
|
setDirectoryFileList([]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const items: TabsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: t('file'),
|
||||||
|
children: (
|
||||||
|
<FileUpload
|
||||||
|
directory={false}
|
||||||
|
fileList={fileList}
|
||||||
|
setFileList={setFileList}
|
||||||
|
></FileUpload>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: t('directory'),
|
||||||
|
children: (
|
||||||
|
<FileUpload
|
||||||
|
directory
|
||||||
|
fileList={directoryFileList}
|
||||||
|
setFileList={setDirectoryFileList}
|
||||||
|
></FileUpload>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title={t('uploadFile')}
|
||||||
|
open={visible}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={hideModal}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Flex gap={'large'} vertical>
|
||||||
|
<Segmented
|
||||||
|
options={[
|
||||||
|
{ label: t('local'), value: 'local' },
|
||||||
|
{ label: t('s3'), value: 's3' },
|
||||||
|
]}
|
||||||
|
block
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
/>
|
||||||
|
{value === 'local' ? (
|
||||||
|
<Tabs defaultActiveKey="1" items={items} />
|
||||||
|
) : (
|
||||||
|
t('comingSoon', { keyPrefix: 'common' })
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileUploadModal;
|
@ -184,12 +184,12 @@ export const useUploadDocument = () => {
|
|||||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||||
|
|
||||||
const uploadDocument = useCallback(
|
const uploadDocument = useCallback(
|
||||||
(file: UploadFile) => {
|
(fileList: UploadFile[]) => {
|
||||||
try {
|
try {
|
||||||
return dispatch<any>({
|
return dispatch<any>({
|
||||||
type: 'kFModel/upload_document',
|
type: 'kFModel/upload_document',
|
||||||
payload: {
|
payload: {
|
||||||
file,
|
fileList,
|
||||||
kb_id: knowledgeId,
|
kb_id: knowledgeId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
IThirdOAIModelCollection,
|
IThirdOAIModelCollection,
|
||||||
} from '@/interfaces/database/llm';
|
} from '@/interfaces/database/llm';
|
||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
|
import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/commonUtil';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'umi';
|
import { useDispatch, useSelector } from 'umi';
|
||||||
|
|
||||||
@ -110,13 +111,12 @@ export const useFetchLlmFactoryListOnMount = () => {
|
|||||||
const factoryList = useSelectLlmFactoryList();
|
const factoryList = useSelectLlmFactoryList();
|
||||||
const myLlmList = useSelectMyLlmList();
|
const myLlmList = useSelectMyLlmList();
|
||||||
|
|
||||||
const list = useMemo(
|
const list = useMemo(() => {
|
||||||
() =>
|
const currentList = factoryList.filter((x) =>
|
||||||
factoryList.filter((x) =>
|
Object.keys(myLlmList).every((y) => y !== x.name),
|
||||||
Object.keys(myLlmList).every((y) => y !== x.name),
|
);
|
||||||
),
|
return sortLLmFactoryListBySpecifiedOrder(currentList);
|
||||||
[factoryList, myLlmList],
|
}, [factoryList, myLlmList]);
|
||||||
);
|
|
||||||
|
|
||||||
const fetchLlmFactoryList = useCallback(() => {
|
const fetchLlmFactoryList = useCallback(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -30,9 +30,14 @@ import styles from './index.less';
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
selectedRowKeys: string[];
|
selectedRowKeys: string[];
|
||||||
showCreateModal(): void;
|
showCreateModal(): void;
|
||||||
|
showDocumentUploadModal(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
|
const DocumentToolbar = ({
|
||||||
|
selectedRowKeys,
|
||||||
|
showCreateModal,
|
||||||
|
showDocumentUploadModal,
|
||||||
|
}: IProps) => {
|
||||||
const { t } = useTranslate('knowledgeDetails');
|
const { t } = useTranslate('knowledgeDetails');
|
||||||
const { fetchDocumentList } = useFetchDocumentListOnMount();
|
const { fetchDocumentList } = useFetchDocumentListOnMount();
|
||||||
const { setPagination, searchString } = useGetPagination(fetchDocumentList);
|
const { setPagination, searchString } = useGetPagination(fetchDocumentList);
|
||||||
@ -48,7 +53,7 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
onClick: linkToUploadPage,
|
onClick: showDocumentUploadModal,
|
||||||
label: (
|
label: (
|
||||||
<div>
|
<div>
|
||||||
<Button type="link">
|
<Button type="link">
|
||||||
@ -75,7 +80,7 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => {
|
|||||||
// disabled: true,
|
// disabled: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [linkToUploadPage, showCreateModal, t]);
|
}, [showDocumentUploadModal, showCreateModal, t]);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
showDeleteConfirm({
|
showDeleteConfirm({
|
||||||
|
@ -4,13 +4,15 @@ import {
|
|||||||
useFetchDocumentList,
|
useFetchDocumentList,
|
||||||
useSaveDocumentName,
|
useSaveDocumentName,
|
||||||
useSetDocumentParser,
|
useSetDocumentParser,
|
||||||
|
useUploadDocument,
|
||||||
} from '@/hooks/documentHooks';
|
} from '@/hooks/documentHooks';
|
||||||
import { useGetKnowledgeSearchParams } from '@/hooks/routeHook';
|
import { useGetKnowledgeSearchParams } from '@/hooks/routeHook';
|
||||||
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||||
import { useFetchTenantInfo } from '@/hooks/userSettingHook';
|
import { useFetchTenantInfo } from '@/hooks/userSettingHook';
|
||||||
import { Pagination } from '@/interfaces/common';
|
import { Pagination } from '@/interfaces/common';
|
||||||
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
||||||
import { PaginationProps } from 'antd';
|
import { getUnSupportedFilesCount } from '@/utils/documentUtils';
|
||||||
|
import { PaginationProps, UploadFile } from 'antd';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useNavigate, useSelector } from 'umi';
|
import { useDispatch, useNavigate, useSelector } from 'umi';
|
||||||
import { KnowledgeRouteKey } from './constant';
|
import { KnowledgeRouteKey } from './constant';
|
||||||
@ -242,3 +244,42 @@ export const useGetRowSelection = () => {
|
|||||||
|
|
||||||
return rowSelection;
|
return rowSelection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useHandleUploadDocument = () => {
|
||||||
|
const {
|
||||||
|
visible: documentUploadVisible,
|
||||||
|
hideModal: hideDocumentUploadModal,
|
||||||
|
showModal: showDocumentUploadModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const uploadDocument = useUploadDocument();
|
||||||
|
|
||||||
|
const onDocumentUploadOk = useCallback(
|
||||||
|
async (fileList: UploadFile[]): Promise<number | undefined> => {
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
const ret: any = await uploadDocument(fileList);
|
||||||
|
const count = getUnSupportedFilesCount(ret.retmsg);
|
||||||
|
/// 500 error code indicates that some file types are not supported
|
||||||
|
let retcode = ret.retcode;
|
||||||
|
if (
|
||||||
|
ret.retcode === 0 ||
|
||||||
|
(ret.retcode === 500 && count !== fileList.length) // Some files were not uploaded successfully, but some were uploaded successfully.
|
||||||
|
) {
|
||||||
|
retcode = 0;
|
||||||
|
hideDocumentUploadModal();
|
||||||
|
}
|
||||||
|
return retcode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[uploadDocument, hideDocumentUploadModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loading = useOneNamespaceEffectsLoading('kFModel', ['upload_document']);
|
||||||
|
|
||||||
|
return {
|
||||||
|
documentUploadLoading: loading,
|
||||||
|
onDocumentUploadOk,
|
||||||
|
documentUploadVisible,
|
||||||
|
hideDocumentUploadModal,
|
||||||
|
showDocumentUploadModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
useFetchDocumentListOnMount,
|
useFetchDocumentListOnMount,
|
||||||
useGetPagination,
|
useGetPagination,
|
||||||
useGetRowSelection,
|
useGetRowSelection,
|
||||||
|
useHandleUploadDocument,
|
||||||
useNavigateToOtherPage,
|
useNavigateToOtherPage,
|
||||||
useRenameDocument,
|
useRenameDocument,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
@ -26,6 +27,7 @@ import ParsingActionCell from './parsing-action-cell';
|
|||||||
import ParsingStatusCell from './parsing-status-cell';
|
import ParsingStatusCell from './parsing-status-cell';
|
||||||
import RenameModal from './rename-modal';
|
import RenameModal from './rename-modal';
|
||||||
|
|
||||||
|
import FileUploadModal from '@/components/file-upload-modal';
|
||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
@ -58,6 +60,13 @@ const KnowledgeFile = () => {
|
|||||||
hideChangeParserModal,
|
hideChangeParserModal,
|
||||||
showChangeParserModal,
|
showChangeParserModal,
|
||||||
} = useChangeDocumentParser(currentRecord.id);
|
} = useChangeDocumentParser(currentRecord.id);
|
||||||
|
const {
|
||||||
|
documentUploadVisible,
|
||||||
|
hideDocumentUploadModal,
|
||||||
|
showDocumentUploadModal,
|
||||||
|
onDocumentUploadOk,
|
||||||
|
documentUploadLoading,
|
||||||
|
} = useHandleUploadDocument();
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
keyPrefix: 'knowledgeDetails',
|
keyPrefix: 'knowledgeDetails',
|
||||||
});
|
});
|
||||||
@ -157,6 +166,7 @@ const KnowledgeFile = () => {
|
|||||||
<DocumentToolbar
|
<DocumentToolbar
|
||||||
selectedRowKeys={rowSelection.selectedRowKeys as string[]}
|
selectedRowKeys={rowSelection.selectedRowKeys as string[]}
|
||||||
showCreateModal={showCreateModal}
|
showCreateModal={showCreateModal}
|
||||||
|
showDocumentUploadModal={showDocumentUploadModal}
|
||||||
></DocumentToolbar>
|
></DocumentToolbar>
|
||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
@ -190,6 +200,12 @@ const KnowledgeFile = () => {
|
|||||||
hideModal={hideRenameModal}
|
hideModal={hideRenameModal}
|
||||||
initialName={currentRecord.name}
|
initialName={currentRecord.name}
|
||||||
></RenameModal>
|
></RenameModal>
|
||||||
|
<FileUploadModal
|
||||||
|
visible={documentUploadVisible}
|
||||||
|
hideModal={hideDocumentUploadModal}
|
||||||
|
loading={documentUploadLoading}
|
||||||
|
onOk={onDocumentUploadOk}
|
||||||
|
></FileUploadModal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -210,11 +210,15 @@ const model: DvaModel<KFModelState> = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
*upload_document({ payload = {} }, { call, put }) {
|
*upload_document({ payload = {} }, { call, put }) {
|
||||||
|
const fileList = payload.fileList;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', payload.file);
|
|
||||||
formData.append('kb_id', payload.kb_id);
|
formData.append('kb_id', payload.kb_id);
|
||||||
|
fileList.forEach((file: any) => {
|
||||||
|
formData.append('file', file);
|
||||||
|
});
|
||||||
|
|
||||||
const { data } = yield call(kbService.document_upload, formData);
|
const { data } = yield call(kbService.document_upload, formData);
|
||||||
if (data.retcode === 0) {
|
if (data.retcode === 0 || data.retcode === 500) {
|
||||||
yield put({
|
yield put({
|
||||||
type: 'getKfList',
|
type: 'getKfList',
|
||||||
payload: { kb_id: payload.kb_id },
|
payload: { kb_id: payload.kb_id },
|
||||||
|
@ -85,8 +85,6 @@ const model: DvaModel<FileManagerModelState> = {
|
|||||||
const pathList = payload.path;
|
const pathList = payload.path;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('parent_id', payload.parentId);
|
formData.append('parent_id', payload.parentId);
|
||||||
// formData.append('file', payload.file);
|
|
||||||
// formData.append('path', payload.path);
|
|
||||||
fileList.forEach((file: any, index: number) => {
|
fileList.forEach((file: any, index: number) => {
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
formData.append('path', pathList[index]);
|
formData.append('path', pathList[index]);
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
.modelWrapper {
|
.modelWrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modelContainer {
|
||||||
|
width: 100%;
|
||||||
.factoryOperationWrapper {
|
.factoryOperationWrapper {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
@ -223,9 +223,9 @@ const UserSettingModel = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section id="xx" className={styles.modelWrapper}>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<section className={styles.modelWrapper}>
|
<section className={styles.modelContainer}>
|
||||||
<SettingTitle
|
<SettingTitle
|
||||||
title={t('model')}
|
title={t('model')}
|
||||||
description={t('modelDescription')}
|
description={t('modelDescription')}
|
||||||
@ -257,7 +257,7 @@ const UserSettingModel = () => {
|
|||||||
loading={llmAddingLoading}
|
loading={llmAddingLoading}
|
||||||
llmFactory={selectedLlmFactory}
|
llmFactory={selectedLlmFactory}
|
||||||
></OllamaModal>
|
></OllamaModal>
|
||||||
</>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { IFactory } from '@/interfaces/database/llm';
|
||||||
import isObject from 'lodash/isObject';
|
import isObject from 'lodash/isObject';
|
||||||
import snakeCase from 'lodash/snakeCase';
|
import snakeCase from 'lodash/snakeCase';
|
||||||
|
|
||||||
@ -33,3 +34,29 @@ export const formatNumberWithThousandsSeparator = (numberStr: string) => {
|
|||||||
const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
return formattedNumber;
|
return formattedNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const orderFactoryList = [
|
||||||
|
'OpenAI',
|
||||||
|
'Moonshot',
|
||||||
|
'ZHIPU-AI',
|
||||||
|
'Ollama',
|
||||||
|
'Xinference',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sortLLmFactoryListBySpecifiedOrder = (list: IFactory[]) => {
|
||||||
|
const finalList: IFactory[] = [];
|
||||||
|
orderFactoryList.forEach((orderItem) => {
|
||||||
|
const index = list.findIndex((item) => item.name === orderItem);
|
||||||
|
if (index !== -1) {
|
||||||
|
finalList.push(list[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
list.forEach((item) => {
|
||||||
|
if (finalList.every((x) => x.name !== item.name)) {
|
||||||
|
finalList.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return finalList;
|
||||||
|
};
|
||||||
|
@ -42,3 +42,7 @@ export const getExtension = (name: string) =>
|
|||||||
export const isPdf = (name: string) => {
|
export const isPdf = (name: string) => {
|
||||||
return getExtension(name) === 'pdf';
|
return getExtension(name) === 'pdf';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUnSupportedFilesCount = (message: string) => {
|
||||||
|
return message.split('\n').length;
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user