// Простые настройки
const BACKEND_URL = (new URLSearchParams(location.search).get("api") || "").trim() || "http://localhost:8000";
const LESSON_ID = (new URLSearchParams(location.search).get("lesson") || "L01").trim();
let lessonData = null;
let itemIndex = 0;
let mediaRecorder = null;
let chunks = [];
const lessonTitle = document.getElementById("lessonTitle");
const itemCounter = document.getElementById("itemCounter");
const promptEl = document.getElementById("prompt");
const tipsEl = document.getElementById("tips");
const feedbackEl = document.getElementById("feedback");
const player = document.getElementById("player");
const buddyAudio = document.getElementById("buddyAudio");
const recBtn = document.getElementById("recBtn");
const nextBtn = document.getElementById("nextBtn");
const repeatBtn = document.getElementById("repeatBtn");
const progressBar = document.getElementById("progressBar");
const buddySpeakBtn = document.getElementById("buddySpeakBtn");
async function loadLesson() {
const res = await fetch(`./lessons/${LESSON_ID}.json`);
if (!res.ok) {
promptEl.innerHTML = "Не найден файл урока. Проверьте параметр ?lesson=
и наличие JSON.";
return;
}
lessonData = await res.json();
lessonTitle.textContent = `${lessonData.title || LESSON_ID}`;
itemIndex = 0;
showItem();
}
function showItem() {
if (!lessonData) return;
const items = lessonData.items || [];
const item = items[itemIndex];
itemCounter.textContent = `${itemIndex + 1}/${items.length}`;
promptEl.textContent = item.prompt || "";
tipsEl.textContent = (item.tips || []).join(" • ");
feedbackEl.textContent = "";
progressBar.style.width = `${Math.round(((itemIndex) / items.length) * 100)}%`;
nextBtn.disabled = true;
repeatBtn.disabled = true;
buddySpeakBtn.disabled = true;
buddyAudio.src = "";
}
async function startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
chunks = [];
mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
mediaRecorder.onstop = async () => {
const blob = new Blob(chunks, { type: "audio/webm" });
player.src = URL.createObjectURL(blob);
const form = new FormData();
const refText = (lessonData.items[itemIndex].reference_text || "").trim();
form.append("audio", blob, "speech.webm");
form.append("lesson_id", LESSON_ID);
form.append("item_index", String(itemIndex));
form.append("reference_text", refText);
feedbackEl.textContent = "⏳ Оцениваем произношение…";
try {
const res = await fetch(`${BACKEND_URL}/assess`, { method: "POST", body: form });
const data = await res.json();
const lines = [];
lines.push(`Точность: ${data.score_accuracy ?? "-"}%`);
(data.diagnostics || []).forEach(d => {
lines.push(`• ${d.target}: ${d.issue} — ${d.advice}`);
});
feedbackEl.textContent = lines.join("\n");
// Синтез ответа Бади голосом (по желанию)
const reply = data.reply || "Хорошая попытка! Попробуй ещё раз — удлиняй гласный и будь внимательнее к конечному согласному.";
buddySpeakBtn.onclick = async () => {
const tform = new FormData();
tform.append("text", reply);
const tres = await fetch(`${BACKEND_URL}/tts`, { method: "POST", body: tform });
const tdata = await tres.json();
if (tdata.url) {
buddyAudio.src = tdata.url;
} else if (tdata.inline_base64) {
buddyAudio.src = `data:audio/mp3;base64,${tdata.inline_base64}`;
}
};
buddySpeakBtn.disabled = false;
nextBtn.disabled = false;
repeatBtn.disabled = false;
} catch (e) {
feedbackEl.textContent = "Ошибка при оценке. Проверь BACKEND_URL и доступность сервера.";
}
};
mediaRecorder.start();
recBtn.textContent = "■ Стоп";
} catch (e) {
alert("Браузер не дал доступ к микрофону. Откройте страницу напрямую (не в приватном режиме) и разрешите микрофон.");
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== "inactive") {
mediaRecorder.stop();
recBtn.textContent = "● Записать";
}
}
recBtn.onclick = () => {
if (!mediaRecorder || mediaRecorder.state === "inactive") startRecording();
else stopRecording();
};
nextBtn.onclick = () => {
const total = (lessonData.items || []).length;
if (itemIndex < total - 1) {
itemIndex += 1;
showItem();
} else {
progressBar.style.width = "100%";
feedbackEl.textContent = "Готово! Урок завершён ✅";
nextBtn.disabled = true;
repeatBtn.disabled = true;
}
};
repeatBtn.onclick = () => {
showItem();
};
loadLesson();