@goscribe/server 1.0.11 → 1.1.1

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.
Files changed (83) hide show
  1. package/ANALYSIS_PROGRESS_SPEC.md +463 -0
  2. package/PROGRESS_QUICK_REFERENCE.md +239 -0
  3. package/dist/lib/ai-session.d.ts +20 -9
  4. package/dist/lib/ai-session.js +316 -80
  5. package/dist/lib/auth.d.ts +35 -2
  6. package/dist/lib/auth.js +88 -15
  7. package/dist/lib/env.d.ts +32 -0
  8. package/dist/lib/env.js +46 -0
  9. package/dist/lib/errors.d.ts +33 -0
  10. package/dist/lib/errors.js +78 -0
  11. package/dist/lib/inference.d.ts +4 -1
  12. package/dist/lib/inference.js +9 -11
  13. package/dist/lib/logger.d.ts +62 -0
  14. package/dist/lib/logger.js +342 -0
  15. package/dist/lib/podcast-prompts.d.ts +43 -0
  16. package/dist/lib/podcast-prompts.js +135 -0
  17. package/dist/lib/pusher.d.ts +1 -0
  18. package/dist/lib/pusher.js +14 -2
  19. package/dist/lib/storage.d.ts +3 -3
  20. package/dist/lib/storage.js +51 -47
  21. package/dist/lib/validation.d.ts +51 -0
  22. package/dist/lib/validation.js +64 -0
  23. package/dist/routers/_app.d.ts +697 -111
  24. package/dist/routers/_app.js +5 -0
  25. package/dist/routers/auth.d.ts +11 -1
  26. package/dist/routers/chat.d.ts +11 -1
  27. package/dist/routers/flashcards.d.ts +205 -6
  28. package/dist/routers/flashcards.js +144 -66
  29. package/dist/routers/members.d.ts +165 -0
  30. package/dist/routers/members.js +531 -0
  31. package/dist/routers/podcast.d.ts +78 -63
  32. package/dist/routers/podcast.js +330 -393
  33. package/dist/routers/studyguide.d.ts +11 -1
  34. package/dist/routers/worksheets.d.ts +124 -13
  35. package/dist/routers/worksheets.js +123 -50
  36. package/dist/routers/workspace.d.ts +213 -26
  37. package/dist/routers/workspace.js +303 -181
  38. package/dist/server.js +12 -4
  39. package/dist/services/flashcard-progress.service.d.ts +183 -0
  40. package/dist/services/flashcard-progress.service.js +383 -0
  41. package/dist/services/flashcard.service.d.ts +183 -0
  42. package/dist/services/flashcard.service.js +224 -0
  43. package/dist/services/podcast-segment-reorder.d.ts +0 -0
  44. package/dist/services/podcast-segment-reorder.js +107 -0
  45. package/dist/services/podcast.service.d.ts +0 -0
  46. package/dist/services/podcast.service.js +326 -0
  47. package/dist/services/worksheet.service.d.ts +0 -0
  48. package/dist/services/worksheet.service.js +295 -0
  49. package/dist/trpc.d.ts +13 -2
  50. package/dist/trpc.js +55 -6
  51. package/dist/types/index.d.ts +126 -0
  52. package/dist/types/index.js +1 -0
  53. package/package.json +3 -2
  54. package/prisma/schema.prisma +142 -4
  55. package/src/lib/ai-session.ts +356 -85
  56. package/src/lib/auth.ts +113 -19
  57. package/src/lib/env.ts +59 -0
  58. package/src/lib/errors.ts +92 -0
  59. package/src/lib/inference.ts +11 -11
  60. package/src/lib/logger.ts +405 -0
  61. package/src/lib/pusher.ts +15 -3
  62. package/src/lib/storage.ts +56 -51
  63. package/src/lib/validation.ts +75 -0
  64. package/src/routers/_app.ts +5 -0
  65. package/src/routers/chat.ts +2 -23
  66. package/src/routers/flashcards.ts +108 -24
  67. package/src/routers/members.ts +586 -0
  68. package/src/routers/podcast.ts +385 -420
  69. package/src/routers/worksheets.ts +118 -36
  70. package/src/routers/workspace.ts +356 -195
  71. package/src/server.ts +13 -4
  72. package/src/services/flashcard-progress.service.ts +541 -0
  73. package/src/trpc.ts +59 -6
  74. package/src/types/index.ts +165 -0
  75. package/AUTH_FRONTEND_SPEC.md +0 -21
  76. package/CHAT_FRONTEND_SPEC.md +0 -474
  77. package/DATABASE_SETUP.md +0 -165
  78. package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
  79. package/PODCAST_FRONTEND_SPEC.md +0 -595
  80. package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
  81. package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
  82. package/WORKSPACE_FRONTEND_SPEC.md +0 -47
  83. 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.completed,
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 as any;
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.completed,
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 as any;
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
- completed: input.completed,
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
- completed: input.completed,
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,
@@ -440,38 +444,116 @@ export const worksheets = router({
440
444
  answer,
441
445
  difficulty: (input.difficulty.toUpperCase()) as any,
442
446
  order: i,
447
+ type,
443
448
  meta: {
444
- type,
445
449
  options: options.length > 0 ? options : undefined,
450
+ markScheme: problem.mark_scheme || undefined,
446
451
  },
447
452
  },
448
453
  });
449
454
  }
450
455
  } catch {
451
- const lines = content.split('\n').filter(line => line.trim());
452
- for (let i = 0; i < Math.min(lines.length, input.numQuestions); i++) {
453
- const line = lines[i];
454
- if (line.includes(' - ')) {
455
- const [q, a] = line.split(' - ');
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
- aiSessionService.deleteSession(session.id);
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