feat: show learning progress in web app

This commit is contained in:
user
2026-04-26 18:41:13 +09:00
parent ce38189f33
commit 7866f6dcb3
12 changed files with 245 additions and 6 deletions

View File

@@ -2,6 +2,7 @@ const state = {
session: null,
selectedQuestion: null,
lastAnswer: null,
progress: null,
};
const els = {
@@ -11,6 +12,8 @@ const els = {
answerButton: document.querySelector("#answer-button"),
questions: document.querySelector("#questions"),
feedback: document.querySelector("#feedback"),
progress: document.querySelector("#progress"),
refreshProgress: document.querySelector("#refresh-progress"),
status: document.querySelector("#status-line"),
error: document.querySelector("#error-line"),
title: document.querySelector("#session-title"),
@@ -45,6 +48,11 @@ els.sessionForm.addEventListener("submit", async (event) => {
}
});
els.refreshProgress.addEventListener("click", async () => {
clearError();
await refreshProgress();
});
els.answerForm.addEventListener("submit", async (event) => {
event.preventDefault();
clearError();
@@ -63,6 +71,7 @@ els.answerForm.addEventListener("submit", async (event) => {
});
state.lastAnswer = answer;
renderFeedback();
await refreshProgress();
setStatus(`Answer graded as ${answer.grade.overall}`);
} catch (error) {
showError(error.message);
@@ -96,6 +105,54 @@ function renderSession() {
els.answerButton.disabled = !state.selectedQuestion;
}
async function refreshProgress() {
if (!state.session) return;
setStatus("Refreshing learning progress...");
try {
const userID = encodeURIComponent(state.session.user_id);
const [memory, readiness, challenge] = await Promise.all([
request(`/api/v1/learners/${userID}/memory`),
request(`/api/v1/learners/${userID}/readiness-map`),
request(`/api/v1/learners/${userID}/next-challenge`),
]);
state.progress = { memory, readiness, challenge };
renderProgress();
setStatus("Learning progress updated");
} catch (error) {
showError(error.message);
renderProgress();
}
}
function renderProgress() {
els.refreshProgress.disabled = !state.session;
if (!state.progress) {
els.progress.className = "feedback empty-state";
els.progress.textContent = "Answer once to update learner memory and readiness.";
return;
}
const { memory, readiness, challenge } = state.progress;
const mastery = memory.mastery || [];
els.progress.className = "feedback";
els.progress.innerHTML = `
<section>
<div class="readiness-value">${readiness.readiness_percentage}%</div>
<p class="status-line">${escapeHTML(memory.profile.target_role)} readiness</p>
</section>
<section>
<h2>Concept memory</h2>
<div>${mastery.map((item) => `<span class="concept-pill">${escapeHTML(item.concept.label)} · ${escapeHTML(item.state)}</span>`).join("")}</div>
</section>
<section>
<h2>Next challenge</h2>
<p class="status-line">${escapeHTML(challenge.concept.label)} · ${escapeHTML(challenge.ladder_level)}</p>
<p>${escapeHTML(challenge.question)}</p>
</section>
`;
}
function renderFeedback() {
if (!state.lastAnswer) {
els.feedback.className = "feedback empty-state";