@goscribe/server 1.0.7 → 1.0.9

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 (74) hide show
  1. package/AUTH_FRONTEND_SPEC.md +21 -0
  2. package/CHAT_FRONTEND_SPEC.md +474 -0
  3. package/MEETINGSUMMARY_FRONTEND_SPEC.md +28 -0
  4. package/PODCAST_FRONTEND_SPEC.md +595 -0
  5. package/STUDYGUIDE_FRONTEND_SPEC.md +18 -0
  6. package/WORKSHEETS_FRONTEND_SPEC.md +26 -0
  7. package/WORKSPACE_FRONTEND_SPEC.md +47 -0
  8. package/dist/context.d.ts +1 -1
  9. package/dist/lib/ai-session.d.ts +26 -0
  10. package/dist/lib/ai-session.js +343 -0
  11. package/dist/lib/auth.js +10 -6
  12. package/dist/lib/inference.d.ts +2 -0
  13. package/dist/lib/inference.js +21 -0
  14. package/dist/lib/pusher.d.ts +14 -0
  15. package/dist/lib/pusher.js +94 -0
  16. package/dist/lib/storage.d.ts +10 -2
  17. package/dist/lib/storage.js +63 -6
  18. package/dist/routers/_app.d.ts +878 -100
  19. package/dist/routers/_app.js +8 -2
  20. package/dist/routers/ai-session.d.ts +0 -0
  21. package/dist/routers/ai-session.js +1 -0
  22. package/dist/routers/auth.d.ts +13 -11
  23. package/dist/routers/auth.js +50 -21
  24. package/dist/routers/chat.d.ts +171 -0
  25. package/dist/routers/chat.js +270 -0
  26. package/dist/routers/flashcards.d.ts +51 -39
  27. package/dist/routers/flashcards.js +143 -31
  28. package/dist/routers/meetingsummary.d.ts +0 -0
  29. package/dist/routers/meetingsummary.js +377 -0
  30. package/dist/routers/podcast.d.ts +277 -0
  31. package/dist/routers/podcast.js +847 -0
  32. package/dist/routers/studyguide.d.ts +54 -0
  33. package/dist/routers/studyguide.js +125 -0
  34. package/dist/routers/worksheets.d.ts +147 -40
  35. package/dist/routers/worksheets.js +348 -33
  36. package/dist/routers/workspace.d.ts +163 -8
  37. package/dist/routers/workspace.js +453 -8
  38. package/dist/server.d.ts +1 -1
  39. package/dist/server.js +7 -2
  40. package/dist/trpc.d.ts +5 -5
  41. package/package.json +11 -3
  42. package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +213 -0
  43. package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +31 -0
  44. package/prisma/migrations/migration_lock.toml +3 -0
  45. package/prisma/schema.prisma +87 -6
  46. package/prisma/seed.mjs +135 -0
  47. package/src/lib/ai-session.ts +411 -0
  48. package/src/lib/auth.ts +1 -1
  49. package/src/lib/inference.ts +21 -0
  50. package/src/lib/pusher.ts +104 -0
  51. package/src/lib/storage.ts +89 -6
  52. package/src/routers/_app.ts +6 -0
  53. package/src/routers/auth.ts +8 -4
  54. package/src/routers/chat.ts +275 -0
  55. package/src/routers/flashcards.ts +151 -33
  56. package/src/routers/meetingsummary.ts +416 -0
  57. package/src/routers/podcast.ts +934 -0
  58. package/src/routers/studyguide.ts +144 -0
  59. package/src/routers/worksheets.ts +346 -18
  60. package/src/routers/workspace.ts +500 -8
  61. package/src/server.ts +7 -2
  62. package/test-ai-integration.js +134 -0
  63. package/dist/context.d.ts.map +0 -1
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/lib/auth.d.ts.map +0 -1
  66. package/dist/lib/file.d.ts.map +0 -1
  67. package/dist/lib/prisma.d.ts.map +0 -1
  68. package/dist/lib/storage.d.ts.map +0 -1
  69. package/dist/routers/_app.d.ts.map +0 -1
  70. package/dist/routers/auth.d.ts.map +0 -1
  71. package/dist/routers/sample.js +0 -21
  72. package/dist/routers/workspace.d.ts.map +0 -1
  73. package/dist/server.d.ts.map +0 -1
  74. package/dist/trpc.d.ts.map +0 -1
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { TRPCError } from '@trpc/server';
3
3
  import { router, authedProcedure } from '../trpc.js';
4
+ import { aiSessionService } from '../lib/ai-session.js';
5
+ import PusherService from '../lib/pusher.js';
4
6
  // Avoid importing Prisma enums directly; mirror values as string literals
5
7
  const ArtifactType = {
6
8
  WORKSHEET: 'WORKSHEET',
@@ -10,76 +12,171 @@ const Difficulty = {
10
12
  MEDIUM: 'MEDIUM',
11
13
  HARD: 'HARD',
12
14
  };
15
+ const QuestionType = {
16
+ MULTIPLE_CHOICE: 'MULTIPLE_CHOICE',
17
+ TEXT: 'TEXT',
18
+ NUMERIC: 'NUMERIC',
19
+ TRUE_FALSE: 'TRUE_FALSE',
20
+ MATCHING: 'MATCHING',
21
+ FILL_IN_THE_BLANK: 'FILL_IN_THE_BLANK',
22
+ };
13
23
  export const worksheets = router({
14
24
  // List all worksheet artifacts for a workspace
15
- listSets: authedProcedure
16
- .input(z.object({ workspaceId: z.string().uuid() }))
25
+ list: authedProcedure
26
+ .input(z.object({ workspaceId: z.string() }))
17
27
  .query(async ({ ctx, input }) => {
18
- const workspace = await ctx.db.workspace.findFirst({
19
- where: { id: input.workspaceId, ownerId: ctx.session.user.id },
20
- });
21
- if (!workspace)
22
- throw new TRPCError({ code: 'NOT_FOUND' });
23
- return ctx.db.artifact.findMany({
28
+ const worksheets = await ctx.db.artifact.findMany({
24
29
  where: { workspaceId: input.workspaceId, type: ArtifactType.WORKSHEET },
30
+ include: {
31
+ versions: {
32
+ orderBy: { version: 'desc' },
33
+ take: 1, // Get only the latest version
34
+ },
35
+ questions: true,
36
+ },
25
37
  orderBy: { updatedAt: 'desc' },
26
38
  });
39
+ if (!worksheets)
40
+ throw new TRPCError({ code: 'NOT_FOUND' });
41
+ // Merge per-user progress into question.meta for compatibility with UI
42
+ const allQuestionIds = worksheets.flatMap(w => w.questions.map(q => q.id));
43
+ if (allQuestionIds.length === 0)
44
+ return worksheets;
45
+ const progress = await ctx.db.worksheetQuestionProgress.findMany({
46
+ where: { userId: ctx.session.user.id, worksheetQuestionId: { in: allQuestionIds } },
47
+ });
48
+ const progressByQuestionId = new Map(progress.map(p => [p.worksheetQuestionId, p]));
49
+ const merged = worksheets.map(w => ({
50
+ ...w,
51
+ questions: w.questions.map(q => {
52
+ const p = progressByQuestionId.get(q.id);
53
+ if (!p)
54
+ return q;
55
+ const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta)) : {};
56
+ return {
57
+ ...q,
58
+ meta: {
59
+ ...existingMeta,
60
+ completed: p.completed,
61
+ userAnswer: p.userAnswer,
62
+ completedAt: p.completedAt,
63
+ },
64
+ };
65
+ }),
66
+ }));
67
+ return merged;
27
68
  }),
28
69
  // Create a worksheet set
29
- createSet: authedProcedure
30
- .input(z.object({ workspaceId: z.string().uuid(), title: z.string().min(1).max(120) }))
70
+ create: authedProcedure
71
+ .input(z.object({
72
+ workspaceId: z.string(),
73
+ title: z.string().min(1).max(120),
74
+ description: z.string().optional(),
75
+ difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
76
+ estimatedTime: z.string().optional(),
77
+ problems: z.array(z.object({
78
+ question: z.string().min(1),
79
+ answer: z.string().min(1),
80
+ type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).default('TEXT'),
81
+ options: z.array(z.string()).optional(),
82
+ })).optional(),
83
+ }))
31
84
  .mutation(async ({ ctx, input }) => {
32
85
  const workspace = await ctx.db.workspace.findFirst({
33
86
  where: { id: input.workspaceId, ownerId: ctx.session.user.id },
34
87
  });
35
88
  if (!workspace)
36
89
  throw new TRPCError({ code: 'NOT_FOUND' });
90
+ const { problems, ...worksheetData } = input;
37
91
  return ctx.db.artifact.create({
38
92
  data: {
39
93
  workspaceId: input.workspaceId,
40
94
  type: ArtifactType.WORKSHEET,
41
95
  title: input.title,
96
+ difficulty: input.difficulty,
97
+ estimatedTime: input.estimatedTime,
42
98
  createdById: ctx.session.user.id,
99
+ questions: problems ? {
100
+ create: problems.map((problem, index) => ({
101
+ prompt: problem.question,
102
+ answer: problem.answer,
103
+ type: problem.type,
104
+ order: index,
105
+ meta: problem.options ? { options: problem.options } : undefined,
106
+ })),
107
+ } : undefined,
108
+ },
109
+ include: {
110
+ questions: true,
43
111
  },
44
112
  });
45
113
  }),
46
114
  // Get a worksheet with its questions
47
- getSet: authedProcedure
48
- .input(z.object({ setId: z.string().uuid() }))
115
+ get: authedProcedure
116
+ .input(z.object({ worksheetId: z.string() }))
49
117
  .query(async ({ ctx, input }) => {
50
- const set = await ctx.db.artifact.findFirst({
118
+ const worksheet = await ctx.db.artifact.findFirst({
51
119
  where: {
52
- id: input.setId,
120
+ id: input.worksheetId,
53
121
  type: ArtifactType.WORKSHEET,
54
122
  workspace: { ownerId: ctx.session.user.id },
55
123
  },
56
124
  include: { questions: true },
125
+ orderBy: { updatedAt: 'desc' },
57
126
  });
58
- if (!set)
127
+ if (!worksheet)
59
128
  throw new TRPCError({ code: 'NOT_FOUND' });
60
- return set;
129
+ // Merge per-user progress into question.meta for compatibility with UI
130
+ const questionIds = worksheet.questions.map(q => q.id);
131
+ if (questionIds.length === 0)
132
+ return worksheet;
133
+ const progress = await ctx.db.worksheetQuestionProgress.findMany({
134
+ where: { userId: ctx.session.user.id, worksheetQuestionId: { in: questionIds } },
135
+ });
136
+ const progressByQuestionId = new Map(progress.map(p => [p.worksheetQuestionId, p]));
137
+ const merged = {
138
+ ...worksheet,
139
+ questions: worksheet.questions.map(q => {
140
+ const p = progressByQuestionId.get(q.id);
141
+ if (!p)
142
+ return q;
143
+ const existingMeta = q.meta ? (typeof q.meta === 'object' ? q.meta : JSON.parse(q.meta)) : {};
144
+ return {
145
+ ...q,
146
+ meta: {
147
+ ...existingMeta,
148
+ completed: p.completed,
149
+ userAnswer: p.userAnswer,
150
+ completedAt: p.completedAt,
151
+ },
152
+ };
153
+ }),
154
+ };
155
+ return merged;
61
156
  }),
62
157
  // Add a question to a worksheet
63
- createQuestion: authedProcedure
158
+ createWorksheetQuestion: authedProcedure
64
159
  .input(z.object({
65
- setId: z.string().uuid(),
160
+ worksheetId: z.string(),
66
161
  prompt: z.string().min(1),
67
162
  answer: z.string().optional(),
163
+ type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).optional(),
68
164
  difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
69
165
  order: z.number().int().optional(),
70
166
  meta: z.record(z.string(), z.unknown()).optional(),
71
167
  }))
72
168
  .mutation(async ({ ctx, input }) => {
73
- const set = await ctx.db.artifact.findFirst({
74
- where: { id: input.setId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } },
169
+ const worksheet = await ctx.db.artifact.findFirst({
170
+ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } },
75
171
  });
76
- if (!set)
172
+ if (!worksheet)
77
173
  throw new TRPCError({ code: 'NOT_FOUND' });
78
174
  return ctx.db.worksheetQuestion.create({
79
175
  data: {
80
- artifactId: input.setId,
176
+ artifactId: input.worksheetId,
81
177
  prompt: input.prompt,
82
178
  answer: input.answer,
179
+ type: (input.type ?? QuestionType.TEXT),
83
180
  difficulty: (input.difficulty ?? Difficulty.MEDIUM),
84
181
  order: input.order ?? 0,
85
182
  meta: input.meta,
@@ -87,26 +184,28 @@ export const worksheets = router({
87
184
  });
88
185
  }),
89
186
  // Update a question
90
- updateQuestion: authedProcedure
187
+ updateWorksheetQuestion: authedProcedure
91
188
  .input(z.object({
92
- questionId: z.string().uuid(),
189
+ worksheetQuestionId: z.string(),
93
190
  prompt: z.string().optional(),
94
191
  answer: z.string().optional(),
192
+ type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).optional(),
95
193
  difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
96
194
  order: z.number().int().optional(),
97
195
  meta: z.record(z.string(), z.unknown()).optional(),
98
196
  }))
99
197
  .mutation(async ({ ctx, input }) => {
100
198
  const q = await ctx.db.worksheetQuestion.findFirst({
101
- where: { id: input.questionId, artifact: { type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } } },
199
+ where: { id: input.worksheetQuestionId, artifact: { type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } } },
102
200
  });
103
201
  if (!q)
104
202
  throw new TRPCError({ code: 'NOT_FOUND' });
105
203
  return ctx.db.worksheetQuestion.update({
106
- where: { id: input.questionId },
204
+ where: { id: input.worksheetQuestionId },
107
205
  data: {
108
206
  prompt: input.prompt ?? q.prompt,
109
207
  answer: input.answer ?? q.answer,
208
+ type: (input.type ?? q.type),
110
209
  difficulty: (input.difficulty ?? q.difficulty),
111
210
  order: input.order ?? q.order,
112
211
  meta: (input.meta ?? q.meta),
@@ -114,26 +213,242 @@ export const worksheets = router({
114
213
  });
115
214
  }),
116
215
  // Delete a question
117
- deleteQuestion: authedProcedure
118
- .input(z.object({ questionId: z.string().uuid() }))
216
+ deleteWorksheetQuestion: authedProcedure
217
+ .input(z.object({ worksheetQuestionId: z.string() }))
119
218
  .mutation(async ({ ctx, input }) => {
120
219
  const q = await ctx.db.worksheetQuestion.findFirst({
121
- where: { id: input.questionId, artifact: { workspace: { ownerId: ctx.session.user.id } } },
220
+ where: { id: input.worksheetQuestionId, artifact: { workspace: { ownerId: ctx.session.user.id } } },
122
221
  });
123
222
  if (!q)
124
223
  throw new TRPCError({ code: 'NOT_FOUND' });
125
- await ctx.db.worksheetQuestion.delete({ where: { id: input.questionId } });
224
+ await ctx.db.worksheetQuestion.delete({ where: { id: input.worksheetQuestionId } });
126
225
  return true;
127
226
  }),
227
+ // Update problem completion status
228
+ updateProblemStatus: authedProcedure
229
+ .input(z.object({
230
+ problemId: z.string(),
231
+ completed: z.boolean(),
232
+ answer: z.string().optional(),
233
+ }))
234
+ .mutation(async ({ ctx, input }) => {
235
+ // Verify question ownership through worksheet
236
+ const question = await ctx.db.worksheetQuestion.findFirst({
237
+ where: {
238
+ id: input.problemId,
239
+ artifact: {
240
+ type: ArtifactType.WORKSHEET,
241
+ workspace: { ownerId: ctx.session.user.id },
242
+ },
243
+ },
244
+ });
245
+ if (!question)
246
+ throw new TRPCError({ code: 'NOT_FOUND' });
247
+ // Upsert per-user progress row
248
+ const progress = await ctx.db.worksheetQuestionProgress.upsert({
249
+ where: {
250
+ worksheetQuestionId_userId: {
251
+ worksheetQuestionId: input.problemId,
252
+ userId: ctx.session.user.id,
253
+ },
254
+ },
255
+ create: {
256
+ worksheetQuestionId: input.problemId,
257
+ userId: ctx.session.user.id,
258
+ completed: input.completed,
259
+ userAnswer: input.answer,
260
+ completedAt: input.completed ? new Date() : null,
261
+ attempts: 1,
262
+ },
263
+ update: {
264
+ completed: input.completed,
265
+ userAnswer: input.answer,
266
+ completedAt: input.completed ? new Date() : null,
267
+ attempts: { increment: 1 },
268
+ },
269
+ });
270
+ return progress;
271
+ }),
272
+ // Get current user's progress for all questions in a worksheet
273
+ getProgress: authedProcedure
274
+ .input(z.object({ worksheetId: z.string() }))
275
+ .query(async ({ ctx, input }) => {
276
+ // Verify worksheet ownership
277
+ const worksheet = await ctx.db.artifact.findFirst({
278
+ where: {
279
+ id: input.worksheetId,
280
+ type: ArtifactType.WORKSHEET,
281
+ workspace: { ownerId: ctx.session.user.id },
282
+ },
283
+ });
284
+ if (!worksheet)
285
+ throw new TRPCError({ code: 'NOT_FOUND' });
286
+ const questions = await ctx.db.worksheetQuestion.findMany({
287
+ where: { artifactId: input.worksheetId },
288
+ select: { id: true },
289
+ });
290
+ const questionIds = questions.map(q => q.id);
291
+ const progress = await ctx.db.worksheetQuestionProgress.findMany({
292
+ where: {
293
+ userId: ctx.session.user.id,
294
+ worksheetQuestionId: { in: questionIds },
295
+ },
296
+ });
297
+ return progress;
298
+ }),
299
+ // Update a worksheet
300
+ update: authedProcedure
301
+ .input(z.object({
302
+ id: z.string(),
303
+ title: z.string().min(1).max(120).optional(),
304
+ description: z.string().optional(),
305
+ difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
306
+ estimatedTime: z.string().optional(),
307
+ problems: z.array(z.object({
308
+ id: z.string().optional(),
309
+ question: z.string().min(1),
310
+ answer: z.string().min(1),
311
+ type: z.enum(['MULTIPLE_CHOICE', 'TEXT', 'NUMERIC', 'TRUE_FALSE', 'MATCHING', 'FILL_IN_THE_BLANK']).default('TEXT'),
312
+ options: z.array(z.string()).optional(),
313
+ })).optional(),
314
+ }))
315
+ .mutation(async ({ ctx, input }) => {
316
+ const { id, problems, ...updateData } = input;
317
+ // Verify worksheet ownership
318
+ const existingWorksheet = await ctx.db.artifact.findFirst({
319
+ where: {
320
+ id,
321
+ type: ArtifactType.WORKSHEET,
322
+ workspace: { ownerId: ctx.session.user.id },
323
+ },
324
+ });
325
+ if (!existingWorksheet)
326
+ throw new TRPCError({ code: 'NOT_FOUND' });
327
+ // Handle questions update if provided
328
+ if (problems) {
329
+ // Delete existing questions and create new ones
330
+ await ctx.db.worksheetQuestion.deleteMany({
331
+ where: { artifactId: id },
332
+ });
333
+ await ctx.db.worksheetQuestion.createMany({
334
+ data: problems.map((problem, index) => ({
335
+ artifactId: id,
336
+ prompt: problem.question,
337
+ answer: problem.answer,
338
+ type: problem.type,
339
+ order: index,
340
+ meta: problem.options ? { options: problem.options } : undefined,
341
+ })),
342
+ });
343
+ }
344
+ // Process update data
345
+ const processedUpdateData = {
346
+ ...updateData,
347
+ difficulty: updateData.difficulty,
348
+ };
349
+ return ctx.db.artifact.update({
350
+ where: { id },
351
+ data: processedUpdateData,
352
+ include: {
353
+ questions: {
354
+ orderBy: { order: 'asc' },
355
+ },
356
+ },
357
+ });
358
+ }),
128
359
  // Delete a worksheet set and its questions
129
- deleteSet: authedProcedure
130
- .input(z.object({ setId: z.string().uuid() }))
360
+ delete: authedProcedure
361
+ .input(z.object({ id: z.string() }))
131
362
  .mutation(async ({ ctx, input }) => {
132
363
  const deleted = await ctx.db.artifact.deleteMany({
133
- where: { id: input.setId, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } },
364
+ where: { id: input.id, type: ArtifactType.WORKSHEET, workspace: { ownerId: ctx.session.user.id } },
134
365
  });
135
366
  if (deleted.count === 0)
136
367
  throw new TRPCError({ code: 'NOT_FOUND' });
137
368
  return true;
138
369
  }),
370
+ // Generate a worksheet from a user prompt
371
+ generateFromPrompt: authedProcedure
372
+ .input(z.object({
373
+ workspaceId: z.string(),
374
+ prompt: z.string().min(1),
375
+ numQuestions: z.number().int().min(1).max(20).default(8),
376
+ difficulty: z.enum(['easy', 'medium', 'hard']).default('medium'),
377
+ title: z.string().optional(),
378
+ estimatedTime: z.string().optional(),
379
+ }))
380
+ .mutation(async ({ ctx, input }) => {
381
+ const workspace = await ctx.db.workspace.findFirst({ where: { id: input.workspaceId, ownerId: ctx.session.user.id } });
382
+ if (!workspace)
383
+ 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
+ const artifact = await ctx.db.artifact.create({
391
+ data: {
392
+ workspaceId: input.workspaceId,
393
+ type: ArtifactType.WORKSHEET,
394
+ title: input.title || `Worksheet - ${new Date().toLocaleString()}`,
395
+ createdById: ctx.session.user.id,
396
+ difficulty: (input.difficulty.toUpperCase()),
397
+ estimatedTime: input.estimatedTime,
398
+ },
399
+ });
400
+ 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);
406
+ }
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(' - ');
437
+ await ctx.db.worksheetQuestion.create({
438
+ data: {
439
+ artifactId: artifact.id,
440
+ prompt: q.trim(),
441
+ answer: a.trim(),
442
+ difficulty: (input.difficulty.toUpperCase()),
443
+ order: i,
444
+ meta: { type: 'TEXT' },
445
+ },
446
+ });
447
+ }
448
+ }
449
+ }
450
+ await PusherService.emitWorksheetComplete(input.workspaceId, artifact);
451
+ aiSessionService.deleteSession(session.id);
452
+ return { artifact };
453
+ }),
139
454
  });