// ---------- Pronunciation Assessment ---------- async function assessPronunciation(expectedText, whisperResult) { try { const r = await fetch('/pronounce/assess', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expected: expectedText, whisper: whisperResult, lang: 'it' }) }); if (!r.ok) { console.log('[PRONOUNCE] HTTP', r.status); return null; } const data = await r.json(); console.log('[PRONOUNCE] score=' + data.overall_score + ' ' + data.quality); return data; } catch(e) { console.log('[PRONOUNCE] ERR', e); return null; } } function renderPronunciation(assess) { if (!assess) return ''; const s = assess.overall_score; const emoji = s >= 90 ? '🟢' : s >= 70 ? '🟡' : s >= 50 ? '🟠' : '🔴'; let html = '
'; // Score bar const barColor = s >= 90 ? '#4caf50' : s >= 70 ? '#ff9800' : s >= 50 ? '#f44336' : '#9e9e9e'; html += '
'; html += '' + emoji + ''; html += '
'; html += '
Произношение: ' + s + '%
'; html += '
' + (assess.quality || '') + '
'; // Bar html += '
'; html += '
'; html += '
'; // Details grid html += '
'; // Phonemes if (assess.phoneme_comparison) { const pc = assess.phoneme_comparison; html += '
🔤 Фонемы: ' + pc.accuracy + '%
'; html += '
✓ Совпадений: ' + (pc.matches || 0) + '/' + (pc.total_phonemes || 0) + '
'; if (pc.errors && pc.errors.length > 0) { const subs = pc.errors.filter(e => e.type === 'sub'); const dels = pc.errors.filter(e => e.type === 'del'); const inss = pc.errors.filter(e => e.type === 'ins'); html += '
'; if (subs.length) html += 'Замен: ' + subs.map(e => e.expected + '→' + e.actual).join(', ') + ' '; if (dels.length) html += 'Пропущено: ' + dels.map(e => e.expected).join(', ') + ' '; if (inss.length) html += 'Лишних: ' + inss.map(e => e.actual).join(', ') + ''; html += '
'; } } // Timing if (assess.timing) { const tm = assess.timing; html += '
⏱ Ритм: ' + (tm.rhythm_score || 0) + '%
'; html += '
📏 Длит-ть: ' + (tm.total_duration || 0).toFixed(1) + 'с
'; if (tm.timing_quality) { const tq = tm.timing_quality; html += '
'; html += tq === 'good' ? '✅ Ритм ровный' : tq === 'ok' ? '⚠️ Ритм неровный' : '❌ Ритм сбит'; html += '
'; } } html += '
'; // Feedback if (assess.feedback) { html += '
'; html += '💬 ' + assess.feedback; html += '
'; } // Phoneme detail if (assess.expected && assess.expected.phonemes) { html += '
'; html += '🎯 Эталон: /' + assess.expected.phonemes.join(' ') + '/'; if (assess.actual && assess.actual.phonemes) { html += ' | 🗣 Вы: /' + assess.actual.phonemes.join(' ') + '/'; } html += '
'; } html += '
'; return html; }