2024-08-14 17:26:47 +08:00
|
|
|
import { useTranslate } from '@/hooks/common-hooks';
|
2024-08-15 16:10:21 +08:00
|
|
|
import {
|
2024-08-15 18:31:46 +08:00
|
|
|
useDeleteDocument,
|
2024-08-15 16:10:21 +08:00
|
|
|
useFetchDocumentInfosByIds,
|
|
|
|
useRemoveNextDocument,
|
2024-08-22 18:01:48 +08:00
|
|
|
useUploadAndParseDocument,
|
2024-08-15 16:10:21 +08:00
|
|
|
} from '@/hooks/document-hooks';
|
2024-08-15 14:39:56 +08:00
|
|
|
import { getExtension } from '@/utils/document-util';
|
2024-08-15 16:10:21 +08:00
|
|
|
import { formatBytes } from '@/utils/file-util';
|
2024-08-15 18:31:46 +08:00
|
|
|
import {
|
|
|
|
CloseCircleOutlined,
|
|
|
|
InfoCircleOutlined,
|
|
|
|
LoadingOutlined,
|
|
|
|
} from '@ant-design/icons';
|
2024-08-14 17:26:47 +08:00
|
|
|
import type { GetProp, UploadFile } from 'antd';
|
2024-08-15 14:39:56 +08:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Card,
|
|
|
|
Flex,
|
|
|
|
Input,
|
|
|
|
List,
|
|
|
|
Space,
|
|
|
|
Spin,
|
|
|
|
Typography,
|
|
|
|
Upload,
|
|
|
|
UploadProps,
|
|
|
|
} from 'antd';
|
2024-08-15 16:10:21 +08:00
|
|
|
import classNames from 'classnames';
|
2024-08-14 17:26:47 +08:00
|
|
|
import get from 'lodash/get';
|
2024-08-22 18:01:48 +08:00
|
|
|
import {
|
|
|
|
ChangeEventHandler,
|
|
|
|
memo,
|
|
|
|
useCallback,
|
|
|
|
useEffect,
|
|
|
|
useRef,
|
|
|
|
useState,
|
|
|
|
} from 'react';
|
2024-08-15 14:39:56 +08:00
|
|
|
import FileIcon from '../file-icon';
|
2024-08-15 16:10:21 +08:00
|
|
|
import SvgIcon from '../svg-icon';
|
2024-08-15 14:39:56 +08:00
|
|
|
import styles from './index.less';
|
2024-08-14 17:26:47 +08:00
|
|
|
|
|
|
|
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
2024-08-15 14:39:56 +08:00
|
|
|
const { Text } = Typography;
|
|
|
|
|
|
|
|
const getFileId = (file: UploadFile) => get(file, 'response.data.0');
|
2024-08-14 17:26:47 +08:00
|
|
|
|
2024-08-15 16:10:21 +08:00
|
|
|
const getFileIds = (fileList: UploadFile[]) => {
|
|
|
|
const ids = fileList.reduce((pre, cur) => {
|
|
|
|
return pre.concat(get(cur, 'response.data', []));
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return ids;
|
|
|
|
};
|
|
|
|
|
2024-08-15 18:31:46 +08:00
|
|
|
const isUploadSuccess = (file: UploadFile) => {
|
|
|
|
const retcode = get(file, 'response.retcode');
|
|
|
|
return typeof retcode === 'number' && retcode === 0;
|
|
|
|
};
|
|
|
|
|
2024-08-14 17:26:47 +08:00
|
|
|
interface IProps {
|
|
|
|
disabled: boolean;
|
|
|
|
value: string;
|
|
|
|
sendDisabled: boolean;
|
|
|
|
sendLoading: boolean;
|
2024-08-15 09:19:17 +08:00
|
|
|
onPressEnter(documentIds: string[]): void;
|
2024-08-14 17:26:47 +08:00
|
|
|
onInputChange: ChangeEventHandler<HTMLInputElement>;
|
|
|
|
conversationId: string;
|
2024-08-22 18:01:48 +08:00
|
|
|
uploadMethod?: string;
|
2024-08-15 18:31:46 +08:00
|
|
|
isShared?: boolean;
|
2024-08-16 18:50:48 +08:00
|
|
|
showUploadIcon?: boolean;
|
2024-08-22 18:01:48 +08:00
|
|
|
createConversationBeforeUploadDocument?(message: string): Promise<any>;
|
2024-08-14 17:26:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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 = ({
|
2024-08-15 18:31:46 +08:00
|
|
|
isShared = false,
|
2024-08-14 17:26:47 +08:00
|
|
|
disabled,
|
|
|
|
value,
|
|
|
|
onPressEnter,
|
|
|
|
sendDisabled,
|
|
|
|
sendLoading,
|
|
|
|
onInputChange,
|
|
|
|
conversationId,
|
2024-08-16 18:50:48 +08:00
|
|
|
showUploadIcon = true,
|
2024-08-22 18:01:48 +08:00
|
|
|
createConversationBeforeUploadDocument,
|
|
|
|
uploadMethod = 'upload_and_parse',
|
2024-08-14 17:26:47 +08:00
|
|
|
}: IProps) => {
|
|
|
|
const { t } = useTranslate('chat');
|
2024-08-15 09:19:17 +08:00
|
|
|
const { removeDocument } = useRemoveNextDocument();
|
2024-08-15 18:31:46 +08:00
|
|
|
const { deleteDocument } = useDeleteDocument();
|
2024-08-15 16:10:21 +08:00
|
|
|
const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
|
2024-08-22 18:01:48 +08:00
|
|
|
const { uploadAndParseDocument } = useUploadAndParseDocument(uploadMethod);
|
|
|
|
const conversationIdRef = useRef(conversationId);
|
2024-08-14 17:26:47 +08:00
|
|
|
|
2024-08-15 09:19:17 +08:00
|
|
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
2024-08-14 17:26:47 +08:00
|
|
|
|
|
|
|
const handlePreview = async (file: UploadFile) => {
|
|
|
|
if (!file.url && !file.preview) {
|
|
|
|
file.preview = await getBase64(file.originFileObj as FileType);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-08-22 18:01:48 +08:00
|
|
|
const handleChange: UploadProps['onChange'] = async ({
|
|
|
|
// fileList: newFileList,
|
|
|
|
file,
|
|
|
|
}) => {
|
|
|
|
let nextConversationId: string = conversationId;
|
2024-09-27 18:20:19 +08:00
|
|
|
if (createConversationBeforeUploadDocument) {
|
2024-08-22 18:01:48 +08:00
|
|
|
const creatingRet = await createConversationBeforeUploadDocument(
|
|
|
|
file.name,
|
|
|
|
);
|
2024-10-18 17:21:12 +08:00
|
|
|
if (creatingRet?.retcode === 0) {
|
2024-08-22 18:01:48 +08:00
|
|
|
nextConversationId = creatingRet.data.id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setFileList((list) => {
|
|
|
|
list.push({
|
|
|
|
...file,
|
|
|
|
status: 'uploading',
|
|
|
|
originFileObj: file as any,
|
|
|
|
});
|
|
|
|
return [...list];
|
|
|
|
});
|
2024-10-18 17:21:12 +08:00
|
|
|
|
2024-08-22 18:01:48 +08:00
|
|
|
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?.retcode === 0 ? 'done' : 'error',
|
|
|
|
});
|
|
|
|
return nextList;
|
|
|
|
});
|
2024-08-14 17:26:47 +08:00
|
|
|
};
|
2024-08-22 18:01:48 +08:00
|
|
|
|
2024-08-15 09:19:17 +08:00
|
|
|
const isUploadingFile = fileList.some((x) => x.status === 'uploading');
|
2024-08-14 17:26:47 +08:00
|
|
|
|
|
|
|
const handlePressEnter = useCallback(async () => {
|
2024-08-15 09:19:17 +08:00
|
|
|
if (isUploadingFile) return;
|
2024-08-15 18:31:46 +08:00
|
|
|
const ids = getFileIds(fileList.filter((x) => isUploadSuccess(x)));
|
2024-08-14 17:26:47 +08:00
|
|
|
|
2024-08-15 09:19:17 +08:00
|
|
|
onPressEnter(ids);
|
2024-08-14 17:26:47 +08:00
|
|
|
setFileList([]);
|
2024-08-15 09:19:17 +08:00
|
|
|
}, [fileList, onPressEnter, isUploadingFile]);
|
|
|
|
|
|
|
|
const handleRemove = useCallback(
|
2024-08-15 14:39:56 +08:00
|
|
|
async (file: UploadFile) => {
|
2024-08-15 09:19:17 +08:00
|
|
|
const ids = get(file, 'response.data', []);
|
2024-08-15 18:31:46 +08:00
|
|
|
// Upload Successfully
|
|
|
|
if (Array.isArray(ids) && ids.length) {
|
|
|
|
if (isShared) {
|
|
|
|
await deleteDocument(ids);
|
|
|
|
} else {
|
|
|
|
await removeDocument(ids[0]);
|
|
|
|
}
|
2024-08-15 14:39:56 +08:00
|
|
|
setFileList((preList) => {
|
|
|
|
return preList.filter((x) => getFileId(x) !== ids[0]);
|
|
|
|
});
|
2024-08-15 18:31:46 +08:00
|
|
|
} else {
|
|
|
|
// Upload failed
|
|
|
|
setFileList((preList) => {
|
|
|
|
return preList.filter((x) => x.uid !== file.uid);
|
|
|
|
});
|
2024-08-15 09:19:17 +08:00
|
|
|
}
|
|
|
|
},
|
2024-08-15 18:31:46 +08:00
|
|
|
[removeDocument, deleteDocument, isShared],
|
2024-08-15 09:19:17 +08:00
|
|
|
);
|
2024-08-14 17:26:47 +08:00
|
|
|
|
2024-08-15 16:10:21 +08:00
|
|
|
const getDocumentInfoById = useCallback(
|
|
|
|
(id: string) => {
|
|
|
|
return documentInfos.find((x) => x.id === id);
|
|
|
|
},
|
|
|
|
[documentInfos],
|
2024-08-14 17:26:47 +08:00
|
|
|
);
|
|
|
|
|
2024-08-15 16:10:21 +08:00
|
|
|
useEffect(() => {
|
|
|
|
const ids = getFileIds(fileList);
|
|
|
|
setDocumentIds(ids);
|
|
|
|
}, [fileList, setDocumentIds]);
|
|
|
|
|
2024-08-22 18:01:48 +08:00
|
|
|
useEffect(() => {
|
|
|
|
if (
|
|
|
|
conversationIdRef.current &&
|
|
|
|
conversationId !== conversationIdRef.current
|
|
|
|
) {
|
|
|
|
setFileList([]);
|
|
|
|
}
|
|
|
|
conversationIdRef.current = conversationId;
|
|
|
|
}, [conversationId, setFileList]);
|
|
|
|
|
2024-08-14 17:26:47 +08:00
|
|
|
return (
|
2024-08-15 14:39:56 +08:00
|
|
|
<Flex gap={20} vertical className={styles.messageInputWrapper}>
|
2024-08-14 17:26:47 +08:00
|
|
|
<Input
|
|
|
|
size="large"
|
|
|
|
placeholder={t('sendPlaceholder')}
|
|
|
|
value={value}
|
|
|
|
disabled={disabled}
|
2024-08-15 16:10:21 +08:00
|
|
|
className={classNames({ [styles.inputWrapper]: fileList.length === 0 })}
|
2024-08-14 17:26:47 +08:00
|
|
|
suffix={
|
2024-08-15 14:39:56 +08:00
|
|
|
<Space>
|
2024-08-22 18:01:48 +08:00
|
|
|
{showUploadIcon && (
|
2024-08-15 16:10:21 +08:00
|
|
|
<Upload
|
|
|
|
onPreview={handlePreview}
|
|
|
|
onChange={handleChange}
|
2024-08-22 18:01:48 +08:00
|
|
|
multiple={false}
|
2024-08-15 16:10:21 +08:00
|
|
|
onRemove={handleRemove}
|
|
|
|
showUploadList={false}
|
2024-10-18 17:21:12 +08:00
|
|
|
beforeUpload={() => {
|
2024-08-22 18:01:48 +08:00
|
|
|
return false;
|
|
|
|
}}
|
2024-08-15 16:10:21 +08:00
|
|
|
>
|
|
|
|
<Button
|
|
|
|
type={'text'}
|
2024-09-27 18:20:19 +08:00
|
|
|
disabled={disabled}
|
2024-08-15 16:10:21 +08:00
|
|
|
icon={
|
2024-09-27 18:20:19 +08:00
|
|
|
<SvgIcon
|
|
|
|
name="paper-clip"
|
|
|
|
width={18}
|
|
|
|
height={22}
|
|
|
|
disabled={disabled}
|
|
|
|
></SvgIcon>
|
2024-08-15 16:10:21 +08:00
|
|
|
}
|
|
|
|
></Button>
|
|
|
|
</Upload>
|
|
|
|
)}
|
2024-08-15 14:39:56 +08:00
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
onClick={handlePressEnter}
|
|
|
|
loading={sendLoading}
|
|
|
|
disabled={sendDisabled || isUploadingFile}
|
|
|
|
>
|
|
|
|
{t('send')}
|
|
|
|
</Button>
|
|
|
|
</Space>
|
2024-08-14 17:26:47 +08:00
|
|
|
}
|
|
|
|
onPressEnter={handlePressEnter}
|
|
|
|
onChange={onInputChange}
|
|
|
|
/>
|
2024-08-15 16:10:21 +08:00
|
|
|
|
2024-08-15 14:39:56 +08:00
|
|
|
{fileList.length > 0 && (
|
|
|
|
<List
|
|
|
|
grid={{
|
|
|
|
gutter: 16,
|
|
|
|
xs: 1,
|
2024-08-15 16:10:21 +08:00
|
|
|
sm: 1,
|
|
|
|
md: 1,
|
2024-08-15 14:39:56 +08:00
|
|
|
lg: 1,
|
|
|
|
xl: 2,
|
|
|
|
xxl: 4,
|
|
|
|
}}
|
|
|
|
dataSource={fileList}
|
2024-08-15 16:10:21 +08:00
|
|
|
className={styles.listWrapper}
|
2024-08-15 14:39:56 +08:00
|
|
|
renderItem={(item) => {
|
2024-08-15 16:10:21 +08:00
|
|
|
const id = getFileId(item);
|
2024-08-22 18:01:48 +08:00
|
|
|
const documentInfo = getDocumentInfoById(id);
|
|
|
|
const fileExtension = getExtension(documentInfo?.name ?? '');
|
|
|
|
const fileName = item.originFileObj?.name ?? '';
|
2024-08-15 14:39:56 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<List.Item>
|
|
|
|
<Card className={styles.documentCard}>
|
2024-08-15 16:10:21 +08:00
|
|
|
<Flex gap={10} align="center">
|
2024-10-18 17:21:12 +08:00
|
|
|
{item.status === 'uploading' ? (
|
2024-08-15 16:10:21 +08:00
|
|
|
<Spin
|
|
|
|
indicator={
|
|
|
|
<LoadingOutlined style={{ fontSize: 24 }} spin />
|
|
|
|
}
|
|
|
|
/>
|
2024-10-18 17:21:12 +08:00
|
|
|
) : item.status === 'error' ? (
|
|
|
|
<InfoCircleOutlined size={30}></InfoCircleOutlined>
|
2024-08-15 16:10:21 +08:00
|
|
|
) : (
|
2024-08-22 18:01:48 +08:00
|
|
|
<FileIcon id={id} name={fileName}></FileIcon>
|
2024-08-15 16:10:21 +08:00
|
|
|
)}
|
|
|
|
<Flex vertical style={{ width: '90%' }}>
|
|
|
|
<Text
|
2024-08-22 18:01:48 +08:00
|
|
|
ellipsis={{ tooltip: fileName }}
|
2024-08-15 16:10:21 +08:00
|
|
|
className={styles.nameText}
|
|
|
|
>
|
2024-08-22 18:01:48 +08:00
|
|
|
<b> {fileName}</b>
|
2024-08-15 16:10:21 +08:00
|
|
|
</Text>
|
2024-10-18 17:21:12 +08:00
|
|
|
{item.status === 'error' ? (
|
2024-08-15 18:31:46 +08:00
|
|
|
t('uploadFailed')
|
2024-08-15 14:39:56 +08:00
|
|
|
) : (
|
2024-08-15 18:31:46 +08:00
|
|
|
<>
|
|
|
|
{item.percent !== 100 ? (
|
|
|
|
t('uploading')
|
|
|
|
) : !item.response ? (
|
|
|
|
t('parsing')
|
|
|
|
) : (
|
|
|
|
<Space>
|
|
|
|
<span>{fileExtension?.toUpperCase()},</span>
|
|
|
|
<span>
|
|
|
|
{formatBytes(
|
|
|
|
getDocumentInfoById(id)?.size ?? 0,
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
</Space>
|
|
|
|
)}
|
|
|
|
</>
|
2024-08-15 14:39:56 +08:00
|
|
|
)}
|
|
|
|
</Flex>
|
2024-08-15 16:10:21 +08:00
|
|
|
</Flex>
|
2024-08-15 14:39:56 +08:00
|
|
|
|
|
|
|
{item.status !== 'uploading' && (
|
2024-08-15 18:31:46 +08:00
|
|
|
<span className={styles.deleteIcon}>
|
|
|
|
<CloseCircleOutlined onClick={() => handleRemove(item)} />
|
|
|
|
</span>
|
2024-08-15 14:39:56 +08:00
|
|
|
)}
|
|
|
|
</Card>
|
|
|
|
</List.Item>
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
2024-08-14 17:26:47 +08:00
|
|
|
</Flex>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-08-22 18:01:48 +08:00
|
|
|
export default memo(MessageInput);
|