feat(lessons): implement comprehensive lesson configurator, session runner and AI debrief (Beta)

This commit is contained in:
2026-10-07 13:03:16 +03:00
parent 31e105b2c2
commit 718e9797eb
12 changed files with 4093 additions and 1 deletions
+65
View File
@@ -10,6 +10,7 @@ import { initSrs, startSrsTrainingSession, openStatsDashboard } from './modules/
import { initReader, loadBookshelf, openBook } from './modules/reader.js';
import { initCefr, loadCefrOverview, startCefrQuiz } from './modules/cefr.js';
import { initGrammar, loadGrammarUnits, openGrammarUnit } from './modules/grammar.js';
import { initLessonModule, openLessonConfigurator } from './modules/lesson.js';
let currentUser = null;
let isRouting = false;
@@ -44,6 +45,7 @@ export function updateActiveNavState(viewName) {
'reader': 'drawer-nav-reader',
'cefr': 'drawer-nav-cefr',
'grammar': 'drawer-nav-grammar',
'lesson': 'drawer-nav-lesson',
};
const targetId = drawerMap[viewName];
if (targetId) {
@@ -73,6 +75,8 @@ export function applyInitialViewPreflight() {
hash = '#cefr';
} else if (lastView === 'grammar-view') {
hash = '#grammar';
} else if (lastView === 'lesson-view') {
hash = '#lesson';
}
} catch (e) {}
}
@@ -82,6 +86,8 @@ export function applyInitialViewPreflight() {
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
const grammarView = document.getElementById('grammar-view');
const lessonView = document.getElementById('lesson-view');
const lessonRunnerView = document.getElementById('lesson-runner-view');
const bookshelfContainer = document.getElementById('bookshelf-container');
const readerInterface = document.getElementById('reader-interface');
const readerBookTitle = document.getElementById('reader-book-title');
@@ -91,6 +97,8 @@ export function applyInitialViewPreflight() {
if (srsView) srsView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (grammarView) grammarView.classList.add('hidden');
if (lessonView) lessonView.classList.add('hidden');
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
if (readerView) readerView.classList.remove('hidden');
updateActiveNavState('reader');
if (hash.includes('/book/')) {
@@ -103,6 +111,8 @@ export function applyInitialViewPreflight() {
if (readerView) readerView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (grammarView) grammarView.classList.add('hidden');
if (lessonView) lessonView.classList.add('hidden');
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
if (srsView) srsView.classList.remove('hidden');
updateActiveNavState('srs');
} else if (hash.startsWith('#cefr')) {
@@ -110,6 +120,8 @@ export function applyInitialViewPreflight() {
if (readerView) readerView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (grammarView) grammarView.classList.add('hidden');
if (lessonView) lessonView.classList.add('hidden');
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
if (cefrView) cefrView.classList.remove('hidden');
updateActiveNavState('cefr');
} else if (hash.startsWith('#grammar')) {
@@ -117,8 +129,19 @@ export function applyInitialViewPreflight() {
if (readerView) readerView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (lessonView) lessonView.classList.add('hidden');
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
if (grammarView) grammarView.classList.remove('hidden');
updateActiveNavState('grammar');
} else if (hash.startsWith('#lesson')) {
if (dictionaryView) dictionaryView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (grammarView) grammarView.classList.add('hidden');
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
if (lessonView) lessonView.classList.remove('hidden');
updateActiveNavState('lesson');
}
}
@@ -271,6 +294,36 @@ export function openGrammarView(unitId = null, updateHash = true) {
}
}
export function openLessonView(presetId = null, updateHash = true) {
const targetHash = presetId ? `#lesson/${presetId}` : '#lesson';
if (updateHash && !window.location.hash.startsWith('#lesson')) {
window.location.hash = targetHash;
return;
}
const dictionaryView = document.getElementById('dictionary-view');
const srsView = document.getElementById('srs-view');
const readerView = document.getElementById('reader-view');
const cefrView = document.getElementById('cefr-view');
const grammarView = document.getElementById('grammar-view');
const lessonView = document.getElementById('lesson-view');
const lessonRunnerView = document.getElementById('lesson-runner-view');
if (dictionaryView) dictionaryView.classList.add('hidden');
if (srsView) srsView.classList.add('hidden');
if (readerView) readerView.classList.add('hidden');
if (cefrView) cefrView.classList.add('hidden');
if (grammarView) grammarView.classList.add('hidden');
if (lessonRunnerView) lessonRunnerView.classList.add('hidden');
if (lessonView) lessonView.classList.remove('hidden');
updateActiveNavState('lesson');
try { localStorage.setItem('notes_active_view', 'lesson-view'); } catch (e) {}
openLessonConfigurator(presetId);
}
// -----------------------------------------------------------------------
// Route Handler
// -----------------------------------------------------------------------
@@ -337,6 +390,10 @@ export async function handleRouting() {
} else {
openBookshelfView(false);
}
} else if (cleanHash.startsWith('lesson')) {
const parts = cleanHash.split('/');
const presetId = parts[1] || null;
openLessonView(presetId, false);
} else {
openDictionaryView(false);
}
@@ -523,6 +580,13 @@ export function initNavigationDrawer() {
window.location.hash = '#grammar';
});
}
const drawerNavLesson = document.getElementById('drawer-nav-lesson');
if (drawerNavLesson) {
drawerNavLesson.addEventListener('click', () => {
closeDrawer();
window.location.hash = '#lesson';
});
}
if (drawerNavStats) {
drawerNavStats.addEventListener('click', () => {
closeDrawer();
@@ -721,6 +785,7 @@ document.addEventListener('DOMContentLoaded', () => {
initReader();
initCefr();
initGrammar();
initLessonModule();
// 5. Global Navigation & Header Actions
const navBrandHome = document.getElementById('nav-brand-home');
+52
View File
@@ -422,5 +422,57 @@ export const api = {
if (resp.ok) return await resp.json();
throw new Error(`Failed to cancel task: ${resp.status}`);
}
},
// Comprehensive Lessons Configurator & Runner (Beta)
lessons: {
async getPresets() {
const resp = await fetch('/api/lessons/presets');
if (resp.ok) return await resp.json();
throw new Error(`Failed to get presets: ${resp.status}`);
},
async savePreset(payload) {
const resp = await fetch('/api/lessons/presets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (resp.ok) return await resp.json();
throw new Error(`Failed to save preset: ${resp.status}`);
},
async deletePreset(presetId) {
const resp = await fetch(`/api/lessons/presets/${presetId}`, {
method: 'DELETE'
});
if (resp.ok) return await resp.json();
throw new Error(`Failed to delete preset: ${resp.status}`);
},
async getGrammarRecommendations() {
const resp = await fetch('/api/lessons/grammar-recommendations');
if (resp.ok) return await resp.json();
throw new Error(`Failed to get grammar recommendations: ${resp.status}`);
},
async getArticles(topic = null) {
let url = '/api/lessons/articles';
if (topic) url += `?topic=${encodeURIComponent(topic)}`;
const resp = await fetch(url);
if (resp.ok) return await resp.json();
throw new Error(`Failed to get articles: ${resp.status}`);
},
async submitDebrief(payload) {
const resp = await fetch('/api/lessons/debrief', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (resp.ok) return await resp.json();
throw new Error(`Failed to submit debrief: ${resp.status}`);
},
async getHistory(limit = 15) {
const resp = await fetch(`/api/lessons/history?limit=${limit}`);
if (resp.ok) return await resp.json();
throw new Error(`Failed to get lesson history: ${resp.status}`);
}
}
};
File diff suppressed because it is too large Load Diff