@goscribe/server 1.2.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/check-difficulty.cjs +14 -0
- package/check-questions.cjs +14 -0
- package/db-summary.cjs +22 -0
- 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/mcq-test.cjs +36 -0
- package/package.json +9 -2
- package/prisma/migrations/20260413143206_init/migration.sql +873 -0
- package/prisma/schema.prisma +471 -324
- package/src/context.ts +4 -1
- package/src/lib/activity_human_description.test.ts +28 -0
- package/src/lib/activity_human_description.ts +239 -0
- package/src/lib/activity_log_service.test.ts +37 -0
- package/src/lib/activity_log_service.ts +353 -0
- package/src/lib/ai-session.ts +79 -51
- package/src/lib/email.ts +213 -29
- package/src/lib/env.ts +23 -6
- package/src/lib/inference.ts +2 -2
- package/src/lib/notification-service.test.ts +106 -0
- package/src/lib/notification-service.ts +677 -0
- package/src/lib/prisma.ts +6 -1
- package/src/lib/pusher.ts +86 -2
- package/src/lib/stripe.ts +39 -0
- package/src/lib/subscription_service.ts +722 -0
- package/src/lib/usage_service.ts +74 -0
- package/src/lib/worksheet-generation.test.ts +31 -0
- package/src/lib/worksheet-generation.ts +139 -0
- package/src/routers/_app.ts +9 -0
- package/src/routers/admin.ts +710 -0
- package/src/routers/annotations.ts +41 -0
- package/src/routers/auth.ts +338 -28
- package/src/routers/copilot.ts +719 -0
- package/src/routers/flashcards.ts +201 -68
- package/src/routers/members.ts +280 -80
- package/src/routers/notifications.ts +142 -0
- package/src/routers/payment.ts +448 -0
- package/src/routers/podcast.ts +112 -83
- package/src/routers/studyguide.ts +12 -0
- package/src/routers/worksheets.ts +289 -66
- package/src/routers/workspace.ts +329 -122
- package/src/scripts/purge-deleted-users.ts +167 -0
- package/src/server.ts +137 -11
- package/src/services/flashcard-progress.service.ts +49 -37
- package/src/trpc.ts +184 -5
- package/test-generate.js +30 -0
- package/test-ratio.cjs +9 -0
- package/zod-test.cjs +22 -0
- package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
- package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
- package/prisma/seed.mjs +0 -135
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
|
3
|
-
import { router, authedProcedure } from '../trpc.js';
|
|
3
|
+
import { router, authedProcedure, verifiedProcedure,limitedProcedure } from '../trpc.js';
|
|
4
4
|
import createInferenceService from '../lib/inference.js';
|
|
5
5
|
import { aiSessionService } from '../lib/ai-session.js';
|
|
6
6
|
import PusherService from '../lib/pusher.js';
|
|
7
|
+
import { notifyArtifactFailed, notifyArtifactReady } from '../lib/notification-service.js';
|
|
7
8
|
import { createFlashcardProgressService } from '../services/flashcard-progress.service.js';
|
|
8
9
|
import { ArtifactType } from '../lib/constants.js';
|
|
9
10
|
import { workspaceAccessFilter } from '../lib/workspace-access.js';
|
|
11
|
+
import inference from '../lib/inference.js';
|
|
12
|
+
|
|
13
|
+
const typedAnswerGradeSchema = z.object({
|
|
14
|
+
isCorrect: z.boolean(),
|
|
15
|
+
confidence: z.number().min(0).max(1),
|
|
16
|
+
reason: z.string().min(1),
|
|
17
|
+
matchedAnswer: z.string().nullable(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function normalizeAcceptedAnswers(answers?: string[]): string[] {
|
|
21
|
+
if (!answers || answers.length === 0) return [];
|
|
22
|
+
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
const normalized: string[] = [];
|
|
25
|
+
|
|
26
|
+
for (const answer of answers) {
|
|
27
|
+
const trimmed = answer.trim();
|
|
28
|
+
if (!trimmed) continue;
|
|
29
|
+
const key = trimmed.toLowerCase();
|
|
30
|
+
if (seen.has(key)) continue;
|
|
31
|
+
seen.add(key);
|
|
32
|
+
normalized.push(trimmed);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractFirstJsonObject(text: string): string | null {
|
|
39
|
+
const start = text.indexOf('{');
|
|
40
|
+
const end = text.lastIndexOf('}');
|
|
41
|
+
if (start === -1 || end === -1 || end <= start) return null;
|
|
42
|
+
return text.slice(start, end + 1);
|
|
43
|
+
}
|
|
10
44
|
|
|
11
45
|
export const flashcards = router({
|
|
12
46
|
listSets: authedProcedure
|
|
@@ -44,7 +78,7 @@ export const flashcards = router({
|
|
|
44
78
|
},
|
|
45
79
|
|
|
46
80
|
},
|
|
47
|
-
orderBy: {
|
|
81
|
+
orderBy: { createdAt: 'desc' },
|
|
48
82
|
});
|
|
49
83
|
if (!set) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
50
84
|
return set.flashcards;
|
|
@@ -53,23 +87,27 @@ export const flashcards = router({
|
|
|
53
87
|
.input(z.object({ workspaceId: z.string() }))
|
|
54
88
|
.query(async ({ ctx, input }) => {
|
|
55
89
|
const artifact = await ctx.db.artifact.findFirst({
|
|
56
|
-
where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
90
|
+
where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
91
|
+
orderBy: { createdAt: 'desc' },
|
|
57
92
|
});
|
|
58
93
|
return artifact?.generating;
|
|
59
94
|
}),
|
|
60
|
-
createCard:
|
|
95
|
+
createCard: limitedProcedure
|
|
61
96
|
.input(z.object({
|
|
62
97
|
workspaceId: z.string(),
|
|
63
98
|
front: z.string().min(1),
|
|
64
99
|
back: z.string().min(1),
|
|
100
|
+
acceptedAnswers: z.array(z.string()).optional(),
|
|
65
101
|
tags: z.array(z.string()).optional(),
|
|
66
102
|
order: z.number().int().optional(),
|
|
67
103
|
}))
|
|
68
104
|
.mutation(async ({ ctx, input }) => {
|
|
69
105
|
const set = await ctx.db.artifact.findFirst({
|
|
70
|
-
where: {
|
|
71
|
-
|
|
72
|
-
|
|
106
|
+
where: {
|
|
107
|
+
type: ArtifactType.FLASHCARD_SET, workspace: {
|
|
108
|
+
id: input.workspaceId,
|
|
109
|
+
}
|
|
110
|
+
},
|
|
73
111
|
include: {
|
|
74
112
|
flashcards: true,
|
|
75
113
|
},
|
|
@@ -81,6 +119,7 @@ export const flashcards = router({
|
|
|
81
119
|
artifactId: set.id,
|
|
82
120
|
front: input.front,
|
|
83
121
|
back: input.back,
|
|
122
|
+
acceptedAnswers: normalizeAcceptedAnswers(input.acceptedAnswers),
|
|
84
123
|
tags: input.tags ?? [],
|
|
85
124
|
order: input.order ?? 0,
|
|
86
125
|
},
|
|
@@ -92,6 +131,7 @@ export const flashcards = router({
|
|
|
92
131
|
cardId: z.string(),
|
|
93
132
|
front: z.string().optional(),
|
|
94
133
|
back: z.string().optional(),
|
|
134
|
+
acceptedAnswers: z.array(z.string()).optional(),
|
|
95
135
|
tags: z.array(z.string()).optional(),
|
|
96
136
|
order: z.number().int().optional(),
|
|
97
137
|
}))
|
|
@@ -105,12 +145,83 @@ export const flashcards = router({
|
|
|
105
145
|
data: {
|
|
106
146
|
front: input.front ?? card.front,
|
|
107
147
|
back: input.back ?? card.back,
|
|
148
|
+
acceptedAnswers: input.acceptedAnswers ? normalizeAcceptedAnswers(input.acceptedAnswers) : card.acceptedAnswers,
|
|
108
149
|
tags: input.tags ?? card.tags,
|
|
109
150
|
order: input.order ?? card.order,
|
|
110
151
|
},
|
|
111
152
|
});
|
|
112
153
|
}),
|
|
113
154
|
|
|
155
|
+
gradeTypedAnswer: authedProcedure
|
|
156
|
+
.input(z.object({
|
|
157
|
+
flashcardId: z.string().cuid(),
|
|
158
|
+
userAnswer: z.string().min(1),
|
|
159
|
+
}))
|
|
160
|
+
.mutation(async ({ ctx, input }) => {
|
|
161
|
+
const flashcard = await ctx.db.flashcard.findFirst({
|
|
162
|
+
where: {
|
|
163
|
+
id: input.flashcardId,
|
|
164
|
+
artifact: {
|
|
165
|
+
type: ArtifactType.FLASHCARD_SET,
|
|
166
|
+
workspace: workspaceAccessFilter(ctx.session.user.id),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
select: {
|
|
170
|
+
id: true,
|
|
171
|
+
front: true,
|
|
172
|
+
back: true,
|
|
173
|
+
acceptedAnswers: true,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!flashcard) {
|
|
178
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Flashcard not found' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const acceptedAnswers = [
|
|
182
|
+
flashcard.back,
|
|
183
|
+
...normalizeAcceptedAnswers(flashcard.acceptedAnswers),
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const prompt = [
|
|
187
|
+
'Grade whether the student answer is semantically correct for the flashcard.',
|
|
188
|
+
'',
|
|
189
|
+
'Return ONLY valid JSON with this exact shape:',
|
|
190
|
+
'{"isCorrect": boolean, "confidence": number, "reason": string, "matchedAnswer": string | null}',
|
|
191
|
+
'',
|
|
192
|
+
'Rules:',
|
|
193
|
+
'- Accept synonyms, short paraphrases, and equivalent wording.',
|
|
194
|
+
'- Reject answers that are contradictory, unrelated, or materially incomplete.',
|
|
195
|
+
'- confidence must be from 0 to 1.',
|
|
196
|
+
'- reason must be under 160 characters.',
|
|
197
|
+
'- matchedAnswer must be the matched canonical/alias answer, or null if incorrect.',
|
|
198
|
+
'',
|
|
199
|
+
`Question: ${flashcard.front}`,
|
|
200
|
+
`Canonical answer: ${flashcard.back}`,
|
|
201
|
+
`Accepted aliases: ${JSON.stringify(acceptedAnswers)}`,
|
|
202
|
+
`Student answer: ${input.userAnswer}`,
|
|
203
|
+
].join('\n');
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const response = await inference([{ role: 'user', content: prompt }]);
|
|
207
|
+
const content = response.choices?.[0]?.message?.content ?? '';
|
|
208
|
+
const jsonCandidate = extractFirstJsonObject(content);
|
|
209
|
+
|
|
210
|
+
if (!jsonCandidate) {
|
|
211
|
+
throw new Error('No JSON object found in grading response');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const parsed = JSON.parse(jsonCandidate);
|
|
215
|
+
return typedAnswerGradeSchema.parse(parsed);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
throw new TRPCError({
|
|
218
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
219
|
+
message: 'Failed to grade typed answer. Please retry.',
|
|
220
|
+
cause: error,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}),
|
|
224
|
+
|
|
114
225
|
deleteCard: authedProcedure
|
|
115
226
|
.input(z.object({ cardId: z.string() }))
|
|
116
227
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -133,7 +244,7 @@ export const flashcards = router({
|
|
|
133
244
|
}),
|
|
134
245
|
|
|
135
246
|
// Generate a flashcard set from a user prompt
|
|
136
|
-
generateFromPrompt:
|
|
247
|
+
generateFromPrompt: limitedProcedure
|
|
137
248
|
.input(z.object({
|
|
138
249
|
workspaceId: z.string(),
|
|
139
250
|
prompt: z.string().min(1),
|
|
@@ -164,84 +275,106 @@ export const flashcards = router({
|
|
|
164
275
|
});
|
|
165
276
|
|
|
166
277
|
try {
|
|
167
|
-
|
|
168
|
-
where: { id: flashcardCurrent?.id },
|
|
169
|
-
data: { generating: true, generatingMetadata: { quantity: input.numCards, difficulty: input.difficulty.toLowerCase() } },
|
|
170
|
-
});
|
|
278
|
+
await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_info', { status: 'generating', numCards: input.numCards, difficulty: input.difficulty });
|
|
171
279
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
280
|
+
const artifact = await ctx.db.artifact.create({
|
|
281
|
+
data: {
|
|
282
|
+
workspaceId: input.workspaceId,
|
|
283
|
+
type: ArtifactType.FLASHCARD_SET,
|
|
284
|
+
title: input.title || `Flashcards - ${new Date().toLocaleString()}`,
|
|
285
|
+
createdById: ctx.session.user.id,
|
|
286
|
+
generating: true,
|
|
287
|
+
generatingMetadata: { quantity: input.numCards, difficulty: input.difficulty.toLowerCase() },
|
|
288
|
+
flashcards: {
|
|
289
|
+
create: flashcardCurrent?.flashcards.map((card) => ({
|
|
290
|
+
front: card.front,
|
|
291
|
+
back: card.back,
|
|
292
|
+
})),
|
|
293
|
+
},
|
|
185
294
|
},
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const currentCards = flashcardCurrent?.flashcards.length || 0;
|
|
190
|
-
const newCards = input.numCards - currentCards;
|
|
295
|
+
});
|
|
191
296
|
|
|
297
|
+
const currentCards = flashcardCurrent?.flashcards.length || 0;
|
|
298
|
+
const newCards = input.numCards - currentCards;
|
|
192
299
|
|
|
193
|
-
// Generate
|
|
194
|
-
const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, ctx.session.user.id, input.numCards, input.difficulty);
|
|
195
300
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
order: i,
|
|
209
|
-
tags: input.tags ?? ['ai-generated', input.difficulty],
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
createdCards++;
|
|
213
|
-
}
|
|
214
|
-
} catch {
|
|
215
|
-
// Fallback to text parsing if JSON fails
|
|
216
|
-
const lines = content.split('\n').filter(line => line.trim());
|
|
217
|
-
for (let i = 0; i < Math.min(lines.length, input.numCards); i++) {
|
|
218
|
-
const line = lines[i];
|
|
219
|
-
if (line.includes(' - ')) {
|
|
220
|
-
const [front, back] = line.split(' - ');
|
|
301
|
+
// Generate
|
|
302
|
+
const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, ctx.session.user.id, input.numCards, input.difficulty, input.prompt);
|
|
303
|
+
|
|
304
|
+
let createdCards = 0;
|
|
305
|
+
try {
|
|
306
|
+
const parsed = typeof content === 'string' ? JSON.parse(content) : content;
|
|
307
|
+
const flashcardData = Array.isArray(parsed) ? parsed : (parsed.flashcards || []);
|
|
308
|
+
|
|
309
|
+
for (let i = 0; i < Math.min(flashcardData.length, input.numCards); i++) {
|
|
310
|
+
const card = flashcardData[i];
|
|
311
|
+
const front = card.term || card.front || card.question || card.prompt || `Question ${i + 1}`;
|
|
312
|
+
const back = card.definition || card.back || card.answer || card.solution || `Answer ${i + 1}`;
|
|
221
313
|
await ctx.db.flashcard.create({
|
|
222
314
|
data: {
|
|
223
315
|
artifactId: artifact.id,
|
|
224
|
-
front
|
|
225
|
-
back
|
|
316
|
+
front,
|
|
317
|
+
back,
|
|
226
318
|
order: i,
|
|
227
319
|
tags: input.tags ?? ['ai-generated', input.difficulty],
|
|
228
320
|
},
|
|
229
321
|
});
|
|
230
322
|
createdCards++;
|
|
231
323
|
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error("Failed to parse flashcard JSON or create cards:", error);
|
|
326
|
+
// Fallback to text parsing if JSON fails
|
|
327
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
328
|
+
for (let i = 0; i < Math.min(lines.length, input.numCards); i++) {
|
|
329
|
+
const line = lines[i];
|
|
330
|
+
if (line.includes(' - ')) {
|
|
331
|
+
const [front, back] = line.split(' - ');
|
|
332
|
+
await ctx.db.flashcard.create({
|
|
333
|
+
data: {
|
|
334
|
+
artifactId: artifact.id,
|
|
335
|
+
front: front.trim(),
|
|
336
|
+
back: back.trim(),
|
|
337
|
+
order: i,
|
|
338
|
+
tags: input.tags ?? ['ai-generated', input.difficulty],
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
createdCards++;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
232
344
|
}
|
|
233
|
-
}
|
|
234
345
|
|
|
235
|
-
|
|
236
|
-
|
|
346
|
+
// Pusher complete
|
|
347
|
+
await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
|
|
348
|
+
|
|
349
|
+
// Set generating to false on the artifact
|
|
350
|
+
await ctx.db.artifact.update({ where: { id: artifact.id }, data: { generating: false } });
|
|
351
|
+
|
|
352
|
+
await notifyArtifactReady(ctx.db, {
|
|
353
|
+
userId: ctx.session.user.id,
|
|
354
|
+
workspaceId: input.workspaceId,
|
|
355
|
+
artifactId: artifact.id,
|
|
356
|
+
artifactType: ArtifactType.FLASHCARD_SET,
|
|
357
|
+
title: artifact.title,
|
|
358
|
+
}).catch(() => {});
|
|
237
359
|
|
|
238
|
-
|
|
360
|
+
return { artifact, createdCards };
|
|
239
361
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (flashcardCurrent?.id) {
|
|
364
|
+
await ctx.db.artifact.update({ where: { id: flashcardCurrent.id }, data: { generating: false } });
|
|
365
|
+
}
|
|
366
|
+
await PusherService.emitError(input.workspaceId, `Failed to generate flashcards: ${error}`, 'flash_card_generation');
|
|
367
|
+
await notifyArtifactFailed(ctx.db, {
|
|
368
|
+
userId: ctx.session.user.id,
|
|
369
|
+
workspaceId: input.workspaceId,
|
|
370
|
+
artifactType: ArtifactType.FLASHCARD_SET,
|
|
371
|
+
message:
|
|
372
|
+
error instanceof Error
|
|
373
|
+
? error.message
|
|
374
|
+
: 'Flashcard generation failed.',
|
|
375
|
+
}).catch(() => {});
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
245
378
|
}),
|
|
246
379
|
|
|
247
380
|
// Record study attempt
|