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.

726 lines (724 loc) 27.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _exportNames = { getConfigureItem: true }; exports.default = void 0; Object.defineProperty(exports, "getConfigureItem", { enumerable: true, get: function () { return _getConfigureItem.default; } }); var _throttle2 = _interopRequireDefault(require("lodash/throttle")); var _isEqual2 = _interopRequireDefault(require("lodash/isEqual")); var _noop2 = _interopRequireDefault(require("lodash/noop")); var _react = _interopRequireDefault(require("react")); var _baseComponent = _interopRequireDefault(require("../_base/baseComponent")); var _constants = require("@douyinfe/semi-foundation/lib/cjs/aiChatInput/constants"); var _index = require("../index"); var _semiIcons = require("@douyinfe/semi-icons"); require("@douyinfe/semi-foundation/lib/cjs/aiChatInput/aiChatInput.css"); var _horizontalScroller = _interopRequireDefault(require("./horizontalScroller")); var _classnames = _interopRequireDefault(require("classnames")); var _utils = require("@douyinfe/semi-foundation/lib/cjs/aiChatInput/utils"); var _configure = _interopRequireDefault(require("./configure")); var _richTextInput = _interopRequireDefault(require("./richTextInput")); var _uuid = require("@douyinfe/semi-foundation/lib/cjs/utils/uuid"); var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/aiChatInput/foundation")); var _prosemirrorState = require("prosemirror-state"); var _context = _interopRequireDefault(require("../configProvider/context")); var _getConfigureItem = _interopRequireDefault(require("./configure/getConfigureItem")); var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer")); var _skillItem = _interopRequireDefault(require("./skillItem")); var _suggestionItem = _interopRequireDefault(require("./suggestionItem")); var _interface = require("./interface"); Object.keys(_interface).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; if (key in exports && exports[key] === _interface[key]) return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _interface[key]; } }); }); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var __rest = void 0 && (void 0).__rest || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; /* eslint-disable jsx-a11y/no-static-element-interactions */ const prefixCls = _constants.cssClasses.PREFIX; class AIChatInput extends _baseComponent.default { constructor(props) { var _a, _b; super(props); this.richTextDIVRef = /*#__PURE__*/_react.default.createRef(); this.suggestionPanelRef = /*#__PURE__*/_react.default.createRef(); // ref method this.setContent = content => { this.adapter.setContent(content); }; // ref method this.focusEditor = pos => { this.adapter.focusEditor(pos); }; // ref method & inner method this.changeTemplateVisible = value => { this.foundation.changeTemplateVisible(value); }; // ref method & inner method this.getEditor = () => this.editor; this.setEditor = editor => { this.editor = editor; }; this.setContentWhileSaveTool = content => { const { skill } = this.state; let realContent = ''; if (!skill) { realContent = `<p>${content}</p>`; } else { realContent = `<p>${(0, _utils.getSkillSlotString)(skill)}${content}</p>`; } this.setContent(realContent); }; this.handleReferenceDelete = reference => { const { onReferenceDelete } = this.props; onReferenceDelete(reference); }; // ref method this.deleteUploadFile = item => { this.foundation.handleUploadFileDelete(item); }; this.renderLeftFooter = () => { const { renderConfigureArea, round, showTemplateButton } = this.props; const { skill = {} } = this.state; const { hasTemplate } = skill; return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, { componentName: "AIChatInput" }, locale => (/*#__PURE__*/_react.default.createElement("div", { className: `${prefixCls}-footer-configure` }, /*#__PURE__*/_react.default.createElement(_configure.default, { ref: this.configureRef, round: round, onChange: this.foundation.onConfigureChange }, renderConfigureArea === null || renderConfigureArea === void 0 ? void 0 : renderConfigureArea(), (showTemplateButton || hasTemplate) && /*#__PURE__*/_react.default.createElement(_configure.default.Button, { key: "template", field: "template", onClick: this.changeTemplateVisible, icon: /*#__PURE__*/_react.default.createElement(_semiIcons.IconTemplateStroked, null) }, locale.template))))); }; this.renderUploadNode = () => { const { uploadTipProps, uploadProps, renderUploadButton } = this.props; const { attachments } = this.state; const _a = uploadProps !== null && uploadProps !== void 0 ? uploadProps : {}, { children } = _a, rest = __rest(_a, ["children"]); const realUploadProps = Object.assign(Object.assign({}, rest), { onChange: this.foundation.onUploadChange }); const defaultButtonNode = /*#__PURE__*/_react.default.createElement("button", { className: `${prefixCls}-footer-action-button ${prefixCls}-footer-action-upload` }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconPaperclip, null)); const openFileDialog = () => { var _a, _b; (_b = (_a = this.uploadRef.current) === null || _a === void 0 ? void 0 : _a.openFileDialog) === null || _b === void 0 ? void 0 : _b.call(_a); }; const renderProps = { defaultNode: children !== null && children !== void 0 ? children : defaultButtonNode, openFileDialog, disabled: Boolean(uploadProps === null || uploadProps === void 0 ? void 0 : uploadProps.disabled), attachments: attachments !== null && attachments !== void 0 ? attachments : [] }; const uploadChild = renderUploadButton ? renderUploadButton(renderProps) : renderProps.defaultNode; const uploadNode = /*#__PURE__*/_react.default.createElement(_index.Upload, Object.assign({ ref: this.uploadRef, fileList: attachments, listType: "none" }, realUploadProps, { key: 'upload' }), uploadChild); return uploadTipProps ? /*#__PURE__*/_react.default.createElement(_index.Tooltip, Object.assign({}, uploadTipProps, { key: 'upload' }), /*#__PURE__*/_react.default.createElement("span", null, uploadNode)) : uploadNode; }; this.renderSendButton = () => { const { generating } = this.props; const canSend = this.foundation.canSend(); return /*#__PURE__*/_react.default.createElement("button", { key: "send", className: (0, _classnames.default)(`${prefixCls}-footer-action-button`, { [`${prefixCls}-footer-action-send`]: !generating, [`${prefixCls}-footer-action-stop`]: generating, [`${prefixCls}-footer-action-send-disabled`]: !generating && !canSend }), onClick: generating ? this.foundation.handleStopGenerate : this.foundation.handleSend }, generating ? /*#__PURE__*/_react.default.createElement(_semiIcons.IconStop, null) : /*#__PURE__*/_react.default.createElement(_semiIcons.IconArrowUp, null)); }; this.renderRightFooter = () => { const { renderActionArea, showUploadButton } = this.props; const actionCls = `${prefixCls}-footer-action`; const actionNode = [showUploadButton && this.renderUploadNode(), this.renderSendButton()].filter(Boolean); if (renderActionArea) { return renderActionArea({ menuItem: actionNode, className: actionCls }); } return /*#__PURE__*/_react.default.createElement("div", { className: actionCls }, actionNode); }; this.renderFooter = () => { const round = this.props.round; return /*#__PURE__*/_react.default.createElement("div", { className: (0, _classnames.default)(`${prefixCls}-footer`, { [`${prefixCls}-footer-round`]: round }) }, this.renderLeftFooter(), this.renderRightFooter()); }; this.editor = null; const defaultAttachment = (_b = (_a = props === null || props === void 0 ? void 0 : props.uploadProps) === null || _a === void 0 ? void 0 : _a.defaultFileList) !== null && _b !== void 0 ? _b : []; this.state = { popupKey: 1, templateVisible: false, skillVisible: false, suggestionVisible: false, attachments: defaultAttachment, content: null, popupWidth: null, skill: undefined, activeSkillIndex: 0, activeSuggestionIndex: 0, /** * richTextInit 用于标识富文本编辑区是否初始化完成,会影响初始化时发送按钮是否可以点击 * richTextInit is used to identify whether the rich text editing area has been initialized, * which will affect whether the send button can be clicked during initialization. */ richTextInit: false }; this.triggerRef = /*#__PURE__*/_react.default.createRef(); this.popUpOptionListID = (0, _uuid.getUuidShort)(); this.foundation = new _foundation.default(this.adapter); this.transformedContent = []; this.uploadRef = /*#__PURE__*/_react.default.createRef(); this.configureRef = /*#__PURE__*/_react.default.createRef(); this.richTextDIVRef = /*#__PURE__*/_react.default.createRef(); this.suggestionPanelRef = /*#__PURE__*/_react.default.createRef(); this.clickOutsideHandler = null; } get adapter() { return Object.assign(Object.assign({}, super.adapter), { reposPopover: (0, _throttle2.default)(() => { const { templateVisible } = this.state; if (templateVisible) { this.setState({ popupKey: this.state.popupKey + 1 }); } }, 200), setContent: content => { this.editor.commands.setContent(content); }, clearContent: () => { this.setContent(''); }, clearAttachments: () => { this.setState({ attachments: [] }); }, focusEditor: pos => { var _a; (_a = this.editor) === null || _a === void 0 ? void 0 : _a.commands.focus(pos || 'end'); }, getTriggerWidth: () => { const el = this.triggerRef.current; return el && el.getBoundingClientRect().width; }, getEditor: () => this.editor, getPopupID: () => this.popUpOptionListID, notifySkillChange: skill => { var _a, _b; (_b = (_a = this.props).onSkillChange) === null || _b === void 0 ? void 0 : _b.call(_a, skill); }, notifyContentChange: result => { var _a, _b; this.transformedContent = result; (_b = (_a = this.props).onContentChange) === null || _b === void 0 ? void 0 : _b.call(_a, result); }, notifyConfigureChange: (value, changedValue) => { var _a, _b; (_b = (_a = this.props).onConfigureChange) === null || _b === void 0 ? void 0 : _b.call(_a, value, changedValue); }, manualUpload: files => { const uploadComponent = this.uploadRef.current; if (uploadComponent) { uploadComponent.insert(files); } }, notifyMessageSend: props => { var _a, _b; (_b = (_a = this.props).onMessageSend) === null || _b === void 0 ? void 0 : _b.call(_a, props); }, notifyStopGenerate: () => { var _a, _b; (_b = (_a = this.props).onStopGenerate) === null || _b === void 0 ? void 0 : _b.call(_a); }, getRichTextDiv: () => { var _a; return (_a = this.richTextDIVRef) === null || _a === void 0 ? void 0 : _a.current; }, registerClickOutsideHandler: cb => { const clickOutsideHandler = e => { const optionsDom = this.suggestionPanelRef && this.suggestionPanelRef.current; const triggerDom = this.triggerRef && this.triggerRef.current; const target = e.target; const path = e.composedPath && e.composedPath() || [target]; if (optionsDom && (!optionsDom.contains(target) || !optionsDom.contains(target.parentNode)) && triggerDom && !triggerDom.contains(target) && !(path.includes(triggerDom) || path.includes(optionsDom))) { cb(e); } }; this.clickOutsideHandler = clickOutsideHandler; document.addEventListener('mousedown', clickOutsideHandler, false); }, unregisterClickOutsideHandler: () => { if (this.clickOutsideHandler) { document.removeEventListener('mousedown', this.clickOutsideHandler, false); } }, handleReferenceDelete: reference => { var _a, _b; (_b = (_a = this.props).onReferenceDelete) === null || _b === void 0 ? void 0 : _b.call(_a, reference); }, handleReferenceClick: reference => { var _a, _b; (_b = (_a = this.props).onReferenceClick) === null || _b === void 0 ? void 0 : _b.call(_a, reference); }, isSelectionText: selection => { return selection instanceof _prosemirrorState.TextSelection; }, createSelection: (node, pos) => { return _prosemirrorState.NodeSelection.create(node, pos); }, notifyFocus: event => { var _a, _b; (_b = (_a = this.props).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, notifyBlur: event => { var _a, _b; (_b = (_a = this.props).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, event); }, getConfigureValue: () => { var _a, _b; return (_b = (_a = this.configureRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.getConfigureValue(); } }); } componentDidUpdate(prevProps) { const { suggestions, keepSkillAfterSend, clearContentOnGenerating } = this.props; if (!(0, _isEqual2.default)(suggestions, prevProps.suggestions)) { const newVisible = suggestions && suggestions.length > 0 ? true : false; newVisible ? this.foundation.showSuggestionPanel() : this.foundation.hideSuggestionPanel(); } if (this.props.generating && this.props.generating !== prevProps.generating) { if (clearContentOnGenerating !== false) { keepSkillAfterSend ? this.setContentWhileSaveTool('') : this.adapter.clearContent(); this.adapter.clearAttachments(); } } } componentWillUnmount() { this.foundation.destroy(); } // ref method deleteContent(content) { this.foundation.handleDeleteContent(content); } renderTemplate() { const { skill } = this.state; const { renderTemplate, templatesStyle, templatesCls } = this.props; const { popupWidth } = this.state; return /*#__PURE__*/_react.default.createElement("div", { className: (0, _classnames.default)(`${prefixCls}-template`, { [templatesCls]: templatesCls }), style: Object.assign({ width: popupWidth, maxHeight: 500 }, templatesStyle) }, renderTemplate === null || renderTemplate === void 0 ? void 0 : renderTemplate(skill, this.setContent)); } renderSkill() { const { popupWidth } = this.state; const { skills, renderSkillItem } = this.props; return /*#__PURE__*/_react.default.createElement("div", { id: `${prefixCls}-skill-${this.popUpOptionListID}`, className: `${prefixCls}-skill`, style: { width: popupWidth, maxHeight: _constants.numbers.SKILL_MAX_HEIGHT } }, skills === null || skills === void 0 ? void 0 : skills.map((item, index) => (/*#__PURE__*/_react.default.createElement(_skillItem.default, { index: index, isActive: this.state.activeSkillIndex === index, key: item.key || item.value, skill: item, renderSkillItem: renderSkillItem, onClick: this.foundation.handleSkillSelect, onMouseEnter: this.foundation.setActiveSkillIndex })))); } renderSuggestions() { const { suggestions, renderSuggestionItem } = this.props; const { popupWidth, activeSuggestionIndex } = this.state; return /*#__PURE__*/_react.default.createElement("div", { id: `${prefixCls}-suggestion-${this.popUpOptionListID}`, className: `${prefixCls}-suggestion`, style: { width: popupWidth, maxHeight: _constants.numbers.SUGGESTION_MAX_HEIGHT }, ref: this.suggestionPanelRef }, suggestions.map((item, index) => (/*#__PURE__*/_react.default.createElement(_suggestionItem.default, { index: index, key: typeof item === 'string' ? item : item && 'content' in item ? item.content : index, suggestion: item, isActive: activeSuggestionIndex === index, renderSuggestionItem: renderSuggestionItem, onClick: this.foundation.handleSuggestionSelect, onMouseEnter: this.foundation.setActiveSuggestionIndex })))); } renderPopoverContent() { const { templateVisible, skillVisible, suggestionVisible } = this.state; if (templateVisible) { return this.renderTemplate(); } else if (skillVisible) { return this.renderSkill(); } else if (suggestionVisible) { return this.renderSuggestions(); } else { return null; } } getIconByType(type) { let size = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'small'; let iconNode; if (type === 'text') { return null; } switch (type) { case 'file': case 'word': iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconWord, { size: size }); break; case 'code': iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconCode, { size: size }); break; case 'excel': iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconExcel, { size: size }); break; case 'video': iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconVideo, { size: size }); break; case 'audio': iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconMusic, { size: size }); break; case 'pdf': iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconPdf, { size: size }); break; default: iconNode = /*#__PURE__*/_react.default.createElement(_semiIcons.IconFile, { size: size }); break; } return iconNode; } getReferenceIconByType(type) { let iconNode = this.getIconByType(type); return /*#__PURE__*/_react.default.createElement("span", { className: `${prefixCls}-ref-icon ${prefixCls}-ref-icon-${type} ${prefixCls}-reference-icon` }, iconNode); } getAttachmentIconByType(type) { let iconNode = this.getIconByType(type, 'large'); return /*#__PURE__*/_react.default.createElement("span", { className: `${prefixCls}-attachment-icon ${prefixCls}-ref-icon ${prefixCls}-ref-icon-${type}` }, iconNode); } renderReference() { const { references = [], renderReference } = this.props; if (references.length === 0) { return null; } return /*#__PURE__*/_react.default.createElement("div", { className: `${prefixCls}-references` }, references.map(item => { if (renderReference) { return renderReference(item); } const { id, type, content, name, url } = item; const isImage = (0, _utils.isImageType)(item); const signIconType = (0, _utils.getContentType)((0, _utils.getAttachmentType)(item)); // eslint-disable-next-line jsx-a11y/click-events-have-key-events return /*#__PURE__*/_react.default.createElement("div", { key: id, className: `${prefixCls}-reference`, onClick: () => { this.foundation.handleReferenceClick(item); } }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconSendMsgStroked, null), /*#__PURE__*/_react.default.createElement("span", { className: `${prefixCls}-reference-content` }, type !== 'text' && (isImage ? /*#__PURE__*/_react.default.createElement("img", { className: `${prefixCls}-reference-img`, src: url, alt: name }) : this.getReferenceIconByType(signIconType)), /*#__PURE__*/_react.default.createElement("span", { className: `${prefixCls}-reference-name` }, type === 'text' ? content : name)), /*#__PURE__*/_react.default.createElement(_semiIcons.IconCrossStroked, { size: "small", className: `${prefixCls}-reference-delete`, onClick: e => { this.handleReferenceDelete(item); e.stopPropagation(); } })); })); } renderAttachment() { const { attachments = [] } = this.state; if (attachments.length === 0) { return null; } return /*#__PURE__*/_react.default.createElement(_horizontalScroller.default, { prefix: `${prefixCls}` }, attachments === null || attachments === void 0 ? void 0 : attachments.map((item, index) => { const isImage = (0, _utils.isImageType)(item); const realType = (0, _utils.getAttachmentType)(item); const signIconType = (0, _utils.getContentType)(realType); const { uid, name, url, size, percent, status } = item; const showPercent = !(percent === 100 || typeof percent === 'undefined') && status === 'uploading'; return /*#__PURE__*/_react.default.createElement("div", { className: `${prefixCls}-attachment`, key: uid }, isImage ? /*#__PURE__*/_react.default.createElement("img", { className: `${prefixCls}-attachment-img`, src: url, alt: name }) : this.getAttachmentIconByType(signIconType), /*#__PURE__*/_react.default.createElement("div", { className: `${prefixCls}-attachment-content` }, /*#__PURE__*/_react.default.createElement("div", { className: `${prefixCls}-attachment-content-name` }, name), /*#__PURE__*/_react.default.createElement("div", { className: `${prefixCls}-attachment-content-size` }, `${realType} ${size}`)), showPercent && /*#__PURE__*/_react.default.createElement(_index.Progress, { type: "circle", width: 30, className: `${prefixCls}-attachment-progress`, percent: percent, showInfo: false, "aria-label": "upload progress" }), /*#__PURE__*/_react.default.createElement(_semiIcons.IconClose, { className: `${prefixCls}-attachment-delete`, size: "small", onClick: () => { this.foundation.handleUploadFileDelete(item); } })); })); } renderTopArea() { const { references, topSlotPosition, renderTopSlot, showReference, showUploadFile } = this.props; const { attachments } = this.state; const topSlot = renderTopSlot === null || renderTopSlot === void 0 ? void 0 : renderTopSlot({ references, attachments, content: this.transformedContent, handleUploadFileDelete: this.foundation.handleUploadFileDelete, handleReferenceDelete: this.handleReferenceDelete }); return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, topSlotPosition === 'top' && topSlot, showReference && this.renderReference(), topSlotPosition === 'middle' && topSlot, showUploadFile && this.renderAttachment(), topSlotPosition === 'bottom' && topSlot); } render() { const { direction } = this.context; const defaultPosition = direction === 'rtl' ? 'bottomRight' : 'bottomLeft'; const { style, className, popoverProps, placeholder, extensions, defaultContent, immediatelyRender, onPaste } = this.props; const { templateVisible, skillVisible, suggestionVisible, popupKey } = this.state; return /*#__PURE__*/_react.default.createElement(_index.Popover, Object.assign({ position: defaultPosition }, popoverProps, { rePosKey: popupKey, className: (0, _classnames.default)({ [`${prefixCls}-popover-suggestion`]: suggestionVisible, [`${prefixCls}-popover-skill`]: skillVisible, [`${prefixCls}-popover-template`]: templateVisible }), content: this.renderPopoverContent(), visible: templateVisible || skillVisible || suggestionVisible, trigger: "custom", disableArrowKeyDown: true }), /*#__PURE__*/_react.default.createElement("div", { className: (0, _classnames.default)(prefixCls, { [className]: className }), style: style, ref: this.triggerRef, onClick: this.foundation.handleContainerClick, onMouseDown: this.foundation.handleContainerMouseDown }, this.renderTopArea(), /*#__PURE__*/_react.default.createElement(_richTextInput.default, { immediatelyRender: immediatelyRender, innerRef: this.richTextDIVRef, defaultContent: defaultContent, placeholder: placeholder, onKeyDown: this.foundation.handleKeyDown, setEditor: this.setEditor, onChange: this.foundation.handleContentChange, extensions: extensions, handleKeyDown: this.foundation.handRichTextArealKeyDown, onPasteEvent: onPaste, onPaste: this.foundation.handlePaste, onFocus: this.foundation.handleFocus, onBlur: this.foundation.handleBlur, handleCreate: this.foundation.handleCreate, showPlaceholderWhenSkillOnly: this.props.showPlaceholderWhenSkillOnly }), this.renderFooter())); } } AIChatInput.__SemiComponentName__ = "AIChatInput"; AIChatInput.Configure = _configure.default; AIChatInput.contextType = _context.default; AIChatInput.getCustomSlotAttribute = _utils.getCustomSlotAttribute; AIChatInput.defaultProps = { onContentChange: _noop2.default, onStopGenerate: _noop2.default, showReference: true, showUploadFile: true, generating: false, dropdownMatchTriggerWidth: true, round: true, topSlotPosition: 'top', sendHotKey: _constants.strings.SEND_HOTKEY.ENTER, keepSkillAfterSend: false, showUploadButton: true, clearContentOnGenerating: true }; var _default = exports.default = AIChatInput;