Skip to content

Commit

Permalink
Merge pull request #808 from Shariq2003/GeminiChat
Browse files Browse the repository at this point in the history
Added Gemini AI Chat (Backend + Frontend) | Issue #765
  • Loading branch information
manikumarreddyu authored Nov 6, 2024
2 parents 9066109 + b57b514 commit 4ab3a22
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 0 deletions.
1 change: 1 addition & 0 deletions backend/.env.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
MONGODB_URL=
GEMINI_API_KEY=
SMTP_EMAIL=
# Add your SMTP passkey (you need two factor authentication for it and this passkey you need to generate first by following steps :
# 1. Enable 2-Step Verification for Your Email Account
Expand Down
2 changes: 2 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const passport = require('passport');
const authMiddleware = require('./middleware/auth');
const bcrypt = require('bcryptjs')
require("./services/passport")
const geminiChatRoute = require('./routes/geminiChatRoute');
const app = express();

app.use(cors()); // This allows all origins to access your API
Expand All @@ -43,6 +44,7 @@ app.use('/api', rentProductRoutes);
app.use('/api', userRoutes);
app.use('/api/discussions', discussionRoutes);
app.use('/api/products', agriProductRoutes);
app.use('/api/generate-content', geminiChatRoute);


app.post('/api/send-email', async (req, res) => {
Expand Down
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@google/generative-ai": "^0.21.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
Expand Down
22 changes: 22 additions & 0 deletions backend/routes/geminiChatRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const express = require('express');
const dotenv = require("dotenv").config();
const {GoogleGenerativeAI} = require('@google/generative-ai');

const router = express.Router();
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);

router.post('/', async (req, res) => {
const { prompt } = req.body;
try {
const model = genAI.getGenerativeModel({ model: 'gemini-pro' });
const result = await model.generateContent(prompt);
const response = await result.response;
const generatedText = await response.text();

res.status(200).json({ generatedText });
} catch (error) {
res.status(500).json({ message: error.message });
}
});

module.exports = router;
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@google/generative-ai": "^0.21.0",
"@headlessui/react": "^2.1.10",
"@heroicons/react": "^2.1.5",
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.1.3",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/MainContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import CancelAndReturnPolicy from './AgroShopAI/pages/FooterPages/CancelAndRetur
import TermsOfUse from './AgroShopAI/pages/FooterPages/TermsOfUse';
import ForgotPasswordPage from './components/ForgotPassword';
import AccountVerificationPage from './components/EmailVerification';
import GeminiChat from './components/tools/GeminiChat';

const MainContent = () => {
UseScrollToTop();
Expand Down Expand Up @@ -113,6 +114,7 @@ const MainContent = () => {
<Route path="/soiltestingcentres" element={<SoilTestingCentres />} />

<Route path="/TaskReminder" element={<TaskReminder />} />
<Route path="/GeminiChat" element={<GeminiChat />} />
<Route path="/SugarcaneRecognition" element={<SugarcaneRecognition />} />
<Route path="/PaddyRecognition" element={<PaddyRecognition />} />
<Route path="/DiseaseRecognition" element={<DiseaseRecognition />} />
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const Navbar = () => {
<div className="absolute left-0 mt-2 w-60 bg-white text-black rounded-lg shadow-lg z-50">
<NavLink to="/PlantTaskReminder" className="block py-2 px-4 hover:bg-gray-200" onClick={closeMenu}>Plant Task Reminder</NavLink>
<NavLink to="/TaskReminder" className="block py-2 px-4 hover:bg-gray-200" onClick={closeMenu}>Plant Task Reminder Advanced</NavLink>
<NavLink to="/GeminiChat" className="block py-2 px-4 hover:bg-gray-200" onClick={closeMenu}>Gemini Chat</NavLink>
</div>
)}
</div>
Expand Down
172 changes: 172 additions & 0 deletions frontend/src/components/tools/GeminiChat.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React, { useEffect, useRef, useState } from 'react';
import { Container, TextField, Button, Paper, Box, List, ListItem, ListItemText, Divider, IconButton, Stack } from '@mui/material';
import SendIcon from '@mui/icons-material/Send';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { motion } from 'framer-motion';
import img1 from "../../assets/tp.png";

const GeminiChat = () => {
const [userInput, setUserInput] = useState('');
const [chatHistory, setChatHistory] = useState(() => {
const savedHistory = localStorage.getItem('chatHistory');
return savedHistory ? JSON.parse(savedHistory) : [];
});
const chatContainerRef = useRef(null);

useEffect(() => {
localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
}, [chatHistory]);

const formatMessage = (htmlContent) => {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
};


const handleSubmit = async (e) => {
e.preventDefault();
if (!userInput) return;

const userMessage = { sender: "User", text: userInput };
setChatHistory((prev) => [...prev, userMessage]);
setUserInput("");

try {
const previousMessages = chatHistory.map(msg => msg.text).join("\n");
const res = await fetch('http://localhost:8080/api/generate-content/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: "Previous Responses By You: " + previousMessages + "\nMy New Query: " + userInput + "\nNote : Give response in HTML (Tags only, only inside body content)",
}),
});

if (!res.ok) {
throw new Error(`Server error: ${res.statusText}`);
}

const data = await res.json();
const responseLines = data.generatedText.split('\n');
const contentWithoutHeading = responseLines.slice(1).join('\n'); // Skip the first line

const botMessage = { sender: "Gemini AI", text: contentWithoutHeading };
setChatHistory((prev) => [...prev, botMessage]);
} catch (error) {
console.error('Error:', error);
}
};

const handleClearChat = () => {
setChatHistory([]);
localStorage.removeItem('chatHistory');
};

return (
<div
className="min-h-screen flex items-center justify-center p-6 mt-14 relative"
style={{
backgroundImage: `url(${img1})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
<div className="absolute inset-0 bg-black opacity-40 pointer-events-none"></div>
<Container style={{ zIndex: 1, marginBottom: '2rem' }}>
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.5 }}>
<Paper elevation={3} style={{ padding: '1.5rem', borderRadius: '8px', background:"#05B913" }}>
<motion.h1
className="m-2 text-5xl text-center font-extrabold text-white"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5 }}
>
Gemini AI Chat
</motion.h1>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<Box
ref={chatContainerRef}
style={{
height: '60vh',
overflowY: 'auto',
padding: '1rem',
backgroundColor: '#f1f1f1',
borderRadius: '8px',
marginBottom: '1rem',
}}
>
<List>
{chatHistory.map((msg, index) => (
<motion.div
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: index * 0.1 }}
style={{ marginBottom: '0.5rem' }}
>
<ListItem
alignItems="flex-start"
style={{
display: 'flex',
justifyContent: msg.sender === "User" ? 'flex-end' : 'flex-start',
}}
>
<Paper
elevation={1}
style={{
padding: '0.01rem 1rem',
borderRadius: '5px',
backgroundColor: msg.sender === "User" ? '#05B913' : 'white',
color: msg.sender === "User" ? 'white' : 'black',
maxWidth: '70%',
}}
>
<ListItemText
primary={msg.sender === "User" ? msg.text : formatMessage(msg.text)}
secondary={msg.sender === "User" ? "" : msg.sender}
secondaryTypographyProps={{
style: { color: msg.sender === "User" ? '#bbdefb' : '#757575' },
}}
/>
</Paper>
</ListItem>
</motion.div>
))}
</List>
</Box>
</motion.div>
<Divider />
<Stack direction="row" spacing={2} style={{ marginTop: '1rem' }}>
<TextField
fullWidth
variant="outlined"
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
placeholder="Type a message..."
style={{ backgroundColor: 'white', borderRadius: '4px' }}
/>
<Button type="submit" variant="contained" style={{ background: "white", color: "black" }} endIcon={<SendIcon />} onClick={handleSubmit}>
Send
</Button>
<Button variant="contained" style={{background:"white",color:"black"}} onClick={handleClearChat}>
Clear
</Button>
</Stack>
</Paper>
</motion.div>
<ToastContainer />
</Container>
</div>
);
};

export default GeminiChat;

0 comments on commit 4ab3a22

Please sign in to comment.