@goscribe/server 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ANALYSIS_PROGRESS_SPEC.md +463 -0
- package/PROGRESS_QUICK_REFERENCE.md +239 -0
- package/dist/lib/ai-session.d.ts +20 -9
- package/dist/lib/ai-session.js +316 -80
- package/dist/lib/auth.d.ts +35 -2
- package/dist/lib/auth.js +88 -15
- package/dist/lib/env.d.ts +32 -0
- package/dist/lib/env.js +46 -0
- package/dist/lib/errors.d.ts +33 -0
- package/dist/lib/errors.js +78 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +9 -11
- package/dist/lib/logger.d.ts +62 -0
- package/dist/lib/logger.js +342 -0
- package/dist/lib/podcast-prompts.d.ts +43 -0
- package/dist/lib/podcast-prompts.js +135 -0
- package/dist/lib/pusher.d.ts +1 -0
- package/dist/lib/pusher.js +14 -2
- package/dist/lib/storage.d.ts +3 -3
- package/dist/lib/storage.js +51 -47
- package/dist/lib/validation.d.ts +51 -0
- package/dist/lib/validation.js +64 -0
- package/dist/routers/_app.d.ts +697 -111
- package/dist/routers/_app.js +5 -0
- package/dist/routers/auth.d.ts +11 -1
- package/dist/routers/chat.d.ts +11 -1
- package/dist/routers/flashcards.d.ts +205 -6
- package/dist/routers/flashcards.js +144 -66
- package/dist/routers/members.d.ts +165 -0
- package/dist/routers/members.js +531 -0
- package/dist/routers/podcast.d.ts +78 -63
- package/dist/routers/podcast.js +330 -393
- package/dist/routers/studyguide.d.ts +11 -1
- package/dist/routers/worksheets.d.ts +124 -13
- package/dist/routers/worksheets.js +123 -50
- package/dist/routers/workspace.d.ts +213 -26
- package/dist/routers/workspace.js +303 -181
- package/dist/server.js +12 -4
- package/dist/services/flashcard-progress.service.d.ts +183 -0
- package/dist/services/flashcard-progress.service.js +383 -0
- package/dist/services/flashcard.service.d.ts +183 -0
- package/dist/services/flashcard.service.js +224 -0
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +107 -0
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +326 -0
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +295 -0
- package/dist/trpc.d.ts +13 -2
- package/dist/trpc.js +55 -6
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.js +1 -0
- package/package.json +3 -2
- package/prisma/schema.prisma +142 -4
- package/src/lib/ai-session.ts +356 -85
- package/src/lib/auth.ts +113 -19
- package/src/lib/env.ts +59 -0
- package/src/lib/errors.ts +92 -0
- package/src/lib/inference.ts +11 -11
- package/src/lib/logger.ts +405 -0
- package/src/lib/pusher.ts +15 -3
- package/src/lib/storage.ts +56 -51
- package/src/lib/validation.ts +75 -0
- package/src/routers/_app.ts +5 -0
- package/src/routers/chat.ts +2 -23
- package/src/routers/flashcards.ts +108 -24
- package/src/routers/members.ts +586 -0
- package/src/routers/podcast.ts +385 -420
- package/src/routers/worksheets.ts +117 -35
- package/src/routers/workspace.ts +328 -195
- package/src/server.ts +13 -4
- package/src/services/flashcard-progress.service.ts +541 -0
- package/src/trpc.ts +59 -6
- package/src/types/index.ts +165 -0
- package/AUTH_FRONTEND_SPEC.md +0 -21
- package/CHAT_FRONTEND_SPEC.md +0 -474
- package/DATABASE_SETUP.md +0 -165
- package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
- package/PODCAST_FRONTEND_SPEC.md +0 -595
- package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
- package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
- package/WORKSPACE_FRONTEND_SPEC.md +0 -47
- package/test-ai-integration.js +0 -134
|
@@ -3,6 +3,7 @@ import { TRPCError } from '@trpc/server';
|
|
|
3
3
|
import { router, authedProcedure } from '../trpc.js';
|
|
4
4
|
import { aiSessionService } from '../lib/ai-session.js';
|
|
5
5
|
import PusherService from '../lib/pusher.js';
|
|
6
|
+
import { logger } from '../lib/logger.js';
|
|
6
7
|
|
|
7
8
|
// Avoid importing Prisma enums directly; mirror values as string literals
|
|
8
9
|
const ArtifactType = {
|
|
@@ -57,19 +58,21 @@ export const worksheets = router({
|
|
|
57
58
|
const p = progressByQuestionId.get(q.id);
|
|
58
59
|
if (!p) return q;
|
|
59
60
|
const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta as any)) : {} as any;
|
|
61
|
+
const progressMeta = p.meta ? (typeof p.meta === 'object' ? p.meta : JSON.parse(p.meta as any)) : {} as any;
|
|
60
62
|
return {
|
|
61
63
|
...q,
|
|
62
64
|
meta: {
|
|
63
65
|
...existingMeta,
|
|
64
|
-
completed: p.
|
|
66
|
+
completed: p.modified,
|
|
65
67
|
userAnswer: p.userAnswer,
|
|
66
68
|
completedAt: p.completedAt,
|
|
69
|
+
userMarkScheme: progressMeta.userMarkScheme,
|
|
67
70
|
},
|
|
68
71
|
} as typeof q;
|
|
69
72
|
}),
|
|
70
73
|
}));
|
|
71
74
|
|
|
72
|
-
return merged
|
|
75
|
+
return merged;
|
|
73
76
|
}),
|
|
74
77
|
|
|
75
78
|
// Create a worksheet set
|
|
@@ -148,19 +151,21 @@ export const worksheets = router({
|
|
|
148
151
|
const p = progressByQuestionId.get(q.id);
|
|
149
152
|
if (!p) return q;
|
|
150
153
|
const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta as any)) : {} as any;
|
|
154
|
+
const progressMeta = p.meta ? (typeof p.meta === 'object' ? p.meta : JSON.parse(p.meta as any)) : {} as any;
|
|
151
155
|
return {
|
|
152
156
|
...q,
|
|
153
157
|
meta: {
|
|
154
158
|
...existingMeta,
|
|
155
|
-
completed: p.
|
|
159
|
+
completed: p.modified,
|
|
156
160
|
userAnswer: p.userAnswer,
|
|
157
161
|
completedAt: p.completedAt,
|
|
162
|
+
userMarkScheme: progressMeta.userMarkScheme,
|
|
158
163
|
},
|
|
159
164
|
} as typeof q;
|
|
160
165
|
}),
|
|
161
166
|
};
|
|
162
167
|
|
|
163
|
-
return merged
|
|
168
|
+
return merged;
|
|
164
169
|
}),
|
|
165
170
|
|
|
166
171
|
// Add a question to a worksheet
|
|
@@ -239,6 +244,7 @@ export const worksheets = router({
|
|
|
239
244
|
problemId: z.string(),
|
|
240
245
|
completed: z.boolean(),
|
|
241
246
|
answer: z.string().optional(),
|
|
247
|
+
correct: z.boolean().optional(),
|
|
242
248
|
}))
|
|
243
249
|
.mutation(async ({ ctx, input }) => {
|
|
244
250
|
// Verify question ownership through worksheet
|
|
@@ -264,13 +270,14 @@ export const worksheets = router({
|
|
|
264
270
|
create: {
|
|
265
271
|
worksheetQuestionId: input.problemId,
|
|
266
272
|
userId: ctx.session.user.id,
|
|
267
|
-
|
|
273
|
+
modified: input.completed,
|
|
268
274
|
userAnswer: input.answer,
|
|
275
|
+
correct: input.correct,
|
|
269
276
|
completedAt: input.completed ? new Date() : null,
|
|
270
277
|
attempts: 1,
|
|
271
278
|
},
|
|
272
279
|
update: {
|
|
273
|
-
|
|
280
|
+
modified: input.completed,
|
|
274
281
|
userAnswer: input.answer,
|
|
275
282
|
completedAt: input.completed ? new Date() : null,
|
|
276
283
|
attempts: { increment: 1 },
|
|
@@ -400,15 +407,6 @@ export const worksheets = router({
|
|
|
400
407
|
const workspace = await ctx.db.workspace.findFirst({ where: { id: input.workspaceId, ownerId: ctx.session.user.id } });
|
|
401
408
|
if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
402
409
|
|
|
403
|
-
await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_load_start', { source: 'prompt' });
|
|
404
|
-
|
|
405
|
-
const session = await aiSessionService.initSession(input.workspaceId, ctx.session.user.id);
|
|
406
|
-
await aiSessionService.setInstruction(session.id, `Create a worksheet with ${input.numQuestions} questions at ${input.difficulty} difficulty from this prompt. Return JSON.\n\nPrompt:\n${input.prompt}`);
|
|
407
|
-
await aiSessionService.startLLMSession(session.id);
|
|
408
|
-
|
|
409
|
-
const content = await aiSessionService.generateWorksheetQuestions(session.id, input.numQuestions, input.difficulty);
|
|
410
|
-
await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength: content.length });
|
|
411
|
-
|
|
412
410
|
const artifact = await ctx.db.artifact.create({
|
|
413
411
|
data: {
|
|
414
412
|
workspaceId: input.workspaceId,
|
|
@@ -417,9 +415,14 @@ export const worksheets = router({
|
|
|
417
415
|
createdById: ctx.session.user.id,
|
|
418
416
|
difficulty: (input.difficulty.toUpperCase()) as any,
|
|
419
417
|
estimatedTime: input.estimatedTime,
|
|
418
|
+
generating: true,
|
|
419
|
+
generatingMetadata: { quantity: input.numQuestions, difficulty: input.difficulty.toLowerCase() },
|
|
420
420
|
},
|
|
421
421
|
});
|
|
422
|
-
|
|
422
|
+
await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength: input.numQuestions });
|
|
423
|
+
try {
|
|
424
|
+
|
|
425
|
+
const content = await aiSessionService.generateWorksheetQuestions(input.workspaceId, ctx.session.user.id, input.numQuestions, input.difficulty as any);
|
|
423
426
|
try {
|
|
424
427
|
const worksheetData = JSON.parse(content);
|
|
425
428
|
let actualWorksheetData = worksheetData;
|
|
@@ -433,6 +436,7 @@ export const worksheets = router({
|
|
|
433
436
|
const answer = problem.answer || problem.solution || `Answer ${i + 1}`;
|
|
434
437
|
const type = problem.type || 'TEXT';
|
|
435
438
|
const options = problem.options || [];
|
|
439
|
+
|
|
436
440
|
await ctx.db.worksheetQuestion.create({
|
|
437
441
|
data: {
|
|
438
442
|
artifactId: artifact.id,
|
|
@@ -443,35 +447,113 @@ export const worksheets = router({
|
|
|
443
447
|
meta: {
|
|
444
448
|
type,
|
|
445
449
|
options: options.length > 0 ? options : undefined,
|
|
450
|
+
mark_scheme: problem.mark_scheme || undefined,
|
|
446
451
|
},
|
|
447
452
|
},
|
|
448
453
|
});
|
|
449
454
|
}
|
|
450
455
|
} catch {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
await ctx.db.worksheetQuestion.create({
|
|
457
|
-
data: {
|
|
458
|
-
artifactId: artifact.id,
|
|
459
|
-
prompt: q.trim(),
|
|
460
|
-
answer: a.trim(),
|
|
461
|
-
difficulty: (input.difficulty.toUpperCase()) as any,
|
|
462
|
-
order: i,
|
|
463
|
-
meta: { type: 'TEXT' },
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
456
|
+
logger.error('Failed to parse worksheet JSON,');
|
|
457
|
+
await ctx.db.artifact.delete({
|
|
458
|
+
where: { id: artifact.id },
|
|
459
|
+
});
|
|
460
|
+
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to parse worksheet JSON' });
|
|
468
461
|
}
|
|
469
462
|
|
|
463
|
+
await ctx.db.artifact.update({
|
|
464
|
+
where: { id: artifact.id },
|
|
465
|
+
data: { generating: false },
|
|
466
|
+
});
|
|
467
|
+
|
|
470
468
|
await PusherService.emitWorksheetComplete(input.workspaceId, artifact);
|
|
471
|
-
|
|
469
|
+
} catch (error) {
|
|
470
|
+
await ctx.db.artifact.delete({
|
|
471
|
+
where: { id: artifact.id },
|
|
472
|
+
});
|
|
473
|
+
await PusherService.emitError(input.workspaceId, `Failed to generate worksheet: ${error instanceof Error ? error.message : 'Unknown error'}`, 'worksheet_generation');
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
472
476
|
|
|
473
477
|
return { artifact };
|
|
474
478
|
}),
|
|
475
|
-
|
|
479
|
+
checkAnswer: authedProcedure
|
|
480
|
+
.input(z.object({
|
|
481
|
+
worksheetId: z.string(),
|
|
482
|
+
questionId: z.string(),
|
|
483
|
+
answer: z.string().min(1),
|
|
484
|
+
}))
|
|
485
|
+
.mutation(async ({ ctx, input }) => {
|
|
486
|
+
const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } }, include: { workspace: true } });
|
|
487
|
+
if (!worksheet) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
488
|
+
const question = await ctx.db.worksheetQuestion.findFirst({ where: { id: input.questionId, artifactId: input.worksheetId } });
|
|
489
|
+
if (!question) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
490
|
+
|
|
491
|
+
// Parse question meta to get mark_scheme
|
|
492
|
+
const questionMeta = question.meta ? (typeof question.meta === 'object' ? question.meta : JSON.parse(question.meta as any)) : {} as any;
|
|
493
|
+
const markScheme = questionMeta.mark_scheme;
|
|
494
|
+
|
|
495
|
+
let isCorrect = false;
|
|
496
|
+
let userMarkScheme = null;
|
|
497
|
+
|
|
498
|
+
// If mark scheme exists, use AI marking
|
|
499
|
+
if (markScheme && markScheme.points && markScheme.points.length > 0) {
|
|
500
|
+
try {
|
|
501
|
+
userMarkScheme = await aiSessionService.checkWorksheetQuestions(
|
|
502
|
+
worksheet.workspace.id,
|
|
503
|
+
ctx.session.user.id,
|
|
504
|
+
question.prompt,
|
|
505
|
+
input.answer,
|
|
506
|
+
markScheme
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// Determine if correct by comparing achieved points vs total points
|
|
510
|
+
const achievedTotal = userMarkScheme.points.reduce((sum: number, p: any) => sum + (p.achievedPoints || 0), 0);
|
|
511
|
+
isCorrect = achievedTotal === markScheme.totalPoints;
|
|
512
|
+
|
|
513
|
+
} catch (error) {
|
|
514
|
+
logger.error('Failed to check answer with AI', error instanceof Error ? error.message : 'Unknown error');
|
|
515
|
+
// Fallback to simple string comparison
|
|
516
|
+
isCorrect = question.answer === input.answer;
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
// Simple string comparison if no mark scheme
|
|
520
|
+
isCorrect = question.answer === input.answer;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
// @todo: figure out this wierd fix
|
|
526
|
+
const progress = await ctx.db.worksheetQuestionProgress.upsert({
|
|
527
|
+
where: {
|
|
528
|
+
worksheetQuestionId_userId: {
|
|
529
|
+
worksheetQuestionId: input.questionId,
|
|
530
|
+
userId: ctx.session.user.id,
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
create: {
|
|
534
|
+
worksheetQuestionId: input.questionId,
|
|
535
|
+
userId: ctx.session.user.id,
|
|
536
|
+
modified: true,
|
|
537
|
+
userAnswer: input.answer,
|
|
538
|
+
correct: isCorrect,
|
|
539
|
+
completedAt: new Date(),
|
|
540
|
+
attempts: 1,
|
|
541
|
+
meta: userMarkScheme ? { userMarkScheme: JSON.parse(JSON.stringify(userMarkScheme)) } : { userMarkScheme: null },
|
|
542
|
+
},
|
|
543
|
+
update: {
|
|
544
|
+
modified: true,
|
|
545
|
+
userAnswer: input.answer,
|
|
546
|
+
correct: isCorrect,
|
|
547
|
+
completedAt: new Date(),
|
|
548
|
+
attempts: { increment: 1 },
|
|
549
|
+
meta: userMarkScheme
|
|
550
|
+
? { userMarkScheme: JSON.parse(JSON.stringify(userMarkScheme)) }
|
|
551
|
+
: { userMarkScheme: null },
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return { isCorrect, userMarkScheme, progress };
|
|
556
|
+
}),
|
|
557
|
+
});
|
|
476
558
|
|
|
477
559
|
|