zj-plugin-intelligent 1.2.26-beta.4 → 1.2.26-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1012,7 +1012,7 @@ eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@
|
|
|
1012
1012
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1013
1013
|
|
|
1014
1014
|
"use strict";
|
|
1015
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _logo = _interopRequireDefault(__webpack_require__(/*! @/assets/logo.png */ \"./src/assets/logo.png\"));\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.mjs\"));\n\nvar _kbChat = __webpack_require__(/*! ./api/kbChat */ \"./src/lib/knowledgeBase/api/kbChat.js\");\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\nvar _native = __webpack_require__(/*! ./native.js */ \"./src/lib/knowledgeBase/native.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar AI_AVATAR_URL = 'https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/doc_query.png'; // 与 ChatDetail 使用相同的 Markdown 配置\n\nvar md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default = {\n name: 'KbChatMain',\n props: {\n companyId: {\n type: String,\n default: ''\n },\n userno: {\n type: String,\n default: ''\n },\n imAccount: {\n type: [String, Object],\n default: ''\n },\n userName: {\n type: String,\n default: ''\n },\n // 是否移动端:由父级(KnowledgeBaseMain)传入;未传时组件自身按窗口宽度判断\n isMobile: {\n type: Boolean,\n default: null\n }\n },\n data: function data() {\n // 首帧前计算是否移动端,避免 H5 先显示 PC 布局再切换的闪烁(与 AI 助理一致)\n // 宿主没有 Element UI 时(H5/平板横屏)一律走自绘 UI 分支,避免 el-* 未知元素报错\n var _isMobile = typeof window !== 'undefined' && (window.innerWidth <= 768 || !(0, _native.hasElementUI)(this));\n\n return {\n initDone: false,\n activeSessionId: '',\n sessionList: [],\n // 默认折叠侧边栏:避免新用户进入时先展开再合住的闪烁,\n // 加载出会话后再按规则决定是否展开\n sidebarCollapsed: true,\n messages: [],\n inputText: '',\n streaming: false,\n streamMsg: null,\n showToolInfo: false,\n contentCache: {},\n eventSource: null,\n streamRequestId: 0,\n suggestedQuestions: [],\n // 消息多选模式(豆包式):PC 由消息操作区「删除/多选」进入,移动端长按气泡弹出菜单进入\n selectMode: false,\n // 已选中的消息 id 列表(后端主键)\n selectedMsgIds: [],\n // 当前公司是否没有知识库文档(无资料时展示遮罩)\n noDocument: false,\n // 移动端状态(仿 AI 助理):showH5List=true 显示会话列表页,false 显示聊天详情页\n localIsMobile: _isMobile,\n showH5List: _isMobile,\n // 移动端会话「…」弹出的操作单\n mobileAction: null,\n // 移动端长按气泡弹出的消息操作单(复制 / 删除 / 多选)\n msgAction: null,\n // 移动端侧滑手势(参考 AI 助理):记录起手坐标与是否已触发\n _swipeStartX: 0,\n _swipeStartY: 0,\n _swipeHandled: false,\n _swipeFromLeftEdge: false,\n _swipeFromRightEdge: false,\n _swipeFromEdge: false\n };\n },\n computed: {\n /** 实际是否移动端:优先用父级 prop,未传则用自身窗口判断 */\n isMobileActive: function isMobileActive() {\n return this.isMobile !== null ? this.isMobile : this.localIsMobile;\n },\n aiAvatar: function aiAvatar() {\n return AI_AVATAR_URL;\n },\n userAvatar: function userAvatar() {\n var account = this.imAccount;\n console.log('KbChatMain - imAccount 原始值:', account); // 尝试解析字符串为对象\n\n if (typeof account === 'string') {\n try {\n account = JSON.parse(account);\n console.log('KbChatMain - imAccount 解析后:', account);\n } catch (e) {\n // 如果解析失败,检查是否是纯 URL 字符串\n if (account.trim() && (account.includes('http://') || account.includes('https://'))) {\n console.log('KbChatMain - imAccount 是 URL:', account.trim());\n return account.trim();\n }\n }\n } // 如果是对象,尝试多个可能的头像字段(优先使用 headImg)\n\n\n if (account && (0, _typeof2.default)(account) === 'object') {\n var avatarFields = ['headImg', 'headPortrait', 'avatar', 'avatarUrl', 'headImage', 'photoUrl', 'portrait', 'headUrl', 'userHeadUrl', 'userAvatar'];\n\n for (var _i = 0, _avatarFields = avatarFields; _i < _avatarFields.length; _i++) {\n var field = _avatarFields[_i];\n\n if (account[field] && String(account[field]).trim()) {\n console.log(\"KbChatMain - \\u627E\\u5230\\u5934\\u50CF\\u5B57\\u6BB5 \".concat(field, \":\"), account[field]);\n return String(account[field]).trim();\n }\n }\n }\n\n console.log('KbChatMain - 使用默认头像');\n return _logo.default;\n },\n // 是否有真实头像(区别于默认 fallback 图片)\n hasUserAvatar: function hasUserAvatar() {\n return this.userAvatar && this.userAvatar !== _logo.default;\n },\n // 无头像时的文字头像(与 zjkjAvatar 一致:取名字最后两个字)\n userAvatarText: function userAvatarText() {\n var name = this.userName || '';\n return name ? name.substring(name.length - 2) : '';\n },\n // 可删除的消息 id(仅后端已落库、带回 id 的消息可删)\n deletableMsgIds: function deletableMsgIds() {\n return this.messages.filter(function (m) {\n return m.id;\n }).map(function (m) {\n return m.id;\n });\n },\n // 是否已全选\n isAllSelected: function isAllSelected() {\n return this.deletableMsgIds.length > 0 && this.selectedMsgIds.length === this.deletableMsgIds.length;\n }\n },\n watch: {\n companyId: function companyId() {\n this.initDone = false;\n this.finishStream();\n this.loadSuggestedQuestions();\n this.checkKbDocuments();\n this.initSession();\n },\n userno: function userno() {\n this.initDone = false;\n this.finishStream();\n this.activeSessionId = '';\n this.messages = [];\n this.initSession();\n },\n // 移动端进入时默认先显示会话列表页(仿 AI 助理)\n isMobileActive: function isMobileActive(val) {\n if (val) {\n // 无资料时遮罩只在详情页可见(列表页 .kb-chat-main 被隐藏),故保持停在详情页\n this.showH5List = !this.noDocument;\n this.mobileAction = null;\n }\n }\n },\n mounted: function mounted() {\n var _this = this;\n\n // 独立使用(父级未传 isMobile)时自检窗口宽度\n this._onResize = this.handleResize;\n window.addEventListener('resize', this._onResize, {\n passive: true\n }); // Android WebView 兼容:在 document 级别监听 touchmove/touchend,\n // 避免可滚动子元素消费事件后不冒泡到父级(参考 AI 助理)\n\n this._swipeMoveHandler = this._onSwipeMove;\n this._swipeEndHandler = this._onSwipeEnd;\n this._popStateHandler = this._onPopState;\n document.addEventListener('touchmove', this._swipeMoveHandler, {\n passive: true\n });\n document.addEventListener('touchend', this._swipeEndHandler, {\n passive: true\n }); // 拦截 Android 系统返回手势:通过 history.pushState + popstate\n\n window.addEventListener('popstate', this._popStateHandler); // 移动端首屏为会话列表页,推入一条历史状态供系统返回拦截\n\n if (this.isMobileActive) {\n history.pushState({\n page: 'kbChatList'\n }, '');\n }\n\n this.initSession();\n this.checkKbDocuments(); // v-show 隐藏期间加载的历史消息无法滚动(scrollHeight 为 0),\n // 监听组件由隐藏变为可见时,滚动到最新问答;\n // 同时每次变为可见时刷新\"猜您想问\"(管理端可能刚更新过)\n\n if (typeof IntersectionObserver !== 'undefined' && this.$el) {\n this._visibilityObserver = new IntersectionObserver(function (entries) {\n if (entries.some(function (e) {\n return e.isIntersecting;\n })) {\n _this.loadSuggestedQuestions(); // 管理端可能刚上传/删除了文档,重新校验遮罩状态\n\n\n _this.checkKbDocuments();\n\n if (_this.messages.length) {\n _this.$nextTick(function () {\n return _this.scrollToBottom();\n });\n }\n }\n });\n\n this._visibilityObserver.observe(this.$el);\n } else {\n this.loadSuggestedQuestions();\n }\n },\n beforeDestroy: function beforeDestroy() {\n if (this._visibilityObserver) {\n this._visibilityObserver.disconnect();\n\n this._visibilityObserver = null;\n }\n\n if (this._onResize) {\n window.removeEventListener('resize', this._onResize);\n }\n\n if (this._swipeMoveHandler) {\n document.removeEventListener('touchmove', this._swipeMoveHandler);\n }\n\n if (this._swipeEndHandler) {\n document.removeEventListener('touchend', this._swipeEndHandler);\n }\n\n if (this._popStateHandler) {\n window.removeEventListener('popstate', this._popStateHandler);\n }\n\n this.streamRequestId++;\n this.closeEventSource();\n this.clearTypeTimer();\n this.onMsgTouchEnd();\n this.contentCache = {};\n },\n methods: {\n // 独立使用时自检移动端(父级已传 isMobile 时忽略)\n handleResize: function handleResize() {\n var now = typeof window !== 'undefined' && (window.innerWidth <= 768 || !(0, _native.hasElementUI)(this));\n this.localIsMobile = now;\n },\n\n /**\n * 用户消息按纯文本渲染(转义 HTML + 保留换行)。\n * 不能用 markdown 渲染用户输入:行首的「33.」「1.」会被当作有序列表标记、\n * 「- 」「* 」当无序列表标记、「---」当分隔线,标记本身不显示,\n * 导致仅输入「33.」时气泡内一片空白(内容看起来\"消失\")。\n * 用户原文需原样可见,故不做任何 markdown 语义解析。\n */\n renderUserContent: function renderUserContent(text) {\n if (!text) return '';\n return this.escapeHtml(text).replace(/\\n/g, '<br>');\n },\n renderMarkdown: function renderMarkdown(text) {\n if (!text) return '';\n\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n // 与 ChatDetail 一致的增量渲染:完整行走 markdown、未完成尾行转义拼接,\n // 借助 markdown-it breaks:true 实现逐行浮现效果。\n renderStreaming: function renderStreaming(text, msgKey) {\n if (!text) return '';\n var cache = this.contentCache[msgKey];\n var lines = text.split('\\n');\n var lastLineIdx = lines.length - 1;\n\n if (!cache) {\n try {\n var html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html: html,\n fullText: text\n });\n return html;\n } catch (_unused) {\n return this.escapeHtml(text);\n }\n }\n\n if (text === cache.fullText) return cache.html;\n cache.fullText = text; // 行数没变,仅末尾追加字符,直接拼接尾行\n\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n } // 行数变少/文本回退截断,全量重渲染\n\n\n if (lastLineIdx < cache.renderedLines) {\n try {\n var fullLines = lines.slice(0, lastLineIdx);\n var newBaseHtml = '';\n\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n\n var _lastLine = this.escapeHtml(lines[lastLineIdx]);\n\n return cache.html + (_lastLine || '');\n } catch (_unused2) {\n return this.escapeHtml(text);\n }\n } // 新增完整行\n\n\n if (cache.renderedLines < lastLineIdx) {\n var allFullLines = lines.slice(0, lastLineIdx);\n var _newBaseHtml = '';\n\n if (allFullLines.length > 0) {\n _newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n\n cache.html = _newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n\n var lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n escapeHtml: function escapeHtml(text) {\n if (!text) return '';\n var map = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return String(text).replace(/[&<>\"']/g, function (c) {\n return map[c];\n });\n },\n autoResizeTextarea: function autoResizeTextarea() {\n var ta = this.$refs.taRef;\n if (!ta) return; // 移动端从单行起增高(与 38px 最小高度一致),PC 保持 100px 基准\n\n var baseH = this.isMobileActive ? 38 : 100;\n var maxH = this.isMobileActive ? 120 : 180;\n ta.style.height = \"\".concat(baseH, \"px\");\n var realH = ta.scrollHeight;\n\n if (realH > baseH) {\n ta.style.height = \"\".concat(Math.min(realH, maxH), \"px\");\n }\n },\n newLine: function newLine(e) {\n var _this2 = this;\n\n e.preventDefault();\n var ta = this.$refs.taRef;\n var start = ta.selectionStart;\n var end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(function () {\n ta.selectionStart = ta.selectionEnd = start + 1;\n\n _this2.autoResizeTextarea();\n });\n },\n\n /**\n * 加载\"猜您想问\"问题(按公司,所有用户可见)\n */\n loadSuggestedQuestions: function loadSuggestedQuestions() {\n var _this3 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var list;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (_this3.companyId) {\n _context.next = 3;\n break;\n }\n\n _this3.suggestedQuestions = [];\n return _context.abrupt(\"return\");\n\n case 3:\n _context.prev = 3;\n _context.next = 6;\n return (0, _knowledgeBase.getSuggestedQuestions)(_this3.companyId);\n\n case 6:\n _context.t0 = _context.sent;\n\n if (_context.t0) {\n _context.next = 9;\n break;\n }\n\n _context.t0 = [];\n\n case 9:\n list = _context.t0;\n _this3.suggestedQuestions = list.map(function (q) {\n return q.questionText;\n });\n _context.next = 16;\n break;\n\n case 13:\n _context.prev = 13;\n _context.t1 = _context[\"catch\"](3);\n console.error('加载猜您想问失败', _context.t1);\n\n case 16:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[3, 13]]);\n }))();\n },\n\n /**\n * 检查当前公司是否有「已索引」的知识库文档,无可用资料时展示遮罩提示\n * 仅已索引(indexStatus === 1)的文档才能被检索,未索引/索引中/索引失败都不算\n */\n checkKbDocuments: function checkKbDocuments() {\n var _this4 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var data, isEmpty;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n if (_this4.companyId) {\n _context2.next = 3;\n break;\n }\n\n _this4.noDocument = false;\n return _context2.abrupt(\"return\");\n\n case 3:\n _context2.prev = 3;\n _context2.next = 6;\n return (0, _knowledgeBase.getFileList)({\n companyId: _this4.companyId,\n indexStatus: 1,\n pageNum: 1,\n pageSize: 1\n });\n\n case 6:\n data = _context2.sent;\n isEmpty = (data && data.total || 0) === 0;\n _this4.noDocument = isEmpty; // 无资料时把会话固定回第一个\n\n if (isEmpty) {\n _this4.lockFirstSession(); // 无资料遮罩位于聊天详情页的 .kb-chat-main 内,而移动端首屏是会话列表页\n // (h5-list-mode 下 .kb-chat-main 被 display:none 隐藏),若停留在列表页\n // 则遮罩永远不可见;故无资料时强制切到详情页把遮罩展示出来\n\n\n if (_this4.isMobileActive) {\n _this4.showH5List = false;\n history.pushState({\n page: 'kbChatDetail'\n }, '');\n }\n }\n\n _context2.next = 16;\n break;\n\n case 12:\n _context2.prev = 12;\n _context2.t0 = _context2[\"catch\"](3);\n // 查询失败(如无管理权限)时不展示遮罩,避免误拦截\n console.error('检查知识库文档失败', _context2.t0);\n _this4.noDocument = false;\n\n case 16:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[3, 12]]);\n }))();\n },\n initSession: function initSession() {\n var _this5 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var list;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n if (_this5.companyId) {\n _context3.next = 2;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 2:\n if (!(_this5.initDone && (_this5.messages.length || _this5.streaming || _this5.inputText))) {\n _context3.next = 4;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 4:\n _this5.initDone = false;\n _context3.prev = 5;\n _context3.next = 8;\n return (0, _kbChat.getKbSessionList)(_this5.companyId);\n\n case 8:\n _context3.t0 = _context3.sent;\n\n if (_context3.t0) {\n _context3.next = 11;\n break;\n }\n\n _context3.t0 = [];\n\n case 11:\n list = _context3.t0;\n _this5.sessionList = list;\n\n if (!(list.length > 0)) {\n _context3.next = 19;\n break;\n }\n\n // 只有 1 个会话且无消息(新用户首次进入自动生成的空会话)时,\n // 默认折叠侧边栏只显示聊天主界面;在 selectSession 前同步设置,\n // 避免「先展开再合住」的闪烁\n _this5.sidebarCollapsed = list.length === 1 && !list[0].lastContent;\n _context3.next = 17;\n return _this5.selectSession(list[0].sessionId);\n\n case 17:\n _context3.next = 21;\n break;\n\n case 19:\n _context3.next = 21;\n return _this5.autoCreateSession();\n\n case 21:\n _context3.next = 28;\n break;\n\n case 23:\n _context3.prev = 23;\n _context3.t1 = _context3[\"catch\"](5);\n console.error('获取会话列表失败,尝试自动创建', _context3.t1);\n _context3.next = 28;\n return _this5.autoCreateSession();\n\n case 28:\n _context3.prev = 28;\n _this5.initDone = true;\n return _context3.finish(28);\n\n case 31:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[5, 23, 28, 31]]);\n }))();\n },\n autoCreateSession: function autoCreateSession() {\n var _this6 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n var data;\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n if (_this6.companyId) {\n _context4.next = 3;\n break;\n }\n\n console.warn('[KbChat] 无法创建会话:companyId 为空');\n return _context4.abrupt(\"return\");\n\n case 3:\n console.log('[KbChat] 创建会话,companyId:', _this6.companyId);\n _context4.prev = 4;\n _context4.next = 7;\n return (0, _kbChat.createKbSession)(_this6.companyId);\n\n case 7:\n data = _context4.sent;\n console.log('[KbChat] 会话创建成功,sessionId:', data.sessionId);\n\n _this6.finishStream();\n\n _this6.activeSessionId = data.sessionId;\n _this6.streamRequestId++;\n _this6.messages = [];\n _this6.sessionList = [{\n sessionId: data.sessionId,\n title: '',\n lastContent: ''\n }].concat(_this6.sessionList || []); // 新建会话时保持侧边栏展开,方便继续切换会话\n\n _this6.sidebarCollapsed = false;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n\n _context4.next = 21;\n break;\n\n case 18:\n _context4.prev = 18;\n _context4.t0 = _context4[\"catch\"](4);\n console.error('自动创建会话失败', _context4.t0);\n\n case 21:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[4, 18]]);\n }))();\n },\n\n /** 新建会话(豆包式「新对话」) */\n handleNewSession: function handleNewSession() {\n var _this7 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n if (!_this7.noDocument) {\n _context5.next = 2;\n break;\n }\n\n return _context5.abrupt(\"return\");\n\n case 2:\n if (_this7.streaming) _this7.handleStop();\n _context5.next = 5;\n return _this7.autoCreateSession();\n\n case 5:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5);\n }))();\n },\n\n /** 切换会话 */\n handleSelectSession: function handleSelectSession(item) {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6() {\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n if (!(!item || !item.sessionId)) {\n _context6.next = 2;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 2:\n if (!_this8.noDocument) {\n _context6.next = 5;\n break;\n }\n\n _this8.lockFirstSession();\n\n return _context6.abrupt(\"return\");\n\n case 5:\n if (!(item.sessionId === _this8.activeSessionId && _this8.messages.length)) {\n _context6.next = 8;\n break;\n }\n\n if (_this8.isMobileActive) {\n _this8.showH5List = false;\n history.pushState({\n page: 'kbChatDetail'\n }, '');\n\n _this8.$nextTick(function () {\n return _this8.scrollToBottom();\n });\n }\n\n return _context6.abrupt(\"return\");\n\n case 8:\n _context6.next = 10;\n return _this8.selectSession(item.sessionId);\n\n case 10:\n // 移动端:选中后跳到聊天详情页\n if (_this8.isMobileActive) {\n _this8.showH5List = false; // 推入历史状态,使 Android 系统返回手势先触发 popstate 被拦截\n\n history.pushState({\n page: 'kbChatDetail'\n }, ''); // 列表页时聊天区被 display:none,scrollHeight 为 0 导致上面的滚动无效;\n // 切到详情页、容器可见后需再滚一次,默认定位到最新消息\n\n _this8.$nextTick(function () {\n return _this8.scrollToBottom();\n });\n }\n\n case 11:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6);\n }))();\n },\n // 企管版本菜单排序联调开发完成提测\n // \n\n /** 返回宿主 App 原生页面(参考 AI 助理 handleNativeBack) */\n handleNativeBack: function handleNativeBack() {\n if (this.$hybrid && typeof this.$hybrid.closePage === 'function') {\n this.$hybrid.closePage();\n } else if (window.history.length > 1) {\n window.history.back();\n }\n },\n\n /** 会话列表页返回按钮:返回原生页面 */\n handleSidebarBack: function handleSidebarBack() {\n this.handleNativeBack();\n },\n // ── H5 侧滑手势(参考 AI 助理)──\n // 从左/右屏幕边缘起手横滑触发返回,避免页面内部横向滚动被误判\n onSwipeTouchStart: function onSwipeTouchStart(e) {\n if (!this.isMobileActive) return;\n var t = e.touches[0];\n this._swipeStartX = t.clientX;\n this._swipeStartY = t.clientY;\n this._swipeHandled = false;\n var edge = Math.min(56, Math.round(window.innerWidth * 0.09));\n this._swipeFromLeftEdge = t.clientX <= edge;\n this._swipeFromRightEdge = t.clientX >= window.innerWidth - edge;\n this._swipeFromEdge = this._swipeFromLeftEdge || this._swipeFromRightEdge;\n },\n _swipeShouldTrigger: function _swipeShouldTrigger(diffX, diffY) {\n if (!this._swipeFromEdge) return false; // 从左边缘 → 右滑;从右边缘 → 左滑\n\n var directionOk = this._swipeFromLeftEdge && diffX >= 60 || this._swipeFromRightEdge && diffX <= -60;\n if (!directionOk) return false; // 水平距离明显大于垂直距离(排除上下滚动干扰)\n\n return Math.abs(diffX) >= Math.abs(diffY) * 2;\n },\n _onSwipeMove: function _onSwipeMove(e) {\n if (!this.isMobileActive || this._swipeHandled) return;\n if (!e.touches || !e.touches.length) return;\n var diffX = e.touches[0].clientX - this._swipeStartX;\n var diffY = e.touches[0].clientY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n // touchend 兜底,兼容不支持 touchmove 的场景\n _onSwipeEnd: function _onSwipeEnd(e) {\n if (!this.isMobileActive || this._swipeHandled) return;\n if (!e.changedTouches || !e.changedTouches.length) return;\n var diffX = e.changedTouches[0].clientX - this._swipeStartX;\n var diffY = e.changedTouches[0].clientY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n\n /** 侧滑触发:列表页 → 返回原生页面;详情页 → 回会话列表页 */\n _triggerSwipeAction: function _triggerSwipeAction() {\n if (this.showH5List) {\n this.handleNativeBack();\n } else {\n this.handleMobileBack();\n }\n },\n\n /**\n * 拦截 Android 系统返回手势(history.pushState + popstate)\n * WebView 先回退历史栈触发 popstate,这里拦截后导航到列表页,避免 Activity 被直接关闭\n */\n _onPopState: function _onPopState() {\n if (!this.isMobileActive) return;\n\n if (!this.showH5List) {\n // 详情页 → 返回会话列表页\n this.showH5List = true;\n history.pushState({\n page: 'kbChatList'\n }, '');\n } else {\n // 列表页 → 直接关闭 WebView 返回原生页面\n this.handleNativeBack();\n }\n },\n\n /** 移动端详情页返回:无资料遮罩时返回原生页面;否则回到会话列表页 */\n handleMobileBack: function handleMobileBack() {\n // 无资料遮罩期间无法进入会话,返回到宿主 App 原生页面\n if (this.noDocument) {\n this.handleNativeBack();\n return;\n } // 正常情况:回到会话列表页\n\n\n this.showH5List = true;\n },\n\n /** 无资料遮罩期间:把当前会话固定回列表中的第一个 */\n lockFirstSession: function lockFirstSession() {\n var first = this.sessionList[0];\n\n if (first && first.sessionId && first.sessionId !== this.activeSessionId) {\n this.selectSession(first.sessionId);\n }\n },\n\n /** 会话项「…」菜单:重命名 / 删除 */\n handleSessionMenu: function handleSessionMenu(cmd, item) {\n if (!item || !item.sessionId) return;\n\n if (cmd === 'rename') {\n this.handleRenameSession(item);\n } else if (cmd === 'delete') {\n this.handleDeleteSession(item);\n }\n },\n // ==================== 移动端会话操作单 ====================\n openMobileAction: function openMobileAction(item) {\n if (this.noDocument) return;\n this.mobileAction = item;\n },\n doMobileRename: function doMobileRename(item) {\n this.mobileAction = null;\n this.handleRenameSession(item);\n },\n doMobileDelete: function doMobileDelete(item) {\n this.mobileAction = null;\n this.handleDeleteSession(item);\n },\n\n /** 重命名会话 */\n handleRenameSession: function handleRenameSession(item) {\n var _this9 = this;\n\n if (!item || !item.sessionId) return;\n this.$prompt('请输入新的会话名称', '重命名会话', {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n inputValue: item.title || '',\n inputPattern: /\\S+/,\n inputErrorMessage: '名称不能为空'\n }).then(function (_ref) {\n var value = _ref.value;\n var title = (value || '').trim();\n if (!title) return;\n return (0, _kbChat.renameKbSession)(item.sessionId, title).then(function () {\n item.title = title;\n\n _this9.$message.success('重命名成功');\n });\n }).catch(function (err) {\n // 取消或校验失败,不提示错误\n if (err && err !== 'cancel' && err !== 'close') {\n console.error('重命名失败', err);\n\n _this9.$message.error('重命名失败');\n }\n });\n },\n\n /** 删除会话 */\n handleDeleteSession: function handleDeleteSession(item) {\n var _this10 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n if (!(!item || !item.sessionId)) {\n _context7.next = 2;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 2:\n if (!(_this10.sessionList.length <= 1)) {\n _context7.next = 5;\n break;\n }\n\n _this10.$message.warning('至少保留一个会话');\n\n return _context7.abrupt(\"return\");\n\n case 5:\n _context7.prev = 5;\n _context7.next = 8;\n return _this10.$confirm('删除后无法恢复,确定删除该会话吗?', '删除会话', {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n type: 'warning'\n });\n\n case 8:\n _context7.next = 13;\n break;\n\n case 10:\n _context7.prev = 10;\n _context7.t0 = _context7[\"catch\"](5);\n return _context7.abrupt(\"return\");\n\n case 13:\n _context7.prev = 13;\n _context7.next = 16;\n return (0, _kbChat.deleteKbSession)(item.sessionId);\n\n case 16:\n _this10.sessionList = (_this10.sessionList || []).filter(function (s) {\n return s.sessionId !== item.sessionId;\n });\n\n if (!(_this10.activeSessionId === item.sessionId)) {\n _context7.next = 28;\n break;\n }\n\n _this10.finishStream();\n\n _this10.activeSessionId = '';\n _this10.messages = [];\n\n if (!(_this10.sessionList.length > 0)) {\n _context7.next = 26;\n break;\n }\n\n _context7.next = 24;\n return _this10.selectSession(_this10.sessionList[0].sessionId);\n\n case 24:\n _context7.next = 28;\n break;\n\n case 26:\n _context7.next = 28;\n return _this10.autoCreateSession();\n\n case 28:\n _this10.$message.success('删除成功');\n\n _context7.next = 35;\n break;\n\n case 31:\n _context7.prev = 31;\n _context7.t1 = _context7[\"catch\"](13);\n console.error('删除会话失败', _context7.t1);\n\n _this10.$message.error('删除失败');\n\n case 35:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[5, 10], [13, 31]]);\n }))();\n },\n\n /** 会话列表时间格式化:今天显示时分,更早显示月日 */\n formatTime: function formatTime(time) {\n if (!time) return '';\n var d = new Date(time);\n if (isNaN(d.getTime())) return '';\n var now = new Date();\n var sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();\n\n var pad = function pad(n) {\n return n < 10 ? '0' + n : '' + n;\n };\n\n if (sameDay) {\n return pad(d.getHours()) + ':' + pad(d.getMinutes());\n }\n\n return pad(d.getMonth() + 1) + '-' + pad(d.getDate());\n },\n\n /** 后端消息 → 前端消息对象(保留 id/msgKey,删除功能需要) */\n mapMessage: function mapMessage(m, i) {\n var thinking = m.thinking || m.reasoning || m.reasoning_result || m.reasoningContent || m.reasoning_content || m.thinkingContent || m.thinking_content || '';\n return {\n id: m.id,\n msgKey: m.msgKey || m.msg_key || '',\n role: m.role,\n content: m.content || '',\n thinking: thinking,\n _key: m.createTime ? 'msg_' + m.createTime + '_' + i : 'msg_' + Date.now() + '_' + Math.random()\n };\n },\n selectSession: function selectSession(sessionId) {\n var _this11 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee8() {\n var raw, msgs;\n return regeneratorRuntime.wrap(function _callee8$(_context8) {\n while (1) {\n switch (_context8.prev = _context8.next) {\n case 0:\n if (_this11.streaming) _this11.handleStop();\n\n _this11.exitSelectMode();\n\n _this11.msgAction = null;\n _this11.activeSessionId = sessionId;\n _this11.messages = [];\n _this11.streamMsg = null;\n _context8.prev = 6;\n _context8.next = 9;\n return (0, _kbChat.getKbSessionMessages)(sessionId);\n\n case 9:\n raw = _context8.sent;\n msgs = Array.isArray(raw) ? raw : raw.messages || raw.records || raw.list || [];\n console.log('[KbChat] selectSession 回读', {\n sessionId: sessionId,\n rawIsArray: Array.isArray(raw),\n rawKeys: raw && !Array.isArray(raw) ? Object.keys(raw) : null,\n count: msgs.length,\n list: msgs.map(function (m) {\n return {\n id: m.id,\n role: m.role,\n content: String(m.content || '').slice(0, 25)\n };\n })\n });\n _this11.messages = _this11.dedupeMessages(msgs).map(function (m, i) {\n return _this11.mapMessage(m, i);\n });\n\n _this11.$nextTick(function () {\n return _this11.scrollToBottom();\n });\n\n _context8.next = 19;\n break;\n\n case 16:\n _context8.prev = 16;\n _context8.t0 = _context8[\"catch\"](6);\n console.error('加载消息失败', _context8.t0);\n\n case 19:\n case \"end\":\n return _context8.stop();\n }\n }\n }, _callee8, null, [[6, 16]]);\n }))();\n },\n\n /**\n * 后端回读消息去重。\n * 后端存在同一问答对被重复落库的情况(同一会话里出现两套相同的「问+答」),\n * 直接渲染会让用户看到重复问答。这里按「问答对」为单位去重:\n * 把消息切成组(用户问 + 其后的 AI 答为一组),整组内容完全相同的只保留第一次出现的。\n * 相比「只和上一条比」的相邻去重,能覆盖组与组之间被隔开、或 content 有空白差异的情况。\n */\n dedupeMessages: function dedupeMessages(msgs) {\n var _this12 = this;\n\n // 先切组:以 user 消息为一个新组的起点,其后连续的 assistant 归入同组\n var groups = [];\n msgs.forEach(function (m) {\n var role = _this12.normRole(m.role);\n\n if (role === 'user' || !groups.length) {\n groups.push([m]);\n } else {\n groups[groups.length - 1].push(m);\n }\n });\n var seen = {};\n var out = [];\n groups.forEach(function (group) {\n var signature = group.map(function (m) {\n return _this12.normRole(m.role) + \"\\x01\" + String(m.content || '').replace(/\\s+/g, '');\n }).join(\"\\x02\");\n if (signature && seen[signature]) return;\n if (signature) seen[signature] = true;\n out.push.apply(out, group);\n });\n return out;\n },\n\n /**\n * 内容比对用的归一化:压掉所有空白(含换行)差异。\n * 后端落库时常会把换行/多空格规范化(或存成 CRLF),只 trim 首尾的严格相等会漏匹配,\n * 漏匹配 = 消息拿不到 id = 「删除」按钮偶现不出现。\n */\n normContent: function normContent(text) {\n return String(text == null ? '' : text).replace(/\\s+/g, ' ').trim();\n },\n\n /**\n * 回读当前会话消息,补全后端持久化后生成的 id。\n * 流式回答期间消息只存在于本地数组(无 id),无法按 id 删除;\n * 流式结束后调用一次即可拿到 id。已全部有 id 时跳过,避免多余请求。\n *\n * 注意:不能直接用后端数组整体覆盖 this.messages。后端回读可能包含重复/多余记录\n * (如同一会话里用户消息被落库两次),若整体覆盖会把重复消息直接渲染出来。\n * 这里以本地数组为基准,只用后端记录按「角色 + 内容」顺序回填 id,保证界面条数不变。\n *\n * 「删除」按钮偶现丢失(id 补不全)的易失点,本函数逐一兜住:\n * 1) **AI 回答条的内容与后端存储对不上**:本地内容是流式拼接/agent_result 的结果,\n * 后端会做规范化甚至改写,靠「内容相等」匹配回答条天然不可靠 →\n * 改为优先用 SSE 下发的 `msgKey`(与后端记录一一对应)做精确匹配,\n * 这是「回答出来了却没有删除按钮」的主因(提问条没有 msgKey,仍走内容匹配);\n * 2) 后端落库延迟:有限次重试;重试期间**不能**因为开了新流(streaming)就直接 return,\n * 否则重试预算被静默吞掉,消息永远拿不到 id;\n * 3) 历史消息已带 id 时 cursor 必须同步前进,且同一条后端记录不能重复分配给多条本地消息:\n * 否则会拿到**别的消息的 id**(删错消息)或彻底匹配不上。\n */\n syncMessageIds: function syncMessageIds() {\n var _arguments = arguments,\n _this13 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee9() {\n var retries, sessionId, backendMsgs, scheduleRetry, raw, usedIds, isUsed, assign, cursor;\n return regeneratorRuntime.wrap(function _callee9$(_context9) {\n while (1) {\n switch (_context9.prev = _context9.next) {\n case 0:\n retries = _arguments.length > 0 && _arguments[0] !== undefined ? _arguments[0] : 0;\n\n if (_this13.activeSessionId) {\n _context9.next = 3;\n break;\n }\n\n return _context9.abrupt(\"return\");\n\n case 3:\n if (!(_this13.messages.length && _this13.messages.every(function (m) {\n return m.id;\n }))) {\n _context9.next = 5;\n break;\n }\n\n return _context9.abrupt(\"return\");\n\n case 5:\n sessionId = _this13.activeSessionId;\n backendMsgs = []; // 重试排期:仅在仍属于同一会话且仍有消息缺 id 时继续,避免污染其它会话\n\n scheduleRetry = function scheduleRetry() {\n var missing = _this13.messages.filter(function (m) {\n return !m.id;\n });\n\n if (!missing.length || sessionId !== _this13.activeSessionId) return;\n\n if (retries > 0) {\n setTimeout(function () {\n return _this13.syncMessageIds(retries - 1);\n }, 800);\n return;\n } // 重试耗尽仍有缺 id(表现为该条没有「删除」按钮)→ 留一条诊断日志,便于对照\n // 本地内容与后端内容到底差在哪(内容差异 / 后端未返回 msgKey / 落库仍没完成)\n\n\n console.warn('[KbChat] 重试耗尽仍有消息缺 id(该条不显示删除按钮)', {\n sessionId: sessionId,\n missing: missing.map(function (m) {\n return {\n role: m.role,\n msgKey: m.msgKey || '',\n snippet: String(m.content || '').slice(0, 60)\n };\n }),\n backend: backendMsgs.map(function (b) {\n return {\n id: b.id,\n role: b.role,\n msgKey: b.msgKey || b.msg_key || '',\n snippet: String(b.content || '').slice(0, 60)\n };\n })\n });\n };\n\n _context9.prev = 8;\n _context9.next = 11;\n return (0, _kbChat.getKbSessionMessages)(sessionId);\n\n case 11:\n raw = _context9.sent;\n\n if (!(sessionId !== _this13.activeSessionId)) {\n _context9.next = 14;\n break;\n }\n\n return _context9.abrupt(\"return\");\n\n case 14:\n backendMsgs = Array.isArray(raw) ? raw : raw.messages || raw.records || raw.list || []; // 已占用的后端 id:同一条后端记录不能分配给多条本地消息\n\n usedIds = _this13.messages.filter(function (m) {\n return m.id;\n }).map(function (m) {\n return String(m.id);\n });\n\n isUsed = function isUsed(b) {\n return usedIds.indexOf(String(b.id)) !== -1;\n };\n\n assign = function assign(m, b) {\n // 本地消息对象在 handleSend 中创建时未声明 id/msgKey,\n // 直接赋值 Vue 2 无法侦测新增属性,模板不会重渲染(删除按钮仍不出现);\n // 必须用 $set 才能让「删除」等依赖 msg.id 的 UI 即时刷新。\n _this13.$set(m, 'id', b.id);\n\n if (!m.msgKey) _this13.$set(m, 'msgKey', b.msgKey || b.msg_key || '');\n usedIds.push(String(b.id));\n }; // Pass 1:msgKey 精确匹配(AI 回答条)。SSE 的 msgKey 与后端记录一一对应,\n // 不受\"后端改写/规范化回答内容\"影响,是回答条最可靠的配对方式。\n\n\n _this13.messages.forEach(function (m) {\n if (m.id || !m.msgKey) return;\n var b = backendMsgs.find(function (x) {\n return x && x.id != null && !isUsed(x) && String(x.msgKey || x.msg_key || '') === String(m.msgKey);\n });\n if (b) assign(m, b);\n }); // Pass 2:角色 + 归一化内容 + cursor 顺序匹配(提问条无 msgKey,走内容匹配)\n\n\n cursor = 0;\n\n _this13.messages.forEach(function (m) {\n if (m.id) {\n // 已补全的消息:按 id 定位并推进 cursor(不做则 cursor 停留在 0)\n var idx = backendMsgs.findIndex(function (b, i) {\n return i >= cursor && String(b.id) === String(m.id);\n });\n if (idx >= 0) cursor = idx + 1;\n return;\n }\n\n var role = _this13.normRole(m.role);\n\n var key = _this13.normContent(m.content);\n\n for (var i = cursor; i < backendMsgs.length; i++) {\n var b = backendMsgs[i];\n if (isUsed(b)) continue;\n\n if (_this13.normRole(b.role) === role && _this13.normContent(b.content) === key) {\n assign(m, b);\n cursor = i + 1;\n return;\n }\n }\n });\n\n _context9.next = 26;\n break;\n\n case 23:\n _context9.prev = 23;\n _context9.t0 = _context9[\"catch\"](8);\n console.error('同步消息 id 失败', _context9.t0);\n\n case 26:\n scheduleRetry();\n\n case 27:\n case \"end\":\n return _context9.stop();\n }\n }\n }, _callee9, null, [[8, 23]]);\n }))();\n },\n // ==================== 消息多选 / 删除 ====================\n\n /** 进入多选模式(preselectMsg 传入时默认勾选该条所在的整组问答,对应豆包式「删除」入口) */\n enterSelectMode: function enterSelectMode(preselectMsg) {\n var _this14 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee10() {\n var preselectId, idx;\n return regeneratorRuntime.wrap(function _callee10$(_context10) {\n while (1) {\n switch (_context10.prev = _context10.next) {\n case 0:\n if (!(_this14.streaming || !_this14.messages.length)) {\n _context10.next = 2;\n break;\n }\n\n return _context10.abrupt(\"return\");\n\n case 2:\n // 点击条的后端持久化 id(模板上「删除」按钮仅在 msg.id 存在时渲染,故必有值)\n preselectId = preselectMsg ? preselectMsg.id : null; // 先回读消息确保每条都有 id(刚流式完成的消息本地没有 id)。\n // 回读只补 id、不重建数组,定位可用 id 或对象引用。\n\n _context10.next = 5;\n return _this14.syncMessageIds();\n\n case 5:\n _this14.selectMode = true;\n _this14.selectedMsgIds = [];\n idx = preselectId != null ? _this14.messages.findIndex(function (m) {\n return m.id === preselectId;\n }) : -1;\n if (idx === -1 && preselectMsg) idx = _this14.messages.indexOf(preselectMsg);\n\n if (idx !== -1) {\n // 默认勾选整组问答(用户问 + AI 答),便于一键删除\n _this14.selectedMsgIds = _this14.getQaPairIdsAt(idx);\n } // 滚动定位:让默认勾选的整组问答都进入视野(长回答会把用户问顶出屏幕)\n\n\n _this14.scrollSelectedIntoView();\n\n case 11:\n case \"end\":\n return _context10.stop();\n }\n }\n }, _callee10);\n }))();\n },\n\n /**\n * 把首个被选中的消息行滚动到容器内可见。\n * 选中行是「用户问 + AI 答」中的靠前一条,滚到它即能同时看到整组。\n * 若靠前那条已在视野内则不动,避免无谓跳动。\n */\n scrollSelectedIntoView: function scrollSelectedIntoView() {\n var _this15 = this;\n\n this.$nextTick(function () {\n var container = _this15.$refs.messagesRef;\n if (!container) return;\n var rows = container.querySelectorAll('.msg-row');\n if (!rows.length) return; // 找到第一个处于选中态、且带 id 的行(与 selectedMsgIds 顺序无关,取 DOM 顺序)\n\n var target = null;\n\n for (var i = 0; i < rows.length; i++) {\n var box = rows[i].querySelector('.msg-check-box.checked');\n\n if (box) {\n target = rows[i];\n break;\n }\n }\n\n if (!target) return;\n var cRect = container.getBoundingClientRect();\n var tRect = target.getBoundingClientRect(); // 目标行顶部已在容器可视区内则无需滚动\n\n if (tRect.top >= cRect.top && tRect.top < cRect.bottom) return; // 滚动到目标行顶部(留 8px 间距)\n\n container.scrollTop += tRect.top - cRect.top - 8;\n });\n },\n exitSelectMode: function exitSelectMode() {\n this.selectMode = false;\n this.selectedMsgIds = [];\n },\n isMsgSelected: function isMsgSelected(msg) {\n return !!msg.id && this.selectedMsgIds.indexOf(msg.id) !== -1;\n },\n\n /** 归一化 role,兼容后端可能返回的大小写/空白 */\n normRole: function normRole(role) {\n return String(role == null ? '' : role).trim().toLowerCase();\n },\n\n /**\n * 取与下标 idx 处消息成对的另一条:用户问 ↔ AI 答视为一组问答。\n * 只认紧邻的一条(问答对在列表里必然相邻),相邻非同组则不配对。\n * 不依赖 role 的具体拼写(user/assistant 的大小写、空白均容错)。\n */\n getQaPartnerAt: function getQaPartnerAt(idx) {\n if (idx < 0 || idx >= this.messages.length) return null;\n var cur = this.messages[idx];\n if (!cur) return null;\n var role = this.normRole(cur.role);\n if (role !== 'user' && role !== 'assistant') return null; // 只认紧邻的那一条:问答对在列表里必然相邻。\n // 若相邻消息不是异角色(例如回答已被删除),说明当前消息已无配对,\n // 不能继续向前/向后扫描,否则会误勾到上一个问答的回答。\n\n var neighbor = role === 'user' ? this.messages[idx + 1] : this.messages[idx - 1];\n if (!neighbor) return null;\n var neighborRole = this.normRole(neighbor.role);\n var expect = role === 'user' ? 'assistant' : 'user';\n return neighborRole === expect ? neighbor : null;\n },\n\n /** 下标 idx 处消息所在问答对的可删 id 列表(用户问 + AI 答) */\n getQaPairIdsAt: function getQaPairIdsAt(idx) {\n var ids = [];\n var cur = this.messages[idx];\n if (cur && cur.id) ids.push(cur.id);\n var partner = this.getQaPartnerAt(idx);\n if (partner && partner.id && ids.indexOf(partner.id) === -1) ids.push(partner.id);\n return ids;\n },\n\n /**\n * 勾选单条消息:勾上时连带把问答对里另一条一起勾上;\n * 取消时只取消当前这条,从而支持「先整组勾选、再单独取消一条」的单条选择。\n */\n toggleSelectMessage: function toggleSelectMessage(msg) {\n var _this16 = this;\n\n if (!msg.id) return;\n var i = this.selectedMsgIds.indexOf(msg.id);\n\n if (i === -1) {\n var idx = this.messages.indexOf(msg);\n var pairIds = idx === -1 ? [msg.id] : this.getQaPairIdsAt(idx);\n pairIds.forEach(function (id) {\n if (_this16.selectedMsgIds.indexOf(id) === -1) _this16.selectedMsgIds.push(id);\n });\n } else {\n this.selectedMsgIds.splice(i, 1);\n }\n },\n toggleSelectAll: function toggleSelectAll() {\n var ids = this.deletableMsgIds;\n this.selectedMsgIds = this.selectedMsgIds.length === ids.length ? [] : ids.slice();\n },\n\n /** 多选模式下点击整行即切换选中 */\n onMsgRowClick: function onMsgRowClick(msg) {\n // 长按弹出菜单后浏览器还会补一次 click,短暂屏蔽避免误触\n if (Date.now() < this._ignoreClickUntil) return;\n if (this.selectMode) this.toggleSelectMessage(msg);\n },\n\n /** 移动端长按气泡弹出消息操作单(豆包式:复制 / 删除 / 多选) */\n onMsgTouchStart: function onMsgTouchStart(msg) {\n var _this17 = this;\n\n if (!this.isMobileActive || this.selectMode) return;\n this._longPressMsg = msg;\n clearTimeout(this._longPressTimer);\n this._longPressTimer = setTimeout(function () {\n _this17._longPressTimer = null; // 用户正在长按选中文本(复制用),此时不弹菜单,避免抢走选择行为\n\n var sel = window.getSelection && window.getSelection();\n if (sel && String(sel).trim()) return;\n\n _this17.openMsgAction(_this17._longPressMsg);\n }, 500);\n },\n onMsgTouchEnd: function onMsgTouchEnd() {\n if (this._longPressTimer) {\n clearTimeout(this._longPressTimer);\n this._longPressTimer = null;\n }\n },\n openMsgAction: function openMsgAction(msg) {\n this.msgAction = msg; // 长按抬手后浏览器可能补发 click,短暂屏蔽,避免刚弹出的菜单被立即关闭\n\n this._ignoreClickUntil = Date.now() + 350;\n },\n closeMsgAction: function closeMsgAction() {\n if (Date.now() < this._ignoreClickUntil) return;\n this.msgAction = null;\n },\n\n /** 移动端消息操作单:复制 / 删除(删除进入多选模式,与豆包一致) */\n doMsgAction: function doMsgAction(action) {\n var msg = this.msgAction;\n this.msgAction = null;\n if (!msg) return;\n\n if (action === 'copy') {\n this.copyText(msg.content);\n } else if (action === 'delete') {\n this.enterSelectMode(msg);\n }\n },\n\n /** 批量删除选中的消息 */\n handleDeleteSelected: function handleDeleteSelected() {\n var _this18 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee11() {\n var ids;\n return regeneratorRuntime.wrap(function _callee11$(_context11) {\n while (1) {\n switch (_context11.prev = _context11.next) {\n case 0:\n if (!(!_this18.selectedMsgIds.length || !_this18.activeSessionId)) {\n _context11.next = 2;\n break;\n }\n\n return _context11.abrupt(\"return\");\n\n case 2:\n ids = _this18.selectedMsgIds.slice();\n _context11.prev = 3;\n _context11.next = 6;\n return _this18.$confirm(\"\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\uFF0C\\u786E\\u5B9A\\u5220\\u9664\\u9009\\u4E2D\\u7684 \".concat(ids.length, \" \\u6761\\u6D88\\u606F\\u5417\\uFF1F\"), '删除消息', {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n type: 'warning'\n });\n\n case 6:\n _context11.next = 11;\n break;\n\n case 8:\n _context11.prev = 8;\n _context11.t0 = _context11[\"catch\"](3);\n return _context11.abrupt(\"return\");\n\n case 11:\n _context11.prev = 11;\n _context11.next = 14;\n return (0, _kbChat.deleteKbMessages)(_this18.activeSessionId, ids);\n\n case 14:\n _this18.messages = _this18.messages.filter(function (m) {\n return ids.indexOf(m.id) === -1;\n });\n\n _this18.exitSelectMode();\n\n _this18.$message.success('删除成功');\n\n _this18.refreshSessionList();\n\n _context11.next = 24;\n break;\n\n case 20:\n _context11.prev = 20;\n _context11.t1 = _context11[\"catch\"](11);\n console.error('批量删除消息失败', _context11.t1);\n\n _this18.$message.error('删除失败');\n\n case 24:\n case \"end\":\n return _context11.stop();\n }\n }\n }, _callee11, null, [[3, 8], [11, 20]]);\n }))();\n },\n handleResetSession: function handleResetSession() {\n var _this19 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee12() {\n return regeneratorRuntime.wrap(function _callee12$(_context12) {\n while (1) {\n switch (_context12.prev = _context12.next) {\n case 0:\n if (!_this19.activeSessionId) {\n _context12.next = 8;\n break;\n }\n\n _context12.prev = 1;\n _context12.next = 4;\n return (0, _kbChat.closeKbSession)(_this19.activeSessionId);\n\n case 4:\n _context12.next = 8;\n break;\n\n case 6:\n _context12.prev = 6;\n _context12.t0 = _context12[\"catch\"](1);\n\n case 8:\n _context12.next = 10;\n return _this19.autoCreateSession();\n\n case 10:\n case \"end\":\n return _context12.stop();\n }\n }\n }, _callee12, null, [[1, 6]]);\n }))();\n },\n handleSend: function handleSend(quickText) {\n var _this20 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee13() {\n var text, requestSessionId, requestId, msgKey, msgIdx, streamMsg, isCurrentStream, answerBuffer, finalContent, streamDone, url;\n return regeneratorRuntime.wrap(function _callee13$(_context13) {\n while (1) {\n switch (_context13.prev = _context13.next) {\n case 0:\n text = typeof quickText === 'string' ? quickText.trim() : _this20.inputText.trim();\n\n if (!(!text || !_this20.activeSessionId || _this20.streaming)) {\n _context13.next = 3;\n break;\n }\n\n return _context13.abrupt(\"return\");\n\n case 3:\n _this20.exitSelectMode();\n\n _this20.inputText = '';\n requestSessionId = _this20.activeSessionId;\n requestId = ++_this20.streamRequestId;\n\n _this20.clearTypeTimer();\n\n _this20.messages.push({\n role: 'user',\n content: text\n });\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n }); // 流式消息:作为 messages 中的对象(带 _key/loading),与 ChatDetail 一致\n\n\n msgKey = 'stream_' + requestId;\n msgIdx = _this20.messages.push({\n role: 'assistant',\n content: '',\n thinking: '',\n toolCalls: [],\n loading: true,\n _key: msgKey\n }) - 1;\n streamMsg = _this20.messages[msgIdx];\n _this20.streamMsg = streamMsg;\n _this20.streaming = true;\n\n isCurrentStream = function isCurrentStream() {\n return requestId === _this20.streamRequestId && requestSessionId === _this20.activeSessionId && _this20.streamMsg === streamMsg;\n }; // SSE 只把数据累积到 answerBuffer。typeTimer(30ms) 节流按「完整换行」逐行把内容\n // 推进到 msg.content:每个 tick 最多只推进「到下一个 \\n 为止」的一行,未完成的尾行\n // 保持不动;配合 renderStreaming(breaks:true) 实现「一行显示一行」。\n // 不是打字机模式(不逐字)、也不是一次性整段渲染(不会一出就是全文)。\n\n\n answerBuffer = '';\n finalContent = '';\n streamDone = false;\n _this20._typeTimer = setInterval(function () {\n if (!isCurrentStream()) return;\n var msg = _this20.messages[msgIdx];\n var source = finalContent || answerBuffer;\n var curLen = msg._prevReveal || 0;\n var nextLen = curLen; // 找到「当前已揭示进度之后」的第一条完整换行,仅推进这一行\n\n var nl = source.indexOf('\\n', curLen);\n\n if (nl !== -1) {\n nextLen = nl + 1;\n } else if (streamDone) {\n // 流结束且没有更多换行:最后一段未以换行结尾的内容一次性露出(此时即完整一行)\n nextLen = source.length;\n }\n\n if (nextLen > curLen) {\n msg._prevReveal = nextLen;\n msg.content = source.substring(0, nextLen);\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n } // 流结束且所有内容都已逐行露出 → 收尾\n\n\n if (streamDone && nextLen >= source.length) {\n _this20.finishStream(); // 后端已在此刻落库,回读一次补全消息 id(单条删除需要)。\n // 落库延迟偶发较大(10 × 800ms ≈ 8s),给足重试次数,\n // 避免「删除」按钮偶现不出现、必须重新进入会话才恢复(见 syncMessageIds 注释)。\n\n\n _this20.syncMessageIds(10);\n }\n }, 30);\n url = (0, _kbChat.createKbStreamUrl)(_this20.activeSessionId, text);\n _this20.eventSource = new EventSource(url);\n\n _this20.eventSource.addEventListener('tool_result', function (e) {\n if (!isCurrentStream() || streamDone) return;\n\n if (e.data && _this20.messages[msgIdx]) {\n if (!_this20.messages[msgIdx].toolCalls) _this20.messages[msgIdx].toolCalls = [];\n\n _this20.messages[msgIdx].toolCalls.push(e.data);\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n }\n });\n\n _this20.eventSource.addEventListener('answer', function (e) {\n if (!isCurrentStream() || streamDone) return;\n answerBuffer += e.data;\n });\n\n _this20.eventSource.addEventListener('agent_result', function (e) {\n if (!isCurrentStream() || streamDone) return; // 与档案君(AiChat.vue)对齐:AGENT_RESULT 为最终真相源。\n // 只记录 finalContent 并结束流,不打断已在逐行推进的 content,交给 typeTimer 以 target 推进/校正。\n\n if (e.data) finalContent = e.data;\n streamDone = true;\n\n _this20.closeEventSource();\n });\n\n _this20.eventSource.addEventListener('msgKey', function (e) {\n if (!isCurrentStream()) return;\n\n if (e.data && _this20.messages[msgIdx]) {\n _this20.$set(_this20.messages[msgIdx], 'msgKey', e.data);\n }\n });\n\n _this20.eventSource.addEventListener('reasoning', function (e) {\n if (!isCurrentStream() || streamDone) return;\n streamMsg.thinking += e.data;\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n });\n\n _this20.eventSource.addEventListener('reasoning_result', function (e) {\n if (!isCurrentStream() || streamDone) return;\n streamMsg.thinking = e.data;\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n });\n\n _this20.eventSource.addEventListener('error', function () {\n if (!isCurrentStream() || streamDone) return;\n if (!answerBuffer) answerBuffer = '❌ 连接中断';\n streamDone = true;\n\n _this20.closeEventSource();\n });\n\n case 29:\n case \"end\":\n return _context13.stop();\n }\n }\n }, _callee13);\n }))();\n },\n handleStop: function handleStop() {\n this.streamRequestId++;\n\n if (this.activeSessionId) {\n (0, _kbChat.stopKbStream)(this.activeSessionId).catch(function () {});\n }\n\n this.finishStream(); // 手动停止后 typeTimer 被清掉,收尾处的 syncMessageIds 永远不会执行,\n // 该条回答就一直没有 id →「删除」按钮不出现(偶现的典型来源)。\n // 这里主动补一次(带重试,等后端把已生成内容落库)。\n\n this.syncMessageIds(10);\n },\n finishStream: function finishStream() {\n var _this21 = this;\n\n this.clearTypeTimer();\n\n if (this.streamMsg) {\n this.$delete(this.contentCache, this.streamMsg._key);\n this.streamMsg.loading = false;\n this.streamMsg = null;\n }\n\n this.closeEventSource();\n this.streaming = false;\n this.refreshSessionList();\n this.$nextTick(function () {\n return _this21.scrollToBottom();\n });\n },\n\n /** 流式结束后刷新会话列表(更新标题/最后消息) */\n refreshSessionList: function refreshSessionList() {\n var _this22 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee14() {\n var list;\n return regeneratorRuntime.wrap(function _callee14$(_context14) {\n while (1) {\n switch (_context14.prev = _context14.next) {\n case 0:\n if (_this22.companyId) {\n _context14.next = 2;\n break;\n }\n\n return _context14.abrupt(\"return\");\n\n case 2:\n _context14.prev = 2;\n _context14.next = 5;\n return (0, _kbChat.getKbSessionList)(_this22.companyId);\n\n case 5:\n _context14.t0 = _context14.sent;\n\n if (_context14.t0) {\n _context14.next = 8;\n break;\n }\n\n _context14.t0 = [];\n\n case 8:\n list = _context14.t0;\n _this22.sessionList = list;\n _context14.next = 15;\n break;\n\n case 12:\n _context14.prev = 12;\n _context14.t1 = _context14[\"catch\"](2);\n console.error('刷新会话列表失败', _context14.t1);\n\n case 15:\n case \"end\":\n return _context14.stop();\n }\n }\n }, _callee14, null, [[2, 12]]);\n }))();\n },\n clearTypeTimer: function clearTypeTimer() {\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n },\n closeEventSource: function closeEventSource() {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n },\n copyText: function copyText(text) {\n if (navigator.clipboard) {\n navigator.clipboard.writeText(text);\n }\n },\n scrollToBottom: function scrollToBottom() {\n var el = this.$refs.messagesRef;\n\n if (el) {\n el.scrollTop = el.scrollHeight;\n }\n }\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1015
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _logo = _interopRequireDefault(__webpack_require__(/*! @/assets/logo.png */ \"./src/assets/logo.png\"));\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.mjs\"));\n\nvar _kbChat = __webpack_require__(/*! ./api/kbChat */ \"./src/lib/knowledgeBase/api/kbChat.js\");\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\nvar _native = __webpack_require__(/*! ./native.js */ \"./src/lib/knowledgeBase/native.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar AI_AVATAR_URL = 'https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/doc_query.png'; // 与 ChatDetail 使用相同的 Markdown 配置\n\nvar md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default = {\n name: 'KbChatMain',\n props: {\n companyId: {\n type: String,\n default: ''\n },\n userno: {\n type: String,\n default: ''\n },\n imAccount: {\n type: [String, Object],\n default: ''\n },\n userName: {\n type: String,\n default: ''\n },\n // 是否移动端:由父级(KnowledgeBaseMain)传入;未传时组件自身按窗口宽度判断\n isMobile: {\n type: Boolean,\n default: null\n }\n },\n data: function data() {\n // 首帧前计算是否移动端,避免 H5 先显示 PC 布局再切换的闪烁(与 AI 助理一致)\n // 宿主没有 Element UI 时(H5/平板横屏)一律走自绘 UI 分支,避免 el-* 未知元素报错\n var _isMobile = typeof window !== 'undefined' && (window.innerWidth <= 768 || !(0, _native.hasElementUI)(this));\n\n return {\n initDone: false,\n activeSessionId: '',\n sessionList: [],\n // 默认折叠侧边栏:避免新用户进入时先展开再合住的闪烁,\n // 加载出会话后再按规则决定是否展开\n sidebarCollapsed: true,\n messages: [],\n inputText: '',\n streaming: false,\n streamMsg: null,\n showToolInfo: false,\n contentCache: {},\n eventSource: null,\n streamRequestId: 0,\n suggestedQuestions: [],\n // 消息多选模式(豆包式):PC 由消息操作区「删除/多选」进入,移动端长按气泡弹出菜单进入\n selectMode: false,\n // 已选中的消息 id 列表(后端主键)\n selectedMsgIds: [],\n // 当前公司是否没有知识库文档(无资料时展示遮罩)\n noDocument: false,\n // 移动端状态(仿 AI 助理):showH5List=true 显示会话列表页,false 显示聊天详情页\n localIsMobile: _isMobile,\n showH5List: _isMobile,\n // 移动端会话「…」弹出的操作单\n mobileAction: null,\n // 移动端长按气泡弹出的消息操作单(复制 / 删除 / 多选)\n msgAction: null,\n // 移动端侧滑手势(参考 AI 助理):记录起手坐标与是否已触发\n _swipeStartX: 0,\n _swipeStartY: 0,\n _swipeHandled: false,\n _swipeFromLeftEdge: false,\n _swipeFromRightEdge: false,\n _swipeFromEdge: false\n };\n },\n computed: {\n /** 实际是否移动端:优先用父级 prop,未传则用自身窗口判断 */\n isMobileActive: function isMobileActive() {\n return this.isMobile !== null ? this.isMobile : this.localIsMobile;\n },\n aiAvatar: function aiAvatar() {\n return AI_AVATAR_URL;\n },\n userAvatar: function userAvatar() {\n var account = this.imAccount;\n console.log('KbChatMain - imAccount 原始值:', account); // 尝试解析字符串为对象\n\n if (typeof account === 'string') {\n try {\n account = JSON.parse(account);\n console.log('KbChatMain - imAccount 解析后:', account);\n } catch (e) {\n // 如果解析失败,检查是否是纯 URL 字符串\n if (account.trim() && (account.includes('http://') || account.includes('https://'))) {\n console.log('KbChatMain - imAccount 是 URL:', account.trim());\n return account.trim();\n }\n }\n } // 如果是对象,尝试多个可能的头像字段(优先使用 headImg)\n\n\n if (account && (0, _typeof2.default)(account) === 'object') {\n var avatarFields = ['headImg', 'headPortrait', 'avatar', 'avatarUrl', 'headImage', 'photoUrl', 'portrait', 'headUrl', 'userHeadUrl', 'userAvatar'];\n\n for (var _i = 0, _avatarFields = avatarFields; _i < _avatarFields.length; _i++) {\n var field = _avatarFields[_i];\n\n if (account[field] && String(account[field]).trim()) {\n console.log(\"KbChatMain - \\u627E\\u5230\\u5934\\u50CF\\u5B57\\u6BB5 \".concat(field, \":\"), account[field]);\n return String(account[field]).trim();\n }\n }\n }\n\n console.log('KbChatMain - 使用默认头像');\n return _logo.default;\n },\n // 是否有真实头像(区别于默认 fallback 图片)\n hasUserAvatar: function hasUserAvatar() {\n return this.userAvatar && this.userAvatar !== _logo.default;\n },\n // 无头像时的文字头像(与 zjkjAvatar 一致:取名字最后两个字)\n userAvatarText: function userAvatarText() {\n var name = this.userName || '';\n return name ? name.substring(name.length - 2) : '';\n },\n // 可删除的消息 id(仅后端已落库、带回 id 的消息可删)\n deletableMsgIds: function deletableMsgIds() {\n return this.messages.filter(function (m) {\n return m.id;\n }).map(function (m) {\n return m.id;\n });\n },\n // 是否已全选\n isAllSelected: function isAllSelected() {\n return this.deletableMsgIds.length > 0 && this.selectedMsgIds.length === this.deletableMsgIds.length;\n }\n },\n watch: {\n companyId: function companyId() {\n this.initDone = false;\n this.finishStream();\n this.loadSuggestedQuestions();\n this.checkKbDocuments();\n this.initSession();\n },\n userno: function userno() {\n this.initDone = false;\n this.finishStream();\n this.activeSessionId = '';\n this.messages = [];\n this.initSession();\n },\n // 移动端进入时默认先显示会话列表页(仿 AI 助理)\n isMobileActive: function isMobileActive(val) {\n if (val) {\n // 无资料时遮罩只在详情页可见(列表页 .kb-chat-main 被隐藏),故保持停在详情页\n this.showH5List = !this.noDocument;\n this.mobileAction = null;\n }\n }\n },\n mounted: function mounted() {\n var _this = this;\n\n // 独立使用(父级未传 isMobile)时自检窗口宽度\n this._onResize = this.handleResize;\n window.addEventListener('resize', this._onResize, {\n passive: true\n }); // Android WebView 兼容:在 document 级别监听 touchmove/touchend,\n // 避免可滚动子元素消费事件后不冒泡到父级(参考 AI 助理)\n\n this._swipeMoveHandler = this._onSwipeMove;\n this._swipeEndHandler = this._onSwipeEnd;\n this._popStateHandler = this._onPopState;\n document.addEventListener('touchmove', this._swipeMoveHandler, {\n passive: true\n });\n document.addEventListener('touchend', this._swipeEndHandler, {\n passive: true\n }); // 拦截 Android 系统返回手势:通过 history.pushState + popstate\n\n window.addEventListener('popstate', this._popStateHandler); // 移动端首屏为会话列表页,推入一条历史状态供系统返回拦截\n\n if (this.isMobileActive) {\n history.pushState({\n page: 'kbChatList'\n }, '');\n }\n\n this.initSession();\n this.checkKbDocuments(); // v-show 隐藏期间加载的历史消息无法滚动(scrollHeight 为 0),\n // 监听组件由隐藏变为可见时,滚动到最新问答;\n // 同时每次变为可见时刷新\"猜您想问\"(管理端可能刚更新过)\n\n if (typeof IntersectionObserver !== 'undefined' && this.$el) {\n this._visibilityObserver = new IntersectionObserver(function (entries) {\n if (entries.some(function (e) {\n return e.isIntersecting;\n })) {\n _this.loadSuggestedQuestions(); // 管理端可能刚上传/删除了文档,重新校验遮罩状态\n\n\n _this.checkKbDocuments();\n\n if (_this.messages.length) {\n _this.$nextTick(function () {\n return _this.scrollToBottom();\n });\n }\n }\n });\n\n this._visibilityObserver.observe(this.$el);\n } else {\n this.loadSuggestedQuestions();\n }\n },\n beforeDestroy: function beforeDestroy() {\n if (this._visibilityObserver) {\n this._visibilityObserver.disconnect();\n\n this._visibilityObserver = null;\n }\n\n if (this._onResize) {\n window.removeEventListener('resize', this._onResize);\n }\n\n if (this._swipeMoveHandler) {\n document.removeEventListener('touchmove', this._swipeMoveHandler);\n }\n\n if (this._swipeEndHandler) {\n document.removeEventListener('touchend', this._swipeEndHandler);\n }\n\n if (this._popStateHandler) {\n window.removeEventListener('popstate', this._popStateHandler);\n }\n\n this.streamRequestId++;\n this.closeEventSource();\n this.clearTypeTimer();\n this.onMsgTouchEnd();\n this.contentCache = {};\n },\n methods: {\n // 独立使用时自检移动端(父级已传 isMobile 时忽略)\n handleResize: function handleResize() {\n var now = typeof window !== 'undefined' && (window.innerWidth <= 768 || !(0, _native.hasElementUI)(this));\n this.localIsMobile = now;\n },\n\n /**\n * 用户消息按纯文本渲染(转义 HTML + 保留换行)。\n * 不能用 markdown 渲染用户输入:行首的「33.」「1.」会被当作有序列表标记、\n * 「- 」「* 」当无序列表标记、「---」当分隔线,标记本身不显示,\n * 导致仅输入「33.」时气泡内一片空白(内容看起来\"消失\")。\n * 用户原文需原样可见,故不做任何 markdown 语义解析。\n */\n renderUserContent: function renderUserContent(text) {\n if (!text) return '';\n return this.escapeHtml(text).replace(/\\n/g, '<br>');\n },\n renderMarkdown: function renderMarkdown(text) {\n if (!text) return '';\n\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n // 与 ChatDetail 一致的增量渲染:完整行走 markdown、未完成尾行转义拼接,\n // 借助 markdown-it breaks:true 实现逐行浮现效果。\n renderStreaming: function renderStreaming(text, msgKey) {\n if (!text) return '';\n var cache = this.contentCache[msgKey];\n var lines = text.split('\\n');\n var lastLineIdx = lines.length - 1;\n\n if (!cache) {\n try {\n var html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html: html,\n fullText: text\n });\n return html;\n } catch (_unused) {\n return this.escapeHtml(text);\n }\n }\n\n if (text === cache.fullText) return cache.html;\n cache.fullText = text; // 行数没变,仅末尾追加字符,直接拼接尾行\n\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n } // 行数变少/文本回退截断,全量重渲染\n\n\n if (lastLineIdx < cache.renderedLines) {\n try {\n var fullLines = lines.slice(0, lastLineIdx);\n var newBaseHtml = '';\n\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n\n var _lastLine = this.escapeHtml(lines[lastLineIdx]);\n\n return cache.html + (_lastLine || '');\n } catch (_unused2) {\n return this.escapeHtml(text);\n }\n } // 新增完整行\n\n\n if (cache.renderedLines < lastLineIdx) {\n var allFullLines = lines.slice(0, lastLineIdx);\n var _newBaseHtml = '';\n\n if (allFullLines.length > 0) {\n _newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n\n cache.html = _newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n\n var lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n escapeHtml: function escapeHtml(text) {\n if (!text) return '';\n var map = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return String(text).replace(/[&<>\"']/g, function (c) {\n return map[c];\n });\n },\n autoResizeTextarea: function autoResizeTextarea() {\n var ta = this.$refs.taRef;\n if (!ta) return; // 移动端从单行起增高(与 38px 最小高度一致),PC 保持 100px 基准\n\n var baseH = this.isMobileActive ? 38 : 100;\n var maxH = this.isMobileActive ? 120 : 180;\n ta.style.height = \"\".concat(baseH, \"px\");\n var realH = ta.scrollHeight;\n\n if (realH > baseH) {\n ta.style.height = \"\".concat(Math.min(realH, maxH), \"px\");\n }\n },\n newLine: function newLine(e) {\n var _this2 = this;\n\n e.preventDefault();\n var ta = this.$refs.taRef;\n var start = ta.selectionStart;\n var end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(function () {\n ta.selectionStart = ta.selectionEnd = start + 1;\n\n _this2.autoResizeTextarea();\n });\n },\n\n /**\n * 加载\"猜您想问\"问题(按公司,所有用户可见)\n */\n loadSuggestedQuestions: function loadSuggestedQuestions() {\n var _this3 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var list;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (_this3.companyId) {\n _context.next = 3;\n break;\n }\n\n _this3.suggestedQuestions = [];\n return _context.abrupt(\"return\");\n\n case 3:\n _context.prev = 3;\n _context.next = 6;\n return (0, _knowledgeBase.getSuggestedQuestions)(_this3.companyId);\n\n case 6:\n _context.t0 = _context.sent;\n\n if (_context.t0) {\n _context.next = 9;\n break;\n }\n\n _context.t0 = [];\n\n case 9:\n list = _context.t0;\n _this3.suggestedQuestions = list.map(function (q) {\n return q.questionText;\n });\n _context.next = 16;\n break;\n\n case 13:\n _context.prev = 13;\n _context.t1 = _context[\"catch\"](3);\n console.error('加载猜您想问失败', _context.t1);\n\n case 16:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[3, 13]]);\n }))();\n },\n\n /**\n * 检查当前公司是否有「已索引」的知识库文档,无可用资料时展示遮罩提示\n * 仅已索引(indexStatus === 1)的文档才能被检索,未索引/索引中/索引失败都不算\n */\n checkKbDocuments: function checkKbDocuments() {\n var _this4 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var data, isEmpty;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n if (_this4.companyId) {\n _context2.next = 3;\n break;\n }\n\n _this4.noDocument = false;\n return _context2.abrupt(\"return\");\n\n case 3:\n _context2.prev = 3;\n _context2.next = 6;\n return (0, _knowledgeBase.getFileList)({\n companyId: _this4.companyId,\n indexStatus: 1,\n pageNum: 1,\n pageSize: 1\n });\n\n case 6:\n data = _context2.sent;\n isEmpty = (data && data.total || 0) === 0;\n _this4.noDocument = isEmpty; // 无资料时把会话固定回第一个\n\n if (isEmpty) {\n _this4.lockFirstSession(); // 无资料遮罩位于聊天详情页的 .kb-chat-main 内,而移动端首屏是会话列表页\n // (h5-list-mode 下 .kb-chat-main 被 display:none 隐藏),若停留在列表页\n // 则遮罩永远不可见;故无资料时强制切到详情页把遮罩展示出来\n\n\n if (_this4.isMobileActive) {\n _this4.showH5List = false;\n history.pushState({\n page: 'kbChatDetail'\n }, '');\n }\n }\n\n _context2.next = 16;\n break;\n\n case 12:\n _context2.prev = 12;\n _context2.t0 = _context2[\"catch\"](3);\n // 查询失败(如无管理权限)时不展示遮罩,避免误拦截\n console.error('检查知识库文档失败', _context2.t0);\n _this4.noDocument = false;\n\n case 16:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[3, 12]]);\n }))();\n },\n initSession: function initSession() {\n var _this5 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var list;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n if (_this5.companyId) {\n _context3.next = 2;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 2:\n if (!(_this5.initDone && (_this5.messages.length || _this5.streaming || _this5.inputText))) {\n _context3.next = 4;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 4:\n _this5.initDone = false;\n _context3.prev = 5;\n _context3.next = 8;\n return (0, _kbChat.getKbSessionList)(_this5.companyId);\n\n case 8:\n _context3.t0 = _context3.sent;\n\n if (_context3.t0) {\n _context3.next = 11;\n break;\n }\n\n _context3.t0 = [];\n\n case 11:\n list = _context3.t0;\n _this5.sessionList = list;\n\n if (!(list.length > 0)) {\n _context3.next = 19;\n break;\n }\n\n // 只有 1 个会话且无消息(新用户首次进入自动生成的空会话)时,\n // 默认折叠侧边栏只显示聊天主界面;在 selectSession 前同步设置,\n // 避免「先展开再合住」的闪烁\n _this5.sidebarCollapsed = list.length === 1 && !list[0].lastContent;\n _context3.next = 17;\n return _this5.selectSession(list[0].sessionId);\n\n case 17:\n _context3.next = 21;\n break;\n\n case 19:\n _context3.next = 21;\n return _this5.autoCreateSession();\n\n case 21:\n _context3.next = 28;\n break;\n\n case 23:\n _context3.prev = 23;\n _context3.t1 = _context3[\"catch\"](5);\n console.error('获取会话列表失败,尝试自动创建', _context3.t1);\n _context3.next = 28;\n return _this5.autoCreateSession();\n\n case 28:\n _context3.prev = 28;\n _this5.initDone = true;\n return _context3.finish(28);\n\n case 31:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[5, 23, 28, 31]]);\n }))();\n },\n autoCreateSession: function autoCreateSession() {\n var _this6 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n var data;\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n if (_this6.companyId) {\n _context4.next = 3;\n break;\n }\n\n console.warn('[KbChat] 无法创建会话:companyId 为空');\n return _context4.abrupt(\"return\");\n\n case 3:\n console.log('[KbChat] 创建会话,companyId:', _this6.companyId);\n _context4.prev = 4;\n _context4.next = 7;\n return (0, _kbChat.createKbSession)(_this6.companyId);\n\n case 7:\n data = _context4.sent;\n console.log('[KbChat] 会话创建成功,sessionId:', data.sessionId);\n\n _this6.finishStream();\n\n _this6.activeSessionId = data.sessionId;\n _this6.streamRequestId++;\n _this6.messages = [];\n _this6.sessionList = [{\n sessionId: data.sessionId,\n title: '',\n lastContent: ''\n }].concat(_this6.sessionList || []); // 新建会话时保持侧边栏展开,方便继续切换会话\n\n _this6.sidebarCollapsed = false;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n\n _context4.next = 21;\n break;\n\n case 18:\n _context4.prev = 18;\n _context4.t0 = _context4[\"catch\"](4);\n console.error('自动创建会话失败', _context4.t0);\n\n case 21:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[4, 18]]);\n }))();\n },\n\n /** 新建会话(豆包式「新对话」) */\n handleNewSession: function handleNewSession() {\n var _this7 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n if (!_this7.noDocument) {\n _context5.next = 2;\n break;\n }\n\n return _context5.abrupt(\"return\");\n\n case 2:\n if (_this7.streaming) _this7.handleStop();\n _context5.next = 5;\n return _this7.autoCreateSession();\n\n case 5:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5);\n }))();\n },\n\n /** 切换会话 */\n handleSelectSession: function handleSelectSession(item) {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6() {\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n if (!(!item || !item.sessionId)) {\n _context6.next = 2;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 2:\n if (!_this8.noDocument) {\n _context6.next = 5;\n break;\n }\n\n _this8.lockFirstSession();\n\n return _context6.abrupt(\"return\");\n\n case 5:\n if (!(item.sessionId === _this8.activeSessionId && _this8.messages.length)) {\n _context6.next = 8;\n break;\n }\n\n if (_this8.isMobileActive) {\n _this8.showH5List = false;\n history.pushState({\n page: 'kbChatDetail'\n }, '');\n\n _this8.$nextTick(function () {\n return _this8.scrollToBottom();\n });\n }\n\n return _context6.abrupt(\"return\");\n\n case 8:\n _context6.next = 10;\n return _this8.selectSession(item.sessionId);\n\n case 10:\n // 移动端:选中后跳到聊天详情页\n if (_this8.isMobileActive) {\n _this8.showH5List = false; // 推入历史状态,使 Android 系统返回手势先触发 popstate 被拦截\n\n history.pushState({\n page: 'kbChatDetail'\n }, ''); // 列表页时聊天区被 display:none,scrollHeight 为 0 导致上面的滚动无效;\n // 切到详情页、容器可见后需再滚一次,默认定位到最新消息\n\n _this8.$nextTick(function () {\n return _this8.scrollToBottom();\n });\n }\n\n case 11:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6);\n }))();\n },\n // 企管版本菜单排序联调开发完成提测\n // \n\n /** 返回宿主 App 原生页面(参考 AI 助理 handleNativeBack) */\n handleNativeBack: function handleNativeBack() {\n if (this.$hybrid && typeof this.$hybrid.closePage === 'function') {\n this.$hybrid.closePage();\n } else if (window.history.length > 1) {\n window.history.back();\n }\n },\n\n /** 会话列表页返回按钮:返回原生页面 */\n handleSidebarBack: function handleSidebarBack() {\n this.handleNativeBack();\n },\n // ── H5 侧滑手势(参考 AI 助理)──\n // 从左/右屏幕边缘起手横滑触发返回,避免页面内部横向滚动被误判\n onSwipeTouchStart: function onSwipeTouchStart(e) {\n if (!this.isMobileActive) return;\n var t = e.touches[0];\n this._swipeStartX = t.clientX;\n this._swipeStartY = t.clientY;\n this._swipeHandled = false;\n var edge = Math.min(56, Math.round(window.innerWidth * 0.09));\n this._swipeFromLeftEdge = t.clientX <= edge;\n this._swipeFromRightEdge = t.clientX >= window.innerWidth - edge;\n this._swipeFromEdge = this._swipeFromLeftEdge || this._swipeFromRightEdge;\n },\n _swipeShouldTrigger: function _swipeShouldTrigger(diffX, diffY) {\n if (!this._swipeFromEdge) return false; // 从左边缘 → 右滑;从右边缘 → 左滑\n\n var directionOk = this._swipeFromLeftEdge && diffX >= 60 || this._swipeFromRightEdge && diffX <= -60;\n if (!directionOk) return false; // 水平距离明显大于垂直距离(排除上下滚动干扰)\n\n return Math.abs(diffX) >= Math.abs(diffY) * 2;\n },\n _onSwipeMove: function _onSwipeMove(e) {\n if (!this.isMobileActive || this._swipeHandled) return;\n if (!e.touches || !e.touches.length) return;\n var diffX = e.touches[0].clientX - this._swipeStartX;\n var diffY = e.touches[0].clientY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n // touchend 兜底,兼容不支持 touchmove 的场景\n _onSwipeEnd: function _onSwipeEnd(e) {\n if (!this.isMobileActive || this._swipeHandled) return;\n if (!e.changedTouches || !e.changedTouches.length) return;\n var diffX = e.changedTouches[0].clientX - this._swipeStartX;\n var diffY = e.changedTouches[0].clientY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n\n /** 侧滑触发:列表页 → 返回原生页面;详情页 → 回会话列表页 */\n _triggerSwipeAction: function _triggerSwipeAction() {\n if (this.showH5List) {\n this.handleNativeBack();\n } else {\n this.handleMobileBack();\n }\n },\n\n /**\n * 拦截 Android 系统返回手势(history.pushState + popstate)\n * WebView 先回退历史栈触发 popstate,这里拦截后导航到列表页,避免 Activity 被直接关闭\n */\n _onPopState: function _onPopState() {\n if (!this.isMobileActive) return;\n\n if (!this.showH5List) {\n // 详情页 → 返回会话列表页\n this.showH5List = true;\n history.pushState({\n page: 'kbChatList'\n }, '');\n } else {\n // 列表页 → 直接关闭 WebView 返回原生页面\n this.handleNativeBack();\n }\n },\n\n /** 移动端详情页返回:无资料遮罩时返回原生页面;否则回到会话列表页 */\n handleMobileBack: function handleMobileBack() {\n // 无资料遮罩期间无法进入会话,返回到宿主 App 原生页面\n if (this.noDocument) {\n this.handleNativeBack();\n return;\n } // 正常情况:回到会话列表页\n\n\n this.showH5List = true;\n },\n\n /** 无资料遮罩期间:把当前会话固定回列表中的第一个 */\n lockFirstSession: function lockFirstSession() {\n var first = this.sessionList[0];\n\n if (first && first.sessionId && first.sessionId !== this.activeSessionId) {\n this.selectSession(first.sessionId);\n }\n },\n\n /** 会话项「…」菜单:重命名 / 删除 */\n handleSessionMenu: function handleSessionMenu(cmd, item) {\n if (!item || !item.sessionId) return;\n\n if (cmd === 'rename') {\n this.handleRenameSession(item);\n } else if (cmd === 'delete') {\n this.handleDeleteSession(item);\n }\n },\n // ==================== 移动端会话操作单 ====================\n openMobileAction: function openMobileAction(item) {\n if (this.noDocument) return;\n this.mobileAction = item;\n },\n doMobileRename: function doMobileRename(item) {\n this.mobileAction = null;\n this.handleRenameSession(item);\n },\n doMobileDelete: function doMobileDelete(item) {\n this.mobileAction = null;\n this.handleDeleteSession(item);\n },\n\n /** 重命名会话 */\n handleRenameSession: function handleRenameSession(item) {\n var _this9 = this;\n\n if (!item || !item.sessionId) return;\n this.$prompt('请输入新的会话名称', '重命名会话', {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n inputValue: item.title || '',\n inputPattern: /\\S+/,\n inputErrorMessage: '名称不能为空'\n }).then(function (_ref) {\n var value = _ref.value;\n var title = (value || '').trim();\n if (!title) return;\n return (0, _kbChat.renameKbSession)(item.sessionId, title).then(function () {\n item.title = title;\n\n _this9.$message.success('重命名成功');\n });\n }).catch(function (err) {\n // 取消或校验失败,不提示错误\n if (err && err !== 'cancel' && err !== 'close') {\n console.error('重命名失败', err);\n\n _this9.$message.error('重命名失败');\n }\n });\n },\n\n /** 删除会话 */\n handleDeleteSession: function handleDeleteSession(item) {\n var _this10 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n if (!(!item || !item.sessionId)) {\n _context7.next = 2;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 2:\n if (!(_this10.sessionList.length <= 1)) {\n _context7.next = 5;\n break;\n }\n\n _this10.$message.warning('至少保留一个会话');\n\n return _context7.abrupt(\"return\");\n\n case 5:\n _context7.prev = 5;\n _context7.next = 8;\n return _this10.$confirm('删除后无法恢复,确定删除该会话吗?', '删除会话', {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n type: 'warning'\n });\n\n case 8:\n _context7.next = 13;\n break;\n\n case 10:\n _context7.prev = 10;\n _context7.t0 = _context7[\"catch\"](5);\n return _context7.abrupt(\"return\");\n\n case 13:\n _context7.prev = 13;\n _context7.next = 16;\n return (0, _kbChat.deleteKbSession)(item.sessionId);\n\n case 16:\n _this10.sessionList = (_this10.sessionList || []).filter(function (s) {\n return s.sessionId !== item.sessionId;\n });\n\n if (!(_this10.activeSessionId === item.sessionId)) {\n _context7.next = 28;\n break;\n }\n\n _this10.finishStream();\n\n _this10.activeSessionId = '';\n _this10.messages = [];\n\n if (!(_this10.sessionList.length > 0)) {\n _context7.next = 26;\n break;\n }\n\n _context7.next = 24;\n return _this10.selectSession(_this10.sessionList[0].sessionId);\n\n case 24:\n _context7.next = 28;\n break;\n\n case 26:\n _context7.next = 28;\n return _this10.autoCreateSession();\n\n case 28:\n _this10.$message.success('删除成功');\n\n _context7.next = 35;\n break;\n\n case 31:\n _context7.prev = 31;\n _context7.t1 = _context7[\"catch\"](13);\n console.error('删除会话失败', _context7.t1);\n\n _this10.$message.error('删除失败');\n\n case 35:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[5, 10], [13, 31]]);\n }))();\n },\n\n /** 会话列表时间格式化:今天显示时分,更早显示月日 */\n formatTime: function formatTime(time) {\n if (!time) return '';\n var d = new Date(time);\n if (isNaN(d.getTime())) return '';\n var now = new Date();\n var sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();\n\n var pad = function pad(n) {\n return n < 10 ? '0' + n : '' + n;\n };\n\n if (sameDay) {\n return pad(d.getHours()) + ':' + pad(d.getMinutes());\n }\n\n return pad(d.getMonth() + 1) + '-' + pad(d.getDate());\n },\n\n /** 后端消息 → 前端消息对象(保留 id/msgKey,删除功能需要) */\n mapMessage: function mapMessage(m, i) {\n var thinking = m.thinking || m.reasoning || m.reasoning_result || m.reasoningContent || m.reasoning_content || m.thinkingContent || m.thinking_content || '';\n return {\n id: m.id,\n msgKey: m.msgKey || m.msg_key || '',\n role: m.role,\n content: m.content || '',\n thinking: thinking,\n _key: m.createTime ? 'msg_' + m.createTime + '_' + i : 'msg_' + Date.now() + '_' + Math.random()\n };\n },\n selectSession: function selectSession(sessionId) {\n var _this11 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee8() {\n var raw, msgs;\n return regeneratorRuntime.wrap(function _callee8$(_context8) {\n while (1) {\n switch (_context8.prev = _context8.next) {\n case 0:\n if (_this11.streaming) _this11.handleStop();\n\n _this11.exitSelectMode();\n\n _this11.msgAction = null;\n _this11.activeSessionId = sessionId;\n _this11.messages = [];\n _this11.streamMsg = null;\n _context8.prev = 6;\n _context8.next = 9;\n return (0, _kbChat.getKbSessionMessages)(sessionId);\n\n case 9:\n raw = _context8.sent;\n msgs = Array.isArray(raw) ? raw : raw.messages || raw.records || raw.list || [];\n console.log('[KbChat] selectSession 回读', {\n sessionId: sessionId,\n rawIsArray: Array.isArray(raw),\n rawKeys: raw && !Array.isArray(raw) ? Object.keys(raw) : null,\n count: msgs.length,\n list: msgs.map(function (m) {\n return {\n id: m.id,\n role: m.role,\n content: String(m.content || '').slice(0, 25)\n };\n })\n });\n _this11.messages = _this11.dedupeMessages(msgs).map(function (m, i) {\n return _this11.mapMessage(m, i);\n });\n\n _this11.$nextTick(function () {\n return _this11.scrollToBottom();\n });\n\n _context8.next = 19;\n break;\n\n case 16:\n _context8.prev = 16;\n _context8.t0 = _context8[\"catch\"](6);\n console.error('加载消息失败', _context8.t0);\n\n case 19:\n case \"end\":\n return _context8.stop();\n }\n }\n }, _callee8, null, [[6, 16]]);\n }))();\n },\n\n /**\n * 后端回读消息去重。\n * 后端存在同一问答对被重复落库的情况(同一会话里出现两套相同的「问+答」),\n * 直接渲染会让用户看到重复问答。这里按「问答对」为单位去重:\n * 把消息切成组(用户问 + 其后的 AI 答为一组),整组内容完全相同的只保留第一次出现的。\n * 相比「只和上一条比」的相邻去重,能覆盖组与组之间被隔开、或 content 有空白差异的情况。\n */\n dedupeMessages: function dedupeMessages(msgs) {\n var _this12 = this;\n\n // 先切组:以 user 消息为一个新组的起点,其后连续的 assistant 归入同组\n var groups = [];\n msgs.forEach(function (m) {\n var role = _this12.normRole(m.role);\n\n if (role === 'user' || !groups.length) {\n groups.push([m]);\n } else {\n groups[groups.length - 1].push(m);\n }\n });\n var seen = {};\n var out = [];\n groups.forEach(function (group) {\n var signature = group.map(function (m) {\n return _this12.normRole(m.role) + \"\\x01\" + String(m.content || '').replace(/\\s+/g, '');\n }).join(\"\\x02\");\n if (signature && seen[signature]) return;\n if (signature) seen[signature] = true;\n out.push.apply(out, group);\n });\n return out;\n },\n\n /**\n * 内容比对用的归一化:压掉所有空白(含换行)差异。\n * 后端落库时常会把换行/多空格规范化(或存成 CRLF),只 trim 首尾的严格相等会漏匹配,\n * 漏匹配 = 消息拿不到 id = 「删除」按钮偶现不出现。\n */\n normContent: function normContent(text) {\n return String(text == null ? '' : text).replace(/\\s+/g, ' ').trim();\n },\n\n /**\n * 回读当前会话消息,补全后端持久化后生成的 id。\n * 流式回答期间消息只存在于本地数组(无 id),无法按 id 删除;\n * 流式结束后调用一次即可拿到 id。已全部有 id 时跳过,避免多余请求。\n *\n * 注意:不能直接用后端数组整体覆盖 this.messages。后端回读可能包含重复/多余记录\n * (如同一会话里用户消息被落库两次),若整体覆盖会把重复消息直接渲染出来。\n * 这里以本地数组为基准,只用后端记录按「角色 + 内容」顺序回填 id,保证界面条数不变。\n *\n * 「删除」按钮偶现丢失(id 补不全)的易失点,本函数逐一兜住:\n * 1) **AI 回答条的内容与后端存储对不上**:本地内容是流式拼接/agent_result 的结果,\n * 后端会做规范化甚至改写,靠「内容相等」匹配回答条天然不可靠 →\n * 改为优先用 SSE 下发的 `msgKey`(与后端记录一一对应)做精确匹配,\n * 这是「回答出来了却没有删除按钮」的主因(提问条没有 msgKey,仍走内容匹配);\n * 2) 后端落库延迟:有限次重试;重试期间**不能**因为开了新流(streaming)就直接 return,\n * 否则重试预算被静默吞掉,消息永远拿不到 id;\n * 3) 历史消息已带 id 时 cursor 必须同步前进,且同一条后端记录不能重复分配给多条本地消息:\n * 否则会拿到**别的消息的 id**(删错消息)或彻底匹配不上。\n */\n syncMessageIds: function syncMessageIds() {\n var _arguments = arguments,\n _this13 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee9() {\n var retries, sessionId, backendMsgs, scheduleRetry, raw, usedIds, isUsed, assign, cursor;\n return regeneratorRuntime.wrap(function _callee9$(_context9) {\n while (1) {\n switch (_context9.prev = _context9.next) {\n case 0:\n retries = _arguments.length > 0 && _arguments[0] !== undefined ? _arguments[0] : 0;\n\n if (_this13.activeSessionId) {\n _context9.next = 3;\n break;\n }\n\n return _context9.abrupt(\"return\");\n\n case 3:\n if (!(_this13.messages.length && _this13.messages.every(function (m) {\n return m.id;\n }))) {\n _context9.next = 5;\n break;\n }\n\n return _context9.abrupt(\"return\");\n\n case 5:\n sessionId = _this13.activeSessionId;\n backendMsgs = []; // 重试排期:仅在仍属于同一会话且仍有消息缺 id 时继续,避免污染其它会话\n\n scheduleRetry = function scheduleRetry() {\n var missing = _this13.messages.filter(function (m) {\n return !m.id;\n });\n\n if (!missing.length || sessionId !== _this13.activeSessionId) return;\n\n if (retries > 0) {\n setTimeout(function () {\n return _this13.syncMessageIds(retries - 1);\n }, 800);\n return;\n } // 重试耗尽仍有缺 id(表现为该条没有「删除」按钮)→ 留一条诊断日志,便于对照\n // 本地内容与后端内容到底差在哪(内容差异 / 后端未返回 msgKey / 落库仍没完成)\n\n\n console.warn('[KbChat] 重试耗尽仍有消息缺 id(该条不显示删除按钮)', {\n sessionId: sessionId,\n missing: missing.map(function (m) {\n return {\n role: m.role,\n msgKey: m.msgKey || '',\n snippet: String(m.content || '').slice(0, 60)\n };\n }),\n backend: backendMsgs.map(function (b) {\n return {\n id: b.id,\n role: b.role,\n msgKey: b.msgKey || b.msg_key || '',\n snippet: String(b.content || '').slice(0, 60)\n };\n })\n });\n };\n\n _context9.prev = 8;\n _context9.next = 11;\n return (0, _kbChat.getKbSessionMessages)(sessionId);\n\n case 11:\n raw = _context9.sent;\n\n if (!(sessionId !== _this13.activeSessionId)) {\n _context9.next = 14;\n break;\n }\n\n return _context9.abrupt(\"return\");\n\n case 14:\n backendMsgs = Array.isArray(raw) ? raw : raw.messages || raw.records || raw.list || []; // 已占用的后端 id:同一条后端记录不能分配给多条本地消息\n\n usedIds = _this13.messages.filter(function (m) {\n return m.id;\n }).map(function (m) {\n return String(m.id);\n });\n\n isUsed = function isUsed(b) {\n return usedIds.indexOf(String(b.id)) !== -1;\n };\n\n assign = function assign(m, b) {\n // 本地消息对象在 handleSend 中创建时未声明 id/msgKey,\n // 直接赋值 Vue 2 无法侦测新增属性,模板不会重渲染(删除按钮仍不出现);\n // 必须用 $set 才能让「删除」等依赖 msg.id 的 UI 即时刷新。\n _this13.$set(m, 'id', b.id);\n\n if (!m.msgKey) _this13.$set(m, 'msgKey', b.msgKey || b.msg_key || '');\n usedIds.push(String(b.id));\n }; // Pass 1:msgKey 精确匹配(AI 回答条)。SSE 的 msgKey 与后端记录一一对应,\n // 不受\"后端改写/规范化回答内容\"影响,是回答条最可靠的配对方式。\n\n\n _this13.messages.forEach(function (m) {\n if (m.id || !m.msgKey) return;\n var b = backendMsgs.find(function (x) {\n return x && x.id != null && !isUsed(x) && String(x.msgKey || x.msg_key || '') === String(m.msgKey);\n });\n if (b) assign(m, b);\n }); // Pass 2:角色 + 归一化内容 + cursor 顺序匹配(提问条无 msgKey,走内容匹配)\n\n\n cursor = 0;\n\n _this13.messages.forEach(function (m) {\n if (m.id) {\n // 已补全的消息:按 id 定位并推进 cursor(不做则 cursor 停留在 0)\n var idx = backendMsgs.findIndex(function (b, i) {\n return i >= cursor && String(b.id) === String(m.id);\n });\n if (idx >= 0) cursor = idx + 1;\n return;\n }\n\n var role = _this13.normRole(m.role);\n\n var key = _this13.normContent(m.content);\n\n for (var i = cursor; i < backendMsgs.length; i++) {\n var b = backendMsgs[i];\n if (isUsed(b)) continue;\n\n if (_this13.normRole(b.role) === role && _this13.normContent(b.content) === key) {\n assign(m, b);\n cursor = i + 1;\n return;\n }\n }\n });\n\n _context9.next = 26;\n break;\n\n case 23:\n _context9.prev = 23;\n _context9.t0 = _context9[\"catch\"](8);\n console.error('同步消息 id 失败', _context9.t0);\n\n case 26:\n scheduleRetry();\n\n case 27:\n case \"end\":\n return _context9.stop();\n }\n }\n }, _callee9, null, [[8, 23]]);\n }))();\n },\n // ==================== 消息多选 / 删除 ====================\n\n /** 进入多选模式(preselectMsg 传入时默认勾选该条所在的整组问答,对应豆包式「删除」入口) */\n enterSelectMode: function enterSelectMode(preselectMsg) {\n var _this14 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee10() {\n var preselectId, idx;\n return regeneratorRuntime.wrap(function _callee10$(_context10) {\n while (1) {\n switch (_context10.prev = _context10.next) {\n case 0:\n if (!(_this14.streaming || !_this14.messages.length)) {\n _context10.next = 2;\n break;\n }\n\n return _context10.abrupt(\"return\");\n\n case 2:\n // 点击条的后端持久化 id(模板上「删除」按钮仅在 msg.id 存在时渲染,故必有值)\n preselectId = preselectMsg ? preselectMsg.id : null; // 先回读消息确保每条都有 id(刚流式完成的消息本地没有 id)。\n // 回读只补 id、不重建数组,定位可用 id 或对象引用。\n\n _context10.next = 5;\n return _this14.syncMessageIds();\n\n case 5:\n _this14.selectMode = true;\n _this14.selectedMsgIds = [];\n idx = preselectId != null ? _this14.messages.findIndex(function (m) {\n return m.id === preselectId;\n }) : -1;\n if (idx === -1 && preselectMsg) idx = _this14.messages.indexOf(preselectMsg);\n\n if (idx !== -1) {\n // 默认勾选整组问答(用户问 + AI 答),便于一键删除\n _this14.selectedMsgIds = _this14.getQaPairIdsAt(idx);\n } // 滚动定位:让默认勾选的整组问答都进入视野(长回答会把用户问顶出屏幕)\n\n\n _this14.scrollSelectedIntoView();\n\n case 11:\n case \"end\":\n return _context10.stop();\n }\n }\n }, _callee10);\n }))();\n },\n\n /**\n * 把首个被选中的消息行滚动到容器内可见。\n * 选中行是「用户问 + AI 答」中的靠前一条,滚到它即能同时看到整组。\n * 若靠前那条已在视野内则不动,避免无谓跳动。\n */\n scrollSelectedIntoView: function scrollSelectedIntoView() {\n var _this15 = this;\n\n this.$nextTick(function () {\n var container = _this15.$refs.messagesRef;\n if (!container) return;\n var rows = container.querySelectorAll('.msg-row');\n if (!rows.length) return; // 找到第一个处于选中态、且带 id 的行(与 selectedMsgIds 顺序无关,取 DOM 顺序)\n\n var target = null;\n\n for (var i = 0; i < rows.length; i++) {\n var box = rows[i].querySelector('.msg-check-box.checked');\n\n if (box) {\n target = rows[i];\n break;\n }\n }\n\n if (!target) return;\n var cRect = container.getBoundingClientRect();\n var tRect = target.getBoundingClientRect(); // 目标行顶部已在容器可视区内则无需滚动\n\n if (tRect.top >= cRect.top && tRect.top < cRect.bottom) return; // 滚动到目标行顶部(留 8px 间距)\n\n container.scrollTop += tRect.top - cRect.top - 8;\n });\n },\n exitSelectMode: function exitSelectMode() {\n this.selectMode = false;\n this.selectedMsgIds = [];\n },\n isMsgSelected: function isMsgSelected(msg) {\n return !!msg.id && this.selectedMsgIds.indexOf(msg.id) !== -1;\n },\n\n /** 归一化 role,兼容后端可能返回的大小写/空白 */\n normRole: function normRole(role) {\n return String(role == null ? '' : role).trim().toLowerCase();\n },\n\n /**\n * 取与下标 idx 处消息成对的另一条:用户问 ↔ AI 答视为一组问答。\n * 只认紧邻的一条(问答对在列表里必然相邻),相邻非同组则不配对。\n * 不依赖 role 的具体拼写(user/assistant 的大小写、空白均容错)。\n */\n getQaPartnerAt: function getQaPartnerAt(idx) {\n if (idx < 0 || idx >= this.messages.length) return null;\n var cur = this.messages[idx];\n if (!cur) return null;\n var role = this.normRole(cur.role);\n if (role !== 'user' && role !== 'assistant') return null; // 只认紧邻的那一条:问答对在列表里必然相邻。\n // 若相邻消息不是异角色(例如回答已被删除),说明当前消息已无配对,\n // 不能继续向前/向后扫描,否则会误勾到上一个问答的回答。\n\n var neighbor = role === 'user' ? this.messages[idx + 1] : this.messages[idx - 1];\n if (!neighbor) return null;\n var neighborRole = this.normRole(neighbor.role);\n var expect = role === 'user' ? 'assistant' : 'user';\n return neighborRole === expect ? neighbor : null;\n },\n\n /** 下标 idx 处消息所在问答对的可删 id 列表(用户问 + AI 答) */\n getQaPairIdsAt: function getQaPairIdsAt(idx) {\n var ids = [];\n var cur = this.messages[idx];\n if (cur && cur.id) ids.push(cur.id);\n var partner = this.getQaPartnerAt(idx);\n if (partner && partner.id && ids.indexOf(partner.id) === -1) ids.push(partner.id);\n return ids;\n },\n\n /**\n * 勾选单条消息:勾上时连带把问答对里另一条一起勾上;\n * 取消时只取消当前这条,从而支持「先整组勾选、再单独取消一条」的单条选择。\n */\n toggleSelectMessage: function toggleSelectMessage(msg) {\n var _this16 = this;\n\n if (!msg.id) return;\n var i = this.selectedMsgIds.indexOf(msg.id);\n\n if (i === -1) {\n var idx = this.messages.indexOf(msg);\n var pairIds = idx === -1 ? [msg.id] : this.getQaPairIdsAt(idx);\n pairIds.forEach(function (id) {\n if (_this16.selectedMsgIds.indexOf(id) === -1) _this16.selectedMsgIds.push(id);\n });\n } else {\n this.selectedMsgIds.splice(i, 1);\n }\n },\n toggleSelectAll: function toggleSelectAll() {\n var ids = this.deletableMsgIds;\n this.selectedMsgIds = this.selectedMsgIds.length === ids.length ? [] : ids.slice();\n },\n\n /** 多选模式下点击整行即切换选中 */\n onMsgRowClick: function onMsgRowClick(msg) {\n // 长按弹出菜单后浏览器还会补一次 click,短暂屏蔽避免误触\n if (Date.now() < this._ignoreClickUntil) return;\n if (this.selectMode) this.toggleSelectMessage(msg);\n },\n\n /** 移动端长按气泡弹出消息操作单(豆包式:复制 / 删除 / 多选) */\n onMsgTouchStart: function onMsgTouchStart(msg, e) {\n var _this17 = this;\n\n if (!this.isMobileActive || this.selectMode) return;\n this._longPressMsg = msg; // 记录起点:Android 手指轻微抖动会持续触发 touchmove,\n // 旧逻辑「任何 touchmove 都取消」导致长按几乎不可能触发。\n\n var t = e && e.touches && e.touches[0];\n this._lpStartX = t ? t.clientX : 0;\n this._lpStartY = t ? t.clientY : 0;\n clearTimeout(this._longPressTimer);\n this._longPressTimer = setTimeout(function () {\n _this17._longPressTimer = null; // 用户正在长按选中文本(复制用),此时不弹菜单,避免抢走选择行为\n\n var sel = window.getSelection && window.getSelection();\n if (sel && String(sel).trim()) return;\n\n _this17.openMsgAction(_this17._longPressMsg);\n }, 500);\n },\n\n /** 移动端长按过程中仅当手指移动超过阈值才取消,避免 Android 抖动误取消 */\n onMsgTouchMove: function onMsgTouchMove(e) {\n if (!this._longPressTimer) return;\n var t = e && e.touches && e.touches[0];\n if (!t) return;\n var moved = Math.abs(t.clientX - (this._lpStartX || 0)) + Math.abs(t.clientY - (this._lpStartY || 0));\n if (moved > 12) this.onMsgTouchEnd();\n },\n\n /** Android 长按会触发系统 contextmenu,移动端拦截并改弹自定义操作单;PC 不受影响 */\n onMsgContextMenu: function onMsgContextMenu(e, msg) {\n if (!this.isMobileActive) return;\n e.preventDefault();\n if (this.selectMode) return;\n this.onMsgTouchEnd();\n this._longPressMsg = msg;\n this.openMsgAction(msg);\n },\n onMsgTouchEnd: function onMsgTouchEnd() {\n if (this._longPressTimer) {\n clearTimeout(this._longPressTimer);\n this._longPressTimer = null;\n }\n },\n openMsgAction: function openMsgAction(msg) {\n this.msgAction = msg; // 长按抬手后浏览器可能补发 click,短暂屏蔽,避免刚弹出的菜单被立即关闭\n\n this._ignoreClickUntil = Date.now() + 350;\n },\n closeMsgAction: function closeMsgAction() {\n if (Date.now() < this._ignoreClickUntil) return;\n this.msgAction = null;\n },\n\n /** 移动端消息操作单:复制 / 删除(删除进入多选模式,与豆包一致) */\n doMsgAction: function doMsgAction(action) {\n var msg = this.msgAction;\n this.msgAction = null;\n if (!msg) return;\n\n if (action === 'copy') {\n this.copyText(msg.content);\n } else if (action === 'delete') {\n this.enterSelectMode(msg);\n }\n },\n\n /** 批量删除选中的消息 */\n handleDeleteSelected: function handleDeleteSelected() {\n var _this18 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee11() {\n var ids;\n return regeneratorRuntime.wrap(function _callee11$(_context11) {\n while (1) {\n switch (_context11.prev = _context11.next) {\n case 0:\n if (!(!_this18.selectedMsgIds.length || !_this18.activeSessionId)) {\n _context11.next = 2;\n break;\n }\n\n return _context11.abrupt(\"return\");\n\n case 2:\n ids = _this18.selectedMsgIds.slice();\n _context11.prev = 3;\n _context11.next = 6;\n return _this18.$confirm(\"\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\uFF0C\\u786E\\u5B9A\\u5220\\u9664\\u9009\\u4E2D\\u7684 \".concat(ids.length, \" \\u6761\\u6D88\\u606F\\u5417\\uFF1F\"), '删除消息', {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n type: 'warning'\n });\n\n case 6:\n _context11.next = 11;\n break;\n\n case 8:\n _context11.prev = 8;\n _context11.t0 = _context11[\"catch\"](3);\n return _context11.abrupt(\"return\");\n\n case 11:\n _context11.prev = 11;\n _context11.next = 14;\n return (0, _kbChat.deleteKbMessages)(_this18.activeSessionId, ids);\n\n case 14:\n _this18.messages = _this18.messages.filter(function (m) {\n return ids.indexOf(m.id) === -1;\n });\n\n _this18.exitSelectMode();\n\n _this18.$message.success('删除成功');\n\n _this18.refreshSessionList();\n\n _context11.next = 24;\n break;\n\n case 20:\n _context11.prev = 20;\n _context11.t1 = _context11[\"catch\"](11);\n console.error('批量删除消息失败', _context11.t1);\n\n _this18.$message.error('删除失败');\n\n case 24:\n case \"end\":\n return _context11.stop();\n }\n }\n }, _callee11, null, [[3, 8], [11, 20]]);\n }))();\n },\n handleResetSession: function handleResetSession() {\n var _this19 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee12() {\n return regeneratorRuntime.wrap(function _callee12$(_context12) {\n while (1) {\n switch (_context12.prev = _context12.next) {\n case 0:\n if (!_this19.activeSessionId) {\n _context12.next = 8;\n break;\n }\n\n _context12.prev = 1;\n _context12.next = 4;\n return (0, _kbChat.closeKbSession)(_this19.activeSessionId);\n\n case 4:\n _context12.next = 8;\n break;\n\n case 6:\n _context12.prev = 6;\n _context12.t0 = _context12[\"catch\"](1);\n\n case 8:\n _context12.next = 10;\n return _this19.autoCreateSession();\n\n case 10:\n case \"end\":\n return _context12.stop();\n }\n }\n }, _callee12, null, [[1, 6]]);\n }))();\n },\n handleSend: function handleSend(quickText) {\n var _this20 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee13() {\n var text, requestSessionId, requestId, msgKey, msgIdx, streamMsg, isCurrentStream, answerBuffer, finalContent, streamDone, url;\n return regeneratorRuntime.wrap(function _callee13$(_context13) {\n while (1) {\n switch (_context13.prev = _context13.next) {\n case 0:\n text = typeof quickText === 'string' ? quickText.trim() : _this20.inputText.trim();\n\n if (!(!text || !_this20.activeSessionId || _this20.streaming)) {\n _context13.next = 3;\n break;\n }\n\n return _context13.abrupt(\"return\");\n\n case 3:\n _this20.exitSelectMode();\n\n _this20.inputText = '';\n requestSessionId = _this20.activeSessionId;\n requestId = ++_this20.streamRequestId;\n\n _this20.clearTypeTimer();\n\n _this20.messages.push({\n role: 'user',\n content: text\n });\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n }); // 流式消息:作为 messages 中的对象(带 _key/loading),与 ChatDetail 一致\n\n\n msgKey = 'stream_' + requestId;\n msgIdx = _this20.messages.push({\n role: 'assistant',\n content: '',\n thinking: '',\n toolCalls: [],\n loading: true,\n _key: msgKey\n }) - 1;\n streamMsg = _this20.messages[msgIdx];\n _this20.streamMsg = streamMsg;\n _this20.streaming = true;\n\n isCurrentStream = function isCurrentStream() {\n return requestId === _this20.streamRequestId && requestSessionId === _this20.activeSessionId && _this20.streamMsg === streamMsg;\n }; // SSE 只把数据累积到 answerBuffer。typeTimer(30ms) 节流按「完整换行」逐行把内容\n // 推进到 msg.content:每个 tick 最多只推进「到下一个 \\n 为止」的一行,未完成的尾行\n // 保持不动;配合 renderStreaming(breaks:true) 实现「一行显示一行」。\n // 不是打字机模式(不逐字)、也不是一次性整段渲染(不会一出就是全文)。\n\n\n answerBuffer = '';\n finalContent = '';\n streamDone = false;\n _this20._typeTimer = setInterval(function () {\n if (!isCurrentStream()) return;\n var msg = _this20.messages[msgIdx];\n var source = finalContent || answerBuffer;\n var curLen = msg._prevReveal || 0;\n var nextLen = curLen; // 找到「当前已揭示进度之后」的第一条完整换行,仅推进这一行\n\n var nl = source.indexOf('\\n', curLen);\n\n if (nl !== -1) {\n nextLen = nl + 1;\n } else if (streamDone) {\n // 流结束且没有更多换行:最后一段未以换行结尾的内容一次性露出(此时即完整一行)\n nextLen = source.length;\n }\n\n if (nextLen > curLen) {\n msg._prevReveal = nextLen;\n msg.content = source.substring(0, nextLen);\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n } // 流结束且所有内容都已逐行露出 → 收尾\n\n\n if (streamDone && nextLen >= source.length) {\n _this20.finishStream(); // 后端已在此刻落库,回读一次补全消息 id(单条删除需要)。\n // 落库延迟偶发较大(10 × 800ms ≈ 8s),给足重试次数,\n // 避免「删除」按钮偶现不出现、必须重新进入会话才恢复(见 syncMessageIds 注释)。\n\n\n _this20.syncMessageIds(10);\n }\n }, 30);\n url = (0, _kbChat.createKbStreamUrl)(_this20.activeSessionId, text);\n _this20.eventSource = new EventSource(url);\n\n _this20.eventSource.addEventListener('tool_result', function (e) {\n if (!isCurrentStream() || streamDone) return;\n\n if (e.data && _this20.messages[msgIdx]) {\n if (!_this20.messages[msgIdx].toolCalls) _this20.messages[msgIdx].toolCalls = [];\n\n _this20.messages[msgIdx].toolCalls.push(e.data);\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n }\n });\n\n _this20.eventSource.addEventListener('answer', function (e) {\n if (!isCurrentStream() || streamDone) return;\n answerBuffer += e.data;\n });\n\n _this20.eventSource.addEventListener('agent_result', function (e) {\n if (!isCurrentStream() || streamDone) return; // 与档案君(AiChat.vue)对齐:AGENT_RESULT 为最终真相源。\n // 只记录 finalContent 并结束流,不打断已在逐行推进的 content,交给 typeTimer 以 target 推进/校正。\n\n if (e.data) finalContent = e.data;\n streamDone = true;\n\n _this20.closeEventSource();\n });\n\n _this20.eventSource.addEventListener('msgKey', function (e) {\n if (!isCurrentStream()) return;\n\n if (e.data && _this20.messages[msgIdx]) {\n _this20.$set(_this20.messages[msgIdx], 'msgKey', e.data);\n }\n });\n\n _this20.eventSource.addEventListener('reasoning', function (e) {\n if (!isCurrentStream() || streamDone) return;\n streamMsg.thinking += e.data;\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n });\n\n _this20.eventSource.addEventListener('reasoning_result', function (e) {\n if (!isCurrentStream() || streamDone) return;\n streamMsg.thinking = e.data;\n\n _this20.$nextTick(function () {\n return _this20.scrollToBottom();\n });\n });\n\n _this20.eventSource.addEventListener('error', function () {\n if (!isCurrentStream() || streamDone) return;\n if (!answerBuffer) answerBuffer = '❌ 连接中断';\n streamDone = true;\n\n _this20.closeEventSource();\n });\n\n case 29:\n case \"end\":\n return _context13.stop();\n }\n }\n }, _callee13);\n }))();\n },\n handleStop: function handleStop() {\n this.streamRequestId++;\n\n if (this.activeSessionId) {\n (0, _kbChat.stopKbStream)(this.activeSessionId).catch(function () {});\n }\n\n this.finishStream(); // 手动停止后 typeTimer 被清掉,收尾处的 syncMessageIds 永远不会执行,\n // 该条回答就一直没有 id →「删除」按钮不出现(偶现的典型来源)。\n // 这里主动补一次(带重试,等后端把已生成内容落库)。\n\n this.syncMessageIds(10);\n },\n finishStream: function finishStream() {\n var _this21 = this;\n\n this.clearTypeTimer();\n\n if (this.streamMsg) {\n this.$delete(this.contentCache, this.streamMsg._key);\n this.streamMsg.loading = false;\n this.streamMsg = null;\n }\n\n this.closeEventSource();\n this.streaming = false;\n this.refreshSessionList();\n this.$nextTick(function () {\n return _this21.scrollToBottom();\n });\n },\n\n /** 流式结束后刷新会话列表(更新标题/最后消息) */\n refreshSessionList: function refreshSessionList() {\n var _this22 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee14() {\n var list;\n return regeneratorRuntime.wrap(function _callee14$(_context14) {\n while (1) {\n switch (_context14.prev = _context14.next) {\n case 0:\n if (_this22.companyId) {\n _context14.next = 2;\n break;\n }\n\n return _context14.abrupt(\"return\");\n\n case 2:\n _context14.prev = 2;\n _context14.next = 5;\n return (0, _kbChat.getKbSessionList)(_this22.companyId);\n\n case 5:\n _context14.t0 = _context14.sent;\n\n if (_context14.t0) {\n _context14.next = 8;\n break;\n }\n\n _context14.t0 = [];\n\n case 8:\n list = _context14.t0;\n _this22.sessionList = list;\n _context14.next = 15;\n break;\n\n case 12:\n _context14.prev = 12;\n _context14.t1 = _context14[\"catch\"](2);\n console.error('刷新会话列表失败', _context14.t1);\n\n case 15:\n case \"end\":\n return _context14.stop();\n }\n }\n }, _callee14, null, [[2, 12]]);\n }))();\n },\n clearTypeTimer: function clearTypeTimer() {\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n },\n closeEventSource: function closeEventSource() {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n },\n copyText: function copyText(text) {\n if (navigator.clipboard) {\n navigator.clipboard.writeText(text);\n }\n },\n scrollToBottom: function scrollToBottom() {\n var el = this.$refs.messagesRef;\n\n if (el) {\n el.scrollTop = el.scrollHeight;\n }\n }\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1016
1016
|
|
|
1017
1017
|
/***/ }),
|
|
1018
1018
|
|
|
@@ -1192,7 +1192,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
|
|
1192
1192
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
1193
1193
|
|
|
1194
1194
|
"use strict";
|
|
1195
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n {\n staticClass: \"kb-chat-root\",\n class: {\n \"is-mobile\": _vm.isMobileActive,\n \"h5-list-mode\": _vm.isMobileActive && _vm.showH5List\n },\n on: {\n \"&touchstart\": function($event) {\n return _vm.onSwipeTouchStart.apply(null, arguments)\n }\n }\n },\n [\n _c(\n \"div\",\n {\n staticClass: \"kb-chat-sidebar\",\n class: {\n collapsed: _vm.sidebarCollapsed || _vm.sessionList.length === 0\n }\n },\n [\n _c(\"div\", { staticClass: \"sidebar-header\" }, [\n _vm.isMobileActive\n ? _c(\n \"button\",\n {\n staticClass: \"kb-sidebar-back-btn\",\n attrs: { title: \"返回\" },\n on: { click: _vm.handleSidebarBack }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"22\",\n height: \"22\",\n fill: \"none\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M15 18l-6-6 6-6\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _c(\n \"div\",\n { staticClass: \"sidebar-title\", attrs: { title: \"会话列表\" } },\n [_vm._v(\"会话列表\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"new-chat-btn\",\n attrs: { title: \"新建会话\", disabled: _vm.noDocument },\n on: { click: _vm.handleNewSession }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z\"\n }\n })\n ]\n )\n ]\n )\n ]),\n _c(\n \"div\",\n { staticClass: \"sidebar-list\" },\n [\n _vm._l(_vm.sessionList, function(item) {\n return _c(\n \"div\",\n {\n key: item.sessionId,\n staticClass: \"session-item\",\n class: {\n active: item.sessionId === _vm.activeSessionId,\n \"is-locked\": _vm.noDocument\n },\n on: {\n click: function($event) {\n return _vm.handleSelectSession(item)\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"session-item-main\" }, [\n _c(\n \"div\",\n {\n staticClass: \"session-item-title\",\n attrs: { title: item.title }\n },\n [_vm._v(_vm._s(item.title || \"新对话\"))]\n ),\n _c(\n \"div\",\n {\n staticClass: \"session-item-sub\",\n attrs: { title: item.lastContent }\n },\n [_vm._v(_vm._s(item.lastContent || \"暂无消息\"))]\n )\n ]),\n _c(\n \"div\",\n { staticClass: \"session-item-right\" },\n [\n _c(\"div\", { staticClass: \"session-item-time\" }, [\n _vm._v(\n _vm._s(\n _vm.formatTime(\n item.lastTime ||\n item.updateTime ||\n item.createTime\n )\n )\n )\n ]),\n !_vm.isMobileActive\n ? [\n _c(\n \"el-dropdown\",\n {\n staticClass: \"session-more-menu\",\n attrs: { trigger: \"click\" },\n on: {\n command: function(cmd) {\n return _vm.handleSessionMenu(cmd, item)\n }\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass: \"session-more-btn\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"el-dropdown-menu\",\n {\n attrs: { slot: \"dropdown\" },\n slot: \"dropdown\"\n },\n [\n _c(\n \"el-dropdown-item\",\n {\n attrs: {\n command: \"rename\",\n icon: \"el-icon-edit-outline\"\n }\n },\n [_vm._v(\"重命名\")]\n ),\n _vm.sessionList.length > 1\n ? _c(\n \"el-dropdown-item\",\n {\n attrs: {\n command: \"delete\",\n icon: \"el-icon-delete\",\n divided: \"\"\n }\n },\n [_vm._v(\"删除\")]\n )\n : _vm._e()\n ],\n 1\n )\n ],\n 1\n )\n ]\n : _c(\n \"button\",\n {\n staticClass:\n \"session-more-btn session-more-btn--m\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.openMobileAction(item)\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n }\n })\n ]\n )\n ]\n )\n ],\n 2\n )\n ]\n )\n }),\n _vm.sessionList.length === 0\n ? _c(\"div\", { staticClass: \"sidebar-empty\" }, [\n _vm._v(\"暂无会话,点击「新对话」开始\")\n ])\n : _vm._e()\n ],\n 2\n )\n ]\n ),\n _c(\"div\", { staticClass: \"kb-chat-main\" }, [\n !_vm.isMobileActive && _vm.sessionList.length > 0\n ? _c(\n \"button\",\n {\n staticClass: \"sidebar-toggle-btn\",\n class: { collapsed: _vm.sidebarCollapsed },\n attrs: { title: \"收起/展开会话列表\" },\n on: {\n click: function($event) {\n _vm.sidebarCollapsed = !_vm.sidebarCollapsed\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _vm.isMobileActive\n ? _c(\n \"button\",\n {\n staticClass: \"kb-m-chat-back\",\n attrs: { title: \"返回\" },\n on: { click: _vm.handleMobileBack }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _c(\"div\", { staticClass: \"chat-header\" }, [\n _c(\"div\", { staticClass: \"header-left\" }, [\n _c(\"div\", { staticClass: \"agent-info\" }, [\n _c(\"div\", { staticClass: \"agent-avatar\" }, [\n _c(\"img\", {\n staticClass: \"avatar-image\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n })\n ]),\n _vm._m(0)\n ])\n ])\n ]),\n _c(\n \"div\",\n { ref: \"messagesRef\", staticClass: \"chat-body\" },\n [\n !_vm.activeSessionId\n ? _c(\"div\", { staticClass: \"welcome welcome-loading\" }, [\n _c(\"div\", { staticClass: \"welcome-title\" }, [\n _vm._v(\"知识库 AI 助手\")\n ]),\n _c(\"div\", { staticClass: \"welcome-text\" }, [\n _vm._v(\"正在初始化会话...\")\n ])\n ])\n : _vm.messages.length === 0\n ? _c(\"div\", { staticClass: \"chat-empty-welcome\" }, [\n _c(\"div\", { staticClass: \"welcome-card\" }, [\n _c(\"div\", { staticClass: \"welcome-avatar-box\" }, [\n _c(\"div\", {\n staticClass: \"welcome-avatar has-chat-agent-img\",\n style: { backgroundImage: \"url(\" + _vm.aiAvatar + \")\" }\n })\n ]),\n _vm._m(1)\n ]),\n _c(\"div\", { staticClass: \"guess-box\" }, [\n _c(\"div\", { staticClass: \"guess-title\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"#1677ff\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })\n ]\n ),\n _c(\"span\", [_vm._v(\"猜您想问\")])\n ]),\n _c(\n \"div\",\n { staticClass: \"tag-wrap\" },\n _vm._l(_vm.suggestedQuestions, function(question) {\n return _c(\n \"div\",\n {\n key: question,\n staticClass: \"quick-tag\",\n on: {\n click: function($event) {\n return _vm.handleSend(question)\n }\n }\n },\n [_vm._v(\" \" + _vm._s(question) + \" \")]\n )\n }),\n 0\n )\n ]),\n _vm.isMobileActive\n ? _c(\"div\", { staticClass: \"kb-m-guide-mask\" }, [\n _c(\"div\", { staticClass: \"kb-m-guide-line\" }),\n _c(\"div\", { staticClass: \"kb-m-guide-tip\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"15\",\n height: \"15\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })\n ]\n ),\n _c(\"span\", [\n _vm._v(\"在下方输入框输入问题,立即开始对话\")\n ])\n ])\n ])\n : _vm._e()\n ])\n : _vm._l(_vm.messages, function(msg, idx) {\n return _c(\n \"div\",\n {\n key: msg._key || \"m-\" + idx,\n staticClass: \"msg-row\",\n class: [\n msg.role,\n {\n \"select-mode\": _vm.selectMode,\n selected: _vm.selectMode && _vm.isMsgSelected(msg)\n }\n ],\n on: {\n click: function($event) {\n return _vm.onMsgRowClick(msg)\n },\n \"&touchstart\": function($event) {\n return _vm.onMsgTouchStart(msg)\n },\n \"&touchmove\": function($event) {\n return _vm.onMsgTouchEnd.apply(null, arguments)\n },\n touchend: _vm.onMsgTouchEnd\n }\n },\n [\n _vm.selectMode\n ? _c(\n \"div\",\n {\n staticClass: \"msg-check\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.toggleSelectMessage(msg)\n }\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass: \"msg-check-box\",\n class: { checked: _vm.isMsgSelected(msg) }\n },\n [\n _vm.isMsgSelected(msg)\n ? _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"13\",\n height: \"13\",\n fill: \"none\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M4 12.5l5 5L20 6.5\",\n stroke: \"#fff\",\n \"stroke-width\": \"3.4\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })\n ]\n )\n : _vm._e()\n ]\n )\n ]\n )\n : _vm._e(),\n _c(\"div\", { staticClass: \"msg-main\" }, [\n msg.role === \"assistant\"\n ? _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n })\n : _vm.hasUserAvatar\n ? _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: { src: _vm.userAvatar, alt: \"用户\" }\n })\n : _c(\n \"div\",\n {\n staticClass: \"message-avatar user-text-avatar\"\n },\n [_vm._v(_vm._s(_vm.userAvatarText))]\n ),\n _c(\"div\", { staticClass: \"msg-content\" }, [\n _c(\n \"div\",\n { staticClass: \"bubble\", class: msg.role },\n [\n msg.role === \"assistant\" && msg.thinking\n ? _c(\n \"details\",\n {\n staticClass: \"thinking-details\",\n attrs: { open: msg.loading }\n },\n [\n _c(\n \"summary\",\n { staticClass: \"thinking-summary\" },\n [_vm._v(\"💭 思考过程\")]\n ),\n _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(\n _vm.escapeHtml(msg.thinking)\n )\n }\n })\n ]\n )\n : _vm._e(),\n msg.role === \"user\" && msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(\n _vm.renderUserContent(msg.content)\n )\n }\n })\n : msg.loading && msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n attrs: { \"aria-live\": \"polite\" },\n domProps: {\n innerHTML: _vm._s(\n _vm.renderStreaming(\n msg.content,\n msg._key\n )\n )\n }\n })\n : msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(\n _vm.renderMarkdown(msg.content)\n )\n }\n })\n : _vm._e(),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value:\n _vm.showToolInfo &&\n msg.toolCalls &&\n msg.toolCalls.length,\n expression:\n \"showToolInfo && msg.toolCalls && msg.toolCalls.length\"\n }\n ],\n staticClass: \"tool-calls\"\n },\n _vm._l(msg.toolCalls, function(tc, ti) {\n return _c(\n \"div\",\n { key: ti, staticClass: \"tool-item\" },\n [\n _c(\n \"span\",\n { staticClass: \"tool-badge\" },\n [_vm._v(\"工具返回\")]\n ),\n _c(\"pre\", [\n _vm._v(\n _vm._s(tc.substring(0, 300)) +\n _vm._s(tc.length > 300 ? \"...\" : \"\")\n )\n ])\n ]\n )\n }),\n 0\n ),\n msg.loading && !msg.content\n ? _c(\"div\", { staticClass: \"typing\" }, [\n _c(\"span\", { staticClass: \"phrase\" }, [\n _vm._v(\"思考中\")\n ]),\n _vm._m(2, true)\n ])\n : _vm._e()\n ]\n ),\n !_vm.selectMode &&\n !_vm.isMobileActive &&\n msg.content &&\n !msg.loading\n ? _c(\"div\", { staticClass: \"msg-actions\" }, [\n _c(\n \"span\",\n {\n staticClass: \"action-btn\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.copyText(msg.content)\n }\n }\n },\n [_vm._v(\"复制\")]\n ),\n msg.id\n ? _c(\n \"span\",\n {\n staticClass:\n \"action-btn action-btn--danger\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.enterSelectMode(msg)\n }\n }\n },\n [_vm._v(\"删除\")]\n )\n : _vm._e(),\n msg.role === \"assistant\"\n ? _c(\"span\", { staticClass: \"ai-badge\" }, [\n _vm._v(\"AI 生成(仅供参考)\")\n ])\n : _vm._e()\n ])\n : _vm._e()\n ])\n ])\n ]\n )\n })\n ],\n 2\n ),\n _vm.selectMode\n ? _c(\"div\", { staticClass: \"kb-select-bar\" }, [\n _c(\"div\", { staticClass: \"select-bar-left\" }, [\n _c(\"span\", { staticClass: \"select-bar-count\" }, [\n _vm._v(\"已选 \" + _vm._s(_vm.selectedMsgIds.length) + \" 条\")\n ]),\n _c(\n \"span\",\n {\n staticClass: \"select-bar-all\",\n on: { click: _vm.toggleSelectAll }\n },\n [_vm._v(_vm._s(_vm.isAllSelected ? \"取消全选\" : \"全选\"))]\n )\n ]),\n _c(\"div\", { staticClass: \"select-bar-right\" }, [\n _c(\n \"button\",\n {\n staticClass: \"select-bar-cancel\",\n on: { click: _vm.exitSelectMode }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"select-bar-delete\",\n attrs: { disabled: !_vm.selectedMsgIds.length },\n on: { click: _vm.handleDeleteSelected }\n },\n [_vm._v(\"删除\")]\n )\n ])\n ])\n : _c(\"div\", { staticClass: \"chat-footer\" }, [\n _c(\"div\", { staticClass: \"input-area\" }, [\n _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.inputText,\n expression: \"inputText\"\n }\n ],\n ref: \"taRef\",\n staticClass: \"chat-textarea\",\n attrs: {\n placeholder: \"请输入消息\",\n maxlength: \"300\",\n disabled: !_vm.activeSessionId || _vm.streaming\n },\n domProps: { value: _vm.inputText },\n on: {\n input: [\n function($event) {\n if ($event.target.composing) {\n return\n }\n _vm.inputText = $event.target.value\n },\n _vm.autoResizeTextarea\n ],\n keydown: [\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k(\n $event.keyCode,\n \"enter\",\n 13,\n $event.key,\n \"Enter\"\n )\n ) {\n return null\n }\n if (\n $event.ctrlKey ||\n $event.shiftKey ||\n $event.altKey ||\n $event.metaKey\n ) {\n return null\n }\n $event.preventDefault()\n return _vm.handleSend.apply(null, arguments)\n },\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k(\n $event.keyCode,\n \"enter\",\n 13,\n $event.key,\n \"Enter\"\n )\n ) {\n return null\n }\n if (!$event.ctrlKey) {\n return null\n }\n return _vm.newLine.apply(null, arguments)\n }\n ]\n }\n }),\n !_vm.streaming\n ? _c(\n \"button\",\n {\n staticClass: \"send-btn\",\n class: { active: _vm.inputText.trim() },\n attrs: {\n disabled:\n !_vm.activeSessionId || !_vm.inputText.trim()\n },\n on: { click: _vm.handleSend }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _vm.streaming\n ? _c(\n \"button\",\n {\n staticClass: \"stop-btn\",\n on: { click: _vm.handleStop }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e()\n ])\n ]),\n _vm.noDocument\n ? _c(\"div\", { staticClass: \"kb-no-doc-mask\" }, [\n _c(\"div\", { staticClass: \"kb-no-doc-box\" }, [\n _c(\"div\", { staticClass: \"kb-no-doc-icon\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"34\",\n height: \"34\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z\"\n }\n })\n ]\n )\n ]),\n _c(\"div\", { staticClass: \"kb-no-doc-text\" }, [\n _vm._v(\"当前知识库没有资料,请联系管理员上传\")\n ]),\n _vm.isMobileActive\n ? _c(\"div\", { staticClass: \"kb-no-doc-mobile-tip\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"14\",\n height: \"14\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z\"\n }\n })\n ]\n ),\n _c(\"span\", [_vm._v(\"上传并完成索引后,即可开始智能问答\")])\n ])\n : _vm._e()\n ])\n ])\n : _vm._e()\n ]),\n _vm.isMobileActive && _vm.mobileAction\n ? _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet\",\n on: {\n click: function($event) {\n if ($event.target !== $event.currentTarget) {\n return null\n }\n _vm.mobileAction = null\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"kb-m-actionsheet-box\" }, [\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-title\",\n attrs: { title: _vm.mobileAction.title || \"新对话\" }\n },\n [\n _vm._v(\n \" \" + _vm._s(_vm.mobileAction.title || \"新对话\") + \" \"\n )\n ]\n ),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item\",\n on: {\n click: function($event) {\n return _vm.doMobileRename(_vm.mobileAction)\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a.996.996 0 0 0 0-1.41l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z\"\n }\n })\n ]\n ),\n _vm._v(\"重命名 \")\n ]\n ),\n _vm.sessionList.length > 1\n ? _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item danger\",\n on: {\n click: function($event) {\n return _vm.doMobileDelete(_vm.mobileAction)\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\n }\n })\n ]\n ),\n _vm._v(\"删除 \")\n ]\n )\n : _vm._e(),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-cancel\",\n on: {\n click: function($event) {\n _vm.mobileAction = null\n }\n }\n },\n [_vm._v(\"取消\")]\n )\n ])\n ]\n )\n : _vm._e(),\n _vm.isMobileActive && _vm.msgAction\n ? _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet\",\n on: {\n click: function($event) {\n if ($event.target !== $event.currentTarget) {\n return null\n }\n return _vm.closeMsgAction.apply(null, arguments)\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"kb-m-actionsheet-box\" }, [\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item\",\n on: {\n click: function($event) {\n return _vm.doMsgAction(\"copy\")\n }\n }\n },\n [_vm._v(\"复制\")]\n ),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item danger\",\n on: {\n click: function($event) {\n return _vm.doMsgAction(\"delete\")\n }\n }\n },\n [_vm._v(\"删除\")]\n ),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-cancel\",\n on: { click: _vm.closeMsgAction }\n },\n [_vm._v(\"取消\")]\n )\n ])\n ]\n )\n : _vm._e()\n ]\n )\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"agent-text\" }, [\n _c(\"div\", { staticClass: \"agent-name\" }, [_vm._v(\"知识库 AI\")]),\n _c(\"div\", { staticClass: \"agent-desc\" }, [\n _vm._v(\"基于企业知识库智能问答\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"welcome-text-group\" }, [\n _c(\"div\", { staticClass: \"text-sm\" }, [_vm._v(\"您好,我是知识库 AI\")]),\n _c(\"div\", { staticClass: \"text-lg\" }, [\n _vm._v(\"我可以帮您快速查找和解读企业知识\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"dots\" }, [\n _c(\"span\"),\n _c(\"span\"),\n _c(\"span\")\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1195
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n {\n staticClass: \"kb-chat-root\",\n class: {\n \"is-mobile\": _vm.isMobileActive,\n \"h5-list-mode\": _vm.isMobileActive && _vm.showH5List\n },\n on: {\n \"&touchstart\": function($event) {\n return _vm.onSwipeTouchStart.apply(null, arguments)\n }\n }\n },\n [\n _c(\n \"div\",\n {\n staticClass: \"kb-chat-sidebar\",\n class: {\n collapsed: _vm.sidebarCollapsed || _vm.sessionList.length === 0\n }\n },\n [\n _c(\"div\", { staticClass: \"sidebar-header\" }, [\n _vm.isMobileActive\n ? _c(\n \"button\",\n {\n staticClass: \"kb-sidebar-back-btn\",\n attrs: { title: \"返回\" },\n on: { click: _vm.handleSidebarBack }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"22\",\n height: \"22\",\n fill: \"none\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M15 18l-6-6 6-6\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _c(\n \"div\",\n { staticClass: \"sidebar-title\", attrs: { title: \"会话列表\" } },\n [_vm._v(\"会话列表\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"new-chat-btn\",\n attrs: { title: \"新建会话\", disabled: _vm.noDocument },\n on: { click: _vm.handleNewSession }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z\"\n }\n })\n ]\n )\n ]\n )\n ]),\n _c(\n \"div\",\n { staticClass: \"sidebar-list\" },\n [\n _vm._l(_vm.sessionList, function(item) {\n return _c(\n \"div\",\n {\n key: item.sessionId,\n staticClass: \"session-item\",\n class: {\n active: item.sessionId === _vm.activeSessionId,\n \"is-locked\": _vm.noDocument\n },\n on: {\n click: function($event) {\n return _vm.handleSelectSession(item)\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"session-item-main\" }, [\n _c(\n \"div\",\n {\n staticClass: \"session-item-title\",\n attrs: { title: item.title }\n },\n [_vm._v(_vm._s(item.title || \"新对话\"))]\n ),\n _c(\n \"div\",\n {\n staticClass: \"session-item-sub\",\n attrs: { title: item.lastContent }\n },\n [_vm._v(_vm._s(item.lastContent || \"暂无消息\"))]\n )\n ]),\n _c(\n \"div\",\n { staticClass: \"session-item-right\" },\n [\n _c(\"div\", { staticClass: \"session-item-time\" }, [\n _vm._v(\n _vm._s(\n _vm.formatTime(\n item.lastTime ||\n item.updateTime ||\n item.createTime\n )\n )\n )\n ]),\n !_vm.isMobileActive\n ? [\n _c(\n \"el-dropdown\",\n {\n staticClass: \"session-more-menu\",\n attrs: { trigger: \"click\" },\n on: {\n command: function(cmd) {\n return _vm.handleSessionMenu(cmd, item)\n }\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass: \"session-more-btn\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"el-dropdown-menu\",\n {\n attrs: { slot: \"dropdown\" },\n slot: \"dropdown\"\n },\n [\n _c(\n \"el-dropdown-item\",\n {\n attrs: {\n command: \"rename\",\n icon: \"el-icon-edit-outline\"\n }\n },\n [_vm._v(\"重命名\")]\n ),\n _vm.sessionList.length > 1\n ? _c(\n \"el-dropdown-item\",\n {\n attrs: {\n command: \"delete\",\n icon: \"el-icon-delete\",\n divided: \"\"\n }\n },\n [_vm._v(\"删除\")]\n )\n : _vm._e()\n ],\n 1\n )\n ],\n 1\n )\n ]\n : _c(\n \"button\",\n {\n staticClass:\n \"session-more-btn session-more-btn--m\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.openMobileAction(item)\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n }\n })\n ]\n )\n ]\n )\n ],\n 2\n )\n ]\n )\n }),\n _vm.sessionList.length === 0\n ? _c(\"div\", { staticClass: \"sidebar-empty\" }, [\n _vm._v(\"暂无会话,点击「新对话」开始\")\n ])\n : _vm._e()\n ],\n 2\n )\n ]\n ),\n _c(\"div\", { staticClass: \"kb-chat-main\" }, [\n !_vm.isMobileActive && _vm.sessionList.length > 0\n ? _c(\n \"button\",\n {\n staticClass: \"sidebar-toggle-btn\",\n class: { collapsed: _vm.sidebarCollapsed },\n attrs: { title: \"收起/展开会话列表\" },\n on: {\n click: function($event) {\n _vm.sidebarCollapsed = !_vm.sidebarCollapsed\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _vm.isMobileActive\n ? _c(\n \"button\",\n {\n staticClass: \"kb-m-chat-back\",\n attrs: { title: \"返回\" },\n on: { click: _vm.handleMobileBack }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _c(\"div\", { staticClass: \"chat-header\" }, [\n _c(\"div\", { staticClass: \"header-left\" }, [\n _c(\"div\", { staticClass: \"agent-info\" }, [\n _c(\"div\", { staticClass: \"agent-avatar\" }, [\n _c(\"img\", {\n staticClass: \"avatar-image\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n })\n ]),\n _vm._m(0)\n ])\n ])\n ]),\n _c(\n \"div\",\n { ref: \"messagesRef\", staticClass: \"chat-body\" },\n [\n !_vm.activeSessionId\n ? _c(\"div\", { staticClass: \"welcome welcome-loading\" }, [\n _c(\"div\", { staticClass: \"welcome-title\" }, [\n _vm._v(\"知识库 AI 助手\")\n ]),\n _c(\"div\", { staticClass: \"welcome-text\" }, [\n _vm._v(\"正在初始化会话...\")\n ])\n ])\n : _vm.messages.length === 0\n ? _c(\"div\", { staticClass: \"chat-empty-welcome\" }, [\n _c(\"div\", { staticClass: \"welcome-card\" }, [\n _c(\"div\", { staticClass: \"welcome-avatar-box\" }, [\n _c(\"div\", {\n staticClass: \"welcome-avatar has-chat-agent-img\",\n style: { backgroundImage: \"url(\" + _vm.aiAvatar + \")\" }\n })\n ]),\n _vm._m(1)\n ]),\n _c(\"div\", { staticClass: \"guess-box\" }, [\n _c(\"div\", { staticClass: \"guess-title\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"#1677ff\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })\n ]\n ),\n _c(\"span\", [_vm._v(\"猜您想问\")])\n ]),\n _c(\n \"div\",\n { staticClass: \"tag-wrap\" },\n _vm._l(_vm.suggestedQuestions, function(question) {\n return _c(\n \"div\",\n {\n key: question,\n staticClass: \"quick-tag\",\n on: {\n click: function($event) {\n return _vm.handleSend(question)\n }\n }\n },\n [_vm._v(\" \" + _vm._s(question) + \" \")]\n )\n }),\n 0\n )\n ]),\n _vm.isMobileActive\n ? _c(\"div\", { staticClass: \"kb-m-guide-mask\" }, [\n _c(\"div\", { staticClass: \"kb-m-guide-line\" }),\n _c(\"div\", { staticClass: \"kb-m-guide-tip\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"15\",\n height: \"15\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })\n ]\n ),\n _c(\"span\", [\n _vm._v(\"在下方输入框输入问题,立即开始对话\")\n ])\n ])\n ])\n : _vm._e()\n ])\n : _vm._l(_vm.messages, function(msg, idx) {\n return _c(\n \"div\",\n {\n key: msg._key || \"m-\" + idx,\n staticClass: \"msg-row\",\n class: [\n msg.role,\n {\n \"select-mode\": _vm.selectMode,\n selected: _vm.selectMode && _vm.isMsgSelected(msg)\n }\n ],\n on: {\n click: function($event) {\n return _vm.onMsgRowClick(msg)\n },\n \"&touchstart\": function($event) {\n return _vm.onMsgTouchStart(msg, $event)\n },\n \"&touchmove\": function($event) {\n return _vm.onMsgTouchMove.apply(null, arguments)\n },\n touchend: _vm.onMsgTouchEnd,\n touchcancel: _vm.onMsgTouchEnd,\n contextmenu: function($event) {\n return _vm.onMsgContextMenu($event, msg)\n }\n }\n },\n [\n _vm.selectMode\n ? _c(\n \"div\",\n {\n staticClass: \"msg-check\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.toggleSelectMessage(msg)\n }\n }\n },\n [\n _c(\n \"span\",\n {\n staticClass: \"msg-check-box\",\n class: { checked: _vm.isMsgSelected(msg) }\n },\n [\n _vm.isMsgSelected(msg)\n ? _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"13\",\n height: \"13\",\n fill: \"none\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M4 12.5l5 5L20 6.5\",\n stroke: \"#fff\",\n \"stroke-width\": \"3.4\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })\n ]\n )\n : _vm._e()\n ]\n )\n ]\n )\n : _vm._e(),\n _c(\"div\", { staticClass: \"msg-main\" }, [\n msg.role === \"assistant\"\n ? _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n })\n : _vm.hasUserAvatar\n ? _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: { src: _vm.userAvatar, alt: \"用户\" }\n })\n : _c(\n \"div\",\n {\n staticClass: \"message-avatar user-text-avatar\"\n },\n [_vm._v(_vm._s(_vm.userAvatarText))]\n ),\n _c(\"div\", { staticClass: \"msg-content\" }, [\n _c(\n \"div\",\n { staticClass: \"bubble\", class: msg.role },\n [\n msg.role === \"assistant\" && msg.thinking\n ? _c(\n \"details\",\n {\n staticClass: \"thinking-details\",\n attrs: { open: msg.loading }\n },\n [\n _c(\n \"summary\",\n { staticClass: \"thinking-summary\" },\n [_vm._v(\"💭 思考过程\")]\n ),\n _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(\n _vm.escapeHtml(msg.thinking)\n )\n }\n })\n ]\n )\n : _vm._e(),\n msg.role === \"user\" && msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(\n _vm.renderUserContent(msg.content)\n )\n }\n })\n : msg.loading && msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n attrs: { \"aria-live\": \"polite\" },\n domProps: {\n innerHTML: _vm._s(\n _vm.renderStreaming(\n msg.content,\n msg._key\n )\n )\n }\n })\n : msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(\n _vm.renderMarkdown(msg.content)\n )\n }\n })\n : _vm._e(),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value:\n _vm.showToolInfo &&\n msg.toolCalls &&\n msg.toolCalls.length,\n expression:\n \"showToolInfo && msg.toolCalls && msg.toolCalls.length\"\n }\n ],\n staticClass: \"tool-calls\"\n },\n _vm._l(msg.toolCalls, function(tc, ti) {\n return _c(\n \"div\",\n { key: ti, staticClass: \"tool-item\" },\n [\n _c(\n \"span\",\n { staticClass: \"tool-badge\" },\n [_vm._v(\"工具返回\")]\n ),\n _c(\"pre\", [\n _vm._v(\n _vm._s(tc.substring(0, 300)) +\n _vm._s(tc.length > 300 ? \"...\" : \"\")\n )\n ])\n ]\n )\n }),\n 0\n ),\n msg.loading && !msg.content\n ? _c(\"div\", { staticClass: \"typing\" }, [\n _c(\"span\", { staticClass: \"phrase\" }, [\n _vm._v(\"思考中\")\n ]),\n _vm._m(2, true)\n ])\n : _vm._e()\n ]\n ),\n !_vm.selectMode &&\n !_vm.isMobileActive &&\n msg.content &&\n !msg.loading\n ? _c(\"div\", { staticClass: \"msg-actions\" }, [\n _c(\n \"span\",\n {\n staticClass: \"action-btn\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.copyText(msg.content)\n }\n }\n },\n [_vm._v(\"复制\")]\n ),\n msg.id\n ? _c(\n \"span\",\n {\n staticClass:\n \"action-btn action-btn--danger\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n return _vm.enterSelectMode(msg)\n }\n }\n },\n [_vm._v(\"删除\")]\n )\n : _vm._e(),\n msg.role === \"assistant\"\n ? _c(\"span\", { staticClass: \"ai-badge\" }, [\n _vm._v(\"AI 生成(仅供参考)\")\n ])\n : _vm._e()\n ])\n : _vm._e()\n ])\n ])\n ]\n )\n })\n ],\n 2\n ),\n _vm.selectMode\n ? _c(\"div\", { staticClass: \"kb-select-bar\" }, [\n _c(\"div\", { staticClass: \"select-bar-left\" }, [\n _c(\"span\", { staticClass: \"select-bar-count\" }, [\n _vm._v(\"已选 \" + _vm._s(_vm.selectedMsgIds.length) + \" 条\")\n ]),\n _c(\n \"span\",\n {\n staticClass: \"select-bar-all\",\n on: { click: _vm.toggleSelectAll }\n },\n [_vm._v(_vm._s(_vm.isAllSelected ? \"取消全选\" : \"全选\"))]\n )\n ]),\n _c(\"div\", { staticClass: \"select-bar-right\" }, [\n _c(\n \"button\",\n {\n staticClass: \"select-bar-cancel\",\n on: { click: _vm.exitSelectMode }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"select-bar-delete\",\n attrs: { disabled: !_vm.selectedMsgIds.length },\n on: { click: _vm.handleDeleteSelected }\n },\n [_vm._v(\"删除\")]\n )\n ])\n ])\n : _c(\"div\", { staticClass: \"chat-footer\" }, [\n _c(\"div\", { staticClass: \"input-area\" }, [\n _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.inputText,\n expression: \"inputText\"\n }\n ],\n ref: \"taRef\",\n staticClass: \"chat-textarea\",\n attrs: {\n placeholder: \"请输入消息\",\n maxlength: \"300\",\n disabled: !_vm.activeSessionId || _vm.streaming\n },\n domProps: { value: _vm.inputText },\n on: {\n input: [\n function($event) {\n if ($event.target.composing) {\n return\n }\n _vm.inputText = $event.target.value\n },\n _vm.autoResizeTextarea\n ],\n keydown: [\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k(\n $event.keyCode,\n \"enter\",\n 13,\n $event.key,\n \"Enter\"\n )\n ) {\n return null\n }\n if (\n $event.ctrlKey ||\n $event.shiftKey ||\n $event.altKey ||\n $event.metaKey\n ) {\n return null\n }\n $event.preventDefault()\n return _vm.handleSend.apply(null, arguments)\n },\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k(\n $event.keyCode,\n \"enter\",\n 13,\n $event.key,\n \"Enter\"\n )\n ) {\n return null\n }\n if (!$event.ctrlKey) {\n return null\n }\n return _vm.newLine.apply(null, arguments)\n }\n ]\n }\n }),\n !_vm.streaming\n ? _c(\n \"button\",\n {\n staticClass: \"send-btn\",\n class: { active: _vm.inputText.trim() },\n attrs: {\n disabled:\n !_vm.activeSessionId || !_vm.inputText.trim()\n },\n on: { click: _vm.handleSend }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d: \"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _vm.streaming\n ? _c(\n \"button\",\n {\n staticClass: \"stop-btn\",\n on: { click: _vm.handleStop }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e()\n ])\n ]),\n _vm.noDocument\n ? _c(\"div\", { staticClass: \"kb-no-doc-mask\" }, [\n _c(\"div\", { staticClass: \"kb-no-doc-box\" }, [\n _c(\"div\", { staticClass: \"kb-no-doc-icon\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"34\",\n height: \"34\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z\"\n }\n })\n ]\n )\n ]),\n _c(\"div\", { staticClass: \"kb-no-doc-text\" }, [\n _vm._v(\"当前知识库没有资料,请联系管理员上传\")\n ]),\n _vm.isMobileActive\n ? _c(\"div\", { staticClass: \"kb-no-doc-mobile-tip\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"14\",\n height: \"14\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z\"\n }\n })\n ]\n ),\n _c(\"span\", [_vm._v(\"上传并完成索引后,即可开始智能问答\")])\n ])\n : _vm._e()\n ])\n ])\n : _vm._e()\n ]),\n _vm.isMobileActive && _vm.mobileAction\n ? _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet\",\n on: {\n click: function($event) {\n if ($event.target !== $event.currentTarget) {\n return null\n }\n _vm.mobileAction = null\n },\n contextmenu: function($event) {\n $event.preventDefault()\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"kb-m-actionsheet-box\" }, [\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-title\",\n attrs: { title: _vm.mobileAction.title || \"新对话\" }\n },\n [\n _vm._v(\n \" \" + _vm._s(_vm.mobileAction.title || \"新对话\") + \" \"\n )\n ]\n ),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item\",\n on: {\n click: function($event) {\n return _vm.doMobileRename(_vm.mobileAction)\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a.996.996 0 0 0 0-1.41l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z\"\n }\n })\n ]\n ),\n _vm._v(\"重命名 \")\n ]\n ),\n _vm.sessionList.length > 1\n ? _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item danger\",\n on: {\n click: function($event) {\n return _vm.doMobileDelete(_vm.mobileAction)\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\"\n }\n })\n ]\n ),\n _vm._v(\"删除 \")\n ]\n )\n : _vm._e(),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-cancel\",\n on: {\n click: function($event) {\n _vm.mobileAction = null\n }\n }\n },\n [_vm._v(\"取消\")]\n )\n ])\n ]\n )\n : _vm._e(),\n _vm.isMobileActive && _vm.msgAction\n ? _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet\",\n on: {\n click: function($event) {\n if ($event.target !== $event.currentTarget) {\n return null\n }\n return _vm.closeMsgAction.apply(null, arguments)\n },\n contextmenu: function($event) {\n $event.preventDefault()\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"kb-m-actionsheet-box\" }, [\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item\",\n on: {\n click: function($event) {\n return _vm.doMsgAction(\"copy\")\n }\n }\n },\n [_vm._v(\"复制\")]\n ),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-item danger\",\n on: {\n click: function($event) {\n return _vm.doMsgAction(\"delete\")\n }\n }\n },\n [_vm._v(\"删除\")]\n ),\n _c(\n \"div\",\n {\n staticClass: \"kb-m-actionsheet-cancel\",\n on: { click: _vm.closeMsgAction }\n },\n [_vm._v(\"取消\")]\n )\n ])\n ]\n )\n : _vm._e()\n ]\n )\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"agent-text\" }, [\n _c(\"div\", { staticClass: \"agent-name\" }, [_vm._v(\"知识库 AI\")]),\n _c(\"div\", { staticClass: \"agent-desc\" }, [\n _vm._v(\"基于企业知识库智能问答\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"welcome-text-group\" }, [\n _c(\"div\", { staticClass: \"text-sm\" }, [_vm._v(\"您好,我是知识库 AI\")]),\n _c(\"div\", { staticClass: \"text-lg\" }, [\n _vm._v(\"我可以帮您快速查找和解读企业知识\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"dots\" }, [\n _c(\"span\"),\n _c(\"span\"),\n _c(\"span\")\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1196
1196
|
|
|
1197
1197
|
/***/ }),
|
|
1198
1198
|
|
|
@@ -1789,7 +1789,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
1789
1789
|
/*! no static exports found */
|
|
1790
1790
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1791
1791
|
|
|
1792
|
-
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-chat-root[data-v-1ce90f0b] {\\n flex: 1;\\n display: flex;\\n flex-direction: row;\\n min-width: 0;\\n min-height: 0;\\n}\\n\\n/* 完全隔离主应用 lib-flexible 的影响 - 强制所有子元素使用 px 单位 */\\n.kb-chat-root[data-v-1ce90f0b],\\n.kb-chat-root *[data-v-1ce90f0b],\\n.kb-chat-root *[data-v-1ce90f0b]::before,\\n.kb-chat-root *[data-v-1ce90f0b]::after {\\n font-size: 14px !important;\\n}\\n\\n/* ===== 左侧会话列表 ===== */\\n.kb-chat-sidebar[data-v-1ce90f0b] {\\n width: clamp(240px, 20vw, 280px);\\n flex-shrink: 0;\\n display: flex;\\n flex-direction: column;\\n background: #f7f8fa;\\n border-right: 1px solid #e8e8e8;\\n transition: width 0.2s ease;\\n overflow: hidden;\\n}\\n.kb-chat-sidebar.collapsed[data-v-1ce90f0b] {\\n width: 0;\\n border-right: none;\\n}\\n.sidebar-header[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 14px 12px 10px;\\n flex-shrink: 0;\\n}\\n.sidebar-title[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n gap: 6px;\\n font-size: 16px!important;\\n font-weight: 600;\\n color: #333;\\n white-space: nowrap;\\n}\\n/* 移动端会话列表头部返回按钮(参考 AI 助理 sidebar-back-btn) */\\n.kb-sidebar-back-btn[data-v-1ce90f0b] {\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n width: 36px;\\n height: 36px;\\n padding: 0;\\n margin-right: 4px;\\n border: none;\\n border-radius: 10px;\\n background: transparent;\\n color: #333;\\n cursor: pointer;\\n flex-shrink: 0;\\n transition: background .2s;\\n}\\n.kb-sidebar-back-btn svg[data-v-1ce90f0b] { display: block;\\n}\\n.kb-sidebar-back-btn[data-v-1ce90f0b]:active { background: #f0f2f5;\\n}\\n/* 移动端:返回按钮占位后,标题紧随其后(去掉自动撑开的间距,避免标题被推到中间) */\\n.kb-chat-root.is-mobile .sidebar-header[data-v-1ce90f0b] { justify-content: flex-start;\\n}\\n.kb-chat-root.is-mobile .sidebar-header .new-chat-btn[data-v-1ce90f0b] { margin-left: auto;\\n}\\n.sidebar-title[data-v-1ce90f0b]::before {\\n content: '';\\n flex-shrink: 0;\\n width: 4px;\\n height: 16px;\\n border-radius: 2px;\\n background: linear-gradient(180deg, #409eff, #79bbff);\\n}\\n.new-chat-btn[data-v-1ce90f0b] {\\n width: 30px;\\n height: 32px;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n border: none;\\n background: transparent;\\n color: #909399;\\n cursor: pointer;\\n padding: 0;\\n font-size: 32px!important;\\n transition: all 0.2s;\\n}\\n.new-chat-btn[data-v-1ce90f0b]:hover {\\n background: transparent;\\n color: #409eff;\\n}\\n.new-chat-btn[data-v-1ce90f0b]:disabled {\\n cursor: not-allowed;\\n opacity: 0.45;\\n}\\n.new-chat-btn[data-v-1ce90f0b]:disabled:hover {\\n color: #909399;\\n}\\n.new-chat-btn i[data-v-1ce90f0b]::before {\\n font-size: 21px!important;\\n}\\n.sidebar-list[data-v-1ce90f0b] {\\n flex: 1;\\n overflow-y: auto;\\n padding: 0 8px 12px;\\n}\\n.session-item[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n gap: 8px;\\n padding: 10px 10px;\\n border-radius: 8px;\\n cursor: pointer;\\n transition: background 0.2s;\\n margin-bottom: 2px;\\n}\\n.session-item[data-v-1ce90f0b]:hover {\\n background: #eef1f5;\\n}\\n.session-item.active[data-v-1ce90f0b] {\\n background: #e6f0ff;\\n}\\n/* 无资料遮罩期间:会话列表不可切换 */\\n.session-item.is-locked[data-v-1ce90f0b] {\\n cursor: not-allowed;\\n}\\n.session-item.is-locked[data-v-1ce90f0b]:hover {\\n background: transparent;\\n}\\n.session-item.is-locked.active[data-v-1ce90f0b]:hover {\\n background: #e6f0ff;\\n}\\n.session-item.is-locked .session-more-menu[data-v-1ce90f0b] {\\n display: none;\\n}\\n.session-item-main[data-v-1ce90f0b] {\\n flex: 1;\\n min-width: 0;\\n}\\n.session-item-title[data-v-1ce90f0b] {\\n font-size: 14px;\\n color: #333;\\n font-weight: 500;\\n white-space: nowrap;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n}\\n.session-item.active .session-item-title[data-v-1ce90f0b] {\\n color: #1677ff;\\n font-weight: 600;\\n}\\n.session-item-sub[data-v-1ce90f0b] {\\n font-size: 12px;\\n color: #999;\\n margin-top: 2px;\\n white-space: nowrap;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n}\\n.session-item-right[data-v-1ce90f0b] {\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n align-items: flex-end;\\n gap: 4px;\\n flex-shrink: 0;\\n}\\n.session-item-time[data-v-1ce90f0b] {\\n font-size: 11px;\\n color: #bbb;\\n white-space: nowrap;\\n}\\n/* 悬浮显示的「…」按钮绝对定位覆盖在时间区域上,不参与文档流,\\n 避免 hover 时撑高会话项导致列表整体变高 */\\n.session-more-menu[data-v-1ce90f0b] {\\n display: none;\\n line-height: 1;\\n position: absolute;\\n top: 0;\\n right: 0;\\n}\\n.session-item:hover .session-more-menu[data-v-1ce90f0b] {\\n display: block;\\n}\\n.session-item:hover .session-item-time[data-v-1ce90f0b] {\\n visibility: hidden;\\n}\\n.session-more-btn[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 22px;\\n height: 22px;\\n border-radius: 4px;\\n color: #999;\\n cursor: pointer;\\n transition: all 0.2s;\\n}\\n.session-more-btn[data-v-1ce90f0b]:hover {\\n color: #1677ff;\\n background: #eef4ff;\\n}\\n.sidebar-empty[data-v-1ce90f0b] {\\n padding: 30px 10px;\\n text-align: center;\\n font-size: 13px;\\n color: #bbb;\\n}\\n\\n/* ===== 右侧聊天主区 ===== */\\n.kb-chat-main[data-v-1ce90f0b] {\\n flex: 1;\\n min-width: 0;\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n}\\n\\n/* ===== 无知识库资料遮罩 ===== */\\n.kb-no-doc-mask[data-v-1ce90f0b] {\\n position: absolute;\\n top: 0;\\n right: 0;\\n bottom: 0;\\n left: 0;\\n z-index: 20;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n padding: 24px;\\n background: rgba(255, 255, 255, 0.5);\\n -webkit-backdrop-filter: blur(2px);\\n backdrop-filter: blur(2px);\\n}\\n.kb-no-doc-box[data-v-1ce90f0b] {\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n gap: 26px;\\n max-width: 560px;\\n padding: 64px 76px;\\n background: rgba(255, 255, 255, 0.94);\\n border: 1px solid rgba(255, 255, 255, 0.9);\\n border-radius: 24px;\\n box-shadow: 0 16px 48px rgba(31, 45, 61, 0.14), 0 2px 8px rgba(31, 45, 61, 0.04);\\n text-align: center;\\n overflow: hidden;\\n -webkit-animation: kb-no-doc-in-data-v-1ce90f0b 0.2s ease-out;\\n animation: kb-no-doc-in-data-v-1ce90f0b 0.2s ease-out;\\n}\\n\\n/* 卡片顶部柔光:呼应红色图标的暖色氛围 */\\n.kb-no-doc-box[data-v-1ce90f0b]::before {\\n content: '';\\n position: absolute;\\n top: -80px;\\n left: 50%;\\n width: 280px;\\n height: 190px;\\n transform: translateX(-50%);\\n background: radial-gradient(closest-side, rgba(245, 108, 108, 0.18), rgba(245, 108, 108, 0));\\n pointer-events: none;\\n}\\n.kb-no-doc-icon[data-v-1ce90f0b] {\\n position: relative;\\n width: 76px;\\n height: 76px;\\n border-radius: 50%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background: linear-gradient(135deg, #ff9f9f, #ef5a5a);\\n border: none;\\n color: #fff;\\n font-size: 32px !important;\\n box-shadow: 0 0 0 8px rgba(239, 90, 90, 0.08), 0 10px 24px rgba(239, 90, 90, 0.3);\\n}\\n.kb-no-doc-icon[data-v-1ce90f0b] ::before {\\n font-size: 28px !important;\\n}\\n.kb-no-doc-text[data-v-1ce90f0b] {\\n font-size: 16px !important;\\n line-height: 1.6;\\n letter-spacing: 0.2px;\\n color: #606266;\\n}\\n@-webkit-keyframes kb-no-doc-in-data-v-1ce90f0b {\\nfrom {\\n opacity: 0;\\n transform: translateY(8px);\\n}\\nto {\\n opacity: 1;\\n transform: translateY(0);\\n}\\n}\\n@keyframes kb-no-doc-in-data-v-1ce90f0b {\\nfrom {\\n opacity: 0;\\n transform: translateY(8px);\\n}\\nto {\\n opacity: 1;\\n transform: translateY(0);\\n}\\n}\\n\\n/* ===== 聊天头部 ===== */\\n.chat-header[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 10px 20px;\\n background: #fff;\\n border-bottom: 1px solid #e8e8e8;\\n flex-shrink: 0;\\n z-index: 10;\\n}\\n.sidebar-toggle-btn[data-v-1ce90f0b] {\\n position: absolute;\\n left: 0;\\n top: 50%;\\n transform: translateY(-50%);\\n width: 20px;\\n height: 64px;\\n z-index: 20;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n border: none;\\n background: #fff;\\n color: #606266;\\n border: 1px solid #dcdfe6;\\n border-left: none;\\n border-radius: 0 8px 8px 0;\\n box-shadow: 2px 0 8px rgba(0,0,0,.08);\\n cursor: pointer;\\n flex-shrink: 0;\\n transition: background 0.2s, color 0.2s, box-shadow 0.2s;\\n}\\n.sidebar-toggle-btn[data-v-1ce90f0b]:hover {\\n background: #ecf5ff;\\n color: #409eff;\\n box-shadow: 2px 0 10px rgba(64,158,255,.25);\\n}\\n.sidebar-toggle-btn svg[data-v-1ce90f0b] {\\n transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);\\n}\\n/* 展开时箭头朝左(收起方向);收起时旋转 180° 朝右(展开方向),并轻微横向弹动强调 */\\n.sidebar-toggle-btn svg[data-v-1ce90f0b] {\\n transform: rotate(0deg);\\n}\\n.sidebar-toggle-btn.collapsed svg[data-v-1ce90f0b] {\\n transform: rotate(180deg);\\n}\\n@-webkit-keyframes toggle-bounce-data-v-1ce90f0b {\\n0% { transform: rotate(0deg) translateX(0);\\n}\\n60% { transform: rotate(0deg) translateX(-3px);\\n}\\n100% { transform: rotate(0deg) translateX(0);\\n}\\n}\\n@keyframes toggle-bounce-data-v-1ce90f0b {\\n0% { transform: rotate(0deg) translateX(0);\\n}\\n60% { transform: rotate(0deg) translateX(-3px);\\n}\\n100% { transform: rotate(0deg) translateX(0);\\n}\\n}\\n.sidebar-toggle-btn:active svg[data-v-1ce90f0b] {\\n -webkit-animation: toggle-bounce-data-v-1ce90f0b 0.3s ease;\\n animation: toggle-bounce-data-v-1ce90f0b 0.3s ease;\\n}\\n.header-left[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1;\\n}\\n.agent-info[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px;\\n}\\n.agent-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: #fff;\\n}\\n.avatar-image[data-v-1ce90f0b] { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;\\n}\\n.agent-text[data-v-1ce90f0b] { min-width: 0;\\n}\\n.agent-name[data-v-1ce90f0b] { font-size: 15px; font-weight: 600; color: #333; line-height: 1.3;\\n}\\n.agent-desc[data-v-1ce90f0b] { font-size: 12px; color: #999; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: min(300px, 45vw);\\n}\\n\\n/* ===== 消息列表 ===== */\\n.chat-body[data-v-1ce90f0b] {\\n flex: 1;\\n overflow-y: auto;\\n padding: 16px clamp(12px, 3vw, 40px);\\n scroll-behavior: auto;\\n position: relative;\\n -webkit-overflow-scrolling: touch;\\n background: url(https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/agentbackground.png) 100% no-repeat;\\n background-size: cover;\\n background-position: center;\\n min-height: 0;\\n}\\n\\n/* 欢迎页 */\\n.welcome[data-v-1ce90f0b] { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #bbb; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin: 0 -20px;\\n}\\n.welcome-icon[data-v-1ce90f0b] { margin-bottom: 12px; opacity: .7;\\n}\\n.welcome-title[data-v-1ce90f0b] { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 8px;\\n}\\n.welcome-text[data-v-1ce90f0b] { font-size: 15px; color: #bbb;\\n}\\n@-webkit-keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\n@keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\n\\n/* 空消息欢迎卡片 */\\n.chat-empty-welcome[data-v-1ce90f0b] {\\n width: 100%;\\n padding: 40px 20px;\\n box-sizing: border-box;\\n display: flex;\\n flex-direction: column;\\n gap: 0;\\n max-width: 600px;\\n margin: 80px auto;\\n}\\n.welcome-card[data-v-1ce90f0b] {\\n position: relative;\\n background: linear-gradient(296deg, #D6EDFF 20%, #E8EBFF 80%);\\n border-radius: 16px 16px 0 0;\\n padding: 24px 24px 24px 150px;\\n overflow: visible;\\n}\\n.welcome-avatar-box[data-v-1ce90f0b] {\\n position: absolute;\\n left: 15px;\\n bottom: 0;\\n width: 110px;\\n height: 137px;\\n}\\n.welcome-avatar[data-v-1ce90f0b] {\\n width: 100%;\\n height: 100%;\\n border-radius: 12px;\\n background-size: cover;\\n background-position: center;\\n background-repeat: no-repeat;\\n}\\n.welcome-text-group[data-v-1ce90f0b] { display: flex; flex-direction: column; gap: 6px;\\n}\\n.welcome-text-group .text-sm[data-v-1ce90f0b] { font-weight: 400; color: #666; font-size: 12px; text-align: left;\\n}\\n.welcome-text-group .text-lg[data-v-1ce90f0b] { color: #000; font-size: 16px; font-weight: 500;\\n}\\n\\n/* 猜您想问 */\\n.guess-box[data-v-1ce90f0b] {\\n border-top: 1px solid #fff;\\n padding: 20px;\\n background: linear-gradient(180deg, #b8dcff 0%, #dff0ff 50%, #fff 100%);\\n border-radius: 0 0 16px 16px;\\n}\\n.guess-title[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; color: #222; font-size: 16px; font-weight: 500;\\n}\\n.tag-wrap[data-v-1ce90f0b] { display: flex; flex-wrap: wrap; gap: 10px;\\n}\\n.quick-tag[data-v-1ce90f0b] { padding: 8px 14px; background: #f2faff; border: 1px solid #e0eaff; border-radius: 8px; color: #333; cursor: pointer; font-size: 14px; white-space: nowrap; transition: all .2s;\\n}\\n.quick-tag[data-v-1ce90f0b]:hover { background: #f0f7ff; border-color: #1677ff;\\n}\\n\\n/* ── Thinking ── */\\n.thinking-details[data-v-1ce90f0b] { margin-bottom: 10px; border: 1px solid #e8e8e8; border-radius: 8px; overflow: hidden; background: #fafafa;\\n}\\n.thinking-summary[data-v-1ce90f0b] { font-size: 12px; color: #888; cursor: pointer; padding: 6px 10px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.thinking-summary[data-v-1ce90f0b]:hover { color: #1677ff;\\n}\\n.thinking-body[data-v-1ce90f0b] { font-size: 14px; color: #999; padding: 8px 12px; line-height: 1.6; border-top: 1px solid #f0f0f0; text-align: left; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere;\\n}\\n.final-content[data-v-1ce90f0b] { word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere; white-space: normal; font-size: 16px; line-height: 1.7;\\n}\\n/* markdown 内容子元素样式 */\\n.final-content[data-v-1ce90f0b] h1, .final-content[data-v-1ce90f0b] h2, .final-content[data-v-1ce90f0b] h3, .final-content[data-v-1ce90f0b] h4, .final-content[data-v-1ce90f0b] h5, .final-content[data-v-1ce90f0b] h6 { margin: 12px 0 6px; font-weight: 600; line-height: 1.5;\\n}\\n.final-content[data-v-1ce90f0b] h1 { font-size: 24px;\\n}\\n.final-content[data-v-1ce90f0b] h2 { font-size: 20px;\\n}\\n.final-content[data-v-1ce90f0b] h3 { font-size: 18px;\\n}\\n.final-content[data-v-1ce90f0b] h4, .final-content[data-v-1ce90f0b] h5, .final-content[data-v-1ce90f0b] h6 { font-size: 16px;\\n}\\n.final-content[data-v-1ce90f0b] strong { font-weight: 700;\\n}\\n.final-content[data-v-1ce90f0b] em { font-style: italic;\\n}\\n.final-content[data-v-1ce90f0b] blockquote { margin: 8px 0; padding: 4px 12px; border-left: 3px solid #b3d8ff; color: #666; background: #f6f8fa;\\n}\\n.final-content[data-v-1ce90f0b] a { color: #1677ff;\\n}\\n.final-content[data-v-1ce90f0b] p { margin: 6px 0;\\n}\\n.final-content[data-v-1ce90f0b] ul, .final-content[data-v-1ce90f0b] ol { padding-left: 20px; margin: 4px 0;\\n}\\n.final-content[data-v-1ce90f0b] li { margin: 2px 0;\\n}\\n.final-content[data-v-1ce90f0b] table { border-collapse: collapse; width: 100%; margin: 6px 0; font-size: 14px;\\n}\\n.final-content[data-v-1ce90f0b] th, .final-content[data-v-1ce90f0b] td { border: 1px solid #e8e8e8; padding: 5px 8px; text-align: left;\\n}\\n.final-content[data-v-1ce90f0b] th { background: #fafafa; font-weight: 600;\\n}\\n.final-content[data-v-1ce90f0b] code { padding: 1px 5px; border-radius: 3px; font-size: 14px;\\n}\\n.final-content[data-v-1ce90f0b] pre { background: #f6f8fa; color: #333; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 13px; border: 1px solid #e8e8e8; margin: 8px 0; line-height: 1.5; white-space: pre;\\n}\\n.final-content[data-v-1ce90f0b] pre code { padding: 0; font-size: inherit;\\n}\\n\\n/* 消息行 */\\n.msg-row[data-v-1ce90f0b] { width: 100%; display: flex; justify-content: center; padding: 20px 16px; box-sizing: border-box; align-items: center;\\n}\\n.msg-main[data-v-1ce90f0b] { display: flex; align-items: flex-start; gap: 10px; width: min(100%, 860px) !important; max-width: 860px;\\n}\\n.msg-row.user .msg-main[data-v-1ce90f0b] { flex-direction: row-reverse;\\n}\\n.msg-row.assistant .msg-main[data-v-1ce90f0b] { flex-direction: row;\\n}\\n\\n/* 头像 */\\n.message-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; -o-object-fit: cover; object-fit: cover;\\n}\\n/* 无头像时的文字头像(与 zjkjAvatar 一致:蓝底白字) */\\n.user-text-avatar[data-v-1ce90f0b] { display: flex; align-items: center; justify-content: center; background: #007fff; color: #fff; font-size: 12px; line-height: 1;\\n}\\n\\n/* 消息内容区域 */\\n.msg-content[data-v-1ce90f0b] { width: 100% !important; max-width: 100%; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column;\\n}\\n.msg-row.user .msg-content[data-v-1ce90f0b] { align-items: flex-end;\\n}\\n.msg-row.assistant .msg-content[data-v-1ce90f0b] { align-items: flex-start;\\n}\\n\\n/* 气泡 */\\n.bubble[data-v-1ce90f0b] { max-width: 100%; min-width: 30px; width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; padding: 8px 14px; border-radius: 12px; line-height: 1.4; font-size: 15px; word-break: break-word; white-space: normal; text-align: left; overflow-wrap: anywhere;\\n}\\n.bubble.assistant[data-v-1ce90f0b] { background: #fff; color: #333; border-bottom-left-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,.06); width: 100%; box-sizing: border-box;\\n}\\n.bubble.user[data-v-1ce90f0b] { background: #1677ff; color: #fff; border-bottom-right-radius: 4px; box-shadow: 0 2px 8px rgba(22,119,255,.15); float: none; margin-left: auto; max-width: min(100%, 520px);\\n}\\n\\n/* ── Tool Calls(与 AI 助理 ChatDetail 完全一致)── */\\n.tool-calls[data-v-1ce90f0b] { margin-top: 8px;\\n}\\n.tool-item[data-v-1ce90f0b] { background: #f5f5f5; border-radius: 6px; padding: 6px 10px; margin-bottom: 4px; font-size: 12px;\\n}\\n.tool-badge[data-v-1ce90f0b] { display: inline-block; margin-bottom: 2px; font-size: 11px; color: #fa8c16; font-weight: 500;\\n}\\n.tool-item pre[data-v-1ce90f0b] { margin: 0; white-space: pre-wrap; word-break: break-all; font-size: 12px; color: #666; max-height: 120px; overflow-y: auto;\\n}\\n\\n/* 消息操作区 */\\n.msg-actions[data-v-1ce90f0b] { display: flex; gap: 4px; margin-top: 4px; padding-left: 4px; align-items: center; justify-content: space-between;\\n}\\n.action-btn[data-v-1ce90f0b] { font-size: 12px; color: #999; cursor: pointer; padding: 2px 6px 2px 0; border-radius: 4px; transition: all .2s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.action-btn[data-v-1ce90f0b]:hover { color: #1677ff; background: #f0f5ff;\\n}\\n.ai-badge[data-v-1ce90f0b] { font-size: 11px; color: #999; padding: 1px 6px; border-radius: 3px; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin-left: auto;\\n}\\n\\n/* ── 消息删除 / 多选(豆包式)── */\\n/* 单条删除按钮(与「复制」并列常显) */\\n.action-btn--danger[data-v-1ce90f0b]:hover { color: #f5222d; background: #fff1f0;\\n}\\n/* 用户消息操作区右对齐,避免「复制/删除」被撑到两端 */\\n.msg-row.user .msg-actions[data-v-1ce90f0b] { justify-content: flex-end; gap: 8px;\\n}\\n\\n/* 多选模式:勾选框绝对定位悬浮在左侧,不参与布局,消息位置保持与普通态完全一致 */\\n.msg-row.select-mode[data-v-1ce90f0b] { justify-content: center; align-items: center; position: relative;\\n}\\n.msg-row.select-mode .msg-main[data-v-1ce90f0b] { flex: none; min-width: 0; width: min(100%, 860px) !important; max-width: 860px;\\n}\\n.msg-row.select-mode .bubble[data-v-1ce90f0b] { cursor: pointer;\\n}\\n/* 选中行整行浅灰背景 + 圆角(豆包式),未选中不加背景 */\\n.msg-row.select-mode.selected[data-v-1ce90f0b] { background: #f5f6f7; border-radius: 8px;\\n}\\n.msg-check[data-v-1ce90f0b] { position: absolute; left: 16px; top: 16px; width: 20px; flex-shrink: 0; display: flex; justify-content: center; cursor: pointer;\\n}\\n.msg-check-box[data-v-1ce90f0b] { width: 20px; height: 20px; border-radius: 4px; border: 1.5px solid #d8dbdf; background: #fff; box-sizing: border-box; display: flex; align-items: center; justify-content: center; transition: all .15s;\\n}\\n.msg-check-box.checked[data-v-1ce90f0b] { background: #1677ff; border-color: #1677ff;\\n}\\n/* 对勾只由模板内的 svg 渲染,禁用任何伪元素对勾,避免出现两个勾 */\\n.msg-check-box[data-v-1ce90f0b]::after, .msg-check-box[data-v-1ce90f0b]::before { content: none !important;\\n}\\n/* H5 多选:勾选框绝对定位在行左侧(left:16px),PC 宽屏下消息主体(860px 居中)离左侧很远不冲突;\\n 但窄屏下消息主体左边缘就在 16px,会被勾选框压住头像/气泡。移动端选中态给消息主体左侧留白,\\n 让勾选框落在留白区,仅选中模式生效,PC 不受影响。 */\\n.kb-chat-root.is-mobile .msg-row.select-mode .msg-main[data-v-1ce90f0b] { padding-left: 28px; box-sizing: border-box;\\n}\\n/* H5 多选:隐藏头像并让其不占空间(display:none 不参与布局),气泡用足宽度;\\n .msg-content 撑满剩余宽度,隐藏头像不影响气泡对齐(用户靠右、AI 靠左)。仅移动端选中态,PC 不受影响。 */\\n.kb-chat-root.is-mobile .msg-row.select-mode .message-avatar[data-v-1ce90f0b] { display: none;\\n}\\n\\n/* 多选底部操作栏(替换输入框) */\\n.kb-select-bar[data-v-1ce90f0b] { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 12px clamp(16px, 3vw, 40px); background: #fff; border-top: 1px solid #e8e8e8;\\n}\\n.select-bar-left[data-v-1ce90f0b] { display: flex; align-items: center; gap: 16px;\\n}\\n.select-bar-count[data-v-1ce90f0b] { font-size: 14px; color: #333;\\n}\\n.select-bar-all[data-v-1ce90f0b] { font-size: 14px; color: #1677ff; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.select-bar-right[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px;\\n}\\n.select-bar-cancel[data-v-1ce90f0b] { height: 34px; padding: 0 16px; border: 1px solid #dcdfe6; background: #fff; color: #606266; border-radius: 6px; font-size: 14px; cursor: pointer;\\n}\\n.select-bar-delete[data-v-1ce90f0b] { height: 34px; padding: 0 20px; border: none; background: #f5222d; color: #fff; border-radius: 6px; font-size: 14px; cursor: pointer;\\n}\\n.select-bar-delete[data-v-1ce90f0b]:disabled { background: #f5b7b7; cursor: not-allowed;\\n}\\n\\n/* 流式打字动画 */\\n.typing[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; padding: 4px 0;\\n}\\n.phrase[data-v-1ce90f0b] { font-size: 14px; color: #999;\\n}\\n.dots[data-v-1ce90f0b] { display: flex; align-items: center; gap: 3px;\\n}\\n.dots span[data-v-1ce90f0b] { width: 6px; height: 6px; border-radius: 50%; background: #bbb; display: inline-block; -webkit-animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both; animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(1) { -webkit-animation-delay: -0.32s; animation-delay: -0.32s;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(2) { -webkit-animation-delay: -0.16s; animation-delay: -0.16s;\\n}\\n@-webkit-keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\n@keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\n\\n/* ===== 输入区域 ===== */\\n.chat-footer[data-v-1ce90f0b] { flex-shrink: 0; border-top: none; padding: 30px 0; display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%;\\n}\\n.input-area[data-v-1ce90f0b] { position: relative; display: flex; align-items: flex-end; gap: 6px; background: #fff; border-radius: 24px; margin: 0 24px; padding: 18px 20px; border: 1px solid #b3d8ff; transition: border-color .2s; flex-wrap: wrap; width: min(100%, 1000px) !important; box-sizing: border-box;\\n}\\n.input-area[data-v-1ce90f0b]:focus-within { border-color: #1677ff; background: #fff; box-shadow: none;\\n}\\n.input-area[data-v-1ce90f0b]::after { content: \\\"Enter 发送,Ctrl+Enter 换行\\\"; position: absolute; right: 100px; bottom: 28px; font-size: 14px; color: #8c8c8c; pointer-events: none;\\n}\\n.chat-textarea[data-v-1ce90f0b] { flex: 1 1 320px; min-width: 0; border: none; outline: none; background: transparent; resize: none; font-size: 16px; line-height: 24px; min-height: 38px; height: 100px; max-height: 180px; overflow-y: auto; padding: 0; box-sizing: border-box; font-family: inherit; color: #333;\\n}\\n.chat-textarea[data-v-1ce90f0b]::-moz-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:-ms-input-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]::placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:disabled { cursor: not-allowed; opacity: .5;\\n}\\n.send-btn[data-v-1ce90f0b] { width: auto; height: 40px; padding: 0 21px; border-radius: 6px; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: all .2s; background: #1677ff; color: #fff; font-size: 14px;\\n}\\n.send-btn svg[data-v-1ce90f0b] { display: none;\\n}\\n.send-btn[data-v-1ce90f0b]::after { content: \\\"发送\\\";\\n}\\n.send-btn.active[data-v-1ce90f0b] { background: #1677ff;\\n}\\n.send-btn.active[data-v-1ce90f0b]:hover { background: #4096ff;\\n}\\n.send-btn[data-v-1ce90f0b]:disabled { background: #88bfff; cursor: not-allowed;\\n}\\n.stop-btn[data-v-1ce90f0b] { width: 35px; height: 35px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; background: #ff4d4f; color: #fff; flex-shrink: 0; transition: all .2s;\\n}\\n.stop-btn[data-v-1ce90f0b]:hover { background: #ff7875;\\n}\\n\\n/* ===== 移动端布局(仿 AI 助理:列表页 ⇄ 聊天详情页) ===== */\\n.kb-chat-root.is-mobile[data-v-1ce90f0b] {\\n position: relative;\\n display: block;\\n width: 100%;\\n max-width: 100%;\\n height: 100%;\\n overflow: hidden;\\n}\\n\\n/* 移动端会话列表页:全屏占位;详情页时滑出隐藏 */\\n.kb-chat-root.is-mobile .kb-chat-sidebar[data-v-1ce90f0b] {\\n position: absolute;\\n top: 0;\\n bottom: 0;\\n left: 0;\\n width: 100%;\\n border-right: none;\\n z-index: 5;\\n background: #fff;\\n}\\n.kb-chat-root.is-mobile:not(.h5-list-mode) .kb-chat-sidebar[data-v-1ce90f0b] {\\n transform: translateX(-100%);\\n}\\n\\n/* 移动端聊天主区:默认全屏;\\n h5-list-mode(列表页)时隐藏聊天主区,避免残留 */\\n.kb-chat-root.is-mobile .kb-chat-main[data-v-1ce90f0b] {\\n width: 100%;\\n max-width: 100%;\\n height: 100%;\\n overflow: hidden;\\n}\\n\\n/* 移动端输入框(自绘仿原生聊天输入条;.is-mobile 由 v-if/v-class 控制,与宽度无关,不需媒体查询) */\\n.kb-chat-root.is-mobile .chat-footer[data-v-1ce90f0b] {\\n padding: 10px 0 calc(10px + env(safe-area-inset-bottom));\\n}\\n.kb-chat-root.is-mobile .input-area[data-v-1ce90f0b] {\\n margin: 0 10px;\\n padding: 6px 6px 6px 16px;\\n width: calc(100% - 20px) !important;\\n gap: 8px;\\n background: #f5f6f8;\\n border: 1px solid transparent;\\n border-radius: 22px;\\n transition: background .2s, border-color .2s, box-shadow .2s;\\n}\\n/* 聚焦时点亮:白底 + 蓝描边 + 轻投影 */\\n.kb-chat-root.is-mobile .input-area[data-v-1ce90f0b]:focus-within {\\n background: #fff;\\n border-color: #1677ff;\\n box-shadow: 0 2px 12px rgba(22, 119, 255, .12);\\n}\\n.kb-chat-root.is-mobile .input-area[data-v-1ce90f0b]::after {\\n display: none;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b] {\\n flex: 1 1 auto;\\n height: 38px;\\n min-height: 38px;\\n max-height: 120px;\\n padding: 7px 0;\\n line-height: 24px;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b]::-moz-placeholder { color: #b8bcc4;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b]:-ms-input-placeholder { color: #b8bcc4;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b]::placeholder { color: #b8bcc4;\\n}\\n/* 移动端发送键:圆形图标按钮(PC 端仍为文字按钮,不受影响) */\\n.kb-chat-root.is-mobile .send-btn[data-v-1ce90f0b] {\\n width: 36px;\\n height: 36px;\\n padding: 0;\\n border-radius: 50%;\\n}\\n.kb-chat-root.is-mobile .send-btn svg[data-v-1ce90f0b] { display: block;\\n}\\n.kb-chat-root.is-mobile .send-btn[data-v-1ce90f0b]::after { content: none;\\n}\\n.kb-chat-root.is-mobile .send-btn.active[data-v-1ce90f0b]:active { background: #0f66e0;\\n}\\n.kb-chat-root.is-mobile .send-btn[data-v-1ce90f0b]:disabled { background: #d3dce9;\\n}\\n.kb-chat-root.is-mobile .stop-btn[data-v-1ce90f0b] { width: 36px; height: 36px;\\n}\\n.kb-chat-root.is-mobile.h5-list-mode .kb-chat-main[data-v-1ce90f0b] {\\n display: none;\\n}\\n\\n/* 移动端「返回」按钮 */\\n.kb-m-chat-back[data-v-1ce90f0b] {\\n position: absolute;\\n top: 10px;\\n left: 10px;\\n z-index: 20;\\n width: 36px;\\n height: 36px;\\n border: 1px solid #e8e8e8;\\n background: #f5f6f8;\\n color: #303133;\\n border-radius: 50%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n cursor: pointer;\\n transition: background 0.2s;\\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);\\n}\\n.kb-m-chat-back[data-v-1ce90f0b]:active { background: #e8eaed;\\n}\\n\\n/* 移动端详情页:头部左侧为返回按钮让位,避免头像/标题被遮挡(.is-mobile 已限定,不需媒体查询) */\\n.kb-chat-root.is-mobile:not(.h5-list-mode) .chat-header[data-v-1ce90f0b] {\\n padding-left: 56px;\\n}\\n\\n/* ===== 移动端引导遮罩(仅 .is-mobile 渲染,PC 不受影响) ===== */\\n.kb-m-guide-mask[data-v-1ce90f0b] {\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n gap: 10px;\\n margin-top: 28px;\\n}.kb-m-guide-line[data-v-1ce90f0b] {\\n width: 1px;\\n height: 22px;\\n background: linear-gradient(to bottom, rgba(22, 119, 255, 0), rgba(22, 119, 255, 0.45));\\n}\\n.kb-m-guide-tip[data-v-1ce90f0b] {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n max-width: 100%;\\n padding: 8px 14px;\\n border-radius: 18px;\\n background: rgba(22, 119, 255, 0.08);\\n border: 1px dashed rgba(22, 119, 255, 0.35);\\n color: #1677ff;\\n font-size: 12px;\\n line-height: 1.5;\\n -webkit-animation: kbGuideBreath-data-v-1ce90f0b 2s ease-in-out infinite;\\n animation: kbGuideBreath-data-v-1ce90f0b 2s ease-in-out infinite;\\n}\\n.kb-m-guide-tip svg[data-v-1ce90f0b] {\\n flex-shrink: 0;\\n}\\n@-webkit-keyframes kbGuideBreath-data-v-1ce90f0b {\\n0%, 100% { opacity: 0.72;\\n}\\n50% { opacity: 1;\\n}\\n}\\n@keyframes kbGuideBreath-data-v-1ce90f0b {\\n0%, 100% { opacity: 0.72;\\n}\\n50% { opacity: 1;\\n}\\n}\\n\\n/* 移动端无文档遮罩:补充说明文案 */\\n.kb-no-doc-mobile-tip[data-v-1ce90f0b] {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n margin-top: 14px;\\n padding: 8px 14px;\\n border-radius: 10px;\\n background: #f5f7fa;\\n color: #909399;\\n font-size: 12px;\\n line-height: 1.5;\\n}\\n.kb-no-doc-mobile-tip svg[data-v-1ce90f0b] {\\n flex-shrink: 0;\\n color: #1677ff;\\n}\\n/* 移动端无文档卡片收窄内边距,适配小屏 */\\n.kb-chat-root.is-mobile .kb-no-doc-mask[data-v-1ce90f0b] {\\n /* 下移让出头部,避免遮挡返回按钮/标题(头部高 ≈ 57px:padding 20 + 内容 36 + 边框 1) */\\n top: 57px;\\n z-index: 9; /* 降到头部(z-index: 10)之下,双保险防止遮挡 */\\n padding: 16px;\\n}\\n/* 移动端引导区收窄外边距,保证引导遮罩在首屏可见 */\\n.kb-chat-root.is-mobile .chat-empty-welcome[data-v-1ce90f0b] {\\n margin: 24px auto;\\n padding: 16px 14px;\\n}\\n.kb-chat-root.is-mobile .welcome-card[data-v-1ce90f0b] {\\n padding: 18px 16px 18px 118px;\\n border-radius: 14px 14px 0 0;\\n}\\n.kb-chat-root.is-mobile .welcome-avatar-box[data-v-1ce90f0b] {\\n left: 8px;\\n width: 96px;\\n height: 120px;\\n}\\n.kb-chat-root.is-mobile .kb-no-doc-box[data-v-1ce90f0b] {\\n gap: 16px;\\n max-width: 100%;\\n padding: 36px 24px;\\n border-radius: 18px;\\n}\\n.kb-chat-root.is-mobile .kb-no-doc-icon[data-v-1ce90f0b] {\\n width: 60px;\\n height: 60px;\\n}\\n.kb-chat-root.is-mobile .kb-no-doc-text[data-v-1ce90f0b] {\\n font-size: 14px !important;\\n}\\n\\n/* 移动端会话「…」按钮 */\\n.session-more-btn--m[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 26px;\\n height: 26px;\\n border-radius: 6px;\\n border: none;\\n background: transparent;\\n color: #909399;\\n cursor: pointer;\\n}\\n\\n/* 移动端会话操作单 */\\n.kb-m-actionsheet[data-v-1ce90f0b] {\\n position: absolute;\\n top: 0;\\n right: 0;\\n bottom: 0;\\n left: 0;\\n z-index: 40;\\n display: flex;\\n align-items: flex-end;\\n justify-content: center;\\n background: rgba(0, 0, 0, 0.35);\\n -webkit-animation: kb-m-fade-data-v-1ce90f0b 0.15s ease-out;\\n animation: kb-m-fade-data-v-1ce90f0b 0.15s ease-out;\\n}\\n.kb-m-actionsheet-box[data-v-1ce90f0b] {\\n width: 100%;\\n max-width: 520px;\\n box-sizing: border-box;\\n padding: 8px;\\n background: #f2f3f5;\\n border-radius: 12px 12px 0 0;\\n}\\n.kb-m-actionsheet-title[data-v-1ce90f0b] {\\n padding: 12px 14px;\\n font-size: 13px;\\n color: #909399;\\n text-align: center;\\n word-break: break-all;\\n}\\n.kb-m-actionsheet-item[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n gap: 6px;\\n height: 50px;\\n background: #fff;\\n border-radius: 8px;\\n font-size: 16px;\\n color: #303133;\\n margin-bottom: 8px;\\n}\\n.kb-m-actionsheet-item.danger[data-v-1ce90f0b] { color: #ff4d4f;\\n}\\n.kb-m-actionsheet-item[data-v-1ce90f0b]:active { background: #f5f6f8;\\n}\\n.kb-m-actionsheet-cancel[data-v-1ce90f0b] {\\n height: 50px;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background: #fff;\\n border-radius: 8px;\\n font-size: 16px;\\n color: #303133;\\n}\\n.kb-m-actionsheet-cancel[data-v-1ce90f0b]:active { background: #f5f6f8;\\n}\\n@-webkit-keyframes kb-m-fade-data-v-1ce90f0b {\\nfrom { opacity: 0;\\n}\\nto { opacity: 1;\\n}\\n}\\n@keyframes kb-m-fade-data-v-1ce90f0b {\\nfrom { opacity: 0;\\n}\\nto { opacity: 1;\\n}\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1792
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-chat-root[data-v-1ce90f0b] {\\n flex: 1;\\n display: flex;\\n flex-direction: row;\\n min-width: 0;\\n min-height: 0;\\n}\\n\\n/* 完全隔离主应用 lib-flexible 的影响 - 强制所有子元素使用 px 单位 */\\n.kb-chat-root[data-v-1ce90f0b],\\n.kb-chat-root *[data-v-1ce90f0b],\\n.kb-chat-root *[data-v-1ce90f0b]::before,\\n.kb-chat-root *[data-v-1ce90f0b]::after {\\n font-size: 14px !important;\\n}\\n\\n/* ===== 左侧会话列表 ===== */\\n.kb-chat-sidebar[data-v-1ce90f0b] {\\n width: clamp(240px, 20vw, 280px);\\n flex-shrink: 0;\\n display: flex;\\n flex-direction: column;\\n background: #f7f8fa;\\n border-right: 1px solid #e8e8e8;\\n transition: width 0.2s ease;\\n overflow: hidden;\\n}\\n.kb-chat-sidebar.collapsed[data-v-1ce90f0b] {\\n width: 0;\\n border-right: none;\\n}\\n.sidebar-header[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 14px 12px 10px;\\n flex-shrink: 0;\\n}\\n.sidebar-title[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n gap: 6px;\\n font-size: 16px!important;\\n font-weight: 600;\\n color: #333;\\n white-space: nowrap;\\n}\\n/* 移动端会话列表头部返回按钮(参考 AI 助理 sidebar-back-btn) */\\n.kb-sidebar-back-btn[data-v-1ce90f0b] {\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n width: 36px;\\n height: 36px;\\n padding: 0;\\n margin-right: 4px;\\n border: none;\\n border-radius: 10px;\\n background: transparent;\\n color: #333;\\n cursor: pointer;\\n flex-shrink: 0;\\n transition: background .2s;\\n}\\n.kb-sidebar-back-btn svg[data-v-1ce90f0b] { display: block;\\n}\\n.kb-sidebar-back-btn[data-v-1ce90f0b]:active { background: #f0f2f5;\\n}\\n/* 移动端:返回按钮占位后,标题紧随其后(去掉自动撑开的间距,避免标题被推到中间) */\\n.kb-chat-root.is-mobile .sidebar-header[data-v-1ce90f0b] { justify-content: flex-start;\\n}\\n.kb-chat-root.is-mobile .sidebar-header .new-chat-btn[data-v-1ce90f0b] { margin-left: auto;\\n}\\n.sidebar-title[data-v-1ce90f0b]::before {\\n content: '';\\n flex-shrink: 0;\\n width: 4px;\\n height: 16px;\\n border-radius: 2px;\\n background: linear-gradient(180deg, #409eff, #79bbff);\\n}\\n.new-chat-btn[data-v-1ce90f0b] {\\n width: 30px;\\n height: 32px;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n border: none;\\n background: transparent;\\n color: #909399;\\n cursor: pointer;\\n padding: 0;\\n font-size: 32px!important;\\n transition: all 0.2s;\\n}\\n.new-chat-btn[data-v-1ce90f0b]:hover {\\n background: transparent;\\n color: #409eff;\\n}\\n.new-chat-btn[data-v-1ce90f0b]:disabled {\\n cursor: not-allowed;\\n opacity: 0.45;\\n}\\n.new-chat-btn[data-v-1ce90f0b]:disabled:hover {\\n color: #909399;\\n}\\n.new-chat-btn i[data-v-1ce90f0b]::before {\\n font-size: 21px!important;\\n}\\n.sidebar-list[data-v-1ce90f0b] {\\n flex: 1;\\n overflow-y: auto;\\n padding: 0 8px 12px;\\n}\\n.session-item[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n gap: 8px;\\n padding: 10px 10px;\\n border-radius: 8px;\\n cursor: pointer;\\n transition: background 0.2s;\\n margin-bottom: 2px;\\n}\\n.session-item[data-v-1ce90f0b]:hover {\\n background: #eef1f5;\\n}\\n.session-item.active[data-v-1ce90f0b] {\\n background: #e6f0ff;\\n}\\n/* 无资料遮罩期间:会话列表不可切换 */\\n.session-item.is-locked[data-v-1ce90f0b] {\\n cursor: not-allowed;\\n}\\n.session-item.is-locked[data-v-1ce90f0b]:hover {\\n background: transparent;\\n}\\n.session-item.is-locked.active[data-v-1ce90f0b]:hover {\\n background: #e6f0ff;\\n}\\n.session-item.is-locked .session-more-menu[data-v-1ce90f0b] {\\n display: none;\\n}\\n.session-item-main[data-v-1ce90f0b] {\\n flex: 1;\\n min-width: 0;\\n}\\n.session-item-title[data-v-1ce90f0b] {\\n font-size: 14px;\\n color: #333;\\n font-weight: 500;\\n white-space: nowrap;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n}\\n.session-item.active .session-item-title[data-v-1ce90f0b] {\\n color: #1677ff;\\n font-weight: 600;\\n}\\n.session-item-sub[data-v-1ce90f0b] {\\n font-size: 12px;\\n color: #999;\\n margin-top: 2px;\\n white-space: nowrap;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n}\\n.session-item-right[data-v-1ce90f0b] {\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n align-items: flex-end;\\n gap: 4px;\\n flex-shrink: 0;\\n}\\n.session-item-time[data-v-1ce90f0b] {\\n font-size: 11px;\\n color: #bbb;\\n white-space: nowrap;\\n}\\n/* 悬浮显示的「…」按钮绝对定位覆盖在时间区域上,不参与文档流,\\n 避免 hover 时撑高会话项导致列表整体变高 */\\n.session-more-menu[data-v-1ce90f0b] {\\n display: none;\\n line-height: 1;\\n position: absolute;\\n top: 0;\\n right: 0;\\n}\\n.session-item:hover .session-more-menu[data-v-1ce90f0b] {\\n display: block;\\n}\\n.session-item:hover .session-item-time[data-v-1ce90f0b] {\\n visibility: hidden;\\n}\\n.session-more-btn[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 22px;\\n height: 22px;\\n border-radius: 4px;\\n color: #999;\\n cursor: pointer;\\n transition: all 0.2s;\\n}\\n.session-more-btn[data-v-1ce90f0b]:hover {\\n color: #1677ff;\\n background: #eef4ff;\\n}\\n.sidebar-empty[data-v-1ce90f0b] {\\n padding: 30px 10px;\\n text-align: center;\\n font-size: 13px;\\n color: #bbb;\\n}\\n\\n/* ===== 右侧聊天主区 ===== */\\n.kb-chat-main[data-v-1ce90f0b] {\\n flex: 1;\\n min-width: 0;\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n}\\n\\n/* ===== 无知识库资料遮罩 ===== */\\n.kb-no-doc-mask[data-v-1ce90f0b] {\\n position: absolute;\\n top: 0;\\n right: 0;\\n bottom: 0;\\n left: 0;\\n z-index: 20;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n padding: 24px;\\n background: rgba(255, 255, 255, 0.5);\\n -webkit-backdrop-filter: blur(2px);\\n backdrop-filter: blur(2px);\\n}\\n.kb-no-doc-box[data-v-1ce90f0b] {\\n position: relative;\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n gap: 26px;\\n max-width: 560px;\\n padding: 64px 76px;\\n background: rgba(255, 255, 255, 0.94);\\n border: 1px solid rgba(255, 255, 255, 0.9);\\n border-radius: 24px;\\n box-shadow: 0 16px 48px rgba(31, 45, 61, 0.14), 0 2px 8px rgba(31, 45, 61, 0.04);\\n text-align: center;\\n overflow: hidden;\\n -webkit-animation: kb-no-doc-in-data-v-1ce90f0b 0.2s ease-out;\\n animation: kb-no-doc-in-data-v-1ce90f0b 0.2s ease-out;\\n}\\n\\n/* 卡片顶部柔光:呼应红色图标的暖色氛围 */\\n.kb-no-doc-box[data-v-1ce90f0b]::before {\\n content: '';\\n position: absolute;\\n top: -80px;\\n left: 50%;\\n width: 280px;\\n height: 190px;\\n transform: translateX(-50%);\\n background: radial-gradient(closest-side, rgba(245, 108, 108, 0.18), rgba(245, 108, 108, 0));\\n pointer-events: none;\\n}\\n.kb-no-doc-icon[data-v-1ce90f0b] {\\n position: relative;\\n width: 76px;\\n height: 76px;\\n border-radius: 50%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background: linear-gradient(135deg, #ff9f9f, #ef5a5a);\\n border: none;\\n color: #fff;\\n font-size: 32px !important;\\n box-shadow: 0 0 0 8px rgba(239, 90, 90, 0.08), 0 10px 24px rgba(239, 90, 90, 0.3);\\n}\\n.kb-no-doc-icon[data-v-1ce90f0b] ::before {\\n font-size: 28px !important;\\n}\\n.kb-no-doc-text[data-v-1ce90f0b] {\\n font-size: 16px !important;\\n line-height: 1.6;\\n letter-spacing: 0.2px;\\n color: #606266;\\n}\\n@-webkit-keyframes kb-no-doc-in-data-v-1ce90f0b {\\nfrom {\\n opacity: 0;\\n transform: translateY(8px);\\n}\\nto {\\n opacity: 1;\\n transform: translateY(0);\\n}\\n}\\n@keyframes kb-no-doc-in-data-v-1ce90f0b {\\nfrom {\\n opacity: 0;\\n transform: translateY(8px);\\n}\\nto {\\n opacity: 1;\\n transform: translateY(0);\\n}\\n}\\n\\n/* ===== 聊天头部 ===== */\\n.chat-header[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 10px 20px;\\n background: #fff;\\n border-bottom: 1px solid #e8e8e8;\\n flex-shrink: 0;\\n z-index: 10;\\n}\\n.sidebar-toggle-btn[data-v-1ce90f0b] {\\n position: absolute;\\n left: 0;\\n top: 50%;\\n transform: translateY(-50%);\\n width: 20px;\\n height: 64px;\\n z-index: 20;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n border: none;\\n background: #fff;\\n color: #606266;\\n border: 1px solid #dcdfe6;\\n border-left: none;\\n border-radius: 0 8px 8px 0;\\n box-shadow: 2px 0 8px rgba(0,0,0,.08);\\n cursor: pointer;\\n flex-shrink: 0;\\n transition: background 0.2s, color 0.2s, box-shadow 0.2s;\\n}\\n.sidebar-toggle-btn[data-v-1ce90f0b]:hover {\\n background: #ecf5ff;\\n color: #409eff;\\n box-shadow: 2px 0 10px rgba(64,158,255,.25);\\n}\\n.sidebar-toggle-btn svg[data-v-1ce90f0b] {\\n transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);\\n}\\n/* 展开时箭头朝左(收起方向);收起时旋转 180° 朝右(展开方向),并轻微横向弹动强调 */\\n.sidebar-toggle-btn svg[data-v-1ce90f0b] {\\n transform: rotate(0deg);\\n}\\n.sidebar-toggle-btn.collapsed svg[data-v-1ce90f0b] {\\n transform: rotate(180deg);\\n}\\n@-webkit-keyframes toggle-bounce-data-v-1ce90f0b {\\n0% { transform: rotate(0deg) translateX(0);\\n}\\n60% { transform: rotate(0deg) translateX(-3px);\\n}\\n100% { transform: rotate(0deg) translateX(0);\\n}\\n}\\n@keyframes toggle-bounce-data-v-1ce90f0b {\\n0% { transform: rotate(0deg) translateX(0);\\n}\\n60% { transform: rotate(0deg) translateX(-3px);\\n}\\n100% { transform: rotate(0deg) translateX(0);\\n}\\n}\\n.sidebar-toggle-btn:active svg[data-v-1ce90f0b] {\\n -webkit-animation: toggle-bounce-data-v-1ce90f0b 0.3s ease;\\n animation: toggle-bounce-data-v-1ce90f0b 0.3s ease;\\n}\\n.header-left[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1;\\n}\\n.agent-info[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px;\\n}\\n.agent-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: #fff;\\n}\\n.avatar-image[data-v-1ce90f0b] { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;\\n}\\n.agent-text[data-v-1ce90f0b] { min-width: 0;\\n}\\n.agent-name[data-v-1ce90f0b] { font-size: 15px; font-weight: 600; color: #333; line-height: 1.3;\\n}\\n.agent-desc[data-v-1ce90f0b] { font-size: 12px; color: #999; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: min(300px, 45vw);\\n}\\n\\n/* ===== 消息列表 ===== */\\n.chat-body[data-v-1ce90f0b] {\\n flex: 1;\\n overflow-y: auto;\\n padding: 16px clamp(12px, 3vw, 40px);\\n scroll-behavior: auto;\\n position: relative;\\n -webkit-overflow-scrolling: touch;\\n background: url(https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/agentbackground.png) 100% no-repeat;\\n background-size: cover;\\n background-position: center;\\n min-height: 0;\\n}\\n\\n/* 欢迎页 */\\n.welcome[data-v-1ce90f0b] { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #bbb; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin: 0 -20px;\\n}\\n.welcome-icon[data-v-1ce90f0b] { margin-bottom: 12px; opacity: .7;\\n}\\n.welcome-title[data-v-1ce90f0b] { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 8px;\\n}\\n.welcome-text[data-v-1ce90f0b] { font-size: 15px; color: #bbb;\\n}\\n@-webkit-keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\n@keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\n\\n/* 空消息欢迎卡片 */\\n.chat-empty-welcome[data-v-1ce90f0b] {\\n width: 100%;\\n padding: 40px 20px;\\n box-sizing: border-box;\\n display: flex;\\n flex-direction: column;\\n gap: 0;\\n max-width: 600px;\\n margin: 80px auto;\\n}\\n.welcome-card[data-v-1ce90f0b] {\\n position: relative;\\n background: linear-gradient(296deg, #D6EDFF 20%, #E8EBFF 80%);\\n border-radius: 16px 16px 0 0;\\n padding: 24px 24px 24px 150px;\\n overflow: visible;\\n}\\n.welcome-avatar-box[data-v-1ce90f0b] {\\n position: absolute;\\n left: 15px;\\n bottom: 0;\\n width: 110px;\\n height: 137px;\\n}\\n.welcome-avatar[data-v-1ce90f0b] {\\n width: 100%;\\n height: 100%;\\n border-radius: 12px;\\n background-size: cover;\\n background-position: center;\\n background-repeat: no-repeat;\\n}\\n.welcome-text-group[data-v-1ce90f0b] { display: flex; flex-direction: column; gap: 6px;\\n}\\n.welcome-text-group .text-sm[data-v-1ce90f0b] { font-weight: 400; color: #666; font-size: 12px; text-align: left;\\n}\\n.welcome-text-group .text-lg[data-v-1ce90f0b] { color: #000; font-size: 16px; font-weight: 500;\\n}\\n\\n/* 猜您想问 */\\n.guess-box[data-v-1ce90f0b] {\\n border-top: 1px solid #fff;\\n padding: 20px;\\n background: linear-gradient(180deg, #b8dcff 0%, #dff0ff 50%, #fff 100%);\\n border-radius: 0 0 16px 16px;\\n}\\n.guess-title[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; color: #222; font-size: 16px; font-weight: 500;\\n}\\n.tag-wrap[data-v-1ce90f0b] { display: flex; flex-wrap: wrap; gap: 10px;\\n}\\n.quick-tag[data-v-1ce90f0b] { padding: 8px 14px; background: #f2faff; border: 1px solid #e0eaff; border-radius: 8px; color: #333; cursor: pointer; font-size: 14px; white-space: nowrap; transition: all .2s;\\n}\\n.quick-tag[data-v-1ce90f0b]:hover { background: #f0f7ff; border-color: #1677ff;\\n}\\n\\n/* ── Thinking ── */\\n.thinking-details[data-v-1ce90f0b] { margin-bottom: 10px; border: 1px solid #e8e8e8; border-radius: 8px; overflow: hidden; background: #fafafa;\\n}\\n.thinking-summary[data-v-1ce90f0b] { font-size: 12px; color: #888; cursor: pointer; padding: 6px 10px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.thinking-summary[data-v-1ce90f0b]:hover { color: #1677ff;\\n}\\n.thinking-body[data-v-1ce90f0b] { font-size: 14px; color: #999; padding: 8px 12px; line-height: 1.6; border-top: 1px solid #f0f0f0; text-align: left; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere;\\n}\\n.final-content[data-v-1ce90f0b] { word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere; white-space: normal; font-size: 16px; line-height: 1.7;\\n}\\n/* markdown 内容子元素样式 */\\n.final-content[data-v-1ce90f0b] h1, .final-content[data-v-1ce90f0b] h2, .final-content[data-v-1ce90f0b] h3, .final-content[data-v-1ce90f0b] h4, .final-content[data-v-1ce90f0b] h5, .final-content[data-v-1ce90f0b] h6 { margin: 12px 0 6px; font-weight: 600; line-height: 1.5;\\n}\\n.final-content[data-v-1ce90f0b] h1 { font-size: 24px;\\n}\\n.final-content[data-v-1ce90f0b] h2 { font-size: 20px;\\n}\\n.final-content[data-v-1ce90f0b] h3 { font-size: 18px;\\n}\\n.final-content[data-v-1ce90f0b] h4, .final-content[data-v-1ce90f0b] h5, .final-content[data-v-1ce90f0b] h6 { font-size: 16px;\\n}\\n.final-content[data-v-1ce90f0b] strong { font-weight: 700;\\n}\\n.final-content[data-v-1ce90f0b] em { font-style: italic;\\n}\\n.final-content[data-v-1ce90f0b] blockquote { margin: 8px 0; padding: 4px 12px; border-left: 3px solid #b3d8ff; color: #666; background: #f6f8fa;\\n}\\n.final-content[data-v-1ce90f0b] a { color: #1677ff;\\n}\\n.final-content[data-v-1ce90f0b] p { margin: 6px 0;\\n}\\n.final-content[data-v-1ce90f0b] ul, .final-content[data-v-1ce90f0b] ol { padding-left: 20px; margin: 4px 0;\\n}\\n.final-content[data-v-1ce90f0b] li { margin: 2px 0;\\n}\\n.final-content[data-v-1ce90f0b] table { border-collapse: collapse; width: 100%; margin: 6px 0; font-size: 14px;\\n}\\n.final-content[data-v-1ce90f0b] th, .final-content[data-v-1ce90f0b] td { border: 1px solid #e8e8e8; padding: 5px 8px; text-align: left;\\n}\\n.final-content[data-v-1ce90f0b] th { background: #fafafa; font-weight: 600;\\n}\\n.final-content[data-v-1ce90f0b] code { padding: 1px 5px; border-radius: 3px; font-size: 14px;\\n}\\n.final-content[data-v-1ce90f0b] pre { background: #f6f8fa; color: #333; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 13px; border: 1px solid #e8e8e8; margin: 8px 0; line-height: 1.5; white-space: pre;\\n}\\n.final-content[data-v-1ce90f0b] pre code { padding: 0; font-size: inherit;\\n}\\n\\n/* 消息行 */\\n.msg-row[data-v-1ce90f0b] { width: 100%; display: flex; justify-content: center; padding: 20px 16px; box-sizing: border-box; align-items: center;\\n}\\n.msg-main[data-v-1ce90f0b] { display: flex; align-items: flex-start; gap: 10px; width: min(100%, 860px) !important; max-width: 860px;\\n}\\n.msg-row.user .msg-main[data-v-1ce90f0b] { flex-direction: row-reverse;\\n}\\n.msg-row.assistant .msg-main[data-v-1ce90f0b] { flex-direction: row;\\n}\\n\\n/* 头像 */\\n.message-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; -o-object-fit: cover; object-fit: cover;\\n}\\n/* 无头像时的文字头像(与 zjkjAvatar 一致:蓝底白字) */\\n.user-text-avatar[data-v-1ce90f0b] { display: flex; align-items: center; justify-content: center; background: #007fff; color: #fff; font-size: 12px; line-height: 1;\\n}\\n\\n/* 消息内容区域 */\\n.msg-content[data-v-1ce90f0b] { width: 100% !important; max-width: 100%; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column;\\n}\\n.msg-row.user .msg-content[data-v-1ce90f0b] { align-items: flex-end;\\n}\\n.msg-row.assistant .msg-content[data-v-1ce90f0b] { align-items: flex-start;\\n}\\n\\n/* 气泡 */\\n.bubble[data-v-1ce90f0b] { max-width: 100%; min-width: 30px; width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; padding: 8px 14px; border-radius: 12px; line-height: 1.4; font-size: 15px; word-break: break-word; white-space: normal; text-align: left; overflow-wrap: anywhere;\\n}\\n.bubble.assistant[data-v-1ce90f0b] { background: #fff; color: #333; border-bottom-left-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,.06); width: 100%; box-sizing: border-box;\\n}\\n.bubble.user[data-v-1ce90f0b] { background: #1677ff; color: #fff; border-bottom-right-radius: 4px; box-shadow: 0 2px 8px rgba(22,119,255,.15); float: none; margin-left: auto; max-width: min(100%, 520px);\\n}\\n\\n/* ── Tool Calls(与 AI 助理 ChatDetail 完全一致)── */\\n.tool-calls[data-v-1ce90f0b] { margin-top: 8px;\\n}\\n.tool-item[data-v-1ce90f0b] { background: #f5f5f5; border-radius: 6px; padding: 6px 10px; margin-bottom: 4px; font-size: 12px;\\n}\\n.tool-badge[data-v-1ce90f0b] { display: inline-block; margin-bottom: 2px; font-size: 11px; color: #fa8c16; font-weight: 500;\\n}\\n.tool-item pre[data-v-1ce90f0b] { margin: 0; white-space: pre-wrap; word-break: break-all; font-size: 12px; color: #666; max-height: 120px; overflow-y: auto;\\n}\\n\\n/* 消息操作区 */\\n.msg-actions[data-v-1ce90f0b] { display: flex; gap: 4px; margin-top: 4px; padding-left: 4px; align-items: center; justify-content: space-between;\\n}\\n.action-btn[data-v-1ce90f0b] { font-size: 12px; color: #999; cursor: pointer; padding: 2px 6px 2px 0; border-radius: 4px; transition: all .2s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.action-btn[data-v-1ce90f0b]:hover { color: #1677ff; background: #f0f5ff;\\n}\\n.ai-badge[data-v-1ce90f0b] { font-size: 11px; color: #999; padding: 1px 6px; border-radius: 3px; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin-left: auto;\\n}\\n\\n/* ── 消息删除 / 多选(豆包式)── */\\n/* 单条删除按钮(与「复制」并列常显) */\\n.action-btn--danger[data-v-1ce90f0b]:hover { color: #f5222d; background: #fff1f0;\\n}\\n/* 用户消息操作区右对齐,避免「复制/删除」被撑到两端 */\\n.msg-row.user .msg-actions[data-v-1ce90f0b] { justify-content: flex-end; gap: 8px;\\n}\\n\\n/* 多选模式:勾选框绝对定位悬浮在左侧,不参与布局,消息位置保持与普通态完全一致 */\\n.msg-row.select-mode[data-v-1ce90f0b] { justify-content: center; align-items: center; position: relative;\\n}\\n.msg-row.select-mode .msg-main[data-v-1ce90f0b] { flex: none; min-width: 0; width: min(100%, 860px) !important; max-width: 860px;\\n}\\n.msg-row.select-mode .bubble[data-v-1ce90f0b] { cursor: pointer;\\n}\\n/* 选中行整行浅灰背景 + 圆角(豆包式),未选中不加背景 */\\n.msg-row.select-mode.selected[data-v-1ce90f0b] { background: #f5f6f7; border-radius: 8px;\\n}\\n.msg-check[data-v-1ce90f0b] { position: absolute; left: 16px; top: 16px; width: 20px; flex-shrink: 0; display: flex; justify-content: center; cursor: pointer;\\n}\\n.msg-check-box[data-v-1ce90f0b] { width: 20px; height: 20px; border-radius: 4px; border: 1.5px solid #d8dbdf; background: #fff; box-sizing: border-box; display: flex; align-items: center; justify-content: center; transition: all .15s;\\n}\\n.msg-check-box.checked[data-v-1ce90f0b] { background: #1677ff; border-color: #1677ff;\\n}\\n/* 对勾只由模板内的 svg 渲染,禁用任何伪元素对勾,避免出现两个勾 */\\n.msg-check-box[data-v-1ce90f0b]::after, .msg-check-box[data-v-1ce90f0b]::before { content: none !important;\\n}\\n/* H5 多选:勾选框绝对定位在行左侧(left:16px),PC 宽屏下消息主体(860px 居中)离左侧很远不冲突;\\n 但窄屏下消息主体左边缘就在 16px,会被勾选框压住头像/气泡。移动端选中态给消息主体左侧留白,\\n 让勾选框落在留白区,仅选中模式生效,PC 不受影响。 */\\n.kb-chat-root.is-mobile .msg-row.select-mode .msg-main[data-v-1ce90f0b] { padding-left: 28px; box-sizing: border-box;\\n}\\n/* H5 多选:隐藏头像并让其不占空间(display:none 不参与布局),气泡用足宽度;\\n .msg-content 撑满剩余宽度,隐藏头像不影响气泡对齐(用户靠右、AI 靠左)。仅移动端选中态,PC 不受影响。 */\\n.kb-chat-root.is-mobile .msg-row.select-mode .message-avatar[data-v-1ce90f0b] { display: none;\\n}\\n\\n/* H5 长按菜单:Android 长按文字会自动进入系统文本选择并弹原生菜单,\\n 此时 getSelection() 非空 → 自定义操作单被跳过、看不到「删除」。\\n 移动端禁用气泡内文本选择,复制统一走长按操作单的「复制」;PC 不生效,保持可选中复制。 */\\n.kb-chat-root.is-mobile .bubble[data-v-1ce90f0b] {\\n -webkit-touch-callout: none;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n -ms-user-select: none;\\n user-select: none;\\n}\\n\\n/* 多选底部操作栏(替换输入框) */\\n.kb-select-bar[data-v-1ce90f0b] { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 12px clamp(16px, 3vw, 40px); background: #fff; border-top: 1px solid #e8e8e8;\\n}\\n.select-bar-left[data-v-1ce90f0b] { display: flex; align-items: center; gap: 16px;\\n}\\n.select-bar-count[data-v-1ce90f0b] { font-size: 14px; color: #333;\\n}\\n.select-bar-all[data-v-1ce90f0b] { font-size: 14px; color: #1677ff; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.select-bar-right[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px;\\n}\\n.select-bar-cancel[data-v-1ce90f0b] { height: 34px; padding: 0 16px; border: 1px solid #dcdfe6; background: #fff; color: #606266; border-radius: 6px; font-size: 14px; cursor: pointer;\\n}\\n.select-bar-delete[data-v-1ce90f0b] { height: 34px; padding: 0 20px; border: none; background: #f5222d; color: #fff; border-radius: 6px; font-size: 14px; cursor: pointer;\\n}\\n.select-bar-delete[data-v-1ce90f0b]:disabled { background: #f5b7b7; cursor: not-allowed;\\n}\\n\\n/* 流式打字动画 */\\n.typing[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; padding: 4px 0;\\n}\\n.phrase[data-v-1ce90f0b] { font-size: 14px; color: #999;\\n}\\n.dots[data-v-1ce90f0b] { display: flex; align-items: center; gap: 3px;\\n}\\n.dots span[data-v-1ce90f0b] { width: 6px; height: 6px; border-radius: 50%; background: #bbb; display: inline-block; -webkit-animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both; animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(1) { -webkit-animation-delay: -0.32s; animation-delay: -0.32s;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(2) { -webkit-animation-delay: -0.16s; animation-delay: -0.16s;\\n}\\n@-webkit-keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\n@keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\n\\n/* ===== 输入区域 ===== */\\n.chat-footer[data-v-1ce90f0b] { flex-shrink: 0; border-top: none; padding: 30px 0; display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%;\\n}\\n.input-area[data-v-1ce90f0b] { position: relative; display: flex; align-items: flex-end; gap: 6px; background: #fff; border-radius: 24px; margin: 0 24px; padding: 18px 20px; border: 1px solid #b3d8ff; transition: border-color .2s; flex-wrap: wrap; width: min(100%, 1000px) !important; box-sizing: border-box;\\n}\\n.input-area[data-v-1ce90f0b]:focus-within { border-color: #1677ff; background: #fff; box-shadow: none;\\n}\\n.input-area[data-v-1ce90f0b]::after { content: \\\"Enter 发送,Ctrl+Enter 换行\\\"; position: absolute; right: 100px; bottom: 28px; font-size: 14px; color: #8c8c8c; pointer-events: none;\\n}\\n.chat-textarea[data-v-1ce90f0b] { flex: 1 1 320px; min-width: 0; border: none; outline: none; background: transparent; resize: none; font-size: 16px; line-height: 24px; min-height: 38px; height: 100px; max-height: 180px; overflow-y: auto; padding: 0; box-sizing: border-box; font-family: inherit; color: #333;\\n}\\n.chat-textarea[data-v-1ce90f0b]::-moz-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:-ms-input-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]::placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:disabled { cursor: not-allowed; opacity: .5;\\n}\\n.send-btn[data-v-1ce90f0b] { width: auto; height: 40px; padding: 0 21px; border-radius: 6px; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: all .2s; background: #1677ff; color: #fff; font-size: 14px;\\n}\\n.send-btn svg[data-v-1ce90f0b] { display: none;\\n}\\n.send-btn[data-v-1ce90f0b]::after { content: \\\"发送\\\";\\n}\\n.send-btn.active[data-v-1ce90f0b] { background: #1677ff;\\n}\\n.send-btn.active[data-v-1ce90f0b]:hover { background: #4096ff;\\n}\\n.send-btn[data-v-1ce90f0b]:disabled { background: #88bfff; cursor: not-allowed;\\n}\\n.stop-btn[data-v-1ce90f0b] { width: 35px; height: 35px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; background: #ff4d4f; color: #fff; flex-shrink: 0; transition: all .2s;\\n}\\n.stop-btn[data-v-1ce90f0b]:hover { background: #ff7875;\\n}\\n\\n/* ===== 移动端布局(仿 AI 助理:列表页 ⇄ 聊天详情页) ===== */\\n.kb-chat-root.is-mobile[data-v-1ce90f0b] {\\n position: relative;\\n display: block;\\n width: 100%;\\n max-width: 100%;\\n height: 100%;\\n overflow: hidden;\\n}\\n\\n/* 移动端会话列表页:全屏占位;详情页时滑出隐藏 */\\n.kb-chat-root.is-mobile .kb-chat-sidebar[data-v-1ce90f0b] {\\n position: absolute;\\n top: 0;\\n bottom: 0;\\n left: 0;\\n width: 100%;\\n border-right: none;\\n z-index: 5;\\n background: #fff;\\n}\\n.kb-chat-root.is-mobile:not(.h5-list-mode) .kb-chat-sidebar[data-v-1ce90f0b] {\\n transform: translateX(-100%);\\n}\\n\\n/* 移动端聊天主区:默认全屏;\\n h5-list-mode(列表页)时隐藏聊天主区,避免残留 */\\n.kb-chat-root.is-mobile .kb-chat-main[data-v-1ce90f0b] {\\n width: 100%;\\n max-width: 100%;\\n height: 100%;\\n overflow: hidden;\\n}\\n\\n/* 移动端输入框(自绘仿原生聊天输入条;.is-mobile 由 v-if/v-class 控制,与宽度无关,不需媒体查询) */\\n.kb-chat-root.is-mobile .chat-footer[data-v-1ce90f0b] {\\n padding: 10px 0 calc(10px + env(safe-area-inset-bottom));\\n}\\n.kb-chat-root.is-mobile .input-area[data-v-1ce90f0b] {\\n margin: 0 10px;\\n padding: 6px 6px 6px 16px;\\n width: calc(100% - 20px) !important;\\n gap: 8px;\\n background: #f5f6f8;\\n border: 1px solid transparent;\\n border-radius: 22px;\\n transition: background .2s, border-color .2s, box-shadow .2s;\\n}\\n/* 聚焦时点亮:白底 + 蓝描边 + 轻投影 */\\n.kb-chat-root.is-mobile .input-area[data-v-1ce90f0b]:focus-within {\\n background: #fff;\\n border-color: #1677ff;\\n box-shadow: 0 2px 12px rgba(22, 119, 255, .12);\\n}\\n.kb-chat-root.is-mobile .input-area[data-v-1ce90f0b]::after {\\n display: none;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b] {\\n flex: 1 1 auto;\\n height: 38px;\\n min-height: 38px;\\n max-height: 120px;\\n padding: 7px 0;\\n line-height: 24px;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b]::-moz-placeholder { color: #b8bcc4;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b]:-ms-input-placeholder { color: #b8bcc4;\\n}\\n.kb-chat-root.is-mobile .chat-textarea[data-v-1ce90f0b]::placeholder { color: #b8bcc4;\\n}\\n/* 移动端发送键:圆形图标按钮(PC 端仍为文字按钮,不受影响) */\\n.kb-chat-root.is-mobile .send-btn[data-v-1ce90f0b] {\\n width: 36px;\\n height: 36px;\\n padding: 0;\\n border-radius: 50%;\\n}\\n.kb-chat-root.is-mobile .send-btn svg[data-v-1ce90f0b] { display: block;\\n}\\n.kb-chat-root.is-mobile .send-btn[data-v-1ce90f0b]::after { content: none;\\n}\\n.kb-chat-root.is-mobile .send-btn.active[data-v-1ce90f0b]:active { background: #0f66e0;\\n}\\n.kb-chat-root.is-mobile .send-btn[data-v-1ce90f0b]:disabled { background: #d3dce9;\\n}\\n.kb-chat-root.is-mobile .stop-btn[data-v-1ce90f0b] { width: 36px; height: 36px;\\n}\\n.kb-chat-root.is-mobile.h5-list-mode .kb-chat-main[data-v-1ce90f0b] {\\n display: none;\\n}\\n\\n/* 移动端「返回」按钮 */\\n.kb-m-chat-back[data-v-1ce90f0b] {\\n position: absolute;\\n top: 10px;\\n left: 10px;\\n z-index: 20;\\n width: 36px;\\n height: 36px;\\n border: 1px solid #e8e8e8;\\n background: #f5f6f8;\\n color: #303133;\\n border-radius: 50%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n cursor: pointer;\\n transition: background 0.2s;\\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);\\n}\\n.kb-m-chat-back[data-v-1ce90f0b]:active { background: #e8eaed;\\n}\\n\\n/* 移动端详情页:头部左侧为返回按钮让位,避免头像/标题被遮挡(.is-mobile 已限定,不需媒体查询) */\\n.kb-chat-root.is-mobile:not(.h5-list-mode) .chat-header[data-v-1ce90f0b] {\\n padding-left: 56px;\\n}\\n\\n/* ===== 移动端引导遮罩(仅 .is-mobile 渲染,PC 不受影响) ===== */\\n.kb-m-guide-mask[data-v-1ce90f0b] {\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n gap: 10px;\\n margin-top: 28px;\\n}.kb-m-guide-line[data-v-1ce90f0b] {\\n width: 1px;\\n height: 22px;\\n background: linear-gradient(to bottom, rgba(22, 119, 255, 0), rgba(22, 119, 255, 0.45));\\n}\\n.kb-m-guide-tip[data-v-1ce90f0b] {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n max-width: 100%;\\n padding: 8px 14px;\\n border-radius: 18px;\\n background: rgba(22, 119, 255, 0.08);\\n border: 1px dashed rgba(22, 119, 255, 0.35);\\n color: #1677ff;\\n font-size: 12px;\\n line-height: 1.5;\\n -webkit-animation: kbGuideBreath-data-v-1ce90f0b 2s ease-in-out infinite;\\n animation: kbGuideBreath-data-v-1ce90f0b 2s ease-in-out infinite;\\n}\\n.kb-m-guide-tip svg[data-v-1ce90f0b] {\\n flex-shrink: 0;\\n}\\n@-webkit-keyframes kbGuideBreath-data-v-1ce90f0b {\\n0%, 100% { opacity: 0.72;\\n}\\n50% { opacity: 1;\\n}\\n}\\n@keyframes kbGuideBreath-data-v-1ce90f0b {\\n0%, 100% { opacity: 0.72;\\n}\\n50% { opacity: 1;\\n}\\n}\\n\\n/* 移动端无文档遮罩:补充说明文案 */\\n.kb-no-doc-mobile-tip[data-v-1ce90f0b] {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n margin-top: 14px;\\n padding: 8px 14px;\\n border-radius: 10px;\\n background: #f5f7fa;\\n color: #909399;\\n font-size: 12px;\\n line-height: 1.5;\\n}\\n.kb-no-doc-mobile-tip svg[data-v-1ce90f0b] {\\n flex-shrink: 0;\\n color: #1677ff;\\n}\\n/* 移动端无文档卡片收窄内边距,适配小屏 */\\n.kb-chat-root.is-mobile .kb-no-doc-mask[data-v-1ce90f0b] {\\n /* 下移让出头部,避免遮挡返回按钮/标题(头部高 ≈ 57px:padding 20 + 内容 36 + 边框 1) */\\n top: 57px;\\n z-index: 9; /* 降到头部(z-index: 10)之下,双保险防止遮挡 */\\n padding: 16px;\\n}\\n/* 移动端引导区收窄外边距,保证引导遮罩在首屏可见 */\\n.kb-chat-root.is-mobile .chat-empty-welcome[data-v-1ce90f0b] {\\n margin: 24px auto;\\n padding: 16px 14px;\\n}\\n.kb-chat-root.is-mobile .welcome-card[data-v-1ce90f0b] {\\n padding: 18px 16px 18px 118px;\\n border-radius: 14px 14px 0 0;\\n}\\n.kb-chat-root.is-mobile .welcome-avatar-box[data-v-1ce90f0b] {\\n left: 8px;\\n width: 96px;\\n height: 120px;\\n}\\n.kb-chat-root.is-mobile .kb-no-doc-box[data-v-1ce90f0b] {\\n gap: 16px;\\n max-width: 100%;\\n padding: 36px 24px;\\n border-radius: 18px;\\n}\\n.kb-chat-root.is-mobile .kb-no-doc-icon[data-v-1ce90f0b] {\\n width: 60px;\\n height: 60px;\\n}\\n.kb-chat-root.is-mobile .kb-no-doc-text[data-v-1ce90f0b] {\\n font-size: 14px !important;\\n}\\n\\n/* 移动端会话「…」按钮 */\\n.session-more-btn--m[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 26px;\\n height: 26px;\\n border-radius: 6px;\\n border: none;\\n background: transparent;\\n color: #909399;\\n cursor: pointer;\\n}\\n\\n/* 移动端会话操作单 */\\n.kb-m-actionsheet[data-v-1ce90f0b] {\\n position: absolute;\\n top: 0;\\n right: 0;\\n bottom: 0;\\n left: 0;\\n z-index: 40;\\n display: flex;\\n align-items: flex-end;\\n justify-content: center;\\n background: rgba(0, 0, 0, 0.35);\\n -webkit-animation: kb-m-fade-data-v-1ce90f0b 0.15s ease-out;\\n animation: kb-m-fade-data-v-1ce90f0b 0.15s ease-out;\\n /* 操作单在长按未抬手时弹出,手指仍停留在菜单上:\\n Android/iOS 会顺势进入文本选择、把「复制」等文字选中。\\n 禁用选中与系统长按菜单,只保留点击。 */\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n -ms-user-select: none;\\n user-select: none;\\n -webkit-touch-callout: none;\\n}\\n.kb-m-actionsheet-box[data-v-1ce90f0b] {\\n width: 100%;\\n max-width: 520px;\\n box-sizing: border-box;\\n padding: 8px;\\n background: #f2f3f5;\\n border-radius: 12px 12px 0 0;\\n}\\n.kb-m-actionsheet-title[data-v-1ce90f0b] {\\n padding: 12px 14px;\\n font-size: 13px;\\n color: #909399;\\n text-align: center;\\n word-break: break-all;\\n}\\n.kb-m-actionsheet-item[data-v-1ce90f0b] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n gap: 6px;\\n height: 50px;\\n background: #fff;\\n border-radius: 8px;\\n font-size: 16px;\\n color: #303133;\\n margin-bottom: 8px;\\n}\\n.kb-m-actionsheet-item.danger[data-v-1ce90f0b] { color: #ff4d4f;\\n}\\n.kb-m-actionsheet-item[data-v-1ce90f0b]:active { background: #f5f6f8;\\n}\\n.kb-m-actionsheet-cancel[data-v-1ce90f0b] {\\n height: 50px;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background: #fff;\\n border-radius: 8px;\\n font-size: 16px;\\n color: #303133;\\n}\\n.kb-m-actionsheet-cancel[data-v-1ce90f0b]:active { background: #f5f6f8;\\n}\\n@-webkit-keyframes kb-m-fade-data-v-1ce90f0b {\\nfrom { opacity: 0;\\n}\\nto { opacity: 1;\\n}\\n}\\n@keyframes kb-m-fade-data-v-1ce90f0b {\\nfrom { opacity: 0;\\n}\\nto { opacity: 1;\\n}\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1793
1793
|
|
|
1794
1794
|
/***/ }),
|
|
1795
1795
|
|
|
@@ -14747,7 +14747,7 @@ eval("// Generated by CoffeeScript 1.12.7\n(function() {\n var XMLDocument, XML
|
|
|
14747
14747
|
/*! exports provided: name, version, main, private, scripts, dependencies, devDependencies, browserslist, directories, keywords, license, files, default */
|
|
14748
14748
|
/***/ (function(module) {
|
|
14749
14749
|
|
|
14750
|
-
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.26-beta.
|
|
14750
|
+
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.26-beta.5\\\",\\\"main\\\":\\\"lib/ZjPluginIntelligent.umd.min.js\\\",\\\"private\\\":false,\\\"scripts\\\":{\\\"serve\\\":\\\"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve\\\",\\\"build\\\":\\\"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build\\\",\\\"lib\\\":\\\"node scripts/build-lib.js\\\",\\\"lib:test\\\":\\\"node scripts/build-lib.js test\\\"},\\\"dependencies\\\":{\\\"@jboltai/tokui\\\":\\\"^0.1.6\\\",\\\"crypto-js\\\":\\\"^4.2.0\\\",\\\"docx\\\":\\\"^8.5.0\\\",\\\"html2canvas\\\":\\\"^1.4.1\\\",\\\"mammoth\\\":\\\"^1.6.0\\\",\\\"markdown-it\\\":\\\"^13.0.2\\\",\\\"marked\\\":\\\"^4.3.0\\\",\\\"xlsx\\\":\\\"^0.18.5\\\"},\\\"devDependencies\\\":{\\\"@babel/preset-env\\\":\\\"^7.14.9\\\",\\\"@vue/cli-plugin-babel\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-eslint\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-vuex\\\":\\\"~4.5.0\\\",\\\"@vue/cli-service\\\":\\\"~4.5.0\\\",\\\"axios\\\":\\\"^0.27.2\\\",\\\"babel-eslint\\\":\\\"^10.1.0\\\",\\\"babel-plugin-component\\\":\\\"^1.1.1\\\",\\\"compression-webpack-plugin\\\":\\\"^6.0.3\\\",\\\"core-js\\\":\\\"^3.6.5\\\",\\\"element-ui\\\":\\\"^2.15.4\\\",\\\"eslint\\\":\\\"^6.8.0\\\",\\\"eslint-plugin-vue\\\":\\\"^7.13.0\\\",\\\"js-cookie\\\":\\\"^2.2.1\\\",\\\"obs-upload\\\":\\\"^1.0.4\\\",\\\"pako\\\":\\\"^2.1.0\\\",\\\"reconnecting-websocket\\\":\\\"^4.4.0\\\",\\\"sass\\\":\\\"^1.71.0\\\",\\\"sass-loader\\\":\\\"^8.0.2\\\",\\\"snowflake-id\\\":\\\"^1.1.0\\\",\\\"vue\\\":\\\"^2.6.11\\\",\\\"vue-template-compiler\\\":\\\"^2.6.11\\\",\\\"vuex\\\":\\\"^3.4.0\\\",\\\"webpack-bundle-analyzer\\\":\\\"^4.4.2\\\"},\\\"browserslist\\\":[\\\"> 1%\\\",\\\"last 2 versions\\\",\\\"not dead\\\"],\\\"directories\\\":{\\\"lib\\\":\\\"lib\\\"},\\\"keywords\\\":[],\\\"license\\\":\\\"ISC\\\",\\\"files\\\":[\\\"lib\\\"]}\");\n\n//# sourceURL=webpack://ZjPluginIntelligent/./package.json?");
|
|
14751
14751
|
|
|
14752
14752
|
/***/ }),
|
|
14753
14753
|
|