feat: implement knowledge testing system with leaderboard, quiz generation, and PocketBase integration
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { anthropicApi } from './api';
|
||||
import { storage } from './storage';
|
||||
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.
|
||||
@@ -33,12 +33,8 @@ const CONTENT_SCHEMA = `{
|
||||
}
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Get the assigned topic for a user for a given week using round-robin.
|
||||
* hash(userId + weekNumber) % topicCount
|
||||
*/
|
||||
export function getAssignedTopic(userId, weekNumber) {
|
||||
const topics = storage.get('kb:topics', []);
|
||||
export async function getAssignedTopic(userId, weekNumber) {
|
||||
const topics = await db.getTopics();
|
||||
if (!topics || topics.length === 0) return null;
|
||||
|
||||
const str = `${userId}:${weekNumber}`;
|
||||
@@ -51,43 +47,24 @@ export function getAssignedTopic(userId, weekNumber) {
|
||||
return topics[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache key for a topic's content.
|
||||
*/
|
||||
export function getContentCacheKey(topicId) {
|
||||
return `kb:content:${topicId}`;
|
||||
export async function getCachedContent(topicId) {
|
||||
return db.getContent(topicId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cached content for a topic, or null if none exists.
|
||||
*/
|
||||
export function getCachedContent(topicId) {
|
||||
return storage.get(getContentCacheKey(topicId), null);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all topics that have generated content.
|
||||
*/
|
||||
export function getAllGeneratedContent() {
|
||||
const topics = storage.get('kb:topics', []);
|
||||
return topics
|
||||
.map(topic => ({
|
||||
topic,
|
||||
content: getCachedContent(topic.id),
|
||||
hasContent: !!getCachedContent(topic.id),
|
||||
}))
|
||||
.filter(item => item.hasContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a complete learning module for a topic.
|
||||
* Uses cached version if available (unless force = true).
|
||||
*/
|
||||
export async function generateLearningContent(topic, force = false) {
|
||||
const cacheKey = getContentCacheKey(topic.id);
|
||||
|
||||
if (!force) {
|
||||
const cached = storage.get(cacheKey);
|
||||
const cached = await db.getContent(topic.id);
|
||||
if (cached) {
|
||||
console.log(`[Learn] Cache hit for topic: ${topic.id}`);
|
||||
return cached;
|
||||
@@ -115,17 +92,12 @@ Provide at least 3 article sections, 4 slides, 3 stats, and 3-5 steps in the inf
|
||||
throw new Error('AI could not generate valid learning content. Please try again.');
|
||||
}
|
||||
|
||||
storage.set(cacheKey, content);
|
||||
await db.setContent(topic.id, content);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refine existing content for a topic using a natural language instruction.
|
||||
* Sends current content + refinement prompt to AI, saves new version.
|
||||
*/
|
||||
export async function refineLearningContent(topic, refinementInstruction) {
|
||||
const cacheKey = getContentCacheKey(topic.id);
|
||||
const existing = storage.get(cacheKey);
|
||||
const existing = await db.getContent(topic.id);
|
||||
|
||||
const prompt = `You have previously generated the following learning module for the topic "${topic.label}":
|
||||
|
||||
@@ -146,22 +118,16 @@ Apply the refinement and return the complete updated JSON object using the same
|
||||
throw new Error('AI could not process the refinement. Please try a different instruction.');
|
||||
}
|
||||
|
||||
storage.set(cacheKey, content);
|
||||
await db.setContent(topic.id, content);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cached content for a topic, forcing a fresh generation next time.
|
||||
*/
|
||||
export function deleteCachedContent(topicId) {
|
||||
storage.remove(getContentCacheKey(topicId));
|
||||
export async function deleteCachedContent(topicId) {
|
||||
return db.deleteContent(topicId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new custom topic metadata on the fly from user input.
|
||||
*/
|
||||
export async function generateCustomTopic(label) {
|
||||
const prompt = `A user wants to learn about "${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:
|
||||
@@ -172,7 +138,7 @@ Return ONLY a JSON object with this structure:
|
||||
}`;
|
||||
|
||||
const responseText = await anthropicApi.generateContent(
|
||||
"You are a knowledge graph AI categorizing topics.",
|
||||
"You are a knowledge graph AI categorizing topics.",
|
||||
prompt
|
||||
);
|
||||
|
||||
@@ -185,9 +151,6 @@ Return ONLY a JSON object with this structure:
|
||||
throw new Error('Could not process custom topic. Please try again.');
|
||||
}
|
||||
|
||||
// Add to global knowledge base so others can see it too
|
||||
const topics = storage.get('kb:topics', []);
|
||||
storage.set('kb:topics', [...topics, newTopic]);
|
||||
|
||||
await db.upsertTopic(newTopic);
|
||||
return newTopic;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user