@goscribe/server 1.0.11 → 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/dist/lib/ai-session.d.ts
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
package/dist/lib/ai-session.js
CHANGED
|
@@ -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://
|
|
4
|
-
const AI_RESPONSE_URL = 'https://
|
|
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('
|
|
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
|
-
//
|
|
101
|
-
async
|
|
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', '
|
|
108
|
-
formData.append('
|
|
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
|
-
|
|
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
|
|
183
|
+
message: `Failed to generate study guide: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
131
184
|
});
|
|
132
185
|
}
|
|
133
186
|
}
|
|
134
|
-
//
|
|
135
|
-
async
|
|
136
|
-
const session = this.sessions.get(sessionId);
|
|
137
|
-
if (!session) {
|
|
138
|
-
|
|
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', '
|
|
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(
|
|
152
|
-
|
|
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
|
|
226
|
+
message: `Failed to generate flashcard questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
164
227
|
});
|
|
165
228
|
}
|
|
166
229
|
}
|
|
167
|
-
// Generate
|
|
168
|
-
async
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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', '
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
280
|
+
message: `Failed to generate worksheet questions: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
194
281
|
});
|
|
195
282
|
}
|
|
196
283
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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', '
|
|
205
|
-
formData.append('
|
|
206
|
-
formData.append('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
369
|
+
message: `Failed to generate podcast structure: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
226
370
|
});
|
|
227
371
|
}
|
|
228
372
|
}
|
|
229
|
-
// Generate
|
|
230
|
-
async
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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', '
|
|
237
|
-
formData.append('
|
|
238
|
-
formData.append('
|
|
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
|
-
|
|
406
|
+
const errorText = await response.text();
|
|
407
|
+
throw new Error(`AI service error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
246
408
|
}
|
|
247
|
-
|
|
248
|
-
|
|
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
|
|
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, {
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
|
|
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
|
-
}
|
|
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;
|