Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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",
Comment thread
aaryagodbole marked this conversation as resolved.
"passport": "^0.7.0",
"resend": "^6.5.2",
"zod": "^4.0.9"
Expand Down
134 changes: 134 additions & 0 deletions backend/src/ai/chatBrain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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 }[];
}) {
Comment thread
aaryagodbole marked this conversation as resolved.
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. 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);
Comment thread
aaryagodbole marked this conversation as resolved.

// --- CASE 1: DSA TOPIC ONLY ---
if (questionContext?.type === "DSA" && questionContext.isTopicOnly) {
return `
${baseRole}
User is currently browsing the topic: ${questionContext.topicTitle}.

Conversation so far:
${formatHistory(history)}

Instructions:
- Provide a brief overview of ${questionContext.topicTitle}.
- Mention common interview patterns.

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.
`;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// --- CASE 3: INTERVIEW ANALYST (COLLECTION) ---
if (questionContext?.type === "INTERVIEW_COLLECTION") {
// 🛡️ SAFETY CHECK: Ensure ragData is an array before mapping
const allExperiences = Array.isArray(ragData)
? ragData.map((exp: any) => `
Company: ${exp.company}
Verdict: ${exp.verdict}
Experience: ${exp.content}
`).join("\n---\n")
: "No interview data available to analyze.";

return `
${baseRole}
You are an Interview Analyst. You have access to ${questionContext.count || 0} interview stories.

DATASET:
${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.
`;
}

// --- CASE 4: SINGLE INTERVIEW EXPERIENCE ---
// chatBrain.ts update for CASE 4
// --- CASE 4: SINGLE INTERVIEW EXPERIENCE ---
if (questionContext?.type === "INTERVIEW_EXPERIENCE") {
const interview = ragData || questionContext;
// Added a check to see if content is actually there
const content = ragData?.content || "No database content found. Request might have failed.";

// LOGGING: Check your backend terminal to see what is being packed into the prompt
console.log("🛠️ PROMPT BUILDING WITH CONTENT:", content.substring(0, 50) + "...");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

return `
${baseRole}
CURRENT CONTEXT:
Company: ${interview.company}
Role: ${interview.role}
Verdict: ${interview.verdict}
Full Experience: ${content}

User Query: ${userAnswer}
`;
}
Comment thread
aaryagodbole marked this conversation as resolved.
Comment thread
aaryagodbole marked this conversation as resolved.

// --- 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());
Comment thread
aaryagodbole marked this conversation as resolved.
app.use("/chat", chatRoutes);
Comment thread
aaryagodbole marked this conversation as resolved.
Comment thread
aaryagodbole marked this conversation as resolved.

const upload = multer({ storage: multer.memoryStorage(),
limits: { fileSize: 2 * 1024 * 1024 }
Expand Down
55 changes: 55 additions & 0 deletions backend/src/controllers/chatController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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,
} = req.body;
Comment thread
aaryagodbole marked this conversation as resolved.


// Fetch RAG data
const ragData = await fetchRAGContext(questionContext);
if (ragData) {
console.log("✅ DATA FOUND. Company:", ragData.company || "N/A");
console.log("📝 CONTENT PREVIEW:", ragData.content?.substring(0, 50) + "...");
} else {
console.log("🔍 DATA TO AI: No record found.");
}

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

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

const result = await chat.sendMessage(prompt); // prompt has all context
const reply = result.response.text();

// Send Response
res.json({ reply });

} catch (error) {
console.error("Chat Controller Error:", error);

const err = error as Error;


res.status(500).json({
reply: "Cortex is having trouble accessing the context right now. Please try again.",
error: err.message
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
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",
});
Comment thread
aaryagodbole marked this conversation as resolved.
58 changes: 58 additions & 0 deletions backend/src/services/retrival.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import fetch from "node-fetch";

export async function fetchRAGContext(questionContext?: any) {
if (!questionContext) return null;

try {
// --- 1. DSA LOGIC ---
if (questionContext.type === "DSA") {
if (questionContext.questionId) {
const res = await fetch(`${process.env.API_URL}/dsa/questions/${questionContext.questionId}`);
return res.ok ? await res.json() : null;
}

if (questionContext.isTopicOnly && questionContext.topicId) {
const res = await fetch(`${process.env.API_URL}/topics/${questionContext.topicId}`);
return res.ok ? await res.json() : null;
}
}

// --- 2. INTERVIEW COLLECTION (Analysis Mode) ---

if (questionContext.type === "INTERVIEW_COLLECTION") {
if (questionContext.interviewIds && questionContext.interviewIds.length > 0) {
const res = await fetch(`${process.env.API_URL}/interviews?ids=${questionContext.interviewIds.join(',')}`);
const data = await res.json() as any;
return Array.isArray(data) ? data : (data.interviews || []);
}
Comment thread
aaryagodbole marked this conversation as resolved.
Outdated

const res = await fetch(`${process.env.API_URL}/interviews/all`);
if (!res.ok) return [];
const data = await res.json() as any;
return Array.isArray(data) ? data : (data.interviews || []);
}


if (questionContext.type === "INTERVIEW_EXPERIENCE") {
const res = await fetch(`http://localhost:3000/api/v1/interviews/${questionContext.id}`);

if (res.ok) {
const json = await res.json() as any;

// Extract the actual data (handling potential API wrappers)
const actualData = json.data || json.interview || json;

console.log("SUCCESS: Data found for", actualData.company);
return { ...questionContext, ...actualData };
}
console.log("Fetch failed. Status:", res.status);
return questionContext;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

} catch (error) {
console.error("Error in RAG retrieval:", error);
return null;
}

return null;
}
1 change: 1 addition & 0 deletions bun.lock

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

Loading