@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
@@ -4,7 +4,7 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
4
4
  session: any;
5
5
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
6
6
  res: import("express").Response<any, Record<string, any>>;
7
- cookies: any;
7
+ cookies: Record<string, string | undefined>;
8
8
  };
9
9
  meta: object;
10
10
  errorShape: import("@trpc/server").TRPCDefaultErrorShape;
@@ -14,64 +14,49 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
14
14
  input: {
15
15
  workspaceId: string;
16
16
  };
17
- output: {
17
+ output: ({
18
+ versions: {
19
+ id: string;
20
+ createdAt: Date;
21
+ createdById: string | null;
22
+ artifactId: string;
23
+ content: string;
24
+ data: import("@prisma/client/runtime/library").JsonValue | null;
25
+ version: number;
26
+ }[];
27
+ } & {
18
28
  id: string;
19
29
  createdAt: Date;
20
30
  updatedAt: Date;
21
31
  title: string;
32
+ description: string | null;
22
33
  workspaceId: string;
23
34
  type: import("@prisma/client").$Enums.ArtifactType;
24
35
  isArchived: boolean;
36
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
37
+ estimatedTime: string | null;
25
38
  createdById: string | null;
26
- }[];
39
+ })[];
27
40
  meta: object;
28
41
  }>;
29
- createSet: import("@trpc/server").TRPCMutationProcedure<{
42
+ listCards: import("@trpc/server").TRPCQueryProcedure<{
30
43
  input: {
31
44
  workspaceId: string;
32
- title: string;
33
45
  };
34
46
  output: {
35
47
  id: string;
36
48
  createdAt: Date;
37
- updatedAt: Date;
38
- title: string;
39
- workspaceId: string;
40
- type: import("@prisma/client").$Enums.ArtifactType;
41
- isArchived: boolean;
42
- createdById: string | null;
43
- };
44
- meta: object;
45
- }>;
46
- getSet: import("@trpc/server").TRPCQueryProcedure<{
47
- input: {
48
- setId: string;
49
- };
50
- output: {
51
- flashcards: {
52
- id: string;
53
- createdAt: Date;
54
- artifactId: string;
55
- front: string;
56
- back: string;
57
- tags: string[];
58
- order: number;
59
- }[];
60
- } & {
61
- id: string;
62
- createdAt: Date;
63
- updatedAt: Date;
64
- title: string;
65
- workspaceId: string;
66
- type: import("@prisma/client").$Enums.ArtifactType;
67
- isArchived: boolean;
68
- createdById: string | null;
69
- };
49
+ artifactId: string;
50
+ front: string;
51
+ back: string;
52
+ tags: string[];
53
+ order: number;
54
+ }[];
70
55
  meta: object;
71
56
  }>;
72
57
  createCard: import("@trpc/server").TRPCMutationProcedure<{
73
58
  input: {
74
- setId: string;
59
+ workspaceId: string;
75
60
  front: string;
76
61
  back: string;
77
62
  tags?: string[] | undefined;
@@ -121,4 +106,31 @@ export declare const flashcards: import("@trpc/server").TRPCBuiltRouter<{
121
106
  output: boolean;
122
107
  meta: object;
123
108
  }>;
109
+ generateFromPrompt: import("@trpc/server").TRPCMutationProcedure<{
110
+ input: {
111
+ workspaceId: string;
112
+ prompt: string;
113
+ numCards?: number | undefined;
114
+ difficulty?: "easy" | "medium" | "hard" | undefined;
115
+ title?: string | undefined;
116
+ tags?: string[] | undefined;
117
+ };
118
+ output: {
119
+ artifact: {
120
+ id: string;
121
+ createdAt: Date;
122
+ updatedAt: Date;
123
+ title: string;
124
+ description: string | null;
125
+ workspaceId: string;
126
+ type: import("@prisma/client").$Enums.ArtifactType;
127
+ isArchived: boolean;
128
+ difficulty: import("@prisma/client").$Enums.Difficulty | null;
129
+ estimatedTime: string | null;
130
+ createdById: string | null;
131
+ };
132
+ createdCards: number;
133
+ };
134
+ meta: object;
135
+ }>;
124
136
  }>>;
@@ -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
  // Prisma enum values mapped manually to avoid type import issues in ESM
5
7
  const ArtifactType = {
6
8
  STUDY_GUIDE: 'STUDY_GUIDE',
@@ -11,7 +13,7 @@ const ArtifactType = {
11
13
  };
12
14
  export const flashcards = router({
13
15
  listSets: authedProcedure
14
- .input(z.object({ workspaceId: z.string().uuid() }))
16
+ .input(z.object({ workspaceId: z.string() }))
15
17
  .query(async ({ ctx, input }) => {
16
18
  const workspace = await ctx.db.workspace.findFirst({
17
19
  where: { id: input.workspaceId, ownerId: ctx.session.user.id },
@@ -20,44 +22,32 @@ export const flashcards = router({
20
22
  throw new TRPCError({ code: 'NOT_FOUND' });
21
23
  return ctx.db.artifact.findMany({
22
24
  where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET },
23
- orderBy: { updatedAt: 'desc' },
24
- });
25
- }),
26
- createSet: authedProcedure
27
- .input(z.object({ workspaceId: z.string().uuid(), title: z.string().min(1).max(120) }))
28
- .mutation(async ({ ctx, input }) => {
29
- const workspace = await ctx.db.workspace.findFirst({
30
- where: { id: input.workspaceId, ownerId: ctx.session.user.id },
31
- });
32
- if (!workspace)
33
- throw new TRPCError({ code: 'NOT_FOUND' });
34
- return ctx.db.artifact.create({
35
- data: {
36
- workspaceId: input.workspaceId,
37
- type: ArtifactType.FLASHCARD_SET,
38
- title: input.title,
39
- createdById: ctx.session.user.id,
25
+ include: {
26
+ versions: {
27
+ orderBy: { version: 'desc' },
28
+ take: 1, // Get only the latest version
29
+ },
40
30
  },
31
+ orderBy: { updatedAt: 'desc' },
41
32
  });
42
33
  }),
43
- getSet: authedProcedure
44
- .input(z.object({ setId: z.string().uuid() }))
34
+ listCards: authedProcedure
35
+ .input(z.object({ workspaceId: z.string() }))
45
36
  .query(async ({ ctx, input }) => {
46
37
  const set = await ctx.db.artifact.findFirst({
47
- where: {
48
- id: input.setId,
49
- type: ArtifactType.FLASHCARD_SET,
50
- workspace: { ownerId: ctx.session.user.id },
38
+ where: { workspaceId: input.workspaceId, type: ArtifactType.FLASHCARD_SET, workspace: { ownerId: ctx.session.user.id } },
39
+ include: {
40
+ flashcards: true,
51
41
  },
52
- include: { flashcards: true },
42
+ orderBy: { updatedAt: 'desc' },
53
43
  });
54
44
  if (!set)
55
45
  throw new TRPCError({ code: 'NOT_FOUND' });
56
- return set;
46
+ return set.flashcards;
57
47
  }),
58
48
  createCard: authedProcedure
59
49
  .input(z.object({
60
- setId: z.string().uuid(),
50
+ workspaceId: z.string(),
61
51
  front: z.string().min(1),
62
52
  back: z.string().min(1),
63
53
  tags: z.array(z.string()).optional(),
@@ -65,13 +55,19 @@ export const flashcards = router({
65
55
  }))
66
56
  .mutation(async ({ ctx, input }) => {
67
57
  const set = await ctx.db.artifact.findFirst({
68
- where: { id: input.setId, type: ArtifactType.FLASHCARD_SET, workspace: { ownerId: ctx.session.user.id } },
58
+ where: { type: ArtifactType.FLASHCARD_SET, workspace: {
59
+ id: input.workspaceId,
60
+ } },
61
+ include: {
62
+ flashcards: true,
63
+ },
64
+ orderBy: { updatedAt: 'desc' },
69
65
  });
70
66
  if (!set)
71
67
  throw new TRPCError({ code: 'NOT_FOUND' });
72
68
  return ctx.db.flashcard.create({
73
69
  data: {
74
- artifactId: input.setId,
70
+ artifactId: set.id,
75
71
  front: input.front,
76
72
  back: input.back,
77
73
  tags: input.tags ?? [],
@@ -81,7 +77,7 @@ export const flashcards = router({
81
77
  }),
82
78
  updateCard: authedProcedure
83
79
  .input(z.object({
84
- cardId: z.string().uuid(),
80
+ cardId: z.string(),
85
81
  front: z.string().optional(),
86
82
  back: z.string().optional(),
87
83
  tags: z.array(z.string()).optional(),
@@ -104,7 +100,7 @@ export const flashcards = router({
104
100
  });
105
101
  }),
106
102
  deleteCard: authedProcedure
107
- .input(z.object({ cardId: z.string().uuid() }))
103
+ .input(z.object({ cardId: z.string() }))
108
104
  .mutation(async ({ ctx, input }) => {
109
105
  const card = await ctx.db.flashcard.findFirst({
110
106
  where: { id: input.cardId, artifact: { workspace: { ownerId: ctx.session.user.id } } },
@@ -124,4 +120,120 @@ export const flashcards = router({
124
120
  throw new TRPCError({ code: 'NOT_FOUND' });
125
121
  return true;
126
122
  }),
123
+ // Generate a flashcard set from a user prompt
124
+ generateFromPrompt: authedProcedure
125
+ .input(z.object({
126
+ workspaceId: z.string(),
127
+ prompt: z.string().min(1),
128
+ numCards: z.number().int().min(1).max(50).default(10),
129
+ difficulty: z.enum(['easy', 'medium', 'hard']).default('medium'),
130
+ title: z.string().optional(),
131
+ tags: z.array(z.string()).optional(),
132
+ }))
133
+ .mutation(async ({ ctx, input }) => {
134
+ // Verify workspace ownership
135
+ const workspace = await ctx.db.workspace.findFirst({
136
+ where: { id: input.workspaceId, ownerId: ctx.session.user.id },
137
+ });
138
+ if (!workspace)
139
+ throw new TRPCError({ code: 'NOT_FOUND' });
140
+ // Pusher start
141
+ await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_load_start', { source: 'prompt' });
142
+ const flashcardCurrent = await ctx.db.artifact.findFirst({
143
+ where: {
144
+ workspaceId: input.workspaceId,
145
+ type: ArtifactType.FLASHCARD_SET,
146
+ },
147
+ select: {
148
+ flashcards: true,
149
+ },
150
+ orderBy: {
151
+ updatedAt: 'desc',
152
+ },
153
+ });
154
+ const formattedPreviousCards = flashcardCurrent?.flashcards.map((card) => ({
155
+ front: card.front,
156
+ back: card.back,
157
+ }));
158
+ const partialPrompt = `
159
+ This is the users previous flashcards, avoid repeating any existing cards.
160
+ Please generate ${input.numCards} new cards,
161
+ Of a ${input.difficulty} difficulty,
162
+ Of a ${input.tags?.join(', ')} tag,
163
+ Of a ${input.title} title.
164
+ ${formattedPreviousCards?.map((card) => `Front: ${card.front}\nBack: ${card.back}`).join('\n')}
165
+
166
+ The user has also left you this prompt: ${input.prompt}
167
+ `;
168
+ // Init AI session and seed with prompt as instruction
169
+ const session = await aiSessionService.initSession(input.workspaceId);
170
+ await aiSessionService.setInstruction(session.id, partialPrompt);
171
+ await aiSessionService.startLLMSession(session.id);
172
+ const currentCards = flashcardCurrent?.flashcards.length || 0;
173
+ const newCards = input.numCards - currentCards;
174
+ // Generate
175
+ await PusherService.emitTaskComplete(input.workspaceId, 'flash_card_info', { status: 'generating', numCards: input.numCards, difficulty: input.difficulty });
176
+ const content = await aiSessionService.generateFlashcardQuestions(session.id, input.numCards, input.difficulty);
177
+ // Previous cards
178
+ // Create artifact
179
+ const artifact = await ctx.db.artifact.create({
180
+ data: {
181
+ workspaceId: input.workspaceId,
182
+ type: ArtifactType.FLASHCARD_SET,
183
+ title: input.title || `Flashcards - ${new Date().toLocaleString()}`,
184
+ createdById: ctx.session.user.id,
185
+ flashcards: {
186
+ create: flashcardCurrent?.flashcards.map((card) => ({
187
+ front: card.front,
188
+ back: card.back,
189
+ })),
190
+ },
191
+ },
192
+ });
193
+ // Parse and create cards
194
+ let createdCards = 0;
195
+ try {
196
+ const flashcardData = JSON.parse(content);
197
+ for (let i = 0; i < Math.min(flashcardData.length, input.numCards); i++) {
198
+ const card = flashcardData[i];
199
+ const front = card.term || card.front || card.question || card.prompt || `Question ${i + 1}`;
200
+ const back = card.definition || card.back || card.answer || card.solution || `Answer ${i + 1}`;
201
+ await ctx.db.flashcard.create({
202
+ data: {
203
+ artifactId: artifact.id,
204
+ front,
205
+ back,
206
+ order: i,
207
+ tags: input.tags ?? ['ai-generated', input.difficulty],
208
+ },
209
+ });
210
+ createdCards++;
211
+ }
212
+ }
213
+ catch {
214
+ // Fallback to text parsing if JSON fails
215
+ const lines = content.split('\n').filter(line => line.trim());
216
+ for (let i = 0; i < Math.min(lines.length, input.numCards); i++) {
217
+ const line = lines[i];
218
+ if (line.includes(' - ')) {
219
+ const [front, back] = line.split(' - ');
220
+ await ctx.db.flashcard.create({
221
+ data: {
222
+ artifactId: artifact.id,
223
+ front: front.trim(),
224
+ back: back.trim(),
225
+ order: i,
226
+ tags: input.tags ?? ['ai-generated', input.difficulty],
227
+ },
228
+ });
229
+ createdCards++;
230
+ }
231
+ }
232
+ }
233
+ // Pusher complete
234
+ await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
235
+ // Cleanup AI session (best-effort)
236
+ aiSessionService.deleteSession(session.id);
237
+ return { artifact, createdCards };
238
+ }),
127
239
  });
File without changes