// ---------- Groq API: транскрипция (WebSocket, base64-чанки — обход DPI) ---------- 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.onerror = reject; reader.readAsDataURL(blob); }); const CHUNK = 4096; // 4KB base64 на чанк const total = Math.ceil(base64.length / CHUNK); const mime = blob.type || 'audio/webm'; console.log('[WS] blob=' + (blob.size/1024).toFixed(1) + 'KB b64=' + base64.length + ' chunks=' + total); return new Promise((resolve) => { try { const ws = new WebSocket('wss://lang.kube5s.ru/ws'); const timeout = setTimeout(() => { console.log('[WS] timeout'); ws.close(); resolve({ text: '', error: 'timeout' }); }, 60000); ws.onopen = async () => { console.log('[WS] connected, sending ' + total + ' chunks'); for (let i = 0; i < base64.length; i += CHUNK) { ws.send(JSON.stringify({ c: Math.floor(i / CHUNK), b: base64.slice(i, i + CHUNK), t: GROQ_API_KEY, m: mime, n: total })); // Даём браузеру протолкнуть буфер — Chrome может терять фреймы без этого await new Promise(r => setTimeout(r, 10)); } ws.send(JSON.stringify({ c: -1 })); console.log('[WS] all sent'); }; 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.onerror = () => { clearTimeout(timeout); ws.close(); resolve({ text: '', error: 'network' }); }; } catch (err) { resolve({ text: '', error: 'network' }); } }); } async function translateToRussian(text) { if (!GROQ_API_KEY) return; try { const res = await fetch('https://lang.kube5s.ru/openai/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${GROQ_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama-3.3-70b-versatile', messages: [{ role: 'user', content: 'Переведи на русский одним словом или фразой: ' + text }], max_tokens: 50, temperature: 0 }) }); const data = await res.json(); if (data.choices?.[0]?.message?.content) { document.getElementById('translation').textContent = '🇷🇺 ' + data.choices[0].message.content; } } catch(e) { /* тихо */ } }