@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/lib/ai-session.ts
CHANGED
|
@@ -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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
|
223
|
+
message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
166
224
|
});
|
|
167
225
|
}
|
|
168
226
|
}
|
|
169
227
|
|
|
170
|
-
//
|
|
171
|
-
async
|
|
172
|
-
const session = this.sessions.get(sessionId);
|
|
173
|
-
if (!session) {
|
|
174
|
-
|
|
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', '
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
273
|
+
message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
204
274
|
});
|
|
205
275
|
}
|
|
206
276
|
}
|
|
207
277
|
|
|
208
|
-
// Generate
|
|
209
|
-
async
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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', '
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
334
|
+
message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
238
335
|
});
|
|
239
336
|
}
|
|
240
337
|
}
|
|
241
338
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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', '
|
|
251
|
-
formData.append('
|
|
252
|
-
formData.append('
|
|
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
|
-
|
|
431
|
+
const errorText = await response.text();
|
|
432
|
+
throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
262
433
|
}
|
|
263
434
|
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
440
|
+
message: `Failed to generate podcast structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
275
441
|
});
|
|
276
442
|
}
|
|
277
443
|
}
|
|
278
444
|
|
|
279
|
-
// Generate
|
|
280
|
-
async
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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', '
|
|
288
|
-
formData.append('
|
|
289
|
-
formData.append('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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, {
|