From ec2790496a0121f274eca6c23cd288c124a08215 Mon Sep 17 00:00:00 2001 From: Akshith Date: Sun, 21 Jun 2026 15:05:54 +0530 Subject: [PATCH] fix(project-ideas): restore project generation and improve error handling --- backend/routes/aiRoutes.js | 8 +- backend/validation/aiPromptSchema.js | 3 +- .../src/pages/ProjectIdeas/ProjectIdeas.jsx | 134 ++++++++++++++---- 3 files changed, 112 insertions(+), 33 deletions(-) diff --git a/backend/routes/aiRoutes.js b/backend/routes/aiRoutes.js index 7ddc6a6..feb95cc 100644 --- a/backend/routes/aiRoutes.js +++ b/backend/routes/aiRoutes.js @@ -25,7 +25,7 @@ const offTopicCache = new NodeCache({ stdTTL: 3600 }); * 200 {"text": "...", "model": "models/gemini-2.5-flash"} */ async function generateHandler(req, res) { - const { prompt, history = [] } = req.body || {}; + const { prompt, history = [], systemInstruction } = req.body || {}; if (!prompt || !prompt.trim()) { return res.status(400).json({ error: "Missing prompt" }); } @@ -73,7 +73,7 @@ async function generateHandler(req, res) { try { const model = genAI.getGenerativeModel({ model: m, - systemInstruction: `You are PrepPilot AI Mentor. + systemInstruction: systemInstruction || `You are PrepPilot AI Mentor. 1. Allow friendly greetings and casual onboarding conversation. 2. Focus primarily on PrepPilot-related domains: interview preparation, coding interviews, aptitude, resumes, career guidance, mock interviews, and platform usage. 3. Politely redirect unrelated conversations. @@ -106,6 +106,10 @@ async function generateHandler(req, res) { if (!result) throw lastErr || new Error("All Gemini models failed"); const rawText = await result.response.text(); + console.log("Incoming Prompt:", prompt); + console.log("Model Used:", usedModel); + console.log("Raw Gemini Response:", rawText); + let cleanedText = rawText .replace(/^[\s`]*json\s*/i, "") .replace(/^\s*```/i, "") diff --git a/backend/validation/aiPromptSchema.js b/backend/validation/aiPromptSchema.js index 4b32cab..6d63dde 100644 --- a/backend/validation/aiPromptSchema.js +++ b/backend/validation/aiPromptSchema.js @@ -35,9 +35,10 @@ const safePrompt = (value, helpers) => { const aiPromptSchema = Joi.object({ prompt: Joi.string() .min(1) - .max(500) + .max(5000) .required() .custom(safePrompt, "Prompt Injection Protection"), + systemInstruction: Joi.string().optional(), history: Joi.array().items( Joi.object({ role: Joi.string().valid("user", "model").required(), diff --git a/frontend/src/pages/ProjectIdeas/ProjectIdeas.jsx b/frontend/src/pages/ProjectIdeas/ProjectIdeas.jsx index 6cd2d33..a645c39 100644 --- a/frontend/src/pages/ProjectIdeas/ProjectIdeas.jsx +++ b/frontend/src/pages/ProjectIdeas/ProjectIdeas.jsx @@ -16,6 +16,7 @@ import { BookOpen, Star, Zap, + X, } from "lucide-react"; // ───────────────────────────────────────────────────────── @@ -99,13 +100,39 @@ const LEVELS = [ // ───────────────────────────────────────────────────────── // Helpers // ───────────────────────────────────────────────────────── -function buildRequestBody(domainLabel, level) { - // Backend schema: prompt(max 500), role(max 50), topic(max 100) — all required - return { - role: `${level} ${domainLabel} developer`, - topic: `${domainLabel} project ideas for ${level} level`, - prompt: `List 3 ${level} ${domainLabel} project ideas as JSON array. Each: {title, tagline, techStack:[], features:[], githubSearch, difficulty, domain}. Return only valid JSON array.`, - }; +function buildPrompt(domainLabel, level) { + return `Generate 3 to 5 unique project ideas for a ${level} level ${domainLabel} developer. +Avoid generic ideas like Todo App, Calculator, or Weather App. + +For Frontend, prefer things like: Real-time collaborative whiteboard, Design system builder, Accessibility auditing dashboard. +For Backend, prefer things like: Distributed job scheduler, Event-driven notification platform. +For Full Stack, prefer things like: AI-powered hiring platform, Skill exchange marketplace. +For AI / ML, prefer things like: Resume scoring engine, Interview performance analyzer. +For DevOps / Cloud, prefer things like: Kubernetes deployment assistant, Infrastructure monitoring platform. +For Java, prefer things like: Banking transaction simulator, Distributed inventory system. + +For each project, provide: +- "title": Project Title +- "description": Short Description +- "techStack": Array of technologies +- "features": Array of key features +- "learningOutcomes": Array of learning outcomes +- "difficulty": "${level}" +- "resumeValue": String explaining resume value + +Return ONLY a valid JSON array of objects with the exact keys: +[ + { + "title": "...", + "description": "...", + "techStack": ["..."], + "features": ["..."], + "learningOutcomes": ["..."], + "difficulty": "...", + "resumeValue": "..." + } +] +`; } function tryParseJSON(text) { @@ -165,7 +192,7 @@ const IdeaCard = ({ idea, domain, index }) => { {idea.title}

- {idea.tagline} + {idea.description || idea.tagline}

@@ -205,7 +232,7 @@ const IdeaCard = ({ idea, domain, index }) => { {/* Features */} -
+
{
    - {(idea.features || []).slice(0, 3).map((f, i) => ( + {(idea.features || []).slice(0, 2).map((f, i) => (
  • {
+ {/* Learning Outcomes */} +
+
+ + + Learning Outcomes + +
+
    + {(idea.learningOutcomes || []).slice(0, 2).map((l, i) => ( +
  • + + {l} +
  • + ))} +
+
+ + {/* Resume Value */} +
+
+ + + Resume Value + +
+

+ "{idea.resumeValue || "Great addition to your portfolio."}" +

+
+ {/* GitHub Link */} { selectedLevel, ); + console.log("BASE_URL:", BASE_URL); + console.log("Prompt:", prompt); + const res = await fetch(`${BASE_URL}/api/generate`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt }), + body: JSON.stringify({ + prompt, + systemInstruction: "You are an API that ONLY returns valid JSON arrays. Do not include any conversational text, greetings, or formatting outside the JSON array." + }), }); + console.log("Response Status:", res.status); + if (!res.ok) throw new Error(`Server error ${res.status}`); - // collect the full streamed response - let fullText = ""; - if (res.body && typeof res.body.getReader === "function") { - const reader = res.body.getReader(); - const decoder = new TextDecoder(); - let done = false; - while (!done) { - const { value, done: d } = await reader.read(); - done = d; - if (value) fullText += decoder.decode(value, { stream: !done }); - } - } else { - const data = await res.json(); - fullText = data.text || ""; - } + const data = await res.json(); + const fullText = data.text || ""; const parsed = tryParseJSON(fullText); - if (parsed && Array.isArray(parsed) && parsed.length > 0) { - setIdeas(parsed); - setGenerated(true); + console.log("Parsed Response:", parsed); + if (parsed && Array.isArray(parsed)) { + if (parsed.length > 0) { + setIdeas(parsed); + setGenerated(true); + } else { + setError("The AI could not generate any ideas. Please try different options."); + } } else { setError("The AI returned an unexpected format. Please try again."); } } catch (err) { - setError(err.message || "Something went wrong. Please try again."); + console.error("Project Ideas Generator Error:", err); + setError(err.message || "Unknown error"); } finally { setLoading(false); }