Files
learning-platform/src/lib/learningService.js

157 lines
4.9 KiB
JavaScript

import { anthropicApi } from './api';
import * as db from './db';
const CONTENT_GENERATION_SYSTEM = `You are an expert learning content writer for Respellion, an internal IT company.
You write training material for employees based on knowledge topics.
Always write in clear, professional English.
ALWAYS return valid JSON only — no markdown code blocks, no extra text.`;
const CONTENT_SCHEMA = `{
"article": {
"title": "Article title",
"intro": "Short intro of 1-2 sentences",
"sections": [
{ "heading": "Section title", "body": "Section text of at least 3 sentences." }
],
"keyTakeaways": ["Takeaway 1", "Takeaway 2", "Takeaway 3"]
},
"slides": [
{ "title": "Slide title", "bullets": ["Point 1", "Point 2", "Point 3"], "speakerNote": "Speaker note for this slide." }
],
"podcastScript": "A natural spoken script of approx. 300 words summarizing the topic as a podcast episode.",
"infographic": {
"headline": "A short, punchy headline summarizing the topic (max 8 words)",
"tagline": "A subtitle of max 15 words",
"stats": [
{ "value": "Number or %", "label": "Short description", "icon": "📊" }
],
"steps": [
{ "number": 1, "title": "Step title", "description": "One-sentence description.", "icon": "🔑" }
],
"quote": "An inspiring or insightful quote about the topic.",
"colorTheme": "teal"
}
}`;
export async function getAssignedTopic(userId, weekNumber) {
const topics = await db.getTopics();
if (!topics || topics.length === 0) return null;
const str = `${userId}:${weekNumber}`;
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0;
}
const index = Math.abs(hash) % topics.length;
return topics[index];
}
export async function getCachedContent(topicId) {
return db.getContent(topicId);
}
export async function getAllGeneratedContent() {
const topics = await db.getTopics();
const results = await Promise.all(
topics.map(async topic => {
const content = await db.getContent(topic.id);
return { topic, content, hasContent: !!content };
})
);
return results.filter(item => item.hasContent);
}
export async function generateLearningContent(topic, force = false) {
if (!force) {
const cached = await db.getContent(topic.id);
if (cached) {
console.log(`[Learn] Cache hit for topic: ${topic.id}`);
return cached;
}
}
const prompt = `Generate a complete learning module for the following topic:
Label: ${topic.label}
Type: ${topic.type}
Description: ${topic.description}
Return ONLY a JSON object with the following structure:
${CONTENT_SCHEMA}
Provide at least 3 article sections, 4 slides, 3 stats, and 3-5 steps in the infographic.`;
const responseText = await anthropicApi.generateContent(CONTENT_GENERATION_SYSTEM, prompt);
let content;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
content = JSON.parse(jsonMatch ? jsonMatch[0] : responseText);
} catch (e) {
throw new Error('AI could not generate valid learning content. Please try again.');
}
await db.setContent(topic.id, content);
return content;
}
export async function refineLearningContent(topic, refinementInstruction) {
const existing = await db.getContent(topic.id);
const prompt = `You have previously generated the following learning module for the topic "${topic.label}":
${JSON.stringify(existing, null, 2)}
The admin has requested the following refinement:
"${refinementInstruction}"
Apply the refinement and return the complete updated JSON object using the same structure. Return ONLY valid JSON.`;
const responseText = await anthropicApi.generateContent(CONTENT_GENERATION_SYSTEM, prompt);
let content;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
content = JSON.parse(jsonMatch ? jsonMatch[0] : responseText);
} catch (e) {
throw new Error('AI could not process the refinement. Please try a different instruction.');
}
await db.setContent(topic.id, content);
return content;
}
export async function deleteCachedContent(topicId) {
return db.deleteContent(topicId);
}
export async function generateCustomTopic(label) {
const prompt = `A user wants to learn about "${label}".
Create a short description (2-3 sentences) and categorize it.
Return ONLY a JSON object with this structure:
{
"label": "Polished topic title",
"type": "concept", // one of: concept, role, process
"description": "Short description"
}`;
const responseText = await anthropicApi.generateContent(
"You are a knowledge graph AI categorizing topics.",
prompt
);
let newTopic;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
newTopic = JSON.parse(jsonMatch ? jsonMatch[0] : responseText);
newTopic.id = 'custom_' + Date.now().toString(36);
} catch (e) {
throw new Error('Could not process custom topic. Please try again.');
}
await db.upsertTopic(newTopic);
return newTopic;
}