UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

441 lines (440 loc) 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var React = _interopRequireWildcard(require("react")); var _baseComponent = _interopRequireDefault(require("../_base/baseComponent")); var _classnames = _interopRequireDefault(require("classnames")); var _propTypes = _interopRequireDefault(require("prop-types")); var _inputBox = _interopRequireDefault(require("./inputBox")); require("@douyinfe/semi-foundation/lib/cjs/chat/chat.css"); var _hint = _interopRequireDefault(require("./hint")); var _semiIcons = require("@douyinfe/semi-icons"); var _chatContent = _interopRequireDefault(require("./chatContent")); var _utils = require("../_utils"); var _constants = require("@douyinfe/semi-foundation/lib/cjs/chat/constants"); var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/chat/foundation")); var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer")); var _index = require("../index"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const prefixCls = _constants.cssClasses.PREFIX; const { CHAT_ALIGN, MODE, SEND_HOT_KEY, MESSAGE_STATUS } = _constants.strings; class Chat extends _baseComponent.default { constructor(props) { super(props); // dragStatus: Whether the component contains the dragged object this.dragStatus = false; this.resetMessage = () => { this.foundation.resetMessage(null); }; this.clearContext = () => { this.foundation.clearContext(null); }; this.scrollToBottom = animation => { if (animation) { this.foundation.scrollToBottomWithAnimation(); } else { this.foundation.scrollToBottomImmediately(); } }; this.sendMessage = (content, attachment) => { this.foundation.onMessageSend(content, attachment); }; this.containerScroll = e => { this.scrollTargetRef.current = e.target; if (e.target !== e.currentTarget) { return; } this.foundation.containerScroll(e); }; this.containerRef = /*#__PURE__*/React.createRef(); this.uploadRef = /*#__PURE__*/React.createRef(); this.dropAreaRef = /*#__PURE__*/React.createRef(); this.wheelEventHandler = null; this.foundation = new _foundation.default(this.adapter); this.scrollTargetRef = /*#__PURE__*/React.createRef(); this.state = { backBottomVisible: false, chats: [], cacheHints: [], wheelScroll: false, uploadAreaVisible: false }; } get adapter() { return Object.assign(Object.assign({}, super.adapter), { getContainerRef: () => { var _a; return (_a = this.containerRef) === null || _a === void 0 ? void 0 : _a.current; }, setWheelScroll: flag => { this.setState({ wheelScroll: flag }); }, notifyChatsChange: chats => { const { onChatsChange } = this.props; onChatsChange && onChatsChange(chats); }, notifyLikeMessage: message => { const { onMessageGoodFeedback } = this.props; onMessageGoodFeedback && onMessageGoodFeedback(message); }, notifyDislikeMessage: message => { const { onMessageBadFeedback } = this.props; onMessageBadFeedback && onMessageBadFeedback(message); }, notifyCopyMessage: message => { const { onMessageCopy } = this.props; onMessageCopy && onMessageCopy(message); }, notifyClearContext: () => { const { onClear } = this.props; onClear && onClear(); }, notifyMessageSend: (content, attachment) => { const { onMessageSend } = this.props; onMessageSend && onMessageSend(content, attachment); }, notifyInputChange: props => { const { onInputChange } = this.props; onInputChange && onInputChange(props); }, setBackBottomVisible: visible => { 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 => { var _a; /** * Why use this.scrollTargetRef.current and wheel's currentTarget target comparison? * Both scroll and wheel events are on the container * his.scrollTargetRef.current is the object where scrolling actually occurs * wheel's currentTarget is the container, * Only when the wheel event occurs and there is scroll, the following logic(show scroll bar) needs to be executed */ if (((_a = this.scrollTargetRef) === null || _a === void 0 ? void 0 : _a.current) !== e.currentTarget) { 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 => { const { onStopGenerator } = this.props; onStopGenerator && onStopGenerator(e); }, notifyHintClick: hint => { const { onHintClick } = this.props; onHintClick && onHintClick(hint); }, setUploadAreaVisible: visible => { this.setState({ uploadAreaVisible: visible }); }, manualUpload: file => { const uploadComponent = this.uploadRef.current; if (uploadComponent) { uploadComponent.insert(file); } }, getDropAreaElement: () => { var _a; return (_a = this.dropAreaRef) === null || _a === void 0 ? void 0 : _a.current; }, getDragStatus: () => this.dragStatus, setDragStatus: status => { this.dragStatus = status; } }); } static getDerivedStateFromProps(nextProps, prevState) { const { chats, hints } = nextProps; const newState = {}; if (chats !== prevState.chats) { newState.chats = chats !== null && chats !== void 0 ? chats : []; } if (hints !== prevState.cacheHints) { newState.cacheHints = hints; } if (Object.keys(newState).length) { return newState; } return null; } componentDidMount() { this.foundation.init(); } componentDidUpdate(prevProps, prevState, snapshot) { 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 && newChats.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() { this.foundation.destroy(); } 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, renderDivider, markdownRenderProps, enableUpload } = 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 === null || lastChat === void 0 ? void 0 : lastChat.status) && [MESSAGE_STATUS.LOADING, MESSAGE_STATUS.INCOMPLETE].includes(lastChat === null || lastChat === void 0 ? void 0 : lastChat.status); disableSend = lastChatOnGoing; showStopGenerate && (showStopGenerateFlag = lastChatOnGoing); } const { dragUpload, clickUpload, pasteUpload } = this.foundation.getUploadProps(enableUpload); const dragEventHandlers = dragUpload ? { onDragOver: this.foundation.handleDragOver, onDragStart: this.foundation.handleDragStart, onDragEnd: this.foundation.handleDragEnd } : {}; return /*#__PURE__*/React.createElement("div", Object.assign({ className: (0, _classnames.default)(`${prefixCls}`, className), style: style }, dragEventHandlers), dragUpload && uploadAreaVisible && /*#__PURE__*/React.createElement("div", { ref: this.dropAreaRef, className: `${prefixCls}-dropArea`, onDragOver: this.foundation.handleContainerDragOver, onDrop: this.foundation.handleContainerDrop, onDragLeave: this.foundation.handleContainerDragLeave }, /*#__PURE__*/React.createElement("span", { className: `${prefixCls}-dropArea-text` }, /*#__PURE__*/React.createElement(_localeConsumer.default, { componentName: "Chat" }, locale => locale['dropAreaText']))), /*#__PURE__*/React.createElement("div", { className: `${prefixCls}-inner` }, topSlot, /*#__PURE__*/React.createElement("div", { className: `${prefixCls}-content` }, /*#__PURE__*/React.createElement("div", { className: (0, _classnames.default)(`${prefixCls}-container`, { 'semi-chat-container-scroll-hidden': !wheelScroll }), onScroll: this.containerScroll, ref: this.containerRef }, /*#__PURE__*/React.createElement(_chatContent.default, { 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, renderDivider: renderDivider, markdownRenderProps: markdownRenderProps }), !!(hints === null || hints === void 0 ? void 0 : hints.length) && /*#__PURE__*/React.createElement(_hint.default, { className: hintCls, style: hintStyle, value: hints, onHintClick: this.foundation.onHintClick, renderHintBox: renderHintBox }))), backBottomVisible && !showStopGenerateFlag && (/*#__PURE__*/React.createElement("span", { className: `${prefixCls}-action` }, /*#__PURE__*/React.createElement(_index.Button, { className: `${prefixCls}-action-content ${prefixCls}-action-backBottom`, icon: /*#__PURE__*/React.createElement(_semiIcons.IconChevronDown, { size: "extra-large" }), type: "tertiary", onClick: this.foundation.scrollToBottomWithAnimation }))), showStopGenerateFlag && (/*#__PURE__*/React.createElement("span", { className: `${prefixCls}-action` }, /*#__PURE__*/React.createElement(_index.Button, { className: `${prefixCls}-action-content ${prefixCls}-action-stop`, icon: /*#__PURE__*/React.createElement(_semiIcons.IconDisc, { size: "extra-large" }), type: "tertiary", onClick: this.foundation.stopGenerate }, /*#__PURE__*/React.createElement(_localeConsumer.default, { componentName: "Chat" }, locale => locale['stop'])))), /*#__PURE__*/React.createElement(_inputBox.default, { 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, clickUpload: clickUpload, pasteUpload: pasteUpload }), bottomSlot)); } } Chat.__SemiComponentName__ = "Chat"; Chat.propTypes = { className: _propTypes.default.string, style: _propTypes.default.object, roleConfig: _propTypes.default.object, chats: _propTypes.default.array, hints: _propTypes.default.array, renderHintBox: _propTypes.default.func, onChatsChange: _propTypes.default.func, align: _propTypes.default.string, chatBoxRenderConfig: _propTypes.default.object, customMarkDownComponents: _propTypes.default.object, onClear: _propTypes.default.func, onMessageDelete: _propTypes.default.func, onMessageReset: _propTypes.default.func, onMessageCopy: _propTypes.default.func, onMessageGoodFeedback: _propTypes.default.func, onMessageBadFeedback: _propTypes.default.func, inputContentConvert: _propTypes.default.func, onMessageSend: _propTypes.default.func, InputBoxStyle: _propTypes.default.object, inputBoxCls: _propTypes.default.string, renderFullInputBox: _propTypes.default.func, placeholder: _propTypes.default.string, topSlot: _propTypes.default.node || _propTypes.default.array, bottomSlot: _propTypes.default.node || _propTypes.default.array, showStopGenerate: _propTypes.default.bool, showClearContext: _propTypes.default.bool, hintStyle: _propTypes.default.object, hintCls: _propTypes.default.string, uploadProps: _propTypes.default.object, uploadTipProps: _propTypes.default.object, mode: _propTypes.default.string, markdownRenderProps: _propTypes.default.object }; Chat.defaultProps = (0, _utils.getDefaultPropsFromGlobalConfig)(Chat.__SemiComponentName__, { align: CHAT_ALIGN.LEFT_RIGHT, showStopGenerate: false, mode: MODE.BUBBLE, showClearContext: false, sendHotKey: SEND_HOT_KEY.ENTER }); var _default = exports.default = Chat;