@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,11 +1,5 @@
|
|
|
1
1
|
export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
2
|
-
ctx:
|
|
3
|
-
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
4
|
-
session: any;
|
|
5
|
-
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
6
|
-
res: import("express").Response<any, Record<string, any>>;
|
|
7
|
-
cookies: Record<string, string | undefined>;
|
|
8
|
-
};
|
|
2
|
+
ctx: import("../context.js").Context;
|
|
9
3
|
meta: object;
|
|
10
4
|
errorShape: {
|
|
11
5
|
data: {
|
|
@@ -38,17 +32,18 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
38
32
|
id: string;
|
|
39
33
|
createdAt: Date;
|
|
40
34
|
updatedAt: Date;
|
|
41
|
-
title: string;
|
|
42
|
-
description: string | null;
|
|
43
35
|
workspaceId: string;
|
|
44
36
|
type: import("@prisma/client").$Enums.ArtifactType;
|
|
37
|
+
title: string;
|
|
45
38
|
isArchived: boolean;
|
|
46
|
-
generating: boolean;
|
|
47
|
-
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
48
39
|
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
49
40
|
estimatedTime: string | null;
|
|
50
|
-
imageObjectKey: string | null;
|
|
51
41
|
createdById: string | null;
|
|
42
|
+
description: string | null;
|
|
43
|
+
generating: boolean;
|
|
44
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
45
|
+
worksheetConfig: import("@prisma/client/runtime/library").JsonValue | null;
|
|
46
|
+
imageObjectKey: string | null;
|
|
52
47
|
})[];
|
|
53
48
|
meta: object;
|
|
54
49
|
}>;
|
|
@@ -82,6 +77,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
82
77
|
front: string;
|
|
83
78
|
back: string;
|
|
84
79
|
tags: string[];
|
|
80
|
+
acceptedAnswers: string[];
|
|
85
81
|
})[];
|
|
86
82
|
meta: object;
|
|
87
83
|
}>;
|
|
@@ -97,6 +93,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
97
93
|
workspaceId: string;
|
|
98
94
|
front: string;
|
|
99
95
|
back: string;
|
|
96
|
+
acceptedAnswers?: string[] | undefined;
|
|
100
97
|
tags?: string[] | undefined;
|
|
101
98
|
order?: number | undefined;
|
|
102
99
|
};
|
|
@@ -108,6 +105,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
108
105
|
front: string;
|
|
109
106
|
back: string;
|
|
110
107
|
tags: string[];
|
|
108
|
+
acceptedAnswers: string[];
|
|
111
109
|
};
|
|
112
110
|
meta: object;
|
|
113
111
|
}>;
|
|
@@ -116,6 +114,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
116
114
|
cardId: string;
|
|
117
115
|
front?: string | undefined;
|
|
118
116
|
back?: string | undefined;
|
|
117
|
+
acceptedAnswers?: string[] | undefined;
|
|
119
118
|
tags?: string[] | undefined;
|
|
120
119
|
order?: number | undefined;
|
|
121
120
|
};
|
|
@@ -127,6 +126,20 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
127
126
|
front: string;
|
|
128
127
|
back: string;
|
|
129
128
|
tags: string[];
|
|
129
|
+
acceptedAnswers: string[];
|
|
130
|
+
};
|
|
131
|
+
meta: object;
|
|
132
|
+
}>;
|
|
133
|
+
gradeTypedAnswer: import("@trpc/server").TRPCMutationProcedure<{
|
|
134
|
+
input: {
|
|
135
|
+
flashcardId: string;
|
|
136
|
+
userAnswer: string;
|
|
137
|
+
};
|
|
138
|
+
output: {
|
|
139
|
+
isCorrect: boolean;
|
|
140
|
+
confidence: number;
|
|
141
|
+
reason: string;
|
|
142
|
+
matchedAnswer: string | null;
|
|
130
143
|
};
|
|
131
144
|
meta: object;
|
|
132
145
|
}>;
|
|
@@ -158,17 +171,18 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
158
171
|
id: string;
|
|
159
172
|
createdAt: Date;
|
|
160
173
|
updatedAt: Date;
|
|
161
|
-
title: string;
|
|
162
|
-
description: string | null;
|
|
163
174
|
workspaceId: string;
|
|
164
175
|
type: import("@prisma/client").$Enums.ArtifactType;
|
|
176
|
+
title: string;
|
|
165
177
|
isArchived: boolean;
|
|
166
|
-
generating: boolean;
|
|
167
|
-
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
168
178
|
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
169
179
|
estimatedTime: string | null;
|
|
170
|
-
imageObjectKey: string | null;
|
|
171
180
|
createdById: string | null;
|
|
181
|
+
description: string | null;
|
|
182
|
+
generating: boolean;
|
|
183
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
184
|
+
worksheetConfig: import("@prisma/client/runtime/library").JsonValue | null;
|
|
185
|
+
imageObjectKey: string | null;
|
|
172
186
|
};
|
|
173
187
|
createdCards: number;
|
|
174
188
|
};
|
|
@@ -181,33 +195,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
181
195
|
confidence?: "easy" | "medium" | "hard" | undefined;
|
|
182
196
|
timeSpentMs?: number | undefined;
|
|
183
197
|
};
|
|
184
|
-
output:
|
|
185
|
-
flashcard: {
|
|
186
|
-
id: string;
|
|
187
|
-
createdAt: Date;
|
|
188
|
-
artifactId: string;
|
|
189
|
-
order: number;
|
|
190
|
-
front: string;
|
|
191
|
-
back: string;
|
|
192
|
-
tags: string[];
|
|
193
|
-
};
|
|
194
|
-
} & {
|
|
195
|
-
id: string;
|
|
196
|
-
createdAt: Date;
|
|
197
|
-
updatedAt: Date;
|
|
198
|
-
userId: string;
|
|
199
|
-
flashcardId: string;
|
|
200
|
-
timesStudied: number;
|
|
201
|
-
timesCorrect: number;
|
|
202
|
-
timesIncorrect: number;
|
|
203
|
-
timesIncorrectConsecutive: number;
|
|
204
|
-
easeFactor: number;
|
|
205
|
-
interval: number;
|
|
206
|
-
repetitions: number;
|
|
207
|
-
masteryLevel: number;
|
|
208
|
-
lastStudiedAt: Date | null;
|
|
209
|
-
nextReviewAt: Date | null;
|
|
210
|
-
};
|
|
198
|
+
output: any;
|
|
211
199
|
meta: object;
|
|
212
200
|
}>;
|
|
213
201
|
getSetProgress: import("@trpc/server").TRPCQueryProcedure<{
|
|
@@ -247,17 +235,18 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
247
235
|
id: string;
|
|
248
236
|
createdAt: Date;
|
|
249
237
|
updatedAt: Date;
|
|
250
|
-
title: string;
|
|
251
|
-
description: string | null;
|
|
252
238
|
workspaceId: string;
|
|
253
239
|
type: import("@prisma/client").$Enums.ArtifactType;
|
|
240
|
+
title: string;
|
|
254
241
|
isArchived: boolean;
|
|
255
|
-
generating: boolean;
|
|
256
|
-
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
257
242
|
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
258
243
|
estimatedTime: string | null;
|
|
259
|
-
imageObjectKey: string | null;
|
|
260
244
|
createdById: string | null;
|
|
245
|
+
description: string | null;
|
|
246
|
+
generating: boolean;
|
|
247
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
248
|
+
worksheetConfig: import("@prisma/client/runtime/library").JsonValue | null;
|
|
249
|
+
imageObjectKey: string | null;
|
|
261
250
|
};
|
|
262
251
|
} & {
|
|
263
252
|
id: string;
|
|
@@ -267,22 +256,24 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
267
256
|
front: string;
|
|
268
257
|
back: string;
|
|
269
258
|
tags: string[];
|
|
259
|
+
acceptedAnswers: string[];
|
|
270
260
|
}) | {
|
|
271
261
|
artifact: {
|
|
272
262
|
id: string;
|
|
273
263
|
createdAt: Date;
|
|
274
264
|
updatedAt: Date;
|
|
275
|
-
title: string;
|
|
276
|
-
description: string | null;
|
|
277
265
|
workspaceId: string;
|
|
278
266
|
type: import("@prisma/client").$Enums.ArtifactType;
|
|
267
|
+
title: string;
|
|
279
268
|
isArchived: boolean;
|
|
280
|
-
generating: boolean;
|
|
281
|
-
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
282
269
|
difficulty: import("@prisma/client").$Enums.Difficulty | null;
|
|
283
270
|
estimatedTime: string | null;
|
|
284
|
-
imageObjectKey: string | null;
|
|
285
271
|
createdById: string | null;
|
|
272
|
+
description: string | null;
|
|
273
|
+
generating: boolean;
|
|
274
|
+
generatingMetadata: import("@prisma/client/runtime/library").JsonValue | null;
|
|
275
|
+
worksheetConfig: import("@prisma/client/runtime/library").JsonValue | null;
|
|
276
|
+
imageObjectKey: string | null;
|
|
286
277
|
};
|
|
287
278
|
id: string;
|
|
288
279
|
createdAt: Date;
|
|
@@ -291,6 +282,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
291
282
|
front: string;
|
|
292
283
|
back: string;
|
|
293
284
|
tags: string[];
|
|
285
|
+
acceptedAnswers: string[];
|
|
294
286
|
})[];
|
|
295
287
|
meta: object;
|
|
296
288
|
}>;
|
|
@@ -327,33 +319,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
327
319
|
timeSpentMs?: number | undefined;
|
|
328
320
|
}[];
|
|
329
321
|
};
|
|
330
|
-
output:
|
|
331
|
-
flashcard: {
|
|
332
|
-
id: string;
|
|
333
|
-
createdAt: Date;
|
|
334
|
-
artifactId: string;
|
|
335
|
-
order: number;
|
|
336
|
-
front: string;
|
|
337
|
-
back: string;
|
|
338
|
-
tags: string[];
|
|
339
|
-
};
|
|
340
|
-
} & {
|
|
341
|
-
id: string;
|
|
342
|
-
createdAt: Date;
|
|
343
|
-
updatedAt: Date;
|
|
344
|
-
userId: string;
|
|
345
|
-
flashcardId: string;
|
|
346
|
-
timesStudied: number;
|
|
347
|
-
timesCorrect: number;
|
|
348
|
-
timesIncorrect: number;
|
|
349
|
-
timesIncorrectConsecutive: number;
|
|
350
|
-
easeFactor: number;
|
|
351
|
-
interval: number;
|
|
352
|
-
repetitions: number;
|
|
353
|
-
masteryLevel: number;
|
|
354
|
-
lastStudiedAt: Date | null;
|
|
355
|
-
nextReviewAt: Date | null;
|
|
356
|
-
})[];
|
|
322
|
+
output: any[];
|
|
357
323
|
meta: object;
|
|
358
324
|
}>;
|
|
359
325
|
}>>;
|
|
@@ -1,17 +1,43 @@
|
|
|
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 { createFlashcardProgressService } from '../services/flashcard-progress.service.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
import { ArtifactType } from '../lib/constants.js';
|
|
9
|
+
import { workspaceAccessFilter } from '../lib/workspace-access.js';
|
|
10
|
+
import inference from '../lib/inference.js';
|
|
11
|
+
const typedAnswerGradeSchema = z.object({
|
|
12
|
+
isCorrect: z.boolean(),
|
|
13
|
+
confidence: z.number().min(0).max(1),
|
|
14
|
+
reason: z.string().min(1),
|
|
15
|
+
matchedAnswer: z.string().nullable(),
|
|
16
|
+
});
|
|
17
|
+
function normalizeAcceptedAnswers(answers) {
|
|
18
|
+
if (!answers || answers.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const normalized = [];
|
|
22
|
+
for (const answer of answers) {
|
|
23
|
+
const trimmed = answer.trim();
|
|
24
|
+
if (!trimmed)
|
|
25
|
+
continue;
|
|
26
|
+
const key = trimmed.toLowerCase();
|
|
27
|
+
if (seen.has(key))
|
|
28
|
+
continue;
|
|
29
|
+
seen.add(key);
|
|
30
|
+
normalized.push(trimmed);
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function extractFirstJsonObject(text) {
|
|
35
|
+
const start = text.indexOf('{');
|
|
36
|
+
const end = text.lastIndexOf('}');
|
|
37
|
+
if (start === -1 || end === -1 || end <= start)
|
|
38
|
+
return null;
|
|
39
|
+
return text.slice(start, end + 1);
|
|
40
|
+
}
|
|
15
41
|
export const flashcards = router({
|
|
16
42
|
listSets: authedProcedure
|
|
17
43
|
.input(z.object({ workspaceId: z.string() }))
|
|
@@ -36,7 +62,7 @@ export const flashcards = router({
|
|
|
36
62
|
.input(z.object({ workspaceId: z.string() }))
|
|
37
63
|
.query(async ({ ctx, input }) => {
|
|
38
64
|
const set = await ctx.db.artifact.findFirst({
|
|
39
|
-
where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace:
|
|
65
|
+
where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
40
66
|
include: {
|
|
41
67
|
flashcards: {
|
|
42
68
|
include: {
|
|
@@ -48,7 +74,7 @@ export const flashcards = router({
|
|
|
48
74
|
}
|
|
49
75
|
},
|
|
50
76
|
},
|
|
51
|
-
orderBy: {
|
|
77
|
+
orderBy: { createdAt: 'desc' },
|
|
52
78
|
});
|
|
53
79
|
if (!set)
|
|
54
80
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
@@ -58,23 +84,27 @@ export const flashcards = router({
|
|
|
58
84
|
.input(z.object({ workspaceId: z.string() }))
|
|
59
85
|
.query(async ({ ctx, input }) => {
|
|
60
86
|
const artifact = await ctx.db.artifact.findFirst({
|
|
61
|
-
where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace:
|
|
87
|
+
where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
88
|
+
orderBy: { createdAt: 'desc' },
|
|
62
89
|
});
|
|
63
90
|
return artifact?.generating;
|
|
64
91
|
}),
|
|
65
|
-
createCard:
|
|
92
|
+
createCard: limitedProcedure
|
|
66
93
|
.input(z.object({
|
|
67
94
|
workspaceId: z.string(),
|
|
68
95
|
front: z.string().min(1),
|
|
69
96
|
back: z.string().min(1),
|
|
97
|
+
acceptedAnswers: z.array(z.string()).optional(),
|
|
70
98
|
tags: z.array(z.string()).optional(),
|
|
71
99
|
order: z.number().int().optional(),
|
|
72
100
|
}))
|
|
73
101
|
.mutation(async ({ ctx, input }) => {
|
|
74
102
|
const set = await ctx.db.artifact.findFirst({
|
|
75
|
-
where: {
|
|
103
|
+
where: {
|
|
104
|
+
type: ArtifactType.FLASHCARD_SET, workspace: {
|
|
76
105
|
id: input.workspaceId,
|
|
77
|
-
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
78
108
|
include: {
|
|
79
109
|
flashcards: true,
|
|
80
110
|
},
|
|
@@ -87,6 +117,7 @@ export const flashcards = router({
|
|
|
87
117
|
artifactId: set.id,
|
|
88
118
|
front: input.front,
|
|
89
119
|
back: input.back,
|
|
120
|
+
acceptedAnswers: normalizeAcceptedAnswers(input.acceptedAnswers),
|
|
90
121
|
tags: input.tags ?? [],
|
|
91
122
|
order: input.order ?? 0,
|
|
92
123
|
},
|
|
@@ -97,12 +128,13 @@ export const flashcards = router({
|
|
|
97
128
|
cardId: z.string(),
|
|
98
129
|
front: z.string().optional(),
|
|
99
130
|
back: z.string().optional(),
|
|
131
|
+
acceptedAnswers: z.array(z.string()).optional(),
|
|
100
132
|
tags: z.array(z.string()).optional(),
|
|
101
133
|
order: z.number().int().optional(),
|
|
102
134
|
}))
|
|
103
135
|
.mutation(async ({ ctx, input }) => {
|
|
104
136
|
const card = await ctx.db.flashcard.findFirst({
|
|
105
|
-
where: { id: input.cardId, artifact: { type: ArtifactType.FLASHCARD_SET, workspace:
|
|
137
|
+
where: { id: input.cardId, artifact: { type: ArtifactType.FLASHCARD_SET, workspace: workspaceAccessFilter(ctx.session.user.id) } },
|
|
106
138
|
});
|
|
107
139
|
if (!card)
|
|
108
140
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
@@ -111,16 +143,81 @@ export const flashcards = router({
|
|
|
111
143
|
data: {
|
|
112
144
|
front: input.front ?? card.front,
|
|
113
145
|
back: input.back ?? card.back,
|
|
146
|
+
acceptedAnswers: input.acceptedAnswers ? normalizeAcceptedAnswers(input.acceptedAnswers) : card.acceptedAnswers,
|
|
114
147
|
tags: input.tags ?? card.tags,
|
|
115
148
|
order: input.order ?? card.order,
|
|
116
149
|
},
|
|
117
150
|
});
|
|
118
151
|
}),
|
|
152
|
+
gradeTypedAnswer: authedProcedure
|
|
153
|
+
.input(z.object({
|
|
154
|
+
flashcardId: z.string().cuid(),
|
|
155
|
+
userAnswer: z.string().min(1),
|
|
156
|
+
}))
|
|
157
|
+
.mutation(async ({ ctx, input }) => {
|
|
158
|
+
const flashcard = await ctx.db.flashcard.findFirst({
|
|
159
|
+
where: {
|
|
160
|
+
id: input.flashcardId,
|
|
161
|
+
artifact: {
|
|
162
|
+
type: ArtifactType.FLASHCARD_SET,
|
|
163
|
+
workspace: workspaceAccessFilter(ctx.session.user.id),
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
select: {
|
|
167
|
+
id: true,
|
|
168
|
+
front: true,
|
|
169
|
+
back: true,
|
|
170
|
+
acceptedAnswers: true,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
if (!flashcard) {
|
|
174
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Flashcard not found' });
|
|
175
|
+
}
|
|
176
|
+
const acceptedAnswers = [
|
|
177
|
+
flashcard.back,
|
|
178
|
+
...normalizeAcceptedAnswers(flashcard.acceptedAnswers),
|
|
179
|
+
];
|
|
180
|
+
const prompt = [
|
|
181
|
+
'Grade whether the student answer is semantically correct for the flashcard.',
|
|
182
|
+
'',
|
|
183
|
+
'Return ONLY valid JSON with this exact shape:',
|
|
184
|
+
'{"isCorrect": boolean, "confidence": number, "reason": string, "matchedAnswer": string | null}',
|
|
185
|
+
'',
|
|
186
|
+
'Rules:',
|
|
187
|
+
'- Accept synonyms, short paraphrases, and equivalent wording.',
|
|
188
|
+
'- Reject answers that are contradictory, unrelated, or materially incomplete.',
|
|
189
|
+
'- confidence must be from 0 to 1.',
|
|
190
|
+
'- reason must be under 160 characters.',
|
|
191
|
+
'- matchedAnswer must be the matched canonical/alias answer, or null if incorrect.',
|
|
192
|
+
'',
|
|
193
|
+
`Question: ${flashcard.front}`,
|
|
194
|
+
`Canonical answer: ${flashcard.back}`,
|
|
195
|
+
`Accepted aliases: ${JSON.stringify(acceptedAnswers)}`,
|
|
196
|
+
`Student answer: ${input.userAnswer}`,
|
|
197
|
+
].join('\n');
|
|
198
|
+
try {
|
|
199
|
+
const response = await inference([{ role: 'user', content: prompt }]);
|
|
200
|
+
const content = response.choices?.[0]?.message?.content ?? '';
|
|
201
|
+
const jsonCandidate = extractFirstJsonObject(content);
|
|
202
|
+
if (!jsonCandidate) {
|
|
203
|
+
throw new Error('No JSON object found in grading response');
|
|
204
|
+
}
|
|
205
|
+
const parsed = JSON.parse(jsonCandidate);
|
|
206
|
+
return typedAnswerGradeSchema.parse(parsed);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
throw new TRPCError({
|
|
210
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
211
|
+
message: 'Failed to grade typed answer. Please retry.',
|
|
212
|
+
cause: error,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}),
|
|
119
216
|
deleteCard: authedProcedure
|
|
120
217
|
.input(z.object({ cardId: z.string() }))
|
|
121
218
|
.mutation(async ({ ctx, input }) => {
|
|
122
219
|
const card = await ctx.db.flashcard.findFirst({
|
|
123
|
-
where: { id: input.cardId, artifact: { workspace:
|
|
220
|
+
where: { id: input.cardId, artifact: { workspace: workspaceAccessFilter(ctx.session.user.id) } },
|
|
124
221
|
});
|
|
125
222
|
if (!card)
|
|
126
223
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
@@ -131,14 +228,14 @@ export const flashcards = router({
|
|
|
131
228
|
.input(z.object({ setId: z.string().uuid() }))
|
|
132
229
|
.mutation(async ({ ctx, input }) => {
|
|
133
230
|
const deleted = await ctx.db.artifact.deleteMany({
|
|
134
|
-
where: { id: input.setId, type: ArtifactType.FLASHCARD_SET, workspace:
|
|
231
|
+
where: { id: input.setId, type: ArtifactType.FLASHCARD_SET, workspace: workspaceAccessFilter(ctx.session.user.id) },
|
|
135
232
|
});
|
|
136
233
|
if (deleted.count === 0)
|
|
137
234
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
138
235
|
return true;
|
|
139
236
|
}),
|
|
140
237
|
// Generate a flashcard set from a user prompt
|
|
141
|
-
generateFromPrompt:
|
|
238
|
+
generateFromPrompt: limitedProcedure
|
|
142
239
|
.input(z.object({
|
|
143
240
|
workspaceId: z.string(),
|
|
144
241
|
prompt: z.string().min(1),
|
|
@@ -168,10 +265,6 @@ export const flashcards = router({
|
|
|
168
265
|
},
|
|
169
266
|
});
|
|
170
267
|
try {
|
|
171
|
-
await ctx.db.artifact.update({
|
|
172
|
-
where: { id: flashcardCurrent?.id },
|
|
173
|
-
data: { generating: true, generatingMetadata: { quantity: input.numCards, difficulty: input.difficulty.toLowerCase() } },
|
|
174
|
-
});
|
|
175
268
|
await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_info', { status: 'generating', numCards: input.numCards, difficulty: input.difficulty });
|
|
176
269
|
const artifact = await ctx.db.artifact.create({
|
|
177
270
|
data: {
|
|
@@ -179,6 +272,8 @@ export const flashcards = router({
|
|
|
179
272
|
type: ArtifactType.FLASHCARD_SET,
|
|
180
273
|
title: input.title || `Flashcards - ${new Date().toLocaleString()}`,
|
|
181
274
|
createdById: ctx.session.user.id,
|
|
275
|
+
generating: true,
|
|
276
|
+
generatingMetadata: { quantity: input.numCards, difficulty: input.difficulty.toLowerCase() },
|
|
182
277
|
flashcards: {
|
|
183
278
|
create: flashcardCurrent?.flashcards.map((card) => ({
|
|
184
279
|
front: card.front,
|
|
@@ -190,10 +285,11 @@ export const flashcards = router({
|
|
|
190
285
|
const currentCards = flashcardCurrent?.flashcards.length || 0;
|
|
191
286
|
const newCards = input.numCards - currentCards;
|
|
192
287
|
// Generate
|
|
193
|
-
const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, ctx.session.user.id, input.numCards, input.difficulty);
|
|
288
|
+
const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, ctx.session.user.id, input.numCards, input.difficulty, input.prompt);
|
|
194
289
|
let createdCards = 0;
|
|
195
290
|
try {
|
|
196
|
-
const
|
|
291
|
+
const parsed = typeof content === 'string' ? JSON.parse(content) : content;
|
|
292
|
+
const flashcardData = Array.isArray(parsed) ? parsed : (parsed.flashcards || []);
|
|
197
293
|
for (let i = 0; i < Math.min(flashcardData.length, input.numCards); i++) {
|
|
198
294
|
const card = flashcardData[i];
|
|
199
295
|
const front = card.term || card.front || card.question || card.prompt || `Question ${i + 1}`;
|
|
@@ -210,7 +306,8 @@ export const flashcards = router({
|
|
|
210
306
|
createdCards++;
|
|
211
307
|
}
|
|
212
308
|
}
|
|
213
|
-
catch {
|
|
309
|
+
catch (error) {
|
|
310
|
+
console.error("Failed to parse flashcard JSON or create cards:", error);
|
|
214
311
|
// Fallback to text parsing if JSON fails
|
|
215
312
|
const lines = content.split('\n').filter(line => line.trim());
|
|
216
313
|
for (let i = 0; i < Math.min(lines.length, input.numCards); i++) {
|
|
@@ -232,11 +329,30 @@ export const flashcards = router({
|
|
|
232
329
|
}
|
|
233
330
|
// Pusher complete
|
|
234
331
|
await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
|
|
332
|
+
// Set generating to false on the artifact
|
|
333
|
+
await ctx.db.artifact.update({ where: { id: artifact.id }, data: { generating: false } });
|
|
334
|
+
await notifyArtifactReady(ctx.db, {
|
|
335
|
+
userId: ctx.session.user.id,
|
|
336
|
+
workspaceId: input.workspaceId,
|
|
337
|
+
artifactId: artifact.id,
|
|
338
|
+
artifactType: ArtifactType.FLASHCARD_SET,
|
|
339
|
+
title: artifact.title,
|
|
340
|
+
}).catch(() => { });
|
|
235
341
|
return { artifact, createdCards };
|
|
236
342
|
}
|
|
237
343
|
catch (error) {
|
|
238
|
-
|
|
344
|
+
if (flashcardCurrent?.id) {
|
|
345
|
+
await ctx.db.artifact.update({ where: { id: flashcardCurrent.id }, data: { generating: false } });
|
|
346
|
+
}
|
|
239
347
|
await PusherService.emitError(input.workspaceId, `Failed to generate flashcards: ${error}`, 'flash_card_generation');
|
|
348
|
+
await notifyArtifactFailed(ctx.db, {
|
|
349
|
+
userId: ctx.session.user.id,
|
|
350
|
+
workspaceId: input.workspaceId,
|
|
351
|
+
artifactType: ArtifactType.FLASHCARD_SET,
|
|
352
|
+
message: error instanceof Error
|
|
353
|
+
? error.message
|
|
354
|
+
: 'Flashcard generation failed.',
|
|
355
|
+
}).catch(() => { });
|
|
240
356
|
throw error;
|
|
241
357
|
}
|
|
242
358
|
}),
|
|
@@ -10,13 +10,7 @@
|
|
|
10
10
|
* - Get current user's role
|
|
11
11
|
*/
|
|
12
12
|
export declare const members: import("@trpc/server").TRPCBuiltRouter<{
|
|
13
|
-
ctx:
|
|
14
|
-
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
15
|
-
session: any;
|
|
16
|
-
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
17
|
-
res: import("express").Response<any, Record<string, any>>;
|
|
18
|
-
cookies: Record<string, string | undefined>;
|
|
19
|
-
};
|
|
13
|
+
ctx: import("../context.js").Context;
|
|
20
14
|
meta: object;
|
|
21
15
|
errorShape: {
|
|
22
16
|
data: {
|
|
@@ -109,6 +103,7 @@ export declare const members: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
109
103
|
token: string;
|
|
110
104
|
};
|
|
111
105
|
output: {
|
|
106
|
+
message?: string | undefined;
|
|
112
107
|
workspaceId: string;
|
|
113
108
|
workspaceTitle: string;
|
|
114
109
|
role: string;
|
|
@@ -178,4 +173,38 @@ export declare const members: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
178
173
|
};
|
|
179
174
|
meta: object;
|
|
180
175
|
}>;
|
|
176
|
+
/**
|
|
177
|
+
* Resend a pending invitation (owner only)
|
|
178
|
+
*/
|
|
179
|
+
resendInvitation: import("@trpc/server").TRPCMutationProcedure<{
|
|
180
|
+
input: {
|
|
181
|
+
invitationId: string;
|
|
182
|
+
};
|
|
183
|
+
output: {
|
|
184
|
+
invitationId: string;
|
|
185
|
+
message: string;
|
|
186
|
+
};
|
|
187
|
+
meta: object;
|
|
188
|
+
}>;
|
|
189
|
+
/**
|
|
190
|
+
* DEBUG ONLY: Get all invitations for a workspace
|
|
191
|
+
*/
|
|
192
|
+
getAllInvitationsDebug: import("@trpc/server").TRPCQueryProcedure<{
|
|
193
|
+
input: {
|
|
194
|
+
workspaceId: string;
|
|
195
|
+
};
|
|
196
|
+
output: {
|
|
197
|
+
id: string;
|
|
198
|
+
email: string;
|
|
199
|
+
createdAt: Date;
|
|
200
|
+
updatedAt: Date;
|
|
201
|
+
workspaceId: string;
|
|
202
|
+
role: string;
|
|
203
|
+
token: string;
|
|
204
|
+
invitedById: string;
|
|
205
|
+
acceptedAt: Date | null;
|
|
206
|
+
expiresAt: Date;
|
|
207
|
+
}[];
|
|
208
|
+
meta: object;
|
|
209
|
+
}>;
|
|
181
210
|
}>>;
|