@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
@@ -7,7 +7,17 @@ export declare const studyguide: import("@trpc/server").TRPCBuiltRouter<{
7
7
  cookies: Record<string, string | undefined>;
8
8
  };
9
9
  meta: object;
10
- errorShape: import("@trpc/server").TRPCDefaultErrorShape;
10
+ errorShape: {
11
+ data: {
12
+ zodError: string | null;
13
+ code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
14
+ httpStatus: number;
15
+ path?: string;
16
+ stack?: string;
17
+ };
18
+ message: string;
19
+ code: import("@trpc/server").TRPC_ERROR_CODE_NUMBER;
20
+ };
11
21
  transformer: true;
12
22
  }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
13
23
  get: import("@trpc/server").TRPCQueryProcedure<{
@@ -7,14 +7,60 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
7
7
  cookies: Record<string, string | undefined>;
8
8
  };
9
9
  meta: object;
10
- errorShape: import("@trpc/server").TRPCDefaultErrorShape;
10
+ errorShape: {
11
+ data: {
12
+ zodError: string | null;
13
+ code: import("@trpc/server").TRPC_ERROR_CODE_KEY;
14
+ httpStatus: number;
15
+ path?: string;
16
+ stack?: string;
17
+ };
18
+ message: string;
19
+ code: import("@trpc/server").TRPC_ERROR_CODE_NUMBER;
20
+ };
11
21
  transformer: true;
12
22
  }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
13
23
  list: import("@trpc/server").TRPCQueryProcedure<{
14
24
  input: {
15
25
  workspaceId: string;
16
26
  };
17
- output: any;
27
+ output: ({
28
+ versions: {
29
+ id: string;
30
+ createdAt: Date;
31
+ createdById: string | null;
32
+ artifactId: string;
33
+ content: string;
34
+ data: import("@prisma/client/runtime/library").JsonValue | null;
35
+ version: number;
36
+ }[];
37
+ questions: {
38
+ meta: import("@prisma/client/runtime/library").JsonValue | null;
39
+ id: string;
40
+ createdAt: Date;
41
+ type: import("@prisma/client").$Enums.QuestionType;
42
+ difficulty: import("@prisma/client").$Enums.Difficulty;
43
+ artifactId: string;
44
+ prompt: string;
45
+ answer: string | null;
46
+ order: number;
47
+ }[];
48
+ } & {
49
+ id: string;
50
+ createdAt: Date;
51
+ updatedAt: Date;
52
+ title: string;
53
+ description: string | null;
54
+ workspaceId: string;
55
+ type: import("@prisma/client").$Enums.ArtifactType;
56
+ isArchived: boolean;
57
+ generating: boolean;
58
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
59
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
60
+ estimatedTime: string | null;
61
+ imageObjectKey: string | null;
62
+ createdById: string | null;
63
+ })[];
18
64
  meta: object;
19
65
  }>;
20
66
  create: import("@trpc/server").TRPCMutationProcedure<{
@@ -27,7 +73,7 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
27
73
  problems?: {
28
74
  question: string;
29
75
  answer: string;
30
- type?: "TEXT" | "MULTIPLE_CHOICE" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
76
+ type?: "MULTIPLE_CHOICE" | "TEXT" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
31
77
  options?: string[] | undefined;
32
78
  }[] | undefined;
33
79
  };
@@ -39,9 +85,9 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
39
85
  type: import("@prisma/client").$Enums.QuestionType;
40
86
  difficulty: import("@prisma/client").$Enums.Difficulty;
41
87
  artifactId: string;
42
- order: number;
43
88
  prompt: string;
44
89
  answer: string | null;
90
+ order: number;
45
91
  }[];
46
92
  } & {
47
93
  id: string;
@@ -52,8 +98,11 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
52
98
  workspaceId: string;
53
99
  type: import("@prisma/client").$Enums.ArtifactType;
54
100
  isArchived: boolean;
101
+ generating: boolean;
102
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
55
103
  difficulty: import("@prisma/client").$Enums.Difficulty | null;
56
104
  estimatedTime: string | null;
105
+ imageObjectKey: string | null;
57
106
  createdById: string | null;
58
107
  };
59
108
  meta: object;
@@ -62,7 +111,34 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
62
111
  input: {
63
112
  worksheetId: string;
64
113
  };
65
- output: any;
114
+ output: {
115
+ questions: {
116
+ meta: import("@prisma/client/runtime/library").JsonValue | null;
117
+ id: string;
118
+ createdAt: Date;
119
+ type: import("@prisma/client").$Enums.QuestionType;
120
+ difficulty: import("@prisma/client").$Enums.Difficulty;
121
+ artifactId: string;
122
+ prompt: string;
123
+ answer: string | null;
124
+ order: number;
125
+ }[];
126
+ } & {
127
+ id: string;
128
+ createdAt: Date;
129
+ updatedAt: Date;
130
+ title: string;
131
+ description: string | null;
132
+ workspaceId: string;
133
+ type: import("@prisma/client").$Enums.ArtifactType;
134
+ isArchived: boolean;
135
+ generating: boolean;
136
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
137
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
138
+ estimatedTime: string | null;
139
+ imageObjectKey: string | null;
140
+ createdById: string | null;
141
+ };
66
142
  meta: object;
67
143
  }>;
68
144
  createWorksheetQuestion: import("@trpc/server").TRPCMutationProcedure<{
@@ -70,7 +146,7 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
70
146
  worksheetId: string;
71
147
  prompt: string;
72
148
  answer?: string | undefined;
73
- type?: "TEXT" | "MULTIPLE_CHOICE" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
149
+ type?: "MULTIPLE_CHOICE" | "TEXT" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
74
150
  difficulty?: "EASY" | "MEDIUM" | "HARD" | undefined;
75
151
  order?: number | undefined;
76
152
  meta?: Record<string, unknown> | undefined;
@@ -82,9 +158,9 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
82
158
  type: import("@prisma/client").$Enums.QuestionType;
83
159
  difficulty: import("@prisma/client").$Enums.Difficulty;
84
160
  artifactId: string;
85
- order: number;
86
161
  prompt: string;
87
162
  answer: string | null;
163
+ order: number;
88
164
  };
89
165
  meta: object;
90
166
  }>;
@@ -93,7 +169,7 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
93
169
  worksheetQuestionId: string;
94
170
  prompt?: string | undefined;
95
171
  answer?: string | undefined;
96
- type?: "TEXT" | "MULTIPLE_CHOICE" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
172
+ type?: "MULTIPLE_CHOICE" | "TEXT" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
97
173
  difficulty?: "EASY" | "MEDIUM" | "HARD" | undefined;
98
174
  order?: number | undefined;
99
175
  meta?: Record<string, unknown> | undefined;
@@ -105,9 +181,9 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
105
181
  type: import("@prisma/client").$Enums.QuestionType;
106
182
  difficulty: import("@prisma/client").$Enums.Difficulty;
107
183
  artifactId: string;
108
- order: number;
109
184
  prompt: string;
110
185
  answer: string | null;
186
+ order: number;
111
187
  };
112
188
  meta: object;
113
189
  }>;
@@ -123,6 +199,7 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
123
199
  problemId: string;
124
200
  completed: boolean;
125
201
  answer?: string | undefined;
202
+ correct?: boolean | undefined;
126
203
  };
127
204
  output: {
128
205
  meta: import("@prisma/client/runtime/library").JsonValue | null;
@@ -131,8 +208,9 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
131
208
  updatedAt: Date;
132
209
  userId: string;
133
210
  worksheetQuestionId: string;
134
- completed: boolean;
211
+ modified: boolean;
135
212
  userAnswer: string | null;
213
+ correct: boolean | null;
136
214
  completedAt: Date | null;
137
215
  attempts: number;
138
216
  timeSpentSec: number | null;
@@ -150,8 +228,9 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
150
228
  updatedAt: Date;
151
229
  userId: string;
152
230
  worksheetQuestionId: string;
153
- completed: boolean;
231
+ modified: boolean;
154
232
  userAnswer: string | null;
233
+ correct: boolean | null;
155
234
  completedAt: Date | null;
156
235
  attempts: number;
157
236
  timeSpentSec: number | null;
@@ -169,7 +248,7 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
169
248
  question: string;
170
249
  answer: string;
171
250
  id?: string | undefined;
172
- type?: "TEXT" | "MULTIPLE_CHOICE" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
251
+ type?: "MULTIPLE_CHOICE" | "TEXT" | "NUMERIC" | "TRUE_FALSE" | "MATCHING" | "FILL_IN_THE_BLANK" | undefined;
173
252
  options?: string[] | undefined;
174
253
  }[] | undefined;
175
254
  };
@@ -181,9 +260,9 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
181
260
  type: import("@prisma/client").$Enums.QuestionType;
182
261
  difficulty: import("@prisma/client").$Enums.Difficulty;
183
262
  artifactId: string;
184
- order: number;
185
263
  prompt: string;
186
264
  answer: string | null;
265
+ order: number;
187
266
  }[];
188
267
  } & {
189
268
  id: string;
@@ -194,8 +273,11 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
194
273
  workspaceId: string;
195
274
  type: import("@prisma/client").$Enums.ArtifactType;
196
275
  isArchived: boolean;
276
+ generating: boolean;
277
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
197
278
  difficulty: import("@prisma/client").$Enums.Difficulty | null;
198
279
  estimatedTime: string | null;
280
+ imageObjectKey: string | null;
199
281
  createdById: string | null;
200
282
  };
201
283
  meta: object;
@@ -226,11 +308,40 @@ export declare const worksheets: import("@trpc/server").TRPCBuiltRouter<{
226
308
  workspaceId: string;
227
309
  type: import("@prisma/client").$Enums.ArtifactType;
228
310
  isArchived: boolean;
311
+ generating: boolean;
312
+ generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
229
313
  difficulty: import("@prisma/client").$Enums.Difficulty | null;
230
314
  estimatedTime: string | null;
315
+ imageObjectKey: string | null;
231
316
  createdById: string | null;
232
317
  };
233
318
  };
234
319
  meta: object;
235
320
  }>;
321
+ checkAnswer: import("@trpc/server").TRPCMutationProcedure<{
322
+ input: {
323
+ worksheetId: string;
324
+ questionId: string;
325
+ answer: string;
326
+ };
327
+ output: {
328
+ isCorrect: boolean;
329
+ userMarkScheme: import("../types/index.js").UserMarkScheme | null;
330
+ progress: {
331
+ meta: import("@prisma/client/runtime/library").JsonValue | null;
332
+ id: string;
333
+ createdAt: Date;
334
+ updatedAt: Date;
335
+ userId: string;
336
+ worksheetQuestionId: string;
337
+ modified: boolean;
338
+ userAnswer: string | null;
339
+ correct: boolean | null;
340
+ completedAt: Date | null;
341
+ attempts: number;
342
+ timeSpentSec: number | null;
343
+ };
344
+ };
345
+ meta: object;
346
+ }>;
236
347
  }>>;
@@ -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
  // Avoid importing Prisma enums directly; mirror values as string literals
7
8
  const ArtifactType = {
8
9
  WORKSHEET: 'WORKSHEET',
@@ -53,13 +54,15 @@ export const worksheets = router({
53
54
  if (!p)
54
55
  return q;
55
56
  const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta)) : {};
57
+ const progressMeta = p.meta ? (typeof p.meta === 'object' ? p.meta : JSON.parse(p.meta)) : {};
56
58
  return {
57
59
  ...q,
58
60
  meta: {
59
61
  ...existingMeta,
60
- completed: p.completed,
62
+ completed: p.modified,
61
63
  userAnswer: p.userAnswer,
62
64
  completedAt: p.completedAt,
65
+ userMarkScheme: progressMeta.userMarkScheme,
63
66
  },
64
67
  };
65
68
  }),
@@ -141,13 +144,15 @@ export const worksheets = router({
141
144
  if (!p)
142
145
  return q;
143
146
  const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta)) : {};
147
+ const progressMeta = p.meta ? (typeof p.meta === 'object' ? p.meta : JSON.parse(p.meta)) : {};
144
148
  return {
145
149
  ...q,
146
150
  meta: {
147
151
  ...existingMeta,
148
- completed: p.completed,
152
+ completed: p.modified,
149
153
  userAnswer: p.userAnswer,
150
154
  completedAt: p.completedAt,
155
+ userMarkScheme: progressMeta.userMarkScheme,
151
156
  },
152
157
  };
153
158
  }),
@@ -230,6 +235,7 @@ export const worksheets = router({
230
235
  problemId: z.string(),
231
236
  completed: z.boolean(),
232
237
  answer: z.string().optional(),
238
+ correct: z.boolean().optional(),
233
239
  }))
234
240
  .mutation(async ({ ctx, input }) => {
235
241
  // Verify question ownership through worksheet
@@ -255,13 +261,14 @@ export const worksheets = router({
255
261
  create: {
256
262
  worksheetQuestionId: input.problemId,
257
263
  userId: ctx.session.user.id,
258
- completed: input.completed,
264
+ modified: input.completed,
259
265
  userAnswer: input.answer,
266
+ correct: input.correct,
260
267
  completedAt: input.completed ? new Date() : null,
261
268
  attempts: 1,
262
269
  },
263
270
  update: {
264
- completed: input.completed,
271
+ modified: input.completed,
265
272
  userAnswer: input.answer,
266
273
  completedAt: input.completed ? new Date() : null,
267
274
  attempts: { increment: 1 },
@@ -381,12 +388,6 @@ export const worksheets = router({
381
388
  const workspace = await ctx.db.workspace.findFirst({ where: { id: input.workspaceId, ownerId: ctx.session.user.id } });
382
389
  if (!workspace)
383
390
  throw new TRPCError({ code: 'NOT_FOUND' });
384
- await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_load_start', { source: 'prompt' });
385
- const session = await aiSessionService.initSession(input.workspaceId);
386
- 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}`);
387
- await aiSessionService.startLLMSession(session.id);
388
- const content = await aiSessionService.generateWorksheetQuestions(session.id, input.numQuestions, input.difficulty);
389
- await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength: content.length });
390
391
  const artifact = await ctx.db.artifact.create({
391
392
  data: {
392
393
  workspaceId: input.workspaceId,
@@ -395,60 +396,132 @@ export const worksheets = router({
395
396
  createdById: ctx.session.user.id,
396
397
  difficulty: (input.difficulty.toUpperCase()),
397
398
  estimatedTime: input.estimatedTime,
399
+ generating: true,
400
+ generatingMetadata: { quantity: input.numQuestions, difficulty: input.difficulty.toLowerCase() },
398
401
  },
399
402
  });
403
+ await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength: input.numQuestions });
400
404
  try {
401
- const worksheetData = JSON.parse(content);
402
- let actualWorksheetData = worksheetData;
403
- if (worksheetData.last_response) {
404
- try {
405
- actualWorksheetData = JSON.parse(worksheetData.last_response);
405
+ const content = await aiSessionService.generateWorksheetQuestions(input.workspaceId, ctx.session.user.id, input.numQuestions, input.difficulty);
406
+ try {
407
+ const worksheetData = JSON.parse(content);
408
+ let actualWorksheetData = worksheetData;
409
+ if (worksheetData.last_response) {
410
+ try {
411
+ actualWorksheetData = JSON.parse(worksheetData.last_response);
412
+ }
413
+ catch { }
406
414
  }
407
- catch { }
408
- }
409
- const problems = actualWorksheetData.problems || actualWorksheetData.questions || actualWorksheetData || [];
410
- for (let i = 0; i < Math.min(problems.length, input.numQuestions); i++) {
411
- const problem = problems[i];
412
- const prompt = problem.question || problem.prompt || `Question ${i + 1}`;
413
- const answer = problem.answer || problem.solution || `Answer ${i + 1}`;
414
- const type = problem.type || 'TEXT';
415
- const options = problem.options || [];
416
- await ctx.db.worksheetQuestion.create({
417
- data: {
418
- artifactId: artifact.id,
419
- prompt,
420
- answer,
421
- difficulty: (input.difficulty.toUpperCase()),
422
- order: i,
423
- meta: {
424
- type,
425
- options: options.length > 0 ? options : undefined,
426
- },
427
- },
428
- });
429
- }
430
- }
431
- catch {
432
- const lines = content.split('\n').filter(line => line.trim());
433
- for (let i = 0; i < Math.min(lines.length, input.numQuestions); i++) {
434
- const line = lines[i];
435
- if (line.includes(' - ')) {
436
- const [q, a] = line.split(' - ');
415
+ const problems = actualWorksheetData.problems || actualWorksheetData.questions || actualWorksheetData || [];
416
+ for (let i = 0; i < Math.min(problems.length, input.numQuestions); i++) {
417
+ const problem = problems[i];
418
+ const prompt = problem.question || problem.prompt || `Question ${i + 1}`;
419
+ const answer = problem.answer || problem.solution || `Answer ${i + 1}`;
420
+ const type = problem.type || 'TEXT';
421
+ const options = problem.options || [];
437
422
  await ctx.db.worksheetQuestion.create({
438
423
  data: {
439
424
  artifactId: artifact.id,
440
- prompt: q.trim(),
441
- answer: a.trim(),
425
+ prompt,
426
+ answer,
442
427
  difficulty: (input.difficulty.toUpperCase()),
443
428
  order: i,
444
- meta: { type: 'TEXT' },
429
+ meta: {
430
+ type,
431
+ options: options.length > 0 ? options : undefined,
432
+ mark_scheme: problem.mark_scheme || undefined,
433
+ },
445
434
  },
446
435
  });
447
436
  }
448
437
  }
438
+ catch {
439
+ logger.error('Failed to parse worksheet JSON,');
440
+ await ctx.db.artifact.delete({
441
+ where: { id: artifact.id },
442
+ });
443
+ throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to parse worksheet JSON' });
444
+ }
445
+ await ctx.db.artifact.update({
446
+ where: { id: artifact.id },
447
+ data: { generating: false },
448
+ });
449
+ await PusherService.emitWorksheetComplete(input.workspaceId, artifact);
450
+ }
451
+ catch (error) {
452
+ await ctx.db.artifact.delete({
453
+ where: { id: artifact.id },
454
+ });
455
+ await PusherService.emitError(input.workspaceId, `Failed to generate worksheet: ${error instanceof Error ? error.message : 'Unknown error'}`, 'worksheet_generation');
456
+ throw error;
449
457
  }
450
- await PusherService.emitWorksheetComplete(input.workspaceId, artifact);
451
- aiSessionService.deleteSession(session.id);
452
458
  return { artifact };
453
459
  }),
460
+ checkAnswer: authedProcedure
461
+ .input(z.object({
462
+ worksheetId: z.string(),
463
+ questionId: z.string(),
464
+ answer: z.string().min(1),
465
+ }))
466
+ .mutation(async ({ ctx, input }) => {
467
+ const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } }, include: { workspace: true } });
468
+ if (!worksheet)
469
+ throw new TRPCError({ code: 'NOT_FOUND' });
470
+ const question = await ctx.db.worksheetQuestion.findFirst({ where: { id: input.questionId, artifactId: input.worksheetId } });
471
+ if (!question)
472
+ throw new TRPCError({ code: 'NOT_FOUND' });
473
+ // Parse question meta to get mark_scheme
474
+ const questionMeta = question.meta ? (typeof question.meta === 'object' ? question.meta : JSON.parse(question.meta)) : {};
475
+ const markScheme = questionMeta.mark_scheme;
476
+ let isCorrect = false;
477
+ let userMarkScheme = null;
478
+ // If mark scheme exists, use AI marking
479
+ if (markScheme && markScheme.points && markScheme.points.length > 0) {
480
+ try {
481
+ userMarkScheme = await aiSessionService.checkWorksheetQuestions(worksheet.workspace.id, ctx.session.user.id, question.prompt, input.answer, markScheme);
482
+ // Determine if correct by comparing achieved points vs total points
483
+ const achievedTotal = userMarkScheme.points.reduce((sum, p) => sum + (p.achievedPoints || 0), 0);
484
+ isCorrect = achievedTotal === markScheme.totalPoints;
485
+ }
486
+ catch (error) {
487
+ logger.error('Failed to check answer with AI', error instanceof Error ? error.message : 'Unknown error');
488
+ // Fallback to simple string comparison
489
+ isCorrect = question.answer === input.answer;
490
+ }
491
+ }
492
+ else {
493
+ // Simple string comparison if no mark scheme
494
+ isCorrect = question.answer === input.answer;
495
+ }
496
+ // @todo: figure out this wierd fix
497
+ const progress = await ctx.db.worksheetQuestionProgress.upsert({
498
+ where: {
499
+ worksheetQuestionId_userId: {
500
+ worksheetQuestionId: input.questionId,
501
+ userId: ctx.session.user.id,
502
+ },
503
+ },
504
+ create: {
505
+ worksheetQuestionId: input.questionId,
506
+ userId: ctx.session.user.id,
507
+ modified: true,
508
+ userAnswer: input.answer,
509
+ correct: isCorrect,
510
+ completedAt: new Date(),
511
+ attempts: 1,
512
+ meta: userMarkScheme ? { userMarkScheme: JSON.parse(JSON.stringify(userMarkScheme)) } : { userMarkScheme: null },
513
+ },
514
+ update: {
515
+ modified: true,
516
+ userAnswer: input.answer,
517
+ correct: isCorrect,
518
+ completedAt: new Date(),
519
+ attempts: { increment: 1 },
520
+ meta: userMarkScheme
521
+ ? { userMarkScheme: JSON.parse(JSON.stringify(userMarkScheme)) }
522
+ : { userMarkScheme: null },
523
+ },
524
+ });
525
+ return { isCorrect, userMarkScheme, progress };
526
+ }),
454
527
  });