From a979e4b6d3af321f22168c6473c0f431762cae7a Mon Sep 17 00:00:00 2001 From: Dooy Date: Thu, 10 Oct 2024 18:25:02 +0800 Subject: [PATCH] 2.21.2 --- changlog.md | 3 + package.json | 2 +- src-tauri/tauri.conf.json | 2 +- src/locales/en-US.ts | 10 + src/locales/fr-FR.ts | 9 + src/locales/ko-KR.ts | 11 +- src/locales/ru-RU.ts | 11 +- src/locales/tr-TR.ts | 11 +- src/locales/vi-VN.ts | 9 + src/locales/zh-CN.ts | 10 + src/locales/zh-TW.ts | 11 +- src/utils/wav_renderer.ts | 111 ++++++++ src/views/mj/aiFooter.vue | 2 + src/views/mj/aiMobileMenu.vue | 5 + src/views/mj/aiSider.vue | 13 + src/views/mj/aiTextSetting.vue | 20 +- src/views/wav/an_main.vue | 23 ++ src/views/wav/css/in-and-out.css | 107 ++++++++ src/views/wav/realtime.vue | 423 +++++++++++++++++++++++++++++++ src/views/wav/realtimeLayout.vue | 17 ++ src/views/wav/wav.vue | 45 +++- 21 files changed, 838 insertions(+), 17 deletions(-) create mode 100644 src/utils/wav_renderer.ts create mode 100644 src/views/wav/an_main.vue create mode 100644 src/views/wav/css/in-and-out.css create mode 100644 src/views/wav/realtime.vue create mode 100644 src/views/wav/realtimeLayout.vue diff --git a/changlog.md b/changlog.md index 644383fb4..7257cb2a8 100644 --- a/changlog.md +++ b/changlog.md @@ -1,6 +1,9 @@ # 功能升级日志 # 计划 +# 2.21.2 +- 😄 新增:支持实时语音对话服务 realtime `gpt-4o-realtime-preview` + # 2.21.1 - 😄 优化:dall格式图片本地存储(flex,idemgram 不会图片链接过期而找不到 ) - 😄 优化:kling图片本地存储 diff --git a/package.json b/package.json index 5b72ee690..3006a3631 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chatgpt-web-midjourney-proxy", - "version": "2.21.1", + "version": "2.21.2", "private": false, "description": "ChatGPT Web Midjourney Proxy", "author": "Dooy ", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 43b259964..6fba52467 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "ChatGPT-MJ", - "version": "2.21.1" + "version": "2.21.2" }, "tauri": { "allowlist": { diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 52dc69f7c..ca697cf81 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -223,6 +223,16 @@ export default { "left_turn_forward": "Turn Left and Push Forward" ,kling:'Kling' + ,rttab: 'RealTime', + rtinfo: 'Realtime voice conversation service', + rtsetting: 'Please set the server. Currently, Realtime only supports remote services; for local services, please contact the author.', + rjcloded: 'Connection has been disconnected', + checkkey: 'Please check if the API key is correct', + rtsuccess: 'Connection is normal, maintaining the call', + rtservererror: 'WebSocket connection server error!', + rtservererror2: 'Recording is not supported, it may be due to device reasons!', + rtconecting: 'Connecting to the server' + }, "mjset": { "server": "Server", diff --git a/src/locales/fr-FR.ts b/src/locales/fr-FR.ts index 7f9f6632b..c72ec0c37 100644 --- a/src/locales/fr-FR.ts +++ b/src/locales/fr-FR.ts @@ -221,6 +221,15 @@ export default { "right_turn_forward": "Tourner à droite et avancer", "left_turn_forward": "Tourner à gauche et avancer" ,kling:'Kling' + ,rttab: 'Voix', + rtinfo: 'Service de conversation vocale en temps réel (realtime)', + rtsetting: 'Veuillez configurer le serveur. Actuellement, Realtime ne prend en charge que les services à distance ; pour les services locaux, veuillez contacter l\'auteur.', + rjcloded: 'La connexion a été déconnectée', + checkkey: 'Veuillez vérifier si la clé API est correcte', + rtsuccess: 'Connexion normale, maintien de l\'appel', + rtservererror: 'Erreur de connexion au serveur WebSocket !', + rtservererror2: 'Enregistrement non pris en charge, cela peut être dû à un problème de matériel !', +rtconecting: 'Connexion au serveur en cours' }, "mjset": { "server": "Serveur", diff --git a/src/locales/ko-KR.ts b/src/locales/ko-KR.ts index 238733ef7..8752c17ef 100644 --- a/src/locales/ko-KR.ts +++ b/src/locales/ko-KR.ts @@ -218,7 +218,16 @@ export default { "forward_up": "앞으로 밀고 위로 이동하기", "right_turn_forward": "오른쪽으로 돌리고 앞으로 밀기", "left_turn_forward": "왼쪽으로 돌리고 앞으로 밀기" - ,kling:'Kling' + ,kling:'Kling', + rttab: '음성', + rtinfo: '실시간 음성 대화 서비스 (realtime)', + rtsetting: '서버를 설정하십시오. 현재 Realtime은 원격 서비스만 지원합니다. 로컬 서비스가 필요하면 저자에게 문의하십시오.', + rjcloded: '연결이 끊어졌습니다', + checkkey: 'API 키가 올바른지 확인하십시오', + rtsuccess: '연결이 정상이며 통화를 유지하고 있습니다', + rtservererror: 'WebSocket 서버 연결 오류!', + rtservererror2: '녹음이 지원되지 않습니다. 장치 문제일 수 있습니다!', + rtconecting: '서버에 연결 중' }, "mjset": { "server": "서버" diff --git a/src/locales/ru-RU.ts b/src/locales/ru-RU.ts index cc7faca99..231a56040 100644 --- a/src/locales/ru-RU.ts +++ b/src/locales/ru-RU.ts @@ -221,7 +221,16 @@ export default { "forward_up": "Продвинуть вперед и поднять", "right_turn_forward": "Повернуть вправо и продвинуться вперед", "left_turn_forward": "Повернуть влево и продвинуться вперед" -,kling:'Kling' +,kling:'Kling', +rttab: 'Голос', +rtinfo: 'Служба голосового общения в реальном времени (realtime)', +rtsetting: 'Пожалуйста, настройте сервер. В настоящее время Realtime поддерживает только удаленные службы; для локальных услуг свяжитесь с автором.', +rjcloded: 'Соединение разорвано', +checkkey: 'Пожалуйста, проверьте правильность API-ключа', +rtsuccess: 'Соединение нормально, поддерживаем звонок', +rtservererror: 'Ошибка подключения к серверу WebSocket!', +rtservererror2: 'Запись не поддерживается, возможно, это связано с устройством!', +rtconecting: 'Подключение к серверу' }, "mjset": { diff --git a/src/locales/tr-TR.ts b/src/locales/tr-TR.ts index 53a9d7e95..182dad507 100644 --- a/src/locales/tr-TR.ts +++ b/src/locales/tr-TR.ts @@ -220,7 +220,16 @@ export default { "forward_up": "İleri it ve yukarı kaldır", "right_turn_forward": "Sağa dön ve ileri git", "left_turn_forward": "Sola dön ve ileri git" - ,kling:'Kling' + ,kling:'Kling', + rttab: 'Ses', + rtinfo: 'Gerçek zamanlı sesli görüşme hizmeti (realtime)', + rtsetting: 'Lütfen sunucuyu ayarlayın. Şu anda Realtime yalnızca uzaktan hizmetleri desteklemektedir; yerel hizmetler için lütfen yazarla iletişime geçin.', + rjcloded: 'Bağlantı kesildi', + checkkey: 'API anahtarının doğru olup olmadığını kontrol edin', + rtsuccess: 'Bağlantı normal, görüşmeyi sürdürüyor', + rtservererror: 'WebSocket sunucu bağlantı hatası!', + rtservererror2: 'Kaydetme desteklenmiyor, bu cihaz kaynaklı bir sorun olabilir!', + rtconecting: 'Sunucuya bağlanılıyor' }, "mjset": { "server": "Sunucu", diff --git a/src/locales/vi-VN.ts b/src/locales/vi-VN.ts index 36f2af5b9..c3b7c8185 100644 --- a/src/locales/vi-VN.ts +++ b/src/locales/vi-VN.ts @@ -220,6 +220,15 @@ export default { "right_turn_forward": "Quay phải và đẩy tới", "left_turn_forward": "Quay trái và đẩy tới" ,kling:'Kling' + ,rttab: 'Giọng nói', + rtinfo: 'Dịch vụ hội thoại giọng nói thời gian thực (realtime)', + rtsetting: 'Vui lòng thiết lập máy chủ. Hiện tại, Realtime chỉ hỗ trợ dịch vụ từ xa; nếu cần dịch vụ cục bộ, vui lòng liên hệ với tác giả.', + rjcloded: 'Kết nối đã bị ngắt', + checkkey: 'Vui lòng kiểm tra xem API key có chính xác không', + rtsuccess: 'Kết nối bình thường, duy trì cuộc gọi', + rtservererror: 'Lỗi kết nối máy chủ WebSocket!', + rtservererror2: 'Không hỗ trợ ghi âm, có thể do thiết bị!', + rtconecting: 'Đang kết nối đến máy chủ' }, "mjset": { "server": "Máy chủ", diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index b033c716a..4bd1a7d45 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -326,6 +326,16 @@ export default { ,left_turn_forward: '左旋推进' ,kling:'可灵' + + ,rttab:'语音' + ,rtinfo:'实时语音对话服务(realtime)' + ,rtsetting:'请设置服务端,目前Realtime 仅支持远程服务;需本地服务请联系作者' + ,rjcloded:'连接已断开' + ,checkkey:'请检查 api key 是否正确' + ,rtsuccess:'连接正常保持通话' + ,rtservererror:'websocket 连接服务器错误!' + ,rtservererror2:'不支持录音,可能是设备原因!' + ,rtconecting:'正在连接服务器' }, draw: { diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 92484e7a6..83a6996fd 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -215,7 +215,16 @@ export default { "forward_up": "推進上移", "right_turn_forward": "右旋推進", "left_turn_forward": "左旋推進" - ,kling:'可灵' + ,kling:'可灵', + rttab: '語音', + rtinfo: '實時語音對話服務(realtime)', + rtsetting: '請設置服務器,目前Realtime僅支持遠程服務;需本地服務請聯繫作者', + rjcloded: '連接已斷開', + checkkey: '請檢查api key是否正確', + rtsuccess: '連接正常保持通話', + rtservererror: 'websocket連接服務器錯誤!', + rtservererror2: '不支持錄音,可能是設備原因!', + rtconecting: '正在連接服務器' }, "mjset": { diff --git a/src/utils/wav_renderer.ts b/src/utils/wav_renderer.ts new file mode 100644 index 000000000..7acd22c5d --- /dev/null +++ b/src/utils/wav_renderer.ts @@ -0,0 +1,111 @@ +const dataMap = new WeakMap(); + +/** + * Normalizes a Float32Array to Array(m): We use this to draw amplitudes on a graph + * If we're rendering the same audio data, then we'll often be using + * the same (data, m, downsamplePeaks) triplets so we give option to memoize + */ +const normalizeArray = ( + data: Float32Array, + m: number, + downsamplePeaks: boolean = false, + memoize: boolean = false +) => { + let cache, mKey, dKey; + if (memoize) { + mKey = m.toString(); + dKey = downsamplePeaks.toString(); + cache = dataMap.has(data) ? dataMap.get(data) : {}; + dataMap.set(data, cache); + cache[mKey] = cache[mKey] || {}; + if (cache[mKey][dKey]) { + return cache[mKey][dKey]; + } + } + const n = data.length; + const result = new Array(m); + if (m <= n) { + // Downsampling + result.fill(0); + const count = new Array(m).fill(0); + for (let i = 0; i < n; i++) { + const index = Math.floor(i * (m / n)); + if (downsamplePeaks) { + // take highest result in the set + result[index] = Math.max(result[index], Math.abs(data[i])); + } else { + result[index] += Math.abs(data[i]); + } + count[index]++; + } + if (!downsamplePeaks) { + for (let i = 0; i < result.length; i++) { + result[i] = result[i] / count[i]; + } + } + } else { + for (let i = 0; i < m; i++) { + const index = (i * (n - 1)) / (m - 1); + const low = Math.floor(index); + const high = Math.ceil(index); + const t = index - low; + if (high >= n) { + result[i] = data[n - 1]; + } else { + result[i] = data[low] * (1 - t) + data[high] * t; + } + } + } + if (memoize) { + cache[mKey as string][dKey as string] = result; + } + return result; +}; + +export const WavRenderer = { + /** + * Renders a point-in-time snapshot of an audio sample, usually frequency values + * @param canvas + * @param ctx + * @param data + * @param color + * @param pointCount number of bars to render + * @param barWidth width of bars in px + * @param barSpacing spacing between bars in px + * @param center vertically center the bars + */ + drawBars: ( + canvas: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + data: Float32Array, + color: string, + pointCount: number = 0, + barWidth: number = 0, + barSpacing: number = 0, + center: boolean = false + ) => { + pointCount = Math.floor( + Math.min( + pointCount, + (canvas.width - barSpacing) / (Math.max(barWidth, 1) + barSpacing) + ) + ); + if (!pointCount) { + pointCount = Math.floor( + (canvas.width - barSpacing) / (Math.max(barWidth, 1) + barSpacing) + ); + } + if (!barWidth) { + barWidth = (canvas.width - barSpacing) / pointCount - barSpacing; + } + const points = normalizeArray(data, pointCount, true); + for (let i = 0; i < pointCount; i++) { + const amplitude = Math.abs(points[i]); + const height = Math.max(1, amplitude * canvas.height); + const x = barSpacing + i * (barWidth + barSpacing); + const y = center ? (canvas.height - height) / 2 : canvas.height - height; + ctx.fillStyle = color; + ctx.fillRect(x, y, barWidth, height); + } + }, +}; diff --git a/src/views/mj/aiFooter.vue b/src/views/mj/aiFooter.vue index 3eff36463..ca087820a 100644 --- a/src/views/mj/aiFooter.vue +++ b/src/views/mj/aiFooter.vue @@ -1,10 +1,12 @@ \ No newline at end of file diff --git a/src/views/mj/aiMobileMenu.vue b/src/views/mj/aiMobileMenu.vue index 208d6af62..525bd4900 100644 --- a/src/views/mj/aiMobileMenu.vue +++ b/src/views/mj/aiMobileMenu.vue @@ -43,6 +43,11 @@ watch(()=>homeStore.myData.act, (n:string)=>{
GPTs
+
+ +
{{$t('mj.rttab')}}
+
+
diff --git a/src/views/mj/aiSider.vue b/src/views/mj/aiSider.vue index c15565364..c1468d199 100644 --- a/src/views/mj/aiSider.vue +++ b/src/views/mj/aiSider.vue @@ -134,6 +134,19 @@ const chatId= computed(()=>chatStore.active??'1002' ); + + + + {{ $t('mj.rtinfo') }} + + + diff --git a/src/views/mj/aiTextSetting.vue b/src/views/mj/aiTextSetting.vue index 0c73594e3..759760e75 100644 --- a/src/views/mj/aiTextSetting.vue +++ b/src/views/mj/aiTextSetting.vue @@ -3,15 +3,29 @@ import { homeStore } from '@/store'; import { computed ,ref } from 'vue' import aiSetServer from './aiSetServer.vue'; -import { NTag,NModal } from 'naive-ui' +import { NTag,NModal,NButton } from 'naive-ui' const isHideServer= computed( ()=>homeStore.myData.session.isHideServer ) const st= ref({show:false}) +const pp = defineProps<{msgInfo?:string}>(); +const emit =defineEmits(['close']) +const closeed=()=>{ + emit('close') + st.value.show=false +} \ No newline at end of file diff --git a/src/views/wav/an_main.vue b/src/views/wav/an_main.vue new file mode 100644 index 000000000..7fd0161c5 --- /dev/null +++ b/src/views/wav/an_main.vue @@ -0,0 +1,23 @@ + + \ No newline at end of file diff --git a/src/views/wav/css/in-and-out.css b/src/views/wav/css/in-and-out.css new file mode 100644 index 000000000..08a14659f --- /dev/null +++ b/src/views/wav/css/in-and-out.css @@ -0,0 +1,107 @@ + +/** + * in-and-out + * + * @author in-and-out +*/ +@-webkit-keyframes in-and-out { + 0%, 30% { + -webkit-transform: rotate(calc(var(--offset) * -1deg)); + transform: rotate(calc(var(--offset) * -1deg)); } + 70%, 100% { + -webkit-transform: rotate(calc(var(--offset) * 1deg)); + transform: rotate(calc(var(--offset) * 1deg)); } } +@keyframes in-and-out { + 0%, 30% { + -webkit-transform: rotate(calc(var(--offset) * -1deg)); + transform: rotate(calc(var(--offset) * -1deg)); } + 70%, 100% { + -webkit-transform: rotate(calc(var(--offset) * 1deg)); + transform: rotate(calc(var(--offset) * 1deg)); } } + +@-webkit-keyframes in-and-out-two { + 0%, 30% { + -webkit-transform: rotate(calc(var(--offset) * 1deg)); + transform: rotate(calc(var(--offset) * 1deg)); } + 70%, 100% { + -webkit-transform: rotate(calc(var(--offset) * -1deg)); + transform: rotate(calc(var(--offset) * -1deg)); } } + +@keyframes in-and-out-two { + 0%, 30% { + -webkit-transform: rotate(calc(var(--offset) * 1deg)); + transform: rotate(calc(var(--offset) * 1deg)); } + 70%, 100% { + -webkit-transform: rotate(calc(var(--offset) * -1deg)); + transform: rotate(calc(var(--offset) * -1deg)); } } + +@-webkit-keyframes in-and-out-children { + 0%, 50% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); } + 80%, 100% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); } } + +@keyframes in-and-out-children { + 0%, 50% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); } + 80%, 100% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); } } + +@-webkit-keyframes in-and-out-two-children { + 0%, 50% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); } + 80%, 100% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); } } + +@keyframes in-and-out-two-children { + 0%, 50% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 40px); } + 80%, 100% { + -webkit-transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); + transform: rotate(calc(var(--rotation) * 1deg)) translate(0, 15px); } } + +.in-and-out { + --offset: 18; } + .in-and-out > div { + --radius: 30; + --size: 10; + height: calc(var(--size) * 1px); + width: calc(var(--size) * 1px); + position: absolute; } + .in-and-out > div:nth-of-type(1) { + --multiplier: 1; + -webkit-animation: in-and-out 1s infinite ease both; + animation: in-and-out 1s infinite ease both; } + .in-and-out > div:nth-of-type(1) div { + -webkit-animation: in-and-out-children 1s infinite ease; + animation: in-and-out-children 1s infinite ease; } + .in-and-out > div:nth-of-type(2) { + --multiplier: 2; + -webkit-animation: in-and-out-two 1s infinite ease both; + animation: in-and-out-two 1s infinite ease both; } + .in-and-out > div:nth-of-type(2) div { + -webkit-animation: in-and-out-two-children 1s infinite ease; + animation: in-and-out-two-children 1s infinite ease; } + .in-and-out div div { + background: var(--primary); + border-radius: 100%; + height: 100%; + position: absolute; + width: 100%; } + .in-and-out div div:nth-child(1) { + --rotation: 72; } + .in-and-out div div:nth-child(2) { + --rotation: 144; } + .in-and-out div div:nth-child(3) { + --rotation: 216; } + .in-and-out div div:nth-child(4) { + --rotation: 288; } + .in-and-out div div:nth-child(5) { + --rotation: 360; } \ No newline at end of file diff --git a/src/views/wav/realtime.vue b/src/views/wav/realtime.vue new file mode 100644 index 000000000..df138b084 --- /dev/null +++ b/src/views/wav/realtime.vue @@ -0,0 +1,423 @@ + + + + \ No newline at end of file diff --git a/src/views/wav/realtimeLayout.vue b/src/views/wav/realtimeLayout.vue new file mode 100644 index 000000000..ee87efa41 --- /dev/null +++ b/src/views/wav/realtimeLayout.vue @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/src/views/wav/wav.vue b/src/views/wav/wav.vue index 66e3c5a1f..753b48ec3 100644 --- a/src/views/wav/wav.vue +++ b/src/views/wav/wav.vue @@ -4,15 +4,17 @@ import { NButton,NInput, useMessage,NEmpty } from 'naive-ui'; import { WavRecorder, WavStreamPlayer } from '@openai/realtime-wavtools'; import { RealtimeClient } from '@openai/realtime-api-beta'; import { ItemType } from '@openai/realtime-api-beta/dist/lib/client.js'; -import { ref } from 'vue'; +import { ref,onMounted } from 'vue'; import { mlog } from '@/api'; +import realtime from './realtime.vue'; +import { homeStore } from '@/store'; const ms= useMessage(); const wavRecorder= new WavRecorder({ sampleRate: 24000 }) const wavStreamPlayer= new WavStreamPlayer({ sampleRate: 24000 }) -const st= ref({apikey:'', isConnect:false,baseUrl:'wss://api.openai.com/v1/realtime' }) +const st= ref({apikey:'', isConnect:false,baseUrl:'',isRealtime:false }) const realtimeEvents= ref([]); @@ -64,7 +66,7 @@ const go= async()=>{ client.sendUserMessageContent([ { type: `input_text`, - text: `Hello!`, + text: `请用中文回答我!`, // text: `For testing purposes, I want you to list ten car brands. Number each item, e.g. "one (or whatever number you are one): the item name".` }, ]); @@ -73,6 +75,10 @@ const go= async()=>{ client.updateSession({ turn_detection: { type: 'server_vad' }, }); + // client.on('error', (event: any) =>{ + // ms.error('发生错误:'+event); + // console.error('error.event>>',event); + // }); await wavRecorder.record((data: { mono: Int16Array | ArrayBuffer; }) => { try{ client.appendInputAudio(data.mono) @@ -83,7 +89,10 @@ const go= async()=>{ return } }); + myListen(); + localStorage.setItem("_t_apikey", st.value.apikey); + localStorage.setItem("_t_baseurl", st.value.baseUrl); } @@ -120,7 +129,10 @@ const myListen=()=>{ client.on('realtime.event', (realtimeEvent: RealtimeEvent) => { setRealtimeEvents(realtimeEvent); }); - client.on('error', (event: any) => console.error('error.event>>',event)); + client.on('error', (event: any) =>{ + ms.error('发生错误:'+event); + console.error('error.event>>',event); + }); client.on('conversation.interrupted', async () => { const trackSampleOffset = await wavStreamPlayer.interrupt(); if (trackSampleOffset?.trackId) { @@ -145,11 +157,15 @@ const myListen=()=>{ }); } const setItems=(iitems: ItemType[])=>{ - mlog("setItems", iitems.length, iitems ) + //mlog("setItems", iitems.length, iitems ) items.value=iitems } const setRealtimeEvents=(realtimeEvent: RealtimeEvent )=>{ //mlog("setRealtimeEvents", realtimeEvent.event , realtimeEvent ) + let ev= {...realtimeEvent.event} + if(ev.type=="error" && ev.error && ev.error.message){ + ms.error(ev.error.message) + } const lastEvent = realtimeEvents.value[ realtimeEvents.value.length - 1]; if (lastEvent?.event.type === realtimeEvent.event.type) { @@ -160,15 +176,28 @@ const setRealtimeEvents=(realtimeEvent: RealtimeEvent )=>{ return realtimeEvents.value.concat(realtimeEvent); } } +// onMounted(() => { +// // st.value.apikey = localStorage.getItem("_t_apikey") || ""; +// // st.value.baseUrl = localStorage.getItem("_t_baseurl") || "wss://api.openai.com/v1/realtime"; + +// }), +onMounted(()=>{ + st.value.apikey = localStorage.getItem("_t_apikey") || ""; + st.value.baseUrl = localStorage.getItem("_t_baseurl") || "wss://api.openai.com/v1/realtime"; +})