zj-plugin-intelligent 1.2.5-beta.27 → 1.2.5-beta.29

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.
@@ -951,7 +951,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
951
951
  /***/ (function(module, exports, __webpack_require__) {
952
952
 
953
953
  "use strict";
954
- eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js */ \"./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js\").default;\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n__webpack_require__(/*! core-js/modules/es.error.cause.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.error.cause.js\");\n__webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array.push.js\");\n__webpack_require__(/*! core-js/modules/es.array-buffer.detached.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array-buffer.detached.js\");\n__webpack_require__(/*! core-js/modules/es.array-buffer.transfer.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array-buffer.transfer.js\");\n__webpack_require__(/*! core-js/modules/es.array-buffer.transfer-to-fixed-length.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array-buffer.transfer-to-fixed-length.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.constructor.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.constructor.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.find.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.find.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.for-each.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.for-each.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.map.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.map.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.at.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.at.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.find-last.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.find-last.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.find-last-index.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.find-last-index.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.set.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.set.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.to-reversed.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.to-reversed.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.to-sorted.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.to-sorted.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.with.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.with.js\");\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/_markdown-it@14.1.0@markdown-it/index.mjs\"));\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\nvar _index = __webpack_require__(/*! @lib/socket/fileManager/obs/index */ \"./src/lib/socket/fileManager/obs/index.js\");\nvar _index2 = _interopRequireDefault(__webpack_require__(/*! @lib/socket/utils/index */ \"./src/lib/socket/utils/index.js\"));\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\nvar _apiObs = _interopRequireDefault(__webpack_require__(/*! @lib/api/apiObs */ \"./src/lib/api/apiObs.js\"));\nvar _html2canvas = _interopRequireDefault(__webpack_require__(/*! html2canvas */ \"./node_modules/_html2canvas@1.4.1@html2canvas/dist/html2canvas.js\"));\nconst md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default = exports.default = {\n name: 'ChatDetail',\n props: {\n isMobile: {\n type: Boolean,\n default: false\n },\n activeSessionId: {\n type: String,\n default: ''\n },\n currentAgent: {\n type: String,\n default: ''\n },\n agentInfo: {\n type: Object,\n default: () => ({})\n },\n currentAgentDesc: {\n type: String,\n default: ''\n },\n showSidebar: {\n type: Boolean,\n default: false\n },\n showH5List: {\n type: Boolean,\n default: false\n },\n showAgentSelect: {\n type: Boolean,\n default: false\n },\n agentList: {\n type: Array,\n default: () => []\n }\n },\n data() {\n return {\n messages: [],\n inputText: '',\n sending: false,\n waitTimer: null,\n waitSeconds: 0,\n canceling: false,\n msgPage: 1,\n msgMaxPage: 1,\n msgTotal: 0,\n eventSource: null,\n showToolInfo: false,\n showDownBtn: false,\n lastScrollTop: 0,\n scrollTimer: null,\n contentCache: {},\n loadingSessionMsg: false,\n greeting: '',\n _scrollTimer: null,\n _userScrolledUp: false,\n voiceMode: false,\n isRecording: false,\n recordDuration: 0,\n recordingTimer: null,\n audioContext: null,\n audioStream: null,\n audioProcessor: null,\n audioChunks: [],\n audioSampleRate: 16000,\n isCancelSwipe: false,\n recordStartY: 0,\n _pendingSendText: null\n };\n },\n computed: {\n waitClass() {\n if (this.waitSeconds > 15) return 'wait-15';\n if (this.waitSeconds > 10) return 'wait-10';\n if (this.waitSeconds > 5) return 'wait-5';\n return '';\n },\n currentAgentName() {\n if (!this.currentAgent) return '';\n const a = this.agentList.find(x => x.agentCode === this.currentAgent);\n return a ? a.agentName || a.agentCode : this.currentAgent;\n }\n },\n watch: {\n contentCache: {\n handler(val) {\n // 保持响应性\n },\n immediate: true\n },\n activeSessionId(val) {\n if (val && this._pendingSendText) {\n const text = this._pendingSendText;\n this._pendingSendText = null;\n this.$nextTick(() => this._sendMessage(text));\n }\n }\n },\n mounted() {\n if (window.visualViewport) {\n window.visualViewport.addEventListener('resize', this.onViewportResize, {\n passive: true\n });\n }\n },\n beforeDestroy() {\n if (window.visualViewport) {\n window.visualViewport.removeEventListener('resize', this.onViewportResize);\n }\n this.releaseAudio();\n this.contentCache = {};\n clearTimeout(this._scrollTimer);\n if (this.eventSource) {\n this.eventSource.onerror = null;\n this.eventSource.close();\n this.eventSource = null;\n }\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n },\n methods: {\n getInitial(name) {\n if (!name) return '?';\n return name.charAt(0);\n },\n formatTime(t) {\n if (!t) return '';\n const d = new Date(t.replace(' ', 'T'));\n const now = new Date();\n const diff = (now - d) / 1000;\n if (diff < 60) return '刚刚';\n if (diff < 3600) return Math.floor(diff / 60) + '分钟前';\n if (diff < 86400) return Math.floor(diff / 3600) + '小时前';\n return d.getMonth() + 1 + '/' + d.getDate();\n },\n escapeHtml(text) {\n if (!text) return '';\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n },\n renderMarkdown(text) {\n if (!text) return '';\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n renderStreaming(text, msgKey) {\n if (!text) return '';\n const cache = this.contentCache[msgKey];\n const lines = text.split('\\n');\n const lastLineIdx = lines.length - 1;\n if (!cache) {\n try {\n const html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html,\n fullText: text\n });\n return html;\n } catch {\n return this.escapeHtml(text);\n }\n }\n if (text === cache.fullText) return cache.html;\n cache.fullText = text;\n\n // 行数没变,仅末尾追加字符,直接拼接尾行\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n }\n\n // 场景1:行数变少/文本回退截断,全量重渲染\n if (lastLineIdx < cache.renderedLines) {\n try {\n const fullLines = lines.slice(0, lastLineIdx);\n let newBaseHtml = '';\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n const lastLine = this.escapeHtml(lines[lastLineIdx]);\n return cache.html + (lastLine || '');\n } catch {\n return this.escapeHtml(text);\n }\n }\n\n // 场景2:新增完整行\n if (cache.renderedLines < lastLineIdx) {\n const allFullLines = lines.slice(0, lastLineIdx);\n let newBaseHtml = '';\n if (allFullLines.length > 0) {\n newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n const lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n getChatAvatarStyle(agent, avatar) {\n if (avatar) {\n return {\n backgroundImage: `url(${avatar})`,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n }\n const _agent = this.agentList.find(x => x.agentCode === agent);\n if (_agent && _agent.avatar) {\n return {\n backgroundImage: `url(${_agent.avatar})`,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n }\n // 没有网络图片时,根据智能体在 agentList 中的索引位置决定颜色\n const index = this.agentList.findIndex(item => item.agentCode === agent);\n if (index !== -1) {\n const colorIndex = index % 4 + 1;\n return {\n background: this.getGradientByColorIndex(colorIndex)\n };\n }\n return {\n background: 'linear-gradient(135deg, #1677ff, #4096ff)'\n };\n },\n getGradientByColorIndex(index) {\n const gradients = {\n 1: 'linear-gradient(135deg, #1677ff, #4096ff)',\n 2: 'linear-gradient(135deg, #52c41a, #73d13d)',\n 3: 'linear-gradient(135deg, #fa8c16, #ffa940)',\n 4: 'linear-gradient(135deg, #722ed1, #b37feb)'\n };\n return gradients[index] || gradients[1];\n },\n sendQuickMsg(message) {\n if (message) {\n this.inputText = message;\n this.sendMessage();\n }\n },\n formatMixText(str) {\n if (!str) return '';\n let res = str.replace(/([a-zA-Z0-9])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z0-9])/g, '$1 $2');\n res = res.replace(/([a-z])([A-Z])/g, '$1 $2');\n return res;\n },\n // 检测是否滚动到底部,控制向下箭头显示隐藏\n checkScrollBottom(forceShowWhenNotBottom = false) {\n const el = this.$refs.messagesRef;\n if (!el) {\n this.showDownBtn = false;\n return;\n }\n const {\n scrollHeight,\n scrollTop,\n clientHeight\n } = el;\n const remain = scrollHeight - scrollTop - clientHeight;\n const atBottom = remain <= 80;\n\n // 检测用户是否主动向上滚动(远离底部)\n if (remain > 80 && scrollTop < this.lastScrollTop) {\n this._userScrolledUp = true;\n }\n if (atBottom) {\n this._userScrolledUp = false;\n this.showDownBtn = false;\n } else if (this._userScrolledUp) {\n // 用户已主动上滚,保持显示\"到底部\"按钮,不自动回滚\n this.showDownBtn = true;\n } else if (this.sending && scrollTop >= this.lastScrollTop && !forceShowWhenNotBottom) {\n this.showDownBtn = false;\n } else {\n this.showDownBtn = true;\n }\n this.lastScrollTop = scrollTop;\n this.$emit('scroll-bottom-check', {\n showDownBtn: this.showDownBtn,\n lastScrollTop: this.lastScrollTop\n });\n },\n // H5 输入框获取焦点 - 滚动到底部让键盘不遮挡\n onInputFocus() {\n if (!this.isMobile) return;\n setTimeout(() => {\n window.scrollTo(0, document.body.scrollHeight);\n }, 300);\n },\n // visualViewport 变化时(如键盘弹出)调整滚动位置\n onViewportResize() {\n if (!this.isMobile) return;\n const activeEl = document.activeElement;\n if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.tagName === 'INPUT')) {\n window.scrollTo(0, document.body.scrollHeight);\n }\n },\n autoResizeTextarea() {\n const ta = this.$refs.taRef;\n if (!ta) return;\n const isMobile = this.isMobile;\n const baseH = isMobile ? 36 : 100;\n const maxH = isMobile ? 100 : 180;\n ta.style.height = `${baseH}px`;\n const realH = ta.scrollHeight;\n if (realH > baseH) {\n ta.style.height = `${Math.min(realH, maxH)}px`;\n }\n this.$nextTick(() => {\n this.checkScrollBottom(true);\n });\n },\n // Ctrl+Enter换行\n newLine(e) {\n e.preventDefault();\n const ta = this.$refs.taRef;\n const start = ta.selectionStart;\n const end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(() => {\n ta.selectionStart = ta.selectionEnd = start + 1;\n this.autoResizeTextarea();\n });\n },\n toggleVoiceMode() {\n if (this.voiceMode) {\n this.voiceMode = false;\n this.releaseAudio();\n return;\n }\n // 进入语音模式前提前请求麦克风权限,避免触摸时异步弹窗导致 touchend 丢失\n this.ensureAudioReady().then(ready => {\n if (ready) {\n this.voiceMode = true;\n } else {\n this.voiceMode = false;\n }\n });\n },\n // 确保音频设备已就绪\n async ensureAudioReady() {\n if (this.audioContext && this.audioStream) return true;\n if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\n alert('当前环境不支持麦克风访问');\n return false;\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true\n });\n this.audioStream = stream;\n const audioContext = new (window.AudioContext || window.webkitAudioContext)();\n // iOS 上 AudioContext 默认 suspended,需要手动 resume\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n this.audioContext = audioContext;\n // 采集实际采样率(iOS 会忽略 sampleRate 参数,使用硬件采样率)\n this.audioSampleRate = audioContext.sampleRate || 16000;\n const source = audioContext.createMediaStreamSource(stream);\n // 保存 source 引用到实例,防止 iOS Safari 垃圾回收导致音频处理中断\n this.audioSource = source;\n const processor = audioContext.createScriptProcessor(4096, 1, 1);\n this.audioProcessor = processor;\n\n // iOS 上 onaudioprocess 可能先于 isRecording=true 触发\n // 因此不依赖 isRecording 过滤,由外部控制 audioChunks 的清零时机\n processor.onaudioprocess = e => {\n const inputData = e.inputBuffer.getChannelData(0);\n this.audioChunks.push(new Float32Array(inputData));\n };\n source.connect(processor);\n processor.connect(audioContext.destination);\n return true;\n } catch (err) {\n console.error('麦克风访问失败:', err);\n alert('麦克风权限被拒绝');\n return false;\n }\n },\n // H5语音录制 - 触摸开始\n async onRecordTouchStart(e) {\n if (this.sending) return;\n const ready = await this.ensureAudioReady();\n if (!ready) return;\n this.isCancelSwipe = false;\n this.recordStartY = e.touches[0].clientY;\n this.recordDuration = 0;\n this.audioChunks = [];\n this.isRecording = true;\n this.recordingTimer = setInterval(() => {\n this.recordDuration++;\n if (this.recordDuration >= 60) {\n this.onRecordTouchEnd();\n }\n }, 1000);\n },\n // H5语音录制 - 触摸移动\n onRecordTouchMove(e) {\n if (!this.isRecording) return;\n const touch = e.touches[0];\n const moveY = touch.clientY;\n const cancelThreshold = 60;\n this.isCancelSwipe = this.recordStartY - moveY > cancelThreshold;\n },\n // H5语音录制 - 触摸结束\n onRecordTouchEnd() {\n if (!this.isRecording) return;\n this.isRecording = false;\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n this.stopAudioCapture(!this.isCancelSwipe);\n },\n // 停止音频采集并输出 WAV\n stopAudioCapture(shouldSend) {\n this.isRecording = false;\n if (shouldSend && this.audioChunks.length > 0) {\n const wavBlob = this.encodePCMToWav(this.audioChunks, this.audioSampleRate || 16000);\n this.sendAudioBlob(wavBlob);\n }\n this.audioChunks = [];\n },\n // 取消录音\n cancelRecording() {\n this.isRecording = false;\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n this.recordDuration = 0;\n this.isCancelSwipe = false;\n this.audioChunks = [];\n },\n // 释放音频资源\n releaseAudio() {\n this.cancelRecording();\n if (this.audioProcessor) {\n this.audioProcessor.disconnect();\n this.audioProcessor = null;\n }\n if (this.audioSource) {\n this.audioSource.disconnect();\n this.audioSource = null;\n }\n if (this.audioContext) {\n this.audioContext.close().catch(() => {});\n this.audioContext = null;\n }\n if (this.audioStream) {\n this.audioStream.getTracks().forEach(t => t.stop());\n this.audioStream = null;\n }\n },\n // 将 Float32 PCM 数据编码为 WAV Blob\n encodePCMToWav(chunks, sampleRate) {\n let totalLength = 0;\n for (const chunk of chunks) {\n totalLength += chunk.length;\n }\n const pcmData = new Int16Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n for (let i = 0; i < chunk.length; i++) {\n const s = Math.max(-1, Math.min(1, chunk[i]));\n pcmData[offset++] = s < 0 ? s * 0x8000 : s * 0x7FFF;\n }\n }\n const numChannels = 1;\n const bitsPerSample = 16;\n const byteRate = sampleRate * numChannels * bitsPerSample / 8;\n const blockAlign = numChannels * bitsPerSample / 8;\n const dataSize = pcmData.length * (bitsPerSample / 8);\n const headerSize = 44;\n const totalSize = headerSize + dataSize;\n const buffer = new ArrayBuffer(totalSize);\n const view = new DataView(buffer);\n this._wavWriteString(view, 0, 'RIFF');\n view.setUint32(4, totalSize - 8, true);\n this._wavWriteString(view, 8, 'WAVE');\n this._wavWriteString(view, 12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, numChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bitsPerSample, true);\n this._wavWriteString(view, 36, 'data');\n view.setUint32(40, dataSize, true);\n const pcmView = new Int16Array(buffer, headerSize, pcmData.length);\n pcmView.set(pcmData);\n return new Blob([buffer], {\n type: 'audio/wav'\n });\n },\n _wavWriteString(view, offset, str) {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n },\n // 发送音频\n sendAudioBlob(blob) {\n let appSeqId = _index2.default.getAppSeqId();\n let name = appSeqId + '.wav';\n const file = new File([blob], name, {\n type: 'audio/wav'\n });\n this.sendAudioMessage(file);\n },\n sendAudioMessage(file) {\n const sttKey = 'stt_' + Date.now();\n const sttMsg = {\n role: 'user',\n content: '转化中...',\n _sttStatus: 'pending',\n _sttKey: sttKey,\n createTime: new Date().toISOString(),\n _key: sttKey\n };\n this.messages.push(sttMsg);\n this.scrollToBottom(true);\n (0, _index.fileUploadToObs)({\n _file: file,\n appSeqId: file.name\n }).then(resp => {\n if (resp.CommonMsg.Status === 200) {\n return _apiObs.default.aliAudioToText({\n url: resp.obs_upload_data.obsUrl\n }).then(result => {\n if (result.code === '200' && result.data) {\n const idx = this.messages.findIndex(m => m._sttKey === sttKey);\n if (idx !== -1) {\n this.messages.splice(idx, 1);\n }\n this.sendQuickMsg(result.data);\n } else {\n throw new Error(result.msg || '语音转文字失败');\n }\n });\n } else {\n throw new Error(resp.CommonMsg.Message || '文件上传失败');\n }\n }).catch(err => {\n console.error('语音转文字失败:', err);\n const idx = this.messages.findIndex(m => m._sttKey === sttKey);\n if (idx !== -1) {\n this.$set(this.messages, idx, {\n ...this.messages[idx],\n _sttStatus: 'error',\n content: '语音转文字失败'\n });\n }\n this.$message.error('语音转文字失败');\n });\n this.$message.success(`录音完成 (${this.recordDuration}s)`);\n },\n clearWaitTimer() {\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n },\n loadMoreMessages() {\n const nextPage = this.msgPage + 1;\n const maxPage = Math.ceil(this.msgTotal / 10);\n if (nextPage > maxPage) {\n this.loadingMore = false;\n return;\n }\n (0, _apichat.getSessionMessages)(this.activeSessionId, nextPage, 10).then(res => {\n const list = Array.isArray(res) ? res : res.list || [];\n this.msgPage = nextPage;\n const asAsc = (list || []).slice().reverse();\n const prepend = asAsc.map(m => ({\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: '',\n createTime: m.createTime || null,\n _key: m.createTime ? 'msg_' + m.createTime : 'msg_' + Date.now() + '_' + Math.random()\n }));\n const container = this.$refs.messagesRef;\n const oldScrollHeight = container ? container.scrollHeight : 0;\n const oldScrollTop = container ? container.scrollTop : 0;\n this.messages = [...prepend, ...this.messages];\n this.$nextTick(() => {\n if (container) {\n container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);\n }\n this.loadingMore = false;\n });\n }).catch(() => {\n this.loadingMore = false;\n });\n },\n onScrollMessages() {\n const container = this.$refs.messagesRef;\n if (!container || this.loadingMore) return;\n const isAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 100;\n this.checkScrollBottom();\n if (isAtBottom) return;\n if (container.scrollTop < 50 && this.msgPage < Math.ceil(this.msgTotal / 10)) {\n this.loadingMore = true;\n this.loadMoreMessages();\n }\n },\n async sendMessage() {\n const text = this.inputText.trim();\n if (!text || this.sending) return;\n if (text.length > 300) {\n this.$message({\n message: '消息内容不能超过300字',\n type: 'warning',\n duration: 2000\n });\n return;\n }\n if (!this.activeSessionId) {\n this.$emit('start-session');\n this._pendingSendText = text;\n return;\n }\n this._sendMessage(text);\n },\n async _sendMessage(text) {\n this.inputText = '';\n const ta = this.$refs.taRef;\n const isMobile = this.isMobile;\n const baseH = isMobile ? 36 : 100;\n if (ta) {\n ta.style.height = baseH + 'px';\n }\n this.showDownBtn = false;\n this.messages.push({\n role: 'user',\n content: text,\n createTime: new Date().toISOString(),\n _key: 'user_' + Date.now() + '_' + Math.random()\n });\n const agentName = this.currentAgentName;\n const msgIdx = this.messages.push({\n role: \"assistant\",\n content: \"\",\n thinking: \"\",\n loading: true,\n toolCalls: [],\n agentName,\n createTime: new Date().toISOString(),\n _key: 'asst_' + Date.now() + '_' + Math.random()\n }) - 1;\n this.scrollToBottom(true);\n this.sending = true;\n this.waitSeconds = 0;\n if (this.waitTimer) clearInterval(this.waitTimer);\n this.waitTimer = setInterval(() => {\n this.waitSeconds++;\n }, 1000);\n let thinkingBuffer = '';\n let finalContent = '';\n let answerBuffer = '';\n let thinkDisplayed = 0;\n let typeTimer = null;\n let streamDone = false;\n typeTimer = setInterval(() => {\n if (!this.messages[msgIdx]) return;\n\n // Phase 1: 逐字展示思考内容(不 return,让 Phase 2 也能同时执行)\n if (thinkDisplayed < thinkingBuffer.length) {\n thinkDisplayed += 50;\n if (thinkDisplayed > thinkingBuffer.length) thinkDisplayed = thinkingBuffer.length;\n this.messages[msgIdx].thinking = thinkingBuffer.substring(0, thinkDisplayed);\n this.scrollToBottom();\n // 不 return,让 Phase 2 也能执行,避免 iOS 上思考内容过长阻塞回答内容展示\n }\n\n // Phase 2: 逐行展示(已完成的行整行出现,未完成的行也即时显示)\n const target = finalContent || answerBuffer;\n const msg = this.messages[msgIdx];\n const revealedLen = msg._revealedLen || 0;\n if (target.length > revealedLen) {\n const remaining = target.substring(revealedLen);\n const nextNewline = remaining.indexOf('\\n');\n if (nextNewline !== -1) {\n // 新的一行已完成 → 整行出现\n const endPos = revealedLen + nextNewline + 1;\n msg._revealedLen = endPos;\n msg.content = target.substring(0, endPos);\n this.scrollToBottom();\n return;\n }\n\n // 当前行未完成 → 逐步展示(每次 30 字符),确保 iOS 上也有渐进效果\n const revealStep = 30;\n const newRevealedLen = Math.min(revealedLen + revealStep, target.length);\n msg._revealedLen = newRevealedLen;\n msg.content = target.substring(0, newRevealedLen);\n this.scrollToBottom();\n return;\n }\n\n // Phase 3: 流已结束且内容已全部展示 → 收尾\n if (streamDone) {\n clearInterval(typeTimer);\n const key = this.messages[msgIdx]._key;\n if (key) {\n this.$delete(this.contentCache, key);\n this.$delete(this.contentCache, key + '_t');\n }\n typeTimer = null;\n this.messages[msgIdx].content = finalContent;\n this.messages[msgIdx].thinking = thinkingBuffer;\n this.messages[msgIdx].loading = false;\n this.sending = false;\n this.clearWaitTimer();\n this.scrollToBottom();\n this.$emit('session-refresh');\n this.$emit('message-sent');\n }\n }, 30);\n const url = (0, _apichat.createStreamUrl)(this.activeSessionId, text);\n if (this.eventSource) {\n this.eventSource.close();\n }\n this.eventSource = new EventSource(url);\n const es = this.eventSource;\n es.addEventListener('reasoning', e => {\n if (e.data) {\n const chunk = e.data;\n let cleanChunk = chunk.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2').replace(/\\s+([:,.!?])/g, '$1').replace(/([:,.!?])\\s+/g, '$1 ');\n cleanChunk = cleanChunk.replace(/\\s+/g, ' ');\n const lastChar = thinkingBuffer.slice(-1);\n const firstChar = cleanChunk.slice(0, 1);\n const isLetter = c => /[a-zA-Z]/.test(c);\n if (isLetter(lastChar) && isLetter(firstChar)) {\n thinkingBuffer += ' ' + cleanChunk;\n } else {\n thinkingBuffer += cleanChunk;\n }\n }\n });\n es.addEventListener('reasoning_result', e => {\n if (e.data) {\n thinkingBuffer = e.data;\n }\n });\n es.addEventListener('answer', e => {\n if (e.data) {\n answerBuffer += e.data.replace(/([a-zA-Z]+)([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z]+)/g, '$1 $2').replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2');\n }\n });\n es.addEventListener('tool_result', e => {\n if (e.data && this.messages[msgIdx]) {\n if (!this.messages[msgIdx].toolCalls) this.messages[msgIdx].toolCalls = [];\n this.messages[msgIdx].toolCalls.push(e.data);\n this.scrollToBottom();\n }\n });\n es.addEventListener('agent_result', e => {\n finalContent = e.data || '';\n streamDone = true;\n es.close();\n this.eventSource = null;\n });\n es.onerror = err => {\n console.log('[SSE] onerror', err);\n if (thinkingBuffer.length > 0 && !streamDone) finalContent = thinkingBuffer;\n if (!streamDone) finalContent = '❌ 连接中断';\n streamDone = true;\n es.close();\n this.eventSource = null;\n };\n },\n stopAgent() {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n if (this.activeSessionId) {\n (0, _apichat.stopStream)(this.activeSessionId).catch(() => {});\n }\n if (this.messages.length > 0) {\n const last = this.messages[this.messages.length - 1];\n if (last && last.loading) {\n last.content = last.content || last.thinking || '⏹ 已停止';\n last.loading = false;\n this.$delete(this.contentCache, last._key);\n }\n }\n this.sending = false;\n this.clearWaitTimer();\n },\n scrollToTop() {\n const el = this.$refs.messagesRef;\n if (el) el.scrollTop = 0;\n },\n scrollToBottom(force = false) {\n if (this.showDownBtn && !force) return;\n this.$nextTick(() => {\n const el = this.$refs.messagesRef;\n if (el) {\n el.scrollTop = el.scrollHeight;\n this.lastScrollTop = el.scrollTop;\n this.showDownBtn = false;\n this._userScrolledUp = false;\n }\n });\n },\n scrollToBottomOnce() {\n clearTimeout(this._scrollTimer);\n this.$nextTick(() => {\n const tryScroll = () => {\n const el = this.$refs.messagesRef;\n if (!el) {\n this._scrollTimer = setTimeout(tryScroll, 50);\n return;\n }\n const originalScrollBehavior = el.style.scrollBehavior;\n el.style.scrollBehavior = 'auto';\n el.scrollTop = el.scrollHeight - el.clientHeight;\n el.style.scrollBehavior = originalScrollBehavior;\n this.showDownBtn = false;\n this._userScrolledUp = false;\n };\n tryScroll();\n });\n },\n copyText(content) {\n if (!content) return;\n const text = content.replace(/<[^>]+>/g, '');\n\n // 优先使用 Clipboard API(HTTPS 环境)\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(() => {\n this.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(() => {\n this._fallbackCopyText(text);\n });\n } else {\n this._fallbackCopyText(text);\n }\n },\n _fallbackCopyText(text) {\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0';\n document.body.appendChild(ta);\n ta.focus();\n ta.select();\n document.execCommand('copy');\n document.body.removeChild(ta);\n this.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n } catch {\n this.$message({\n message: '复制失败',\n type: 'error',\n duration: 2000\n });\n }\n },\n copyAsImage(content) {\n if (!content) return;\n const div = document.createElement('div');\n div.className = 'copy-image-render';\n div.style.cssText = 'position:fixed;left:-9999px;top:0;width:600px;padding:20px;background:#fff;font-size:14px;line-height:1.6;color:#333;border-radius:8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;';\n // 内联表格样式(因 Vue 作用域样式不覆盖复制用的脱离文档流 div)\n div.innerHTML = this.renderMarkdown(content) + '<style>table{border-collapse:collapse;width:100%;margin:6px 0;font-size:14px}' + 'th,td{border:1px solid #e8e8e8;padding:5px 8px;text-align:left}' + 'th{background:#fafafa;font-weight:600}</style>';\n document.body.appendChild(div);\n this.$message({\n message: '正在生成图片...',\n type: 'info',\n duration: 2000\n });\n (0, _html2canvas.default)(div, {\n scale: 2,\n useCORS: true,\n backgroundColor: '#ffffff'\n }).then(canvas => {\n // 检测是否支持 ClipboardItem(部分 WebView 不支持)\n const canCopyImage = typeof ClipboardItem !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.write === 'function';\n if (canCopyImage) {\n canvas.toBlob(blob => {\n if (blob) {\n navigator.clipboard.write([new ClipboardItem({\n 'image/png': blob\n })]).then(() => {\n this.$message({\n message: '图片已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(() => {\n // ClipboardItem 存在但写入失败 → 降级下载\n this._downloadCanvas(canvas);\n });\n }\n }, 'image/png');\n } else {\n // 不支持剪贴板写图片 → 直接下载\n this._downloadCanvas(canvas);\n }\n }).catch(() => {\n this.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n }).finally(() => {\n document.body.removeChild(div);\n });\n },\n _downloadCanvas(canvas) {\n const link = document.createElement('a');\n link.download = '消息图片.png';\n link.href = canvas.toDataURL('image/png');\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n this.$message({\n message: '已保存图片到本地',\n type: 'success',\n duration: 2000\n });\n }\n }\n};\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatDetail.vue?./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--13-0!./node_modules/_babel-loader@8.4.1@babel-loader/lib!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
954
+ eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js */ \"./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js\").default;\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n__webpack_require__(/*! core-js/modules/es.error.cause.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.error.cause.js\");\n__webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array.push.js\");\n__webpack_require__(/*! core-js/modules/es.array-buffer.detached.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array-buffer.detached.js\");\n__webpack_require__(/*! core-js/modules/es.array-buffer.transfer.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array-buffer.transfer.js\");\n__webpack_require__(/*! core-js/modules/es.array-buffer.transfer-to-fixed-length.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.array-buffer.transfer-to-fixed-length.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.constructor.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.constructor.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.find.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.find.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.for-each.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.for-each.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.map.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.map.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.at.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.at.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.find-last.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.find-last.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.find-last-index.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.find-last-index.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.set.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.set.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.to-reversed.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.to-reversed.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.to-sorted.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.to-sorted.js\");\n__webpack_require__(/*! core-js/modules/es.typed-array.with.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.typed-array.with.js\");\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/_markdown-it@14.1.0@markdown-it/index.mjs\"));\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\nvar _index = __webpack_require__(/*! @lib/socket/fileManager/obs/index */ \"./src/lib/socket/fileManager/obs/index.js\");\nvar _index2 = _interopRequireDefault(__webpack_require__(/*! @lib/socket/utils/index */ \"./src/lib/socket/utils/index.js\"));\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\nvar _apiObs = _interopRequireDefault(__webpack_require__(/*! @lib/api/apiObs */ \"./src/lib/api/apiObs.js\"));\nvar _html2canvas = _interopRequireDefault(__webpack_require__(/*! html2canvas */ \"./node_modules/_html2canvas@1.4.1@html2canvas/dist/html2canvas.js\"));\nconst md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default = exports.default = {\n name: 'ChatDetail',\n props: {\n isMobile: {\n type: Boolean,\n default: false\n },\n activeSessionId: {\n type: String,\n default: ''\n },\n currentAgent: {\n type: String,\n default: ''\n },\n agentInfo: {\n type: Object,\n default: () => ({})\n },\n currentAgentDesc: {\n type: String,\n default: ''\n },\n showSidebar: {\n type: Boolean,\n default: false\n },\n showH5List: {\n type: Boolean,\n default: false\n },\n showAgentSelect: {\n type: Boolean,\n default: false\n },\n agentList: {\n type: Array,\n default: () => []\n }\n },\n data() {\n return {\n messages: [],\n inputText: '',\n sending: false,\n waitTimer: null,\n waitSeconds: 0,\n canceling: false,\n msgPage: 1,\n msgMaxPage: 1,\n msgTotal: 0,\n eventSource: null,\n showToolInfo: false,\n showDownBtn: false,\n lastScrollTop: 0,\n scrollTimer: null,\n contentCache: {},\n loadingSessionMsg: false,\n greeting: '',\n _scrollTimer: null,\n _userScrolledUp: false,\n voiceMode: false,\n isRecording: false,\n recordDuration: 0,\n recordingTimer: null,\n audioContext: null,\n audioStream: null,\n mediaRecorder: null,\n recordedChunks: [],\n audioSource: null,\n isCancelSwipe: false,\n recordStartY: 0,\n _pendingSendText: null\n };\n },\n computed: {\n waitClass() {\n if (this.waitSeconds > 15) return 'wait-15';\n if (this.waitSeconds > 10) return 'wait-10';\n if (this.waitSeconds > 5) return 'wait-5';\n return '';\n },\n currentAgentName() {\n if (!this.currentAgent) return '';\n const a = this.agentList.find(x => x.agentCode === this.currentAgent);\n return a ? a.agentName || a.agentCode : this.currentAgent;\n }\n },\n watch: {\n contentCache: {\n handler(val) {\n // 保持响应性\n },\n immediate: true\n },\n activeSessionId(val) {\n if (val && this._pendingSendText) {\n const text = this._pendingSendText;\n this._pendingSendText = null;\n this.$nextTick(() => this._sendMessage(text));\n }\n }\n },\n mounted() {\n if (window.visualViewport) {\n window.visualViewport.addEventListener('resize', this.onViewportResize, {\n passive: true\n });\n }\n },\n beforeDestroy() {\n if (window.visualViewport) {\n window.visualViewport.removeEventListener('resize', this.onViewportResize);\n }\n this.releaseAudio();\n this.contentCache = {};\n clearTimeout(this._scrollTimer);\n if (this.eventSource) {\n this.eventSource.onerror = null;\n this.eventSource.close();\n this.eventSource = null;\n }\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n },\n methods: {\n getInitial(name) {\n if (!name) return '?';\n return name.charAt(0);\n },\n formatTime(t) {\n if (!t) return '';\n const d = new Date(t.replace(' ', 'T'));\n const now = new Date();\n const diff = (now - d) / 1000;\n if (diff < 60) return '刚刚';\n if (diff < 3600) return Math.floor(diff / 60) + '分钟前';\n if (diff < 86400) return Math.floor(diff / 3600) + '小时前';\n return d.getMonth() + 1 + '/' + d.getDate();\n },\n escapeHtml(text) {\n if (!text) return '';\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n },\n renderMarkdown(text) {\n if (!text) return '';\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n renderStreaming(text, msgKey) {\n if (!text) return '';\n const cache = this.contentCache[msgKey];\n const lines = text.split('\\n');\n const lastLineIdx = lines.length - 1;\n if (!cache) {\n try {\n const html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html,\n fullText: text\n });\n return html;\n } catch {\n return this.escapeHtml(text);\n }\n }\n if (text === cache.fullText) return cache.html;\n cache.fullText = text;\n\n // 行数没变,仅末尾追加字符,直接拼接尾行\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n }\n\n // 场景1:行数变少/文本回退截断,全量重渲染\n if (lastLineIdx < cache.renderedLines) {\n try {\n const fullLines = lines.slice(0, lastLineIdx);\n let newBaseHtml = '';\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n const lastLine = this.escapeHtml(lines[lastLineIdx]);\n return cache.html + (lastLine || '');\n } catch {\n return this.escapeHtml(text);\n }\n }\n\n // 场景2:新增完整行\n if (cache.renderedLines < lastLineIdx) {\n const allFullLines = lines.slice(0, lastLineIdx);\n let newBaseHtml = '';\n if (allFullLines.length > 0) {\n newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n const lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n getChatAvatarStyle(agent, avatar) {\n if (avatar) {\n return {\n backgroundImage: `url(${avatar})`,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n }\n const _agent = this.agentList.find(x => x.agentCode === agent);\n if (_agent && _agent.avatar) {\n return {\n backgroundImage: `url(${_agent.avatar})`,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n }\n // 没有网络图片时,根据智能体在 agentList 中的索引位置决定颜色\n const index = this.agentList.findIndex(item => item.agentCode === agent);\n if (index !== -1) {\n const colorIndex = index % 4 + 1;\n return {\n background: this.getGradientByColorIndex(colorIndex)\n };\n }\n return {\n background: 'linear-gradient(135deg, #1677ff, #4096ff)'\n };\n },\n getGradientByColorIndex(index) {\n const gradients = {\n 1: 'linear-gradient(135deg, #1677ff, #4096ff)',\n 2: 'linear-gradient(135deg, #52c41a, #73d13d)',\n 3: 'linear-gradient(135deg, #fa8c16, #ffa940)',\n 4: 'linear-gradient(135deg, #722ed1, #b37feb)'\n };\n return gradients[index] || gradients[1];\n },\n sendQuickMsg(message) {\n if (message) {\n this.inputText = message;\n this.sendMessage();\n }\n },\n formatMixText(str) {\n if (!str) return '';\n let res = str.replace(/([a-zA-Z0-9])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z0-9])/g, '$1 $2');\n res = res.replace(/([a-z])([A-Z])/g, '$1 $2');\n return res;\n },\n // 检测是否滚动到底部,控制向下箭头显示隐藏\n checkScrollBottom(forceShowWhenNotBottom = false) {\n const el = this.$refs.messagesRef;\n if (!el) {\n this.showDownBtn = false;\n return;\n }\n const {\n scrollHeight,\n scrollTop,\n clientHeight\n } = el;\n const remain = scrollHeight - scrollTop - clientHeight;\n const atBottom = remain <= 80;\n\n // 检测用户是否主动向上滚动(远离底部)\n if (remain > 80 && scrollTop < this.lastScrollTop) {\n this._userScrolledUp = true;\n }\n if (atBottom) {\n this._userScrolledUp = false;\n this.showDownBtn = false;\n } else if (this._userScrolledUp) {\n // 用户已主动上滚,保持显示\"到底部\"按钮,不自动回滚\n this.showDownBtn = true;\n } else if (this.sending && scrollTop >= this.lastScrollTop && !forceShowWhenNotBottom) {\n this.showDownBtn = false;\n } else {\n this.showDownBtn = true;\n }\n this.lastScrollTop = scrollTop;\n this.$emit('scroll-bottom-check', {\n showDownBtn: this.showDownBtn,\n lastScrollTop: this.lastScrollTop\n });\n },\n // H5 输入框获取焦点 - 滚动到底部让键盘不遮挡\n onInputFocus() {\n if (!this.isMobile) return;\n setTimeout(() => {\n window.scrollTo(0, document.body.scrollHeight);\n }, 300);\n },\n // visualViewport 变化时(如键盘弹出)调整滚动位置\n onViewportResize() {\n if (!this.isMobile) return;\n const activeEl = document.activeElement;\n if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.tagName === 'INPUT')) {\n window.scrollTo(0, document.body.scrollHeight);\n }\n },\n autoResizeTextarea() {\n const ta = this.$refs.taRef;\n if (!ta) return;\n const isMobile = this.isMobile;\n const baseH = isMobile ? 36 : 100;\n const maxH = isMobile ? 100 : 180;\n ta.style.height = `${baseH}px`;\n const realH = ta.scrollHeight;\n if (realH > baseH) {\n ta.style.height = `${Math.min(realH, maxH)}px`;\n }\n this.$nextTick(() => {\n this.checkScrollBottom(true);\n });\n },\n // Ctrl+Enter换行\n newLine(e) {\n e.preventDefault();\n const ta = this.$refs.taRef;\n const start = ta.selectionStart;\n const end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(() => {\n ta.selectionStart = ta.selectionEnd = start + 1;\n this.autoResizeTextarea();\n });\n },\n toggleVoiceMode() {\n if (this.voiceMode) {\n this.voiceMode = false;\n this.releaseAudio();\n return;\n }\n // 进入语音模式前提前请求麦克风权限,避免触摸时异步弹窗导致 touchend 丢失\n this.ensureAudioReady().then(ready => {\n if (ready) {\n this.voiceMode = true;\n } else {\n this.voiceMode = false;\n }\n });\n },\n // 确保音频设备已就绪\n async ensureAudioReady() {\n if (this.mediaRecorder && this.audioStream) return true;\n if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\n alert('当前环境不支持麦克风访问');\n return false;\n }\n if (!window.MediaRecorder) {\n alert('当前浏览器不支持录音功能');\n return false;\n }\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true\n });\n this.audioStream = stream;\n\n // 检测平台支持的 MIME 类型\n const mimeType = this._getSupportedMimeType();\n const options = mimeType ? {\n mimeType\n } : {};\n const recorder = new MediaRecorder(stream, options);\n recorder.ondataavailable = e => {\n if (e.data && e.data.size > 0) {\n this.recordedChunks.push(e.data);\n }\n };\n recorder.onstop = () => {\n this._onMediaRecorderStop();\n };\n recorder.onerror = () => {\n console.error('MediaRecorder error');\n this.recordedChunks = [];\n };\n this.mediaRecorder = recorder;\n this.audioContext = new (window.AudioContext || window.webkitAudioContext)();\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n return true;\n } catch (err) {\n console.error('麦克风访问失败:', err);\n alert('麦克风权限被拒绝');\n return false;\n }\n },\n // 检测支持的 MIME 类型\n _getSupportedMimeType() {\n const types = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4;codecs=mp4a.40.2', 'audio/mp4', 'audio/ogg;codecs=opus'];\n for (const t of types) {\n if (MediaRecorder.isTypeSupported(t)) return t;\n }\n return '';\n },\n // MediaRecorder 停止后的回调:解码压缩音频 → 编码 WAV → 发送\n _onMediaRecorderStop() {\n if (this.recordedChunks.length === 0) {\n console.warn('录音数据为空');\n return;\n }\n const blob = new Blob(this.recordedChunks, {\n type: this.mediaRecorder.mimeType || 'audio/webm'\n });\n this.recordedChunks = [];\n\n // 解码压缩音频为 PCM\n const reader = new FileReader();\n reader.onload = () => {\n const arrayBuffer = reader.result;\n this.audioContext.decodeAudioData(arrayBuffer, audioBuffer => {\n // 取第一个声道的数据\n const channelData = audioBuffer.getChannelData(0);\n const wavBlob = this._encodePCMToWavFromBuffer(channelData, audioBuffer.sampleRate);\n this.sendAudioBlob(wavBlob);\n }, err => {\n console.error('decodeAudioData 失败:', err);\n });\n };\n reader.onerror = () => {\n console.error('FileReader 读取失败');\n };\n reader.readAsArrayBuffer(blob);\n },\n // 从 Float32Array 编码 WAV\n _encodePCMToWavFromBuffer(channelData, sampleRate) {\n const numChannels = 1;\n const bitsPerSample = 16;\n const byteRate = sampleRate * numChannels * bitsPerSample / 8;\n const blockAlign = numChannels * bitsPerSample / 8;\n const dataSize = channelData.length * (bitsPerSample / 8);\n const headerSize = 44;\n const totalSize = headerSize + dataSize;\n const buffer = new ArrayBuffer(totalSize);\n const view = new DataView(buffer);\n this._wavWriteString(view, 0, 'RIFF');\n view.setUint32(4, totalSize - 8, true);\n this._wavWriteString(view, 8, 'WAVE');\n this._wavWriteString(view, 12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true); // PCM\n view.setUint16(22, numChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bitsPerSample, true);\n this._wavWriteString(view, 36, 'data');\n view.setUint32(40, dataSize, true);\n const pcmView = new Int16Array(buffer, headerSize, channelData.length);\n for (let i = 0; i < channelData.length; i++) {\n const s = Math.max(-1, Math.min(1, channelData[i]));\n pcmView[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;\n }\n return new Blob([buffer], {\n type: 'audio/wav'\n });\n },\n // H5语音录制 - 触摸开始\n async onRecordTouchStart(e) {\n if (this.sending) return;\n const ready = await this.ensureAudioReady();\n if (!ready) return;\n this.isCancelSwipe = false;\n this.recordStartY = e.touches[0].clientY;\n this.recordDuration = 0;\n this.recordedChunks = [];\n this.isRecording = true;\n this.mediaRecorder.start();\n this.recordingTimer = setInterval(() => {\n this.recordDuration++;\n if (this.recordDuration >= 60) {\n this.onRecordTouchEnd();\n }\n }, 1000);\n },\n // H5语音录制 - 触摸移动\n onRecordTouchMove(e) {\n if (!this.isRecording) return;\n const touch = e.touches[0];\n const moveY = touch.clientY;\n const cancelThreshold = 60;\n this.isCancelSwipe = this.recordStartY - moveY > cancelThreshold;\n },\n // H5语音录制 - 触摸结束\n onRecordTouchEnd() {\n if (!this.isRecording) return;\n this.isRecording = false;\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n // 上滑取消:丢弃录音数据\n if (this.isCancelSwipe) {\n this.recordedChunks = [];\n if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {\n this.mediaRecorder.stop();\n }\n return;\n }\n // 正常发送:停止 MediaRecorder → 触发 onstop → _onMediaRecorderStop → 解码 → 发送\n if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {\n this.mediaRecorder.stop();\n }\n },\n // 取消录音\n cancelRecording() {\n this.isRecording = false;\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {\n this.mediaRecorder.stop();\n }\n this.recordedChunks = [];\n this.recordDuration = 0;\n this.isCancelSwipe = false;\n },\n // 释放音频资源\n releaseAudio() {\n this.cancelRecording();\n if (this.mediaRecorder) {\n if (this.mediaRecorder.state !== 'inactive') {\n try {\n this.mediaRecorder.stop();\n } catch (e) {}\n }\n this.mediaRecorder = null;\n }\n if (this.audioSource) {\n this.audioSource.disconnect();\n this.audioSource = null;\n }\n if (this.audioContext) {\n this.audioContext.close().catch(() => {});\n this.audioContext = null;\n }\n if (this.audioStream) {\n this.audioStream.getTracks().forEach(t => t.stop());\n this.audioStream = null;\n }\n this.recordedChunks = [];\n this.voiceMode = false;\n },\n _wavWriteString(view, offset, str) {\n for (let i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n },\n // 发送音频\n sendAudioBlob(blob) {\n let appSeqId = _index2.default.getAppSeqId();\n let name = appSeqId + '.wav';\n const file = new File([blob], name, {\n type: 'audio/wav'\n });\n this.sendAudioMessage(file);\n },\n sendAudioMessage(file) {\n const sttKey = 'stt_' + Date.now();\n const sttMsg = {\n role: 'user',\n content: '转化中...',\n _sttStatus: 'pending',\n _sttKey: sttKey,\n createTime: new Date().toISOString(),\n _key: sttKey\n };\n this.messages.push(sttMsg);\n this.scrollToBottom(true);\n (0, _index.fileUploadToObs)({\n _file: file,\n appSeqId: file.name\n }).then(resp => {\n if (resp.CommonMsg.Status === 200) {\n return _apiObs.default.aliAudioToText({\n url: resp.obs_upload_data.obsUrl\n }).then(result => {\n if (result.code === '200' && result.data) {\n const idx = this.messages.findIndex(m => m._sttKey === sttKey);\n if (idx !== -1) {\n this.messages.splice(idx, 1);\n }\n this.sendQuickMsg(result.data);\n } else {\n throw new Error(result.msg || '语音转文字失败');\n }\n });\n } else {\n throw new Error(resp.CommonMsg.Message || '文件上传失败');\n }\n }).catch(err => {\n console.error('语音转文字失败:', err);\n const idx = this.messages.findIndex(m => m._sttKey === sttKey);\n if (idx !== -1) {\n this.$set(this.messages, idx, {\n ...this.messages[idx],\n _sttStatus: 'error',\n content: '语音转文字失败'\n });\n }\n this.$message.error('语音转文字失败');\n });\n this.$message.success(`录音完成 (${this.recordDuration}s)`);\n },\n clearWaitTimer() {\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n },\n loadMoreMessages() {\n const nextPage = this.msgPage + 1;\n const maxPage = Math.ceil(this.msgTotal / 10);\n if (nextPage > maxPage) {\n this.loadingMore = false;\n return;\n }\n (0, _apichat.getSessionMessages)(this.activeSessionId, nextPage, 10).then(res => {\n const list = Array.isArray(res) ? res : res.list || [];\n this.msgPage = nextPage;\n const asAsc = (list || []).slice().reverse();\n const prepend = asAsc.map(m => ({\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: '',\n createTime: m.createTime || null,\n _key: m.createTime ? 'msg_' + m.createTime : 'msg_' + Date.now() + '_' + Math.random()\n }));\n const container = this.$refs.messagesRef;\n const oldScrollHeight = container ? container.scrollHeight : 0;\n const oldScrollTop = container ? container.scrollTop : 0;\n this.messages = [...prepend, ...this.messages];\n this.$nextTick(() => {\n if (container) {\n container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);\n }\n this.loadingMore = false;\n });\n }).catch(() => {\n this.loadingMore = false;\n });\n },\n onScrollMessages() {\n const container = this.$refs.messagesRef;\n if (!container || this.loadingMore) return;\n const isAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 100;\n this.checkScrollBottom();\n if (isAtBottom) return;\n if (container.scrollTop < 50 && this.msgPage < Math.ceil(this.msgTotal / 10)) {\n this.loadingMore = true;\n this.loadMoreMessages();\n }\n },\n async sendMessage() {\n const text = this.inputText.trim();\n if (!text || this.sending) return;\n if (text.length > 300) {\n this.$message({\n message: '消息内容不能超过300字',\n type: 'warning',\n duration: 2000\n });\n return;\n }\n if (!this.activeSessionId) {\n this.$emit('start-session');\n this._pendingSendText = text;\n return;\n }\n this._sendMessage(text);\n },\n async _sendMessage(text) {\n this.inputText = '';\n const ta = this.$refs.taRef;\n const isMobile = this.isMobile;\n const baseH = isMobile ? 36 : 100;\n if (ta) {\n ta.style.height = baseH + 'px';\n }\n this.showDownBtn = false;\n this.messages.push({\n role: 'user',\n content: text,\n createTime: new Date().toISOString(),\n _key: 'user_' + Date.now() + '_' + Math.random()\n });\n const agentName = this.currentAgentName;\n const msgIdx = this.messages.push({\n role: \"assistant\",\n content: \"\",\n thinking: \"\",\n loading: true,\n toolCalls: [],\n agentName,\n createTime: new Date().toISOString(),\n _key: 'asst_' + Date.now() + '_' + Math.random()\n }) - 1;\n this.scrollToBottom(true);\n this.sending = true;\n this.waitSeconds = 0;\n if (this.waitTimer) clearInterval(this.waitTimer);\n this.waitTimer = setInterval(() => {\n this.waitSeconds++;\n }, 1000);\n let thinkingBuffer = '';\n let finalContent = '';\n let answerBuffer = '';\n let thinkDisplayed = 0;\n let typeTimer = null;\n let streamDone = false;\n typeTimer = setInterval(() => {\n if (!this.messages[msgIdx]) return;\n\n // Phase 1: 逐字展示思考内容(不 return,让 Phase 2 也能同时执行)\n if (thinkDisplayed < thinkingBuffer.length) {\n thinkDisplayed += 50;\n if (thinkDisplayed > thinkingBuffer.length) thinkDisplayed = thinkingBuffer.length;\n this.messages[msgIdx].thinking = thinkingBuffer.substring(0, thinkDisplayed);\n this.scrollToBottom();\n // 不 return,让 Phase 2 也能执行,避免 iOS 上思考内容过长阻塞回答内容展示\n }\n\n // Phase 2: 逐行展示(已完成的行整行出现,未完成的行也即时显示)\n const target = finalContent || answerBuffer;\n const msg = this.messages[msgIdx];\n const revealedLen = msg._revealedLen || 0;\n if (target.length > revealedLen) {\n const remaining = target.substring(revealedLen);\n const nextNewline = remaining.indexOf('\\n');\n if (nextNewline !== -1) {\n // 新的一行已完成 → 整行出现\n const endPos = revealedLen + nextNewline + 1;\n msg._revealedLen = endPos;\n msg.content = target.substring(0, endPos);\n this.scrollToBottom();\n return;\n }\n\n // 当前行未完成 → 逐步展示(每次 30 字符),确保 iOS 上也有渐进效果\n const revealStep = 30;\n const newRevealedLen = Math.min(revealedLen + revealStep, target.length);\n msg._revealedLen = newRevealedLen;\n msg.content = target.substring(0, newRevealedLen);\n this.scrollToBottom();\n return;\n }\n\n // Phase 3: 流已结束且内容已全部展示 → 收尾\n if (streamDone) {\n clearInterval(typeTimer);\n const key = this.messages[msgIdx]._key;\n if (key) {\n this.$delete(this.contentCache, key);\n this.$delete(this.contentCache, key + '_t');\n }\n typeTimer = null;\n this.messages[msgIdx].content = finalContent;\n this.messages[msgIdx].thinking = thinkingBuffer;\n this.messages[msgIdx].loading = false;\n this.sending = false;\n this.clearWaitTimer();\n this.scrollToBottom();\n this.$emit('session-refresh');\n this.$emit('message-sent');\n }\n }, 30);\n const url = (0, _apichat.createStreamUrl)(this.activeSessionId, text);\n if (this.eventSource) {\n this.eventSource.close();\n }\n this.eventSource = new EventSource(url);\n const es = this.eventSource;\n es.addEventListener('reasoning', e => {\n if (e.data) {\n const chunk = e.data;\n let cleanChunk = chunk.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2').replace(/\\s+([:,.!?])/g, '$1').replace(/([:,.!?])\\s+/g, '$1 ');\n cleanChunk = cleanChunk.replace(/\\s+/g, ' ');\n const lastChar = thinkingBuffer.slice(-1);\n const firstChar = cleanChunk.slice(0, 1);\n const isLetter = c => /[a-zA-Z]/.test(c);\n if (isLetter(lastChar) && isLetter(firstChar)) {\n thinkingBuffer += ' ' + cleanChunk;\n } else {\n thinkingBuffer += cleanChunk;\n }\n }\n });\n es.addEventListener('reasoning_result', e => {\n if (e.data) {\n thinkingBuffer = e.data;\n }\n });\n es.addEventListener('answer', e => {\n if (e.data) {\n answerBuffer += e.data.replace(/([a-zA-Z]+)([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z]+)/g, '$1 $2').replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2');\n }\n });\n es.addEventListener('tool_result', e => {\n if (e.data && this.messages[msgIdx]) {\n if (!this.messages[msgIdx].toolCalls) this.messages[msgIdx].toolCalls = [];\n this.messages[msgIdx].toolCalls.push(e.data);\n this.scrollToBottom();\n }\n });\n es.addEventListener('agent_result', e => {\n finalContent = e.data || '';\n streamDone = true;\n es.close();\n this.eventSource = null;\n });\n es.onerror = err => {\n console.log('[SSE] onerror', err);\n if (thinkingBuffer.length > 0 && !streamDone) finalContent = thinkingBuffer;\n if (!streamDone) finalContent = '❌ 连接中断';\n streamDone = true;\n es.close();\n this.eventSource = null;\n };\n },\n stopAgent() {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n if (this.activeSessionId) {\n (0, _apichat.stopStream)(this.activeSessionId).catch(() => {});\n }\n if (this.messages.length > 0) {\n const last = this.messages[this.messages.length - 1];\n if (last && last.loading) {\n last.content = last.content || last.thinking || '⏹ 已停止';\n last.loading = false;\n this.$delete(this.contentCache, last._key);\n }\n }\n this.sending = false;\n this.clearWaitTimer();\n },\n scrollToTop() {\n const el = this.$refs.messagesRef;\n if (el) el.scrollTop = 0;\n },\n scrollToBottom(force = false) {\n if (this.showDownBtn && !force) return;\n this.$nextTick(() => {\n const el = this.$refs.messagesRef;\n if (el) {\n el.scrollTop = el.scrollHeight;\n this.lastScrollTop = el.scrollTop;\n this.showDownBtn = false;\n this._userScrolledUp = false;\n }\n });\n },\n scrollToBottomOnce() {\n clearTimeout(this._scrollTimer);\n this.$nextTick(() => {\n const tryScroll = () => {\n const el = this.$refs.messagesRef;\n if (!el) {\n this._scrollTimer = setTimeout(tryScroll, 50);\n return;\n }\n const originalScrollBehavior = el.style.scrollBehavior;\n el.style.scrollBehavior = 'auto';\n el.scrollTop = el.scrollHeight - el.clientHeight;\n el.style.scrollBehavior = originalScrollBehavior;\n this.showDownBtn = false;\n this._userScrolledUp = false;\n };\n tryScroll();\n });\n },\n copyText(content) {\n if (!content) return;\n const text = content.replace(/<[^>]+>/g, '');\n\n // 优先使用 Clipboard API(HTTPS 环境)\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(() => {\n this.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(() => {\n this._fallbackCopyText(text);\n });\n } else {\n this._fallbackCopyText(text);\n }\n },\n _fallbackCopyText(text) {\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0';\n document.body.appendChild(ta);\n ta.focus();\n ta.select();\n document.execCommand('copy');\n document.body.removeChild(ta);\n this.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n } catch {\n this.$message({\n message: '复制失败',\n type: 'error',\n duration: 2000\n });\n }\n },\n copyAsImage(content) {\n if (!content) return;\n const div = document.createElement('div');\n div.className = 'copy-image-render';\n div.style.cssText = 'position:fixed;left:-9999px;top:0;width:600px;padding:20px;background:#fff;font-size:14px;line-height:1.6;color:#333;border-radius:8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;';\n // 内联表格样式(因 Vue 作用域样式不覆盖复制用的脱离文档流 div)\n div.innerHTML = this.renderMarkdown(content) + '<style>table{border-collapse:collapse;width:100%;margin:6px 0;font-size:14px}' + 'th,td{border:1px solid #e8e8e8;padding:5px 8px;text-align:left}' + 'th{background:#fafafa;font-weight:600}</style>';\n document.body.appendChild(div);\n this.$message({\n message: '正在生成图片...',\n type: 'info',\n duration: 2000\n });\n (0, _html2canvas.default)(div, {\n scale: 2,\n useCORS: true,\n backgroundColor: '#ffffff'\n }).then(canvas => {\n // 检测是否支持 ClipboardItem(部分 WebView 不支持)\n const canCopyImage = typeof ClipboardItem !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.write === 'function';\n if (canCopyImage) {\n canvas.toBlob(blob => {\n if (blob) {\n navigator.clipboard.write([new ClipboardItem({\n 'image/png': blob\n })]).then(() => {\n this.$message({\n message: '图片已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(() => {\n // ClipboardItem 存在但写入失败 → 降级下载\n this._downloadCanvas(canvas);\n });\n }\n }, 'image/png');\n } else {\n // 不支持剪贴板写图片 → 直接下载\n this._downloadCanvas(canvas);\n }\n }).catch(() => {\n this.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n }).finally(() => {\n document.body.removeChild(div);\n });\n },\n _downloadCanvas(canvas) {\n const link = document.createElement('a');\n link.download = '消息图片.png';\n link.href = canvas.toDataURL('image/png');\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n this.$message({\n message: '已保存图片到本地',\n type: 'success',\n duration: 2000\n });\n }\n }\n};\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatDetail.vue?./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--13-0!./node_modules/_babel-loader@8.4.1@babel-loader/lib!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
955
955
 
956
956
  /***/ }),
957
957
 
@@ -975,7 +975,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
975
975
  /***/ (function(module, exports, __webpack_require__) {
976
976
 
977
977
  "use strict";
978
- eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js */ \"./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js\").default;\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n__webpack_require__(/*! core-js/modules/es.iterator.constructor.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.constructor.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.find.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.find.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.map.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.map.js\");\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\nvar _ChatSidebar = _interopRequireDefault(__webpack_require__(/*! ./ChatSidebar.vue */ \"./src/lib/components/chatMain/ChatSidebar.vue\"));\nvar _AgentSelector = _interopRequireDefault(__webpack_require__(/*! ./AgentSelector.vue */ \"./src/lib/components/chatMain/AgentSelector.vue\"));\nvar _ChatDetail = _interopRequireDefault(__webpack_require__(/*! ./ChatDetail.vue */ \"./src/lib/components/chatMain/ChatDetail.vue\"));\nvar _default = exports.default = {\n name: 'ChatMain',\n components: {\n ChatSidebar: _ChatSidebar.default,\n AgentSelector: _AgentSelector.default,\n ChatDetail: _ChatDetail.default\n },\n props: {\n Ak: {\n type: String,\n default: ''\n },\n Sk: {\n type: String,\n default: ''\n },\n imAccount: {\n type: String,\n default: ''\n },\n companyNo: {\n type: String,\n default: ''\n },\n isIntelShow: {\n default: false,\n type: Boolean\n },\n chatImToken: {\n default: '',\n type: String\n }\n },\n data() {\n // 首次渲染前检测窗口宽度,避免 H5 端先显示 PC 布局再切换导致闪烁\n const _isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;\n return {\n agentList: [],\n currentAgent: '',\n currentAgentDesc: '',\n sessionsLoaded: false,\n sessionList: [],\n activeSessionId: '',\n loadingMore: false,\n showAgentSelect: !_isMobile,\n isMobile: _isMobile,\n showSidebar: false,\n showH5List: _isMobile,\n agentInfo: {},\n sessionsLoading: true,\n _swipeStartX: 0,\n _swipeStartY: 0,\n _swipeHandled: false\n };\n },\n created() {\n if (this.Ak || this.Sk) {\n (0, _apichat.setApiAuth)(this.Ak, this.Sk, this.imAccount, this.companyNo, this.chatImToken);\n }\n this.loadAgents();\n this.loadSessions();\n },\n mounted() {\n this.updateViewportState();\n window.addEventListener('resize', this.updateViewportState, {\n passive: true\n });\n // Android WebView 兼容:在 document 级别监听 touchmove/touchend\n // 避免可滚动子元素消费事件后不冒泡到父级\n document.addEventListener('touchmove', this._onSwipeMove, {\n passive: true\n });\n document.addEventListener('touchend', this._onSwipeEnd, {\n passive: true\n });\n // 拦截 Android 系统返回手势:通过 history.pushState + popstate\n window.addEventListener('popstate', this._onPopState);\n },\n watch: {},\n methods: {\n // ── H5 侧滑手势 ──\n onSwipeTouchStart(e) {\n if (!this.isMobile) return;\n this._swipeStartX = e.touches[0].clientX;\n this._swipeStartY = e.touches[0].clientY;\n this._swipeHandled = false;\n },\n // 在 touchmove 中实时检测\n // 注意:不限制触摸起点位置(移除边缘限制),以兼容 Android 系统手势导航在左边缘的拦截\n _onSwipeMove(e) {\n if (!this.isMobile || this._swipeHandled) return;\n const moveX = e.touches[0].clientX;\n const moveY = e.touches[0].clientY;\n const diffX = moveX - this._swipeStartX;\n const diffY = moveY - this._swipeStartY;\n\n // 向右滑动超过 60px,且水平距离明显大于垂直距离(排除上下滚动干扰)\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) return;\n this._swipeHandled = true;\n this._triggerSwipeAction();\n },\n // touchend 检测(兜底,兼容不支持 touchmove 的场景)\n _onSwipeEnd(e) {\n if (!this.isMobile || this._swipeHandled) return;\n const endX = e.changedTouches[0].clientX;\n const endY = e.changedTouches[0].clientY;\n const diffX = endX - this._swipeStartX;\n const diffY = endY - this._swipeStartY;\n\n // 向右滑动超过 60px,且水平距离明显大于垂直距离\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) return;\n this._swipeHandled = true;\n this._triggerSwipeAction();\n },\n _triggerSwipeAction() {\n if (this.showH5List) {\n // 列表页 → 原生返回\n this.handleNativeBack();\n } else if (!this.showAgentSelect) {\n // 详情页/欢迎页 → 返回列表\n this.goBackToList();\n }\n },\n // 拦截 Android 系统返回手势(通过 history.pushState + popstate)\n // 当用户在详情页触发系统返回时,WebView 先回退历史栈触发 popstate,\n // 我们在这里拦截并导航到列表页,避免 Activity 被直接关闭\n _onPopState() {\n if (!this.isMobile) return;\n // 如果当前在详情页(非列表页、非智能体选择页),拦截系统返回\n if (!this.showH5List && !this.showAgentSelect) {\n this.goBackToList();\n }\n // 如果在列表页(showH5List=true),不做拦截,让系统正常关闭\n },\n handleNewSessionInternal() {\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(res => {\n this.activeSessionId = res.sessionId || '';\n this.loadSessions();\n const agent = this.agentList.find(a => a.agentCode === this.currentAgent);\n if (agent) this.addWelcomeMessage(agent);\n }).catch(() => {\n this.sessionsLoading = false;\n });\n },\n updateViewportState() {\n if (typeof window === 'undefined') return;\n const nextIsMobile = window.innerWidth <= 768;\n const wasMobile = this.isMobile;\n this.isMobile = nextIsMobile;\n if (!nextIsMobile) {\n this.showSidebar = false;\n this.showH5List = false;\n } else if (!wasMobile && nextIsMobile) {\n if (this.sessionList.length > 0 && !this.activeSessionId) {\n this.showH5List = true;\n this.showAgentSelect = false;\n }\n }\n },\n toggleSidebar() {\n if (!this.isMobile) return;\n this.showSidebar = !this.showSidebar;\n },\n closeSidebar() {\n this.showSidebar = false;\n },\n 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 handleSidebarBack() {\n this.handleNativeBack();\n },\n agentjumpclick() {\n const cd = this.$refs.chatDetail;\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.activeSessionId = '';\n this.showAgentSelect = true;\n this.showH5List = false;\n this.closeSidebar();\n },\n goBackToList() {\n const cd = this.$refs.chatDetail;\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n if (cd) {\n if (cd.eventSource) {\n cd.eventSource.close();\n cd.eventSource = null;\n }\n cd.sending = false;\n cd.clearWaitTimer();\n cd.messages = [];\n cd.contentCache = {};\n }\n this.activeSessionId = '';\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.agentInfo = {};\n this.showH5List = true;\n this.showAgentSelect = false;\n this.closeSidebar();\n },\n loadAgents() {\n (0, _apichat.getChatAgents)().then(list => {\n this.agentList = list || [];\n }).catch(() => {\n this.sessionsLoading = false;\n });\n },\n loadSessions() {\n this.sessionsLoading = true;\n (0, _apichat.getSessionList)().then(list => {\n this.sessionsLoading = false;\n if (!this.activeSessionId) {\n this.sessionList = list || [];\n if (this.sessionList.length > 0) {\n if (this.isMobile) {\n this.showAgentSelect = false;\n this.showH5List = true;\n this.showSidebar = false;\n } else {\n this.switchSession(this.sessionList[0]);\n }\n }\n if (this.sessionList.length === 0) {\n this.showAgentSelect = true;\n this.showH5List = false;\n }\n } else {\n this.sessionList = list || [];\n let _item = list.find(res => res.sessionId == this.activeSessionId);\n try {\n if (_item && _item.suggestedQuestions) {\n _item.suggestedQuestions = JSON.parse(_item.suggestedQuestions);\n }\n _item && (this.agentInfo = _item);\n } catch (err) {\n this.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n }\n }).catch(() => {\n this.sessionsLoading = false;\n });\n },\n startChatWithAgent(agent) {\n console.log('选择了助手', agent.agentCode, this.currentAgent);\n if (agent.agentCode == this.currentAgent) {\n return;\n }\n this.currentAgent = agent.agentCode;\n this.currentAgentDesc = agent.description || '';\n this.agentInfo = {};\n this.showAgentSelect = false;\n this.closeSidebar();\n if (this.isMobile) {\n this.showH5List = false;\n }\n const existing = this.sessionList.find(s => s.agentCode === agent.agentCode);\n if (existing) {\n this.switchSession(existing);\n } else {\n this.handleNewSession();\n }\n },\n handleNewSession() {\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(res => {\n this.activeSessionId = res.sessionId || '';\n this.loadSessions();\n const agent = this.agentList.find(a => a.agentCode === this.currentAgent);\n if (agent) this.addWelcomeMessage(agent);\n }).catch(() => {});\n },\n addWelcomeMessage(agent) {\n const cd = this.$refs.chatDetail;\n if (cd) {\n const _agent = this.agentList.find(x => x.agentCode === this.currentAgent);\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n },\n async switchSession(s) {\n let cd = this.$refs.chatDetail;\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n this.showAgentSelect = false;\n this.closeSidebar();\n if (this.isMobile) {\n this.showH5List = false;\n // 推入历史状态,拦截 Android 系统返回手势\n // 系统返回会触发 WebView 历史回退 → popstate → 我们拦截后 goBackToList\n history.pushState({\n page: 'chatDetail'\n }, '');\n }\n if (this.activeSessionId === s.sessionId) return;\n\n // 移动端刚切换 showH5List=false,ChatDetail 尚未渲染完成,需要等待 nextTick\n if (!cd) {\n await this.$nextTick();\n cd = this.$refs.chatDetail;\n }\n if (cd) {\n cd.contentCache = {};\n cd.messages = [];\n cd.msgPage = 1;\n cd.msgTotal = 0;\n cd.loadingSessionMsg = true;\n }\n this.activeSessionId = s.sessionId;\n this.currentAgent = s.agentCode;\n this.agentInfo = s;\n try {\n if (s.suggestedQuestions) {\n this.agentInfo.suggestedQuestions = JSON.parse(s.suggestedQuestions);\n } else {\n this.agentInfo.suggestedQuestions = [];\n }\n } catch (err) {\n this.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n const agent = this.agentList.find(a => a.agentCode === s.agentCode);\n this.currentAgentDesc = agent ? agent.description || '' : '';\n (0, _apichat.syncToken)((0, _cookie.getToken)(), s.sessionId);\n try {\n const res = await (0, _apichat.getSessionMessages)(s.sessionId, 1, 10);\n const list = Array.isArray(res) ? res : res.list || [];\n if (cd) {\n cd.msgTotal = res.total || list.length;\n cd.msgPage = 1;\n const asAsc = (list || []).slice().reverse();\n cd.messages = asAsc.map(m => ({\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: m.role === 'assistant' ? s.agentName || '' : '',\n createTime: m.createTime || null,\n _key: m.createTime ? 'msg_' + m.createTime : 'msg_' + Date.now() + '_' + Math.random()\n }));\n if (cd.messages.length === 0 && agent) {\n const _agent = this.agentList.find(x => x.agentCode === this.currentAgent);\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n cd.loadingSessionMsg = false;\n await this.$nextTick();\n this.$nextTick(() => {\n cd.scrollToBottomOnce();\n cd.checkScrollBottom();\n });\n }\n } catch (e) {\n if (cd) {\n cd.messages = [];\n cd.loadingSessionMsg = false;\n }\n }\n },\n handleDeleteSession(s) {\n this.$confirm(`确认删除「${s.agentName || s.agentCode}」的会话?`, '提示', {\n type: 'warning'\n }).then(() => {\n (0, _apichat.deleteSession)(s.sessionId).then(() => {\n if (this.activeSessionId === s.sessionId) {\n this.activeSessionId = '';\n this.currentAgent = '';\n this.currentAgentDesc = '';\n const cd = this.$refs.chatDetail;\n if (cd) {\n cd.messages = [];\n cd.contentCache = {};\n }\n if (this.isMobile) {\n this.showH5List = false;\n this.showAgentSelect = true;\n } else {\n this.showAgentSelect = true;\n }\n }\n this.loadSessions();\n });\n }).catch(() => {});\n },\n closeOldSession() {}\n },\n beforeDestroy() {\n window.removeEventListener('resize', this.updateViewportState);\n document.removeEventListener('touchmove', this._onSwipeMove);\n document.removeEventListener('touchend', this._onSwipeEnd);\n window.removeEventListener('popstate', this._onPopState);\n }\n};\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.vue?./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--13-0!./node_modules/_babel-loader@8.4.1@babel-loader/lib!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
978
+ eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js */ \"./node_modules/_@babel_runtime@7.28.4@@babel/runtime/helpers/interopRequireDefault.js\").default;\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n__webpack_require__(/*! core-js/modules/es.iterator.constructor.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.constructor.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.find.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.find.js\");\n__webpack_require__(/*! core-js/modules/es.iterator.map.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.map.js\");\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\nvar _ChatSidebar = _interopRequireDefault(__webpack_require__(/*! ./ChatSidebar.vue */ \"./src/lib/components/chatMain/ChatSidebar.vue\"));\nvar _AgentSelector = _interopRequireDefault(__webpack_require__(/*! ./AgentSelector.vue */ \"./src/lib/components/chatMain/AgentSelector.vue\"));\nvar _ChatDetail = _interopRequireDefault(__webpack_require__(/*! ./ChatDetail.vue */ \"./src/lib/components/chatMain/ChatDetail.vue\"));\nvar _default = exports.default = {\n name: 'ChatMain',\n components: {\n ChatSidebar: _ChatSidebar.default,\n AgentSelector: _AgentSelector.default,\n ChatDetail: _ChatDetail.default\n },\n props: {\n Ak: {\n type: String,\n default: ''\n },\n Sk: {\n type: String,\n default: ''\n },\n imAccount: {\n type: String,\n default: ''\n },\n companyNo: {\n type: String,\n default: ''\n },\n isIntelShow: {\n default: false,\n type: Boolean\n },\n chatImToken: {\n default: '',\n type: String\n }\n },\n data() {\n // 首次渲染前检测窗口宽度,避免 H5 端先显示 PC 布局再切换导致闪烁\n const _isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;\n return {\n agentList: [],\n currentAgent: '',\n currentAgentDesc: '',\n sessionsLoaded: false,\n sessionList: [],\n activeSessionId: '',\n loadingMore: false,\n showAgentSelect: !_isMobile,\n isMobile: _isMobile,\n showSidebar: false,\n showH5List: _isMobile,\n agentInfo: {},\n sessionsLoading: true,\n _swipeStartX: 0,\n _swipeStartY: 0,\n _swipeHandled: false\n };\n },\n created() {\n if (this.Ak || this.Sk) {\n (0, _apichat.setApiAuth)(this.Ak, this.Sk, this.imAccount, this.companyNo, this.chatImToken);\n }\n this.loadAgents();\n this.loadSessions();\n },\n mounted() {\n this.updateViewportState();\n window.addEventListener('resize', this.updateViewportState, {\n passive: true\n });\n // Android WebView 兼容:在 document 级别监听 touchmove/touchend\n // 避免可滚动子元素消费事件后不冒泡到父级\n document.addEventListener('touchmove', this._onSwipeMove, {\n passive: true\n });\n document.addEventListener('touchend', this._onSwipeEnd, {\n passive: true\n });\n // 拦截 Android 系统返回手势:通过 history.pushState + popstate\n window.addEventListener('popstate', this._onPopState);\n },\n watch: {},\n methods: {\n // ── H5 侧滑手势 ──\n onSwipeTouchStart(e) {\n if (!this.isMobile) return;\n this._swipeStartX = e.touches[0].clientX;\n this._swipeStartY = e.touches[0].clientY;\n this._swipeHandled = false;\n },\n // 在 touchmove 中实时检测\n // 注意:不限制触摸起点位置(移除边缘限制),以兼容 Android 系统手势导航在左边缘的拦截\n _onSwipeMove(e) {\n if (!this.isMobile || this._swipeHandled) return;\n const moveX = e.touches[0].clientX;\n const moveY = e.touches[0].clientY;\n const diffX = moveX - this._swipeStartX;\n const diffY = moveY - this._swipeStartY;\n\n // 向右滑动超过 60px,且水平距离明显大于垂直距离(排除上下滚动干扰)\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) return;\n this._swipeHandled = true;\n this._triggerSwipeAction();\n },\n // touchend 检测(兜底,兼容不支持 touchmove 的场景)\n _onSwipeEnd(e) {\n if (!this.isMobile || this._swipeHandled) return;\n const endX = e.changedTouches[0].clientX;\n const endY = e.changedTouches[0].clientY;\n const diffX = endX - this._swipeStartX;\n const diffY = endY - this._swipeStartY;\n\n // 向右滑动超过 60px,且水平距离明显大于垂直距离\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) return;\n this._swipeHandled = true;\n this._triggerSwipeAction();\n },\n _triggerSwipeAction() {\n if (this.showH5List) {\n // 列表页 → 原生返回\n this.handleNativeBack();\n } else if (!this.showAgentSelect) {\n // 详情页/欢迎页 → 返回列表\n this.goBackToList();\n }\n },\n // 拦截 Android 系统返回手势(通过 history.pushState + popstate)\n // 当用户在详情页触发系统返回时,WebView 先回退历史栈触发 popstate,\n // 我们在这里拦截并导航到列表页,避免 Activity 被直接关闭\n _onPopState() {\n if (!this.isMobile) return;\n // 如果当前在详情页(非列表页、非智能体选择页),拦截系统返回\n if (!this.showH5List && !this.showAgentSelect) {\n this.goBackToList();\n }\n // 如果在列表页(showH5List=true),不做拦截,让系统正常关闭\n },\n handleNewSessionInternal() {\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(res => {\n this.activeSessionId = res.sessionId || '';\n this.loadSessions();\n const agent = this.agentList.find(a => a.agentCode === this.currentAgent);\n if (agent) this.addWelcomeMessage(agent);\n }).catch(() => {\n this.sessionsLoading = false;\n });\n },\n updateViewportState() {\n if (typeof window === 'undefined') return;\n const nextIsMobile = window.innerWidth <= 768;\n const wasMobile = this.isMobile;\n this.isMobile = nextIsMobile;\n if (!nextIsMobile) {\n this.showSidebar = false;\n this.showH5List = false;\n } else if (!wasMobile && nextIsMobile) {\n if (this.sessionList.length > 0 && !this.activeSessionId) {\n this.showH5List = true;\n this.showAgentSelect = false;\n }\n }\n },\n toggleSidebar() {\n if (!this.isMobile) return;\n this.showSidebar = !this.showSidebar;\n },\n closeSidebar() {\n this.showSidebar = false;\n },\n 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 handleSidebarBack() {\n this.handleNativeBack();\n },\n agentjumpclick() {\n const cd = this.$refs.chatDetail;\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.activeSessionId = '';\n this.showAgentSelect = true;\n this.showH5List = false;\n this.closeSidebar();\n },\n goBackToList() {\n const cd = this.$refs.chatDetail;\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n if (cd) {\n if (cd.eventSource) {\n cd.eventSource.close();\n cd.eventSource = null;\n }\n cd.sending = false;\n cd.clearWaitTimer();\n cd.messages = [];\n cd.contentCache = {};\n }\n this.activeSessionId = '';\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.agentInfo = {};\n this.showH5List = true;\n this.showAgentSelect = false;\n this.closeSidebar();\n },\n loadAgents() {\n (0, _apichat.getChatAgents)().then(list => {\n this.agentList = list || [];\n }).catch(() => {\n this.sessionsLoading = false;\n });\n },\n loadSessions() {\n this.sessionsLoading = true;\n (0, _apichat.getSessionList)().then(list => {\n this.sessionsLoading = false;\n if (!this.activeSessionId) {\n this.sessionList = list || [];\n if (this.sessionList.length > 0) {\n if (this.isMobile) {\n this.showAgentSelect = false;\n this.showH5List = true;\n this.showSidebar = false;\n } else {\n this.switchSession(this.sessionList[0]);\n }\n }\n if (this.sessionList.length === 0) {\n this.showAgentSelect = true;\n this.showH5List = false;\n }\n } else {\n this.sessionList = list || [];\n let _item = list.find(res => res.sessionId == this.activeSessionId);\n try {\n if (_item && _item.suggestedQuestions) {\n _item.suggestedQuestions = JSON.parse(_item.suggestedQuestions);\n }\n _item && (this.agentInfo = _item);\n } catch (err) {\n this.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n }\n }).catch(() => {\n this.sessionsLoading = false;\n });\n },\n startChatWithAgent(agent) {\n console.log('选择了助手', agent.agentCode, this.currentAgent);\n if (agent.agentCode == this.currentAgent) {\n return;\n }\n this.currentAgent = agent.agentCode;\n this.currentAgentDesc = agent.description || '';\n this.agentInfo = {};\n this.showAgentSelect = false;\n this.closeSidebar();\n if (this.isMobile) {\n this.showH5List = false;\n }\n const existing = this.sessionList.find(s => s.agentCode === agent.agentCode);\n if (existing) {\n this.switchSession(existing);\n } else {\n this.handleNewSession();\n }\n },\n handleNewSession() {\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(res => {\n this.activeSessionId = res.sessionId || '';\n this.loadSessions();\n const agent = this.agentList.find(a => a.agentCode === this.currentAgent);\n if (agent) this.addWelcomeMessage(agent);\n }).catch(() => {});\n },\n addWelcomeMessage(agent) {\n const cd = this.$refs.chatDetail;\n if (cd) {\n const _agent = this.agentList.find(x => x.agentCode === this.currentAgent);\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n },\n async switchSession(s) {\n let cd = this.$refs.chatDetail;\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n this.showAgentSelect = false;\n this.closeSidebar();\n if (this.isMobile) {\n this.showH5List = false;\n // 推入历史状态,拦截 Android 系统返回手势\n // 系统返回会触发 WebView 历史回退 → popstate → 我们拦截后 goBackToList\n history.pushState({\n page: 'chatDetail'\n }, '');\n }\n if (this.activeSessionId === s.sessionId) return;\n\n // 移动端刚切换 showH5List=false,ChatDetail 尚未渲染完成,需要等待 nextTick\n if (!cd) {\n await this.$nextTick();\n cd = this.$refs.chatDetail;\n }\n if (cd) {\n cd.contentCache = {};\n cd.messages = [];\n cd.msgPage = 1;\n cd.msgTotal = 0;\n cd.loadingSessionMsg = true;\n }\n this.activeSessionId = s.sessionId;\n this.currentAgent = s.agentCode;\n this.agentInfo = s;\n try {\n if (s.suggestedQuestions) {\n this.agentInfo.suggestedQuestions = JSON.parse(s.suggestedQuestions);\n } else {\n this.agentInfo.suggestedQuestions = [];\n }\n } catch (err) {\n this.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n const agent = this.agentList.find(a => a.agentCode === s.agentCode);\n this.currentAgentDesc = agent ? agent.description || '' : '';\n (0, _apichat.syncToken)((0, _cookie.getToken)(), s.sessionId);\n try {\n const res = await (0, _apichat.getSessionMessages)(s.sessionId, 1, 10);\n const list = Array.isArray(res) ? res : res.list || [];\n if (cd) {\n cd.msgTotal = res.total || list.length;\n cd.msgPage = 1;\n const asAsc = (list || []).slice().reverse();\n cd.messages = asAsc.map(m => ({\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: m.role === 'assistant' ? s.agentName || '' : '',\n createTime: m.createTime || null,\n _key: m.createTime ? 'msg_' + m.createTime : 'msg_' + Date.now() + '_' + Math.random()\n }));\n if (cd.messages.length === 0 && agent) {\n const _agent = this.agentList.find(x => x.agentCode === this.currentAgent);\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n cd.loadingSessionMsg = false;\n await this.$nextTick();\n this.$nextTick(() => {\n cd.scrollToBottomOnce();\n cd.checkScrollBottom();\n });\n }\n } catch (e) {\n if (cd) {\n cd.messages = [];\n cd.loadingSessionMsg = false;\n }\n }\n },\n handleDeleteSession(s) {\n const confirmFn = this.$confirm || _message.showConfirm;\n confirmFn(`确认删除「${s.agentName || s.agentCode}」的会话?`, '提示', {\n type: 'warning'\n }).then(() => {\n (0, _apichat.deleteSession)(s.sessionId).then(() => {\n if (this.activeSessionId === s.sessionId) {\n this.activeSessionId = '';\n this.currentAgent = '';\n this.currentAgentDesc = '';\n const cd = this.$refs.chatDetail;\n if (cd) {\n cd.messages = [];\n cd.contentCache = {};\n }\n if (this.isMobile) {\n this.showH5List = false;\n this.showAgentSelect = true;\n } else {\n this.showAgentSelect = true;\n }\n }\n this.loadSessions();\n });\n }).catch(() => {});\n },\n closeOldSession() {}\n },\n beforeDestroy() {\n window.removeEventListener('resize', this.updateViewportState);\n document.removeEventListener('touchmove', this._onSwipeMove);\n document.removeEventListener('touchend', this._onSwipeEnd);\n window.removeEventListener('popstate', this._onPopState);\n }\n};\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.vue?./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--13-0!./node_modules/_babel-loader@8.4.1@babel-loader/lib!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
979
979
 
980
980
  /***/ }),
981
981
 
@@ -3346,7 +3346,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
3346
3346
  /*! no static exports found */
3347
3347
  /***/ (function(module, exports, __webpack_require__) {
3348
3348
 
3349
- eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../../node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js */ \"./node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n/* ── Sidebar ── */\\n.chat-sidebar[data-v-46646bec] {\\n width: 300px;\\n min-width: 300px;\\n background: #fff;\\n border-right: 1px solid #e8e8e8;\\n display: flex;\\n flex-direction: column;\\n overflow: hidden;\\n}\\n.sidebar-header[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 14px 8px 14px 16px;\\n border-bottom: 1px solid #e8e8e8;\\n flex-shrink: 0;\\n}\\n.sidebar-header i[data-v-46646bec]{\\n font-size: 20px;\\n cursor: pointer;\\n}\\n.sidebar-header img[data-v-46646bec]{\\n cursor: pointer;\\n}\\n.sidebar-title[data-v-46646bec] {\\n font-size: 15px;\\n font-weight: 600;\\n color: #333;\\n}\\n.sidebar-search[data-v-46646bec] {\\n padding: 8px 16px;\\n flex-shrink: 0;\\n position: relative;\\n z-index: 20;\\n}\\n.sidebar-search .search-wrap[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n position: relative;\\n}\\n.sidebar-search .search-icon[data-v-46646bec] {\\n position: absolute;\\n left: 12px;\\n flex-shrink: 0;\\n pointer-events: none;\\n}\\n.sidebar-search .search-input[data-v-46646bec] {\\n width: 100%;\\n height: 32px;\\n border-radius: 16px;\\n background: #f5f5f5;\\n border: 1px solid transparent;\\n font-size: 13px;\\n padding: 0 32px 0 34px;\\n outline: none;\\n box-sizing: border-box;\\n transition: all 0.2s;\\n}\\n.sidebar-search .search-input[data-v-46646bec]:focus {\\n background: #fff;\\n border: 1px solid #1677ff;\\n}\\n.sidebar-search .search-clear[data-v-46646bec] {\\n position: absolute;\\n right: 10px;\\n cursor: pointer;\\n flex-shrink: 0;\\n}\\n.search-suggestions[data-v-46646bec] {\\n position: absolute;\\n top: 100%;\\n left: 16px;\\n right: 16px;\\n background: #fff;\\n border: 1px solid #e8e8e8;\\n border-radius: 8px;\\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\\n z-index: 100;\\n max-height: 240px;\\n overflow-y: auto;\\n margin-top: 4px;\\n}\\n.suggestion-item[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n gap: 10px;\\n padding: 10px 12px;\\n cursor: pointer;\\n transition: background 0.15s;\\n}\\n.suggestion-item[data-v-46646bec]:hover {\\n background: #f5f7fa;\\n}\\n.suggestion-avatar[data-v-46646bec] {\\n width: 28px;\\n height: 28px;\\n border-radius: 6px;\\n background: linear-gradient(135deg, #1677ff, #4096ff);\\n color: #fff;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n font-size: 13px;\\n font-weight: 600;\\n flex-shrink: 0;\\n}\\n.suggestion-name[data-v-46646bec] {\\n font-size: 14px;\\n font-weight: 500;\\n color: #333;\\n white-space: nowrap;\\n}\\n.suggestion-desc[data-v-46646bec] {\\n font-size: 12px;\\n color: #999;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n.sidebar-list[data-v-46646bec] {\\n flex: 1;\\n overflow-y: auto;\\n padding: 8px 0;\\n}\\n.session-item[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n padding: 6px 16px;\\n padding-bottom: 10px;\\n cursor: pointer;\\n border-bottom: 1px solid #f5f5f5;\\n transition: background 0.15s;\\n position: relative;\\n gap: 10px;\\n border-radius: 4px;\\n touch-action: manipulation;\\n -webkit-touch-callout: none;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-left-item[data-v-46646bec] {\\n margin-left: 5px;\\n}\\n.session-item[data-v-46646bec]:hover {\\n background: #f5f7fa;\\n}\\n.session-item.active[data-v-46646bec] {\\n background: #F5F5F5;\\n}\\n.session-avatar[data-v-46646bec] {\\n width: 44px !important;\\n height: 44px !important;\\n border-radius: 50% !important;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n font-size: 16px !important;\\n font-weight: 600;\\n color: #fff;\\n flex-shrink: 0;\\n overflow: hidden;\\n}\\n.avatar-pic[data-v-46646bec] {\\n width: 85%;\\n height: 85%;\\n -o-object-fit: contain;\\n object-fit: contain;\\n}\\n.session-info[data-v-46646bec] {\\n flex: 1;\\n min-width: 0;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-agent[data-v-46646bec] {\\n font-size: 15px;\\n font-weight: 500;\\n color: #333;\\n margin-bottom: 2px;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-preview[data-v-46646bec] {\\n font-size: 12px;\\n color: #999;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-time[data-v-46646bec] {\\n font-size: 11px;\\n color: #bbb;\\n flex-shrink: 0;\\n margin-left: 8px;\\n margin-right: 4px;\\n}\\n.session-delete[data-v-46646bec] {\\n position: absolute;\\n right: 4px;\\n top: 20px;\\n transform: translateY(-50%);\\n opacity: 0;\\n transition: opacity 0.15s;\\n width: 28px;\\n height: 28px;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n border: none;\\n background: transparent;\\n padding: 0;\\n color: #333;\\n cursor: pointer;\\n}\\n.session-delete svg[data-v-46646bec] {\\n color: #333;\\n}\\n.session-item:hover .session-delete[data-v-46646bec] {\\n opacity: 1;\\n}\\n.sidebar-loading[data-v-46646bec] {\\n flex: 1;\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n justify-content: center;\\n color: #bbb;\\n font-size: 14px;\\n padding-bottom: 40px;\\n gap: 12px;\\n}\\n.sidebar-loading .loading-spinner[data-v-46646bec] {\\n width: 24px;\\n height: 24px;\\n border: 2.5px solid #e8e8e8;\\n border-top-color: #1677ff;\\n border-radius: 50%;\\n animation: sidebar-spin-46646bec 0.7s linear infinite;\\n}\\n@keyframes sidebar-spin-46646bec {\\nto { transform: rotate(360deg);\\n}\\n}\\n.sidebar-empty[data-v-46646bec] {\\n flex: 1;\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n justify-content: center;\\n color: #bbb;\\n font-size: 14px;\\n padding-bottom: 40px;\\n}\\n.empty-icon[data-v-46646bec] {\\n font-size: 36px;\\n margin-bottom: 8px;\\n}\\n.empty-hint[data-v-46646bec] {\\n font-size: 12px;\\n color: #d9d9d9;\\n margin-top: 4px;\\n}\\n\\n/* agent-list-avatar base */\\n.agent-list-avatar[data-v-46646bec] {\\n background-repeat: no-repeat;\\n background-position: center;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n color: #fff;\\n font-size: 22px;\\n font-weight: 600;\\n border-radius: 8px;\\n width: 92px;\\n height: 106px;\\n}\\n.agent-list-avatar.has-agent-img[data-v-46646bec] {\\n background-size: cover;\\n}\\n\\n/* suggestion-avatar overrides */\\n.suggestion-avatar[data-v-46646bec] {\\n width: 28px !important;\\n height: 28px !important;\\n border-radius: 8px !important;\\n font-size: 12px !important;\\n font-weight: 600;\\n flex-shrink: 0;\\n}\\n\\n/* Desktop responsive */\\n.chat-sidebar[data-v-46646bec] {\\n width: min(300px, 32vw);\\n}\\n\\n/* ── Mobile (max-width: 768px) ── */\\n@media screen and (max-width: 768px) {\\n.chat-sidebar[data-v-46646bec] {\\n position: absolute;\\n inset: 0 auto 0 0;\\n width: min(82vw, 320px);\\n min-width: auto;\\n max-width: 320px;\\n height: 100%;\\n border-right: none;\\n box-shadow: 8px 0 24px rgba(15, 23, 42, 0.12);\\n transform: translateX(-100%);\\n transition: transform 0.24s ease;\\n}\\n.chat-sidebar.open[data-v-46646bec] {\\n transform: translateX(0);\\n}\\n.sidebar-back-btn[data-v-46646bec] {\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n width: 36px;\\n height: 36px;\\n border: none;\\n border-radius: 10px;\\n background: transparent;\\n color: #333;\\n cursor: pointer;\\n flex-shrink: 0;\\n position: absolute;\\n left: 8px;\\n top: 50%;\\n transform: translateY(-50%);\\n padding: 0;\\n}\\n.sidebar-back-btn svg[data-v-46646bec] {\\n display: block;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar[data-v-46646bec] {\\n position: relative !important;\\n width: 100% !important;\\n max-width: 100% !important;\\n min-width: 100% !important;\\n height: 100%;\\n transform: translateX(0) !important;\\n box-shadow: none !important;\\n border-right: none;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n padding: 14px 16px;\\n position: relative;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header img[data-v-46646bec] {\\n position: absolute;\\n right: 16px;\\n top: 50%;\\n transform: translateY(-50%);\\n width: 22px;\\n height: 22px;\\n -o-object-fit: contain;\\n object-fit: contain;\\n cursor: pointer;\\n}\\n.session-delete[data-v-46646bec] {\\n display: none;\\n}\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatSidebar.vue?./node_modules/_css-loader@3.6.0@css-loader/dist/cjs.js??ref--7-oneOf-1-1!./node_modules/_vue-loader@15.11.1@vue-loader/lib/loaders/stylePostLoader.js!./node_modules/_postcss-loader@3.0.0@postcss-loader/src??ref--7-oneOf-1-2!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
3349
+ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../../node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js */ \"./node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n/* ── Sidebar ── */\\n.chat-sidebar[data-v-46646bec] {\\n width: 300px;\\n min-width: 300px;\\n background: #fff;\\n border-right: 1px solid #e8e8e8;\\n display: flex;\\n flex-direction: column;\\n overflow: hidden;\\n}\\n.sidebar-header[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n padding: 14px 8px 14px 16px;\\n border-bottom: 1px solid #e8e8e8;\\n flex-shrink: 0;\\n}\\n.sidebar-header i[data-v-46646bec]{\\n font-size: 20px;\\n cursor: pointer;\\n}\\n.sidebar-header img[data-v-46646bec]{\\n cursor: pointer;\\n}\\n.sidebar-title[data-v-46646bec] {\\n font-size: 15px;\\n font-weight: 600;\\n color: #333;\\n}\\n.sidebar-search[data-v-46646bec] {\\n padding: 8px 16px;\\n flex-shrink: 0;\\n position: relative;\\n z-index: 20;\\n}\\n.sidebar-search .search-wrap[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n position: relative;\\n}\\n.sidebar-search .search-icon[data-v-46646bec] {\\n position: absolute;\\n left: 12px;\\n flex-shrink: 0;\\n pointer-events: none;\\n}\\n.sidebar-search .search-input[data-v-46646bec] {\\n width: 100%;\\n height: 32px;\\n border-radius: 16px;\\n background: #f5f5f5;\\n border: 1px solid transparent;\\n font-size: 13px;\\n padding: 0 32px 0 34px;\\n outline: none;\\n box-sizing: border-box;\\n transition: all 0.2s;\\n}\\n.sidebar-search .search-input[data-v-46646bec]:focus {\\n background: #fff;\\n border: 1px solid #1677ff;\\n}\\n.sidebar-search .search-clear[data-v-46646bec] {\\n position: absolute;\\n right: 10px;\\n cursor: pointer;\\n flex-shrink: 0;\\n}\\n.search-suggestions[data-v-46646bec] {\\n position: absolute;\\n top: 100%;\\n left: 16px;\\n right: 16px;\\n background: #fff;\\n border: 1px solid #e8e8e8;\\n border-radius: 8px;\\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\\n z-index: 100;\\n max-height: 240px;\\n overflow-y: auto;\\n margin-top: 4px;\\n}\\n.suggestion-item[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n gap: 10px;\\n padding: 10px 12px;\\n cursor: pointer;\\n transition: background 0.15s;\\n}\\n.suggestion-item[data-v-46646bec]:hover {\\n background: #f5f7fa;\\n}\\n.suggestion-avatar[data-v-46646bec] {\\n width: 28px;\\n height: 28px;\\n border-radius: 6px;\\n background: linear-gradient(135deg, #1677ff, #4096ff);\\n color: #fff;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n font-size: 13px;\\n font-weight: 600;\\n flex-shrink: 0;\\n}\\n.suggestion-name[data-v-46646bec] {\\n font-size: 14px;\\n font-weight: 500;\\n color: #333;\\n white-space: nowrap;\\n}\\n.suggestion-desc[data-v-46646bec] {\\n font-size: 12px;\\n color: #999;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n.sidebar-list[data-v-46646bec] {\\n flex: 1;\\n overflow-y: auto;\\n padding: 8px 0;\\n}\\n.session-item[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n padding: 6px 16px;\\n padding-bottom: 10px;\\n cursor: pointer;\\n border-bottom: 1px solid #f5f5f5;\\n transition: background 0.15s;\\n position: relative;\\n gap: 10px;\\n border-radius: 4px;\\n touch-action: manipulation;\\n -webkit-touch-callout: none;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-left-item[data-v-46646bec] {\\n margin-left: 5px;\\n}\\n.session-item[data-v-46646bec]:hover {\\n background: #f5f7fa;\\n}\\n.session-item.active[data-v-46646bec] {\\n background: #F5F5F5;\\n}\\n.session-avatar[data-v-46646bec] {\\n width: 44px !important;\\n height: 44px !important;\\n border-radius: 50% !important;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n font-size: 16px !important;\\n font-weight: 600;\\n color: #fff;\\n flex-shrink: 0;\\n overflow: hidden;\\n}\\n.avatar-pic[data-v-46646bec] {\\n width: 85%;\\n height: 85%;\\n -o-object-fit: contain;\\n object-fit: contain;\\n}\\n.session-info[data-v-46646bec] {\\n flex: 1;\\n min-width: 0;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-agent[data-v-46646bec] {\\n font-size: 15px;\\n font-weight: 500;\\n color: #333;\\n margin-bottom: 2px;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-preview[data-v-46646bec] {\\n font-size: 12px;\\n color: #999;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n -webkit-user-select: none;\\n -moz-user-select: none;\\n user-select: none;\\n}\\n.session-time[data-v-46646bec] {\\n font-size: 11px;\\n color: #bbb;\\n flex-shrink: 0;\\n margin-left: 8px;\\n margin-right: 4px;\\n}\\n.session-delete[data-v-46646bec] {\\n position: absolute;\\n right: 4px;\\n top: 20px;\\n transform: translateY(-50%);\\n opacity: 0;\\n transition: opacity 0.15s;\\n width: 28px;\\n height: 28px;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n border: none;\\n background: transparent;\\n padding: 0;\\n color: #333;\\n cursor: pointer;\\n}\\n.session-delete svg[data-v-46646bec] {\\n color: #333;\\n}\\n.session-item:hover .session-delete[data-v-46646bec] {\\n opacity: 1;\\n}\\n.sidebar-loading[data-v-46646bec] {\\n flex: 1;\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n justify-content: center;\\n color: #bbb;\\n font-size: 14px;\\n padding-bottom: 40px;\\n gap: 12px;\\n}\\n.sidebar-loading .loading-spinner[data-v-46646bec] {\\n width: 24px;\\n height: 24px;\\n border: 2.5px solid #e8e8e8;\\n border-top-color: #1677ff;\\n border-radius: 50%;\\n animation: sidebar-spin-46646bec 0.7s linear infinite;\\n}\\n@keyframes sidebar-spin-46646bec {\\nto { transform: rotate(360deg);\\n}\\n}\\n.sidebar-empty[data-v-46646bec] {\\n flex: 1;\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n justify-content: center;\\n color: #bbb;\\n font-size: 14px;\\n padding-bottom: 40px;\\n}\\n.empty-icon[data-v-46646bec] {\\n font-size: 36px;\\n margin-bottom: 8px;\\n}\\n.empty-hint[data-v-46646bec] {\\n font-size: 12px;\\n color: #d9d9d9;\\n margin-top: 4px;\\n}\\n\\n/* agent-list-avatar base */\\n.agent-list-avatar[data-v-46646bec] {\\n background-repeat: no-repeat;\\n background-position: center;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n color: #fff;\\n font-size: 22px;\\n font-weight: 600;\\n border-radius: 8px;\\n width: 92px;\\n height: 106px;\\n}\\n.agent-list-avatar.has-agent-img[data-v-46646bec] {\\n background-size: cover;\\n}\\n\\n/* suggestion-avatar overrides */\\n.suggestion-avatar[data-v-46646bec] {\\n width: 28px !important;\\n height: 28px !important;\\n border-radius: 8px !important;\\n font-size: 12px !important;\\n font-weight: 600;\\n flex-shrink: 0;\\n}\\n\\n/* Desktop responsive */\\n.chat-sidebar[data-v-46646bec] {\\n width: min(300px, 32vw);\\n}\\n\\n/* ── Mobile (max-width: 768px) ── */\\n@media screen and (max-width: 768px) {\\n.chat-sidebar[data-v-46646bec] {\\n position: absolute;\\n inset: 0 auto 0 0;\\n width: min(82vw, 320px);\\n min-width: auto;\\n max-width: 320px;\\n height: 100%;\\n border-right: none;\\n box-shadow: 8px 0 24px rgba(15, 23, 42, 0.12);\\n transform: translateX(-100%);\\n transition: transform 0.24s ease;\\n}\\n.chat-sidebar.open[data-v-46646bec] {\\n transform: translateX(0);\\n}\\n.sidebar-back-btn[data-v-46646bec] {\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n width: 36px;\\n height: 36px;\\n border: none;\\n border-radius: 10px;\\n background: transparent;\\n color: #333;\\n cursor: pointer;\\n flex-shrink: 0;\\n position: absolute;\\n left: 8px;\\n top: 50%;\\n transform: translateY(-50%);\\n padding: 0;\\n}\\n.sidebar-back-btn svg[data-v-46646bec] {\\n display: block;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar[data-v-46646bec] {\\n position: relative !important;\\n width: 100% !important;\\n max-width: 100% !important;\\n min-width: 100% !important;\\n height: 100%;\\n transform: translateX(0) !important;\\n box-shadow: none !important;\\n border-right: none;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header[data-v-46646bec] {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n padding: 14px 16px;\\n position: relative;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header img[data-v-46646bec] {\\n position: absolute;\\n right: 16px;\\n top: 50%;\\n transform: translateY(-50%);\\n width: 22px;\\n height: 22px;\\n -o-object-fit: contain;\\n object-fit: contain;\\n cursor: pointer;\\n}\\n.session-delete[data-v-46646bec] {\\n display: none;\\n}\\n\\n /* 搜索清除按钮放大触摸区域 */\\n.sidebar-search .search-clear[data-v-46646bec] {\\n width: 20px;\\n height: 20px;\\n padding: 12px;\\n right: -2px;\\n box-sizing: content-box;\\n}\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatSidebar.vue?./node_modules/_css-loader@3.6.0@css-loader/dist/cjs.js??ref--7-oneOf-1-1!./node_modules/_vue-loader@15.11.1@vue-loader/lib/loaders/stylePostLoader.js!./node_modules/_postcss-loader@3.0.0@postcss-loader/src??ref--7-oneOf-1-2!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
3350
3350
 
3351
3351
  /***/ }),
3352
3352
 
@@ -3401,7 +3401,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
3401
3401
  /*! no static exports found */
3402
3402
  /***/ (function(module, exports, __webpack_require__) {
3403
3403
 
3404
- eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js */ \"./node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"@charset \\\"UTF-8\\\";\\n.zj-style[data-v-613edc2b] {\\n position: relative;\\n overflow: hidden;\\n}\\n.zjkf__root.zjkf-body[data-v-613edc2b] {\\n font-family: 'Microsoft YaHei', '微软雅黑', '宋体', helvetica, 'Hiragino Sans GB', MicrosoftYaHei;\\n -ms-text-size-adjust: 100%;\\n -webkit-text-size-adjust: 100%;\\n margin: 0 !important;\\n font-size: 14px;\\n background: #ffffff;\\n}\\n.zjkf__root.zjkf-body .clearfix[data-v-613edc2b]::after {\\n content: '020';\\n display: block;\\n height: 0;\\n clear: both;\\n visibility: hidden;\\n}\\n.zjkf__root.zjkf-body .clearfix[data-v-613edc2b] {\\n zoom: 1;\\n}\\n.zjkf__root.zjkf-body i.zj-iconfont[data-v-613edc2b] {\\n speak: none;\\n font-style: normal;\\n font-weight: 400;\\n font-variant: normal;\\n text-transform: none;\\n line-height: 1;\\n vertical-align: baseline;\\n display: inline-block;\\n -webkit-font-smoothing: antialiased;\\n -moz-osx-font-smoothing: grayscale;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar {\\n width: 7px !important;\\n height: 7px !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-button {\\n display: none !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-thumb {\\n border-radius: 3px !important;\\n background: #dddfe0 !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-track {\\n width: 7px !important;\\n height: 7px !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-track-piece {\\n background: transparent !important;\\n}\\n.zjkf-common-e-ui .el-button.el-button--primary[data-v-613edc2b] {\\n background: #0074fd !important;\\n border-color: #0074fd !important;\\n}\\n.zjkf-common-e-ui .el-button.el-button--primary.is-plain[data-v-613edc2b] {\\n color: #0074fd !important;\\n background: #ffffff !important;\\n border-color: #0074fd !important;\\n}\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b],\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b]:active,\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b]:focus,\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b]:hover {\\n color: #fff !important;\\n background-color: #a0cfff !important;\\n border-color: #a0cfff !important;\\n}\\n.zjkf__root[data-v-613edc2b] {\\n height: 100%;\\n min-height: 100%;\\n overflow: hidden;\\n background: transparent;\\n z-index: 2001;\\n}\\n.zjkf-slide-in-enter-active[data-v-613edc2b] {\\n transition: all 0.3s ease;\\n}\\n.zjkf-slide-in-leave-active[data-v-613edc2b] {\\n transition: all 0.3s ease-out;\\n}\\n.zjkf-slide-in-enter[data-v-613edc2b],\\n.zjkf-slide-in-leave-to[data-v-613edc2b] {\\n transform: translateY(10%);\\n opacity: 0;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/layout/main.vue?./node_modules/_css-loader@3.6.0@css-loader/dist/cjs.js??ref--9-oneOf-1-1!./node_modules/_vue-loader@15.11.1@vue-loader/lib/loaders/stylePostLoader.js!./node_modules/_postcss-loader@3.0.0@postcss-loader/src??ref--9-oneOf-1-2!./node_modules/_sass-loader@8.0.2@sass-loader/dist/cjs.js??ref--9-oneOf-1-3!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
3404
+ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js */ \"./node_modules/_css-loader@3.6.0@css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"@charset \\\"UTF-8\\\";\\n.zj-style[data-v-613edc2b] {\\n position: relative;\\n overflow: hidden;\\n}\\n.zjkf__root.zjkf-body[data-v-613edc2b] {\\n font-family: 'Microsoft YaHei', '微软雅黑', '宋体', helvetica, 'Hiragino Sans GB', MicrosoftYaHei;\\n -ms-text-size-adjust: 100%;\\n -webkit-text-size-adjust: 100%;\\n margin: 0 !important;\\n font-size: 14px;\\n background: #ffffff;\\n}\\n.zjkf__root.zjkf-body .clearfix[data-v-613edc2b]::after {\\n content: '020';\\n display: block;\\n height: 0;\\n clear: both;\\n visibility: hidden;\\n}\\n.zjkf__root.zjkf-body .clearfix[data-v-613edc2b] {\\n zoom: 1;\\n}\\n.zjkf__root.zjkf-body i.zj-iconfont[data-v-613edc2b] {\\n speak: none;\\n font-style: normal;\\n font-weight: 400;\\n font-variant: normal;\\n text-transform: none;\\n line-height: 1;\\n vertical-align: baseline;\\n display: inline-block;\\n -webkit-font-smoothing: antialiased;\\n -moz-osx-font-smoothing: grayscale;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar {\\n width: 7px !important;\\n height: 7px !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-button {\\n display: none !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-thumb {\\n border-radius: 3px !important;\\n background: #dddfe0 !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-track {\\n width: 7px !important;\\n height: 7px !important;\\n}\\n.zjkf-scrollbar[data-v-613edc2b] ::-webkit-scrollbar-track-piece {\\n background: transparent !important;\\n}\\n.zjkf-common-e-ui .el-button.el-button--primary[data-v-613edc2b] {\\n background: #0074fd !important;\\n border-color: #0074fd !important;\\n}\\n.zjkf-common-e-ui .el-button.el-button--primary.is-plain[data-v-613edc2b] {\\n color: #0074fd !important;\\n background: #ffffff !important;\\n border-color: #0074fd !important;\\n}\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b],\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b]:active,\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b]:focus,\\n.zjkf-common-e-ui .el-button--primary.is-disabled[data-v-613edc2b]:hover {\\n color: #fff !important;\\n background-color: #a0cfff !important;\\n border-color: #a0cfff !important;\\n}\\n.zjkf__root[data-v-613edc2b] {\\n height: 100%;\\n overflow: hidden;\\n background: transparent;\\n z-index: 2001;\\n}\\n.zjkf-slide-in-enter-active[data-v-613edc2b] {\\n transition: all 0.3s ease;\\n}\\n.zjkf-slide-in-leave-active[data-v-613edc2b] {\\n transition: all 0.3s ease-out;\\n}\\n.zjkf-slide-in-enter[data-v-613edc2b],\\n.zjkf-slide-in-leave-to[data-v-613edc2b] {\\n transform: translateY(10%);\\n opacity: 0;\\n}\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/layout/main.vue?./node_modules/_css-loader@3.6.0@css-loader/dist/cjs.js??ref--9-oneOf-1-1!./node_modules/_vue-loader@15.11.1@vue-loader/lib/loaders/stylePostLoader.js!./node_modules/_postcss-loader@3.0.0@postcss-loader/src??ref--9-oneOf-1-2!./node_modules/_sass-loader@8.0.2@sass-loader/dist/cjs.js??ref--9-oneOf-1-3!./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js??ref--1-0!./node_modules/_vue-loader@15.11.1@vue-loader/lib??vue-loader-options");
3405
3405
 
3406
3406
  /***/ }),
3407
3407
 
@@ -12608,7 +12608,7 @@ eval("module.exports = function(originalModule) {\n\tif (!originalModule.webpack
12608
12608
  /*! exports provided: name, version, main, private, scripts, dependencies, devDependencies, browserslist, directories, keywords, license, default */
12609
12609
  /***/ (function(module) {
12610
12610
 
12611
- eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.5-beta.27\\\",\\\"main\\\":\\\"lib/ZjPluginIntelligent.umd.min.js\\\",\\\"private\\\":false,\\\"scripts\\\":{\\\"serve\\\":\\\"vue-cli-service serve\\\",\\\"build\\\":\\\"vue-cli-service build\\\",\\\"lib\\\":\\\"vue-cli-service build --target lib --name ZjPluginIntelligent --dest lib src/lib/index.js\\\",\\\"lib:test\\\":\\\"vue-cli-service build --mode test --target lib --name ZjPluginIntelligent --dest lib src/lib/index.js\\\"},\\\"dependencies\\\":{\\\"html2canvas\\\":\\\"^1.4.1\\\",\\\"markdown-it\\\":\\\"^14.1.0\\\"},\\\"devDependencies\\\":{\\\"@babel/preset-env\\\":\\\"^7.14.9\\\",\\\"@vue/cli-plugin-babel\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-eslint\\\":\\\"^4.5.13\\\",\\\"@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\\\":\\\"^7.30.0\\\",\\\"eslint-plugin-vue\\\":\\\"^7.13.0\\\",\\\"js-cookie\\\":\\\"^2.2.1\\\",\\\"node-sass\\\":\\\"^4.12.0\\\",\\\"obs-upload\\\":\\\"^1.0.4-alpha.0\\\",\\\"reconnecting-websocket\\\":\\\"^4.4.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\\\",\\\"pako\\\":\\\"^2.1.0\\\",\\\"webpack-bundle-analyzer\\\":\\\"^4.4.2\\\"},\\\"browserslist\\\":[\\\"> 1%\\\",\\\"last 2 versions\\\",\\\"not dead\\\"],\\\"directories\\\":{\\\"lib\\\":\\\"lib\\\"},\\\"keywords\\\":[],\\\"license\\\":\\\"ISC\\\"}\");\n\n//# sourceURL=webpack://ZjPluginIntelligent/./package.json?");
12611
+ eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.5-beta.29\\\",\\\"main\\\":\\\"lib/ZjPluginIntelligent.umd.min.js\\\",\\\"private\\\":false,\\\"scripts\\\":{\\\"serve\\\":\\\"vue-cli-service serve\\\",\\\"build\\\":\\\"vue-cli-service build\\\",\\\"lib\\\":\\\"vue-cli-service build --target lib --name ZjPluginIntelligent --dest lib src/lib/index.js\\\",\\\"lib:test\\\":\\\"vue-cli-service build --mode test --target lib --name ZjPluginIntelligent --dest lib src/lib/index.js\\\"},\\\"dependencies\\\":{\\\"html2canvas\\\":\\\"^1.4.1\\\",\\\"markdown-it\\\":\\\"^14.1.0\\\"},\\\"devDependencies\\\":{\\\"@babel/preset-env\\\":\\\"^7.14.9\\\",\\\"@vue/cli-plugin-babel\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-eslint\\\":\\\"^4.5.13\\\",\\\"@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\\\":\\\"^7.30.0\\\",\\\"eslint-plugin-vue\\\":\\\"^7.13.0\\\",\\\"js-cookie\\\":\\\"^2.2.1\\\",\\\"node-sass\\\":\\\"^4.12.0\\\",\\\"obs-upload\\\":\\\"^1.0.4-alpha.0\\\",\\\"reconnecting-websocket\\\":\\\"^4.4.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\\\",\\\"pako\\\":\\\"^2.1.0\\\",\\\"webpack-bundle-analyzer\\\":\\\"^4.4.2\\\"},\\\"browserslist\\\":[\\\"> 1%\\\",\\\"last 2 versions\\\",\\\"not dead\\\"],\\\"directories\\\":{\\\"lib\\\":\\\"lib\\\"},\\\"keywords\\\":[],\\\"license\\\":\\\"ISC\\\"}\");\n\n//# sourceURL=webpack://ZjPluginIntelligent/./package.json?");
12612
12612
 
12613
12613
  /***/ }),
12614
12614
 
@@ -13476,7 +13476,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
13476
13476
  /***/ (function(module, exports, __webpack_require__) {
13477
13477
 
13478
13478
  "use strict";
13479
- eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\nexports.showMessage = showMessage;\n/*\n * @Desc: 轻量级原生提示,替代 element-ui 的 Message\n * @Author: Hrc\n * @Date: 2026-07-02 00:00:00\n */\n\nconst ICONS = {\n error: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z\"/></svg>`,\n info: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z\"/></svg>`,\n success: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M696 480H548V332c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v148H336c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h148v148c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h148c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"/></svg>`,\n warning: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M464 688a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm24-370c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V326c0-4.4-3.6-8-8-8h-48z\"/></svg>`\n};\nconst STYLES = {\n error: {\n bg: '#fef0f0',\n border: '#fde2e2',\n color: '#f56c6c'\n },\n info: {\n bg: '#edf2fc',\n border: '#ebeef5',\n color: '#909399'\n },\n success: {\n bg: '#f0f9eb',\n border: '#e1f3d8',\n color: '#67c23a'\n },\n warning: {\n bg: '#fdf6ec',\n border: '#faecd8',\n color: '#e6a23c'\n }\n};\nfunction ensureWrapper() {\n if (typeof document === 'undefined') return null;\n let wrapper = document.getElementById('__npm_message_wrapper');\n if (!wrapper) {\n wrapper = document.createElement('div');\n wrapper.id = '__npm_message_wrapper';\n wrapper.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:2001;display:flex;flex-direction:column;align-items:center;gap:10px;';\n document.body.appendChild(wrapper);\n }\n return wrapper;\n}\nfunction showMessage(msg, type = 'error') {\n console.error(msg);\n const wrapper = ensureWrapper();\n if (!wrapper) return;\n const style = STYLES[type] || STYLES.error;\n const item = document.createElement('div');\n item.style.cssText = `display:flex;align-items:center;min-width:380px;max-width:90vw;padding:15px 20px;background-color:${style.bg};border:1px solid ${style.border};border-radius:4px;box-shadow:0 2px 12px 0 rgba(0,0,0,0.1);color:${style.color};font-size:14px;line-height:1.4;pointer-events:none;transition:opacity .3s,transform .3s;`;\n const iconWrap = document.createElement('span');\n iconWrap.style.cssText = 'flex-shrink:0;margin-right:10px;display:flex;align-items:center;';\n iconWrap.innerHTML = ICONS[type] || ICONS.error;\n const content = document.createElement('p');\n content.style.cssText = 'margin:0;padding:0;word-break:break-all;';\n content.textContent = msg || '请求失败';\n item.appendChild(iconWrap);\n item.appendChild(content);\n wrapper.appendChild(item);\n clearTimeout(item._timer);\n item._timer = setTimeout(() => {\n item.style.opacity = '0';\n item.style.transform = 'translateY(-10px)';\n setTimeout(() => {\n if (item.parentNode) item.parentNode.removeChild(item);\n }, 300);\n }, 3000);\n}\nvar _default = exports.default = showMessage;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/utils/message.js?");
13479
+ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\nexports.showConfirm = showConfirm;\nexports.showMessage = showMessage;\n__webpack_require__(/*! core-js/modules/es.error.cause.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.error.cause.js\");\n/*\n * @Desc: 轻量级原生提示,替代 element-ui 的 Message\n * @Author: Hrc\n * @Date: 2026-07-02 00:00:00\n */\n\nconst ICONS = {\n error: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z\"/></svg>`,\n info: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z\"/></svg>`,\n success: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M696 480H548V332c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v148H336c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h148v148c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h148c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"/></svg>`,\n warning: `<svg width=\"18\" height=\"18\" viewBox=\"0 0 1024 1024\" fill=\"currentColor\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"/><path d=\"M464 688a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm24-370c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V326c0-4.4-3.6-8-8-8h-48z\"/></svg>`\n};\nconst STYLES = {\n error: {\n bg: '#fef0f0',\n border: '#fde2e2',\n color: '#f56c6c'\n },\n info: {\n bg: '#edf2fc',\n border: '#ebeef5',\n color: '#909399'\n },\n success: {\n bg: '#f0f9eb',\n border: '#e1f3d8',\n color: '#67c23a'\n },\n warning: {\n bg: '#fdf6ec',\n border: '#faecd8',\n color: '#e6a23c'\n }\n};\nfunction ensureWrapper() {\n if (typeof document === 'undefined') return null;\n let wrapper = document.getElementById('__npm_message_wrapper');\n if (!wrapper) {\n wrapper = document.createElement('div');\n wrapper.id = '__npm_message_wrapper';\n wrapper.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:2001;display:flex;flex-direction:column;align-items:center;gap:10px;';\n document.body.appendChild(wrapper);\n }\n return wrapper;\n}\nfunction showMessage(msg, type = 'error') {\n console.error(msg);\n const wrapper = ensureWrapper();\n if (!wrapper) return;\n const style = STYLES[type] || STYLES.error;\n const item = document.createElement('div');\n item.style.cssText = `display:flex;align-items:center;min-width:380px;max-width:90vw;padding:15px 20px;background-color:${style.bg};border:1px solid ${style.border};border-radius:4px;box-shadow:0 2px 12px 0 rgba(0,0,0,0.1);color:${style.color};font-size:14px;line-height:1.4;pointer-events:none;transition:opacity .3s,transform .3s;`;\n const iconWrap = document.createElement('span');\n iconWrap.style.cssText = 'flex-shrink:0;margin-right:10px;display:flex;align-items:center;';\n iconWrap.innerHTML = ICONS[type] || ICONS.error;\n const content = document.createElement('p');\n content.style.cssText = 'margin:0;padding:0;word-break:break-all;';\n content.textContent = msg || '请求失败';\n item.appendChild(iconWrap);\n item.appendChild(content);\n wrapper.appendChild(item);\n clearTimeout(item._timer);\n item._timer = setTimeout(() => {\n item.style.opacity = '0';\n item.style.transform = 'translateY(-10px)';\n setTimeout(() => {\n if (item.parentNode) item.parentNode.removeChild(item);\n }, 300);\n }, 3000);\n}\n\n/**\n * @desc: 轻量级原生确认弹窗,替代 element-ui 的 $confirm\n */\nfunction showConfirm(message, title = '提示', options = {}) {\n return new Promise((resolve, reject) => {\n if (typeof document === 'undefined') {\n reject(new Error('document is not defined'));\n return;\n }\n\n // 移除已有弹窗\n const exist = document.getElementById('__npm_confirm_overlay');\n if (exist) exist.remove();\n const confirmText = options.confirmButtonText || '确定';\n const cancelText = options.cancelButtonText || '取消';\n\n // 遮罩层\n const overlay = document.createElement('div');\n overlay.id = '__npm_confirm_overlay';\n overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:2100;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;';\n\n // 弹窗主体\n const box = document.createElement('div');\n box.style.cssText = 'background:#fff;border-radius:8px;width:300px;max-width:85vw;box-shadow:0 4px 20px rgba(0,0,0,0.15);overflow:hidden;';\n\n // 标题\n const header = document.createElement('div');\n header.style.cssText = 'padding:18px 20px 0;font-size:16px;font-weight:600;color:#303133;text-align:center;';\n header.textContent = title;\n\n // 内容\n const body = document.createElement('div');\n body.style.cssText = 'padding:16px 20px 20px;font-size:14px;color:#606266;text-align:center;line-height:1.6;word-break:break-all;';\n body.textContent = message;\n\n // 按钮区域\n const footer = document.createElement('div');\n footer.style.cssText = 'display:flex;border-top:1px solid #ebeef5;';\n const cancelBtn = document.createElement('button');\n cancelBtn.style.cssText = 'flex:1;padding:12px 0;font-size:14px;color:#606266;background:#fff;border:none;border-right:1px solid #ebeef5;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;';\n cancelBtn.textContent = cancelText;\n cancelBtn.addEventListener('click', () => {\n overlay.remove();\n reject(new Error('cancel'));\n });\n const confirmBtn = document.createElement('button');\n confirmBtn.style.cssText = 'flex:1;padding:12px 0;font-size:14px;color:#409eff;background:#fff;border:none;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;';\n confirmBtn.textContent = confirmText;\n confirmBtn.addEventListener('click', () => {\n overlay.remove();\n resolve();\n });\n\n // 点击遮罩关闭\n overlay.addEventListener('click', e => {\n if (e.target === overlay) {\n overlay.remove();\n reject(new Error('cancel'));\n }\n });\n footer.appendChild(cancelBtn);\n footer.appendChild(confirmBtn);\n box.appendChild(header);\n box.appendChild(body);\n box.appendChild(footer);\n overlay.appendChild(box);\n document.body.appendChild(overlay);\n });\n}\nvar _default = exports.default = showMessage;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/utils/message.js?");
13480
13480
 
13481
13481
  /***/ }),
13482
13482