From e9dea4e8e869c3a6f5b56ebe2c7174604d989507 Mon Sep 17 00:00:00 2001 From: Vaishnavi Sampat Shinde Date: Mon, 8 Sep 2025 19:25:10 +0530 Subject: [PATCH 1/3] Added AI powered chatbot integration in DevSync --- backend/config/passport.js | 8 ++ backend/controllers/chatbotController.js | 22 ++++ backend/controllers/contact.controller.js | 3 + backend/dy | 1 + backend/package-lock.json | 32 ++++++ backend/package.json | 2 + backend/routes/chatbot.route.js | 9 ++ backend/server.js | 48 ++++++--- backend/testEnv.js | 5 + backend/testGemini.js | 16 +++ frontend/package-lock.json | 14 +++ frontend/package.json | 1 + frontend/src/App.jsx | 87 +++++++--------- frontend/src/Components/ChatLauncher.jsx | 29 ++++++ frontend/src/Components/ChatMessage.jsx | 17 ++++ frontend/src/Components/ChatWindow.jsx | 119 ++++++++++++++++++++++ frontend/src/main.jsx | 34 +++---- 17 files changed, 364 insertions(+), 83 deletions(-) create mode 100644 backend/controllers/chatbotController.js create mode 100644 backend/dy create mode 100644 backend/routes/chatbot.route.js create mode 100644 backend/testEnv.js create mode 100644 backend/testGemini.js create mode 100644 frontend/src/Components/ChatLauncher.jsx create mode 100644 frontend/src/Components/ChatMessage.jsx create mode 100644 frontend/src/Components/ChatWindow.jsx diff --git a/backend/config/passport.js b/backend/config/passport.js index 73040b5..89b2968 100644 --- a/backend/config/passport.js +++ b/backend/config/passport.js @@ -2,6 +2,14 @@ const passport = require("passport"); const GoogleStrategy = require("passport-google-oauth20").Strategy; const User = require("../models/User"); + + +console.log("Client ID:", process.env.GOOGLE_CLIENT_ID); +console.log("Client Secret:", process.env.GOOGLE_CLIENT_SECRET); +console.log("Callback URL:", process.env.GOOGLE_CALLBACK_URL); + + + passport.use( new GoogleStrategy( { diff --git a/backend/controllers/chatbotController.js b/backend/controllers/chatbotController.js new file mode 100644 index 0000000..36809cc --- /dev/null +++ b/backend/controllers/chatbotController.js @@ -0,0 +1,22 @@ +// backend/controllers/chatbotController.js +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); + +exports.getChatResponse = async (req, res) => { + try { + const { message } = req.body; + if (!message) return res.status(400).json({ error: "Message is required" }); + + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + + const result = await model.generateContent(message); + + const reply = result.response.text(); // Gemini ka reply + + res.json({ reply }); + } catch (err) { + console.error("Chatbot Error:", err); + res.status(500).json({ error: "Failed to get response from Gemini AI" }); + } +}; diff --git a/backend/controllers/contact.controller.js b/backend/controllers/contact.controller.js index 4c90002..f6065db 100644 --- a/backend/controllers/contact.controller.js +++ b/backend/controllers/contact.controller.js @@ -17,3 +17,6 @@ const submitContactForm = async (req, res) => { }; module.exports = { submitContactForm }; + + + diff --git a/backend/dy b/backend/dy new file mode 100644 index 0000000..9b0b425 --- /dev/null +++ b/backend/dy @@ -0,0 +1 @@ +{"error":"Message is required"} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 133eac7..b5fa129 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -8,6 +8,7 @@ "name": "devsync-backend", "version": "1.0.0", "dependencies": { + "@google/generative-ai": "^0.24.1", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", "clienvy": "^1.1.5", @@ -20,6 +21,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.17.1", "multer": "^2.0.2", + "openai": "^5.19.1", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "resend": "^6.0.1" @@ -28,6 +30,15 @@ "nodemon": "^3.1.7" } }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.3.0", "license": "MIT", @@ -1282,6 +1293,27 @@ "node": ">= 0.8" } }, + "node_modules/openai": { + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.19.1.tgz", + "integrity": "sha512-zSqnUF7oR9ksmpusKkpUgkNrj8Sl57U+OyzO8jzc7LUjTMg4DRfR3uCm+EIMA6iw06sRPNp4t7ojp3sCpEUZRQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/parseurl": { "version": "1.3.3", "license": "MIT", diff --git a/backend/package.json b/backend/package.json index c23a822..9a9c579 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "dev": "nodemon server.js" }, "dependencies": { + "@google/generative-ai": "^0.24.1", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", "clienvy": "^1.1.5", @@ -19,6 +20,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.17.1", "multer": "^2.0.2", + "openai": "^5.19.1", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "resend": "^6.0.1" diff --git a/backend/routes/chatbot.route.js b/backend/routes/chatbot.route.js new file mode 100644 index 0000000..6fc59a5 --- /dev/null +++ b/backend/routes/chatbot.route.js @@ -0,0 +1,9 @@ +// backend/routes/chatbot.route.js +const express = require("express"); +const router = express.Router(); +const { getChatResponse } = require("../controllers/chatbotController"); + +// POST /api/chatbot +router.post("/", getChatResponse); + +module.exports = router; diff --git a/backend/server.js b/backend/server.js index 14cc313..97c9f75 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,29 +1,42 @@ // Entry point of the backend server -require("dotenv").config(); -const dbconnection = require("./db/connection"); +require("dotenv").config(); // Load .env first const express = require("express"); -const mongoose = require("mongoose"); +const mongoose = require("mongoose"); // Import mongoose BEFORE using it const cors = require("cors"); const path = require("path"); -const contactRouter = require("./routes/contact.route"); -const passport = require("passport"); // import actual passport -require("./config/passport"); // just execute the strategy config const session = require("express-session"); +const passport = require("passport"); +require("./config/passport"); // execute passport strategy config +const contactRouter = require("./routes/contact.route"); +// ------------------------ +// MongoDB Connection +// ------------------------ +const mongoUri = process.env.MONGO_URI; +if (!mongoUri) { + console.error("Error: MONGO_URI not defined in .env file!"); + process.exit(1); +} +mongoose.connect(mongoUri, { + useNewUrlParser: true, + useUnifiedTopology: true, +}) +.then(() => console.log("MongoDB connected βœ…")) +.catch((err) => console.error("MongoDB connection error:", err)); - -// Initialize express +// ------------------------ +// Initialize Express +// ------------------------ const app = express(); +// Middleware app.use(express.json()); app.use(cors({ - origin: process.env.CLIENT_URL|| "http://localhost:5173", // frontend URL for local dev + origin: process.env.CLIENT_URL || "http://localhost:5173", credentials: true })); - - app.use( session({ secret: process.env.SESSION_SECRET || "devsync_session_secret", @@ -39,16 +52,25 @@ app.use(passport.session()); // Serve uploaded files app.use("/uploads", express.static(path.join(__dirname, "uploads"))); -// Define routes +// ------------------------ +// Define Routes +// ------------------------ app.use("/api/auth", require("./routes/auth")); app.use("/api/profile", require("./routes/profile")); app.use("/api/contact", contactRouter); -// Route to display the initial message on browser +// server.js (somewhere after other app.use(...) lines) +app.use("/api/chatbot", require("./routes/chatbot.route")); + + +// Route to display the initial message app.get("/", (req, res) => { res.send("DEVSYNC BACKEND API"); }); +// ------------------------ +// Start Server +// ------------------------ const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Server is up and running at http://localhost:${PORT} πŸš€`); diff --git a/backend/testEnv.js b/backend/testEnv.js new file mode 100644 index 0000000..0b949c9 --- /dev/null +++ b/backend/testEnv.js @@ -0,0 +1,5 @@ +require("dotenv").config(); + +console.log("Google Client ID:", process.env.GOOGLE_CLIENT_ID); +console.log("Google Client Secret:", process.env.GOOGLE_CLIENT_SECRET); +console.log("Mongo URI:", process.env.MONGO_URI); diff --git a/backend/testGemini.js b/backend/testGemini.js new file mode 100644 index 0000000..a97569d --- /dev/null +++ b/backend/testGemini.js @@ -0,0 +1,16 @@ +require("dotenv").config(); +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +(async () => { + try { + const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); + + // πŸ‘‡ updated model name + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + + const result = await model.generateContent("Hello Gemini!"); + console.log("βœ… Gemini says:", result.response.text()); + } catch (err) { + console.error("❌ Gemini test error:", err); + } +})(); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index afb71c5..47bfdb5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,6 +32,7 @@ "shadcn": "^2.9.2", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.11", + "uuid": "^11.1.0", "zod": "^3.25.76" }, "devDependencies": { @@ -7437,6 +7438,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 143adc8..85bc793 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,6 +34,7 @@ "shadcn": "^2.9.2", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.11", + "uuid": "^11.1.0", "zod": "^3.25.76" }, "devDependencies": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 12ef62a..533189b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,6 @@ // src/App.jsx +import ChatLauncher from "./Components/ChatLauncher"; // πŸ‘ˆ import at top + import React, { useEffect, useState } from "react"; import { Routes, Route } from "react-router-dom"; import Hero from "./Components/Hero"; @@ -9,7 +11,7 @@ import AdStrip from "./Components/Ad"; import { FeaturesSection } from "./Components/Features"; import Footer from "./Components/footer"; import ScrollRevealWrapper from "./Components/ui/ScrollRevealWrapper"; -import Loader from "./Components/ui/Loader"; // βœ… Import the Loader +import Loader from "./Components/ui/Loader"; import Login from "./Components/auth/Login"; import Register from "./Components/auth/Register"; @@ -17,36 +19,25 @@ import Profile from "./Components/profile/Profile"; import ProtectedRoute from "./Components/auth/ProtectedRoute"; import Dashboard from "./Components/Dashboard"; +import { ArrowUp } from "lucide-react"; -// Home component that contains the main landing page content -import { ArrowUp } from "lucide-react"; // <-- icon for back to top - +// βœ… Home component function Home() { const [showTop, setShowTop] = useState(false); useEffect(() => { const handleScroll = () => { - if (window.scrollY > 300) { - setShowTop(true); - } else { - setShowTop(false); - } + setShowTop(window.scrollY > 300); }; window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); }, []); - const scrollToTop = () => { - window.scrollTo({ top: 0, behavior: "smooth" }); - }; - return (
- {/* Navbar */} - {/* Main Content */}
@@ -77,28 +68,23 @@ function Home() {
{/* βœ… Back to Top Button */} - -{showTop && ( - -)} - + {showTop && ( + + )}
); } function App() { const [loading, setLoading] = useState(true); - useEffect(() => { - // Simulate initial app/data loading - const timer = setTimeout(() => { - setLoading(false); - }, 2000); // adjust delay if needed + useEffect(() => { + const timer = setTimeout(() => setLoading(false), 2000); return () => clearTimeout(timer); }, []); @@ -108,25 +94,30 @@ function App() { ); - } + } - return ( - - } /> - } /> - } /> - - - - - } - /> - } /> - + <> + {/* βœ… Routes should only contain Route components */} + + } /> + } /> + } /> + + + + } + /> + } /> + + + {/* βœ… Global components should be outside Routes */} + + ); } -export default App; \ No newline at end of file + +export default App; diff --git a/frontend/src/Components/ChatLauncher.jsx b/frontend/src/Components/ChatLauncher.jsx new file mode 100644 index 0000000..ca614f1 --- /dev/null +++ b/frontend/src/Components/ChatLauncher.jsx @@ -0,0 +1,29 @@ +// src/Components/ChatLauncher.jsx +import React, { useState } from "react"; +import ChatWindow from "./ChatWindow"; +import { FaComments } from "react-icons/fa"; + +export default function ChatLauncher() { + const [open, setOpen] = useState(false); + + return ( + <> + {/* Floating Chat Bubble Button */} + {!open && ( + + )} + + {/* Chat Window */} + {open && ( +
+ setOpen(false)} /> +
+ )} + + ); +} diff --git a/frontend/src/Components/ChatMessage.jsx b/frontend/src/Components/ChatMessage.jsx new file mode 100644 index 0000000..2d3700d --- /dev/null +++ b/frontend/src/Components/ChatMessage.jsx @@ -0,0 +1,17 @@ +// frontend/src/components/ChatMessage.jsx +import React from "react"; + +export default function ChatMessage({ sender = "bot", text }) { + const isUser = sender === "user"; + return ( +
+
+ {text} +
+
+ ); +} diff --git a/frontend/src/Components/ChatWindow.jsx b/frontend/src/Components/ChatWindow.jsx new file mode 100644 index 0000000..66b4ec6 --- /dev/null +++ b/frontend/src/Components/ChatWindow.jsx @@ -0,0 +1,119 @@ +// frontend/src/components/ChatWindow.jsx +import React, { useEffect, useRef, useState } from "react"; +import axios from "axios"; +import { FaPaperPlane } from "react-icons/fa"; +import { v4 as uuidv4 } from "uuid"; +import ChatMessage from "./ChatMessage"; + +const API_BASE = import.meta.env.VITE_API_URL || "http://localhost:5000"; + +export default function ChatWindow({ onClose }) { + const [messages, setMessages] = useState([]); + const [text, setText] = useState(""); + const [loading, setLoading] = useState(false); + const scrollRef = useRef(); + + // create/restore userId + useEffect(() => { + let id = localStorage.getItem("devsync_userId"); + if (!id) { + id = uuidv4(); + localStorage.setItem("devsync_userId", id); + } + + // load local history if any + const saved = localStorage.getItem("devsync_messages"); + if (saved) { + try { setMessages(JSON.parse(saved)); } catch (e) {} + } + + // optional: try to fetch history from backend if implemented + (async () => { + try { + const res = await axios.get(`${API_BASE}/api/chatbot/${id}`); + if (res?.data?.messages) setMessages(res.data.messages); + } catch (err) { + // ignore; backend history optional + } + })(); + }, []); + + // persist + autoscroll + useEffect(() => { + localStorage.setItem("devsync_messages", JSON.stringify(messages)); + if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + }, [messages]); + + const sendMessage = async () => { + const message = text.trim(); + if (!message) return; + const userId = localStorage.getItem("devsync_userId"); + + // optimistic UX + const userMsg = { sender: "user", text: message, timestamp: new Date().toISOString() }; + setMessages((m) => [...m, userMsg]); + setText(""); + setLoading(true); + + try { + const res = await axios.post(`${API_BASE}/api/chatbot`, { userId, message }, { timeout: 60000 }); + const botText = res?.data?.reply || res?.data?.botMessage || "No reply."; + const botMsg = { sender: "bot", text: botText, timestamp: new Date().toISOString() }; + setMessages((m) => [...m, botMsg]); + } catch (err) { + console.error("Chat send error:", err?.response?.data || err?.message); + setMessages((m) => [...m, { sender: "bot", text: "⚠️ Server error. Try again later.", timestamp: new Date().toISOString() }]); + } finally { + setLoading(false); + } + }; + + const onKeyDown = (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }; + + return ( +
+ {/* header */} +
+
+ + DevSync Chatbot +
+ +
+ + {/* messages */} +
+ {messages.length === 0 &&
Hi β€” ask me about your commits, problems solved, or streaks.
} + {messages.map((m, i) => )} +
+ + {/* input */} +
+
+