Skip to content
Open
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
3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ GEMINI_API_KEY=your_gemini_api_key_here
# Gemini model to use (defaults to gemini-1.5-flash-latest if unset)
GEMINI_MODEL=gemini-1.5-flash-latest

# Timeout duration for Gemini API calls in milliseconds (defaults to 30000 if unset)
GEMINI_TIMEOUT=30000

# GitHub Configuration
# Personal access token for fetching book data from GitHub
# Create at: https://github.com/settings/tokens
Expand Down
39 changes: 37 additions & 2 deletions backend/controllers/aiController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {

// Initialize Gemini with API key from .env
const ai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const GEMINI_TIMEOUT = parseInt(process.env.GEMINI_TIMEOUT, 10) || 30000;

// @desc Generate interview questions and answers using Gemini
// @route POST /api/ai/generate-questions
Expand Down Expand Up @@ -37,17 +38,28 @@ const generateInterviewQuestions = async (req, res) => {
let result = null;
let usedModel = null;
for (const m of candidateModels) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), GEMINI_TIMEOUT);
try {
console.log(`Trying model: ${m}`);
const model = ai.getGenerativeModel({ model: m });
const model = ai.getGenerativeModel({ model: m }, { signal: controller.signal });
result = await model.generateContent([prompt]);
usedModel = m;
console.log(`Successfully used model: ${m}`);
break;
} catch (e) {
if (e.name === "AbortError" || (e.message && e.message.includes("abort"))) {
const timeoutErr = new Error(`Gemini API call timed out after ${GEMINI_TIMEOUT}ms for model ${m}`);
timeoutErr.name = "TimeoutError";
console.error(`[Timeout] Model ${m} timed out:`, timeoutErr.message);
lastErr = timeoutErr;
break;
}
console.error(`Model ${m} failed:`, e.message);
lastErr = e;
continue;
} finally {
clearTimeout(timeoutId);
}
}
if (!result) throw lastErr || new Error("All Gemini models failed");
Expand Down Expand Up @@ -76,6 +88,12 @@ const generateInterviewQuestions = async (req, res) => {
}
} catch (error) {
console.error("Gemini API Error:", error); // Log the error
if (error.name === "TimeoutError") {
return res.status(504).json({
message: "Request timed out",
error: error.message,
});
}
res.status(500).json({
message: "Failed to generate questions",
error: error.message,
Expand Down Expand Up @@ -105,17 +123,28 @@ const generateConceptExplanation = async (req, res) => {
let result = null;
let usedModel = null;
for (const m of candidateModels) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), GEMINI_TIMEOUT);
try {
console.log(`Trying model: ${m}`);
const model = ai.getGenerativeModel({ model: m });
const model = ai.getGenerativeModel({ model: m }, { signal: controller.signal });
result = await model.generateContent([prompt]);
usedModel = m;
console.log(`Successfully used model: ${m}`);
break;
} catch (e) {
if (e.name === "AbortError" || (e.message && e.message.includes("abort"))) {
const timeoutErr = new Error(`Gemini API call timed out after ${GEMINI_TIMEOUT}ms for model ${m}`);
timeoutErr.name = "TimeoutError";
console.error(`[Timeout] Model ${m} timed out:`, timeoutErr.message);
lastErr = timeoutErr;
break;
}
console.error(`Model ${m} failed:`, e.message);
lastErr = e;
continue;
} finally {
clearTimeout(timeoutId);
}
}
if (!result) throw lastErr || new Error("All Gemini models failed");
Expand All @@ -139,6 +168,12 @@ const generateConceptExplanation = async (req, res) => {
}
} catch (error) {
console.error("Gemini API Error:", error);
if (error.name === "TimeoutError") {
return res.status(504).json({
message: "Request timed out",
error: error.message,
});
}
res.status(500).json({
message: "Failed to generate explanation",
error: error.message,
Expand Down
21 changes: 20 additions & 1 deletion backend/controllers/resumeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const compileResume = async (req, res) => {
}
}

const GEMINI_TIMEOUT = parseInt(process.env.GEMINI_TIMEOUT, 10) || 30000;

const analyzeResume = async (req, res) => {
try {
if (!req.file) {
Expand Down Expand Up @@ -102,8 +104,10 @@ DO NOT wrap the response in markdown blocks like \`\`\`json. Return ONLY the raw
let result = null;

for (const m of candidateModels) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), GEMINI_TIMEOUT);
try {
const model = genAI.getGenerativeModel({ model: m });
const model = genAI.getGenerativeModel({ model: m }, { signal: controller.signal });
result = await model.generateContent([
prompt,
{
Expand All @@ -115,8 +119,17 @@ DO NOT wrap the response in markdown blocks like \`\`\`json. Return ONLY the raw
]);
break; // Stop on first success
} catch (e) {
if (e.name === "AbortError" || (e.message && e.message.includes("abort"))) {
const timeoutErr = new Error(`Gemini API call timed out after ${GEMINI_TIMEOUT}ms for model ${m}`);
timeoutErr.name = "TimeoutError";
console.error(`[Timeout] Model ${m} timed out:`, timeoutErr.message);
lastErr = timeoutErr;
break;
}
lastErr = e;
continue;
} finally {
clearTimeout(timeoutId);
}
}

Expand All @@ -142,6 +155,12 @@ DO NOT wrap the response in markdown blocks like \`\`\`json. Return ONLY the raw

} catch (error) {
console.error("Resume Analysis Error:", error);
if (error.name === "TimeoutError") {
return res.status(504).json({
message: "Request timed out",
error: error.message,
});
}
res.status(500).json({ message: "Failed to analyze resume", error: error.message });
}
}
Expand Down
20 changes: 19 additions & 1 deletion backend/routes/AptitudeQuestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { sanitizeAiPrompt } = require("../middlewares/sanitizeAiPrompt");

const router = express.Router();
const ai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const GEMINI_TIMEOUT = parseInt(process.env.GEMINI_TIMEOUT, 10) || 30000;

// GET /api/questions?topic=Probability
router.get("/", validateAiPrompt, sanitizeAiPrompt, async (req, res) => {
Expand Down Expand Up @@ -39,17 +40,28 @@ router.get("/", validateAiPrompt, sanitizeAiPrompt, async (req, res) => {
let usedModel = null;

for (const m of candidateModels) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), GEMINI_TIMEOUT);
try {
console.log(`[Aptitude] Trying model: ${m}`);
const model = ai.getGenerativeModel({ model: m });
const model = ai.getGenerativeModel({ model: m }, { signal: controller.signal });
result = await model.generateContent([prompt]);
usedModel = m;
console.log(`[Aptitude] Successfully used model: ${m}`);
break;
} catch (e) {
if (e.name === "AbortError" || (e.message && e.message.includes("abort"))) {
const timeoutErr = new Error(`Gemini API call timed out after ${GEMINI_TIMEOUT}ms for model ${m}`);
timeoutErr.name = "TimeoutError";
console.error(`[Aptitude][Timeout] Model ${m} timed out:`, timeoutErr.message);
lastErr = timeoutErr;
break;
}
console.error(`[Aptitude] Model ${m} failed:`, e.message);
lastErr = e;
continue;
} finally {
clearTimeout(timeoutId);
}
}

Expand Down Expand Up @@ -83,6 +95,12 @@ router.get("/", validateAiPrompt, sanitizeAiPrompt, async (req, res) => {
res.json(questions);
} catch (error) {
console.error("Gemini API error:", error);
if (error.name === "TimeoutError") {
return res.status(504).json({
error: "Request timed out",
details: error.message,
});
}
res
.status(500)
.json({ error: "Failed to generate questions", details: error.message });
Expand Down
21 changes: 20 additions & 1 deletion backend/routes/aiRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const { aiLimiter } = require('../middlewares/rateLimiter');
const { validateAiPrompt } = require('../middlewares/validateAiPrompt');
const { sanitizeAiPrompt } = require('../middlewares/sanitizeAiPrompt');

const GEMINI_TIMEOUT = parseInt(process.env.GEMINI_TIMEOUT, 10) || 30000;

// Shared handler for text generation (used by multiple route aliases)
async function generateHandler(req, res) {
const { prompt } = req.body || {};
Expand All @@ -30,14 +32,25 @@ async function generateHandler(req, res) {
let result = null;
let usedModel = null;
for (const m of candidateModels) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), GEMINI_TIMEOUT);
try {
const model = genAI.getGenerativeModel({ model: m });
const model = genAI.getGenerativeModel({ model: m }, { signal: controller.signal });
result = await model.generateContent(prompt);
usedModel = m;
break;
} catch (e) {
if (e.name === "AbortError" || (e.message && e.message.includes("abort"))) {
const timeoutErr = new Error(`Gemini API call timed out after ${GEMINI_TIMEOUT}ms for model ${m}`);
timeoutErr.name = "TimeoutError";
console.error(`[AI][Timeout] Model ${m} timed out:`, timeoutErr.message);
lastErr = timeoutErr;
break;
}
lastErr = e;
continue;
} finally {
clearTimeout(timeoutId);
}
}
if (!result) throw lastErr || new Error("All Gemini models failed");
Expand All @@ -58,6 +71,12 @@ async function generateHandler(req, res) {
return res.json({ text: cleanedText, model: usedModel });
} catch (error) {
console.error("[AI] Generation failed:", error.message);
if (error.name === "TimeoutError") {
return res.status(504).json({
error: "Request timed out",
detail: error.message,
});
}
return res
.status(500)
.json({ error: "Failed to generate content", detail: error.message });
Expand Down