diff --git a/backend/.env.example b/backend/.env.example
index e36f31a..9157318 100644
--- a/backend/.env.example
+++ b/backend/.env.example
@@ -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=
diff --git a/backend/bun.lock b/backend/bun.lock
index 7ab1e6a..92972ef 100644
--- a/backend/bun.lock
+++ b/backend/bun.lock
@@ -1,9 +1,11 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "backend",
"dependencies": {
+ "@google/generative-ai": "^0.24.1",
"@types/axios": "^0.14.4",
"@types/bcrypt": "^6.0.0",
"@types/cookie-parser": "^1.4.10",
@@ -24,6 +26,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",
@@ -152,6 +155,8 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.4", "", { "dependencies": { "@eslint/core": "^0.15.1", "levn": "^0.4.1" } }, "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw=="],
+ "@google/generative-ai": ["@google/generative-ai@0.24.1", "", {}, "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q=="],
+
"@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@@ -520,6 +525,8 @@
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
+
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="],
@@ -626,6 +633,8 @@
"fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
+ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
+
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
@@ -646,6 +655,8 @@
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
+ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
+
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
@@ -900,6 +911,10 @@
"node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
+
+ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
+
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
@@ -1140,6 +1155,8 @@
"walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
+ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
diff --git a/backend/package.json b/backend/package.json
index 3cddca7..4fceeec 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -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",
@@ -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"
diff --git a/backend/src/ai/chatBrain.ts b/backend/src/ai/chatBrain.ts
new file mode 100644
index 0000000..063e5ed
--- /dev/null
+++ b/backend/src/ai/chatBrain.ts
@@ -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}
+`;
+}
\ No newline at end of file
diff --git a/backend/src/app.ts b/backend/src/app.ts
index 7a167e8..9a9aef1 100644
--- a/backend/src/app.ts
+++ b/backend/src/app.ts
@@ -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();
@@ -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 }
diff --git a/backend/src/controllers/chatController.ts b/backend/src/controllers/chatController.ts
new file mode 100644
index 0000000..4aa734d
--- /dev/null
+++ b/backend/src/controllers/chatController.ts
@@ -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
+ });
+ }
+}
\ No newline at end of file
diff --git a/backend/src/routes/chat.route.ts b/backend/src/routes/chat.route.ts
new file mode 100644
index 0000000..a4321f2
--- /dev/null
+++ b/backend/src/routes/chat.route.ts
@@ -0,0 +1,8 @@
+import { Router } from "express";
+import { chatController } from "../controllers/chatController";
+
+const chatRouter = Router();
+
+chatRouter.post("/", chatController);
+
+export default chatRouter;
diff --git a/backend/src/services/ai.ts b/backend/src/services/ai.ts
new file mode 100644
index 0000000..f33f8d1
--- /dev/null
+++ b/backend/src/services/ai.ts
@@ -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",
+});
diff --git a/backend/src/services/retrival.ts b/backend/src/services/retrival.ts
new file mode 100644
index 0000000..b9b7ceb
--- /dev/null
+++ b/backend/src/services/retrival.ts
@@ -0,0 +1,59 @@
+import api from "../utils/api";
+
+export async function fetchRAGContext(questionContext?: any) {
+ if (!questionContext) return null;
+
+ try {
+ // --- CASE 1: DSA (Same as before, working fine) ---
+ if (questionContext.type === "DSA") {
+ if (questionContext.questionId) {
+ const res = await api.get(`/dsa/questions/${questionContext.questionId}`);
+ return res.data?.data || null;
+ }
+
+ if (questionContext.isTopicOnly && questionContext.topicId) {
+ const res = await api.get(`/topics/${questionContext.topicId}/questions`);
+ const questions = res.data?.questions || [];
+ return {
+ ...questionContext,
+ relatedQuestions: questions,
+ topicOverview: `This topic contains ${questions.length} practice questions.`
+ };
+ }
+ }
+
+ // --- CASE 2 & 3: INTERVIEW SYSTEM (The finale Fix) ---
+ if (questionContext.type === "INTERVIEW_EXPERIENCE") {
+ const specificRes = await api.get(`/interviews/${questionContext.id}`);
+ const specificData = specificRes.data?.data;
+
+
+
+ const globalRes = await api.get(`/interviews?limit=20`);
+ const allInterviews = globalRes.data?.data || [];
+
+ return {
+ currentInterview: specificData,
+ otherInterviews: allInterviews.map((i: any) => ({
+ student: i.member?.name || "Anonymous",
+ company: i.company,
+ verdict: i.verdict,
+ id: i._id
+ })),
+ contextMeta: questionContext
+ };
+ }
+
+ // Default Case
+ if (questionContext.type === "INTERVIEW_COLLECTION") {
+ const res = await api.get(`/interviews?limit=50`);
+ const data = res.data?.data || res.data?.interviews || [];
+ return data;
+ }
+
+ } catch (error: any) {
+ console.error("RAG Fetch Error:", error.message);
+ return questionContext;
+ }
+ return null;
+}
\ No newline at end of file
diff --git a/bun.lock b/bun.lock
index f1cdd40..13a2bef 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "coc-member",
diff --git a/frontend/bun.lock b/frontend/bun.lock
index 253fd66..429e613 100644
--- a/frontend/bun.lock
+++ b/frontend/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "frontend",
@@ -22,7 +23,7 @@
"axios": "^1.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "framer-motion": "^12.23.24",
+ "framer-motion": "^12.25.0",
"lexical": "^0.38.2",
"lucide-react": "^0.548.0",
"motion": "^12.23.24",
@@ -568,7 +569,7 @@
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
- "framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="],
+ "framer-motion": ["framer-motion@12.25.0", "", { "dependencies": { "motion-dom": "^12.24.11", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-mlWqd0rApIjeyhTCSNCqPYsUAEhkcUukZxH3ke6KbstBRPcxhEpuIjmiUQvB+1E9xkEm5SpNHBgHCapH/QHTWg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -760,9 +761,9 @@
"motion": ["motion@12.23.24", "", { "dependencies": { "framer-motion": "^12.23.24", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw=="],
- "motion-dom": ["motion-dom@12.23.23", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA=="],
+ "motion-dom": ["motion-dom@12.24.11", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A=="],
- "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
+ "motion-utils": ["motion-utils@12.24.10", "", {}, "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -986,6 +987,8 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "motion/framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="],
+
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
@@ -1005,5 +1008,9 @@
"@radix-ui/react-toggle/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "motion/framer-motion/motion-dom": ["motion-dom@12.23.23", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA=="],
+
+ "motion/framer-motion/motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
}
}
diff --git a/frontend/package.json b/frontend/package.json
index b22c562..e3791e8 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -28,7 +28,7 @@
"axios": "^1.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "framer-motion": "^12.23.24",
+ "framer-motion": "^12.25.0",
"lexical": "^0.38.2",
"lucide-react": "^0.548.0",
"motion": "^12.23.24",
@@ -36,8 +36,8 @@
"react-dom": "^19.1.0",
"react-hook-form": "^7.58.1",
"react-hot-toast": "^2.6.0",
- "react-markdown": "^10.1.0",
"react-icons": "^5.5.0",
+ "react-markdown": "^10.1.0",
"react-router-dom": "^7.6.2",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.10"
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 3fb3548..f07db72 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -40,3 +40,44 @@
.read-the-docs {
color: #888;
} */
+
+
+/* LIGHT THEME */
+:root {
+ /* CHATBOT – LIGHT */
+ --color-chatbot-border: #2C1810;
+ --color-chatbot-bg-window: #FFF6EE;
+ --color-chatbot-bg-messages: #F5E6D3;
+ --color-chatbot-text: #2C1810;
+}
+
+.dark {
+ /* CHATBOT – DARK */
+ --color-chatbot-border: #F5E6D3;
+ --color-chatbot-bg-window: #121212;
+ --color-chatbot-bg-messages: #1A1A1A;
+ --color-chatbot-text: #F5E6D3;
+}
+
+.typing-dot {
+ width: 6px;
+ height: 6px;
+ background-color: currentColor; /* 👈 theme ke hisaab se */
+ border-radius: 50%;
+ animation: typing 1.4s infinite ease-in-out;
+}
+
+.typing-dot.delay-1 {
+ animation-delay: 0.2s;
+}
+.typing-dot.delay-2 {
+ animation-delay: 0.4s;
+}
+
+@keyframes typing {
+ 0% { opacity: 0.3; transform: translateY(0); }
+ 20% { opacity: 1; transform: translateY(-3px); }
+ 40% { opacity: 0.3; transform: translateY(0); }
+}
+
+
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index fcba206..5dd88ca 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,5 +1,6 @@
import "./App.css";
-import { Navigate, Route , Routes } from "react-router-dom";
+import { useState } from "react";
+import { Navigate, Route, Routes } from "react-router-dom";
import { Toaster } from "react-hot-toast";
import ThemeToggle from "./components/common/ThemeToggle";
import Home from "./pages/LandingPage";
@@ -10,26 +11,44 @@ import AuthPage from "./pages/Signup";
import NotFound from "./pages/NotFound";
import MainLayout from "./components/miniCompo/MainLayout";
import ProtectedRoute from "./utils/ProtectedRoute";
+import ChatbotWrapper from "./components/chatbot/ChatbotWrapper";
+import { useChatbotVisibility } from './hooks/useChatbotVisibility';
+
+const ChatbotContainer = ({ currentQuestionContext }) => {
+ const isChatbotVisible = useChatbotVisibility();
+
+ return isChatbotVisible ? (
+
{">"} STATUS: ACTIVE
+{children},
+ pre: ({ children }) => {children}
+ }}
+ >
+ {msg.text}
+ - {interview.role} -
-+ {interview.role} +
++ {children} +
+ ), + strong: ({ node, ...props }) => ( + + ), + em: ({ node, ...props }) => ( + + ), + ul: ({ node, className, children, ...rest }) => ( +
+ ),
+ hr: ({ node, ...props }) => (
+ + {children} ++ ), }} > - - -
- {children} -
- ), - strong: ({ node, ...props }) => ( - - ), - em: ({ node, ...props }) => ( - - ), - ul: ({ node, className, children, ...rest }) => ( -
- ),
- hr: ({ node, ...props }) => (
- - {children} -- ), - }} - > - {interview.content} -
Help fellow coders by sharing your interview experiences. Every story matters!