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: 2 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
JWT_SECRET=
REFRESH_SECRET=
SALTING=
API_URL='http://localhost:4000/api/v1'
API_URL=
GEMINI_API_KEY=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
Expand Down
17 changes: 17 additions & 0 deletions backend/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@types/axios": "^0.14.4",
"@types/bcrypt": "^6.0.0",
"@types/cookie-parser": "^1.4.10",
Expand All @@ -59,6 +60,7 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",
"node-fetch": "^3.3.2",
"passport": "^0.7.0",
"resend": "^6.5.2",
"zod": "^4.0.9"
Expand Down
142 changes: 142 additions & 0 deletions backend/src/ai/chatBrain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const formatHistory = (history: { from: string; text: string }[] = []) =>
history
.map((m) => `${m.from === "user" ? "User" : "Assistant"}: ${m.text}`)
.join("\n");

export function buildPrompt({
path,
ragData,
userAnswer,
questionContext,
history = [],
}: {
path: string;
ragData: any;
userAnswer: string;
questionContext?: any;
history?: { from: string; text: string }[];
}) {
const baseRole = `
You are Cortex, an intelligent AI analyst for "Call of Code".
STRICT LIMITATION: Your knowledge is limited EXCLUSIVELY to DSA and Interview Experiences.

🎯 YOUR GOALS:
1. If user says hi or hello, greet them back briefly and ask how you can assist in DSA/Interview prep.
2. Provide accurate, concise, and relevant answers ONLY related to DSA problems or interview experiences.
3. Politely refuse to answer anything outside DSA/Interviews with the Refusal Message below.

💡 SPECIAL INSTRUCTIONS BASED ON CONTEXT:
1. DSA HELP (3-Step Method):
- Start with a **simple hint** only.
- If user asks for solution (e.g., "give code", "solve it"), provide: (a) Brute-force logic + code, (b) Optimal solution + code.
- Suggest 2-3 similar practice problems from LeetCode/GFG/CodeChef.
2. CAREER QUESTIONS: Define the role, list skills, give a step-by-step roadmap, and suggest learning platforms.
3. INTERVIEW ANALYSIS: Provide direct answers based on stories. DO NOT give hints for interview data queries.

RULES:
- If the query is NOT about DSA/Interviews, use the Refusal Message: "I am Cortex, specialized only in DSA and Interview analysis. Let's stay focused on your preparation!"
- Do NOT start a mock interview. Do NOT act as Alex.
- Use Markdown (bold, headers, code blocks) and emojis 🚀.
`;

console.log("🧠 QUESTION CONTEXT IN chatBrain 👉", questionContext);

// --- CASE 1: DSA TOPIC ONLY ---
if (questionContext?.type === "DSA" && questionContext.isTopicOnly) {
const questionsList = ragData?.relatedQuestions
? ragData.relatedQuestions.map((q: any) => `- ${q.title}`).join("\n")
: "No questions listed for this topic yet.";

return `
${baseRole}
User is browsing: ${questionContext.topicTitle}
Available Questions in this topic:
${questionsList}

Instructions:
- Briefly explain the importance of ${questionContext.topicTitle} in interviews.
- If user asks what to solve, suggest questions from the list above.

User Query: ${userAnswer}
`;
}

// --- CASE 2: SPECIFIC DSA QUESTION ---
if (questionContext?.type === "DSA" && questionContext.questionId) {
const context = ragData ? `QUESTION: ${ragData.question}\nCONCEPT: ${ragData.concept}` : "No specific details available.";

return `
${baseRole}
Conversation so far:
${formatHistory(history)}

CURRENT QUESTION: ${questionContext.questionName} (${questionContext.topicTitle})
${context}

USER ANSWER / CODE:
${userAnswer}

Instructions:
- Give ONLY a hint first.
- Point out logical mistakes.
`;
}

// --- CASE 3: INTERVIEW ANALYST (COLLECTION) ---
if (questionContext?.type === "INTERVIEW_COLLECTION") {
// Check if ragData exists and has items
const allExperiences = (Array.isArray(ragData) && ragData.length > 0)
? ragData.slice(0, 10).map((exp: any) => // Top 10 for performance
`Student: ${exp.member?.name || "User"} | Company: ${exp.company} | Verdict: ${exp.verdict} | Summary: ${exp.role}`
).join("\n")
: "No specific interview stories found in the database.";

return `
${baseRole}
CONTEXT: The user is looking at the Interview Experiences page.
DATABASE SUMMARY (Last few stories):
${allExperiences}

User Question: ${userAnswer}

Instructions:
- Analyze the common patterns in the stories provided.
- Identify common mistakes leading to "Rejected" verdicts.
- Determine which companies focused more on DSA.
- If they ask for details of a specific person, tell them to click on that card for a deep-dive analysis.
`;
}

// --- CASE 4: SINGLE INTERVIEW EXPERIENCE ---
if (questionContext?.type === "INTERVIEW_EXPERIENCE") {
const current = ragData?.currentInterview || questionContext;
const content = ragData?.currentInterview?.content || "Story content loading...";
const studentName = current.member?.name || current.studentName || "the candidate";

return `
${baseRole}
CONTEXT: Interview for ${current.company}.
CANDIDATE: ${studentName} | VERDICT: ${current.verdict}
DATA: """${content}"""

INSTRUCTIONS:
1. If the user says "hi" or "hello", greet them professionally and ask what they want to know specifically.
2. Provide the FULL ANALYSIS ONLY if the user asks a specific question (e.g., "what rounds?", "questions?", "give analysis").
3. Use this format for analysis:
🔍 **Analysis: ${current.company} Journey**
- **The Process**: ...
- **Technical Deep-Dive**: ...
- **The Verdict Factor**: ...

User Query: ${userAnswer}
`;
}

// --- CASE 5: DEFAULT FALLBACK ---
return `
${baseRole}
Conversation so far:
${formatHistory(history)}
User message: ${userAnswer}
`;
}
5 changes: 5 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { errorHandler } from './utils/apiError';
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
import cookieParser from 'cookie-parser'
import chatRoutes from "./routes/chat.route";




const app = express();
Expand All @@ -32,6 +35,8 @@ app.use(limiter)
app.use(cookieParser());
app.use(json());
app.use(urlencoded({ extended: true }));
app.use(express.json());
app.use("/chat", chatRoutes);

const upload = multer({ storage: multer.memoryStorage(),
limits: { fileSize: 2 * 1024 * 1024 }
Expand Down
59 changes: 59 additions & 0 deletions backend/src/controllers/chatController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Request, Response } from "express";
import { fetchRAGContext } from "../services/retrival";
import { buildPrompt } from "../ai/chatBrain";
import { geminiModel } from "../services/ai";

export async function chatController(req: Request, res: Response) {
try {
const {
path,
userAnswer,
questionContext,
history = [], // Added default value
} = req.body;

// Fetch RAG data
const ragData = await fetchRAGContext(questionContext);


if (ragData) {
console.log(` Context Loaded: ${ragData.company || ragData.topicTitle || 'General'}`);
}

const prompt = buildPrompt({
path,
ragData,
userAnswer,
questionContext,
history,
});

// Call Gemini
const chat = geminiModel.startChat({
history: [],
});

try {
const result = await chat.sendMessage(prompt);
const reply = result.response.text();
return res.json({ reply }); // Explicit return
} catch (aiError: any) {
// ⚠️ Handle Gemini Specific Overload (503)
if (aiError.status === 503 || aiError.message?.includes("503")) {
return res.status(503).json({
reply: "Google's AI servers are a bit busy 🧠⚡. Please wait 5-10 seconds and try again!",
});
}
throw aiError; // Pass to main catch
}

} catch (error) {
console.error("🚨 Chat Controller Error:", error);
const err = error as Error;

res.status(500).json({
reply: "Cortex encountered an issue. Let's try that again in a moment.",
error: process.env.NODE_ENV === 'development' ? err.message : undefined // Hide technical error in production
});
}
}
8 changes: 8 additions & 0 deletions backend/src/routes/chat.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Router } from "express";
import { chatController } from "../controllers/chatController";

const chatRouter = Router();

chatRouter.post("/", chatController);

export default chatRouter;
7 changes: 7 additions & 0 deletions backend/src/services/ai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { GoogleGenerativeAI } from "@google/generative-ai";

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);

export const geminiModel = genAI.getGenerativeModel({
model: "gemini-2.5-flash",
});
Loading