@goscribe/server 1.0.11 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/ANALYSIS_PROGRESS_SPEC.md +463 -0
  2. package/PROGRESS_QUICK_REFERENCE.md +239 -0
  3. package/dist/lib/ai-session.d.ts +20 -9
  4. package/dist/lib/ai-session.js +316 -80
  5. package/dist/lib/auth.d.ts +35 -2
  6. package/dist/lib/auth.js +88 -15
  7. package/dist/lib/env.d.ts +32 -0
  8. package/dist/lib/env.js +46 -0
  9. package/dist/lib/errors.d.ts +33 -0
  10. package/dist/lib/errors.js +78 -0
  11. package/dist/lib/inference.d.ts +4 -1
  12. package/dist/lib/inference.js +9 -11
  13. package/dist/lib/logger.d.ts +62 -0
  14. package/dist/lib/logger.js +342 -0
  15. package/dist/lib/podcast-prompts.d.ts +43 -0
  16. package/dist/lib/podcast-prompts.js +135 -0
  17. package/dist/lib/pusher.d.ts +1 -0
  18. package/dist/lib/pusher.js +14 -2
  19. package/dist/lib/storage.d.ts +3 -3
  20. package/dist/lib/storage.js +51 -47
  21. package/dist/lib/validation.d.ts +51 -0
  22. package/dist/lib/validation.js +64 -0
  23. package/dist/routers/_app.d.ts +697 -111
  24. package/dist/routers/_app.js +5 -0
  25. package/dist/routers/auth.d.ts +11 -1
  26. package/dist/routers/chat.d.ts +11 -1
  27. package/dist/routers/flashcards.d.ts +205 -6
  28. package/dist/routers/flashcards.js +144 -66
  29. package/dist/routers/members.d.ts +165 -0
  30. package/dist/routers/members.js +531 -0
  31. package/dist/routers/podcast.d.ts +78 -63
  32. package/dist/routers/podcast.js +330 -393
  33. package/dist/routers/studyguide.d.ts +11 -1
  34. package/dist/routers/worksheets.d.ts +124 -13
  35. package/dist/routers/worksheets.js +123 -50
  36. package/dist/routers/workspace.d.ts +213 -26
  37. package/dist/routers/workspace.js +303 -181
  38. package/dist/server.js +12 -4
  39. package/dist/services/flashcard-progress.service.d.ts +183 -0
  40. package/dist/services/flashcard-progress.service.js +383 -0
  41. package/dist/services/flashcard.service.d.ts +183 -0
  42. package/dist/services/flashcard.service.js +224 -0
  43. package/dist/services/podcast-segment-reorder.d.ts +0 -0
  44. package/dist/services/podcast-segment-reorder.js +107 -0
  45. package/dist/services/podcast.service.d.ts +0 -0
  46. package/dist/services/podcast.service.js +326 -0
  47. package/dist/services/worksheet.service.d.ts +0 -0
  48. package/dist/services/worksheet.service.js +295 -0
  49. package/dist/trpc.d.ts +13 -2
  50. package/dist/trpc.js +55 -6
  51. package/dist/types/index.d.ts +126 -0
  52. package/dist/types/index.js +1 -0
  53. package/package.json +3 -2
  54. package/prisma/schema.prisma +142 -4
  55. package/src/lib/ai-session.ts +356 -85
  56. package/src/lib/auth.ts +113 -19
  57. package/src/lib/env.ts +59 -0
  58. package/src/lib/errors.ts +92 -0
  59. package/src/lib/inference.ts +11 -11
  60. package/src/lib/logger.ts +405 -0
  61. package/src/lib/pusher.ts +15 -3
  62. package/src/lib/storage.ts +56 -51
  63. package/src/lib/validation.ts +75 -0
  64. package/src/routers/_app.ts +5 -0
  65. package/src/routers/chat.ts +2 -23
  66. package/src/routers/flashcards.ts +108 -24
  67. package/src/routers/members.ts +586 -0
  68. package/src/routers/podcast.ts +385 -420
  69. package/src/routers/worksheets.ts +118 -36
  70. package/src/routers/workspace.ts +356 -195
  71. package/src/server.ts +13 -4
  72. package/src/services/flashcard-progress.service.ts +541 -0
  73. package/src/trpc.ts +59 -6
  74. package/src/types/index.ts +165 -0
  75. package/AUTH_FRONTEND_SPEC.md +0 -21
  76. package/CHAT_FRONTEND_SPEC.md +0 -474
  77. package/DATABASE_SETUP.md +0 -165
  78. package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
  79. package/PODCAST_FRONTEND_SPEC.md +0 -595
  80. package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
  81. package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
  82. package/WORKSPACE_FRONTEND_SPEC.md +0 -47
  83. package/test-ai-integration.js +0 -134
@@ -220,18 +220,7 @@ export const chat = router({
220
220
  }
221
221
  });
222
222
  // Notify via Pusher
223
- await PusherService.emitChannelEvent(chat.channelId, "edit_message", {
224
- chatId: updatedChat.id,
225
- channelId: updatedChat.channelId,
226
- userId: updatedChat.userId,
227
- message: input.message,
228
- updatedAt: updatedChat.updatedAt,
229
- user: {
230
- id: ctx.session.user.id,
231
- name: updatedChat.user?.name,
232
- image: updatedChat.user?.image,
233
- },
234
- });
223
+ await PusherService.emitChannelEvent(chat.channelId, "edit_message", updatedChat);
235
224
  return updatedChat;
236
225
  }),
237
226
  deleteMessage: authedProcedure
@@ -259,17 +248,7 @@ export const chat = router({
259
248
  where: { id: input.chatId },
260
249
  });
261
250
  // Notify via Pusher
262
- await PusherService.emitChannelEvent(chat.channelId, "delete_message", {
263
- chatId: chat.id,
264
- channelId: chat.channelId,
265
- userId: chat.userId,
266
- deletedAt: new Date().toISOString(),
267
- user: {
268
- id: ctx.session.user.id,
269
- name: chat.user?.name,
270
- image: chat.user?.image,
271
- },
272
- });
251
+ await PusherService.emitChannelEvent(chat.channelId, "delete_message", chat);
273
252
  return { success: true };
274
253
  }),
275
254
  });
@@ -4,6 +4,7 @@ import { router, authedProcedure } 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 { createFlashcardProgressService } from '../services/flashcard-progress.service.js';
7
8
  // Prisma enum values mapped manually to avoid type import issues in ESM
8
9
  const ArtifactType = {
9
10
  STUDY_GUIDE: 'STUDY_GUIDE',
@@ -38,13 +39,30 @@ export const flashcards = router({
38
39
  const set = await ctx.db.artifact.findFirst({
39
40
  where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: { ownerId: ctx.session.user.id } },
40
41
  include: {
41
- flashcards: true,
42
+ flashcards: {
43
+ include: {
44
+ progress: {
45
+ where: {
46
+ userId: ctx.session.user.id,
47
+ },
48
+ },
49
+ }
50
+ },
51
+
42
52
  },
43
53
  orderBy: { updatedAt: 'desc' },
44
54
  });
45
55
  if (!set) throw new TRPCError({ code: 'NOT_FOUND' });
46
56
  return set.flashcards;
47
57
  }),
58
+ isGenerating: authedProcedure
59
+ .input(z.object({ workspaceId: z.string() }))
60
+ .query(async ({ ctx, input }) => {
61
+ const artifact = await ctx.db.artifact.findFirst({
62
+ where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: { ownerId: ctx.session.user.id } }, orderBy: { updatedAt: 'desc' },
63
+ });
64
+ return artifact?.generating;
65
+ }),
48
66
  createCard: authedProcedure
49
67
  .input(z.object({
50
68
  workspaceId: z.string(),
@@ -137,14 +155,13 @@ export const flashcards = router({
137
155
  });
138
156
  if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
139
157
 
140
- // Pusher start
141
- await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_load_start', { source: 'prompt' });
142
158
  const flashcardCurrent = await ctx.db.artifact.findFirst({
143
159
  where: {
144
160
  workspaceId: input.workspaceId,
145
161
  type: ArtifactType.FLASHCARD_SET,
146
162
  },
147
163
  select: {
164
+ id: true,
148
165
  flashcards: true,
149
166
  },
150
167
  orderBy: {
@@ -152,6 +169,13 @@ export const flashcards = router({
152
169
  },
153
170
  });
154
171
 
172
+ try {
173
+ await ctx.db.artifact.update({
174
+ where: { id: flashcardCurrent?.id },
175
+ data: { generating: true, generatingMetadata: { quantity: input.numCards, difficulty: input.difficulty.toLowerCase() } },
176
+ });
177
+
178
+ await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_info', { status: 'generating', numCards: input.numCards, difficulty: input.difficulty });
155
179
 
156
180
  const formattedPreviousCards = flashcardCurrent?.flashcards.map((card) => ({
157
181
  front: card.front,
@@ -169,22 +193,6 @@ export const flashcards = router({
169
193
 
170
194
  The user has also left you this prompt: ${input.prompt}
171
195
  `
172
- // Init AI session and seed with prompt as instruction
173
- const session = await aiSessionService.initSession(input.workspaceId, ctx.session.user.id);
174
- await aiSessionService.setInstruction(session.id, partialPrompt);
175
-
176
- await aiSessionService.startLLMSession(session.id);
177
-
178
- const currentCards = flashcardCurrent?.flashcards.length || 0;
179
- const newCards = input.numCards - currentCards;
180
-
181
- // Generate
182
- await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_info', { status: 'generating', numCards: input.numCards, difficulty: input.difficulty });
183
- const content = await aiSessionService.generateFlashcardQuestions(session.id, input.numCards, input.difficulty);
184
-
185
- // Previous cards
186
-
187
- // Create artifact
188
196
  const artifact = await ctx.db.artifact.create({
189
197
  data: {
190
198
  workspaceId: input.workspaceId,
@@ -199,11 +207,17 @@ export const flashcards = router({
199
207
  },
200
208
  },
201
209
  });
210
+
211
+ const currentCards = flashcardCurrent?.flashcards.length || 0;
212
+ const newCards = input.numCards - currentCards;
213
+
214
+
215
+ // Generate
216
+ const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, ctx.session.user.id, input.numCards, input.difficulty);
202
217
 
203
- // Parse and create cards
204
218
  let createdCards = 0;
205
219
  try {
206
- const flashcardData = JSON.parse(content);
220
+ const flashcardData: any = content;
207
221
  for (let i = 0; i < Math.min(flashcardData.length, input.numCards); i++) {
208
222
  const card = flashcardData[i];
209
223
  const front = card.term || card.front || card.question || card.prompt || `Question ${i + 1}`;
@@ -243,10 +257,80 @@ export const flashcards = router({
243
257
  // Pusher complete
244
258
  await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
245
259
 
246
- // Cleanup AI session (best-effort)
247
- aiSessionService.deleteSession(session.id);
248
-
249
260
  return { artifact, createdCards };
261
+
262
+ } catch (error) {
263
+ await ctx.db.artifact.update({ where: { id: flashcardCurrent?.id }, data: { generating: false } });
264
+ await PusherService.emitError(input.workspaceId, `Failed to generate flashcards: ${error}`, 'flash_card_generation');
265
+ throw error;
266
+ }
267
+ }),
268
+
269
+ // Record study attempt
270
+ recordStudyAttempt: authedProcedure
271
+ .input(z.object({
272
+ flashcardId: z.string().cuid(),
273
+ isCorrect: z.boolean(),
274
+ confidence: z.enum(['easy', 'medium', 'hard']).optional(),
275
+ timeSpentMs: z.number().optional(),
276
+ }))
277
+ .mutation(async ({ ctx, input }) => {
278
+ const service = createFlashcardProgressService(ctx.db);
279
+ return service.recordStudyAttempt({
280
+ userId: ctx.userId,
281
+ ...input,
282
+ });
283
+ }),
284
+
285
+ // Get progress for a flashcard set
286
+ getSetProgress: authedProcedure
287
+ .input(z.object({ artifactId: z.string().cuid() }))
288
+ .query(async ({ ctx, input }) => {
289
+ const service = createFlashcardProgressService(ctx.db);
290
+ return service.getSetProgress(ctx.userId, input.artifactId);
291
+ }),
292
+
293
+ // Get flashcards due for review
294
+ getDueFlashcards: authedProcedure
295
+ .input(z.object({ workspaceId: z.string() }))
296
+ .query(async ({ ctx, input }) => {
297
+ const service = createFlashcardProgressService(ctx.db);
298
+
299
+ return service.getDueFlashcards(ctx.userId, input.workspaceId);
300
+ }),
301
+
302
+ // Get statistics for a flashcard set
303
+ getSetStatistics: authedProcedure
304
+ .input(z.object({ artifactId: z.string().cuid() }))
305
+ .query(async ({ ctx, input }) => {
306
+ const service = createFlashcardProgressService(ctx.db);
307
+ return service.getSetStatistics(ctx.userId, input.artifactId);
308
+ }),
309
+
310
+ // Reset progress for a flashcard
311
+ resetProgress: authedProcedure
312
+ .input(z.object({ flashcardId: z.string().cuid() }))
313
+ .mutation(async ({ ctx, input }) => {
314
+ const service = createFlashcardProgressService(ctx.db);
315
+ return service.resetProgress(ctx.userId, input.flashcardId);
316
+ }),
317
+
318
+ // Bulk record study session
319
+ recordStudySession: authedProcedure
320
+ .input(z.object({
321
+ attempts: z.array(z.object({
322
+ flashcardId: z.string().cuid(),
323
+ isCorrect: z.boolean(),
324
+ confidence: z.enum(['easy', 'medium', 'hard']).optional(),
325
+ timeSpentMs: z.number().optional(),
326
+ })),
327
+ }))
328
+ .mutation(async ({ ctx, input }) => {
329
+ const service = createFlashcardProgressService(ctx.db);
330
+ return service.recordStudySession({
331
+ userId: ctx.userId,
332
+ ...input,
333
+ });
250
334
  }),
251
335
  });
252
336