@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
@@ -1,8 +1,18 @@
1
1
  import { TRPCError } from '@trpc/server';
2
+ import { logger } from './logger.js';
3
+ import { MarkScheme, UserMarkScheme } from '../types/index.js';
2
4
 
3
5
  // External AI service configuration
4
- const AI_SERVICE_URL = 'https://7gzvf7uib04yp9-61016.proxy.runpod.net/upload';
5
- const AI_RESPONSE_URL = 'https://7gzvf7uib04yp9-61016.proxy.runpod.net/last_response';
6
+ // const AI_SERVICE_URL = 'https://7gzvf7uib04yp9-61016.proxy.runpod.net/upload';
7
+ // const AI_RESPONSE_URL = 'https://7gzvf7uib04yp9-61016.proxy.runpod.net/last_response';
8
+ const AI_SERVICE_URL = process.env.INFERENCE_API_URL + '/upload';
9
+ const AI_RESPONSE_URL = process.env.INFERENCE_API_URL + '/last_response';
10
+
11
+ console.log('AI_SERVICE_URL', AI_SERVICE_URL);
12
+ console.log('AI_RESPONSE_URL', AI_RESPONSE_URL);
13
+
14
+ // Mock mode flag - when true, returns fake responses instead of calling AI service
15
+ const MOCK_MODE = process.env.DONT_TEST_INFERENCE === 'true';
6
16
 
7
17
  export interface AISession {
8
18
  id: string;
@@ -14,12 +24,30 @@ export interface AISession {
14
24
  updatedAt: Date;
15
25
  }
16
26
 
27
+ const IMITATE_WAIT_TIME_MS = MOCK_MODE ? 1000 * 10 : 0;
28
+
17
29
  export class AISessionService {
18
30
  private sessions = new Map<string, AISession>();
19
31
 
20
32
  // Initialize a new AI session
21
33
  async initSession(workspaceId: string, user: string): Promise<AISession> {
22
34
  const sessionId = `${workspaceId}`;
35
+
36
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
37
+ // Mock mode - return fake session
38
+ if (MOCK_MODE) {
39
+ console.log(`🎭 MOCK MODE: Initializing AI session for workspace ${workspaceId}`);
40
+ const session: AISession = {
41
+ id: sessionId,
42
+ workspaceId,
43
+ status: 'initialized',
44
+ files: [],
45
+ createdAt: new Date(),
46
+ updatedAt: new Date(),
47
+ };
48
+ this.sessions.set(sessionId, session);
49
+ return session;
50
+ }
23
51
 
24
52
  const formData = new FormData();
25
53
  formData.append('command', 'init_session');
@@ -88,18 +116,29 @@ export class AISessionService {
88
116
  }
89
117
 
90
118
  // Upload file to AI session
91
- async uploadFile(sessionId: string, file: File, fileType: 'image' | 'pdf'): Promise<void> {
119
+ async uploadFile(sessionId: string, user: string, file: File, fileType: 'image' | 'pdf'): Promise<void> {
92
120
  const session = this.sessions.get(sessionId);
93
121
  if (!session) {
94
122
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
95
123
  }
96
124
 
125
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
126
+ // Mock mode - simulate successful file upload
127
+ if (MOCK_MODE) {
128
+ console.log(`🎭 MOCK MODE: Uploading ${fileType} file "${file.name}" to session ${sessionId}`);
129
+ session.files.push(file.name);
130
+ session.updatedAt = new Date();
131
+ this.sessions.set(sessionId, session);
132
+ return;
133
+ }
134
+
97
135
  const command = fileType === 'image' ? 'append_image' : 'append_pdflike';
98
136
 
99
137
  const formData = new FormData();
100
138
  formData.append('command', command);
101
139
  formData.append('file', file);
102
-
140
+ formData.append('session', sessionId);
141
+ formData.append('user', user);
103
142
  try {
104
143
  const response = await fetch(AI_SERVICE_URL, {
105
144
  method: 'POST',
@@ -128,17 +167,44 @@ export class AISessionService {
128
167
  }
129
168
  }
130
169
 
131
- // Set instruction text
132
- async setInstruction(sessionId: string, instructionText: string): Promise<void> {
170
+
171
+
172
+ // Generate study guide
173
+ async generateStudyGuide(sessionId: string, user: string): Promise<string> {
133
174
  const session = this.sessions.get(sessionId);
134
175
  if (!session) {
135
176
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
136
177
  }
137
178
 
138
- const formData = new FormData();
139
- formData.append('command', 'set_instruct');
140
- formData.append('instruction_text', instructionText);
179
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
180
+ // Mock mode - return fake study guide
181
+ if (MOCK_MODE) {
182
+ console.log(`🎭 MOCK MODE: Generating study guide for session ${sessionId}`);
183
+ return `# Mock Study Guide
184
+
185
+ ## Overview
186
+ This is a mock study guide generated for testing purposes. In a real scenario, this would contain comprehensive study material based on the uploaded content.
187
+
188
+ ## Key Concepts
189
+ 1. **Concept A**: This is a mock concept that would be derived from the uploaded materials
190
+ 2. **Concept B**: Another mock concept with detailed explanations
191
+ 3. **Concept C**: A third concept with examples and applications
141
192
 
193
+ ## Summary
194
+ This mock study guide demonstrates the structure and format that would be generated by the AI service when processing uploaded educational materials.
195
+
196
+ ## Practice Questions
197
+ 1. What is the main topic covered in this material?
198
+ 2. How do the key concepts relate to each other?
199
+ 3. What are the practical applications of these concepts?
200
+
201
+ *Note: This is a mock response generated when DONT_TEST_INFERENCE=true*`;
202
+ }
203
+
204
+ const formData = new FormData();
205
+ formData.append('command', 'generate_study_guide');
206
+ formData.append('session', sessionId);
207
+ formData.append('user', user);
142
208
  try {
143
209
  const response = await fetch(AI_SERVICE_URL, {
144
210
  method: 'POST',
@@ -150,32 +216,41 @@ export class AISessionService {
150
216
  }
151
217
 
152
218
  const result = await response.json();
153
- console.log(`📋 Set instruction result:`, result);
154
- if (!result.message) {
155
- throw new Error(`AI service error: No response message`);
156
- }
157
-
158
- // Update session
159
- session.instructionText = instructionText;
160
- session.updatedAt = new Date();
161
- this.sessions.set(sessionId, session);
219
+ return result.markdown;
162
220
  } catch (error) {
163
221
  throw new TRPCError({
164
222
  code: 'INTERNAL_SERVER_ERROR',
165
- message: `Failed to set instruction: ${error instanceof Error ? error.message : 'Unknown error'}`,
223
+ message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
166
224
  });
167
225
  }
168
226
  }
169
227
 
170
- // Start LLM session
171
- async startLLMSession(sessionId: string): Promise<void> {
172
- const session = this.sessions.get(sessionId);
173
- if (!session) {
174
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
228
+ // Generate flashcard questions
229
+ async generateFlashcardQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string> {
230
+ // const session = this.sessions.get(sessionId);
231
+ // if (!session) {
232
+ // throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
233
+ // }
234
+
235
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
236
+ // Mock mode - return fake flashcard questions
237
+ if (MOCK_MODE) {
238
+ logger.info(`🎭 MOCK MODE: Generating ${numQuestions} ${difficulty} flashcard questions for session ${sessionId}`);
239
+ return JSON.stringify(Array.from({ length: numQuestions }, (_, i) => ({
240
+ id: `mock-flashcard-${i + 1}`,
241
+ question: `Mock question ${i + 1}: What is the main concept covered in this material?`,
242
+ answer: `Mock answer ${i + 1}: This is a sample answer that would be generated based on the uploaded content.`,
243
+ difficulty: difficulty,
244
+ category: `Mock Category ${(i % 3) + 1}`
245
+ })));
175
246
  }
176
247
 
177
248
  const formData = new FormData();
178
- formData.append('command', 'start_LLM_session');
249
+ formData.append('command', 'generate_flashcard_questions');
250
+ formData.append('session', sessionId);
251
+ formData.append('user', user);
252
+ formData.append('num_questions', numQuestions.toString());
253
+ formData.append('difficulty', difficulty);
179
254
 
180
255
  try {
181
256
  const response = await fetch(AI_SERVICE_URL, {
@@ -188,32 +263,55 @@ export class AISessionService {
188
263
  }
189
264
 
190
265
  const result = await response.json();
191
- console.log(`📋 Start LLM result:`, result);
192
- if (!result.message) {
193
- throw new Error(`AI service error: No response message`);
194
- }
195
266
 
196
- // Update session
197
- session.status = 'ready';
198
- session.updatedAt = new Date();
199
- this.sessions.set(sessionId, session);
267
+ console.log(JSON.parse(result.flashcards))
268
+
269
+ return JSON.parse(result.flashcards).flashcards;
200
270
  } catch (error) {
201
271
  throw new TRPCError({
202
272
  code: 'INTERNAL_SERVER_ERROR',
203
- message: `Failed to start LLM session: ${error instanceof Error ? error.message : 'Unknown error'}`,
273
+ message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
204
274
  });
205
275
  }
206
276
  }
207
277
 
208
- // Generate study guide
209
- async generateStudyGuide(sessionId: string): Promise<string> {
210
- const session = this.sessions.get(sessionId);
211
- if (!session) {
212
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
278
+ // Generate worksheet questions
279
+ async generateWorksheetQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'EASY' | 'MEDIUM' | 'HARD'): Promise<string> {
280
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
281
+ // Mock mode - return fake worksheet questions
282
+ if (MOCK_MODE) {
283
+ logger.info(`🎭 MOCK MODE: Generating ${numQuestions} ${difficulty} worksheet questions for session ${sessionId}`);
284
+ return JSON.stringify({
285
+ worksheet: {
286
+ title: `Mock Worksheet - ${difficulty} Level`,
287
+ questions: Array.from({ length: numQuestions }, (_, i) => ({
288
+ id: `mock-worksheet-q${i + 1}`,
289
+ question: `Mock worksheet question ${i + 1}: Based on the uploaded material, explain the key concept and provide examples.`,
290
+ type: i % 2 === 0 ? 'short_answer' : 'essay',
291
+ difficulty: difficulty,
292
+ estimatedTime: difficulty === 'EASY' ? '2-3 minutes' : difficulty === 'MEDIUM' ? '5-7 minutes' : '10-15 minutes',
293
+ points: difficulty === 'EASY' ? 5 : difficulty === 'MEDIUM' ? 10 : 15
294
+ })),
295
+ instructions: "This is a mock worksheet generated for testing purposes. Answer all questions based on the uploaded materials.",
296
+ totalPoints: numQuestions * (difficulty === 'EASY' ? 5 : difficulty === 'MEDIUM' ? 10 : 15),
297
+ estimatedTime: `${numQuestions * (difficulty === 'EASY' ? 3 : difficulty === 'MEDIUM' ? 6 : 12)} minutes`
298
+ },
299
+ metadata: {
300
+ totalQuestions: numQuestions,
301
+ difficulty: difficulty,
302
+ generatedAt: new Date().toISOString(),
303
+ isMock: true
304
+ }
305
+ });
213
306
  }
214
307
 
215
308
  const formData = new FormData();
216
- formData.append('command', 'generate_study_guide');
309
+ formData.append('command', 'generate_worksheet_questions');
310
+ formData.append('session', sessionId);
311
+ formData.append('user', user);
312
+ formData.append('num_questions', numQuestions.toString());
313
+ formData.append('difficulty', difficulty);
314
+
217
315
 
218
316
  try {
219
317
  const response = await fetch(AI_SERVICE_URL, {
@@ -225,31 +323,103 @@ export class AISessionService {
225
323
  throw new Error(`AI service error: ${response.status} ${response.statusText}`);
226
324
  }
227
325
 
228
- // Get the generated content from the response endpoint
229
- const contentResponse = await fetch(AI_RESPONSE_URL);
230
- if (!contentResponse.ok) {
231
- throw new Error(`Failed to retrieve generated content: ${contentResponse.status}`);
232
- }
233
- return (await contentResponse.json())['last_response'];
326
+ const result = await response.json();
327
+
328
+ console.log(JSON.parse(result.worksheet));
329
+
330
+ return result.worksheet;
234
331
  } catch (error) {
235
332
  throw new TRPCError({
236
333
  code: 'INTERNAL_SERVER_ERROR',
237
- message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
334
+ message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
238
335
  });
239
336
  }
240
337
  }
241
338
 
242
- // Generate flashcard questions
243
- async generateFlashcardQuestions(sessionId: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string> {
244
- const session = this.sessions.get(sessionId);
245
- if (!session) {
246
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
339
+ async checkWorksheetQuestions(sessionId: string, user: string, question: string, answer: string, mark_scheme: MarkScheme): Promise<UserMarkScheme> {
340
+ const formData = new FormData();
341
+
342
+ formData.append('command', 'mark_worksheet_questions');
343
+ formData.append('session', sessionId);
344
+ formData.append('user', user);
345
+ formData.append('question', question);
346
+ formData.append('answer', answer);
347
+ formData.append('mark_scheme', JSON.stringify(mark_scheme));
348
+
349
+ const response = await fetch(AI_SERVICE_URL, {
350
+ method: 'POST',
351
+ body: formData,
352
+ });
353
+
354
+ if (!response.ok) {
355
+ throw new Error(`AI service error: ${response.status} ${response.statusText}`);
356
+ }
357
+
358
+ const result = await response.json();
359
+ console.log(result.marking);
360
+ return JSON.parse(result.marking);
361
+ }
362
+
363
+ // Generate podcast structure
364
+ async generatePodcastStructure(
365
+ sessionId: string,
366
+ user: string,
367
+ title: string,
368
+ description: string,
369
+ prompt: string,
370
+ speakers: Array<{ id: string; role: string; name?: string }>
371
+ ): Promise<any> {
372
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
373
+
374
+ // Mock mode - return fake podcast structure
375
+ if (MOCK_MODE) {
376
+ logger.info(`🎭 MOCK MODE: Generating podcast structure for session ${sessionId}`);
377
+ return {
378
+ success: true,
379
+ structure: {
380
+ episodeTitle: `${title} - AI Generated Episode`,
381
+ totalEstimatedDuration: "15 minutes",
382
+ segments: [
383
+ {
384
+ title: "Welcome & Introduction",
385
+ content: "HOST: Welcome to today's episode!\nGUEST: Thanks for having me!\nHOST: Let's dive into the topic.\nGUEST: Great! Let's start with the basics...",
386
+ speaker: "dialogue",
387
+ voiceId: speakers[0]?.id || "mock-voice-1",
388
+ keyPoints: ["Introduction", "What to expect"],
389
+ estimatedDuration: "3 minutes",
390
+ order: 1
391
+ },
392
+ {
393
+ title: "Main Discussion",
394
+ content: "This is the main content section where we explore the key concepts in detail. We'll cover various aspects and provide practical examples.",
395
+ speaker: "host",
396
+ voiceId: speakers[0]?.id || "mock-voice-1",
397
+ keyPoints: ["Key concept 1", "Key concept 2"],
398
+ estimatedDuration: "8 minutes",
399
+ order: 2
400
+ },
401
+ {
402
+ title: "Conclusion & Takeaways",
403
+ content: "HOST: Let's wrap up what we've learned today.\nGUEST: Yes, the main takeaway is...\nHOST: Thanks for joining us!\nGUEST: Thank you!",
404
+ speaker: "dialogue",
405
+ voiceId: speakers[0]?.id || "mock-voice-1",
406
+ keyPoints: ["Summary", "Next steps"],
407
+ estimatedDuration: "4 minutes",
408
+ order: 3
409
+ }
410
+ ]
411
+ }
412
+ };
247
413
  }
248
414
 
249
415
  const formData = new FormData();
250
- formData.append('command', 'generate_flashcard_questions');
251
- formData.append('num_questions', numQuestions.toString());
252
- formData.append('difficulty', difficulty);
416
+ formData.append('command', 'generate_podcast_structure');
417
+ formData.append('user', user);
418
+ formData.append('session', sessionId);
419
+ formData.append('title', title);
420
+ formData.append('description', description);
421
+ formData.append('prompt', prompt);
422
+ formData.append('speakers', JSON.stringify(speakers));
253
423
 
254
424
  try {
255
425
  const response = await fetch(AI_SERVICE_URL, {
@@ -258,35 +428,58 @@ export class AISessionService {
258
428
  });
259
429
 
260
430
  if (!response.ok) {
261
- throw new Error(`AI service error: ${response.status} ${response.statusText}`);
431
+ const errorText = await response.text();
432
+ throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
262
433
  }
263
434
 
264
- // Get the generated content from the response endpoint
265
- const contentResponse = await fetch(AI_RESPONSE_URL);
266
- if (!contentResponse.ok) {
267
- throw new Error(`Failed to retrieve generated content: ${contentResponse.status}`);
268
- }
269
-
270
- return (await contentResponse.json())['last_response'];
435
+ const result = await response.json();
436
+ return result;
271
437
  } catch (error) {
272
438
  throw new TRPCError({
273
439
  code: 'INTERNAL_SERVER_ERROR',
274
- message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
440
+ message: `Failed to generate podcast structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
275
441
  });
276
442
  }
277
443
  }
278
444
 
279
- // Generate worksheet questions
280
- async generateWorksheetQuestions(sessionId: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string> {
281
- const session = this.sessions.get(sessionId);
282
- if (!session) {
283
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
445
+ // Generate podcast audio from text
446
+ async generatePodcastAudioFromText(
447
+ sessionId: string,
448
+ user: string,
449
+ podcastId: string,
450
+ segmentIndex: number,
451
+ text: string,
452
+ speakers: Array<{ id: string; role: string; name?: string }>,
453
+ voiceId?: string
454
+ ): Promise<any> {
455
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
456
+
457
+ // Mock mode - return fake audio generation result
458
+ if (MOCK_MODE) {
459
+ logger.info(`🎭 MOCK MODE: Generating audio for segment ${segmentIndex} of podcast ${podcastId}`);
460
+ const isDialogue = text.includes('HOST:') || text.includes('GUEST:');
461
+ return {
462
+ success: true,
463
+ segmentIndex: segmentIndex,
464
+ objectKey: `${user}/${sessionId}/podcasts/${podcastId}/segment_${segmentIndex}.mp3`,
465
+ duration: 45 + Math.floor(Math.random() * 30), // Random duration between 45-75 seconds
466
+ type: isDialogue ? 'dialogue' : 'monologue',
467
+ ...(isDialogue && { partCount: 4 })
468
+ };
284
469
  }
285
470
 
286
471
  const formData = new FormData();
287
- formData.append('command', 'generate_worksheet_questions');
288
- formData.append('num_questions', numQuestions.toString());
289
- formData.append('difficulty', difficulty);
472
+ formData.append('command', 'generate_podcast_audio_from_text');
473
+ formData.append('user', user);
474
+ formData.append('session', sessionId);
475
+ formData.append('podcast_id', podcastId);
476
+ formData.append('segment_index', segmentIndex.toString());
477
+ formData.append('text', text);
478
+ formData.append('speakers', JSON.stringify(speakers));
479
+
480
+ if (voiceId) {
481
+ formData.append('voice_id', voiceId);
482
+ }
290
483
 
291
484
  try {
292
485
  const response = await fetch(AI_SERVICE_URL, {
@@ -295,34 +488,52 @@ export class AISessionService {
295
488
  });
296
489
 
297
490
  if (!response.ok) {
298
- throw new Error(`AI service error: ${response.status} ${response.statusText}`);
299
- }
300
-
301
- // Get the generated content from the response endpoint
302
- const contentResponse = await fetch(AI_RESPONSE_URL);
303
- if (!contentResponse.ok) {
304
- throw new Error(`Failed to retrieve generated content: ${contentResponse.status}`);
491
+ const errorText = await response.text();
492
+ throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
305
493
  }
306
494
 
307
- return (await contentResponse.json())['last_response'];
495
+ const result = await response.json();
496
+ return result;
308
497
  } catch (error) {
309
498
  throw new TRPCError({
310
499
  code: 'INTERNAL_SERVER_ERROR',
311
- message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
500
+ message: `Failed to generate podcast audio: ${error instanceof Error ? error.message : 'Unknown error'}`,
312
501
  });
313
502
  }
314
503
  }
504
+
315
505
 
316
506
  // Analyse PDF
317
- async analysePDF(sessionId: string): Promise<string> {
507
+ async analysePDF(sessionId: string, user: string): Promise<string> {
318
508
  const session = this.sessions.get(sessionId);
319
509
  if (!session) {
320
510
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
321
511
  }
322
512
 
513
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
514
+ // Mock mode - return fake PDF analysis
515
+ if (MOCK_MODE) {
516
+ console.log(`🎭 MOCK MODE: Analysing PDF for session ${sessionId}`);
517
+ return `Mock PDF Analysis Results:
518
+
519
+ Document Type: Educational Material
520
+ Pages: 15
521
+ Language: English
522
+ Key Topics Identified:
523
+ - Introduction to Machine Learning
524
+ - Data Preprocessing Techniques
525
+ - Model Training and Validation
526
+ - Performance Metrics
527
+
528
+ Summary: This mock PDF analysis shows the structure and content that would be extracted from an uploaded PDF document. The analysis includes document metadata, key topics, and a summary of the content.
529
+
530
+ Note: This is a mock response generated when DONT_TEST_INFERENCE=true`;
531
+ }
532
+
323
533
  const formData = new FormData();
324
534
  formData.append('command', 'analyse_pdf');
325
-
535
+ formData.append('session', sessionId);
536
+ formData.append('user', user);
326
537
  try {
327
538
  const response = await fetch(AI_SERVICE_URL, {
328
539
  method: 'POST',
@@ -344,15 +555,42 @@ export class AISessionService {
344
555
  }
345
556
 
346
557
  // Analyse Image
347
- async analyseImage(sessionId: string): Promise<string> {
558
+ async analyseImage(sessionId: string, user: string): Promise<string> {
348
559
  const session = this.sessions.get(sessionId);
349
560
  if (!session) {
350
561
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
351
562
  }
352
563
 
564
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
565
+ // Mock mode - return fake image analysis
566
+ if (MOCK_MODE) {
567
+ console.log(`🎭 MOCK MODE: Analysing image for session ${sessionId}`);
568
+ return `Mock Image Analysis Results:
569
+
570
+ Image Type: Educational Diagram
571
+ Format: PNG
572
+ Dimensions: 1920x1080
573
+ Content Description:
574
+ - Contains a flowchart or diagram
575
+ - Shows a process or system architecture
576
+ - Includes text labels and annotations
577
+ - Educational or instructional content
578
+
579
+ Key Elements Identified:
580
+ - Process flow arrows
581
+ - Decision points
582
+ - Input/output elements
583
+ - Descriptive text
584
+
585
+ Summary: This mock image analysis demonstrates the type of content extraction that would be performed on uploaded images. The analysis identifies visual elements, text content, and overall structure.
586
+
587
+ Note: This is a mock response generated when DONT_TEST_INFERENCE=true`;
588
+ }
589
+
353
590
  const formData = new FormData();
354
591
  formData.append('command', 'analyse_img');
355
-
592
+ formData.append('session', sessionId);
593
+ formData.append('user', user);
356
594
  try {
357
595
  const response = await fetch(AI_SERVICE_URL, {
358
596
  method: 'POST',
@@ -373,6 +611,32 @@ export class AISessionService {
373
611
  }
374
612
  }
375
613
 
614
+ async generatePodcastImage(sessionId: string, user: string, summary: string): Promise<string> {
615
+
616
+ const formData = new FormData();
617
+ formData.append('command', 'generate_podcast_image');
618
+ formData.append('session', sessionId);
619
+ formData.append('user', user);
620
+ formData.append('summary', summary);
621
+ try {
622
+ const response = await fetch(AI_SERVICE_URL, {
623
+ method: 'POST',
624
+ body: formData,
625
+ });
626
+
627
+ if (!response.ok) {
628
+ throw new Error(`AI service error: ${response.status} ${response.statusText}`);
629
+ }
630
+
631
+ const result = await response.json();
632
+ return result.image_key;
633
+ } catch (error) {
634
+ throw new TRPCError({
635
+ code: 'INTERNAL_SERVER_ERROR',
636
+ message: `Failed to generate podcast image: ${error instanceof Error ? error.message : 'Unknown error'}`,
637
+ });
638
+ }
639
+ }
376
640
  // Get session by ID
377
641
  getSession(sessionId: string): AISession | undefined {
378
642
  return this.sessions.get(sessionId);
@@ -392,6 +656,13 @@ export class AISessionService {
392
656
 
393
657
  // Check if AI service is available
394
658
  async checkHealth(): Promise<boolean> {
659
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
660
+ // Mock mode - always return healthy
661
+ if (MOCK_MODE) {
662
+ console.log('🎭 MOCK MODE: AI service health check - returning healthy');
663
+ return true;
664
+ }
665
+
395
666
  try {
396
667
  console.log('🏥 Checking AI service health...');
397
668
  const response = await fetch(AI_SERVICE_URL, {