@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.
- package/AUTH_FRONTEND_SPEC.md +21 -0
- package/CHAT_FRONTEND_SPEC.md +474 -0
- package/MEETINGSUMMARY_FRONTEND_SPEC.md +28 -0
- package/PODCAST_FRONTEND_SPEC.md +595 -0
- package/STUDYGUIDE_FRONTEND_SPEC.md +18 -0
- package/WORKSHEETS_FRONTEND_SPEC.md +26 -0
- package/WORKSPACE_FRONTEND_SPEC.md +47 -0
- package/dist/context.d.ts +1 -1
- package/dist/lib/ai-session.d.ts +26 -0
- package/dist/lib/ai-session.js +343 -0
- package/dist/lib/auth.js +10 -6
- package/dist/lib/inference.d.ts +2 -0
- package/dist/lib/inference.js +21 -0
- package/dist/lib/pusher.d.ts +14 -0
- package/dist/lib/pusher.js +94 -0
- package/dist/lib/storage.d.ts +10 -2
- package/dist/lib/storage.js +63 -6
- package/dist/routers/_app.d.ts +878 -100
- package/dist/routers/_app.js +8 -2
- package/dist/routers/ai-session.d.ts +0 -0
- package/dist/routers/ai-session.js +1 -0
- package/dist/routers/auth.d.ts +13 -11
- package/dist/routers/auth.js +50 -21
- package/dist/routers/chat.d.ts +171 -0
- package/dist/routers/chat.js +270 -0
- package/dist/routers/flashcards.d.ts +51 -39
- package/dist/routers/flashcards.js +143 -31
- package/dist/routers/meetingsummary.d.ts +0 -0
- package/dist/routers/meetingsummary.js +377 -0
- package/dist/routers/podcast.d.ts +277 -0
- package/dist/routers/podcast.js +847 -0
- package/dist/routers/studyguide.d.ts +54 -0
- package/dist/routers/studyguide.js +125 -0
- package/dist/routers/worksheets.d.ts +147 -40
- package/dist/routers/worksheets.js +348 -33
- package/dist/routers/workspace.d.ts +163 -8
- package/dist/routers/workspace.js +453 -8
- package/dist/server.d.ts +1 -1
- package/dist/server.js +7 -2
- package/dist/trpc.d.ts +5 -5
- package/package.json +11 -3
- package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +213 -0
- package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +87 -6
- package/prisma/seed.mjs +135 -0
- package/src/lib/ai-session.ts +411 -0
- package/src/lib/auth.ts +1 -1
- package/src/lib/inference.ts +21 -0
- package/src/lib/pusher.ts +104 -0
- package/src/lib/storage.ts +89 -6
- package/src/routers/_app.ts +6 -0
- package/src/routers/auth.ts +8 -4
- package/src/routers/chat.ts +275 -0
- package/src/routers/flashcards.ts +151 -33
- package/src/routers/meetingsummary.ts +416 -0
- package/src/routers/podcast.ts +934 -0
- package/src/routers/studyguide.ts +144 -0
- package/src/routers/worksheets.ts +346 -18
- package/src/routers/workspace.ts +500 -8
- package/src/server.ts +7 -2
- package/test-ai-integration.js +134 -0
- package/dist/context.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/auth.d.ts.map +0 -1
- package/dist/lib/file.d.ts.map +0 -1
- package/dist/lib/prisma.d.ts.map +0 -1
- package/dist/lib/storage.d.ts.map +0 -1
- package/dist/routers/_app.d.ts.map +0 -1
- package/dist/routers/auth.d.ts.map +0 -1
- package/dist/routers/sample.js +0 -21
- package/dist/routers/workspace.d.ts.map +0 -1
- package/dist/server.d.ts.map +0 -1
- 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:
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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()
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
44
|
-
.input(z.object({
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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:
|
|
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()
|
|
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()
|
|
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
|