| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 | 
							- import * as React from 'react';
 
- import BaseComponent from '../_base/baseComponent';
 
- import cls from "classnames";
 
- import PropTypes from 'prop-types';
 
- import type { ChatProps, ChatState, Message } from './interface';
 
- import InputBox from './inputBox';
 
- import "@douyinfe/semi-foundation/chat/chat.scss";
 
- import Hint from './hint';
 
- import { IconChevronDown, IconDisc } from '@douyinfe/semi-icons';
 
- import ChatContent from './chatContent';
 
- import { getDefaultPropsFromGlobalConfig } from '../_utils';
 
- import { cssClasses, strings } from '@douyinfe/semi-foundation/chat/constants';
 
- import ChatFoundation, { ChatAdapter } from '@douyinfe/semi-foundation/chat/foundation';
 
- import type { FileItem } from '../upload';
 
- import LocaleConsumer from "../locale/localeConsumer";
 
- import { Locale } from "../locale/interface";
 
- import { Button, Upload } from '../index';
 
- const prefixCls = cssClasses.PREFIX;
 
- const { CHAT_ALIGN, MODE, SEND_HOT_KEY, MESSAGE_STATUS } = strings;
 
- class Chat extends BaseComponent<ChatProps, ChatState> {
 
-     static __SemiComponentName__ = "Chat";
 
-     containerRef: React.RefObject<HTMLDivElement>;
 
-     animation: any;
 
-     wheelEventHandler: any;
 
-     foundation: ChatFoundation;
 
-     uploadRef: React.RefObject<Upload>;
 
-     dropAreaRef: React.RefObject<HTMLDivElement>;
 
-     static propTypes = {
 
-         className: PropTypes.string,
 
-         style: PropTypes.object,
 
-         roleConfig: PropTypes.object,
 
-         chats: PropTypes.array,
 
-         hints: PropTypes.array,
 
-         renderHintBox: PropTypes.func,
 
-         onChatsChange: PropTypes.func,
 
-         align: PropTypes.string,
 
-         chatBoxRenderConfig: PropTypes.object,
 
-         customMarkDownComponents: PropTypes.object,
 
-         onClear: PropTypes.func,
 
-         onMessageDelete: PropTypes.func,
 
-         onMessageReset: PropTypes.func,
 
-         onMessageCopy: PropTypes.func,
 
-         onMessageGoodFeedback: PropTypes.func,
 
-         onMessageBadFeedback: PropTypes.func,
 
-         inputContentConvert: PropTypes.func,
 
-         onMessageSend: PropTypes.func,
 
-         InputBoxStyle: PropTypes.object,
 
-         inputBoxCls: PropTypes.string,
 
-         renderFullInputBox: PropTypes.func,
 
-         placeholder: PropTypes.string,
 
-         topSlot: PropTypes.node || PropTypes.array,
 
-         bottomSlot: PropTypes.node || PropTypes.array,
 
-         showStopGenerate: PropTypes.bool,
 
-         showClearContext: PropTypes.bool,
 
-         hintStyle: PropTypes.object,
 
-         hintCls: PropTypes.string,
 
-         uploadProps: PropTypes.object,
 
-         uploadTipProps: PropTypes.object,
 
-         mode: PropTypes.string,
 
-     };
 
-     static defaultProps = getDefaultPropsFromGlobalConfig(Chat.__SemiComponentName__, {
 
-         align: CHAT_ALIGN.LEFT_RIGHT,
 
-         showStopGenerate: false,
 
-         mode: MODE.BUBBLE,
 
-         showClearContext: false,
 
-         sendHotKey: SEND_HOT_KEY.ENTER,
 
-     })
 
-     constructor(props: ChatProps) {
 
-         super(props);
 
-         this.containerRef = React.createRef();
 
-         this.uploadRef = React.createRef();
 
-         this.dropAreaRef = React.createRef();
 
-         this.wheelEventHandler = null;
 
-         this.foundation = new ChatFoundation(this.adapter);
 
-         this.state = {
 
-             backBottomVisible: false,
 
-             chats: [],
 
-             cacheHints: [],
 
-             wheelScroll: false,
 
-             uploadAreaVisible: false,
 
-         };
 
-     }
 
-     get adapter(): ChatAdapter {
 
-         return {
 
-             ...super.adapter,
 
-             getContainerRef: () => this.containerRef?.current,
 
-             setWheelScroll: (flag: boolean) => {
 
-                 this.setState({
 
-                     wheelScroll: flag,
 
-                 });
 
-             },
 
-             notifyChatsChange: (chats: Message[]) => {
 
-                 const { onChatsChange } = this.props;
 
-                 onChatsChange && onChatsChange(chats);
 
-             },
 
-             notifyLikeMessage: (message: Message) => {
 
-                 const { onMessageGoodFeedback } = this.props;
 
-                 onMessageGoodFeedback && onMessageGoodFeedback(message);
 
-             },
 
-             notifyDislikeMessage: (message: Message) => {
 
-                 const { onMessageBadFeedback } = this.props;
 
-                 onMessageBadFeedback && onMessageBadFeedback(message);
 
-             },
 
-             notifyCopyMessage: (message: Message) => {
 
-                 const { onMessageCopy } = this.props;
 
-                 onMessageCopy && onMessageCopy(message);
 
-             },
 
-             notifyClearContext: () => {
 
-                 const { onClear } = this.props;
 
-                 onClear && onClear();
 
-             },
 
-             notifyMessageSend: (content: string, attachment: any[]) => {
 
-                 const { onMessageSend } = this.props;
 
-                 onMessageSend && onMessageSend(content, attachment);
 
-             },
 
-             notifyInputChange: (props: { inputValue: string; attachment: any[]}) => {
 
-                 const { onInputChange } = this.props;
 
-                 onInputChange && onInputChange(props);
 
-             },
 
-             setBackBottomVisible: (visible: boolean) => {
 
-                 this.setState((state) => {
 
-                     if (state.backBottomVisible !== visible) {
 
-                         return {
 
-                             backBottomVisible: visible,
 
-                         };
 
-                     }
 
-                     return null;
 
-                 });
 
-             },
 
-             registerWheelEvent: () => {
 
-                 this.adapter.unRegisterWheelEvent();
 
-                 const containerElement = this.containerRef.current;
 
-                 if (!containerElement) {
 
-                     return ;
 
-                 }
 
-                 this.wheelEventHandler = (e: any) => {
 
-                     if (e.target !== containerElement) {
 
-                         return;
 
-                     }
 
-                     this.adapter.setWheelScroll(true);
 
-                     this.adapter.unRegisterWheelEvent();
 
-                 };
 
-                 containerElement.addEventListener('wheel', this.wheelEventHandler);
 
-             },
 
-             unRegisterWheelEvent: () => {
 
-                 if (this.wheelEventHandler) {
 
-                     const containerElement = this.containerRef.current;
 
-                     if (!containerElement) {
 
-                         return ;
 
-                     } else {
 
-                         containerElement.removeEventListener('wheel', this.wheelEventHandler);
 
-                     }
 
-                     this.wheelEventHandler = null;
 
-                 }
 
-             },
 
-             notifyStopGenerate: (e: MouseEvent) => {
 
-                 const { onStopGenerator } = this.props;
 
-                 onStopGenerator && onStopGenerator(e);
 
-             },
 
-             notifyHintClick: (hint: string) => {
 
-                 const { onHintClick } = this.props;
 
-                 onHintClick && onHintClick(hint);
 
-             },
 
-             setUploadAreaVisible: (visible: boolean) => {
 
-                 this.setState({ uploadAreaVisible: visible });
 
-             },
 
-             manualUpload: (file: File[]) => {
 
-                 const uploadComponent = this.uploadRef.current;
 
-                 if (uploadComponent) {
 
-                     uploadComponent.insert(file);
 
-                 }
 
-             },
 
-             getDropAreaElement: () => {
 
-                 return this.dropAreaRef?.current;
 
-             }
 
-         };
 
-     }
 
-     static getDerivedStateFromProps(nextProps: ChatProps, prevState: ChatState) {
 
-         const { chats, hints } = nextProps;
 
-         const newState = {} as any;
 
-         if (chats !== prevState.chats) {
 
-             newState.chats = chats ?? [];
 
-         }
 
-         if (hints !== prevState.cacheHints) {
 
-             newState.cacheHints = hints;
 
-         }
 
-         if (Object.keys(newState).length) {
 
-             return newState;
 
-         }
 
-         return null;
 
-     }
 
-     componentDidMount(): void {
 
-         this.foundation.init();
 
-     }
 
-     componentDidUpdate(prevProps: Readonly<ChatProps>, prevState: Readonly<ChatState>, snapshot?: any): void {
 
-         const { chats: newChats, hints: newHints } = this.props;
 
-         const { chats: oldChats, cacheHints } = prevState;
 
-         const { wheelScroll } = this.state;
 
-         let shouldScroll = false;
 
-         if (newChats !== oldChats) {
 
-             if (Array.isArray(newChats) && Array.isArray(oldChats)) {
 
-                 const newLastChat = newChats[newChats.length - 1];
 
-                 const oldLastChat = oldChats[oldChats.length - 1];
 
-                 if (newChats.length > oldChats.length) {
 
-                     if (oldChats.length === 0 || newLastChat.id !== oldLastChat.id) {
 
-                         shouldScroll = true;
 
-                     }
 
-                 } else if (newChats.length === oldChats.length &&
 
-                     (newLastChat.status !== 'complete' || newLastChat.status !== oldLastChat.status)
 
-                 ) {
 
-                     shouldScroll = true;
 
-                 }
 
-             }
 
-         }
 
-         if (newHints !== cacheHints) {
 
-             if (newHints.length > cacheHints.length) {
 
-                 shouldScroll = true;
 
-             }
 
-         }
 
-         if (!wheelScroll && shouldScroll) {
 
-             this.foundation.scrollToBottomImmediately();
 
-         }
 
-     }
 
-     componentWillUnmount(): void {
 
-         this.foundation.destroy();
 
-     }
 
-     resetMessage = () => {
 
-         this.foundation.resetMessage(null);
 
-     }
 
-     clearContext = () => {
 
-         this.foundation.clearContext(null);
 
-     }
 
-     scrollToBottom = (animation: boolean) => {
 
-         if (animation) {
 
-             this.foundation.scrollToBottomWithAnimation();
 
-         } else {
 
-             this.foundation.scrollToBottomImmediately();
 
-         }
 
-     }
 
-     sendMessage = (content: string, attachment: FileItem[]) => {
 
-         this.foundation.onMessageSend(content, attachment);
 
-     }
 
-     containerScroll = (e: React.UIEvent<HTMLDivElement>) => {
 
-         if (e.target !== e.currentTarget) {
 
-             return;
 
-         }
 
-         this.foundation.containerScroll(e);
 
-     }
 
-     render() {
 
-         const { topSlot, bottomSlot, roleConfig, hints,
 
-             onChatsChange, onMessageCopy, renderInputArea,
 
-             chatBoxRenderConfig, align, renderHintBox,
 
-             style, className, showStopGenerate,
 
-             customMarkDownComponents, mode, showClearContext,
 
-             placeholder, inputBoxCls, inputBoxStyle,
 
-             hintStyle, hintCls, uploadProps, uploadTipProps,
 
-             sendHotKey,
 
-         } = this.props;
 
-         const { backBottomVisible, chats, wheelScroll, uploadAreaVisible } = this.state;
 
-         let showStopGenerateFlag = false;
 
-         const lastChat = chats.length > 0 && chats[chats.length - 1];
 
-         let disableSend = false;
 
-         if (lastChat && showStopGenerate) {
 
-             const lastChatOnGoing = lastChat?.status && [MESSAGE_STATUS.LOADING, MESSAGE_STATUS.INCOMPLETE].includes(lastChat?.status);
 
-             disableSend = lastChatOnGoing;
 
-             showStopGenerate && (showStopGenerateFlag = lastChatOnGoing);
 
-         }
 
-         return (
 
-             <div
 
-                 className={cls(`${prefixCls}`, className)}
 
-                 style={style}
 
-                 onDragOver={this.foundation.handleDragOver}
 
-             >
 
-                 {uploadAreaVisible && <div
 
-                     ref={this.dropAreaRef}
 
-                     className={`${prefixCls}-dropArea`}
 
-                     onDragOver={this.foundation.handleContainerDragOver}
 
-                     onDrop={this.foundation.handleContainerDrop}
 
-                     onDragLeave={this.foundation.handleContainerDragLeave}
 
-                 >
 
-                     <span className={`${prefixCls}-dropArea-text`}>
 
-                         <LocaleConsumer<Locale["Chat"]> componentName="Chat" >
 
-                             {(locale: Locale["Chat"]) => locale['dropAreaText']}
 
-                         </LocaleConsumer>
 
-                     </span>
 
-                 </div>}
 
-                 <div className={`${prefixCls}-inner`}>
 
-                     {/* top slot */}
 
-                     {topSlot}
 
-                     {/* chat area */}
 
-                     <div className={`${prefixCls}-content`}>
 
-                         <div
 
-                             className={cls(`${prefixCls}-container`, {
 
-                                 'semi-chat-container-scroll-hidden': !wheelScroll
 
-                             })}
 
-                             onScroll={this.containerScroll}
 
-                             ref={this.containerRef}
 
-                         >
 
-                             <ChatContent
 
-                                 align={align}
 
-                                 mode={mode}
 
-                                 chats={chats}
 
-                                 roleConfig={roleConfig}
 
-                                 customMarkDownComponents={customMarkDownComponents}
 
-                                 onMessageDelete={this.foundation.deleteMessage}
 
-                                 onChatsChange={onChatsChange}
 
-                                 onMessageBadFeedback={this.foundation.dislikeMessage}
 
-                                 onMessageGoodFeedback={this.foundation.likeMessage}
 
-                                 onMessageReset={this.foundation.resetMessage}
 
-                                 onMessageCopy={onMessageCopy}
 
-                                 chatBoxRenderConfig={chatBoxRenderConfig}
 
-                             />
 
-                             {/* hint area */}
 
-                             {!!hints?.length && <Hint
 
-                                 className={hintCls}
 
-                                 style={hintStyle}
 
-                                 value={hints}
 
-                                 onHintClick={this.foundation.onHintClick}
 
-                                 renderHintBox={renderHintBox}
 
-                             />}
 
-                         </div>
 
-                     </div>
 
-                     {backBottomVisible && !showStopGenerateFlag && (<span className={`${prefixCls}-action`}>
 
-                         <Button
 
-                             className={`${prefixCls}-action-content ${prefixCls}-action-backBottom`}
 
-                             icon={<IconChevronDown size="extra-large"/>}
 
-                             type="tertiary"
 
-                             onClick={this.foundation.scrollToBottomWithAnimation}
 
-                         />
 
-                     </span>)}
 
-                     {showStopGenerateFlag && (<span className={`${prefixCls}-action`}>
 
-                         <Button
 
-                             className={`${prefixCls}-action-content ${prefixCls}-action-stop`}
 
-                             icon={<IconDisc size="extra-large" />}
 
-                             type="tertiary"
 
-                             onClick={this.foundation.stopGenerate}
 
-                         >
 
-                             <LocaleConsumer<Locale["Chat"]> componentName="Chat" >
 
-                                 {(locale: Locale["Chat"]) => locale['stop']}
 
-                             </LocaleConsumer>
 
-                         </Button>
 
-                     </span>)}
 
-                     {/* input area */}
 
-                     <InputBox
 
-                         showClearContext={showClearContext}
 
-                         uploadRef={this.uploadRef}
 
-                         manualUpload={this.adapter.manualUpload}
 
-                         style={inputBoxStyle}
 
-                         className={inputBoxCls}
 
-                         placeholder={placeholder}
 
-                         disableSend={disableSend}
 
-                         onClearContext={this.foundation.clearContext}
 
-                         onSend={this.foundation.onMessageSend}
 
-                         onInputChange={this.foundation.onInputChange}
 
-                         renderInputArea={renderInputArea}
 
-                         uploadProps={uploadProps}
 
-                         uploadTipProps={uploadTipProps}
 
-                         sendHotKey={sendHotKey}
 
-                     />
 
-                     {bottomSlot}
 
-                 </div>
 
-             </div>
 
-         );
 
-     }
 
- }
 
- export default Chat;
 
 
  |