Add specifications for gamification, generation, and R42 chat services
- Introduced gamification service spec detailing responsibilities, API surface, XP calculation, levels, streaks, badges, milestone cards, and heatmap data. - Added generation service spec outlining the process for generating micro learning content, including API endpoints, AI call configuration, prompt strategies, and error handling. - Created R42 chat service spec covering chatbot interactions, retrieval pipeline, prompt construction, response generation, and stateless design principles.
This commit is contained in:
@@ -46,7 +46,7 @@ const field = {
|
||||
}),
|
||||
|
||||
json: (name: string): FieldDef => ({
|
||||
name, type: 'json', required: false, options: {},
|
||||
name, type: 'json', required: false, options: { maxSize: 2097152 },
|
||||
}),
|
||||
|
||||
date: (name: string): FieldDef => ({
|
||||
@@ -146,18 +146,17 @@ async function run(): Promise<void> {
|
||||
const usersCol = await pb.collections.getFirstListItem<CollectionModel>('name="users"');
|
||||
ids.set('users', usersCol.id);
|
||||
|
||||
const hasRole = usersCol.schema.some(s => s.name === 'role');
|
||||
if (!hasRole) {
|
||||
const existingFieldNames = new Set(usersCol.schema.map((s: { name: string }) => s.name));
|
||||
const fieldsToAdd: FieldDef[] = [];
|
||||
if (!existingFieldNames.has('role')) fieldsToAdd.push(field.select('role', ['admin', 'employee'], true));
|
||||
if (!existingFieldNames.has('display_name')) fieldsToAdd.push(field.text('display_name'));
|
||||
|
||||
if (fieldsToAdd.length > 0) {
|
||||
const updateBody: Record<string, unknown> = {
|
||||
schema: [
|
||||
...usersCol.schema,
|
||||
field.select('role', ['admin', 'employee'], true),
|
||||
field.text('display_name'),
|
||||
field.file('avatar', ['image/jpeg', 'image/png', 'image/webp']),
|
||||
],
|
||||
schema: [...usersCol.schema, ...fieldsToAdd],
|
||||
};
|
||||
await pb.collections.update(usersCol.id, updateBody);
|
||||
console.log(' extended with role, display_name, avatar');
|
||||
console.log(` extended with: ${fieldsToAdd.map((f: FieldDef) => f.name).join(', ')}`);
|
||||
} else {
|
||||
console.log(' skip users (already extended)');
|
||||
}
|
||||
|
||||
@@ -26,11 +26,16 @@ export async function embedAndStore(
|
||||
writtenTopics: WrittenTopic[],
|
||||
onProgress: (embedded: number) => void,
|
||||
): Promise<void> {
|
||||
// Build chunk → topic mapping
|
||||
// Build chunk → topic mapping.
|
||||
// The AI labels chunks as [CHUNK-<uuid>] so sourceChunkIds may carry that prefix;
|
||||
// strip it so lookups match the bare UUID used as the Qdrant point ID.
|
||||
const normalise = (id: string): string => id.replace(/^CHUNK-/i, '');
|
||||
|
||||
const chunkTopicMap = new Map<string, string>();
|
||||
const chunkThemeMap = new Map<string, string>();
|
||||
for (const topic of writtenTopics) {
|
||||
for (const chunkId of topic.sourceChunkIds) {
|
||||
for (const rawId of topic.sourceChunkIds) {
|
||||
const chunkId = normalise(rawId);
|
||||
chunkTopicMap.set(chunkId, topic.id);
|
||||
chunkThemeMap.set(chunkId, topic.themeId);
|
||||
}
|
||||
@@ -94,7 +99,9 @@ export async function embedAndStore(
|
||||
// Update topics.qdrant_chunk_ids in PocketBase
|
||||
// -------------------------------------------------------------------------
|
||||
for (const topic of writtenTopics) {
|
||||
const qdrantIds = topic.sourceChunkIds.filter(id => chunkTopicMap.get(id) === topic.id);
|
||||
const qdrantIds = topic.sourceChunkIds
|
||||
.map(id => normalise(id))
|
||||
.filter(id => chunkTopicMap.get(id) === topic.id);
|
||||
if (qdrantIds.length > 0) {
|
||||
await updateTopicQdrantIds(topic.id, qdrantIds);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ function buildUserPrompt(chunks: Chunk[], filename: string, format: DocumentForm
|
||||
async function callClaude(chunks: Chunk[], filename: string, format: DocumentFormat, strict: boolean): Promise<DraftKB> {
|
||||
const response = await anthropic.messages.create({
|
||||
model: MODELS.SONNET,
|
||||
max_tokens: 8000,
|
||||
max_tokens: 16000,
|
||||
temperature: 0,
|
||||
system: SYSTEM_PROMPT,
|
||||
messages: [{ role: 'user', content: buildUserPrompt(chunks, filename, format, strict) }],
|
||||
@@ -76,12 +76,14 @@ async function callClaude(chunks: Chunk[], filename: string, format: DocumentFor
|
||||
try {
|
||||
parsed = JSON.parse(textBlock.text);
|
||||
} catch {
|
||||
console.error(`[structure] JSON parse failed (strict=${strict}), response length=${textBlock.text.length}, stop_reason=${response.stop_reason}`);
|
||||
if (strict) throw new Error('structure_extraction_failed');
|
||||
return callClaude(chunks, filename, format, true);
|
||||
}
|
||||
|
||||
const result = DraftKBSchema.safeParse(parsed);
|
||||
if (!result.success) {
|
||||
console.error(`[structure] Zod validation failed (strict=${strict}):`, JSON.stringify(result.error.issues.slice(0,3)));
|
||||
if (strict) throw new Error('structure_extraction_failed');
|
||||
return callClaude(chunks, filename, format, true);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export interface WrittenTopic {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface SourceChunkPayload {
|
||||
[key: string]: unknown;
|
||||
source_document_id: string;
|
||||
chunk_index: number;
|
||||
text: string;
|
||||
@@ -91,6 +92,7 @@ export interface SourceChunkPayload {
|
||||
}
|
||||
|
||||
export interface TopicSummaryPayload {
|
||||
[key: string]: unknown;
|
||||
topic_id: string;
|
||||
theme_id: string;
|
||||
title: string;
|
||||
|
||||
Reference in New Issue
Block a user