zj-plugin-intelligent 1.2.5-beta.35 → 1.2.5-beta.37
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, '&').replace(/</g, '<').replace(/>/g, '>');\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 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, '&').replace(/</g, '<').replace(/>/g, '>');\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 const base64 = canvas.toDataURL('image/png');\n\n // 移动端:调用原生方法保存到本地相册\n if (this.isMobile) {\n if (this.$hybrid && typeof this.$hybrid.savePic2Library === 'function') {\n this.$hybrid.savePic2Library(base64, () => {\n this.$message({\n message: '图片已保存到本地相册',\n type: 'success',\n duration: 1500\n });\n });\n } else {\n // fallback:展示图片让用户长按保存\n this.previewImage = base64;\n this.$message({\n message: '长按图片可保存到本地',\n type: 'info',\n duration: 3000\n });\n }\n return;\n }\n\n // PC端:复制到剪贴板\n const canCopyImage = typeof ClipboardItem !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.write === 'function';\n if (!canCopyImage) {\n this.$message({\n message: '当前浏览器不支持复制图片',\n type: 'warning',\n duration: 3000\n });\n return;\n }\n canvas.toBlob(blob => {\n if (!blob) {\n this.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n return;\n }\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 this.$message({\n message: '复制失败,请重试',\n type: 'error',\n duration: 2000\n });\n });\n }, 'image/png');\n }).catch(() => {\n this.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n }).finally(() => {\n document.body.removeChild(div);\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
|
|
|
@@ -1035,7 +1035,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
1035
1035
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1036
1036
|
|
|
1037
1037
|
"use strict";
|
|
1038
|
-
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.staticRenderFns = exports.render = void 0;\nvar render = exports.render = function render() {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"div\", {\n staticClass: \"chat-main\"\n }, [_c(\"div\", {\n staticClass: \"chat-header\"\n }, [_c(\"div\", {\n staticClass: \"header-left\"\n }, [_vm.isMobile && _vm.activeSessionId ? _c(\"button\", {\n staticClass: \"mobile-back-btn\",\n attrs: {\n type: \"button\"\n },\n on: {\n click: function ($event) {\n return _vm.$emit(\"go-back-to-list\");\n }\n }\n }, [_c(\"svg\", {\n attrs: {\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"none\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M15 18l-6-6 6-6\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })])]) : _vm.isMobile ? _c(\"button\", {\n staticClass: \"mobile-menu-btn\",\n attrs: {\n type: \"button\"\n },\n on: {\n click: function ($event) {\n return _vm.$emit(\"toggle-sidebar\");\n }\n }\n }, [_c(\"svg\", {\n attrs: {\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"none\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M3 6h18M3 12h18M3 18h18\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\"\n }\n })])]) : _vm._e(), _vm.currentAgent && (_vm.activeSessionId || _vm.messages.length > 0) ? _c(\"div\", {\n staticClass: \"agent-info\"\n }, [_c(\"div\", {\n staticClass: \"chat-agent-avatar-wrapper\"\n }, [_c(\"div\", {\n staticClass: \"agent-list-avatar session-avatar\",\n class: {\n \"has-agent-img\": _vm.agentInfo.avatar\n },\n style: {\n background: _vm.agentInfo.color || \"linear-gradient(135deg, #1677ff, #4096ff)\"\n }\n }, [_vm.agentInfo.avatar ? _c(\"img\", {\n staticClass: \"avatar-pic\",\n attrs: {\n src: _vm.agentInfo.avatar,\n alt: \"\"\n }\n }) : [_vm._v(_vm._s(_vm.getInitial(_vm.agentInfo.agentName || _vm.agentInfo.agentCode)))]], 2)]), _c(\"div\", {\n staticClass: \"agent-text\"\n }, [_c(\"div\", {\n staticClass: \"agent-name\"\n }, [_vm._v(_vm._s(_vm.currentAgentName))]), _c(\"div\", {\n staticClass: \"agent-desc\"\n }, [_vm._v(_vm._s(_vm.currentAgentDesc))])])]) : _c(\"div\", {\n staticClass: \"agent-info\"\n }, [_vm._m(0)])]), _vm.isMobile && !_vm.activeSessionId ? _c(\"div\", {\n staticClass: \"header-actions\"\n }, [_c(\"button\", {\n staticClass: \"mobile-header-btn\",\n attrs: {\n type: \"button\"\n },\n on: {\n click: function ($event) {\n return _vm.$emit(\"agent-jump-click\");\n }\n }\n }, [_c(\"svg\", {\n attrs: {\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 3C6.5 3 2 6.58 2 11c0 2.03.95 3.88 2.5 5.3L3 21l4.6-1.7c1.36.45 2.86.7 4.4.7 5.5 0 10-3.58 10-8s-4.5-8-10-8z\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"8\",\n cy: \"11\",\n r: \"1.3\",\n fill: \"#fff\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"12\",\n cy: \"11\",\n r: \"1.3\",\n fill: \"#fff\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"16\",\n cy: \"11\",\n r: \"1.3\",\n fill: \"#fff\"\n }\n })])])]) : _vm._e()]), _c(\"div\", {\n ref: \"messagesRef\",\n staticClass: \"chat-body\",\n on: {\n scroll: _vm.onScrollMessages\n }\n }, [!_vm.activeSessionId ? _c(\"div\", {\n staticClass: \"welcome welcome-default\"\n }, [_c(\"div\", {\n staticClass: \"welcome-icon\"\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 48 48\",\n width: \"64\",\n height: \"64\"\n }\n }, [_c(\"path\", {\n attrs: {\n fill: \"#e6f4ff\",\n d: \"M40 22c-2.21 0-4 1.79-4 4v10H12c-2.21 0-4-1.79-4-4V14c0-2.21 1.79-4 4-4h24c2.21 0 4 1.79 4 4v8z\"\n }\n }), _c(\"path\", {\n attrs: {\n fill: \"#1677ff\",\n d: \"M36 8c-2.21 0-4 1.79-4 4v18H8c-2.21 0-4-1.79-4-4V12c0-2.21 1.79-4 4-4h28z\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"20\",\n cy: \"28\",\n r: \"3\",\n fill: \"#fff\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"28\",\n cy: \"28\",\n r: \"3\",\n fill: \"#fff\"\n }\n })])]), _c(\"div\", {\n staticClass: \"welcome-title\"\n }, [_vm._v(\"欢迎使用 AI 助手\")]), _c(\"div\", {\n staticClass: \"welcome-text\"\n }, [_vm._v(\"从左侧选择一个会话,或搜索助手开始新的对话\")])]) : _vm.messages.length === 0 && !_vm.loadingSessionMsg ? _c(\"div\", {\n staticClass: \"chat-empty-welcome\"\n }, [_vm.agentInfo.hasOwnProperty(\"agentCode\") ? _c(\"div\", {\n staticClass: \"welcome-card\"\n }, [_c(\"div\", {\n staticClass: \"welcome-avatar-box\"\n }, [_c(\"div\", {\n staticClass: \"welcome-avatar\",\n class: {\n \"has-chat-agent-img\": _vm.agentInfo.avatar\n },\n style: _vm.getChatAvatarStyle(_vm.currentAgent, _vm.agentInfo.avatar)\n }, [!_vm.agentInfo.avatar ? [_vm._v(_vm._s(_vm.getInitial(_vm.currentAgentName)))] : _vm._e()], 2)]), _c(\"div\", {\n staticClass: \"welcome-text-group\"\n }, [_c(\"div\", {\n staticClass: \"text-sm\"\n }, [_vm._v(\"您好,我是\" + _vm._s(_vm.currentAgentName))]), _c(\"div\", {\n staticClass: \"text-lg\"\n }, [_vm._v(_vm._s(_vm.greeting))])])]) : _vm._e(), _c(\"div\", {\n staticClass: \"guess-box\"\n }, [_c(\"div\", {\n staticClass: \"guess-title\"\n }, [_c(\"svg\", {\n attrs: {\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"#1677ff\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })]), _c(\"span\", [_vm._v(\"猜您想问\")])]), _c(\"div\", {\n staticClass: \"tag-wrap\"\n }, _vm._l(_vm.agentInfo.suggestedQuestions, function (tag) {\n return _c(\"div\", {\n key: tag,\n staticClass: \"quick-tag\",\n on: {\n click: function ($event) {\n return _vm.sendQuickMsg(tag);\n }\n }\n }, [_vm._v(\" \" + _vm._s(tag) + \" \")]);\n }), 0)])]) : _vm._e(), _vm._l(_vm.messages, function (m) {\n return _c(\"div\", {\n key: m.id,\n staticClass: \"msg-row\",\n class: m.role\n }, [_c(\"div\", {\n staticClass: \"msg-main\"\n }, [_c(\"div\", {\n staticClass: \"msg-content\"\n }, [_c(\"div\", {\n staticClass: \"bubble\",\n class: m.role\n }, [m.role === \"user\" && m._sttStatus ? _c(\"div\", {\n staticClass: \"stt-bubble-wrap\"\n }, [m._sttStatus === \"pending\" ? _c(\"span\", {\n staticClass: \"stt-spinner stt-outside-icon\"\n }) : m._sttStatus === \"error\" ? _c(\"span\", {\n staticClass: \"stt-error-icon stt-outside-icon\"\n }, [_vm._v(\"!\")]) : _vm._e(), _c(\"span\", {\n staticClass: \"stt-bubble-text\"\n }, [_vm._v(_vm._s(m._sttStatus === \"pending\" ? \"转化中...\" : \"语音转文字失败\"))])]) : _vm._e(), !m._sttStatus && m.thinking ? _c(\"details\", {\n staticClass: \"thinking-details\",\n attrs: {\n open: \"\"\n }\n }, [_c(\"summary\", {\n staticClass: \"thinking-summary\"\n }, [_vm._v(\"💭 思考过程\")]), _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(_vm.escapeHtml(m.thinking))\n }\n })]) : _vm._e(), !m._sttStatus && m.content && m.loading ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(_vm.renderStreaming(m.content, m._key))\n }\n }) : _vm._e(), !m._sttStatus && m.content && !m.loading ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(_vm.renderMarkdown(m.content))\n }\n }) : _vm._e(), _c(\"div\", {\n directives: [{\n name: \"show\",\n rawName: \"v-show\",\n value: !m._sttStatus && _vm.showToolInfo && m.toolCalls && m.toolCalls.length,\n expression: \"!m._sttStatus && showToolInfo && m.toolCalls && m.toolCalls.length\"\n }],\n staticClass: \"tool-calls\"\n }, _vm._l(m.toolCalls, function (tc, ti) {\n return _c(\"div\", {\n key: ti,\n staticClass: \"tool-item\"\n }, [_c(\"span\", {\n staticClass: \"tool-badge\"\n }, [_vm._v(\"工具返回\")]), _c(\"pre\", [_vm._v(_vm._s(tc.substring(0, 300)) + _vm._s(tc.length > 300 ? \"...\" : \"\"))])]);\n }), 0), m.loading ? _c(\"div\", {\n staticClass: \"typing\",\n class: _vm.waitClass\n }, [_c(\"span\", {\n staticClass: \"phrase\"\n }, [_vm._v(\"思考中\")]), _vm._m(1, true)]) : _vm._e()]), m.role === \"assistant\" && m.content && !m.loading ? _c(\"div\", {\n staticClass: \"msg-actions\"\n }, [_c(\"span\", {\n staticClass: \"action-btn\",\n attrs: {\n title: \"复制\"\n },\n on: {\n click: function ($event) {\n return _vm.copyText(m.content);\n }\n }\n }, [_vm._v(\"📋 复制\")]), _c(\"span\", {\n staticClass: \"action-btn\",\n attrs: {\n title: \"复制为图片\"\n },\n on: {\n click: function ($event) {\n return _vm.copyAsImage(m.content);\n }\n }\n }, [_vm._v(\"🖼 复制为图片\")])]) : _vm._e()])])]);\n })], 2), _c(\"div\", {\n staticClass: \"chat-footer\"\n }, [_c(\"div\", {\n staticClass: \"input-area\"\n }, [_vm.isMobile ? _c(\"button\", {\n staticClass: \"voice-btn\",\n attrs: {\n disabled: _vm.sending\n },\n on: {\n click: _vm.toggleVoiceMode\n }\n }, [!_vm.voiceMode ? _c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"22\",\n height: \"22\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 14a3 3 0 003-3V5a3 3 0 00-6 0v6a3 3 0 003 3z\"\n }\n }), _c(\"path\", {\n attrs: {\n d: \"M17 11a5 5 0 01-10 0H5a7 7 0 0014 0h-2z\"\n }\n }), _c(\"rect\", {\n attrs: {\n x: \"11\",\n y: \"17\",\n width: \"2\",\n height: \"4\",\n rx: \"1\"\n }\n }), _c(\"path\", {\n attrs: {\n d: \"M9 20h6v2H9z\"\n }\n })]) : _c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"22\",\n height: \"22\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z\"\n }\n })])]) : _vm._e(), !_vm.voiceMode || !_vm.isMobile ? [_c(\"textarea\", {\n directives: [{\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.inputText,\n expression: \"inputText\"\n }],\n ref: \"taRef\",\n staticClass: \"chat-textarea\",\n attrs: {\n placeholder: \"请输入消息\",\n maxlength: \"300\",\n disabled: _vm.sending\n },\n domProps: {\n value: _vm.inputText\n },\n on: {\n input: [function ($event) {\n if ($event.target.composing) return;\n _vm.inputText = $event.target.value;\n }, _vm.autoResizeTextarea],\n focus: _vm.onInputFocus,\n keydown: [function ($event) {\n if (!$event.type.indexOf(\"key\") && _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")) return null;\n if ($event.ctrlKey || $event.shiftKey || $event.altKey || $event.metaKey) return null;\n $event.preventDefault();\n _vm.isMobile ? _vm.newLine($event) : _vm.sendMessage();\n }, function ($event) {\n if (!$event.type.indexOf(\"key\") && _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")) return null;\n if (!$event.ctrlKey) return null;\n return _vm.newLine.apply(null, arguments);\n }]\n }\n }), !_vm.sending ? _c(\"button\", {\n staticClass: \"send-btn\",\n class: {\n active: _vm.inputText.trim()\n },\n attrs: {\n disabled: _vm.sending || !_vm.inputText.trim()\n },\n on: {\n click: _vm.sendMessage\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"\n }\n })])]) : _vm._e(), _vm.sending ? _c(\"button\", {\n staticClass: \"stop-btn\",\n on: {\n click: _vm.stopAgent\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n }, [_c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })])]) : _vm._e()] : [_c(\"div\", {\n staticClass: \"hold-talk-btn\",\n class: {\n recording: _vm.isRecording\n },\n on: {\n touchstart: function ($event) {\n $event.preventDefault();\n return _vm.onRecordTouchStart($event);\n },\n touchmove: function ($event) {\n $event.preventDefault();\n return _vm.onRecordTouchMove($event);\n },\n touchend: function ($event) {\n $event.preventDefault();\n return _vm.onRecordTouchEnd.apply(null, arguments);\n }\n }\n }, [!_vm.isRecording ? _c(\"svg\", {\n staticClass: \"hold-talk-icon\",\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 14a3 3 0 003-3V5a3 3 0 00-6 0v6a3 3 0 003 3z\"\n }\n }), _c(\"path\", {\n attrs: {\n d: \"M17 11a5 5 0 01-10 0H5a7 7 0 0014 0h-2z\"\n }\n }), _c(\"rect\", {\n attrs: {\n x: \"11\",\n y: \"17\",\n width: \"2\",\n height: \"4\",\n rx: \"1\"\n }\n })]) : _vm._e(), _c(\"span\", [_vm._v(_vm._s(_vm.isRecording ? _vm.isCancelSwipe ? \"松开取消\" : \"松手发送\" : \"按住说话\"))])]), _vm.sending ? _c(\"button\", {\n staticClass: \"stop-btn\",\n on: {\n click: _vm.stopAgent\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n }, [_c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })])]) : _vm._e()], _vm.showDownBtn && _vm.messages.length !== 0 ? _c(\"div\", {\n staticClass: \"scroll-bottom-wrap\"\n }, [_c(\"div\", {\n staticClass: \"scroll-bottom-btn\",\n on: {\n click: function ($event) {\n return _vm.scrollToBottom(true);\n }\n }\n }, [_vm.sending ? _c(\"div\", {\n staticClass: \"loading-circle\"\n }) : _vm._e(), _c(\"svg\", {\n attrs: {\n width: \"14\",\n height: \"14\",\n viewBox: \"0 0 24 24\",\n fill: \"none\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M6 9l6 6 6-6\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })])])]) : _vm._e()], 2)]), _vm.isRecording ? _c(\"div\", {\n staticClass: \"voice-recording-overlay\"\n }, [_c(\"div\", {\n staticClass: \"voice-recording-card\"\n }, [_vm._m(2), _c(\"div\", {\n staticClass: \"voice-recording-duration\"\n }, [_vm._v(_vm._s(_vm.recordDuration)), _c(\"span\", {\n staticClass: \"unit\"\n }, [_vm._v(\"s\")])]), _c(\"div\", {\n staticClass: \"voice-recording-hint\",\n class: {\n cancel: _vm.isCancelSwipe\n }\n }, [_vm.isCancelSwipe ? _c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"#ff4d4f\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z\"\n }\n })]) : _c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"#999\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z\"\n }\n })]), _vm._v(\" \" + _vm._s(_vm.isCancelSwipe ? \"松开取消发送\" : \"手指上滑 取消发送\") + \" \")])])]) : _vm._e()]);\n};\nvar staticRenderFns = exports.staticRenderFns = [function () {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"div\", {\n staticClass: \"agent-text\"\n }, [_c(\"div\", {\n staticClass: \"agent-name\",\n staticStyle: {\n color: \"#bbb\"\n }\n }, [_vm._v(\"请选择助手\")])]);\n}, function () {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"span\", {\n staticClass: \"dots\"\n }, [_c(\"span\"), _c(\"span\"), _c(\"span\")]);\n}, function () {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"div\", {\n staticClass: \"voice-recording-wave\"\n }, [_c(\"span\"), _c(\"span\"), _c(\"span\"), _c(\"span\"), _c(\"span\")]);\n}];\nrender._withStripped = true;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatDetail.vue?./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%224b66220e-vue-loader-template%22%7D!./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/_vue-loader@15.11.1@vue-loader/lib/loaders/templateLoader.js??ref--6!./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");
|
|
1038
|
+
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.staticRenderFns = exports.render = void 0;\nvar render = exports.render = function render() {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"div\", {\n staticClass: \"chat-main\"\n }, [_c(\"div\", {\n staticClass: \"chat-header\"\n }, [_c(\"div\", {\n staticClass: \"header-left\"\n }, [_vm.isMobile && _vm.activeSessionId ? _c(\"button\", {\n staticClass: \"mobile-back-btn\",\n attrs: {\n type: \"button\"\n },\n on: {\n click: function ($event) {\n return _vm.$emit(\"go-back-to-list\");\n }\n }\n }, [_c(\"svg\", {\n attrs: {\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"none\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M15 18l-6-6 6-6\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })])]) : _vm.isMobile ? _c(\"button\", {\n staticClass: \"mobile-menu-btn\",\n attrs: {\n type: \"button\"\n },\n on: {\n click: function ($event) {\n return _vm.$emit(\"toggle-sidebar\");\n }\n }\n }, [_c(\"svg\", {\n attrs: {\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"none\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M3 6h18M3 12h18M3 18h18\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\"\n }\n })])]) : _vm._e(), _vm.currentAgent && (_vm.activeSessionId || _vm.messages.length > 0) ? _c(\"div\", {\n staticClass: \"agent-info\"\n }, [_c(\"div\", {\n staticClass: \"chat-agent-avatar-wrapper\"\n }, [_c(\"div\", {\n staticClass: \"agent-list-avatar session-avatar\",\n class: {\n \"has-agent-img\": _vm.agentInfo.avatar\n },\n style: {\n background: _vm.agentInfo.color || \"linear-gradient(135deg, #1677ff, #4096ff)\"\n }\n }, [_vm.agentInfo.avatar ? _c(\"img\", {\n staticClass: \"avatar-pic\",\n attrs: {\n src: _vm.agentInfo.avatar,\n alt: \"\"\n }\n }) : [_vm._v(_vm._s(_vm.getInitial(_vm.agentInfo.agentName || _vm.agentInfo.agentCode)))]], 2)]), _c(\"div\", {\n staticClass: \"agent-text\"\n }, [_c(\"div\", {\n staticClass: \"agent-name\"\n }, [_vm._v(_vm._s(_vm.currentAgentName))]), _c(\"div\", {\n staticClass: \"agent-desc\"\n }, [_vm._v(_vm._s(_vm.currentAgentDesc))])])]) : _c(\"div\", {\n staticClass: \"agent-info\"\n }, [_vm._m(0)])]), _vm.isMobile && !_vm.activeSessionId ? _c(\"div\", {\n staticClass: \"header-actions\"\n }, [_c(\"button\", {\n staticClass: \"mobile-header-btn\",\n attrs: {\n type: \"button\"\n },\n on: {\n click: function ($event) {\n return _vm.$emit(\"agent-jump-click\");\n }\n }\n }, [_c(\"svg\", {\n attrs: {\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 3C6.5 3 2 6.58 2 11c0 2.03.95 3.88 2.5 5.3L3 21l4.6-1.7c1.36.45 2.86.7 4.4.7 5.5 0 10-3.58 10-8s-4.5-8-10-8z\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"8\",\n cy: \"11\",\n r: \"1.3\",\n fill: \"#fff\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"12\",\n cy: \"11\",\n r: \"1.3\",\n fill: \"#fff\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"16\",\n cy: \"11\",\n r: \"1.3\",\n fill: \"#fff\"\n }\n })])])]) : _vm._e()]), _c(\"div\", {\n ref: \"messagesRef\",\n staticClass: \"chat-body\",\n on: {\n scroll: _vm.onScrollMessages\n }\n }, [!_vm.activeSessionId ? _c(\"div\", {\n staticClass: \"welcome welcome-default\"\n }, [_c(\"div\", {\n staticClass: \"welcome-icon\"\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 48 48\",\n width: \"64\",\n height: \"64\"\n }\n }, [_c(\"path\", {\n attrs: {\n fill: \"#e6f4ff\",\n d: \"M40 22c-2.21 0-4 1.79-4 4v10H12c-2.21 0-4-1.79-4-4V14c0-2.21 1.79-4 4-4h24c2.21 0 4 1.79 4 4v8z\"\n }\n }), _c(\"path\", {\n attrs: {\n fill: \"#1677ff\",\n d: \"M36 8c-2.21 0-4 1.79-4 4v18H8c-2.21 0-4-1.79-4-4V12c0-2.21 1.79-4 4-4h28z\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"20\",\n cy: \"28\",\n r: \"3\",\n fill: \"#fff\"\n }\n }), _c(\"circle\", {\n attrs: {\n cx: \"28\",\n cy: \"28\",\n r: \"3\",\n fill: \"#fff\"\n }\n })])]), _c(\"div\", {\n staticClass: \"welcome-title\"\n }, [_vm._v(\"欢迎使用 AI 助手\")]), _c(\"div\", {\n staticClass: \"welcome-text\"\n }, [_vm._v(\"从左侧选择一个会话,或搜索助手开始新的对话\")])]) : _vm.messages.length === 0 && !_vm.loadingSessionMsg ? _c(\"div\", {\n staticClass: \"chat-empty-welcome\"\n }, [_vm.agentInfo.hasOwnProperty(\"agentCode\") ? _c(\"div\", {\n staticClass: \"welcome-card\"\n }, [_c(\"div\", {\n staticClass: \"welcome-avatar-box\"\n }, [_c(\"div\", {\n staticClass: \"welcome-avatar\",\n class: {\n \"has-chat-agent-img\": _vm.agentInfo.avatar\n },\n style: _vm.getChatAvatarStyle(_vm.currentAgent, _vm.agentInfo.avatar)\n }, [!_vm.agentInfo.avatar ? [_vm._v(_vm._s(_vm.getInitial(_vm.currentAgentName)))] : _vm._e()], 2)]), _c(\"div\", {\n staticClass: \"welcome-text-group\"\n }, [_c(\"div\", {\n staticClass: \"text-sm\"\n }, [_vm._v(\"您好,我是\" + _vm._s(_vm.currentAgentName))]), _c(\"div\", {\n staticClass: \"text-lg\"\n }, [_vm._v(_vm._s(_vm.greeting))])])]) : _vm._e(), _c(\"div\", {\n staticClass: \"guess-box\"\n }, [_c(\"div\", {\n staticClass: \"guess-title\"\n }, [_c(\"svg\", {\n attrs: {\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"#1677ff\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })]), _c(\"span\", [_vm._v(\"猜您想问\")])]), _c(\"div\", {\n staticClass: \"tag-wrap\"\n }, _vm._l(_vm.agentInfo.suggestedQuestions, function (tag) {\n return _c(\"div\", {\n key: tag,\n staticClass: \"quick-tag\",\n on: {\n click: function ($event) {\n return _vm.sendQuickMsg(tag);\n }\n }\n }, [_vm._v(\" \" + _vm._s(tag) + \" \")]);\n }), 0)])]) : _vm._e(), _vm._l(_vm.messages, function (m) {\n return _c(\"div\", {\n key: m.id,\n staticClass: \"msg-row\",\n class: m.role\n }, [_c(\"div\", {\n staticClass: \"msg-main\"\n }, [_c(\"div\", {\n staticClass: \"msg-content\"\n }, [_c(\"div\", {\n staticClass: \"bubble\",\n class: m.role\n }, [m.role === \"user\" && m._sttStatus ? _c(\"div\", {\n staticClass: \"stt-bubble-wrap\"\n }, [m._sttStatus === \"pending\" ? _c(\"span\", {\n staticClass: \"stt-spinner stt-outside-icon\"\n }) : m._sttStatus === \"error\" ? _c(\"span\", {\n staticClass: \"stt-error-icon stt-outside-icon\"\n }, [_vm._v(\"!\")]) : _vm._e(), _c(\"span\", {\n staticClass: \"stt-bubble-text\"\n }, [_vm._v(_vm._s(m._sttStatus === \"pending\" ? \"转化中...\" : \"语音转文字失败\"))])]) : _vm._e(), !m._sttStatus && m.thinking ? _c(\"details\", {\n staticClass: \"thinking-details\",\n attrs: {\n open: \"\"\n }\n }, [_c(\"summary\", {\n staticClass: \"thinking-summary\"\n }, [_vm._v(\"💭 思考过程\")]), _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(_vm.escapeHtml(m.thinking))\n }\n })]) : _vm._e(), !m._sttStatus && m.content && m.loading ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(_vm.renderStreaming(m.content, m._key))\n }\n }) : _vm._e(), !m._sttStatus && m.content && !m.loading ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(_vm.renderMarkdown(m.content))\n }\n }) : _vm._e(), _c(\"div\", {\n directives: [{\n name: \"show\",\n rawName: \"v-show\",\n value: !m._sttStatus && _vm.showToolInfo && m.toolCalls && m.toolCalls.length,\n expression: \"!m._sttStatus && showToolInfo && m.toolCalls && m.toolCalls.length\"\n }],\n staticClass: \"tool-calls\"\n }, _vm._l(m.toolCalls, function (tc, ti) {\n return _c(\"div\", {\n key: ti,\n staticClass: \"tool-item\"\n }, [_c(\"span\", {\n staticClass: \"tool-badge\"\n }, [_vm._v(\"工具返回\")]), _c(\"pre\", [_vm._v(_vm._s(tc.substring(0, 300)) + _vm._s(tc.length > 300 ? \"...\" : \"\"))])]);\n }), 0), m.loading ? _c(\"div\", {\n staticClass: \"typing\",\n class: _vm.waitClass\n }, [_c(\"span\", {\n staticClass: \"phrase\"\n }, [_vm._v(\"思考中\")]), _vm._m(1, true)]) : _vm._e()]), m.role === \"assistant\" && m.content && !m.loading ? _c(\"div\", {\n staticClass: \"msg-actions\"\n }, [_c(\"span\", {\n staticClass: \"action-btn\",\n attrs: {\n title: \"复制\"\n },\n on: {\n click: function ($event) {\n return _vm.copyText(m.content);\n }\n }\n }, [_vm._v(\"📋 复制\")]), _c(\"span\", {\n staticClass: \"action-btn\",\n attrs: {\n title: \"复制为图片\"\n },\n on: {\n click: function ($event) {\n return _vm.copyAsImage(m.content);\n }\n }\n }, [_vm._v(\"🖼 复制为图片\")])]) : _vm._e()])])]);\n })], 2), _c(\"div\", {\n staticClass: \"chat-footer\"\n }, [_c(\"div\", {\n staticClass: \"input-area\"\n }, [_vm.isMobile ? _c(\"button\", {\n staticClass: \"voice-btn\",\n attrs: {\n disabled: \"\"\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"22\",\n height: \"22\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z\"\n }\n })])]) : _vm._e(), !_vm.voiceMode || !_vm.isMobile ? [_c(\"textarea\", {\n directives: [{\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.inputText,\n expression: \"inputText\"\n }],\n ref: \"taRef\",\n staticClass: \"chat-textarea\",\n attrs: {\n placeholder: \"请输入消息\",\n maxlength: \"300\",\n disabled: _vm.sending\n },\n domProps: {\n value: _vm.inputText\n },\n on: {\n input: [function ($event) {\n if ($event.target.composing) return;\n _vm.inputText = $event.target.value;\n }, _vm.autoResizeTextarea],\n focus: _vm.onInputFocus,\n keydown: [function ($event) {\n if (!$event.type.indexOf(\"key\") && _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")) return null;\n if ($event.ctrlKey || $event.shiftKey || $event.altKey || $event.metaKey) return null;\n $event.preventDefault();\n _vm.isMobile ? _vm.newLine($event) : _vm.sendMessage();\n }, function ($event) {\n if (!$event.type.indexOf(\"key\") && _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")) return null;\n if (!$event.ctrlKey) return null;\n return _vm.newLine.apply(null, arguments);\n }]\n }\n }), !_vm.sending ? _c(\"button\", {\n staticClass: \"send-btn\",\n class: {\n active: _vm.inputText.trim()\n },\n attrs: {\n disabled: _vm.sending || !_vm.inputText.trim()\n },\n on: {\n click: _vm.sendMessage\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"\n }\n })])]) : _vm._e(), _vm.sending ? _c(\"button\", {\n staticClass: \"stop-btn\",\n on: {\n click: _vm.stopAgent\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n }, [_c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })])]) : _vm._e()] : [_c(\"div\", {\n staticClass: \"hold-talk-btn\",\n class: {\n recording: _vm.isRecording\n },\n on: {\n touchstart: function ($event) {\n $event.preventDefault();\n return _vm.onRecordTouchStart($event);\n },\n touchmove: function ($event) {\n $event.preventDefault();\n return _vm.onRecordTouchMove($event);\n },\n touchend: function ($event) {\n $event.preventDefault();\n return _vm.onRecordTouchEnd.apply(null, arguments);\n }\n }\n }, [!_vm.isRecording ? _c(\"svg\", {\n staticClass: \"hold-talk-icon\",\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 14a3 3 0 003-3V5a3 3 0 00-6 0v6a3 3 0 003 3z\"\n }\n }), _c(\"path\", {\n attrs: {\n d: \"M17 11a5 5 0 01-10 0H5a7 7 0 0014 0h-2z\"\n }\n }), _c(\"rect\", {\n attrs: {\n x: \"11\",\n y: \"17\",\n width: \"2\",\n height: \"4\",\n rx: \"1\"\n }\n })]) : _vm._e(), _c(\"span\", [_vm._v(_vm._s(_vm.isRecording ? _vm.isCancelSwipe ? \"松开取消\" : \"松手发送\" : \"按住说话\"))])]), _vm.sending ? _c(\"button\", {\n staticClass: \"stop-btn\",\n on: {\n click: _vm.stopAgent\n }\n }, [_c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n }, [_c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })])]) : _vm._e()], _vm.showDownBtn && _vm.messages.length !== 0 ? _c(\"div\", {\n staticClass: \"scroll-bottom-wrap\"\n }, [_c(\"div\", {\n staticClass: \"scroll-bottom-btn\",\n on: {\n click: function ($event) {\n return _vm.scrollToBottom(true);\n }\n }\n }, [_vm.sending ? _c(\"div\", {\n staticClass: \"loading-circle\"\n }) : _vm._e(), _c(\"svg\", {\n attrs: {\n width: \"14\",\n height: \"14\",\n viewBox: \"0 0 24 24\",\n fill: \"none\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M6 9l6 6 6-6\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\"\n }\n })])])]) : _vm._e()], 2)]), _vm.isRecording ? _c(\"div\", {\n staticClass: \"voice-recording-overlay\"\n }, [_c(\"div\", {\n staticClass: \"voice-recording-card\"\n }, [_vm._m(2), _c(\"div\", {\n staticClass: \"voice-recording-duration\"\n }, [_vm._v(_vm._s(_vm.recordDuration)), _c(\"span\", {\n staticClass: \"unit\"\n }, [_vm._v(\"s\")])]), _c(\"div\", {\n staticClass: \"voice-recording-hint\",\n class: {\n cancel: _vm.isCancelSwipe\n }\n }, [_vm.isCancelSwipe ? _c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"#ff4d4f\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z\"\n }\n })]) : _c(\"svg\", {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\",\n fill: \"#999\"\n }\n }, [_c(\"path\", {\n attrs: {\n d: \"M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z\"\n }\n })]), _vm._v(\" \" + _vm._s(_vm.isCancelSwipe ? \"松开取消发送\" : \"手指上滑 取消发送\") + \" \")])])]) : _vm._e()]);\n};\nvar staticRenderFns = exports.staticRenderFns = [function () {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"div\", {\n staticClass: \"agent-text\"\n }, [_c(\"div\", {\n staticClass: \"agent-name\",\n staticStyle: {\n color: \"#bbb\"\n }\n }, [_vm._v(\"请选择助手\")])]);\n}, function () {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"span\", {\n staticClass: \"dots\"\n }, [_c(\"span\"), _c(\"span\"), _c(\"span\")]);\n}, function () {\n var _vm = this,\n _c = _vm._self._c;\n return _c(\"div\", {\n staticClass: \"voice-recording-wave\"\n }, [_c(\"span\"), _c(\"span\"), _c(\"span\"), _c(\"span\"), _c(\"span\")]);\n}];\nrender._withStripped = true;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatDetail.vue?./node_modules/_cache-loader@4.1.0@cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%224b66220e-vue-loader-template%22%7D!./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/_vue-loader@15.11.1@vue-loader/lib/loaders/templateLoader.js??ref--6!./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");
|
|
1039
1039
|
|
|
1040
1040
|
/***/ }),
|
|
1041
1041
|
|
|
@@ -3357,7 +3357,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
3357
3357
|
/*! no static exports found */
|
|
3358
3358
|
/***/ (function(module, exports, __webpack_require__) {
|
|
3359
3359
|
|
|
3360
|
-
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.chat-layout[data-v-bf6d760c] {\\r\\n display: flex;\\r\\n height: 100%;\\r\\n width: 100%;\\r\\n background:
|
|
3360
|
+
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.chat-layout[data-v-bf6d760c] {\\r\\n display: flex;\\r\\n height: 100%;\\r\\n width: 100%;\\r\\n background: yellow;\\r\\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;\\r\\n position: relative;\\r\\n min-height: 0;\\r\\n overflow: hidden;\\n}\\n.sidebar-mask[data-v-bf6d760c] {\\r\\n position: absolute;\\r\\n inset: 0;\\r\\n background: rgba(15, 23, 42, 0.28);\\r\\n z-index: 29;\\n}\\n.chat-layout.is-mobile[data-v-bf6d760c] {\\r\\n display: flex;\\r\\n height: 100%;\\n}\\n.chat-main[data-v-bf6d760c],\\r\\n.chat-body[data-v-bf6d760c] {\\r\\n min-height: 0;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.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");
|
|
3361
3361
|
|
|
3362
3362
|
/***/ }),
|
|
3363
3363
|
|
|
@@ -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 overflow: hidden;\\n background:
|
|
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: yellow;\\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.
|
|
12611
|
+
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.5-beta.37\\\",\\\"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
|
|
|
@@ -13500,7 +13500,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
13500
13500
|
/***/ (function(module, exports, __webpack_require__) {
|
|
13501
13501
|
|
|
13502
13502
|
"use strict";
|
|
13503
|
-
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;\nexports.getAuth = getAuth;\nexports.setAuth = setAuth;\nexports.setBaseURL = setBaseURL;\nexports.setToken = setToken;\nexports.signQueryParams = signQueryParams;\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.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.filter.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.filter.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 _axios = _interopRequireDefault(__webpack_require__(/*! axios */ \"./node_modules/_axios@0.27.2@axios/index.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\");\n/*\r\n * @Author: 高瑞廷 2419056691@qq.com\r\n * @Date: 2026-06-04 11:27:13\r\n * @LastEditors: 高瑞廷 2419056691@qq.com\r\n * @LastEditTime: 2026-07-
|
|
13503
|
+
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;\nexports.getAuth = getAuth;\nexports.setAuth = setAuth;\nexports.setBaseURL = setBaseURL;\nexports.setToken = setToken;\nexports.signQueryParams = signQueryParams;\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.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.filter.js */ \"./node_modules/_core-js@3.45.1@core-js/modules/es.iterator.filter.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 _axios = _interopRequireDefault(__webpack_require__(/*! axios */ \"./node_modules/_axios@0.27.2@axios/index.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\");\n/*\r\n * @Author: 高瑞廷 2419056691@qq.com\r\n * @Date: 2026-06-04 11:27:13\r\n * @LastEditors: 高瑞廷 2419056691@qq.com\r\n * @LastEditTime: 2026-07-20 11:11:13\r\n * @FilePath: \\zjkj-nodejs-npm_customer-client\\src\\lib\\utils\\request.js\r\n * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE\r\n */\n\nlet runtimeAK = Object({\"NODE_ENV\":\"test\",\"VUE_APP_API\":\"https://api.qgbtech.cn\",\"VUE_APP_BASE_API\":\"https://ai.jybtech.cn\",\"VUE_APP_NAME\":\"development\",\"BASE_URL\":\"/\"}).VUE_APP_BASE_AK || '';\nlet runtimeSK = Object({\"NODE_ENV\":\"test\",\"VUE_APP_API\":\"https://api.qgbtech.cn\",\"VUE_APP_BASE_API\":\"https://ai.jybtech.cn\",\"VUE_APP_NAME\":\"development\",\"BASE_URL\":\"/\"}).VUE_APP_BASE_SK || '';\nlet runtimeToken = '';\n\n// let _user = '202007091611352'\n// let _companyNo = '201912241159840'\nlet _user = '';\nlet _companyNo = '';\nlet runtimeBaseURL = \"https://ai.jybtech.cn\" || false;\nconst CryptoJS = __webpack_require__(/*! crypto-js */ \"./node_modules/_crypto-js@4.2.0@crypto-js/index.js\");\nconst service = _axios.default.create({\n baseURL: runtimeBaseURL,\n timeout: 15000\n});\nfunction hmacSha256Base64(rawSignStr, sk) {\n const hmac = CryptoJS.HmacSHA256(rawSignStr, sk);\n // 转为 Base64\n return CryptoJS.enc.Base64.stringify(hmac);\n}\nfunction setAuth(ak, sk, user, companyNo, token) {\n runtimeAK = ak || '';\n runtimeSK = sk || '';\n _user = user;\n _companyNo = companyNo;\n runtimeToken = token || '';\n}\nfunction setToken(token) {\n runtimeToken = token || '';\n}\nfunction getAuth() {\n return {\n ak: runtimeAK,\n sk: runtimeSK,\n token: runtimeToken,\n user: _user,\n companyNo: _companyNo\n };\n}\nfunction setBaseURL(url) {\n runtimeBaseURL = url || '';\n service.defaults.baseURL = runtimeBaseURL;\n}\nfunction signQueryParams(params, secretKey) {\n const keys = Object.keys(params).filter(k => k !== 'ak' && k !== 'sign').sort();\n const raw = keys.map(k => `${k}=${params[k] || ''}`).join('&');\n return hmacSha256Base64(raw, secretKey);\n}\nservice.interceptors.request.use(config => {\n // 流式接口跳过 Header 签名\n if (config.url.includes('/chat/stream')) {\n return config;\n }\n const ts = Date.now().toString();\n const token = runtimeToken || (0, _cookie.getToken)();\n const externalUser = _user || '';\n const signParts = [];\n // 1. 第一位:token(有值才加入)\n if (_companyNo) {\n signParts.push(`companyNo=${_companyNo}`);\n }\n if (token) {\n signParts.push(`token=${token}`);\n }\n // 2. 第二位:user(有值才加入)\n if (externalUser) {\n signParts.push(`user=${externalUser}`);\n }\n // 3. 第三位:ts(必选,永远存在)\n signParts.push(`ts=${ts}`);\n\n // 拼接最终待签字符串\n const signStr = signParts.join('&');\n // 计算签名\n const sign = hmacSha256Base64(signStr, runtimeSK);\n if (_companyNo) {\n config.headers['X-Company-No'] = _companyNo;\n }\n\n // 设置请求头\n config.headers['X-AK'] = runtimeAK;\n config.headers['X-Ts'] = ts;\n config.headers['X-Sign'] = sign;\n if (token) {\n config.headers['X-Auth-Token'] = `${token}`;\n config.headers.Authorization = `Bearer ${token}`;\n }\n config.headers['X-External-User'] = externalUser;\n config.headers['Content-Type'] = 'application/json';\n\n // console.log('排序后key:', sortedKeys)\n\n return config;\n}, error => Promise.reject(error));\nservice.interceptors.response.use(response => {\n const res = response.data;\n if (res.code === 200) {\n return res;\n } else if (res.code === 40101) {\n (0, _message.showMessage)('登录已过期,请重新登录');\n return Promise.reject(new Error(res.message));\n } else {\n (0, _message.showMessage)(res.message || '请求失败');\n return Promise.reject(new Error(res.message));\n }\n}, error => {\n (0, _message.showMessage)(error.message || '网络异常');\n return Promise.reject(error);\n});\nvar _default = exports.default = service;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/utils/request.js?");
|
|
13504
13504
|
|
|
13505
13505
|
/***/ }),
|
|
13506
13506
|
|