Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

// AI service (chainlearn-ai) used for quiz generation
AI_SERVICE_URL: z.string().url().default("http://localhost:8000"),
AI_TIMEOUT_MS: z.coerce.number().default(30_000),
});

export type Env = z.infer<typeof envSchema>;
Expand Down Expand Up @@ -88,6 +89,6 @@
// Lazy config — loadConfig() only runs on first property access, not at import time
export const config: Env = new Proxy({} as Env, {
get(_, prop) {
return (ensureConfig() as any)[prop];

Check warning on line 92 in src/config/index.ts

View workflow job for this annotation

GitHub Actions / Lint & Typecheck

Unexpected any. Specify a different type
},
});
56 changes: 35 additions & 21 deletions src/modules/quizzes/ai-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,40 @@ export interface GenerateQuizFromAIParams {
export async function generateQuizFromAI(
params: GenerateQuizFromAIParams
): Promise<AiQuizQuestion[]> {
const response = await fetch(`${config.AI_SERVICE_URL}/generate-quiz`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: params.userId,
course_id: params.courseId,
module_id: params.moduleId,
difficulty: params.difficulty,
num_questions: params.numQuestions,
}),
});

if (!response.ok) {
logger.error(
{ status: response.status },
"AI service quiz generation failed"
);
throw new Error(`AI service returned ${response.status}`);
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), config.AI_TIMEOUT_MS);

try {
const response = await fetch(`${config.AI_SERVICE_URL}/generate-quiz`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: params.userId,
course_id: params.courseId,
module_id: params.moduleId,
difficulty: params.difficulty,
num_questions: params.numQuestions,
}),
signal: controller.signal,
});

const data = (await response.json()) as AiQuizResponse;
return data.questions;
if (!response.ok) {
logger.error(
{ status: response.status },
"AI service quiz generation failed"
);
throw new Error(`AI service returned ${response.status}`);
}

const data = (await response.json()) as AiQuizResponse;
return data.questions;
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
logger.error({ timeout: config.AI_TIMEOUT_MS }, "AI service request timed out");
throw new Error(`AI service request timed out after ${config.AI_TIMEOUT_MS}ms`);
}
throw err;
} finally {
clearTimeout(timeout);
}
}
Loading