@goscribe/server 1.0.10 → 1.1.0
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/ANALYSIS_PROGRESS_SPEC.md +463 -0
- package/PROGRESS_QUICK_REFERENCE.md +239 -0
- package/dist/lib/ai-session.d.ts +20 -9
- package/dist/lib/ai-session.js +316 -80
- package/dist/lib/auth.d.ts +35 -2
- package/dist/lib/auth.js +88 -15
- package/dist/lib/env.d.ts +32 -0
- package/dist/lib/env.js +46 -0
- package/dist/lib/errors.d.ts +33 -0
- package/dist/lib/errors.js +78 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +9 -11
- package/dist/lib/logger.d.ts +62 -0
- package/dist/lib/logger.js +342 -0
- package/dist/lib/podcast-prompts.d.ts +43 -0
- package/dist/lib/podcast-prompts.js +135 -0
- package/dist/lib/pusher.d.ts +1 -0
- package/dist/lib/pusher.js +14 -2
- package/dist/lib/storage.d.ts +3 -3
- package/dist/lib/storage.js +51 -47
- package/dist/lib/validation.d.ts +51 -0
- package/dist/lib/validation.js +64 -0
- package/dist/routers/_app.d.ts +697 -111
- package/dist/routers/_app.js +5 -0
- package/dist/routers/auth.d.ts +11 -1
- package/dist/routers/chat.d.ts +11 -1
- package/dist/routers/flashcards.d.ts +205 -6
- package/dist/routers/flashcards.js +144 -66
- package/dist/routers/members.d.ts +165 -0
- package/dist/routers/members.js +531 -0
- package/dist/routers/podcast.d.ts +78 -63
- package/dist/routers/podcast.js +330 -393
- package/dist/routers/studyguide.d.ts +11 -1
- package/dist/routers/worksheets.d.ts +124 -13
- package/dist/routers/worksheets.js +123 -50
- package/dist/routers/workspace.d.ts +213 -26
- package/dist/routers/workspace.js +303 -181
- package/dist/server.js +12 -4
- package/dist/services/flashcard-progress.service.d.ts +183 -0
- package/dist/services/flashcard-progress.service.js +383 -0
- package/dist/services/flashcard.service.d.ts +183 -0
- package/dist/services/flashcard.service.js +224 -0
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +107 -0
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +326 -0
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +295 -0
- package/dist/trpc.d.ts +13 -2
- package/dist/trpc.js +55 -6
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.js +1 -0
- package/package.json +3 -2
- package/prisma/schema.prisma +142 -4
- package/src/lib/ai-session.ts +356 -85
- package/src/lib/auth.ts +113 -19
- package/src/lib/env.ts +59 -0
- package/src/lib/errors.ts +92 -0
- package/src/lib/inference.ts +11 -11
- package/src/lib/logger.ts +405 -0
- package/src/lib/pusher.ts +15 -3
- package/src/lib/storage.ts +56 -51
- package/src/lib/validation.ts +75 -0
- package/src/routers/_app.ts +5 -0
- package/src/routers/chat.ts +2 -23
- package/src/routers/flashcards.ts +108 -24
- package/src/routers/members.ts +586 -0
- package/src/routers/podcast.ts +385 -420
- package/src/routers/worksheets.ts +117 -35
- package/src/routers/workspace.ts +328 -195
- package/src/server.ts +13 -4
- package/src/services/flashcard-progress.service.ts +541 -0
- package/src/trpc.ts +59 -6
- package/src/types/index.ts +165 -0
- package/AUTH_FRONTEND_SPEC.md +0 -21
- package/CHAT_FRONTEND_SPEC.md +0 -474
- package/DATABASE_SETUP.md +0 -165
- package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
- package/PODCAST_FRONTEND_SPEC.md +0 -595
- package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
- package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
- package/WORKSPACE_FRONTEND_SPEC.md +0 -47
- package/test-ai-integration.js +0 -134
package/src/routers/workspace.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
|
3
3
|
import { router, publicProcedure, authedProcedure } from '../trpc.js';
|
|
4
|
-
import {
|
|
4
|
+
import { supabaseClient } from '../lib/storage.js';
|
|
5
5
|
import { ArtifactType } from '@prisma/client';
|
|
6
6
|
import { aiSessionService } from '../lib/ai-session.js';
|
|
7
7
|
import PusherService from '../lib/pusher.js';
|
|
8
|
+
import { members } from './members.js';
|
|
9
|
+
import type { PrismaClient } from '@prisma/client';
|
|
10
|
+
|
|
11
|
+
// Helper function to update and emit analysis progress
|
|
12
|
+
async function updateAnalysisProgress(
|
|
13
|
+
db: PrismaClient,
|
|
14
|
+
workspaceId: string,
|
|
15
|
+
progress: any
|
|
16
|
+
) {
|
|
17
|
+
await db.workspace.update({
|
|
18
|
+
where: { id: workspaceId },
|
|
19
|
+
data: { analysisProgress: progress }
|
|
20
|
+
});
|
|
21
|
+
await PusherService.emitAnalysisProgress(workspaceId, progress);
|
|
22
|
+
}
|
|
8
23
|
|
|
9
24
|
// Helper function to calculate search relevance score
|
|
10
25
|
function calculateRelevance(query: string, ...texts: (string | null | undefined)[]): number {
|
|
@@ -97,11 +112,14 @@ export const workspace = router({
|
|
|
97
112
|
},
|
|
98
113
|
},
|
|
99
114
|
});
|
|
115
|
+
|
|
116
|
+
aiSessionService.initSession(ws.id, ctx.session.user.id);
|
|
100
117
|
return ws;
|
|
101
118
|
}),
|
|
102
119
|
createFolder: authedProcedure
|
|
103
120
|
.input(z.object({
|
|
104
121
|
name: z.string().min(1).max(100),
|
|
122
|
+
color: z.string().optional(),
|
|
105
123
|
parentId: z.string().optional(),
|
|
106
124
|
}))
|
|
107
125
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -109,11 +127,30 @@ export const workspace = router({
|
|
|
109
127
|
data: {
|
|
110
128
|
name: input.name,
|
|
111
129
|
ownerId: ctx.session.user.id,
|
|
130
|
+
color: input.color ?? '#9D00FF',
|
|
112
131
|
parentId: input.parentId ?? null,
|
|
113
132
|
},
|
|
114
133
|
});
|
|
115
134
|
return folder;
|
|
116
135
|
}),
|
|
136
|
+
updateFolder: authedProcedure
|
|
137
|
+
.input(z.object({
|
|
138
|
+
id: z.string(),
|
|
139
|
+
name: z.string().min(1).max(100).optional(),
|
|
140
|
+
color: z.string().optional(),
|
|
141
|
+
}))
|
|
142
|
+
.mutation(async ({ ctx, input }) => {
|
|
143
|
+
const folder = await ctx.db.folder.update({ where: { id: input.id }, data: { name: input.name, color: input.color ?? '#9D00FF' } });
|
|
144
|
+
return folder;
|
|
145
|
+
}),
|
|
146
|
+
deleteFolder: authedProcedure
|
|
147
|
+
.input(z.object({
|
|
148
|
+
id: z.string(),
|
|
149
|
+
}))
|
|
150
|
+
.mutation(async ({ ctx, input }) => {
|
|
151
|
+
const folder = await ctx.db.folder.delete({ where: { id: input.id } });
|
|
152
|
+
return folder;
|
|
153
|
+
}),
|
|
117
154
|
get: authedProcedure
|
|
118
155
|
.input(z.object({
|
|
119
156
|
id: z.string(),
|
|
@@ -130,56 +167,30 @@ export const workspace = router({
|
|
|
130
167
|
if (!ws) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
131
168
|
return ws;
|
|
132
169
|
}),
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.query(async ({ ctx, input }) => {
|
|
138
|
-
const ws = await ctx.db.workspace.findFirst({
|
|
139
|
-
where: { id: input.id, ownerId: ctx.session.user.id },
|
|
170
|
+
getStats: authedProcedure
|
|
171
|
+
.query(async ({ ctx }) => {
|
|
172
|
+
const workspaces = await ctx.db.workspace.findMany({
|
|
173
|
+
where: {OR: [{ ownerId: ctx.session.user.id }, { sharedWith: { some: { id: ctx.session.user.id } } }]},
|
|
140
174
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// generate a unique share link if not exists
|
|
144
|
-
if (!ws.shareLink) {
|
|
145
|
-
const shareLink = [...Array(30)].map(() => (Math.random() * 36 | 0).toString(36)).join('');
|
|
146
|
-
const updated = await ctx.db.workspace.update({
|
|
147
|
-
where: { id: ws.id },
|
|
148
|
-
data: { shareLink },
|
|
149
|
-
});
|
|
150
|
-
return { shareLink: updated.shareLink };
|
|
151
|
-
}
|
|
152
|
-
}),
|
|
153
|
-
join: authedProcedure
|
|
154
|
-
.input(z.object({
|
|
155
|
-
shareLink: z.string().min(10).max(100),
|
|
156
|
-
}))
|
|
157
|
-
.mutation(async ({ ctx, input }) => {
|
|
158
|
-
const ws = await ctx.db.workspace.findFirst({
|
|
159
|
-
where: { shareLink: input.shareLink },
|
|
175
|
+
const folders = await ctx.db.folder.findMany({
|
|
176
|
+
where: {OR: [{ ownerId: ctx.session.user.id } ]},
|
|
160
177
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
// add to sharedWith if not already
|
|
166
|
-
const alreadyShared = await ctx.db.workspace.findFirst({
|
|
167
|
-
where: { id: ws.id, sharedWith: { some: { id: ctx.session.user.id } } },
|
|
178
|
+
const lastUpdated = await ctx.db.workspace.findFirst({
|
|
179
|
+
where: {OR: [{ ownerId: ctx.session.user.id }, { sharedWith: { some: { id: ctx.session.user.id } } }]},
|
|
180
|
+
orderBy: { updatedAt: 'desc' },
|
|
168
181
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
where: { id: ws.id },
|
|
174
|
-
data: { sharedWith: { connect: { id: ctx.session.user.id } } },
|
|
182
|
+
|
|
183
|
+
const spaceLeft = await ctx.db.fileAsset.aggregate({
|
|
184
|
+
where: { workspaceId: { in: workspaces.map(ws => ws.id) }, userId: ctx.session.user.id },
|
|
185
|
+
_sum: { size: true },
|
|
175
186
|
});
|
|
187
|
+
|
|
176
188
|
return {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
updatedAt: ws.updatedAt,
|
|
189
|
+
workspaces: workspaces.length,
|
|
190
|
+
folders: folders.length,
|
|
191
|
+
lastUpdated: lastUpdated?.updatedAt,
|
|
192
|
+
spaceUsed: spaceLeft._sum?.size ?? 0,
|
|
193
|
+
spaceLeft: 1000000000 - (spaceLeft._sum?.size ?? 0) || 0,
|
|
183
194
|
};
|
|
184
195
|
}),
|
|
185
196
|
update: authedProcedure
|
|
@@ -187,6 +198,8 @@ export const workspace = router({
|
|
|
187
198
|
id: z.string(),
|
|
188
199
|
name: z.string().min(1).max(100).optional(),
|
|
189
200
|
description: z.string().max(500).optional(),
|
|
201
|
+
color: z.string().optional(),
|
|
202
|
+
icon: z.string().optional(),
|
|
190
203
|
}))
|
|
191
204
|
.mutation(async ({ ctx, input }) => {
|
|
192
205
|
const existed = await ctx.db.workspace.findFirst({
|
|
@@ -198,6 +211,8 @@ export const workspace = router({
|
|
|
198
211
|
data: {
|
|
199
212
|
title: input.name ?? existed.title,
|
|
200
213
|
description: input.description,
|
|
214
|
+
color: input.color ?? existed.color,
|
|
215
|
+
icon: input.icon ?? existed.icon,
|
|
201
216
|
},
|
|
202
217
|
});
|
|
203
218
|
return updated;
|
|
@@ -264,26 +279,30 @@ export const workspace = router({
|
|
|
264
279
|
});
|
|
265
280
|
|
|
266
281
|
// 2. Generate signed URL for direct upload
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
282
|
+
const objectKey = `${ctx.session.user.id}/${record.id}-${file.filename}`;
|
|
283
|
+
const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
|
|
284
|
+
.from('files')
|
|
285
|
+
.createSignedUploadUrl(objectKey); // 5 minutes
|
|
286
|
+
|
|
287
|
+
if (signedUrlError) {
|
|
288
|
+
throw new TRPCError({
|
|
289
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
290
|
+
message: `Failed to generate upload URL: ${signedUrlError.message}`
|
|
273
291
|
});
|
|
292
|
+
}
|
|
274
293
|
|
|
275
294
|
// 3. Update record with bucket info
|
|
276
295
|
await ctx.db.fileAsset.update({
|
|
277
296
|
where: { id: record.id },
|
|
278
297
|
data: {
|
|
279
|
-
bucket:
|
|
280
|
-
objectKey:
|
|
298
|
+
bucket: 'files',
|
|
299
|
+
objectKey: objectKey,
|
|
281
300
|
},
|
|
282
301
|
});
|
|
283
302
|
|
|
284
303
|
results.push({
|
|
285
304
|
fileId: record.id,
|
|
286
|
-
uploadUrl:
|
|
305
|
+
uploadUrl: signedUrlData.signedUrl,
|
|
287
306
|
});
|
|
288
307
|
}
|
|
289
308
|
|
|
@@ -304,13 +323,15 @@ export const workspace = router({
|
|
|
304
323
|
userId: ctx.session.user.id,
|
|
305
324
|
},
|
|
306
325
|
});
|
|
307
|
-
// Delete from
|
|
326
|
+
// Delete from Supabase Storage (best-effort)
|
|
308
327
|
for (const file of files) {
|
|
309
328
|
if (file.bucket && file.objectKey) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
329
|
+
supabaseClient.storage
|
|
330
|
+
.from(file.bucket)
|
|
331
|
+
.remove([file.objectKey])
|
|
332
|
+
.catch((err: unknown) => {
|
|
333
|
+
console.error(`Error deleting file ${file.objectKey} from bucket ${file.bucket}:`, err);
|
|
334
|
+
});
|
|
314
335
|
}
|
|
315
336
|
}
|
|
316
337
|
|
|
@@ -354,12 +375,51 @@ export const workspace = router({
|
|
|
354
375
|
console.error('❌ Workspace not found', { workspaceId: input.workspaceId, userId: ctx.session.user.id });
|
|
355
376
|
throw new TRPCError({ code: 'NOT_FOUND' });
|
|
356
377
|
}
|
|
357
|
-
console.log('✅ Workspace verified', { workspaceId: workspace.id, workspaceTitle: workspace.title });
|
|
358
378
|
|
|
359
|
-
|
|
360
|
-
|
|
379
|
+
const fileType = input.file.contentType.startsWith('image/') ? 'image' : 'pdf';
|
|
380
|
+
|
|
381
|
+
await ctx.db.workspace.update({
|
|
382
|
+
where: { id: input.workspaceId },
|
|
383
|
+
data: { fileBeingAnalyzed: true },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
|
|
388
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
389
|
+
status: 'starting',
|
|
390
|
+
filename: input.file.filename,
|
|
391
|
+
fileType,
|
|
392
|
+
startedAt: new Date().toISOString(),
|
|
393
|
+
steps: {
|
|
394
|
+
fileUpload: {
|
|
395
|
+
order: 1,
|
|
396
|
+
status: 'pending',
|
|
397
|
+
},
|
|
398
|
+
fileAnalysis: {
|
|
399
|
+
order: 2,
|
|
400
|
+
status: 'pending',
|
|
401
|
+
},
|
|
402
|
+
studyGuide: {
|
|
403
|
+
order: 3,
|
|
404
|
+
status: input.generateStudyGuide ? 'pending' : 'skipped',
|
|
405
|
+
},
|
|
406
|
+
flashcards: {
|
|
407
|
+
order: 4,
|
|
408
|
+
status: input.generateFlashcards ? 'pending' : 'skipped',
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error('❌ Failed to update analysis progress:', error);
|
|
414
|
+
await ctx.db.workspace.update({
|
|
415
|
+
where: { id: input.workspaceId },
|
|
416
|
+
data: { fileBeingAnalyzed: false },
|
|
417
|
+
});
|
|
418
|
+
await PusherService.emitError(input.workspaceId, `Failed to update analysis progress: ${error}`, 'file_analysis');
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
|
|
361
422
|
const fileBuffer = Buffer.from(input.file.content, 'base64');
|
|
362
|
-
console.log('✅ File buffer created', { bufferSize: fileBuffer.length });
|
|
363
423
|
|
|
364
424
|
// // Check AI service health first
|
|
365
425
|
// console.log('🏥 Checking AI service health...');
|
|
@@ -374,41 +434,118 @@ export const workspace = router({
|
|
|
374
434
|
// }
|
|
375
435
|
// console.log('✅ AI service is healthy');
|
|
376
436
|
|
|
377
|
-
// Initialize AI session
|
|
378
|
-
console.log('🤖 Initializing AI session...');
|
|
379
|
-
const session = await aiSessionService.initSession(input.workspaceId, ctx.session.user.id);
|
|
380
|
-
console.log('✅ AI session initialized', { sessionId: session.id });
|
|
381
|
-
|
|
382
437
|
const fileObj = new File([fileBuffer], input.file.filename, { type: input.file.contentType });
|
|
383
|
-
const fileType = input.file.contentType.startsWith('image/') ? 'image' : 'pdf';
|
|
384
|
-
console.log('📤 Uploading file to AI service...', { filename: input.file.filename, fileType });
|
|
385
|
-
await aiSessionService.uploadFile(session.id, fileObj, fileType);
|
|
386
|
-
console.log('✅ File uploaded to AI service');
|
|
387
|
-
|
|
388
|
-
console.log('🚀 Starting LLM session...');
|
|
389
|
-
try {
|
|
390
|
-
await aiSessionService.startLLMSession(session.id);
|
|
391
|
-
console.log('✅ LLM session started');
|
|
392
|
-
} catch (error) {
|
|
393
|
-
console.error('❌ Failed to start LLM session:', error);
|
|
394
|
-
throw error;
|
|
395
|
-
}
|
|
396
438
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
439
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
440
|
+
status: 'uploading',
|
|
441
|
+
filename: input.file.filename,
|
|
442
|
+
fileType,
|
|
443
|
+
startedAt: new Date().toISOString(),
|
|
444
|
+
steps: {
|
|
445
|
+
fileUpload: {
|
|
446
|
+
order: 1,
|
|
447
|
+
status: 'in_progress',
|
|
448
|
+
},
|
|
449
|
+
fileAnalysis: {
|
|
450
|
+
order: 2,
|
|
451
|
+
status: 'pending',
|
|
452
|
+
},
|
|
453
|
+
studyGuide: {
|
|
454
|
+
order: 3,
|
|
455
|
+
status: input.generateStudyGuide ? 'pending' : 'skipped',
|
|
456
|
+
},
|
|
457
|
+
flashcards: {
|
|
458
|
+
order: 4,
|
|
459
|
+
status: input.generateFlashcards ? 'pending' : 'skipped',
|
|
460
|
+
},
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
await aiSessionService.uploadFile(input.workspaceId, ctx.session.user.id, fileObj, fileType);
|
|
465
|
+
|
|
466
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
467
|
+
status: 'analyzing',
|
|
468
|
+
filename: input.file.filename,
|
|
469
|
+
fileType,
|
|
470
|
+
startedAt: new Date().toISOString(),
|
|
471
|
+
steps: {
|
|
472
|
+
fileUpload: {
|
|
473
|
+
order: 1,
|
|
474
|
+
status: 'completed',
|
|
475
|
+
},
|
|
476
|
+
fileAnalysis: {
|
|
477
|
+
order: 2,
|
|
478
|
+
status: 'in_progress',
|
|
479
|
+
},
|
|
480
|
+
studyGuide: {
|
|
481
|
+
order: 3,
|
|
482
|
+
status: input.generateStudyGuide ? 'pending' : 'skipped',
|
|
483
|
+
},
|
|
484
|
+
flashcards: {
|
|
485
|
+
order: 4,
|
|
486
|
+
status: input.generateFlashcards ? 'pending' : 'skipped',
|
|
487
|
+
},
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
400
491
|
try {
|
|
401
492
|
if (fileType === 'image') {
|
|
402
|
-
await aiSessionService.analyseImage(session.id);
|
|
403
|
-
console.log('✅ Image analysis completed');
|
|
493
|
+
await aiSessionService.analyseImage(input.workspaceId, ctx.session.user.id);
|
|
404
494
|
} else {
|
|
405
|
-
await aiSessionService.analysePDF(session.id);
|
|
406
|
-
console.log('✅ PDF analysis completed');
|
|
495
|
+
await aiSessionService.analysePDF(input.workspaceId, ctx.session.user.id );
|
|
407
496
|
}
|
|
408
|
-
|
|
497
|
+
|
|
498
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
499
|
+
status: 'generating_artifacts',
|
|
500
|
+
filename: input.file.filename,
|
|
501
|
+
fileType,
|
|
502
|
+
startedAt: new Date().toISOString(),
|
|
503
|
+
steps: {
|
|
504
|
+
fileUpload: {
|
|
505
|
+
order: 1,
|
|
506
|
+
status: 'completed',
|
|
507
|
+
},
|
|
508
|
+
fileAnalysis: {
|
|
509
|
+
order: 2,
|
|
510
|
+
status: 'completed',
|
|
511
|
+
},
|
|
512
|
+
studyGuide: {
|
|
513
|
+
order: 3,
|
|
514
|
+
status: input.generateStudyGuide ? 'pending' : 'skipped',
|
|
515
|
+
},
|
|
516
|
+
flashcards: {
|
|
517
|
+
order: 4,
|
|
518
|
+
status: input.generateFlashcards ? 'pending' : 'skipped',
|
|
519
|
+
},
|
|
520
|
+
}
|
|
521
|
+
});
|
|
409
522
|
} catch (error) {
|
|
410
523
|
console.error('❌ Failed to analyze file:', error);
|
|
411
|
-
await
|
|
524
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
525
|
+
status: 'error',
|
|
526
|
+
filename: input.file.filename,
|
|
527
|
+
fileType,
|
|
528
|
+
error: `Failed to analyze ${fileType}: ${error}`,
|
|
529
|
+
startedAt: new Date().toISOString(),
|
|
530
|
+
steps: {
|
|
531
|
+
fileUpload: {
|
|
532
|
+
order: 1,
|
|
533
|
+
status: 'completed',
|
|
534
|
+
},
|
|
535
|
+
fileAnalysis: {
|
|
536
|
+
order: 2,
|
|
537
|
+
status: 'error',
|
|
538
|
+
},
|
|
539
|
+
studyGuide: {
|
|
540
|
+
order: 3,
|
|
541
|
+
status: 'skipped',
|
|
542
|
+
},
|
|
543
|
+
flashcards: {
|
|
544
|
+
order: 4,
|
|
545
|
+
status: 'skipped',
|
|
546
|
+
},
|
|
547
|
+
}
|
|
548
|
+
});
|
|
412
549
|
throw error;
|
|
413
550
|
}
|
|
414
551
|
|
|
@@ -430,11 +567,32 @@ export const workspace = router({
|
|
|
430
567
|
|
|
431
568
|
// Generate artifacts
|
|
432
569
|
if (input.generateStudyGuide) {
|
|
433
|
-
await
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
570
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
571
|
+
status: 'generating_study_guide',
|
|
572
|
+
filename: input.file.filename,
|
|
573
|
+
fileType,
|
|
574
|
+
startedAt: new Date().toISOString(),
|
|
575
|
+
steps: {
|
|
576
|
+
fileUpload: {
|
|
577
|
+
order: 1,
|
|
578
|
+
status: 'completed',
|
|
579
|
+
},
|
|
580
|
+
fileAnalysis: {
|
|
581
|
+
order: 2,
|
|
582
|
+
status: 'completed',
|
|
583
|
+
},
|
|
584
|
+
studyGuide: {
|
|
585
|
+
order: 3,
|
|
586
|
+
status: 'in_progress',
|
|
587
|
+
},
|
|
588
|
+
flashcards: {
|
|
589
|
+
order: 4,
|
|
590
|
+
status: input.generateFlashcards ? 'pending' : 'skipped',
|
|
591
|
+
},
|
|
592
|
+
}
|
|
593
|
+
});
|
|
437
594
|
|
|
595
|
+
const content = await aiSessionService.generateStudyGuide(input.workspaceId, ctx.session.user.id);
|
|
438
596
|
|
|
439
597
|
let artifact = await ctx.db.artifact.findFirst({
|
|
440
598
|
where: { workspaceId: input.workspaceId, type: ArtifactType.STUDY_GUIDE },
|
|
@@ -460,16 +618,35 @@ export const workspace = router({
|
|
|
460
618
|
});
|
|
461
619
|
|
|
462
620
|
results.artifacts.studyGuide = artifact;
|
|
463
|
-
|
|
464
|
-
// Emit Pusher notification
|
|
465
|
-
await PusherService.emitStudyGuideComplete(input.workspaceId, artifact);
|
|
466
621
|
}
|
|
467
622
|
|
|
468
623
|
if (input.generateFlashcards) {
|
|
469
|
-
await
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
624
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
625
|
+
status: 'generating_flashcards',
|
|
626
|
+
filename: input.file.filename,
|
|
627
|
+
fileType,
|
|
628
|
+
startedAt: new Date().toISOString(),
|
|
629
|
+
steps: {
|
|
630
|
+
fileUpload: {
|
|
631
|
+
order: 1,
|
|
632
|
+
status: 'completed',
|
|
633
|
+
},
|
|
634
|
+
fileAnalysis: {
|
|
635
|
+
order: 2,
|
|
636
|
+
status: 'completed',
|
|
637
|
+
},
|
|
638
|
+
studyGuide: {
|
|
639
|
+
order: 3,
|
|
640
|
+
status: input.generateStudyGuide ? 'completed' : 'skipped',
|
|
641
|
+
},
|
|
642
|
+
flashcards: {
|
|
643
|
+
order: 4,
|
|
644
|
+
status: 'in_progress',
|
|
645
|
+
},
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const content = await aiSessionService.generateFlashcardQuestions(input.workspaceId, ctx.session.user.id, 10, 'medium');
|
|
473
650
|
|
|
474
651
|
const artifact = await ctx.db.artifact.create({
|
|
475
652
|
data: {
|
|
@@ -482,7 +659,7 @@ export const workspace = router({
|
|
|
482
659
|
|
|
483
660
|
// Parse JSON flashcard content
|
|
484
661
|
try {
|
|
485
|
-
const flashcardData =
|
|
662
|
+
const flashcardData: any = content;
|
|
486
663
|
|
|
487
664
|
let createdCards = 0;
|
|
488
665
|
for (let i = 0; i < Math.min(flashcardData.length, 10); i++) {
|
|
@@ -523,101 +700,38 @@ export const workspace = router({
|
|
|
523
700
|
}
|
|
524
701
|
|
|
525
702
|
results.artifacts.flashcards = artifact;
|
|
526
|
-
|
|
527
|
-
// Emit Pusher notification
|
|
528
|
-
await PusherService.emitFlashcardComplete(input.workspaceId, artifact);
|
|
529
703
|
}
|
|
530
704
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
705
|
+
await ctx.db.workspace.update({
|
|
706
|
+
where: { id: input.workspaceId },
|
|
707
|
+
data: { fileBeingAnalyzed: false },
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
await updateAnalysisProgress(ctx.db, input.workspaceId, {
|
|
711
|
+
status: 'completed',
|
|
712
|
+
filename: input.file.filename,
|
|
713
|
+
fileType,
|
|
714
|
+
startedAt: new Date().toISOString(),
|
|
715
|
+
completedAt: new Date().toISOString(),
|
|
716
|
+
steps: {
|
|
717
|
+
fileUpload: {
|
|
718
|
+
order: 1,
|
|
719
|
+
status: 'completed',
|
|
720
|
+
},
|
|
721
|
+
fileAnalysis: {
|
|
722
|
+
order: 2,
|
|
723
|
+
status: 'completed',
|
|
724
|
+
},
|
|
725
|
+
studyGuide: {
|
|
726
|
+
order: 3,
|
|
727
|
+
status: input.generateStudyGuide ? 'completed' : 'skipped',
|
|
728
|
+
},
|
|
729
|
+
flashcards: {
|
|
730
|
+
order: 4,
|
|
731
|
+
status: input.generateFlashcards ? 'completed' : 'skipped',
|
|
542
732
|
},
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
// Parse JSON worksheet content
|
|
546
|
-
try {
|
|
547
|
-
const worksheetData = JSON.parse(content);
|
|
548
|
-
|
|
549
|
-
// The actual worksheet data is in last_response as JSON
|
|
550
|
-
let actualWorksheetData = worksheetData;
|
|
551
|
-
if (worksheetData.last_response) {
|
|
552
|
-
try {
|
|
553
|
-
actualWorksheetData = JSON.parse(worksheetData.last_response);
|
|
554
|
-
} catch (parseError) {
|
|
555
|
-
console.error('❌ Failed to parse last_response JSON:', parseError);
|
|
556
|
-
console.log('📋 Raw last_response:', worksheetData.last_response);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// Handle different JSON structures
|
|
561
|
-
const problems = actualWorksheetData.problems || actualWorksheetData.questions || actualWorksheetData || [];
|
|
562
|
-
let createdQuestions = 0;
|
|
563
|
-
|
|
564
|
-
for (let i = 0; i < Math.min(problems.length, 8); i++) {
|
|
565
|
-
const problem = problems[i];
|
|
566
|
-
const prompt = problem.question || problem.prompt || `Question ${i + 1}`;
|
|
567
|
-
const answer = problem.answer || problem.solution || `Answer ${i + 1}`;
|
|
568
|
-
const type = problem.type || 'TEXT';
|
|
569
|
-
const options = problem.options || [];
|
|
570
|
-
|
|
571
|
-
await ctx.db.worksheetQuestion.create({
|
|
572
|
-
data: {
|
|
573
|
-
artifactId: artifact.id,
|
|
574
|
-
prompt: prompt,
|
|
575
|
-
answer: answer,
|
|
576
|
-
difficulty: 'MEDIUM' as any,
|
|
577
|
-
order: i,
|
|
578
|
-
meta: {
|
|
579
|
-
type: type,
|
|
580
|
-
options: options.length > 0 ? options : undefined
|
|
581
|
-
},
|
|
582
|
-
},
|
|
583
|
-
});
|
|
584
|
-
createdQuestions++;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
} catch (parseError) {
|
|
588
|
-
console.error('❌ Failed to parse worksheet JSON, using fallback parsing:', parseError);
|
|
589
|
-
// Fallback to text parsing if JSON fails
|
|
590
|
-
const lines = content.split('\n').filter(line => line.trim());
|
|
591
|
-
for (let i = 0; i < Math.min(lines.length, 8); i++) {
|
|
592
|
-
const line = lines[i];
|
|
593
|
-
if (line.includes(' - ')) {
|
|
594
|
-
const [prompt, answer] = line.split(' - ');
|
|
595
|
-
await ctx.db.worksheetQuestion.create({
|
|
596
|
-
data: {
|
|
597
|
-
artifactId: artifact.id,
|
|
598
|
-
prompt: prompt.trim(),
|
|
599
|
-
answer: answer.trim(),
|
|
600
|
-
difficulty: 'MEDIUM' as any,
|
|
601
|
-
order: i,
|
|
602
|
-
meta: { type: 'TEXT', },
|
|
603
|
-
},
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
733
|
}
|
|
608
|
-
|
|
609
|
-
results.artifacts.worksheet = artifact;
|
|
610
|
-
|
|
611
|
-
// Emit Pusher notification
|
|
612
|
-
await PusherService.emitWorksheetComplete(input.workspaceId, artifact);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
await PusherService.emitTaskComplete(input.workspaceId, 'analysis_cleanup_start', { filename: input.file.filename });
|
|
616
|
-
aiSessionService.deleteSession(session.id);
|
|
617
|
-
await PusherService.emitTaskComplete(input.workspaceId, 'analysis_cleanup_complete', { filename: input.file.filename });
|
|
618
|
-
|
|
619
|
-
// Emit overall completion notification
|
|
620
|
-
await PusherService.emitOverallComplete(input.workspaceId, input.file.filename, results.artifacts);
|
|
734
|
+
});
|
|
621
735
|
|
|
622
736
|
return results;
|
|
623
737
|
}),
|
|
@@ -651,7 +765,26 @@ export const workspace = router({
|
|
|
651
765
|
},
|
|
652
766
|
take: input.limit,
|
|
653
767
|
});
|
|
768
|
+
|
|
769
|
+
// Update analysisProgress for each workspace with search metadata
|
|
770
|
+
const workspaceUpdates = workspaces.map(ws =>
|
|
771
|
+
ctx.db.workspace.update({
|
|
772
|
+
where: { id: ws.id },
|
|
773
|
+
data: {
|
|
774
|
+
analysisProgress: {
|
|
775
|
+
lastSearched: new Date().toISOString(),
|
|
776
|
+
searchQuery: query,
|
|
777
|
+
matchedIn: ws.title.toLowerCase().includes(query.toLowerCase()) ? 'title' : 'description',
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
await Promise.all(workspaceUpdates);
|
|
784
|
+
|
|
654
785
|
return workspaces;
|
|
655
|
-
})
|
|
786
|
+
}),
|
|
656
787
|
|
|
788
|
+
// Members sub-router
|
|
789
|
+
members,
|
|
657
790
|
});
|