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) 21.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _classnames = _interopRequireDefault(require("classnames")); var _markdownRender = _interopRequireDefault(require("../../markdownRender")); var _constants = require("@douyinfe/semi-foundation/lib/cjs/aiChatDialogue/constants"); var _index = require("../../index"); var _semiIcons = require("@douyinfe/semi-icons"); var _reasoning = require("./contentItem/reasoning"); var _annotation = require("./contentItem/annotation"); var _reference = require("./contentItem/reference"); var _code = _interopRequireDefault(require("./contentItem/code")); var _localeConsumer = _interopRequireDefault(require("../../locale/localeConsumer")); var _dataAdapter = require("@douyinfe/semi-foundation/lib/cjs/aiChatDialogue/dataAdapter"); var _escapeHtml = require("@douyinfe/semi-foundation/lib/cjs/utils/escapeHtml"); 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; } 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; }; const { PREFIX_CONTENT } = _constants.cssClasses; const { STATUS, MODE, ROLE, MESSAGE_ITEM_TYPE, TEXT_TYPES, TOOL_CALL_TYPES, DOCUMENT_TYPES, IMAGE_TYPES, PDF_TYPES, EXCEL_TYPES, CODE_TYPES, VIDEO_TYPES } = _constants.strings; const ImageAttachment = /*#__PURE__*/_react.default.memo(props => { const { src, isList, msg, onImageClick, isLastImage } = props; return /*#__PURE__*/_react.default.createElement(_index.Image, { className: (0, _classnames.default)(`${PREFIX_CONTENT}-img`, { [`${PREFIX_CONTENT}-img-list`]: isList, [`${PREFIX_CONTENT}-img-last`]: isLastImage }), src: src, onClick: () => { onImageClick && onImageClick(msg); } }); }); const FileAttachment = /*#__PURE__*/_react.default.memo(props => { var _a, _b, _c; const { onFileClick, disabledFileItemClick, role, onReferenceClick, showReference, isLastFile } = props, restProps = __rest(props, ["onFileClick", "disabledFileItemClick", "role", "onReferenceClick", "showReference", "isLastFile"]); const suffix = (_a = restProps === null || restProps === void 0 ? void 0 : restProps.filename) === null || _a === void 0 ? void 0 : _a.split('.').pop(); const realType = suffix !== null && suffix !== void 0 ? suffix : (_c = (_b = restProps === null || restProps === void 0 ? void 0 : restProps.fileInstance) === null || _b === void 0 ? void 0 : _b.type) === null || _c === void 0 ? void 0 : _c.split('/').pop(); const renderFileIcon = (0, _react.useCallback)((type, props) => { let icon = null; let typeCls = ''; if (DOCUMENT_TYPES.includes(type)) { typeCls = 'word'; icon = /*#__PURE__*/_react.default.createElement(_semiIcons.IconWord, { size: "extra-large", className: `${PREFIX_CONTENT}-file-icon` }); } else if (IMAGE_TYPES.includes(type)) { typeCls = 'image'; icon = /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-file-icon`, style: { backgroundImage: `url(${props.file_url})` } }); } else if (PDF_TYPES.includes(type)) { typeCls = 'pdf'; icon = /*#__PURE__*/_react.default.createElement(_semiIcons.IconPdf, { size: "extra-large", className: `${PREFIX_CONTENT}-file-icon` }); } else if (EXCEL_TYPES.includes(type)) { typeCls = 'excel'; icon = /*#__PURE__*/_react.default.createElement(_semiIcons.IconExcel, { size: "extra-large", className: `${PREFIX_CONTENT}-file-icon` }); } else if (CODE_TYPES.includes(type)) { typeCls = 'code'; icon = /*#__PURE__*/_react.default.createElement(_semiIcons.IconCode, { size: "extra-large", className: `${PREFIX_CONTENT}-file-icon` }); } else if (VIDEO_TYPES.includes(type)) { typeCls = 'video'; icon = /*#__PURE__*/_react.default.createElement(_semiIcons.IconVideo, { size: "extra-large", className: `${PREFIX_CONTENT}-file-icon` }); } else { typeCls = 'default'; icon = /*#__PURE__*/_react.default.createElement(_semiIcons.IconFile, { size: "extra-large", className: `${PREFIX_CONTENT}-file-icon` }); } return /*#__PURE__*/_react.default.createElement("div", { className: (0, _classnames.default)(`${PREFIX_CONTENT}-file-icon-wrapper`, { [`${PREFIX_CONTENT}-file-icon-${typeCls}`]: typeCls }) }, icon); }, []); const handleFileClick = (0, _react.useCallback)(e => { onFileClick === null || onFileClick === void 0 ? void 0 : onFileClick(restProps); if (disabledFileItemClick) { e.preventDefault(); return; } }, [onFileClick, disabledFileItemClick, restProps]); const handleReferenceClick = (0, _react.useCallback)(e => { onReferenceClick === null || onReferenceClick === void 0 ? void 0 : onReferenceClick({ name: restProps === null || restProps === void 0 ? void 0 : restProps.filename, url: restProps === null || restProps === void 0 ? void 0 : restProps.file_url }); e.preventDefault(); }, [onReferenceClick, restProps]); return /*#__PURE__*/_react.default.createElement("a", { href: restProps === null || restProps === void 0 ? void 0 : restProps.file_url, target: "_blank", onClick: handleFileClick, className: (0, _classnames.default)(`${PREFIX_CONTENT}-file`, { [`${PREFIX_CONTENT}-file-last`]: isLastFile }), rel: "noreferrer" }, renderFileIcon(realType, restProps), /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-file-info` }, /*#__PURE__*/_react.default.createElement("span", { className: (0, _classnames.default)(`${PREFIX_CONTENT}-file-title`, { [`${PREFIX_CONTENT}-file-title-ellipsis`]: role === ROLE.USER && showReference }) }, restProps === null || restProps === void 0 ? void 0 : restProps.filename), /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-file-metadata` }, /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-file-type` }, realType), ' ', restProps === null || restProps === void 0 ? void 0 : restProps.size)), role === ROLE.USER && showReference && ( /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/click-events-have-key-events _react.default.createElement("div", { className: `${PREFIX_CONTENT}-icon-reference`, role: "button", tabIndex: 0, onClick: handleReferenceClick }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconSendMsgStroked, null)))); }); const ToolCallWidget = /*#__PURE__*/_react.default.memo(props => { const { name } = props; return /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-tool-call` }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconWrench, null), name, " ", props.arguments); }); const DialogueContent = /*#__PURE__*/_react.default.memo(props => { const { message, customRenderFunc, role: roleInfo, mode, markdownRenderProps, editing, messageEditRender, showReference, onFileClick, onImageClick, disabledFileItemClick, renderDialogueContentItem, onAnnotationClick, onReferenceClick, escapeHtml } = props; const { content, role, status, references } = message; const shouldEscapeHtml = escapeHtml && role === ROLE.USER; const markdownComponents = (0, _react.useMemo)(() => Object.assign({ 'code': _code.default }, markdownRenderProps === null || markdownRenderProps === void 0 ? void 0 : markdownRenderProps.components), [markdownRenderProps]); const wrapCls = (0, _react.useMemo)(() => { const isUser = role === ROLE.USER; const bubble = mode === MODE.BUBBLE; const userBubble = mode === MODE.USER_BUBBLE && isUser; return (0, _classnames.default)({ [`${PREFIX_CONTENT}`]: true, [`${PREFIX_CONTENT}-${mode}`]: bubble || userBubble, [`${PREFIX_CONTENT}-no-bubble`]: !(bubble || userBubble), [`${PREFIX_CONTENT}-user`]: isUser, [`${PREFIX_CONTENT}-error`]: status === STATUS.FAILED && (bubble || userBubble) }); }, [role, status, mode]); const customRenderer = (0, _react.useCallback)((type, index, item) => { const customRendererFunc = renderDialogueContentItem === null || renderDialogueContentItem === void 0 ? void 0 : renderDialogueContentItem[type]; if (customRendererFunc) { let renderer; // 工具调用类型可从嵌套映射按函数名优先匹配 if (TOOL_CALL_TYPES.includes(type)) { const toolCallItem = item; const functionName = toolCallItem === null || toolCallItem === void 0 ? void 0 : toolCallItem.name; if (typeof customRendererFunc === 'object' && functionName) { const nestedRenderer = customRendererFunc === null || customRendererFunc === void 0 ? void 0 : customRendererFunc[functionName]; if (nestedRenderer) { renderer = nestedRenderer; } } } // 兜底:如果没有匹配到嵌套渲染器且本身是函数,则使用之 if (!renderer && typeof customRendererFunc === 'function') { renderer = customRendererFunc; } if (renderer) { return /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-custom-renderer`, key: `index-${index}` }, renderer(item, message)); } } return null; }, [renderDialogueContentItem, message]); const renderMarkdown = (0, _react.useCallback)((text, key) => { if (text !== '') { const rawText = shouldEscapeHtml ? (0, _escapeHtml.escapeHtmlInMarkdown)(text) : text; return /*#__PURE__*/_react.default.createElement("div", { className: wrapCls, key: key }, /*#__PURE__*/_react.default.createElement(_markdownRender.default, Object.assign({ format: 'md', raw: rawText, components: markdownComponents }, markdownRenderProps)), role === ROLE.USER && showReference && ( /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/click-events-have-key-events _react.default.createElement("div", { className: `${PREFIX_CONTENT}-icon-reference`, role: "button", tabIndex: 0, onClick: () => onReferenceClick === null || onReferenceClick === void 0 ? void 0 : onReferenceClick({ type: 'text', content: text }) }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconSendMsgStroked, null)))); } return null; }, [wrapCls, markdownComponents, markdownRenderProps, role, onReferenceClick, showReference, shouldEscapeHtml]); const renderMessage = (0, _react.useCallback)((msg, index) => { var _a; if (typeof msg.content === 'string') { return renderMarkdown(msg.content, `msg-${index}`); } const inner = (_a = msg.content) !== null && _a !== void 0 ? _a : []; const isImageList = inner.filter(i => (i === null || i === void 0 ? void 0 : i.type) === MESSAGE_ITEM_TYPE.INPUT_IMAGE).length > 1; return inner.map((i, innerIdx) => { var _a, _b; const customNode = customRenderer(i === null || i === void 0 ? void 0 : i.type, index, i); if (customNode) return customNode; if (TEXT_TYPES.includes(i === null || i === void 0 ? void 0 : i.type)) { const annotation = i.annotations; // 过滤掉 file_citation 和 container_file_citation 类型的 annotation const filteredAnnotation = annotation && annotation.length > 0 && annotation.filter(item => item.type !== 'file_citation' && item.type !== 'container_file_citation'); return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, { key: `msg-${index}-${innerIdx}` }, filteredAnnotation && filteredAnnotation.length > 0 && /*#__PURE__*/_react.default.createElement(_annotation.AnnotationWidget, { annotation: filteredAnnotation, // todo: 需要支持动态配置 maxCount: 15, onClick: () => onAnnotationClick(filteredAnnotation) }), renderMarkdown(i.text || '', `msg-${index}-${innerIdx}`), renderMarkdown(i.refusal || '', `msg-${index}-${innerIdx}-refusal`)); } if ((i === null || i === void 0 ? void 0 : i.type) === MESSAGE_ITEM_TYPE.INPUT_IMAGE) { const nextItemType = (_a = inner[innerIdx + 1]) === null || _a === void 0 ? void 0 : _a.type; const isLastImage = innerIdx === inner.length - 1 || nextItemType === MESSAGE_ITEM_TYPE.INPUT_FILE; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, { key: `msg-${index}-${innerIdx}` }, /*#__PURE__*/_react.default.createElement(ImageAttachment, { src: i.image_url, isList: isImageList, msg: i, onImageClick: onImageClick, isLastImage: isLastImage }), nextItemType === MESSAGE_ITEM_TYPE.INPUT_FILE && /*#__PURE__*/_react.default.createElement("br", null)); } if ((i === null || i === void 0 ? void 0 : i.type) === MESSAGE_ITEM_TYPE.INPUT_FILE) { const nextItemType = (_b = inner[innerIdx + 1]) === null || _b === void 0 ? void 0 : _b.type; const isLastFile = innerIdx === inner.length - 1 || nextItemType === MESSAGE_ITEM_TYPE.INPUT_IMAGE; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, { key: `msg-${index}-${innerIdx}` }, /*#__PURE__*/_react.default.createElement(FileAttachment, Object.assign({}, i, { onFileClick: onFileClick, disabledFileItemClick: !!disabledFileItemClick, role: role, onReferenceClick: onReferenceClick, showReference: showReference, isLastFile: isLastFile })), nextItemType === MESSAGE_ITEM_TYPE.INPUT_IMAGE && /*#__PURE__*/_react.default.createElement("br", null)); } return null; }); }, [renderMarkdown, customRenderer, onAnnotationClick, onImageClick, onFileClick, disabledFileItemClick, role, onReferenceClick, showReference]); const renderToolCall = (0, _react.useCallback)((item, index) => (/*#__PURE__*/_react.default.createElement(ToolCallWidget, Object.assign({ key: `tool-${index}` }, item))), []); const builtinRenderers = (0, _react.useMemo)(() => ({ [MESSAGE_ITEM_TYPE.MESSAGE]: (item, index) => renderMessage(item, index), [MESSAGE_ITEM_TYPE.REASONING]: (item, index) => (/*#__PURE__*/_react.default.createElement(_reasoning.ReasoningWidget, { key: `reason-${index}`, summary: item.summary, content: item.content, status: item.status, markdownRenderProps: markdownRenderProps })), [MESSAGE_ITEM_TYPE.FUNCTION_CALL]: renderToolCall, [MESSAGE_ITEM_TYPE.CUSTOM_TOOL_CALL]: renderToolCall }), [renderMessage, markdownRenderProps, renderToolCall]); const loadingNode = (0, _react.useMemo)(() => { const isLoading = [STATUS.QUEUED, STATUS.IN_PROGRESS, STATUS.INCOMPLETE].includes(status); const isOutputExist = content && (content === null || content === void 0 ? void 0 : content.length) > 0 || message.output_text; // 如果内容为空,且没有 output_text,则显示 loading // If the content is empty and there is no output_text, it will display loading if (isLoading && !isOutputExist) { return /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-loading` }, /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-loading-item` }), /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-loading-item` }), /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-loading-item` }), /*#__PURE__*/_react.default.createElement("span", { className: `${PREFIX_CONTENT}-loading-text` }, /*#__PURE__*/_react.default.createElement(_localeConsumer.default, { componentName: "AIChatDialogue" }, locale => locale['loading']))); } else { return null; } }, [status, content, message.output_text]); const node = (0, _react.useMemo)(() => { if (editing) { return messageEditRender === null || messageEditRender === void 0 ? void 0 : messageEditRender((0, _dataAdapter.messageToChatInput)(message)); } else { let realContent; const textContent = typeof content === 'string' ? content : message.output_text; if (textContent) { const defaultRenderer = renderDialogueContentItem === null || renderDialogueContentItem === void 0 ? void 0 : renderDialogueContentItem['default']; if (typeof defaultRenderer === 'function') { realContent = /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-custom-renderer` }, defaultRenderer(textContent, message)); } else { const rawText = shouldEscapeHtml ? (0, _escapeHtml.escapeHtmlInMarkdown)(textContent) : textContent; realContent = /*#__PURE__*/_react.default.createElement("div", { className: wrapCls }, /*#__PURE__*/_react.default.createElement(_markdownRender.default, Object.assign({ format: 'md', raw: rawText, components: markdownComponents }, markdownRenderProps)), role === ROLE.USER && showReference && ( /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/click-events-have-key-events _react.default.createElement("div", { className: `${PREFIX_CONTENT}-icon-reference`, role: "button", tabIndex: 0, onClick: () => onReferenceClick === null || onReferenceClick === void 0 ? void 0 : onReferenceClick({ type: 'text', content: textContent }) }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconSendMsgStroked, null)))); } } else if (Array.isArray(content)) { realContent = content.map((item, index) => { const typeKey = item === null || item === void 0 ? void 0 : item.type; const effectiveType = typeKey !== null && typeKey !== void 0 ? typeKey : MESSAGE_ITEM_TYPE.MESSAGE; // User defined rendering first const customNode = customRenderer(effectiveType, index, item); if (customNode) return customNode; // Then builtin rendering const renderer = builtinRenderers[effectiveType]; if (renderer) return renderer(item, index); return null; }); } return /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-wrapper` }, (status === STATUS.FAILED || status === STATUS.CANCELLED) && /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-failed` }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconAlertCircle, null)), /*#__PURE__*/_react.default.createElement("div", { className: `${PREFIX_CONTENT}-inner` }, realContent)); } }, [status, content, editing, message, role, messageEditRender, markdownRenderProps, wrapCls, markdownComponents, builtinRenderers, customRenderer, renderDialogueContentItem, showReference, onReferenceClick, shouldEscapeHtml]); if (customRenderFunc) { return customRenderFunc({ message, role: roleInfo, defaultContent: node, className: wrapCls }); } else { return /*#__PURE__*/_react.default.createElement("div", { className: (0, _classnames.default)(`${PREFIX_CONTENT}`, { [`${PREFIX_CONTENT}-editing`]: editing }) }, references && references.length > 0 && !editing && /*#__PURE__*/_react.default.createElement(_reference.ReferenceWidget, { references: references }), node, loadingNode); } }); var _default = exports.default = DialogueContent;