Skip to content

Commit 52db49c

Browse files
aaryagodbolei-am-that-guy
authored andcommitted
Integrated context-aware chatbot
1 parent 2ce1fbf commit 52db49c

23 files changed

Lines changed: 1281 additions & 387 deletions

backend/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
JWT_SECRET=
22
REFRESH_SECRET=
33
SALTING=
4-
API_URL='http://localhost:4000/api/v1'
4+
API_URL=
55
GOOGLE_CLIENT_ID=
66
GOOGLE_CLIENT_SECRET=
77
GOOGLE_CALLBACK_URL=

backend/bun.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"typescript": "^5.8.3"
4040
},
4141
"dependencies": {
42+
"@google/generative-ai": "^0.24.1",
4243
"@types/axios": "^0.14.4",
4344
"@types/bcrypt": "^6.0.0",
4445
"@types/cookie-parser": "^1.4.10",
@@ -59,6 +60,7 @@
5960
"helmet": "^8.1.0",
6061
"jsonwebtoken": "^9.0.2",
6162
"multer": "^2.0.2",
63+
"node-fetch": "^3.3.2",
6264
"passport": "^0.7.0",
6365
"resend": "^6.5.2",
6466
"zod": "^4.0.9"

backend/src/ai/chatBrain.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
const formatHistory = (history: { from: string; text: string }[] = []) =>
2+
history
3+
.map((m) => `${m.from === "user" ? "User" : "Assistant"}: ${m.text}`)
4+
.join("\n");
5+
6+
export function buildPrompt({
7+
path,
8+
ragData,
9+
userAnswer,
10+
questionContext,
11+
history = [],
12+
}: {
13+
path: string;
14+
ragData: any;
15+
userAnswer: string;
16+
questionContext?: any;
17+
history?: { from: string; text: string }[];
18+
}) {
19+
const baseRole = `
20+
You are Cortex, an intelligent AI analyst for "Call of Code".
21+
CRITICAL: Do NOT start a mock interview. Do NOT introduce yourself as Alex.
22+
Your ONLY job is to answer the user's question using the DATASET provided below.
23+
If the DATASET is empty, tell the user you don't see any interviews yet.
24+
Rules:
25+
- Always start with a helpful hint.
26+
- Do NOT give full solution unless user explicitly asks.
27+
- Be concise, friendly, and use markdown formatting.
28+
- Do NOT act as an interviewer.
29+
- Do NOT introduce yourself as "Alex" or start a mock session.
30+
- Be concise and use markdown.
31+
`;
32+
33+
console.log("🧠 QUESTION CONTEXT IN chatBrain 👉", questionContext);
34+
35+
// --- CASE 1: DSA TOPIC ONLY ---
36+
if (questionContext?.type === "DSA" && questionContext.isTopicOnly) {
37+
return `
38+
${baseRole}
39+
User is currently browsing the topic: ${questionContext.topicTitle}.
40+
41+
Conversation so far:
42+
${formatHistory(history)}
43+
44+
Instructions:
45+
- Provide a brief overview of ${questionContext.topicTitle}.
46+
- Mention common interview patterns.
47+
48+
User Query: ${userAnswer}
49+
`;
50+
}
51+
52+
// --- CASE 2: SPECIFIC DSA QUESTION ---
53+
if (questionContext?.type === "DSA" && questionContext.questionId) {
54+
const context = ragData ? `QUESTION: ${ragData.question}\nCONCEPT: ${ragData.concept}` : "No specific details available.";
55+
56+
return `
57+
${baseRole}
58+
Conversation so far:
59+
${formatHistory(history)}
60+
61+
CURRENT QUESTION: ${questionContext.questionName} (${questionContext.topicTitle})
62+
${context}
63+
64+
USER ANSWER / CODE:
65+
${userAnswer}
66+
67+
Instructions:
68+
- Give ONLY a hint first.
69+
- Point out logical mistakes.
70+
`;
71+
}
72+
73+
// --- CASE 3: INTERVIEW ANALYST (COLLECTION) ---
74+
if (questionContext?.type === "INTERVIEW_COLLECTION") {
75+
// 🛡️ SAFETY CHECK: Ensure ragData is an array before mapping
76+
const allExperiences = Array.isArray(ragData)
77+
? ragData.map((exp: any) => `
78+
Company: ${exp.company}
79+
Verdict: ${exp.verdict}
80+
Experience: ${exp.content}
81+
`).join("\n---\n")
82+
: "No interview data available to analyze.";
83+
84+
return `
85+
${baseRole}
86+
You are an Interview Analyst. You have access to ${questionContext.count || 0} interview stories.
87+
88+
DATASET:
89+
${allExperiences}
90+
91+
User Question: ${userAnswer}
92+
93+
Instructions:
94+
- Analyze the common patterns in the stories provided.
95+
- Identify common mistakes leading to "Rejected" verdicts.
96+
- Determine which companies focused more on DSA.
97+
`;
98+
}
99+
100+
// --- CASE 4: SINGLE INTERVIEW EXPERIENCE ---
101+
// chatBrain.ts update for CASE 4
102+
// --- CASE 4: SINGLE INTERVIEW EXPERIENCE ---
103+
if (questionContext?.type === "INTERVIEW_EXPERIENCE") {
104+
const interview = ragData || questionContext;
105+
// Added a check to see if content is actually there
106+
const content = ragData?.content || "No database content found. Request might have failed.";
107+
108+
// LOGGING: Check your backend terminal to see what is being packed into the prompt
109+
console.log("🛠️ PROMPT BUILDING WITH CONTENT:", content.substring(0, 50) + "...");
110+
111+
return `
112+
${baseRole}
113+
CURRENT CONTEXT:
114+
Company: ${interview.company}
115+
Role: ${interview.role}
116+
Verdict: ${interview.verdict}
117+
Full Experience: ${content}
118+
119+
User Query: ${userAnswer}
120+
`;
121+
}
122+
123+
// --- CASE 5: DEFAULT FALLBACK ---
124+
return `
125+
${baseRole}
126+
Conversation so far:
127+
${formatHistory(history)}
128+
User message: ${userAnswer}
129+
`;
130+
}

backend/src/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { errorHandler } from './utils/apiError';
88
import rateLimit from 'express-rate-limit';
99
import helmet from 'helmet';
1010
import cookieParser from 'cookie-parser'
11+
import chatRoutes from "./routes/chat.route";
12+
13+
1114

1215

1316
const app = express();
@@ -32,6 +35,8 @@ app.use(limiter)
3235
app.use(cookieParser());
3336
app.use(json());
3437
app.use(urlencoded({ extended: true }));
38+
app.use(express.json());
39+
app.use("/chat", chatRoutes);
3540

3641
const upload = multer({ storage: multer.memoryStorage(),
3742
limits: { fileSize: 2 * 1024 * 1024 }
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Request, Response } from "express";
2+
import { fetchRAGContext } from "../services/retrival";
3+
import { buildPrompt } from "../ai/chatBrain";
4+
import { geminiModel } from "../services/ai";
5+
6+
export async function chatController(req: Request, res: Response) {
7+
try {
8+
const {
9+
path,
10+
userAnswer,
11+
questionContext,
12+
history,
13+
} = req.body;
14+
15+
// 1️⃣ Debugging: Check what the frontend is actually sending
16+
console.log("📥 Incoming Request Context:", JSON.stringify(questionContext, null, 2));
17+
18+
// 2️⃣ Fetch RAG data (Ab ye Topic aur Question dono handle karega)
19+
const ragData = await fetchRAGContext(questionContext);
20+
if (ragData) {
21+
console.log("✅ DATA FOUND. Company:", ragData.company || "N/A");
22+
console.log("📝 CONTENT PREVIEW:", ragData.content?.substring(0, 50) + "...");
23+
} else {
24+
console.log("🔍 DATA TO AI: No record found.");
25+
}
26+
27+
// 3️⃣ Build smart prompt
28+
// Isme hum context pass kar rahe hain taaki AI ko pata chale user kahan hai
29+
const prompt = buildPrompt({
30+
path,
31+
ragData,
32+
userAnswer,
33+
questionContext,
34+
history,
35+
});
36+
37+
// 4️⃣ Call Gemini
38+
const chat = geminiModel.startChat({
39+
history: [], // History hum buildPrompt ke andar handle kar rahe hain string format mein
40+
});
41+
42+
const result = await chat.sendMessage(prompt); // 'prompt' mein baseRole, ragData, aur history sab hai.
43+
const reply = result.response.text();
44+
45+
// 5️⃣ Send Response
46+
res.json({ reply });
47+
48+
} catch (error) {
49+
console.error("❌ Chat Controller Error:", error);
50+
51+
const err = error as Error;
52+
53+
// Error response ko thoda better banaya taaki debugging easy ho
54+
res.status(500).json({
55+
reply: "Cortex is having trouble accessing the context right now. Please try again.",
56+
error: err.message
57+
});
58+
}
59+
}

backend/src/routes/chat.route.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Router } from "express";
2+
import { chatController } from "../controllers/chatController";
3+
4+
const chatRouter = Router();
5+
6+
chatRouter.post("/", chatController);
7+
8+
export default chatRouter;

backend/src/services/ai.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { GoogleGenerativeAI } from "@google/generative-ai";
2+
3+
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
4+
5+
export const geminiModel = genAI.getGenerativeModel({
6+
model: "gemini-2.5-flash",
7+
});

backend/src/services/retrival.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import fetch from "node-fetch";
2+
3+
export async function fetchRAGContext(questionContext?: any) {
4+
if (!questionContext) return null;
5+
6+
try {
7+
// --- 1. DSA LOGIC ---
8+
if (questionContext.type === "DSA") {
9+
if (questionContext.questionId) {
10+
const res = await fetch(`${process.env.API_URL}/dsa/questions/${questionContext.questionId}`);
11+
return res.ok ? await res.json() : null;
12+
}
13+
14+
if (questionContext.isTopicOnly && questionContext.topicId) {
15+
const res = await fetch(`${process.env.API_URL}/topics/${questionContext.topicId}`);
16+
return res.ok ? await res.json() : null;
17+
}
18+
}
19+
20+
// --- 2. INTERVIEW COLLECTION (Analysis Mode) ---
21+
// --- 2. INTERVIEW COLLECTION (Analysis Mode) ---
22+
if (questionContext.type === "INTERVIEW_COLLECTION") {
23+
// Option A: Specific IDs fetch karein (Agar API support karti hai)
24+
if (questionContext.interviewIds && questionContext.interviewIds.length > 0) {
25+
// Aap ek query parameter bhej sakte hain ya loop chala sakte hain
26+
const res = await fetch(`${process.env.API_URL}/interviews?ids=${questionContext.interviewIds.join(',')}`);
27+
const data = await res.json() as any;
28+
return Array.isArray(data) ? data : (data.interviews || []);
29+
}
30+
31+
// Option B: Backup - Saare fetch karein (Jo aap abhi kar rahe hain)
32+
const res = await fetch(`${process.env.API_URL}/interviews/all`);
33+
if (!res.ok) return [];
34+
const data = await res.json() as any;
35+
return Array.isArray(data) ? data : (data.interviews || []);
36+
}
37+
38+
// --- 3. SINGLE INTERVIEW (Specific Experience) ---
39+
// retrival.ts fix
40+
41+
// --- 3. SINGLE INTERVIEW (Specific Experience) ---
42+
// retrival.ts
43+
// retrival.ts
44+
if (questionContext.type === "INTERVIEW_EXPERIENCE") {
45+
// Added /v1 to match app.use("/api/v1", routes(upload)) in app.ts
46+
const res = await fetch(`http://localhost:3000/api/v1/interviews/${questionContext.id}`);
47+
48+
if (res.ok) {
49+
const json = await res.json() as any;
50+
51+
// Extract the actual data (handling potential API wrappers)
52+
const actualData = json.data || json.interview || json;
53+
54+
console.log("✅ SUCCESS: Data found for", actualData.company);
55+
return { ...questionContext, ...actualData };
56+
}
57+
console.log("❌ Fetch failed. Status:", res.status);
58+
return questionContext;
59+
}
60+
61+
} catch (error) {
62+
console.error("❌ Error in RAG retrieval:", error);
63+
return null;
64+
}
65+
66+
return null;
67+
}

0 commit comments

Comments
 (0)