Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# GitHub Personal Access Token for API requests
# Get your token from: https://github.com/settings/tokens
# Required scopes: repo:status, public_repo
VITE_GITHUB_TOKEN=your_github_token_here
VITE_GOOGLE_ANALYTICS_MEASUREMENT_ID=YOUR_GA_MEASUREMENT_ID_HERE
VITE_GITHUB_TOKEN=your_github_token_here
VITE_GOOGLE_ANALYTICS_MEASUREMENT_ID=YOUR_GA_MEASUREMENT_ID_HERE
VITE_GEMINI_API_KEY=Api Key
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"canvas-confetti": "^1.9.2",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Blogs } from './pages/Blogs';
import { BlogPost } from './pages/BlogPost';
import { LinksPage } from './pages/LinksPage';
import * as ga from './utils/analytics';
import Chatbot from './components/Chatbot';

//Google analytics
function AnalyticsWrapper({ children }: { children: React.ReactNode }) {
Expand Down Expand Up @@ -38,6 +39,7 @@ function App() {
</Routes>
</AnalyticsWrapper>
<BackToTop />
<Chatbot />
</BrowserRouter>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/BackToTop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const BackToTop: React.FC = () => {
return (
<button
onClick={scrollToTop}
className={`fixed bottom-6 right-6 z-50 bg-yellow-400 text-black text-xl px-4 py-2 rounded-full shadow-lg transition-opacity ${
className={`fixed bottom-6 left-6 z-50 bg-yellow-400 text-black text-xl px-4 py-2 rounded-full shadow-lg transition-opacity ${
isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'
}`}
>
Expand Down
150 changes: 150 additions & 0 deletions src/components/Chatbot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useState, useEffect } from 'react';
import { GoogleGenerativeAI } from '@google/generative-ai';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

const Chatbot: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const [isFullScreen, setIsFullScreen] = useState(false);
const [messages, setMessages] = useState<{ text: string; sender: 'user' | 'bot' }[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
const savedMessages = localStorage.getItem('chatHistory');
if (savedMessages) {
setMessages(JSON.parse(savedMessages));
}
}, []);

useEffect(() => {
localStorage.setItem('chatHistory', JSON.stringify(messages));
}, [messages]);

const toggleChat = () => {
setIsOpen(!isOpen);
};

const toggleFullScreen = () => {
setIsFullScreen(!isFullScreen);
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
};

const handleSendMessage = async () => {
if (input.trim() === '') return;

const userMessage = { text: input, sender: 'user' as const };
setMessages((prev) => [...prev, userMessage]);
setInput('');
setIsLoading(true);

try {
const genAI = new GoogleGenerativeAI("Api Key");
const model = genAI.getGenerativeModel({
model: 'gemini-1.5-flash',
systemInstruction: 'You are an AI assistant for a YouTube channel named "Engineering in Kannada". Your name is "EiK Assistant". You should only answer questions related to engineering, technology, and education. You should not answer questions about other topics. If you are asked a question that is not related to these topics, you should politely decline to answer and say that you can only answer questions related to engineering, technology, and education. Please format your responses in Markdown.',
});
const chat = model.startChat({
history: messages.map((msg) => ({
role: msg.sender === 'user' ? 'user' : 'model',
parts: [{ text: msg.text }],
})),
});
const result = await chat.sendMessage(input);
const response = await result.response;
const text = await response.text();

const botMessage = { text, sender: 'bot' as const };
setMessages((prev) => [...prev, botMessage]);
} catch (error) {
console.error('Error fetching response from Gemini:', error);
const errorMessage = { text: 'Sorry, something went wrong.', sender: 'bot' as const };
setMessages((prev) => [...prev, errorMessage]);
} finally {
setIsLoading(false);
}
};

return (
<div
className={`${
isFullScreen ? 'fixed inset-0' : 'fixed bottom-4 right-4'
} z-[999]`}
>
<button
onClick={toggleChat}
className="bg-yellow-500 text-white rounded-full w-16 h-16 flex items-center justify-center text-3xl shadow-lg hover:bg-yellow-600 transition-colors"
aria-label="Toggle Chat"
>
🤖
</button>

{isOpen && (
<div
className={`bg-gray-800 rounded-lg shadow-xl flex flex-col ${
isFullScreen
? 'fixed inset-0'
: 'absolute bottom-20 right-0 w-80 h-96'
}`}
>
<div className="bg-gray-900 p-4 rounded-t-lg flex justify-between items-center">
<h3 className="text-white text-lg font-semibold">Chat with our AI</h3>
<button
onClick={toggleFullScreen}
className="text-white hover:text-gray-400"
>
{isFullScreen ? 'Exit Fullscreen' : 'Fullscreen'}
</button>
</div>
<div className="flex-1 p-4 overflow-y-auto">
{messages.map((msg, index) => (
<div
key={index}
className={`flex mb-3 ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`rounded-lg px-3 py-2 ${
msg.sender === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-700 text-white'
}`}
>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{msg.text}</ReactMarkdown>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-700 text-white rounded-lg px-3 py-2">
Thinking...
</div>
</div>
)}
</div>
<div className="p-4 bg-gray-900 rounded-b-lg flex z-[1000]">
<input
type="text"
value={input}
onChange={handleInputChange}
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
placeholder="Ask a question..."
className="flex-1 px-3 py-2 bg-gray-700 text-white rounded-l-md focus:outline-none"
/>
<button
onClick={handleSendMessage}
className="bg-blue-600 text-white px-4 py-2 rounded-r-md hover:bg-blue-700"
disabled={isLoading}
>
Send
</button>
</div>
</div>
)}
</div>
);
};

export default Chatbot;