| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 | 
							- 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);
 
 
  |