@goscribe/server 1.3.0 → 1.3.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.
- package/dist/context.d.ts +5 -1
- package/dist/lib/activity_human_description.d.ts +13 -0
- package/dist/lib/activity_human_description.js +221 -0
- package/dist/lib/activity_human_description.test.d.ts +1 -0
- package/dist/lib/activity_human_description.test.js +16 -0
- package/dist/lib/activity_log_service.d.ts +87 -0
- package/dist/lib/activity_log_service.js +276 -0
- package/dist/lib/activity_log_service.test.d.ts +1 -0
- package/dist/lib/activity_log_service.test.js +27 -0
- package/dist/lib/ai-session.d.ts +15 -2
- package/dist/lib/ai-session.js +147 -85
- package/dist/lib/constants.d.ts +13 -0
- package/dist/lib/constants.js +12 -0
- package/dist/lib/email.d.ts +11 -0
- package/dist/lib/email.js +193 -0
- package/dist/lib/env.d.ts +13 -0
- package/dist/lib/env.js +16 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +3 -3
- package/dist/lib/logger.d.ts +4 -4
- package/dist/lib/logger.js +30 -8
- package/dist/lib/notification-service.d.ts +152 -0
- package/dist/lib/notification-service.js +473 -0
- package/dist/lib/notification-service.test.d.ts +1 -0
- package/dist/lib/notification-service.test.js +87 -0
- package/dist/lib/prisma.d.ts +2 -1
- package/dist/lib/prisma.js +5 -1
- package/dist/lib/pusher.d.ts +23 -0
- package/dist/lib/pusher.js +69 -5
- package/dist/lib/retry.d.ts +15 -0
- package/dist/lib/retry.js +37 -0
- package/dist/lib/storage.js +2 -2
- package/dist/lib/stripe.d.ts +9 -0
- package/dist/lib/stripe.js +36 -0
- package/dist/lib/subscription_service.d.ts +37 -0
- package/dist/lib/subscription_service.js +654 -0
- package/dist/lib/usage_service.d.ts +26 -0
- package/dist/lib/usage_service.js +59 -0
- package/dist/lib/worksheet-generation.d.ts +91 -0
- package/dist/lib/worksheet-generation.js +95 -0
- package/dist/lib/worksheet-generation.test.d.ts +1 -0
- package/dist/lib/worksheet-generation.test.js +20 -0
- package/dist/lib/workspace-access.d.ts +18 -0
- package/dist/lib/workspace-access.js +13 -0
- package/dist/routers/_app.d.ts +1349 -253
- package/dist/routers/_app.js +10 -0
- package/dist/routers/admin.d.ts +361 -0
- package/dist/routers/admin.js +633 -0
- package/dist/routers/annotations.d.ts +219 -0
- package/dist/routers/annotations.js +187 -0
- package/dist/routers/auth.d.ts +88 -7
- package/dist/routers/auth.js +339 -19
- package/dist/routers/chat.d.ts +6 -12
- package/dist/routers/copilot.d.ts +199 -0
- package/dist/routers/copilot.js +571 -0
- package/dist/routers/flashcards.d.ts +47 -81
- package/dist/routers/flashcards.js +143 -27
- package/dist/routers/members.d.ts +36 -7
- package/dist/routers/members.js +200 -19
- package/dist/routers/notifications.d.ts +99 -0
- package/dist/routers/notifications.js +127 -0
- package/dist/routers/payment.d.ts +89 -0
- package/dist/routers/payment.js +403 -0
- package/dist/routers/podcast.d.ts +8 -13
- package/dist/routers/podcast.js +54 -31
- package/dist/routers/studyguide.d.ts +1 -29
- package/dist/routers/studyguide.js +80 -71
- package/dist/routers/worksheets.d.ts +105 -38
- package/dist/routers/worksheets.js +258 -68
- package/dist/routers/workspace.d.ts +139 -60
- package/dist/routers/workspace.js +455 -315
- package/dist/scripts/purge-deleted-users.d.ts +1 -0
- package/dist/scripts/purge-deleted-users.js +149 -0
- package/dist/server.js +130 -10
- package/dist/services/flashcard-progress.service.d.ts +18 -66
- package/dist/services/flashcard-progress.service.js +51 -42
- package/dist/trpc.d.ts +20 -21
- package/dist/trpc.js +150 -1
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
|
3
|
-
import { router, authedProcedure } from '../trpc.js';
|
|
3
|
+
import { router, authedProcedure, limitedProcedure } from '../trpc.js';
|
|
4
4
|
import { aiSessionService } from '../lib/ai-session.js';
|
|
5
5
|
import PusherService from '../lib/pusher.js';
|
|
6
|
+
import { notifyArtifactFailed, notifyArtifactReady } from '../lib/notification-service.js';
|
|
6
7
|
import { logger } from '../lib/logger.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
8
|
+
import { ArtifactType } from '../lib/constants.js';
|
|
9
|
+
import { workspaceAccessFilter } from '../lib/workspace-access.js';
|
|
10
|
+
import { mergeWorksheetGenerationConfig, normalizeWorksheetProblemForDb, parsePresetConfig, worksheetModeSchema, worksheetPresetConfigPartialSchema, } from '../lib/worksheet-generation.js';
|
|
11
11
|
const Difficulty = {
|
|
12
12
|
EASY: 'EASY',
|
|
13
13
|
MEDIUM: 'MEDIUM',
|
|
@@ -69,8 +69,87 @@ export const worksheets = router({
|
|
|
69
69
|
}));
|
|
70
70
|
return merged;
|
|
71
71
|
}),
|
|
72
|
+
listPresets: authedProcedure
|
|
73
|
+
.input(z.object({ workspaceId: z.string() }))
|
|
74
|
+
.query(async ({ ctx, input }) => {
|
|
75
|
+
const ws = await ctx.db.workspace.findFirst({
|
|
76
|
+
where: { id: input.workspaceId, ...workspaceAccessFilter(ctx.session.user.id) },
|
|
77
|
+
});
|
|
78
|
+
if (!ws)
|
|
79
|
+
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
80
|
+
return ctx.db.worksheetPreset.findMany({
|
|
81
|
+
where: {
|
|
82
|
+
OR: [
|
|
83
|
+
{ isSystem: true },
|
|
84
|
+
{
|
|
85
|
+
userId: ctx.session.user.id,
|
|
86
|
+
OR: [{ workspaceId: input.workspaceId }, { workspaceId: null }],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
orderBy: [{ isSystem: 'desc' }, { name: 'asc' }],
|
|
91
|
+
});
|
|
92
|
+
}),
|
|
93
|
+
createPreset: authedProcedure
|
|
94
|
+
.input(z.object({
|
|
95
|
+
workspaceId: z.string().optional(),
|
|
96
|
+
name: z.string().min(1).max(120),
|
|
97
|
+
config: z.record(z.string(), z.unknown()),
|
|
98
|
+
}))
|
|
99
|
+
.mutation(async ({ ctx, input }) => {
|
|
100
|
+
if (input.workspaceId) {
|
|
101
|
+
const ws = await ctx.db.workspace.findFirst({
|
|
102
|
+
where: { id: input.workspaceId, ...workspaceAccessFilter(ctx.session.user.id) },
|
|
103
|
+
});
|
|
104
|
+
if (!ws)
|
|
105
|
+
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
106
|
+
}
|
|
107
|
+
const config = parsePresetConfig(input.config);
|
|
108
|
+
return ctx.db.worksheetPreset.create({
|
|
109
|
+
data: {
|
|
110
|
+
userId: ctx.session.user.id,
|
|
111
|
+
workspaceId: input.workspaceId ?? null,
|
|
112
|
+
name: input.name,
|
|
113
|
+
isSystem: false,
|
|
114
|
+
config: config,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}),
|
|
118
|
+
updatePreset: authedProcedure
|
|
119
|
+
.input(z.object({
|
|
120
|
+
id: z.string(),
|
|
121
|
+
name: z.string().min(1).max(120).optional(),
|
|
122
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
123
|
+
}).refine(d => d.name !== undefined || d.config !== undefined, { message: 'Provide name and/or config' }))
|
|
124
|
+
.mutation(async ({ ctx, input }) => {
|
|
125
|
+
const existing = await ctx.db.worksheetPreset.findFirst({
|
|
126
|
+
where: { id: input.id, userId: ctx.session.user.id, isSystem: false },
|
|
127
|
+
});
|
|
128
|
+
if (!existing)
|
|
129
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Preset not found or read-only' });
|
|
130
|
+
const data = {};
|
|
131
|
+
if (input.name !== undefined)
|
|
132
|
+
data.name = input.name;
|
|
133
|
+
if (input.config !== undefined)
|
|
134
|
+
data.config = parsePresetConfig(input.config);
|
|
135
|
+
return ctx.db.worksheetPreset.update({
|
|
136
|
+
where: { id: input.id },
|
|
137
|
+
data,
|
|
138
|
+
});
|
|
139
|
+
}),
|
|
140
|
+
deletePreset: authedProcedure
|
|
141
|
+
.input(z.object({ id: z.string() }))
|
|
142
|
+
.mutation(async ({ ctx, input }) => {
|
|
143
|
+
const existing = await ctx.db.worksheetPreset.findFirst({
|
|
144
|
+
where: { id: input.id, userId: ctx.session.user.id, isSystem: false },
|
|
145
|
+
});
|
|
146
|
+
if (!existing)
|
|
147
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Preset not found or read-only' });
|
|
148
|
+
await ctx.db.worksheetPreset.delete({ where: { id: input.id } });
|
|
149
|
+
return true;
|
|
150
|
+
}),
|
|
72
151
|
// Create a worksheet set
|
|
73
|
-
create:
|
|
152
|
+
create: limitedProcedure
|
|
74
153
|
.input(z.object({
|
|
75
154
|
workspaceId: z.string(),
|
|
76
155
|
title: z.string().min(1).max(120),
|
|
@@ -122,7 +201,7 @@ export const worksheets = router({
|
|
|
122
201
|
where: {
|
|
123
202
|
id: input.worksheetId,
|
|
124
203
|
type: ArtifactType.WORKSHEET,
|
|
125
|
-
workspace:
|
|
204
|
+
workspace: workspaceAccessFilter(ctx.session.user.id),
|
|
126
205
|
},
|
|
127
206
|
include: { questions: true },
|
|
128
207
|
orderBy: { updatedAt: 'desc' },
|
|
@@ -160,7 +239,7 @@ export const worksheets = router({
|
|
|
160
239
|
return merged;
|
|
161
240
|
}),
|
|
162
241
|
// Add a question to a worksheet
|
|
163
|
-
createWorksheetQuestion:
|
|
242
|
+
createWorksheetQuestion: limitedProcedure
|
|
164
243
|
.input(z.object({
|
|
165
244
|
worksheetId: z.string(),
|
|
166
245
|
prompt: z.string().min(1),
|
|
@@ -172,7 +251,7 @@ export const worksheets = router({
|
|
|
172
251
|
}))
|
|
173
252
|
.mutation(async ({ ctx, input }) => {
|
|
174
253
|
const worksheet = await ctx.db.artifact.findFirst({
|
|
175
|
-
where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace:
|
|
254
|
+
where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
176
255
|
});
|
|
177
256
|
if (!worksheet)
|
|
178
257
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
@@ -201,7 +280,7 @@ export const worksheets = router({
|
|
|
201
280
|
}))
|
|
202
281
|
.mutation(async ({ ctx, input }) => {
|
|
203
282
|
const q = await ctx.db.worksheetQuestion.findFirst({
|
|
204
|
-
where: { id: input.worksheetQuestionId, artifact: { type: ArtifactType.WORKSHEET, workspace:
|
|
283
|
+
where: { id: input.worksheetQuestionId, artifact: { type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) } },
|
|
205
284
|
});
|
|
206
285
|
if (!q)
|
|
207
286
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
@@ -222,7 +301,7 @@ export const worksheets = router({
|
|
|
222
301
|
.input(z.object({ worksheetQuestionId: z.string() }))
|
|
223
302
|
.mutation(async ({ ctx, input }) => {
|
|
224
303
|
const q = await ctx.db.worksheetQuestion.findFirst({
|
|
225
|
-
where: { id: input.worksheetQuestionId, artifact: { workspace:
|
|
304
|
+
where: { id: input.worksheetQuestionId, artifact: { workspace: workspaceAccessFilter(ctx.session.user.id) } },
|
|
226
305
|
});
|
|
227
306
|
if (!q)
|
|
228
307
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
@@ -244,7 +323,7 @@ export const worksheets = router({
|
|
|
244
323
|
id: input.problemId,
|
|
245
324
|
artifact: {
|
|
246
325
|
type: ArtifactType.WORKSHEET,
|
|
247
|
-
workspace:
|
|
326
|
+
workspace: workspaceAccessFilter(ctx.session.user.id),
|
|
248
327
|
},
|
|
249
328
|
},
|
|
250
329
|
});
|
|
@@ -286,7 +365,7 @@ export const worksheets = router({
|
|
|
286
365
|
where: {
|
|
287
366
|
id: input.worksheetId,
|
|
288
367
|
type: ArtifactType.WORKSHEET,
|
|
289
|
-
workspace:
|
|
368
|
+
workspace: workspaceAccessFilter(ctx.session.user.id),
|
|
290
369
|
},
|
|
291
370
|
});
|
|
292
371
|
if (!worksheet)
|
|
@@ -327,7 +406,7 @@ export const worksheets = router({
|
|
|
327
406
|
where: {
|
|
328
407
|
id,
|
|
329
408
|
type: ArtifactType.WORKSHEET,
|
|
330
|
-
workspace:
|
|
409
|
+
workspace: workspaceAccessFilter(ctx.session.user.id),
|
|
331
410
|
},
|
|
332
411
|
});
|
|
333
412
|
if (!existingWorksheet)
|
|
@@ -369,93 +448,204 @@ export const worksheets = router({
|
|
|
369
448
|
.input(z.object({ id: z.string() }))
|
|
370
449
|
.mutation(async ({ ctx, input }) => {
|
|
371
450
|
const deleted = await ctx.db.artifact.deleteMany({
|
|
372
|
-
where: { id: input.id, type: ArtifactType.WORKSHEET, workspace:
|
|
451
|
+
where: { id: input.id, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
373
452
|
});
|
|
374
453
|
if (deleted.count === 0)
|
|
375
454
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
376
455
|
return true;
|
|
377
456
|
}),
|
|
378
457
|
// Generate a worksheet from a user prompt
|
|
379
|
-
generateFromPrompt:
|
|
458
|
+
generateFromPrompt: limitedProcedure
|
|
380
459
|
.input(z.object({
|
|
381
460
|
workspaceId: z.string(),
|
|
382
461
|
prompt: z.string().min(1),
|
|
383
462
|
numQuestions: z.number().int().min(1).max(20).default(8),
|
|
384
463
|
difficulty: z.enum(['easy', 'medium', 'hard']).default('medium'),
|
|
464
|
+
mode: worksheetModeSchema.optional(),
|
|
465
|
+
presetId: z.string().optional(),
|
|
466
|
+
configOverride: worksheetPresetConfigPartialSchema.optional(),
|
|
385
467
|
title: z.string().optional(),
|
|
386
468
|
estimatedTime: z.string().optional(),
|
|
387
469
|
}))
|
|
388
470
|
.mutation(async ({ ctx, input }) => {
|
|
389
|
-
const workspace = await ctx.db.workspace.findFirst({
|
|
471
|
+
const workspace = await ctx.db.workspace.findFirst({
|
|
472
|
+
where: { id: input.workspaceId, ...workspaceAccessFilter(ctx.session.user.id) },
|
|
473
|
+
});
|
|
390
474
|
if (!workspace)
|
|
391
475
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
476
|
+
let presetRow = null;
|
|
477
|
+
if (input.presetId) {
|
|
478
|
+
presetRow = await ctx.db.worksheetPreset.findFirst({
|
|
479
|
+
where: {
|
|
480
|
+
id: input.presetId,
|
|
481
|
+
OR: [
|
|
482
|
+
{ isSystem: true },
|
|
483
|
+
{
|
|
484
|
+
userId: ctx.session.user.id,
|
|
485
|
+
OR: [{ workspaceId: input.workspaceId }, { workspaceId: null }],
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
if (!presetRow)
|
|
491
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Preset not found' });
|
|
492
|
+
}
|
|
493
|
+
const presetConfig = presetRow?.config
|
|
494
|
+
? parsePresetConfig(presetRow.config)
|
|
495
|
+
: undefined;
|
|
496
|
+
const resolved = mergeWorksheetGenerationConfig(presetConfig, input.configOverride ?? undefined, {
|
|
497
|
+
numQuestions: input.numQuestions,
|
|
498
|
+
difficulty: input.difficulty,
|
|
499
|
+
mode: input.mode,
|
|
500
|
+
});
|
|
501
|
+
const difficultyUpper = resolved.difficulty.toUpperCase();
|
|
502
|
+
console.log("[DEBUG TRPC PAYLOAD] input =", input);
|
|
503
|
+
console.log("[DEBUG TRPC PAYLOAD] presetConfig =", presetConfig);
|
|
504
|
+
console.log("[DEBUG TRPC PAYLOAD] legacy merged legacy values:", { numQuestions: input.numQuestions, difficulty: input.difficulty, mode: input.mode });
|
|
505
|
+
console.log("[DEBUG TRPC PAYLOAD] RESOLVED =", resolved);
|
|
506
|
+
const worksheetConfigSnapshot = {
|
|
507
|
+
mode: resolved.mode,
|
|
508
|
+
presetId: input.presetId ?? null,
|
|
509
|
+
presetName: presetRow?.name ?? null,
|
|
510
|
+
numQuestions: resolved.numQuestions,
|
|
511
|
+
difficulty: resolved.difficulty,
|
|
512
|
+
mcqRatio: resolved.mcqRatio ?? null,
|
|
513
|
+
questionTypes: resolved.questionTypes ?? null,
|
|
514
|
+
};
|
|
515
|
+
await PusherService.emitWorksheetGenerationStart(input.workspaceId);
|
|
392
516
|
const artifact = await ctx.db.artifact.create({
|
|
393
517
|
data: {
|
|
394
518
|
workspaceId: input.workspaceId,
|
|
395
519
|
type: ArtifactType.WORKSHEET,
|
|
396
|
-
title: input.title || `Worksheet - ${new Date().toLocaleString()}
|
|
520
|
+
title: input.title || (resolved.mode === 'quiz' ? `Quiz - ${new Date().toLocaleString()}` : `Worksheet - ${new Date().toLocaleString()}`),
|
|
397
521
|
createdById: ctx.session.user.id,
|
|
398
|
-
difficulty:
|
|
522
|
+
difficulty: difficultyUpper,
|
|
399
523
|
estimatedTime: input.estimatedTime,
|
|
400
524
|
generating: true,
|
|
401
|
-
generatingMetadata: {
|
|
525
|
+
generatingMetadata: {
|
|
526
|
+
quantity: resolved.numQuestions,
|
|
527
|
+
difficulty: resolved.difficulty,
|
|
528
|
+
mode: resolved.mode,
|
|
529
|
+
presetId: input.presetId ?? null,
|
|
530
|
+
},
|
|
531
|
+
worksheetConfig: worksheetConfigSnapshot,
|
|
402
532
|
},
|
|
403
533
|
});
|
|
404
|
-
await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength:
|
|
405
|
-
|
|
406
|
-
|
|
534
|
+
await PusherService.emitTaskComplete(input.workspaceId, 'worksheet_info', { contentLength: resolved.numQuestions });
|
|
535
|
+
await PusherService.emitWorksheetNew(input.workspaceId, artifact);
|
|
536
|
+
const userId = ctx.session.user.id;
|
|
537
|
+
const workspaceId = input.workspaceId;
|
|
538
|
+
const promptText = input.prompt;
|
|
539
|
+
const estTime = input.estimatedTime;
|
|
540
|
+
// Launch generation in the background to free up the connection pool immediately
|
|
541
|
+
(async () => {
|
|
407
542
|
try {
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
543
|
+
const content = await aiSessionService.generateWorksheetQuestions(workspaceId, userId, resolved.numQuestions, difficultyUpper, {
|
|
544
|
+
mode: resolved.mode,
|
|
545
|
+
mcqRatio: resolved.mcqRatio,
|
|
546
|
+
questionTypes: resolved.questionTypes,
|
|
547
|
+
prompt: promptText,
|
|
548
|
+
});
|
|
549
|
+
let problems = [];
|
|
550
|
+
try {
|
|
551
|
+
const worksheetData = JSON.parse(content);
|
|
552
|
+
let actualWorksheetData = worksheetData;
|
|
553
|
+
if (worksheetData.last_response) {
|
|
554
|
+
try {
|
|
555
|
+
actualWorksheetData = JSON.parse(worksheetData.last_response);
|
|
556
|
+
}
|
|
557
|
+
catch { /* noop */ }
|
|
413
558
|
}
|
|
414
|
-
|
|
559
|
+
problems = actualWorksheetData.problems || actualWorksheetData.questions || actualWorksheetData || [];
|
|
560
|
+
if (!Array.isArray(problems))
|
|
561
|
+
problems = [];
|
|
415
562
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
563
|
+
catch (parseError) {
|
|
564
|
+
logger.error('Failed to parse worksheet JSON', parseError);
|
|
565
|
+
throw new Error('Failed to parse worksheet JSON');
|
|
566
|
+
}
|
|
567
|
+
const forceMcq = resolved.mode === 'quiz';
|
|
568
|
+
const questionsToCreate = [];
|
|
569
|
+
for (let i = 0; i < Math.min(problems.length, resolved.numQuestions); i++) {
|
|
570
|
+
const problem = problems[i] && typeof problems[i] === 'object' ? problems[i] : {};
|
|
571
|
+
const row = normalizeWorksheetProblemForDb(problem, i, difficultyUpper, forceMcq);
|
|
572
|
+
const metaPayload = {};
|
|
573
|
+
if (row.meta.options?.length)
|
|
574
|
+
metaPayload.options = row.meta.options;
|
|
575
|
+
if (row.meta.markScheme != null)
|
|
576
|
+
metaPayload.markScheme = row.meta.markScheme;
|
|
577
|
+
questionsToCreate.push({
|
|
578
|
+
artifactId: artifact.id,
|
|
579
|
+
prompt: row.prompt,
|
|
580
|
+
answer: row.answer ?? '',
|
|
581
|
+
difficulty: row.difficulty,
|
|
582
|
+
order: row.order,
|
|
583
|
+
type: row.type,
|
|
584
|
+
meta: Object.keys(metaPayload).length ? metaPayload : undefined,
|
|
436
585
|
});
|
|
437
586
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
587
|
+
if (questionsToCreate.length > 0) {
|
|
588
|
+
await ctx.db.worksheetQuestion.createMany({
|
|
589
|
+
data: questionsToCreate,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
let parsedTitle;
|
|
593
|
+
let parsedDescription;
|
|
594
|
+
let parsedEstimated;
|
|
595
|
+
try {
|
|
596
|
+
const worksheetData = JSON.parse(content);
|
|
597
|
+
let actualWorksheetData = worksheetData;
|
|
598
|
+
if (worksheetData.last_response) {
|
|
599
|
+
try {
|
|
600
|
+
actualWorksheetData = JSON.parse(worksheetData.last_response);
|
|
601
|
+
}
|
|
602
|
+
catch { /* noop */ }
|
|
603
|
+
}
|
|
604
|
+
parsedTitle = typeof actualWorksheetData.title === 'string' ? actualWorksheetData.title : undefined;
|
|
605
|
+
parsedDescription = typeof actualWorksheetData.description === 'string' ? actualWorksheetData.description : undefined;
|
|
606
|
+
parsedEstimated = typeof actualWorksheetData.estimatedTime === 'string' ? actualWorksheetData.estimatedTime : undefined;
|
|
607
|
+
}
|
|
608
|
+
catch { /* noop */ }
|
|
609
|
+
await ctx.db.artifact.update({
|
|
610
|
+
where: { id: artifact.id },
|
|
611
|
+
data: {
|
|
612
|
+
generating: false,
|
|
613
|
+
title: parsedTitle || artifact.title,
|
|
614
|
+
description: parsedDescription ?? undefined,
|
|
615
|
+
estimatedTime: estTime || parsedEstimated || undefined,
|
|
616
|
+
worksheetConfig: worksheetConfigSnapshot,
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
const updatedWorksheet = await ctx.db.artifact.findFirst({
|
|
442
620
|
where: { id: artifact.id },
|
|
621
|
+
include: { questions: true }
|
|
443
622
|
});
|
|
444
|
-
|
|
623
|
+
await PusherService.emitWorksheetGenerationComplete(workspaceId, updatedWorksheet);
|
|
624
|
+
await PusherService.emitWorksheetComplete(workspaceId, artifact);
|
|
625
|
+
await notifyArtifactReady(ctx.db, {
|
|
626
|
+
userId,
|
|
627
|
+
workspaceId,
|
|
628
|
+
artifactId: artifact.id,
|
|
629
|
+
artifactType: ArtifactType.WORKSHEET,
|
|
630
|
+
title: updatedWorksheet?.title ?? artifact.title,
|
|
631
|
+
}).catch(() => { });
|
|
445
632
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
633
|
+
catch (error) {
|
|
634
|
+
logger.error(`Background generation failed for artifact ${artifact.id}:`, error);
|
|
635
|
+
await notifyArtifactFailed(ctx.db, {
|
|
636
|
+
userId,
|
|
637
|
+
workspaceId,
|
|
638
|
+
artifactId: artifact.id,
|
|
639
|
+
artifactType: ArtifactType.WORKSHEET,
|
|
640
|
+
title: artifact.title,
|
|
641
|
+
message: `Failed to generate worksheet: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
642
|
+
}).catch(() => { });
|
|
643
|
+
await ctx.db.artifact.deleteMany({
|
|
644
|
+
where: { id: artifact.id },
|
|
645
|
+
}).catch(() => { }); // Ignore delete errors if already gone
|
|
646
|
+
await PusherService.emitError(workspaceId, `Failed to generate worksheet: ${error instanceof Error ? error.message : 'Unknown error'}`, 'worksheet_generation');
|
|
647
|
+
}
|
|
648
|
+
})();
|
|
459
649
|
return { artifact };
|
|
460
650
|
}),
|
|
461
651
|
checkAnswer: authedProcedure
|
|
@@ -465,7 +655,7 @@ export const worksheets = router({
|
|
|
465
655
|
answer: z.string().min(1),
|
|
466
656
|
}))
|
|
467
657
|
.mutation(async ({ ctx, input }) => {
|
|
468
|
-
const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace:
|
|
658
|
+
const worksheet = await ctx.db.artifact.findFirst({ where: { id: input.worksheetId, type: ArtifactType.WORKSHEET, workspace: workspaceAccessFilter(ctx.session.user.id) }, include: { workspace: true } });
|
|
469
659
|
if (!worksheet)
|
|
470
660
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
471
661
|
const question = await ctx.db.worksheetQuestion.findFirst({ where: { id: input.questionId, artifactId: input.worksheetId } });
|