From 446b48ef6d82a103cc0b14d4277638b4efbb8be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E5=85=B8=E5=8D=93?= Date: Wed, 16 Apr 2025 14:24:06 +0800 Subject: [PATCH] =?UTF-8?q?flat:=20=E4=BC=98=E5=8C=96=E8=AF=AD=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes App.vue | 9 +- common/IndexedDBHelper.js | 48 +++- components/FadeView/FadeView.vue | 45 ---- components/MsgTips/MsgTips.vue | 140 +++++++++++ components/md-render/md-render.vue | 80 ++++-- config.js | 4 + directives/collapse.js | 84 +++++++ directives/fade.js | 23 ++ hook/.DS_Store | Bin 6148 -> 6148 bytes hook/useRealtimeRecorder.js | 6 +- hook/useTTSPlayer.js | 249 +++++++++++++++++++ lib/wav-decoder@1.3.0.js | 8 + main.js | 13 + pages/chat/chat.vue | 14 +- pages/chat/components/WaveDisplay.vue | 35 ++- pages/chat/components/ai-paging.vue | 287 ++++++++++++---------- pages/chat/components/popupbadFeeback.vue | 195 ++++++++++++++- static/.DS_Store | Bin 8196 -> 8196 bytes static/icon/.DS_Store | Bin 10244 -> 10244 bytes static/icon/success.png | Bin 0 -> 399 bytes stores/BaseDBStore.js | 4 +- stores/userChatGroupStore.js | 64 +++-- uni_modules/.DS_Store | Bin 6148 -> 6148 bytes unpackage/.DS_Store | Bin 6148 -> 6148 bytes unpackage/dist/.DS_Store | Bin 6148 -> 6148 bytes unpackage/dist/build/.DS_Store | Bin 6148 -> 6148 bytes utils/markdownParser.js | 15 +- 28 files changed, 1059 insertions(+), 264 deletions(-) delete mode 100644 components/FadeView/FadeView.vue create mode 100644 components/MsgTips/MsgTips.vue create mode 100644 directives/collapse.js create mode 100644 directives/fade.js create mode 100644 hook/useTTSPlayer.js create mode 100644 lib/wav-decoder@1.3.0.js create mode 100644 static/icon/success.png diff --git a/.DS_Store b/.DS_Store index c7451e09ef46e79ba4667ff1dfa457ac06fdfcc8..ae628fd4df6ca45e270e19f47481b0df81c31036 100644 GIT binary patch delta 240 zcmZn(XbG6$LAU^hRb)?^+5ebsb^OokF5PG`tt$Y&^GNcGIgPfp6oPhwzT5MW?n z{0YQ$n{@<)nKZc=Qh*A9N*I#CTFQW|VpQc!=^*8kO@!PTcTK)0P{=D)U9D?qWN5CV zU}9)Kd99cUntQj^a~#v`dSGBh^Q eQ7|?&MOL@@snlzh&Fl)lSSC*wi31wFlnDT=l0=LE delta 142 zcmZn(XbG6$XEU^hRb=42iL{muCTEKHM^3)xIo6)EK~vM|z7FgCE9{6I>2@?H^@ z%?pM1F-r0<6fh(*Br{|K$#jNP20ex>hGK?%hP+8)@|)vD&oP7aPHq!)pZr>~Y;v;L c?#a$l!kdkxud+;RFyG9s@QZ~Pi?-`b0G-(?3IG5A diff --git a/App.vue b/App.vue index f3997c2..2b2f4c1 100644 --- a/App.vue +++ b/App.vue @@ -57,14 +57,17 @@ onHide(() => { margin-top: -1rpx; } .uni-tabbar-border { - /* background-color: transparent !important; */ - background-color: #e4e4e4 !important; + background-color: transparent !important; + /* background-color: #e4e4e4 !important; */ +} +.uni-popup { + z-index: 1001 !important; } /* 提升toast层级 */ uni-toast, uni-modal, .uni-modal, .uni-mask { - z-index: 999; + z-index: 998; } diff --git a/common/IndexedDBHelper.js b/common/IndexedDBHelper.js index 7e68a88..2406a9f 100644 --- a/common/IndexedDBHelper.js +++ b/common/IndexedDBHelper.js @@ -186,19 +186,51 @@ class IndexedDBHelper { } /** - * 更新数据 - * @param {string} storeName - * @param {Object} data - * @returns {Promise} + * 更新数据(支持指定 key 或自动使用 keyPath) + * @param {string} storeName 存储对象名 + * @param {Object} data 待更新数据 + * @param {IDBValidKey|IDBKeyRange} [key] 可选参数,指定更新的 key + * @returns {Promise} 更新结果 */ - update(storeName, data) { + update(storeName, data, key) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], "readwrite"); const store = transaction.objectStore(storeName); - const request = store.put(data); + const keyPath = store.keyPath; - request.onsuccess = () => resolve("Data updated successfully"); - request.onerror = (event) => reject(`Update Error: ${event.target.error}`); + // 有传入 key:直接使用 key 更新 + if (key !== undefined) { + const request = store.put(data, key); + request.onsuccess = () => resolve("数据更新成功(指定 key)"); + request.onerror = (event) => reject(`更新失败: ${event.target.error}`); + return; + } + + // 无传入 key:依赖 keyPath 更新 + if (!keyPath) { + reject("当前 store 未设置 keyPath,必须传入 key 参数"); + return; + } + + // 检查数据是否包含 keyPath 属性 + let missingKeys = []; + if (Array.isArray(keyPath)) { + missingKeys = keyPath.filter(k => !data.hasOwnProperty(k)); + } else if (typeof keyPath === 'string') { + if (!data.hasOwnProperty(keyPath)) { + missingKeys.push(keyPath); + } + } + + if (missingKeys.length > 0) { + reject(`数据缺少必要的 keyPath 属性: ${missingKeys.join(', ')}`); + return; + } + + // 默认使用 keyPath 更新 + const request = store.put(data); + request.onsuccess = () => resolve("数据更新成功(默认 keyPath)"); + request.onerror = (event) => reject(`更新失败: ${event.target.error}`); }); } diff --git a/components/FadeView/FadeView.vue b/components/FadeView/FadeView.vue deleted file mode 100644 index 47c01ee..0000000 --- a/components/FadeView/FadeView.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/components/MsgTips/MsgTips.vue b/components/MsgTips/MsgTips.vue new file mode 100644 index 0000000..36a7c28 --- /dev/null +++ b/components/MsgTips/MsgTips.vue @@ -0,0 +1,140 @@ + + + + + \ No newline at end of file diff --git a/components/md-render/md-render.vue b/components/md-render/md-render.vue index 1c0568c..fa90d14 100644 --- a/components/md-render/md-render.vue +++ b/components/md-render/md-render.vue @@ -1,6 +1,7 @@ @@ -72,15 +73,37 @@ const handleItemClick = (e) => { padding-inline-start: 40rpx; li { margin-bottom: -30rpx; + display: list-item; + list-style-position: outside; /* 确保数字/点在左侧 */ + word-break: break-word; + p { + display: inline; + margin: 0; + padding: 0; + } } - li:nth-child(1) { - margin-top: -40rpx; + margin-top: -20rpx; + } + } + ol { + li { + display: list-item; + list-style-position: outside; /* 确保数字/点在左侧 */ + word-break: break-word; + p { + display: inline; + margin: 0; + padding: 0; + } + } + li:nth-child(1) { + margin-top: -20rpx; } } p { font-weight: 500; - line-height: 44.8rpx; + line-height: 1.5; } } .markdown-body { @@ -233,7 +256,7 @@ ol { box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04); border-radius: 20rpx 20rpx 20rpx 20rpx; padding: 28rpx 24rpx; - font-weight: 500; + font-weight: 400; font-size: 28rpx; color: #333333; margin-bottom: 20rpx; @@ -244,49 +267,66 @@ ol { font-weight: 600; display: flex; align-items: center; + justify-content: space-between .title-text max-width: calc(100% - 160rpx); overflow: hidden text-overflow: ellipsis - .card-tag - font-weight: 500; - font-size: 24rpx; - color: #333333; - width: fit-content; - background: #F4F4F4; - border-radius: 4rpx 4rpx 4rpx 4rpx; - padding: 0rpx 20rpx; - margin-left: 16rpx; + font-size: 30rpx + .card-salary + font-size: 28rpx; + color: #FF6E1C; + .card-company - margin-top: 12rpx; + margin-top: 16rpx; + max-width: calc(100%); + overflow: hidden; + text-overflow: ellipsis + color: #6C7282; .card-info - margin-top: 12rpx; + margin-top: 22rpx; display: flex; align-items: center; justify-content: space-between; padding-right: 40rpx; + .info-item + display: flex; + position: relative; + align-items: center; + color: #256BFA; + font-size: 28rpx; + padding-right: 10rpx .position-nav position: absolute; - right: 34rpx; + right: -10rpx; top: 50%; .position-nav::before position: absolute; left: 0; - top: 0; + top: -4rpx; content: ''; width: 4rpx; height: 16rpx; border-radius: 2rpx - background: #8A8A8A; + background: #256BFA; transform: translate(0, -50%) rotate(-45deg) ; .position-nav::after position: absolute; left: 0; - top: 0; + top: -4rpx; content: ''; width: 4rpx; height: 16rpx; border-radius: 2rpx - background: #8A8A8A; + background: #256BFA; transform: rotate(45deg) + .card-tag + font-weight: 500; + font-size: 24rpx; + color: #333333; + width: fit-content; + background: #F4F4F4; + border-radius: 4rpx 4rpx 4rpx 4rpx; + padding: 4rpx 20rpx; + margin-right: 16rpx; diff --git a/config.js b/config.js index eeb3782..f2e5545 100644 --- a/config.js +++ b/config.js @@ -8,8 +8,12 @@ export default { // 语音转文字 // vioceBaseURl: 'ws://39.98.44.136:8080/speech-recognition', vioceBaseURl: 'wss://qd.zhaopinzao8dian.com/api/speech-recognition', + // 语音合成 + speechSynthesis: 'wss://qd.zhaopinzao8dian.com/api/speech-synthesis', // indexedDB DBversion: 2, + // 只使用本地缓寸的数据 + OnlyUseCachedDB: true, // 应用信息 appInfo: { // 应用名称 diff --git a/directives/collapse.js b/directives/collapse.js new file mode 100644 index 0000000..bc432bf --- /dev/null +++ b/directives/collapse.js @@ -0,0 +1,84 @@ +// directives/collapse.js +export default { + mounted(el, binding) { + el._collapse = { + duration: binding.arg ? parseInt(binding.arg) : 300, // 使用指令参数设置 duration + expanded: binding.value, + }; + + el.style.overflow = 'hidden'; + el.style.transition = `height ${el._collapse.duration}ms ease, opacity ${el._collapse.duration}ms ease`; + + if (!binding.value) { + el.style.height = '0px'; + el.style.opacity = '0'; + } else { + setTimeout(() => { + getHeight(el).then((height) => { + el.style.height = height + 'px'; + el.style.opacity = '1'; + }); + }, 0); + } + }, + + updated(el, binding) { + const duration = el._collapse.duration; + const isShow = binding.value; + + if (isShow === el._collapse.expanded) return; + + el._collapse.expanded = isShow; + + if (isShow) { + getHeight(el).then((height) => { + el.style.transition = `none`; + el.style.height = '0px'; + el.style.opacity = '0'; + + // 动画开始 + requestAnimationFrame(() => { + el.style.transition = `height ${duration}ms ease, opacity ${duration}ms ease`; + el.style.height = height + 'px'; + el.style.opacity = '1'; + + // 动画结束后设置为 auto(避免内容变化导致高度错误) + setTimeout(() => { + el.style.height = 'auto'; + }, duration); + }); + }); + } else { + getHeight(el).then((height) => { + console.log(height) + el.style.height = height + 'px'; + el.style.opacity = '1'; + + requestAnimationFrame(() => { + el.style.height = '0px'; + el.style.opacity = '0'; + }); + }); + } + }, + + unmounted(el) { + delete el._collapse; + }, +}; + +// 获取元素高度(兼容 H5 和小程序) +function getHeight(el) { + return new Promise((resolve) => { + // #ifdef H5 + resolve(el.scrollHeight); + // #endif + + // #ifndef H5 + const query = uni.createSelectorQuery(); + query.select(el).boundingClientRect((res) => { + resolve(res?.height || 0); + }).exec(); + // #endif + }); +} \ No newline at end of file diff --git a/directives/fade.js b/directives/fade.js new file mode 100644 index 0000000..4470e06 --- /dev/null +++ b/directives/fade.js @@ -0,0 +1,23 @@ +export default { + mounted(el, binding) { + const duration = binding.arg ? parseInt(binding.arg) : 300; + el.style.transition = `opacity ${duration}ms ease`; + el.style.opacity = binding.value ? '1' : '0'; + if (!binding.value) el.style.display = 'none'; + }, + updated(el, binding) { + const duration = binding.arg ? parseInt(binding.arg) : 300; + + if (binding.value) { + el.style.display = ''; + requestAnimationFrame(() => { + el.style.opacity = '1'; + }); + } else { + el.style.opacity = '0'; + setTimeout(() => { + el.style.display = 'none'; + }, duration); + } + } +}; \ No newline at end of file diff --git a/hook/.DS_Store b/hook/.DS_Store index 056d1bc063ffb855ec5660bf44fdd22796160a04..c099f54d783c253ccf34b20703e5d8d9835519d5 100644 GIT binary patch delta 171 zcmZoMXfc=|#>B)qu~2NHo}wrV0|Nsi1A_nqgC0W`L$PO0esWUI#=_-{${-nbh7yKU zhGHOzB%7ZElmTjBaGadL5C~nmxGB0Rlu`1}mt7%`G51}qyinHbY2p7R2 z|H;4LprGfybWt$j^3J;-=ic+~vbk(NSH;HGYF1q}4);m`ET9Yo=pjOi5yttB{bFzG z(1AJ=wcR>~$QYiWPb7JU>RlYEQ_-^ab;j{6BN9{0(ZC&&t4Q|!NcvGRBU zNZb>xWzqYekoH>0Z^7;##p+3%2)yNABD3<6<2qax7xG~%w{qcdS3*Bwep7k1e*jje BVz~eS diff --git a/hook/useRealtimeRecorder.js b/hook/useRealtimeRecorder.js index e727d01..3791337 100644 --- a/hook/useRealtimeRecorder.js +++ b/hook/useRealtimeRecorder.js @@ -31,8 +31,8 @@ export function useAudioRecorder(wsUrl) { // 配置常量 const SAMPLE_RATE = 16000; const SILENCE_THRESHOLD = 0.02; // 静音阈值 (0-1) - const SILENCE_DURATION = 400; // 静音持续时间(ms)后切片 - const MIN_SOUND_DURATION = 300; // 最小有效声音持续时间(ms) + const SILENCE_DURATION = 100; // 静音持续时间(ms)后切片 + const MIN_SOUND_DURATION = 200; // 最小有效声音持续时间(ms) // 音频处理变量 const lastSoundTime = ref(0); @@ -125,6 +125,7 @@ export function useAudioRecorder(wsUrl) { socket.value = new WebSocket(wsUrl); socket.value.onopen = () => { + console.log('open') isSocketConnected.value = true; resolve(); }; @@ -191,7 +192,6 @@ export function useAudioRecorder(wsUrl) { if (audioChunks.value.length === 0 || !socket.value || socket.value.readyState !== WebSocket.OPEN) { return; } - try { // 合并所有块 const totalBytes = audioChunks.value.reduce((total, chunk) => total + chunk.byteLength, 0); diff --git a/hook/useTTSPlayer.js b/hook/useTTSPlayer.js new file mode 100644 index 0000000..b969236 --- /dev/null +++ b/hook/useTTSPlayer.js @@ -0,0 +1,249 @@ +import { + ref, + onUnmounted, + onBeforeUnmount, + onMounted +} from 'vue' +import { + onHide, + onUnload +} from '@dcloudio/uni-app' +import WavDecoder from '@/lib/wav-decoder@1.3.0.js' + +export function useTTSPlayer(wsUrl) { + const isSpeaking = ref(false) + const isPaused = ref(false) + const isComplete = ref(false) + + const audioContext = new(window.AudioContext || window.webkitAudioContext)() + let playTime = audioContext.currentTime + let sourceNodes = [] + let socket = null + let sampleRate = 16000 + let numChannels = 1 + let isHeaderDecoded = false + let pendingText = null + + let currentPlayId = 0 + let activePlayId = 0 + + const speak = (text) => { + currentPlayId++ + const myPlayId = currentPlayId + reset() + pendingText = text + activePlayId = myPlayId + } + + const pause = () => { + if (audioContext.state === 'running') { + audioContext.suspend() + isPaused.value = true + isSpeaking.value = false + } + } + + const resume = () => { + if (audioContext.state === 'suspended') { + audioContext.resume() + isPaused.value = false + isSpeaking.value = true + } + } + + const cancelAudio = () => { + stop() + } + + const stop = () => { + isSpeaking.value = false + isPaused.value = false + isComplete.value = false + playTime = audioContext.currentTime + + sourceNodes.forEach(node => { + try { + node.stop() + node.disconnect() + } catch (e) {} + }) + sourceNodes = [] + + if (socket) { + socket.close() + socket = null + } + + isHeaderDecoded = false + pendingText = null + } + + const reset = () => { + stop() + isSpeaking.value = false + isPaused.value = false + isComplete.value = false + playTime = audioContext.currentTime + initWebSocket() + } + + const initWebSocket = () => { + const thisPlayId = currentPlayId + socket = new WebSocket(wsUrl) + socket.binaryType = 'arraybuffer' + + socket.onopen = () => { + if (pendingText && thisPlayId === activePlayId) { + const seepdText = extractSpeechText(pendingText) + socket.send(seepdText) + pendingText = null + } + } + + socket.onmessage = async (e) => { + if (thisPlayId !== activePlayId) return // 忽略旧播放的消息 + + if (typeof e.data === 'string') { + try { + const msg = JSON.parse(e.data) + if (msg.status === 'complete') { + isComplete.value = true + setTimeout(() => { + if (thisPlayId === activePlayId) { + isSpeaking.value = false + } + }, (playTime - audioContext.currentTime) * 1000) + } + } catch (e) { + console.log('[TTSPlayer] 文本消息:', e.data) + } + } else if (e.data instanceof ArrayBuffer) { + if (!isHeaderDecoded) { + try { + const decoded = await WavDecoder.decode(e.data) + sampleRate = decoded.sampleRate + numChannels = decoded.channelData.length + decoded.channelData.forEach((channel, i) => { + const audioBuffer = audioContext.createBuffer(1, channel.length, + sampleRate) + audioBuffer.copyToChannel(channel, 0) + playBuffer(audioBuffer) + }) + isHeaderDecoded = true + } catch (err) { + console.error('WAV 解码失败:', err) + } + } else { + const pcm = new Int16Array(e.data) + const audioBuffer = pcmToAudioBuffer(pcm, sampleRate, numChannels) + playBuffer(audioBuffer) + } + } + } + } + + const pcmToAudioBuffer = (pcm, sampleRate, numChannels) => { + const length = pcm.length / numChannels + const audioBuffer = audioContext.createBuffer(numChannels, length, sampleRate) + for (let ch = 0; ch < numChannels; ch++) { + const channelData = audioBuffer.getChannelData(ch) + for (let i = 0; i < length; i++) { + const sample = pcm[i * numChannels + ch] + channelData[i] = sample / 32768 + } + } + return audioBuffer + } + + const playBuffer = (audioBuffer) => { + if (!isSpeaking.value) { + playTime = audioContext.currentTime + } + const source = audioContext.createBufferSource() + source.buffer = audioBuffer + source.connect(audioContext.destination) + source.start(playTime) + sourceNodes.push(source) + playTime += audioBuffer.duration + isSpeaking.value = true + } + + onUnmounted(() => { + stop() + }) + + // 页面刷新/关闭时 + onMounted(() => { + if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', cancelAudio) + } + }) + + onBeforeUnmount(() => { + cancelAudio() + if (typeof window !== 'undefined') { + window.removeEventListener('beforeunload', cancelAudio) + } + }) + + onHide(cancelAudio) + onUnload(cancelAudio) + + initWebSocket() + + return { + speak, + pause, + resume, + cancelAudio, + isSpeaking, + isPaused, + isComplete + } +} + +function extractSpeechText(markdown) { + const jobRegex = /``` job-json\s*({[\s\S]*?})\s*```/g; + const jobs = []; + let match; + let lastJobEndIndex = 0; + let firstJobStartIndex = -1; + + // 提取岗位 json 数据及前后位置 + while ((match = jobRegex.exec(markdown)) !== null) { + const jobStr = match[1]; + try { + const job = JSON.parse(jobStr); + jobs.push(job); + if (firstJobStartIndex === -1) { + firstJobStartIndex = match.index; + } + lastJobEndIndex = jobRegex.lastIndex; + } catch (e) { + console.warn('JSON 解析失败', e); + } + } + + // 提取引导语(第一个 job-json 之前的文字) + const guideText = firstJobStartIndex > 0 ? + markdown.slice(0, firstJobStartIndex).trim() : + ''; + + // 提取结束语(最后一个 job-json 之后的文字) + const endingText = lastJobEndIndex < markdown.length ? + markdown.slice(lastJobEndIndex).trim() : + ''; + + // 岗位信息格式化为语音文本 + const jobTexts = jobs.map((job, index) => { + return `第 ${index + 1} 个岗位,岗位名称是:${job.jobTitle},公司是:${job.companyName},薪资:${job.salary},地点:${job.location},学历要求:${job.education},经验要求:${job.experience}。`; + }); + + // 拼接总语音内容 + const finalTextParts = []; + if (guideText) finalTextParts.push(guideText); + finalTextParts.push(...jobTexts); + if (endingText) finalTextParts.push(endingText); + + return finalTextParts.join('\n'); +} \ No newline at end of file diff --git a/lib/wav-decoder@1.3.0.js b/lib/wav-decoder@1.3.0.js new file mode 100644 index 0000000..87fcdd4 --- /dev/null +++ b/lib/wav-decoder@1.3.0.js @@ -0,0 +1,8 @@ +/** + * Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0. + * Original file: /npm/wav-decoder@1.3.0/index.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +var n="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},t={exports:{}};!function(t){var r={1:"lpcm",3:"lpcm"};function e(t,r){r=r||{},n.Buffer&&t instanceof n.Buffer&&(t=Uint8Array.from(t).buffer);var e=function(n){var t=0;return{remain:function(){return n.byteLength-t},skip:function(n){t+=n},uint8:function(){var r=n.getUint8(t,!0);return t+=1,r},int16:function(){var r=n.getInt16(t,!0);return t+=2,r},uint16:function(){var r=n.getUint16(t,!0);return t+=2,r},uint32:function(){var r=n.getUint32(t,!0);return t+=4,r},string:function(n){for(var t="",r=0;r8388608?r-16777216:r;return t+=3,e<0?e/8388608:e/8388607},pcm24s:function(){var r=n.getUint8(t+0)+(n.getUint8(t+1)<<8)+(n.getUint8(t+2)<<16);return t+=3,(r>8388608?r-16777216:r)/8388608},pcm32:function(){var r=n.getInt32(t,!0);return t+=4,r<0?r/2147483648:r/2147483647},pcm32s:function(){var r=n.getInt32(t,!0);return t+=4,r/2147483648},pcm32f:function(){var r=n.getFloat32(t,!0);return t+=4,r},pcm64f:function(){var r=n.getFloat64(t,!0);return t+=8,r}}}(new DataView(t));if("RIFF"!==e.string(4))throw new TypeError("Invalid WAV file");if(e.uint32(),"WAVE"!==e.string(4))throw new TypeError("Invalid WAV file");var a=null,u=null;do{var f=e.string(4),c=e.uint32();switch(f){case"fmt ":if((a=i(e,c))instanceof Error)throw a;break;case"data":if((u=o(e,c,a,r))instanceof Error)throw u;break;default:e.skip(c)}}while(null===u);return u}function i(n,t){var e=n.uint16();if(!r.hasOwnProperty(e))return new TypeError("Unsupported format in WAV file: 0x"+e.toString(16));var i={formatId:e,floatingPoint:3===e,numberOfChannels:n.uint16(),sampleRate:n.uint32(),byteRate:n.uint32(),blockSize:n.uint16(),bitDepth:n.uint16()};return n.skip(t-16),i}function o(n,t,r,e){t=Math.min(t,n.remain());for(var i=Math.floor(t/r.blockSize),o=r.numberOfChannels,a=r.sampleRate,u=new Array(o),f=0;f v-fade + const name = path.match(/\.\/directives\/(.*)\.js$/)[1]; + app.directive(name, directiveModule.default); + } + app.provide('globalFunction', { ...globalFunction, similarityJobs diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue index 8f1e611..749661b 100644 --- a/pages/chat/chat.vue +++ b/pages/chat/chat.vue @@ -63,6 +63,7 @@ + @@ -143,12 +144,13 @@ function updateSetting() { \ No newline at end of file + diff --git a/static/.DS_Store b/static/.DS_Store index 74689970ebc3b9991b8e32c44c9dd9e47564eb3e..6c4f25a64ca3a79dd01d23d6358e0f757bd2b352 100644 GIT binary patch delta 221 zcmZp1XmQw(FT}WOa-v`%uT*ulu9301k&c47slg<1X~v$(GUD=*y$lQtEDV_p$qe}n zd6~&&1xY#iNem2(`zG59D|3j6ONdHJNy<#l622oHFTh`%kzekaoSzp^lv0eHlqonEu%f7 u2V)>(5Mu~qC}R|3G(-&(BZOv=NHtm>Ix47G?moH80En delta 182 zcmZp1XmQw(FEn|Q*ltl4hD?TJhJ1#+%;d6yq@4UD1_s8>lbwW>IYh<8MI|JqBqrwy z-;s{UOwP{>&a6s}7vL|>$S?N_KY5ko{Zj%K8%5kQ4l3ej1ZbZ21-LI#^_C=?93Az_%^dkd}G5?}_iUm2(dk$?!Z2O#`#!9dn^x>`R?iB7-DgH>J{%vtqKAz7j;BjE_~Vi|L8Y|!&$lx_vcnM2`Mpr zza%kd!Xsy&71!J!?!PnP^qR#hUEC8D&Fy24d)=*lb0lkD`?H%FM#b^V&(~zy#FXU+ z^xcu|ht}G6&3BnIFaC)gFbEhtUHx3vIVCg!0B}0CtpET3 literal 0 HcmV?d00001 diff --git a/stores/BaseDBStore.js b/stores/BaseDBStore.js index 545f275..664fdd8 100644 --- a/stores/BaseDBStore.js +++ b/stores/BaseDBStore.js @@ -13,7 +13,9 @@ class BaseStore { } checkAndInitDB() { // 获取本地数据库版本 - // this.initDB() + if (config.OnlyUseCachedDB) { + return this.initDB() + } const localVersion = uni.getStorageSync('indexedDBVersion') || 1 console.log('DBVersion: ', localVersion, config.DBversion) if (localVersion === config.DBversion) { diff --git a/stores/userChatGroupStore.js b/stores/userChatGroupStore.js index e3304ce..b714bb2 100644 --- a/stores/userChatGroupStore.js +++ b/stores/userChatGroupStore.js @@ -47,7 +47,8 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { chatSessionID.value = tabelRow.sessionId initMessage(tabelRow.sessionId) } else { - console.warn('本地数据库存在数据') + if (config.OnlyUseCachedDB) return; + console.warn('本地数据库不存在数据') getHistory('refresh') } }, 1000) @@ -132,7 +133,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { displayText: '', // 用于流式渲染展示 dataId: customDataID }; - + console.log(messages.value) const index = messages.value.length; messages.value.push(newMsg); @@ -143,7 +144,12 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { function handleUnload() { newMsg.parentGroupId = chatSessionID.value; - baseDB.db.add(massageName.value, newMsg); + baseDB.db.add(massageName.value, newMsg).then((id) => { + messages.value[index] = { + ...newMsg, + id + }; + }); } window.addEventListener("unload", handleUnload); @@ -192,15 +198,32 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { } // 云端数据 - function badFeedback(dataId, sessionId = chatSessionID.value, content = '') { - let parmas = { - dataId: dataId, - sessionId: sessionId, - userBadFeedback: '这是反馈', - } - // $api.chatRequest('/stepped', parmas, 'POST').then((res) => { - // console.log('反馈成功') - // }) + function badFeedback(msgs, content = '') { + return new Promise((resolve, reject) => { + if (!msgs.dataId) { + return msg('旧数据,没有dataId') + } + let parmas = { + dataId: msgs.dataId, + sessionId: msgs.parentGroupId, + userBadFeedback: content, + } + + const dbData = { + ...toRaw(msgs), + userBadFeedback: content, + } + + $api.chatRequest('/stepped', parmas, 'POST').then((res) => { + baseDB.db.update(massageName.value, dbData) // 更新本地数据库 + messages.value.forEach((item) => { + if (item.id === dbData.id) { + item.userBadFeedback = content + resolve() + } + }) + }) + }) } // 云端数据 @@ -226,11 +249,11 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { sessionId: chatSessionID.value } $api.chatRequest('/detail', params, 'GET', loading).then((res) => { - console.log('detail:', res.data) let list = parseHistoryDetail(res.data.list, chatSessionID.value) if (list.length) { - messages.value = list - baseDB.db.add(massageName.value, list); + baseDB.db.add(massageName.value, list).then((ids) => { + messages.value = listAddId(list, ids) + }); } console.log('解析后:', list) }).catch(() => { @@ -271,7 +294,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { displayText: text, self: self, text: text, - userBadFeedback: element.userBadFeedback, + userBadFeedback: element.userBadFeedback || '', dataId: element.dataId, files, }) @@ -279,12 +302,19 @@ const useChatGroupDBStore = defineStore("messageGroup", () => { return arr } + + function listAddId(list, ids) { + return list.map((item, index) => ({ + ...item, + id: ids[index] || ids + })) + } + return { messages, isTyping, textInput, chatSessionID, - addMessage, tabeList, init, toggleTyping, diff --git a/uni_modules/.DS_Store b/uni_modules/.DS_Store index 06836a6408b5d28cbac0b4c50f179261995529b0..f6cefdf85e4ae1d98dda734267e8fcc3d0c3e331 100644 GIT binary patch delta 67 zcmZoMXfc=|#>B)qu~2NHo+2a1#(>?7j2xSJSf(>hmSumnv7w52Gdl-A2T;joL5}at Vlles)IT(O|k%56_bA-qmW&l@~5DNeR delta 451 zcmb7>O-jQ+9L3)x0gDw{q;x0Z0W`gWI|Y$!1b5PWP^&R%o5W%_-ShyKF5I~hT)B}f zJ&;FmsXwP4pk@g3{%_v@;Pro+Db4svUMIn2^l5U z)&tCc9Y&Bo{x-S~>h5c{dUZeRtwyWC6V=B1a#yX+al=Rs K=c+F|Z|)CAk6|PL diff --git a/unpackage/.DS_Store b/unpackage/.DS_Store index 1fb5a3e7cb4016d8d2f7f4b7d047ad33742de9cc..86154e464630b33c8415570aab58238fa1569db9 100644 GIT binary patch delta 97 zcmZoMXffE}&BVBCvJX?Cr&M*du92a!k&c3~scEf_LbaulfsTTSu~}^`Cx@uAzI9N1 hc1~_yeh&i}Ffu}D23{x)qk1kY* delta 47 zcmZoMXffE}&&0T6asX2yw?uWdg`uU6f|-fo;jD0g3$3K1muaFH5 delta 27 fcmZoMXfc?ul-rSkfkE#-7;M}a#=e=2;~zf&ruYn> diff --git a/utils/markdownParser.js b/utils/markdownParser.js index 521207e..2b9c15e 100644 --- a/utils/markdownParser.js +++ b/utils/markdownParser.js @@ -21,18 +21,21 @@ const md = new MarkdownIt({
${result.jobTitle} -
${result.location}
+
${result.salary}
-
${result.companyName}
+
${result.location}·${result.companyName}
-
${result.salary}
-
${result.education}
-
${result.experience}
+
+
${result.education}
+
${result.experience}
+
+
查看详情
-
` } + //
${result.location}
+ //
${result.salary}
// 代码块 let preCode = "" try {