@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
JavaScript
"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;