From 52db49ca2e618dfed663af4ec5e304badafcf4bd Mon Sep 17 00:00:00 2001 From: aaryagodbole Date: Sun, 11 Jan 2026 23:16:10 +0530 Subject: [PATCH 1/3] Integrated context-aware chatbot --- backend/.env.example | 2 +- backend/bun.lock | 17 + backend/package.json | 2 + backend/src/ai/chatBrain.ts | 130 ++++ backend/src/app.ts | 5 + backend/src/controllers/chatController.ts | 59 ++ backend/src/routes/chat.route.ts | 8 + backend/src/services/ai.ts | 7 + backend/src/services/retrival.ts | 67 ++ bun.lock | 1 + frontend/bun.lock | 15 +- frontend/package.json | 4 +- frontend/public/Chatbot-icon.png | Bin 0 -> 22399 bytes frontend/src/App.css | 41 ++ frontend/src/App.jsx | 41 +- .../src/components/chatbot/ChatbotIcon.jsx | 102 +++ .../src/components/chatbot/ChatbotWrapper.jsx | 316 ++++++++++ .../components/dsa/questions/QuestionView.jsx | 183 +++--- .../interview/InterviewExperienceItem.jsx | 588 +++++++++--------- frontend/src/hooks/useChatbotVisibility.js | 20 + frontend/src/pages/DsaDashboard.jsx | 14 +- frontend/src/pages/InterviewExp.jsx | 20 +- frontend/src/utils/buildQuestionContext.js | 26 + 23 files changed, 1281 insertions(+), 387 deletions(-) create mode 100644 backend/src/ai/chatBrain.ts create mode 100644 backend/src/controllers/chatController.ts create mode 100644 backend/src/routes/chat.route.ts create mode 100644 backend/src/services/ai.ts create mode 100644 backend/src/services/retrival.ts create mode 100644 frontend/public/Chatbot-icon.png create mode 100644 frontend/src/components/chatbot/ChatbotIcon.jsx create mode 100644 frontend/src/components/chatbot/ChatbotWrapper.jsx create mode 100644 frontend/src/hooks/useChatbotVisibility.js create mode 100644 frontend/src/utils/buildQuestionContext.js diff --git a/backend/.env.example b/backend/.env.example index e36f31a..c75918e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,7 +1,7 @@ JWT_SECRET= REFRESH_SECRET= SALTING= -API_URL='http://localhost:4000/api/v1' +API_URL= 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..4a73339 --- /dev/null +++ b/backend/src/ai/chatBrain.ts @@ -0,0 +1,130 @@ +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". +CRITICAL: Do NOT start a mock interview. Do NOT introduce yourself as Alex. +Your ONLY job is to answer the user's question using the DATASET provided below. +If the DATASET is empty, tell the user you don't see any interviews yet. +Rules: +- Always start with a helpful hint. +- Do NOT give full solution unless user explicitly asks. +- Be concise, friendly, and use markdown formatting. +- Do NOT act as an interviewer. +- Do NOT introduce yourself as "Alex" or start a mock session. +- Be concise and use markdown. +`; + + console.log("🧠 QUESTION CONTEXT IN chatBrain 👉", questionContext); + + // --- 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. +`; + } + + // --- 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) + "..."); + + return ` +${baseRole} +CURRENT CONTEXT: +Company: ${interview.company} +Role: ${interview.role} +Verdict: ${interview.verdict} +Full Experience: ${content} + +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..aef7be6 --- /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, + } = req.body; + + // 1️⃣ Debugging: Check what the frontend is actually sending + console.log("📥 Incoming Request Context:", JSON.stringify(questionContext, null, 2)); + + // 2️⃣ Fetch RAG data (Ab ye Topic aur Question dono handle karega) + 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."); +} + + // 3️⃣ Build smart prompt + // Isme hum context pass kar rahe hain taaki AI ko pata chale user kahan hai + const prompt = buildPrompt({ + path, + ragData, + userAnswer, + questionContext, + history, + }); + + // 4️⃣ Call Gemini + const chat = geminiModel.startChat({ + history: [], // History hum buildPrompt ke andar handle kar rahe hain string format mein + }); + + const result = await chat.sendMessage(prompt); // 'prompt' mein baseRole, ragData, aur history sab hai. + const reply = result.response.text(); + + // 5️⃣ Send Response + res.json({ reply }); + + } catch (error) { + console.error("❌ Chat Controller Error:", error); + + const err = error as Error; + + // Error response ko thoda better banaya taaki debugging easy ho + res.status(500).json({ + reply: "Cortex is having trouble accessing the context right now. Please try again.", + error: err.message + }); + } +} \ 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..21285d7 --- /dev/null +++ b/backend/src/services/retrival.ts @@ -0,0 +1,67 @@ +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) --- + // --- 2. INTERVIEW COLLECTION (Analysis Mode) --- +if (questionContext.type === "INTERVIEW_COLLECTION") { + // Option A: Specific IDs fetch karein (Agar API support karti hai) + if (questionContext.interviewIds && questionContext.interviewIds.length > 0) { + // Aap ek query parameter bhej sakte hain ya loop chala sakte hain + 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 || []); + } + + // Option B: Backup - Saare fetch karein (Jo aap abhi kar rahe hain) + 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 || []); +} + + // --- 3. SINGLE INTERVIEW (Specific Experience) --- + // retrival.ts fix + +// --- 3. SINGLE INTERVIEW (Specific Experience) --- +// retrival.ts +// retrival.ts +if (questionContext.type === "INTERVIEW_EXPERIENCE") { + // Added /v1 to match app.use("/api/v1", routes(upload)) in app.ts + 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; +} + + } catch (error) { + console.error("❌ Error in RAG retrieval:", error); + return null; + } + + 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/public/Chatbot-icon.png b/frontend/public/Chatbot-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..36c4bc0e111745e957c068a7a1ed21255d4ad412 GIT binary patch literal 22399 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 aC-fR+u=KD$l1=|2*VBG*w_MBq)&Bs`p|D{9 literal 0 HcmV?d00001 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 ? ( + + ) : null; +}; + function App() { + const [currentQuestionContext, setCurrentQuestionContext] = useState(null); + return ( <> - } /> - } > - } /> - } /> - } /> - } /> - }> - } /> - + } /> + } > + } /> + } /> + } /> + } /> + }> + } /> - } /> + + } /> + ); } diff --git a/frontend/src/components/chatbot/ChatbotIcon.jsx b/frontend/src/components/chatbot/ChatbotIcon.jsx new file mode 100644 index 0000000..b9bfa36 --- /dev/null +++ b/frontend/src/components/chatbot/ChatbotIcon.jsx @@ -0,0 +1,102 @@ +"use client"; +import { motion, AnimatePresence, useAnimation } from "framer-motion"; +import { useEffect, useState } from "react"; + +const DARK_BROWN = "#2C1810"; +const BURNT_ORANGE = "#C1502E"; +const OFF_WHITE_LIGHT = "#FFF6EE"; +const SAFFRON = "#FF9933"; + +const CodingCortexIcon = ({ isHovered }) => { + const javaCode = ["public class Cortex {", " void help() {", " System.out.println();", " }", "}"]; + return ( + + + + + + + + {!isHovered ? ( + + + + + + ) : ( + + +
+ + {javaCode.join("\n") + "\n" + javaCode.join("\n")} + +
+
+
+ )} +
+ + + +
+ ); +}; + +export default function ChatbotIcon({ onClick }) { + const [hovered, setHovered] = useState(false); + const controls = useAnimation(); + + useEffect(() => { + if (!hovered) { + controls.start({ y: [0, -8, 0], transition: { duration: 3, repeat: Infinity, ease: 'easeInOut' } }); + } else { + controls.start({ scale: 1.1, transition: { duration: 0.2 } }); + } + }, [hovered, controls]); + + return ( +
+ + {hovered && ( + +
+ System: + system.ask() +
+
+ )} +
+ + setHovered(true)} + onMouseLeave={() => setHovered(false)} + animate={controls} + className="relative p-1 border-4 transition-all duration-300 cursor-pointer" + style={{ + borderColor: DARK_BROWN, + backgroundColor: hovered ? "#FFFFFF" : OFF_WHITE_LIGHT, + borderRadius: "12px", + /* SHADOW: Glow add kiya hai for dark mode popping */ + boxShadow: hovered + ? `8px 8px 0px ${BURNT_ORANGE}, 0px 0px 15px rgba(193, 80, 46, 0.4)` + : `6px 6px 0px ${DARK_BROWN}`, + }} + > +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/chatbot/ChatbotWrapper.jsx b/frontend/src/components/chatbot/ChatbotWrapper.jsx new file mode 100644 index 0000000..73b7752 --- /dev/null +++ b/frontend/src/components/chatbot/ChatbotWrapper.jsx @@ -0,0 +1,316 @@ +"use client"; +import { motion, AnimatePresence } from "framer-motion"; +import ReactMarkdown from "react-markdown"; +import { useState, useRef, useEffect } from "react"; +import ChatbotIcon from "./ChatbotIcon"; // Import the icon component +import { useLocation } from "react-router-dom"; + +// Specific theme colors from QuestionsView.jsx +const COLORS = { + border: "var(--color-chatbot-border, #000000)", + window: "var(--color-chatbot-bg-window, #F5E6D3)", // Light: #F5E6D3, Dark: #1A0D08 + messages: "var(--color-chatbot-bg-messages, #EAD8C3)", + text: "var(--color-chatbot-text, #2C1810)", + + burntOrange: "#C1502E", + saffron: "#FF9933", +}; + +// --- NEW NEOMORPHIC TERMINAL ICON --- +const TerminalIcon = ({ size = "md" }) => { + const isSm = size === "sm"; + return ( +
+
+ {">"} + _ +
+ {/* Decorative Brackets */} +
+ {"{}"} +
+
+ ); +}; + + + +export default function ChatbotWrapper({ currentQuestionContext }) { + const [isOpen, setIsOpen] = useState(false); + const [isFull, setIsFull] = useState(false); + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const [isTyping, setIsTyping] = useState(false); + const location = useLocation(); + + const messagesEndRef = useRef(null); + const lastQuestionContext = useRef(null); + const abortTypingRef = useRef(false); + + + useEffect(() => { + if (currentQuestionContext) { + lastQuestionContext.current = currentQuestionContext; + } + }, [currentQuestionContext]); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + if (!isTyping) { + scrollToBottom(); + } + }, [messages]); + + + + const toggleChat = () => { + setIsOpen((prev) => { + const newState = !prev; + + if (newState && messages.length === 0) { + const welcome = "SYSTEM_READY: Cortex online. How can I help you debug your experience today?"; + let currentText = ""; + let index = 0; + + setTimeout(() => { + const typeWelcome = () => { + if (index < welcome.length) { + currentText += welcome.charAt(index); + setMessages([{ from: "bot", text: currentText }]); + index++; + setTimeout(typeWelcome, 25); + } else { + setMessages([{ from: "bot", text: welcome }]); + } + }; + + typeWelcome(); + }, 500); + } + + return newState; + }); + }; + + + const toggleFullScreen = () => setIsFull((prev) => !prev); + + + const sendMessage = async () => { + if (!input.trim() || isTyping) return; + + const userQuery = input; + const currentPath = location.pathname; // This is the "Context Awareness" part! + + // Add user message to UI + const updatedMessages = [...messages, { from: "user", text: userQuery }]; + setMessages(updatedMessages); + setInput(""); + setIsTyping(true); + + try { + // 3. Call your BACKEND instead of calling Gemini directly from frontend + const response = await fetch(`${import.meta.env.VITE_API_URL}/chat`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + path: currentPath, // "/dsa" + userAnswer: userQuery, + questionContext: lastQuestionContext.current, + history: messages, + + }), + }); + + const data = await response.json(); + + setMessages([...updatedMessages, { from: "bot", text: data.reply }]); + setIsTyping(false); + + } catch (error) { + setMessages([...updatedMessages, { from: "bot", text: "ERR_REFUSED: Check your connection." }]); + } finally { + setIsTyping(false); + } + }; + + + const stopStreaming = () => { + abortTypingRef.current = true; + setIsTyping(false); + }; + + + return ( + <> + {/* 1. Floating Icon */} + + + {/* 2. Chat Window */} + + {isOpen && ( + // Wrapper to hold the floating effect +
+ + {/* Neomorphic Deep Shadow Layer */} +
+ + {/* Main Window */} + + + {/* Header */} +
+
+ +
+

Cortex.exe

+

{">"} STATUS: ACTIVE

+
+
+ + {/* Header Buttons */} +
+ {/* Maximize Button - Turns GREEN on hover */} + + + {/* Close Button - Turns RED on hover */} + +
+ +
+ + {/* Messages */} + {/* Message Area */} +
+ {messages.map((msg, idx) => ( +
+
+ {msg.from !== "user" && } + +
+ {children}, + pre: ({ children }) =>
{children}
+ }} + > + {msg.text} +
+
+
+
+ ))} + + {isTyping && ( +
+ +
+ {/* Dots: Light theme mein Saffron, Dark theme mein White */} +
+
+
+
+
+)} + +
+
+ + {/* Input Area */} +
+
+ {">"} + setInput(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && sendMessage()} + placeholder="ENTER QUERY..." + /* "text-black" add kiya hai taaki light theme mein typing dikhe */ + className="flex-1 bg-white border-4 border-black p-3 text-xs font-bold uppercase text-black placeholder:text-black/40 focus:outline-none" + style={{ boxShadow: `4px 4px 0 0 ${COLORS.border}` }} + /> + +
+
+ + +
+ )} + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/dsa/questions/QuestionView.jsx b/frontend/src/components/dsa/questions/QuestionView.jsx index a9492ea..83bd860 100644 --- a/frontend/src/components/dsa/questions/QuestionView.jsx +++ b/frontend/src/components/dsa/questions/QuestionView.jsx @@ -1,4 +1,5 @@ import { useQuestions } from "@/hooks/useQuestions"; +import { buildQuestionContext } from "@/utils/buildQuestionContext"; import { ArrowLeft, CheckCircle2, @@ -11,7 +12,7 @@ import { useState, useMemo, useEffect } from "react"; import { SearchBar } from "../../common/SearchBar"; import { EmptyState } from "../../common/EmptyState"; -export function QuestionsView({ selectedTopic, onBack }) { +export function QuestionsView({ selectedTopic, onBack, setCurrentQuestionContext, }) { const { questions, isLoading, toggle, completed } = useQuestions(selectedTopic.id); const [difficultyFilter, setDifficultyFilter] = useState("All"); const [searchTerm, setSearchTerm] = useState(""); @@ -48,7 +49,7 @@ export function QuestionsView({ selectedTopic, onBack }) { }; const difficulties = ["All", "Easy", "Medium", "Hard"]; - + return (
@@ -98,19 +99,17 @@ export function QuestionsView({ selectedTopic, onBack }) {