@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,3 +1,4 @@
1
+ import { MarkScheme, UserMarkScheme } from '../types/index.js';
1
2
  export interface AISession {
2
3
  id: string;
3
4
  workspaceId: string;
@@ -9,15 +10,25 @@ export interface AISession {
9
10
  }
10
11
  export declare class AISessionService {
11
12
  private sessions;
12
- initSession(workspaceId: string): Promise<AISession>;
13
- uploadFile(sessionId: string, file: File, fileType: 'image' | 'pdf'): Promise<void>;
14
- setInstruction(sessionId: string, instructionText: string): Promise<void>;
15
- startLLMSession(sessionId: string): Promise<void>;
16
- generateStudyGuide(sessionId: string): Promise<string>;
17
- generateFlashcardQuestions(sessionId: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string>;
18
- generateWorksheetQuestions(sessionId: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string>;
19
- analysePDF(sessionId: string): Promise<string>;
20
- analyseImage(sessionId: string): Promise<string>;
13
+ initSession(workspaceId: string, user: string): Promise<AISession>;
14
+ uploadFile(sessionId: string, user: string, file: File, fileType: 'image' | 'pdf'): Promise<void>;
15
+ generateStudyGuide(sessionId: string, user: string): Promise<string>;
16
+ generateFlashcardQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'easy' | 'medium' | 'hard'): Promise<string>;
17
+ generateWorksheetQuestions(sessionId: string, user: string, numQuestions: number, difficulty: 'EASY' | 'MEDIUM' | 'HARD'): Promise<string>;
18
+ checkWorksheetQuestions(sessionId: string, user: string, question: string, answer: string, mark_scheme: MarkScheme): Promise<UserMarkScheme>;
19
+ generatePodcastStructure(sessionId: string, user: string, title: string, description: string, prompt: string, speakers: Array<{
20
+ id: string;
21
+ role: string;
22
+ name?: string;
23
+ }>): Promise<any>;
24
+ generatePodcastAudioFromText(sessionId: string, user: string, podcastId: string, segmentIndex: number, text: string, speakers: Array<{
25
+ id: string;
26
+ role: string;
27
+ name?: string;
28
+ }>, voiceId?: string): Promise<any>;
29
+ analysePDF(sessionId: string, user: string): Promise<string>;
30
+ analyseImage(sessionId: string, user: string): Promise<string>;
31
+ generatePodcastImage(sessionId: string, user: string, summary: string): Promise<string>;
21
32
  getSession(sessionId: string): AISession | undefined;
22
33
  getSessionsByUserAndWorkspace(userId: string, workspaceId: string): AISession[];
23
34
  deleteSession(sessionId: string): boolean;
@@ -1,17 +1,41 @@
1
1
  import { TRPCError } from '@trpc/server';
2
+ import { logger } from './logger.js';
2
3
  // External AI service configuration
3
- const AI_SERVICE_URL = 'https://txp-tckxn64wn5vtgip72-kzoemq8qw-custom.service.onethingrobot.com/upload';
4
- const AI_RESPONSE_URL = 'https://txp-tckxn64wn5vtgip72-kzoemq8qw-custom.service.onethingrobot.com/last_response';
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 = process.env.INFERENCE_API_URL + '/upload';
7
+ const AI_RESPONSE_URL = process.env.INFERENCE_API_URL + '/last_response';
8
+ console.log('AI_SERVICE_URL', AI_SERVICE_URL);
9
+ console.log('AI_RESPONSE_URL', AI_RESPONSE_URL);
10
+ // Mock mode flag - when true, returns fake responses instead of calling AI service
11
+ const MOCK_MODE = process.env.DONT_TEST_INFERENCE === 'true';
12
+ const IMITATE_WAIT_TIME_MS = MOCK_MODE ? 1000 * 10 : 0;
5
13
  export class AISessionService {
6
14
  constructor() {
7
15
  this.sessions = new Map();
8
16
  }
9
17
  // Initialize a new AI session
10
- async initSession(workspaceId) {
18
+ async initSession(workspaceId, user) {
11
19
  const sessionId = `${workspaceId}`;
20
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
21
+ // Mock mode - return fake session
22
+ if (MOCK_MODE) {
23
+ console.log(`🎭 MOCK MODE: Initializing AI session for workspace ${workspaceId}`);
24
+ const session = {
25
+ id: sessionId,
26
+ workspaceId,
27
+ status: 'initialized',
28
+ files: [],
29
+ createdAt: new Date(),
30
+ updatedAt: new Date(),
31
+ };
32
+ this.sessions.set(sessionId, session);
33
+ return session;
34
+ }
12
35
  const formData = new FormData();
13
36
  formData.append('command', 'init_session');
14
- formData.append('id', sessionId);
37
+ formData.append('session', sessionId);
38
+ formData.append('user', user);
15
39
  // Retry logic for AI service
16
40
  const maxRetries = 3;
17
41
  let lastError = null;
@@ -63,15 +87,26 @@ export class AISessionService {
63
87
  });
64
88
  }
65
89
  // Upload file to AI session
66
- async uploadFile(sessionId, file, fileType) {
90
+ async uploadFile(sessionId, user, file, fileType) {
67
91
  const session = this.sessions.get(sessionId);
68
92
  if (!session) {
69
93
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
70
94
  }
95
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
96
+ // Mock mode - simulate successful file upload
97
+ if (MOCK_MODE) {
98
+ console.log(`🎭 MOCK MODE: Uploading ${fileType} file "${file.name}" to session ${sessionId}`);
99
+ session.files.push(file.name);
100
+ session.updatedAt = new Date();
101
+ this.sessions.set(sessionId, session);
102
+ return;
103
+ }
71
104
  const command = fileType === 'image' ? 'append_image' : 'append_pdflike';
72
105
  const formData = new FormData();
73
106
  formData.append('command', command);
74
107
  formData.append('file', file);
108
+ formData.append('session', sessionId);
109
+ formData.append('user', user);
75
110
  try {
76
111
  const response = await fetch(AI_SERVICE_URL, {
77
112
  method: 'POST',
@@ -97,15 +132,40 @@ export class AISessionService {
97
132
  });
98
133
  }
99
134
  }
100
- // Set instruction text
101
- async setInstruction(sessionId, instructionText) {
135
+ // Generate study guide
136
+ async generateStudyGuide(sessionId, user) {
102
137
  const session = this.sessions.get(sessionId);
103
138
  if (!session) {
104
139
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
105
140
  }
141
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
142
+ // Mock mode - return fake study guide
143
+ if (MOCK_MODE) {
144
+ console.log(`🎭 MOCK MODE: Generating study guide for session ${sessionId}`);
145
+ return `# Mock Study Guide
146
+
147
+ ## Overview
148
+ 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.
149
+
150
+ ## Key Concepts
151
+ 1. **Concept A**: This is a mock concept that would be derived from the uploaded materials
152
+ 2. **Concept B**: Another mock concept with detailed explanations
153
+ 3. **Concept C**: A third concept with examples and applications
154
+
155
+ ## Summary
156
+ This mock study guide demonstrates the structure and format that would be generated by the AI service when processing uploaded educational materials.
157
+
158
+ ## Practice Questions
159
+ 1. What is the main topic covered in this material?
160
+ 2. How do the key concepts relate to each other?
161
+ 3. What are the practical applications of these concepts?
162
+
163
+ *Note: This is a mock response generated when DONT_TEST_INFERENCE=true*`;
164
+ }
106
165
  const formData = new FormData();
107
- formData.append('command', 'set_instruct');
108
- formData.append('instruction_text', instructionText);
166
+ formData.append('command', 'generate_study_guide');
167
+ formData.append('session', sessionId);
168
+ formData.append('user', user);
109
169
  try {
110
170
  const response = await fetch(AI_SERVICE_URL, {
111
171
  method: 'POST',
@@ -115,30 +175,39 @@ export class AISessionService {
115
175
  throw new Error(`AI service error: ${response.status} ${response.statusText}`);
116
176
  }
117
177
  const result = await response.json();
118
- console.log(`📋 Set instruction result:`, result);
119
- if (!result.message) {
120
- throw new Error(`AI service error: No response message`);
121
- }
122
- // Update session
123
- session.instructionText = instructionText;
124
- session.updatedAt = new Date();
125
- this.sessions.set(sessionId, session);
178
+ return result.markdown;
126
179
  }
127
180
  catch (error) {
128
181
  throw new TRPCError({
129
182
  code: 'INTERNAL_SERVER_ERROR',
130
- message: `Failed to set instruction: ${error instanceof Error ? error.message : 'Unknown error'}`,
183
+ message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
131
184
  });
132
185
  }
133
186
  }
134
- // Start LLM session
135
- async startLLMSession(sessionId) {
136
- const session = this.sessions.get(sessionId);
137
- if (!session) {
138
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
187
+ // Generate flashcard questions
188
+ async generateFlashcardQuestions(sessionId, user, numQuestions, difficulty) {
189
+ // const session = this.sessions.get(sessionId);
190
+ // if (!session) {
191
+ // throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
192
+ // }
193
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
194
+ // Mock mode - return fake flashcard questions
195
+ if (MOCK_MODE) {
196
+ logger.info(`🎭 MOCK MODE: Generating ${numQuestions} ${difficulty} flashcard questions for session ${sessionId}`);
197
+ return JSON.stringify(Array.from({ length: numQuestions }, (_, i) => ({
198
+ id: `mock-flashcard-${i + 1}`,
199
+ question: `Mock question ${i + 1}: What is the main concept covered in this material?`,
200
+ answer: `Mock answer ${i + 1}: This is a sample answer that would be generated based on the uploaded content.`,
201
+ difficulty: difficulty,
202
+ category: `Mock Category ${(i % 3) + 1}`
203
+ })));
139
204
  }
140
205
  const formData = new FormData();
141
- formData.append('command', 'start_LLM_session');
206
+ formData.append('command', 'generate_flashcard_questions');
207
+ formData.append('session', sessionId);
208
+ formData.append('user', user);
209
+ formData.append('num_questions', numQuestions.toString());
210
+ formData.append('difficulty', difficulty);
142
211
  try {
143
212
  const response = await fetch(AI_SERVICE_URL, {
144
213
  method: 'POST',
@@ -148,30 +217,51 @@ export class AISessionService {
148
217
  throw new Error(`AI service error: ${response.status} ${response.statusText}`);
149
218
  }
150
219
  const result = await response.json();
151
- console.log(`📋 Start LLM result:`, result);
152
- if (!result.message) {
153
- throw new Error(`AI service error: No response message`);
154
- }
155
- // Update session
156
- session.status = 'ready';
157
- session.updatedAt = new Date();
158
- this.sessions.set(sessionId, session);
220
+ console.log(JSON.parse(result.flashcards));
221
+ return JSON.parse(result.flashcards).flashcards;
159
222
  }
160
223
  catch (error) {
161
224
  throw new TRPCError({
162
225
  code: 'INTERNAL_SERVER_ERROR',
163
- message: `Failed to start LLM session: ${error instanceof Error ? error.message : 'Unknown error'}`,
226
+ message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
164
227
  });
165
228
  }
166
229
  }
167
- // Generate study guide
168
- async generateStudyGuide(sessionId) {
169
- const session = this.sessions.get(sessionId);
170
- if (!session) {
171
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
230
+ // Generate worksheet questions
231
+ async generateWorksheetQuestions(sessionId, user, numQuestions, difficulty) {
232
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
233
+ // Mock mode - return fake worksheet questions
234
+ if (MOCK_MODE) {
235
+ logger.info(`🎭 MOCK MODE: Generating ${numQuestions} ${difficulty} worksheet questions for session ${sessionId}`);
236
+ return JSON.stringify({
237
+ worksheet: {
238
+ title: `Mock Worksheet - ${difficulty} Level`,
239
+ questions: Array.from({ length: numQuestions }, (_, i) => ({
240
+ id: `mock-worksheet-q${i + 1}`,
241
+ question: `Mock worksheet question ${i + 1}: Based on the uploaded material, explain the key concept and provide examples.`,
242
+ type: i % 2 === 0 ? 'short_answer' : 'essay',
243
+ difficulty: difficulty,
244
+ estimatedTime: difficulty === 'EASY' ? '2-3 minutes' : difficulty === 'MEDIUM' ? '5-7 minutes' : '10-15 minutes',
245
+ points: difficulty === 'EASY' ? 5 : difficulty === 'MEDIUM' ? 10 : 15
246
+ })),
247
+ instructions: "This is a mock worksheet generated for testing purposes. Answer all questions based on the uploaded materials.",
248
+ totalPoints: numQuestions * (difficulty === 'EASY' ? 5 : difficulty === 'MEDIUM' ? 10 : 15),
249
+ estimatedTime: `${numQuestions * (difficulty === 'EASY' ? 3 : difficulty === 'MEDIUM' ? 6 : 12)} minutes`
250
+ },
251
+ metadata: {
252
+ totalQuestions: numQuestions,
253
+ difficulty: difficulty,
254
+ generatedAt: new Date().toISOString(),
255
+ isMock: true
256
+ }
257
+ });
172
258
  }
173
259
  const formData = new FormData();
174
- formData.append('command', 'generate_study_guide');
260
+ formData.append('command', 'generate_worksheet_questions');
261
+ formData.append('session', sessionId);
262
+ formData.append('user', user);
263
+ formData.append('num_questions', numQuestions.toString());
264
+ formData.append('difficulty', difficulty);
175
265
  try {
176
266
  const response = await fetch(AI_SERVICE_URL, {
177
267
  method: 'POST',
@@ -180,92 +270,181 @@ export class AISessionService {
180
270
  if (!response.ok) {
181
271
  throw new Error(`AI service error: ${response.status} ${response.statusText}`);
182
272
  }
183
- // Get the generated content from the response endpoint
184
- const contentResponse = await fetch(AI_RESPONSE_URL);
185
- if (!contentResponse.ok) {
186
- throw new Error(`Failed to retrieve generated content: ${contentResponse.status}`);
187
- }
188
- return (await contentResponse.json())['last_response'];
273
+ const result = await response.json();
274
+ console.log(JSON.parse(result.worksheet));
275
+ return result.worksheet;
189
276
  }
190
277
  catch (error) {
191
278
  throw new TRPCError({
192
279
  code: 'INTERNAL_SERVER_ERROR',
193
- message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
280
+ message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
194
281
  });
195
282
  }
196
283
  }
197
- // Generate flashcard questions
198
- async generateFlashcardQuestions(sessionId, numQuestions, difficulty) {
199
- const session = this.sessions.get(sessionId);
200
- if (!session) {
201
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
284
+ async checkWorksheetQuestions(sessionId, user, question, answer, mark_scheme) {
285
+ const formData = new FormData();
286
+ formData.append('command', 'mark_worksheet_questions');
287
+ formData.append('session', sessionId);
288
+ formData.append('user', user);
289
+ formData.append('question', question);
290
+ formData.append('answer', answer);
291
+ formData.append('mark_scheme', JSON.stringify(mark_scheme));
292
+ const response = await fetch(AI_SERVICE_URL, {
293
+ method: 'POST',
294
+ body: formData,
295
+ });
296
+ if (!response.ok) {
297
+ throw new Error(`AI service error: ${response.status} ${response.statusText}`);
298
+ }
299
+ const result = await response.json();
300
+ console.log(result.marking);
301
+ return JSON.parse(result.marking);
302
+ }
303
+ // Generate podcast structure
304
+ async generatePodcastStructure(sessionId, user, title, description, prompt, speakers) {
305
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
306
+ // Mock mode - return fake podcast structure
307
+ if (MOCK_MODE) {
308
+ logger.info(`🎭 MOCK MODE: Generating podcast structure for session ${sessionId}`);
309
+ return {
310
+ success: true,
311
+ structure: {
312
+ episodeTitle: `${title} - AI Generated Episode`,
313
+ totalEstimatedDuration: "15 minutes",
314
+ segments: [
315
+ {
316
+ title: "Welcome & Introduction",
317
+ 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...",
318
+ speaker: "dialogue",
319
+ voiceId: speakers[0]?.id || "mock-voice-1",
320
+ keyPoints: ["Introduction", "What to expect"],
321
+ estimatedDuration: "3 minutes",
322
+ order: 1
323
+ },
324
+ {
325
+ title: "Main Discussion",
326
+ content: "This is the main content section where we explore the key concepts in detail. We'll cover various aspects and provide practical examples.",
327
+ speaker: "host",
328
+ voiceId: speakers[0]?.id || "mock-voice-1",
329
+ keyPoints: ["Key concept 1", "Key concept 2"],
330
+ estimatedDuration: "8 minutes",
331
+ order: 2
332
+ },
333
+ {
334
+ title: "Conclusion & Takeaways",
335
+ 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!",
336
+ speaker: "dialogue",
337
+ voiceId: speakers[0]?.id || "mock-voice-1",
338
+ keyPoints: ["Summary", "Next steps"],
339
+ estimatedDuration: "4 minutes",
340
+ order: 3
341
+ }
342
+ ]
343
+ }
344
+ };
202
345
  }
203
346
  const formData = new FormData();
204
- formData.append('command', 'generate_flashcard_questions');
205
- formData.append('num_questions', numQuestions.toString());
206
- formData.append('difficulty', difficulty);
347
+ formData.append('command', 'generate_podcast_structure');
348
+ formData.append('user', user);
349
+ formData.append('session', sessionId);
350
+ formData.append('title', title);
351
+ formData.append('description', description);
352
+ formData.append('prompt', prompt);
353
+ formData.append('speakers', JSON.stringify(speakers));
207
354
  try {
208
355
  const response = await fetch(AI_SERVICE_URL, {
209
356
  method: 'POST',
210
357
  body: formData,
211
358
  });
212
359
  if (!response.ok) {
213
- throw new Error(`AI service error: ${response.status} ${response.statusText}`);
214
- }
215
- // Get the generated content from the response endpoint
216
- const contentResponse = await fetch(AI_RESPONSE_URL);
217
- if (!contentResponse.ok) {
218
- throw new Error(`Failed to retrieve generated content: ${contentResponse.status}`);
360
+ const errorText = await response.text();
361
+ throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
219
362
  }
220
- return (await contentResponse.json())['last_response'];
363
+ const result = await response.json();
364
+ return result;
221
365
  }
222
366
  catch (error) {
223
367
  throw new TRPCError({
224
368
  code: 'INTERNAL_SERVER_ERROR',
225
- message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
369
+ message: `Failed to generate podcast structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
226
370
  });
227
371
  }
228
372
  }
229
- // Generate worksheet questions
230
- async generateWorksheetQuestions(sessionId, numQuestions, difficulty) {
231
- const session = this.sessions.get(sessionId);
232
- if (!session) {
233
- throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
373
+ // Generate podcast audio from text
374
+ async generatePodcastAudioFromText(sessionId, user, podcastId, segmentIndex, text, speakers, voiceId) {
375
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
376
+ // Mock mode - return fake audio generation result
377
+ if (MOCK_MODE) {
378
+ logger.info(`🎭 MOCK MODE: Generating audio for segment ${segmentIndex} of podcast ${podcastId}`);
379
+ const isDialogue = text.includes('HOST:') || text.includes('GUEST:');
380
+ return {
381
+ success: true,
382
+ segmentIndex: segmentIndex,
383
+ objectKey: `${user}/${sessionId}/podcasts/${podcastId}/segment_${segmentIndex}.mp3`,
384
+ duration: 45 + Math.floor(Math.random() * 30), // Random duration between 45-75 seconds
385
+ type: isDialogue ? 'dialogue' : 'monologue',
386
+ ...(isDialogue && { partCount: 4 })
387
+ };
234
388
  }
235
389
  const formData = new FormData();
236
- formData.append('command', 'generate_worksheet_questions');
237
- formData.append('num_questions', numQuestions.toString());
238
- formData.append('difficulty', difficulty);
390
+ formData.append('command', 'generate_podcast_audio_from_text');
391
+ formData.append('user', user);
392
+ formData.append('session', sessionId);
393
+ formData.append('podcast_id', podcastId);
394
+ formData.append('segment_index', segmentIndex.toString());
395
+ formData.append('text', text);
396
+ formData.append('speakers', JSON.stringify(speakers));
397
+ if (voiceId) {
398
+ formData.append('voice_id', voiceId);
399
+ }
239
400
  try {
240
401
  const response = await fetch(AI_SERVICE_URL, {
241
402
  method: 'POST',
242
403
  body: formData,
243
404
  });
244
405
  if (!response.ok) {
245
- throw new Error(`AI service error: ${response.status} ${response.statusText}`);
406
+ const errorText = await response.text();
407
+ throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
246
408
  }
247
- // Get the generated content from the response endpoint
248
- const contentResponse = await fetch(AI_RESPONSE_URL);
249
- if (!contentResponse.ok) {
250
- throw new Error(`Failed to retrieve generated content: ${contentResponse.status}`);
251
- }
252
- return (await contentResponse.json())['last_response'];
409
+ const result = await response.json();
410
+ return result;
253
411
  }
254
412
  catch (error) {
255
413
  throw new TRPCError({
256
414
  code: 'INTERNAL_SERVER_ERROR',
257
- message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
415
+ message: `Failed to generate podcast audio: ${error instanceof Error ? error.message : 'Unknown error'}`,
258
416
  });
259
417
  }
260
418
  }
261
419
  // Analyse PDF
262
- async analysePDF(sessionId) {
420
+ async analysePDF(sessionId, user) {
263
421
  const session = this.sessions.get(sessionId);
264
422
  if (!session) {
265
423
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
266
424
  }
425
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
426
+ // Mock mode - return fake PDF analysis
427
+ if (MOCK_MODE) {
428
+ console.log(`🎭 MOCK MODE: Analysing PDF for session ${sessionId}`);
429
+ return `Mock PDF Analysis Results:
430
+
431
+ Document Type: Educational Material
432
+ Pages: 15
433
+ Language: English
434
+ Key Topics Identified:
435
+ - Introduction to Machine Learning
436
+ - Data Preprocessing Techniques
437
+ - Model Training and Validation
438
+ - Performance Metrics
439
+
440
+ 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.
441
+
442
+ Note: This is a mock response generated when DONT_TEST_INFERENCE=true`;
443
+ }
267
444
  const formData = new FormData();
268
445
  formData.append('command', 'analyse_pdf');
446
+ formData.append('session', sessionId);
447
+ formData.append('user', user);
269
448
  try {
270
449
  const response = await fetch(AI_SERVICE_URL, {
271
450
  method: 'POST',
@@ -285,13 +464,40 @@ export class AISessionService {
285
464
  }
286
465
  }
287
466
  // Analyse Image
288
- async analyseImage(sessionId) {
467
+ async analyseImage(sessionId, user) {
289
468
  const session = this.sessions.get(sessionId);
290
469
  if (!session) {
291
470
  throw new TRPCError({ code: 'NOT_FOUND', message: 'AI session not found' });
292
471
  }
472
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
473
+ // Mock mode - return fake image analysis
474
+ if (MOCK_MODE) {
475
+ console.log(`🎭 MOCK MODE: Analysing image for session ${sessionId}`);
476
+ return `Mock Image Analysis Results:
477
+
478
+ Image Type: Educational Diagram
479
+ Format: PNG
480
+ Dimensions: 1920x1080
481
+ Content Description:
482
+ - Contains a flowchart or diagram
483
+ - Shows a process or system architecture
484
+ - Includes text labels and annotations
485
+ - Educational or instructional content
486
+
487
+ Key Elements Identified:
488
+ - Process flow arrows
489
+ - Decision points
490
+ - Input/output elements
491
+ - Descriptive text
492
+
493
+ 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.
494
+
495
+ Note: This is a mock response generated when DONT_TEST_INFERENCE=true`;
496
+ }
293
497
  const formData = new FormData();
294
498
  formData.append('command', 'analyse_img');
499
+ formData.append('session', sessionId);
500
+ formData.append('user', user);
295
501
  try {
296
502
  const response = await fetch(AI_SERVICE_URL, {
297
503
  method: 'POST',
@@ -310,6 +516,30 @@ export class AISessionService {
310
516
  });
311
517
  }
312
518
  }
519
+ async generatePodcastImage(sessionId, user, summary) {
520
+ const formData = new FormData();
521
+ formData.append('command', 'generate_podcast_image');
522
+ formData.append('session', sessionId);
523
+ formData.append('user', user);
524
+ formData.append('summary', summary);
525
+ try {
526
+ const response = await fetch(AI_SERVICE_URL, {
527
+ method: 'POST',
528
+ body: formData,
529
+ });
530
+ if (!response.ok) {
531
+ throw new Error(`AI service error: ${response.status} ${response.statusText}`);
532
+ }
533
+ const result = await response.json();
534
+ return result.image_key;
535
+ }
536
+ catch (error) {
537
+ throw new TRPCError({
538
+ code: 'INTERNAL_SERVER_ERROR',
539
+ message: `Failed to generate podcast image: ${error instanceof Error ? error.message : 'Unknown error'}`,
540
+ });
541
+ }
542
+ }
313
543
  // Get session by ID
314
544
  getSession(sessionId) {
315
545
  return this.sessions.get(sessionId);
@@ -324,6 +554,12 @@ export class AISessionService {
324
554
  }
325
555
  // Check if AI service is available
326
556
  async checkHealth() {
557
+ await new Promise(resolve => setTimeout(resolve, IMITATE_WAIT_TIME_MS));
558
+ // Mock mode - always return healthy
559
+ if (MOCK_MODE) {
560
+ console.log('🎭 MOCK MODE: AI service health check - returning healthy');
561
+ return true;
562
+ }
327
563
  try {
328
564
  console.log('🏥 Checking AI service health...');
329
565
  const response = await fetch(AI_SERVICE_URL, {
@@ -1,3 +1,36 @@
1
- export declare function verifyCustomAuthCookie(cookieValue: string | undefined): {
1
+ /**
2
+ * Authentication utilities for custom HMAC-based cookie verification.
3
+ *
4
+ * This module provides secure authentication using HMAC-SHA256 signatures
5
+ * to verify user identity through signed cookies.
6
+ *
7
+ * @fileoverview Custom authentication system with HMAC cookie verification
8
+ * @author Scribe Team
9
+ * @version 1.0.0
10
+ */
11
+ /**
12
+ * Represents the result of successful authentication verification.
13
+ */
14
+ export interface AuthResult {
15
+ /** The authenticated user's unique identifier */
2
16
  userId: string;
3
- } | null;
17
+ }
18
+ /**
19
+ * Verifies a custom HMAC-signed authentication cookie.
20
+ *
21
+ * The cookie format is: `base64(userId).hex(hmacSHA256(base64(userId), secret))`
22
+ *
23
+ * @param cookieValue - The raw cookie value to verify, or undefined if no cookie exists
24
+ * @returns Authentication result with userId if valid, null if invalid or missing
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const result = verifyCustomAuthCookie("dXNlcjEyMw.abc123def456...");
29
+ * if (result) {
30
+ * console.log(`Authenticated user: ${result.userId}`);
31
+ * }
32
+ * ```
33
+ *
34
+ * @throws {Error} Never throws - returns null for all error conditions
35
+ */
36
+ export declare function verifyCustomAuthCookie(cookieValue: string | undefined): AuthResult | null;