mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
Feat: Files uploaded via the dialog box can be uploaded without binding to a dataset. #9590 (#11630)
### What problem does this PR solve? Feat: Files uploaded via the dialog box can be uploaded without binding to a dataset. #9590 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,372 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import {
|
|
||||||
useDeleteDocument,
|
|
||||||
useFetchDocumentInfosByIds,
|
|
||||||
useRemoveNextDocument,
|
|
||||||
useUploadAndParseDocument,
|
|
||||||
} from '@/hooks/document-hooks';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { getExtension } from '@/utils/document-util';
|
|
||||||
import { formatBytes } from '@/utils/file-util';
|
|
||||||
import {
|
|
||||||
CloseCircleOutlined,
|
|
||||||
InfoCircleOutlined,
|
|
||||||
LoadingOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import type { GetProp, UploadFile } from 'antd';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Divider,
|
|
||||||
Flex,
|
|
||||||
Input,
|
|
||||||
List,
|
|
||||||
Space,
|
|
||||||
Spin,
|
|
||||||
Typography,
|
|
||||||
Upload,
|
|
||||||
UploadProps,
|
|
||||||
} from 'antd';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import { CircleStop, Paperclip, SendHorizontal } from 'lucide-react';
|
|
||||||
import {
|
|
||||||
ChangeEventHandler,
|
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import FileIcon from '../file-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const { TextArea } = Input;
|
|
||||||
|
|
||||||
const getFileId = (file: UploadFile) => get(file, 'response.data.0');
|
|
||||||
|
|
||||||
const getFileIds = (fileList: UploadFile[]) => {
|
|
||||||
const ids = fileList.reduce((pre, cur) => {
|
|
||||||
return pre.concat(get(cur, 'response.data', []));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isUploadSuccess = (file: UploadFile) => {
|
|
||||||
const code = get(file, 'response.code');
|
|
||||||
return typeof code === 'number' && code === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
disabled: boolean;
|
|
||||||
value: string;
|
|
||||||
sendDisabled: boolean;
|
|
||||||
sendLoading: boolean;
|
|
||||||
onPressEnter(documentIds: string[]): void;
|
|
||||||
onInputChange: ChangeEventHandler<HTMLTextAreaElement>;
|
|
||||||
conversationId: string;
|
|
||||||
uploadMethod?: string;
|
|
||||||
isShared?: boolean;
|
|
||||||
showUploadIcon?: boolean;
|
|
||||||
createConversationBeforeUploadDocument?(message: string): Promise<any>;
|
|
||||||
stopOutputMessage?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBase64 = (file: FileType): Promise<string> =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file as any);
|
|
||||||
reader.onload = () => resolve(reader.result as string);
|
|
||||||
reader.onerror = (error) => reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
const MessageInput = ({
|
|
||||||
isShared = false,
|
|
||||||
disabled,
|
|
||||||
value,
|
|
||||||
onPressEnter,
|
|
||||||
sendDisabled,
|
|
||||||
sendLoading,
|
|
||||||
onInputChange,
|
|
||||||
conversationId,
|
|
||||||
showUploadIcon = true,
|
|
||||||
createConversationBeforeUploadDocument,
|
|
||||||
uploadMethod = 'upload_and_parse',
|
|
||||||
stopOutputMessage,
|
|
||||||
}: IProps) => {
|
|
||||||
const { t } = useTranslate('chat');
|
|
||||||
const { removeDocument } = useRemoveNextDocument();
|
|
||||||
const { deleteDocument } = useDeleteDocument();
|
|
||||||
const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
|
|
||||||
const { uploadAndParseDocument } = useUploadAndParseDocument(uploadMethod);
|
|
||||||
const conversationIdRef = useRef(conversationId);
|
|
||||||
|
|
||||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
|
||||||
|
|
||||||
const handlePreview = async (file: UploadFile) => {
|
|
||||||
if (!file.url && !file.preview) {
|
|
||||||
file.preview = await getBase64(file.originFileObj as FileType);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange: UploadProps['onChange'] = async ({
|
|
||||||
// fileList: newFileList,
|
|
||||||
file,
|
|
||||||
}) => {
|
|
||||||
let nextConversationId: string = conversationId;
|
|
||||||
if (createConversationBeforeUploadDocument) {
|
|
||||||
const creatingRet = await createConversationBeforeUploadDocument(
|
|
||||||
file.name,
|
|
||||||
);
|
|
||||||
if (creatingRet?.code === 0) {
|
|
||||||
nextConversationId = creatingRet.data.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setFileList((list) => {
|
|
||||||
list.push({
|
|
||||||
...file,
|
|
||||||
status: 'uploading',
|
|
||||||
originFileObj: file as any,
|
|
||||||
});
|
|
||||||
return [...list];
|
|
||||||
});
|
|
||||||
const ret = await uploadAndParseDocument({
|
|
||||||
conversationId: nextConversationId,
|
|
||||||
fileList: [file],
|
|
||||||
});
|
|
||||||
setFileList((list) => {
|
|
||||||
const nextList = list.filter((x) => x.uid !== file.uid);
|
|
||||||
nextList.push({
|
|
||||||
...file,
|
|
||||||
originFileObj: file as any,
|
|
||||||
response: ret,
|
|
||||||
percent: 100,
|
|
||||||
status: ret?.code === 0 ? 'done' : 'error',
|
|
||||||
});
|
|
||||||
return nextList;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const isUploadingFile = fileList.some((x) => x.status === 'uploading');
|
|
||||||
|
|
||||||
const handlePressEnter = useCallback(async () => {
|
|
||||||
if (isUploadingFile) return;
|
|
||||||
const ids = getFileIds(fileList.filter((x) => isUploadSuccess(x)));
|
|
||||||
|
|
||||||
onPressEnter(ids);
|
|
||||||
setFileList([]);
|
|
||||||
}, [fileList, onPressEnter, isUploadingFile]);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
async (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
||||||
// check if it was shift + enter
|
|
||||||
if (event.key === 'Enter' && event.shiftKey) return;
|
|
||||||
if (event.key !== 'Enter') return;
|
|
||||||
if (sendDisabled || isUploadingFile || sendLoading) return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
handlePressEnter();
|
|
||||||
},
|
|
||||||
[sendDisabled, isUploadingFile, sendLoading, handlePressEnter],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(
|
|
||||||
async (file: UploadFile) => {
|
|
||||||
const ids = get(file, 'response.data', []);
|
|
||||||
// Upload Successfully
|
|
||||||
if (Array.isArray(ids) && ids.length) {
|
|
||||||
if (isShared) {
|
|
||||||
await deleteDocument(ids);
|
|
||||||
} else {
|
|
||||||
await removeDocument(ids[0]);
|
|
||||||
}
|
|
||||||
setFileList((preList) => {
|
|
||||||
return preList.filter((x) => getFileId(x) !== ids[0]);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Upload failed
|
|
||||||
setFileList((preList) => {
|
|
||||||
return preList.filter((x) => x.uid !== file.uid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[removeDocument, deleteDocument, isShared],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleStopOutputMessage = useCallback(() => {
|
|
||||||
stopOutputMessage?.();
|
|
||||||
}, [stopOutputMessage]);
|
|
||||||
|
|
||||||
const getDocumentInfoById = useCallback(
|
|
||||||
(id: string) => {
|
|
||||||
return documentInfos.find((x) => x.id === id);
|
|
||||||
},
|
|
||||||
[documentInfos],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ids = getFileIds(fileList);
|
|
||||||
setDocumentIds(ids);
|
|
||||||
}, [fileList, setDocumentIds]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
conversationIdRef.current &&
|
|
||||||
conversationId !== conversationIdRef.current
|
|
||||||
) {
|
|
||||||
setFileList([]);
|
|
||||||
}
|
|
||||||
conversationIdRef.current = conversationId;
|
|
||||||
}, [conversationId, setFileList]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
gap={1}
|
|
||||||
vertical
|
|
||||||
className={cn(styles.messageInputWrapper, 'dark:bg-black')}
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
size="large"
|
|
||||||
placeholder={t('sendPlaceholder')}
|
|
||||||
value={value}
|
|
||||||
allowClear
|
|
||||||
disabled={disabled}
|
|
||||||
style={{
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: 'none',
|
|
||||||
padding: '0px 10px',
|
|
||||||
marginTop: 10,
|
|
||||||
}}
|
|
||||||
autoSize={{ minRows: 2, maxRows: 10 }}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
<Divider style={{ margin: '5px 30px 10px 0px' }} />
|
|
||||||
<Flex justify="space-between" align="center">
|
|
||||||
{fileList.length > 0 && (
|
|
||||||
<List
|
|
||||||
grid={{
|
|
||||||
gutter: 16,
|
|
||||||
xs: 1,
|
|
||||||
sm: 1,
|
|
||||||
md: 1,
|
|
||||||
lg: 1,
|
|
||||||
xl: 2,
|
|
||||||
xxl: 4,
|
|
||||||
}}
|
|
||||||
dataSource={fileList}
|
|
||||||
className={styles.listWrapper}
|
|
||||||
renderItem={(item) => {
|
|
||||||
const id = getFileId(item);
|
|
||||||
const documentInfo = getDocumentInfoById(id);
|
|
||||||
const fileExtension = getExtension(documentInfo?.name ?? '');
|
|
||||||
const fileName = item.originFileObj?.name ?? '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<List.Item>
|
|
||||||
<Card className={styles.documentCard}>
|
|
||||||
<Flex gap={10} align="center">
|
|
||||||
{item.status === 'uploading' ? (
|
|
||||||
<Spin
|
|
||||||
indicator={
|
|
||||||
<LoadingOutlined style={{ fontSize: 24 }} spin />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : item.status === 'error' ? (
|
|
||||||
<InfoCircleOutlined size={30}></InfoCircleOutlined>
|
|
||||||
) : (
|
|
||||||
<FileIcon id={id} name={fileName}></FileIcon>
|
|
||||||
)}
|
|
||||||
<Flex vertical style={{ width: '90%' }}>
|
|
||||||
<Text
|
|
||||||
ellipsis={{ tooltip: fileName }}
|
|
||||||
className={styles.nameText}
|
|
||||||
>
|
|
||||||
<b> {fileName}</b>
|
|
||||||
</Text>
|
|
||||||
{item.status === 'error' ? (
|
|
||||||
t('uploadFailed')
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{item.percent !== 100 ? (
|
|
||||||
t('uploading')
|
|
||||||
) : !item.response ? (
|
|
||||||
t('parsing')
|
|
||||||
) : (
|
|
||||||
<Space>
|
|
||||||
<span>{fileExtension?.toUpperCase()},</span>
|
|
||||||
<span>
|
|
||||||
{formatBytes(
|
|
||||||
getDocumentInfoById(id)?.size ?? 0,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{item.status !== 'uploading' && (
|
|
||||||
<span className={styles.deleteIcon}>
|
|
||||||
<CloseCircleOutlined
|
|
||||||
onClick={() => handleRemove(item)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</List.Item>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Flex
|
|
||||||
gap={5}
|
|
||||||
align="center"
|
|
||||||
justify="flex-end"
|
|
||||||
style={{
|
|
||||||
paddingRight: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
width: fileList.length > 0 ? '50%' : '100%',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showUploadIcon && (
|
|
||||||
<Upload
|
|
||||||
onPreview={handlePreview}
|
|
||||||
onChange={handleChange}
|
|
||||||
multiple={false}
|
|
||||||
onRemove={handleRemove}
|
|
||||||
showUploadList={false}
|
|
||||||
beforeUpload={() => {
|
|
||||||
return false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button type={'primary'} disabled={disabled}>
|
|
||||||
<Paperclip className="size-4" />
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
)}
|
|
||||||
{sendLoading ? (
|
|
||||||
<Button onClick={handleStopOutputMessage}>
|
|
||||||
<CircleStop className="size-5" />
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={handlePressEnter}
|
|
||||||
loading={sendLoading}
|
|
||||||
disabled={sendDisabled || isUploadingFile || sendLoading}
|
|
||||||
>
|
|
||||||
<SendHorizontal className="size-5" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(MessageInput);
|
|
||||||
@ -4,19 +4,16 @@ import {
|
|||||||
IMessage,
|
IMessage,
|
||||||
IReference,
|
IReference,
|
||||||
IReferenceChunk,
|
IReferenceChunk,
|
||||||
|
UploadResponseDataType,
|
||||||
} from '@/interfaces/database/chat';
|
} from '@/interfaces/database/chat';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
|
||||||
useFetchDocumentInfosByIds,
|
|
||||||
useFetchDocumentThumbnailsByIds,
|
|
||||||
} from '@/hooks/document-hooks';
|
|
||||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import MarkdownContent from '../markdown-content';
|
import MarkdownContent from '../markdown-content';
|
||||||
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
||||||
import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
import { UploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
||||||
import { RAGFlowAvatar } from '../ragflow-avatar';
|
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||||
import { useTheme } from '../theme-provider';
|
import { useTheme } from '../theme-provider';
|
||||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||||
@ -55,9 +52,10 @@ const MessageItem = ({
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isAssistant = item.role === MessageType.Assistant;
|
const isAssistant = item.role === MessageType.Assistant;
|
||||||
const isUser = item.role === MessageType.User;
|
const isUser = item.role === MessageType.User;
|
||||||
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
|
|
||||||
const { data: documentThumbnails, setDocumentIds: setIds } =
|
const uploadedFiles = useMemo(() => {
|
||||||
useFetchDocumentThumbnailsByIds();
|
return item?.files ?? [];
|
||||||
|
}, [item?.files]);
|
||||||
|
|
||||||
const referenceDocumentList = useMemo(() => {
|
const referenceDocumentList = useMemo(() => {
|
||||||
return reference?.doc_aggs ?? [];
|
return reference?.doc_aggs ?? [];
|
||||||
@ -67,17 +65,6 @@ const MessageItem = ({
|
|||||||
regenerateMessage?.(item);
|
regenerateMessage?.(item);
|
||||||
}, [regenerateMessage, item]);
|
}, [regenerateMessage, item]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ids = item?.doc_ids ?? [];
|
|
||||||
if (ids.length) {
|
|
||||||
setDocumentIds(ids);
|
|
||||||
const documentIds = ids.filter((x) => !(x in documentThumbnails));
|
|
||||||
if (documentIds.length) {
|
|
||||||
setIds(documentIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.messageItem, {
|
className={classNames(styles.messageItem, {
|
||||||
@ -157,11 +144,13 @@ const MessageItem = ({
|
|||||||
list={referenceDocumentList}
|
list={referenceDocumentList}
|
||||||
></ReferenceDocumentList>
|
></ReferenceDocumentList>
|
||||||
)}
|
)}
|
||||||
{isUser && documentList.length > 0 && (
|
{isUser &&
|
||||||
<InnerUploadedMessageFiles
|
Array.isArray(uploadedFiles) &&
|
||||||
files={documentList}
|
uploadedFiles.length > 0 && (
|
||||||
></InnerUploadedMessageFiles>
|
<UploadedMessageFiles
|
||||||
)}
|
files={uploadedFiles as UploadResponseDataType[]}
|
||||||
|
></UploadedMessageFiles>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
|
import { UploadResponseDataType } from '@/interfaces/database/chat';
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||||
import { getExtension } from '@/utils/document-util';
|
import { getExtension } from '@/utils/document-util';
|
||||||
import { formatBytes } from '@/utils/file-util';
|
import { formatBytes } from '@/utils/file-util';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import FileIcon from '../file-icon';
|
import FileIcon from '../file-icon';
|
||||||
import NewDocumentLink from '../new-document-link';
|
|
||||||
import SvgIcon from '../svg-icon';
|
import SvgIcon from '../svg-icon';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
files?: File[] | IDocumentInfo[];
|
files?: File[] | IDocumentInfo[] | UploadResponseDataType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type NameWidgetType = {
|
type NameWidgetType = {
|
||||||
@ -15,16 +15,16 @@ type NameWidgetType = {
|
|||||||
size: number;
|
size: number;
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
function NameWidget({ name, size, id }: NameWidgetType) {
|
function NameWidget({ name, size }: NameWidgetType) {
|
||||||
return (
|
return (
|
||||||
<div className="text-xs max-w-20">
|
<div className="text-xs max-w-20">
|
||||||
{id ? (
|
{/* {id ? (
|
||||||
<NewDocumentLink documentId={id} documentName={name} prefix="document">
|
<NewDocumentLink documentId={id} documentName={name} prefix="document">
|
||||||
{name}
|
{name}
|
||||||
</NewDocumentLink>
|
</NewDocumentLink>
|
||||||
) : (
|
) : (
|
||||||
<div className="truncate">{name}</div>
|
)} */}
|
||||||
)}
|
<div className="truncate">{name}</div>
|
||||||
<p className="text-text-secondary pt-1">{formatBytes(size)}</p>
|
<p className="text-text-secondary pt-1">{formatBytes(size)}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import {
|
|||||||
IDocumentMetaRequestBody,
|
IDocumentMetaRequestBody,
|
||||||
} from '@/interfaces/request/document';
|
} from '@/interfaces/request/document';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import chatService from '@/services/chat-service';
|
|
||||||
import kbService, { listDocument } from '@/services/knowledge-service';
|
import kbService, { listDocument } from '@/services/knowledge-service';
|
||||||
import api, { api_host } from '@/utils/api';
|
import api, { api_host } from '@/utils/api';
|
||||||
import { buildChunkHighlights } from '@/utils/document-util';
|
import { buildChunkHighlights } from '@/utils/document-util';
|
||||||
@ -428,39 +427,6 @@ export const useDeleteDocument = () => {
|
|||||||
return { data, loading, deleteDocument: mutateAsync };
|
return { data, loading, deleteDocument: mutateAsync };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUploadAndParseDocument = (uploadMethod: string) => {
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
isPending: loading,
|
|
||||||
mutateAsync,
|
|
||||||
} = useMutation({
|
|
||||||
mutationKey: ['uploadAndParseDocument'],
|
|
||||||
mutationFn: async ({
|
|
||||||
conversationId,
|
|
||||||
fileList,
|
|
||||||
}: {
|
|
||||||
conversationId: string;
|
|
||||||
fileList: UploadFile[];
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('conversation_id', conversationId);
|
|
||||||
fileList.forEach((file: UploadFile) => {
|
|
||||||
formData.append('file', file as any);
|
|
||||||
});
|
|
||||||
if (uploadMethod === 'upload_and_parse') {
|
|
||||||
const data = await kbService.upload_and_parse(formData);
|
|
||||||
return data?.data;
|
|
||||||
}
|
|
||||||
const data = await chatService.uploadAndParseExternal(formData);
|
|
||||||
return data?.data;
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { data, loading, uploadAndParseDocument: mutateAsync };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useParseDocument = () => {
|
export const useParseDocument = () => {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { IAskRequestBody } from '@/interfaces/request/chat';
|
|||||||
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||||
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
||||||
import chatService from '@/services/next-chat-service';
|
import chatService from '@/services/next-chat-service';
|
||||||
|
import api from '@/utils/api';
|
||||||
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
|
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useDebounce } from 'ahooks';
|
import { useDebounce } from 'ahooks';
|
||||||
@ -427,6 +428,7 @@ export function useUploadAndParseFile() {
|
|||||||
|
|
||||||
const { data } = await chatService.uploadAndParse(
|
const { data } = await chatService.uploadAndParse(
|
||||||
{
|
{
|
||||||
|
url: api.upload_and_parse(conversationId || id),
|
||||||
signal: controller.current.signal,
|
signal: controller.current.signal,
|
||||||
data: formData,
|
data: formData,
|
||||||
onUploadProgress: ({ progress }) => {
|
onUploadProgress: ({ progress }) => {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ export interface Message {
|
|||||||
id?: string;
|
id?: string;
|
||||||
audio_binary?: string;
|
audio_binary?: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
files?: File[];
|
files?: (File | UploadResponseDataType)[];
|
||||||
chatBoxId?: string;
|
chatBoxId?: string;
|
||||||
attachment?: IAttachment;
|
attachment?: IAttachment;
|
||||||
}
|
}
|
||||||
@ -192,3 +192,14 @@ export interface IMessage extends Message {
|
|||||||
export interface IClientConversation extends IConversation {
|
export interface IClientConversation extends IConversation {
|
||||||
message: IMessage[];
|
message: IMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UploadResponseDataType {
|
||||||
|
created_at: number;
|
||||||
|
created_by: string;
|
||||||
|
extension: string;
|
||||||
|
id: string;
|
||||||
|
mime_type: string;
|
||||||
|
name: string;
|
||||||
|
preview_url: null;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
const { conversationId, isNew } = useGetChatSearchParams();
|
const { conversationId, isNew } = useGetChatSearchParams();
|
||||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||||
|
|
||||||
const { handleUploadFile, fileIds, clearFileIds, isUploading, removeFile } =
|
const { handleUploadFile, isUploading, removeFile, files, clearFiles } =
|
||||||
useUploadFile();
|
useUploadFile();
|
||||||
|
|
||||||
const { send, answer, done } = useSendMessageWithSse(
|
const { send, answer, done } = useSendMessageWithSse(
|
||||||
@ -208,7 +208,7 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
|
|
||||||
addNewestQuestion({
|
addNewestQuestion({
|
||||||
content: value,
|
content: value,
|
||||||
doc_ids: fileIds,
|
files: files,
|
||||||
id,
|
id,
|
||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
});
|
});
|
||||||
@ -218,16 +218,16 @@ export const useSendMessage = (controller: AbortController) => {
|
|||||||
id,
|
id,
|
||||||
content: value.trim(),
|
content: value.trim(),
|
||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
doc_ids: fileIds,
|
files: files,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
clearFileIds();
|
clearFiles();
|
||||||
}, [
|
}, [
|
||||||
value,
|
value,
|
||||||
addNewestQuestion,
|
addNewestQuestion,
|
||||||
fileIds,
|
files,
|
||||||
done,
|
done,
|
||||||
clearFileIds,
|
clearFiles,
|
||||||
setValue,
|
setValue,
|
||||||
handleSendMessage,
|
handleSendMessage,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export function useSendMultipleChatMessage(
|
|||||||
api.completeConversation,
|
api.completeConversation,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { handleUploadFile, fileIds, clearFileIds } = useUploadFile();
|
const { handleUploadFile, files, clearFiles } = useUploadFile();
|
||||||
|
|
||||||
const { setFormRef, getLLMConfigById, isLLMConfigEmpty } =
|
const { setFormRef, getLLMConfigById, isLLMConfigEmpty } =
|
||||||
useBuildFormRefs(chatBoxIds);
|
useBuildFormRefs(chatBoxIds);
|
||||||
@ -181,7 +181,7 @@ export function useSendMultipleChatMessage(
|
|||||||
id,
|
id,
|
||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
chatBoxId,
|
chatBoxId,
|
||||||
doc_ids: fileIds,
|
files,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -195,22 +195,22 @@ export function useSendMultipleChatMessage(
|
|||||||
id,
|
id,
|
||||||
content: value.trim(),
|
content: value.trim(),
|
||||||
role: MessageType.User,
|
role: MessageType.User,
|
||||||
doc_ids: fileIds,
|
files,
|
||||||
},
|
},
|
||||||
chatBoxId,
|
chatBoxId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
clearFileIds();
|
clearFiles();
|
||||||
}, [
|
}, [
|
||||||
value,
|
value,
|
||||||
chatBoxIds,
|
chatBoxIds,
|
||||||
allDone,
|
allDone,
|
||||||
clearFileIds,
|
clearFiles,
|
||||||
isLLMConfigEmpty,
|
isLLMConfigEmpty,
|
||||||
addNewestQuestion,
|
addNewestQuestion,
|
||||||
fileIds,
|
files,
|
||||||
setValue,
|
setValue,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import { useCallback, useState } from 'react';
|
|||||||
|
|
||||||
export function useUploadFile() {
|
export function useUploadFile() {
|
||||||
const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
|
const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [currentFiles, setCurrentFiles] = useState<Record<string, any>[]>([]);
|
||||||
const [fileMap, setFileMap] = useState<Map<File, string>>(new Map());
|
const [fileMap, setFileMap] = useState<Map<File, Record<string, any>>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
|
||||||
type FileUploadParameters = Parameters<
|
type FileUploadParameters = Parameters<
|
||||||
NonNullable<FileUploadProps['onUpload']>
|
NonNullable<FileUploadProps['onUpload']>
|
||||||
@ -20,10 +22,11 @@ export function useUploadFile() {
|
|||||||
if (Array.isArray(files) && files.length) {
|
if (Array.isArray(files) && files.length) {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
const ret = await uploadAndParseFile({ file, options, conversationId });
|
const ret = await uploadAndParseFile({ file, options, conversationId });
|
||||||
if (ret?.code === 0 && Array.isArray(ret?.data)) {
|
if (ret?.code === 0) {
|
||||||
setFileIds((list) => [...list, ...ret.data]);
|
const data = ret.data;
|
||||||
|
setCurrentFiles((list) => [...list, data]);
|
||||||
setFileMap((map) => {
|
setFileMap((map) => {
|
||||||
map.set(files[0], ret.data[0]);
|
map.set(files[0], data);
|
||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -32,8 +35,8 @@ export function useUploadFile() {
|
|||||||
[uploadAndParseFile],
|
[uploadAndParseFile],
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearFileIds = useCallback(() => {
|
const clearFiles = useCallback(() => {
|
||||||
setFileIds([]);
|
setCurrentFiles([]);
|
||||||
setFileMap(new Map());
|
setFileMap(new Map());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ export function useUploadFile() {
|
|||||||
}
|
}
|
||||||
const id = fileMap.get(file);
|
const id = fileMap.get(file);
|
||||||
if (id) {
|
if (id) {
|
||||||
setFileIds((list) => list.filter((item) => item !== id));
|
setCurrentFiles((list) => list.filter((item) => item !== id));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[cancel, fileMap, loading],
|
[cancel, fileMap, loading],
|
||||||
@ -53,9 +56,9 @@ export function useUploadFile() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
handleUploadFile,
|
handleUploadFile,
|
||||||
clearFileIds,
|
files: currentFiles,
|
||||||
fileIds,
|
|
||||||
isUploading: loading,
|
isUploading: loading,
|
||||||
removeFile,
|
removeFile,
|
||||||
|
clearFiles: clearFiles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,6 @@ const {
|
|||||||
web_crawl,
|
web_crawl,
|
||||||
knowledge_graph,
|
knowledge_graph,
|
||||||
document_infos,
|
document_infos,
|
||||||
upload_and_parse,
|
|
||||||
listTagByKnowledgeIds,
|
listTagByKnowledgeIds,
|
||||||
setMeta,
|
setMeta,
|
||||||
getMeta,
|
getMeta,
|
||||||
@ -158,10 +157,6 @@ const methods = {
|
|||||||
url: document_delete,
|
url: document_delete,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
},
|
},
|
||||||
upload_and_parse: {
|
|
||||||
url: upload_and_parse,
|
|
||||||
method: 'post',
|
|
||||||
},
|
|
||||||
listTagByKnowledgeIds: {
|
listTagByKnowledgeIds: {
|
||||||
url: listTagByKnowledgeIds,
|
url: listTagByKnowledgeIds,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
|||||||
@ -107,7 +107,7 @@ export default {
|
|||||||
document_upload: `${api_host}/document/upload`,
|
document_upload: `${api_host}/document/upload`,
|
||||||
web_crawl: `${api_host}/document/web_crawl`,
|
web_crawl: `${api_host}/document/web_crawl`,
|
||||||
document_infos: `${api_host}/document/infos`,
|
document_infos: `${api_host}/document/infos`,
|
||||||
upload_and_parse: `${api_host}/document/upload_and_parse`,
|
upload_and_parse: (id: string) => `${api_host}/document/upload_info`,
|
||||||
parse: `${api_host}/document/parse`,
|
parse: `${api_host}/document/parse`,
|
||||||
setMeta: `${api_host}/document/set_meta`,
|
setMeta: `${api_host}/document/set_meta`,
|
||||||
get_dataset_filter: `${api_host}/document/filter`,
|
get_dataset_filter: `${api_host}/document/filter`,
|
||||||
|
|||||||
Reference in New Issue
Block a user