63 lines
3.0 KiB
JavaScript
63 lines
3.0 KiB
JavaScript
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,
|
|
timeoutMs: 180_000,
|
|
});
|
|
|
|
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);
|
|
}
|