From f4ac9cf717090a30e3722e4fdf1b92ce6e3b6826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 22 May 2026 17:06:47 +0400 Subject: [PATCH] =?UTF-8?q?v49:=20WS=20robust=20=E2=80=94=20onclose,=20rea?= =?UTF-8?q?dyState,=20setTimeout=20defer,=20settled=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 59 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/index.html b/index.html index 9126097..ce46862 100644 --- a/index.html +++ b/index.html @@ -53,7 +53,7 @@ let mediaRecorder, audioChunks = [], userAudioBlob = null, ttsText = ''; let isRecording = false, isAnalyzing = false, micAnalyser = null, micAnimId = null; let recordStartTime = 0, recordDuration = 0; // ---------- Версия ---------- -const VERSION = 'v48'; +const VERSION = 'v49'; document.getElementById('ver').textContent = VERSION; // ---------- Перевод на русский (Groq) ---------- @@ -612,10 +612,13 @@ async function transcribe(blob) { if (!GROQ_API_KEY) return { text: '', error: 'no_key' }; const tStart = performance.now(); - // Кодируем blob в base64 (только текстовые WS-фреймы — DPI не режет) const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = () => resolve(reader.result.split(',')[1]); + reader.onload = () => { + const b64 = reader.result; + if (typeof b64 !== 'string') reject(new Error('not string')); + else resolve(b64.split(',')[1] || b64); + }; reader.onerror = reject; reader.readAsDataURL(blob); }); @@ -623,28 +626,40 @@ async function transcribe(blob) { console.log('[WS] b64=' + (base64.length/1024).toFixed(1) + 'KB'); return new Promise((resolve) => { - try { - const ws = new WebSocket('wss://proxy.kube5s.ru/ws'); - const timeout = setTimeout(() => { - console.log('[WS] timeout'); ws.close(); resolve({ text: '', error: 'timeout' }); - }, 60000); + let settled = false; + const done = (result) => { if (settled) return; settled = true; ws.close(); resolve(result); }; - ws.onopen = () => { - ws.send(JSON.stringify({ token: GROQ_API_KEY, audio_b64: base64, mime: blob.type || 'audio/webm' })); - console.log('[WS] sent'); - }; + const ws = new WebSocket('wss://proxy.kube5s.ru/ws'); + const timeout = setTimeout(() => { console.log('[WS] timeout'); done({ text: '', error: 'timeout' }); }, 60000); - ws.onmessage = (e) => { - clearTimeout(timeout); ws.close(); - const data = JSON.parse(e.data); - console.log('[WS] ← ' + ((performance.now()-tStart)/1000).toFixed(2) + 's → ' + (data.text||'(empty)')); - resolve(data.error ? { text: '', error: data.error } : { text: data.text || '', error: null }); - }; + ws.onopen = () => { + setTimeout(() => { + try { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ token: GROQ_API_KEY, audio_b64: base64, mime: blob.type || 'audio/webm' })); + console.log('[WS] sent'); + } else { + console.log('[WS] not open, state=' + ws.readyState); + clearTimeout(timeout); + done({ text: '', error: 'network' }); + } + } catch (e) { + console.log('[WS] send error:', e); + clearTimeout(timeout); + done({ text: '', error: 'network' }); + } + }, 0); + }; - ws.onerror = () => { clearTimeout(timeout); resolve({ text: '', error: 'network' }); }; - } catch (err) { - resolve({ text: '', error: 'network' }); - } + ws.onmessage = (e) => { + clearTimeout(timeout); + const data = JSON.parse(e.data); + console.log('[WS] ← ' + ((performance.now()-tStart)/1000).toFixed(2) + 's → ' + (data.text||'(empty)')); + done(data.error ? { text: '', error: data.error } : { text: data.text || '', error: null }); + }; + + ws.onerror = () => { console.log('[WS] error'); clearTimeout(timeout); done({ text: '', error: 'network' }); }; + ws.onclose = (e) => { console.log('[WS] close code=' + e.code + ' reason=' + e.reason); clearTimeout(timeout); done({ text: '', error: 'network' }); }; }); }