feat: theme-level weekly sessions and theme-grouped knowledge library
- New theme_sessions + theme_session_completions PocketBase collections - Generate a detailed per-week theme summary covering every topic in the week's curriculum slot via EMIT_THEME_SESSION_TOOL; cache per (curriculum_version, week_number) - Replace Leren.jsx "This Week's Topic" card with "This Week's Theme Session" that opens the new ThemeSessionView; per-topic micro-learnings remain reachable as optional practice from inside the session - Group the Knowledge Library by topic.theme, pin the current week's theme on top, and badge topics already covered by the active session - Mark week complete when the user finishes reading the theme session Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
61
src/lib/themeSessionService.js
Normal file
61
src/lib/themeSessionService.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as db from './db';
|
||||
import { callLLM, cachedSystem } from './llm';
|
||||
import { EMIT_THEME_SESSION_TOOL } from './llmTools';
|
||||
|
||||
const THEME_SESSION_SYSTEM = `You are an expert learning content writer for Respellion, an internal IT company.
|
||||
You produce theme-level weekly learning sessions for the perpetual 26-week curriculum.
|
||||
A weekly session is a single, coherent piece of learning that covers every topic the curriculum has slotted for that week's theme — not a list of separate articles.
|
||||
Write in clear, professional English. Each topic section must be detailed (at least four sentences) and include a concrete workplace example or application.
|
||||
Emit content only through the emit_theme_session tool.`;
|
||||
|
||||
function buildUserPrompt(weekContent) {
|
||||
const topicLines = weekContent.topics
|
||||
.map((t, idx) => `${idx + 1}. id=${t.id} | label="${t.label}" | type=${t.type} | description=${t.description}`)
|
||||
.join('\n');
|
||||
|
||||
return `Theme: ${weekContent.theme}
|
||||
Week ${weekContent.weekNumber} of the 26-week curriculum.
|
||||
Estimated duration: ${weekContent.estimatedDuration || 30} minutes.
|
||||
Rationale for this week's position: ${weekContent.rationale || '—'}
|
||||
|
||||
Topics in this theme this week (you MUST cover every one of them, one section each, reusing the exact topic_id):
|
||||
${topicLines}
|
||||
|
||||
Produce a detailed theme session that synthesises these topics into a single coherent learning experience. Cover every topic in its own section, finish with a "how these connect" paragraph, and at least 3 takeaways for the theme.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached theme session for the active curriculum version + week,
|
||||
* or generate one with the LLM and cache it.
|
||||
*
|
||||
* @param {string} curriculumVersionId — id of the active curriculum_versions row
|
||||
* @param {object} weekContent — output of getCurrentWeekContent: { weekNumber, theme, topics[], estimatedDuration, rationale }
|
||||
* @param {object} [opts]
|
||||
* @param {boolean} [opts.force=false] — regenerate even if cached
|
||||
*/
|
||||
export async function getOrGenerateThemeSession(curriculumVersionId, weekContent, { force = false } = {}) {
|
||||
if (!curriculumVersionId) throw new Error('Theme session requires an active curriculum version.');
|
||||
if (!weekContent || !Array.isArray(weekContent.topics) || weekContent.topics.length === 0) {
|
||||
throw new Error('Theme session requires at least one topic for the week.');
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
const cached = await db.getThemeSession(curriculumVersionId, weekContent.weekNumber);
|
||||
if (cached) return cached;
|
||||
}
|
||||
|
||||
const result = await callLLM({
|
||||
task: 'theme_session.generate',
|
||||
tier: 'standard',
|
||||
system: cachedSystem(THEME_SESSION_SYSTEM),
|
||||
user: buildUserPrompt(weekContent),
|
||||
tools: [EMIT_THEME_SESSION_TOOL],
|
||||
toolChoice: { type: 'tool', name: EMIT_THEME_SESSION_TOOL.name },
|
||||
maxTokens: 8192,
|
||||
});
|
||||
|
||||
const emitted = result.toolUses[0]?.input;
|
||||
if (!emitted) throw new Error('AI did not return a theme session. Please try again.');
|
||||
|
||||
return db.setThemeSession(curriculumVersionId, weekContent.weekNumber, weekContent.theme, emitted);
|
||||
}
|
||||
Reference in New Issue
Block a user