Skip to content

Commit

Permalink
Merge pull request #71 from Yaser-123/main
Browse files Browse the repository at this point in the history
Add AI Chatbot Feature to Enhance User Engagement on Study Website
  • Loading branch information
MastanSayyad authored Jul 5, 2024
2 parents 58b636c + cff0b27 commit dc48e96
Show file tree
Hide file tree
Showing 4 changed files with 562 additions and 1 deletion.
280 changes: 280 additions & 0 deletions chatbot.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
<!-- Author (C) @theSmartBisnu -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generative AI Multimodal Chat App</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/a11y-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
</head>
<body>


<div class="chat-container">
<div id="output-field">How can I help you today?</div>
<div id="output-container" class="mb-3"></div>
<div class="input-group mb-3">
<!-- Image Preview Inside Input -->
<div id="image-preview-container" class="input-group-prepend" style="margin-right: -1px;">
<span id="image-preview" style="display: none;">
<img src="" alt="Image preview" style="height: 38px; margin-right: 5px;">
<button type="button" id="clear-image" class="close" aria-label="Close" style="font-size: 30px; line-height: 18px; color: red;">&times;</button>
</span>
</div>
<input type="text" id="prompt-input" class="form-control" placeholder="Type your prompt here..." aria-label="Message input">
<button class="input-group-text" id="inputGroupFileAddon" onclick="document.getElementById('image-input').click();">
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor" class="bi bi-image" viewBox="0 0 16 16">
<path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0"/>
<path d="M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1z"/>
</svg>
</button>
<input type="file" id="image-input" accept="image/*">
<button id="generate-btn" class="btn btn-primary">Send</button>
</div>
</div>

<style>
body, html {
height: 100%;
margin: 0;
display: block;
padding-top: 20px;
width: 100%;
padding-left: 10px;
padding-right: 10px;
justify-content: center;
align-items: center;
background-color: brown; /* Optional: to visualize the centering */
}




</style>

<script type="importmap">
{
"imports": {
"@google/generative-ai": "https://esm.run/@google/generative-ai"
}
}
</script>

<script type="module">
import { GoogleGenerativeAI, HarmBlockThreshold, HarmCategory } from "@google/generative-ai";

const API_KEY = 'YOUR_API_KEY'; // Replace with your gemini-api actual API key
const genAI = new GoogleGenerativeAI(API_KEY);
let chat;

const safetySettings = [
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
},
];


async function fileToGenerativePart(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64Data = reader.result.split(',')[1];
resolve({ inlineData: { data: base64Data, mimeType: file.type } });
};
reader.readAsDataURL(file);
});
}


async function sendMessage(prompt, imageParts = []) {
let model;
let result;
clearGreeting(); // Clear the greeting after sending the message

if (imageParts.length > 0) {
model = genAI.getGenerativeModel({ model: 'gemini-pro-vision', safetySettings });
} else {
if (!chat) {
chat = await genAI.getGenerativeModel({ model: "gemini-pro", safetySettings }).startChat({
history: [],
generationConfig: {
maxOutputTokens: 4000 // maxOutputTokens Limit around 4096
}
});
}
model = chat;
}

try {
if (imageParts.length > 0) {
result = await model.generateContent([prompt, ...imageParts]);
} else {
result = await model.sendMessage(prompt);
}

const response = await result.response;
if (response) {
const text = await response.text();
displayMessage(text, 'ai');
} else {
// This block handles a null response, suggesting blocked content
displayMessage("This content is not safe for display based on current settings.", 'ai');
}
} catch (error) {
console.error("Error during message generation:", error);
displayMessage("This content is not safe for display based on current settings. or an internal error.", 'ai');
}
clearInputs();
}


function displayMessage(message, sender) {
const outputContainer = document.getElementById('output-container');
const msgDiv = document.createElement('div');
msgDiv.classList.add('chat-message', sender === 'user' ? 'user-message' : 'ai-message');

if (sender === 'ai') {
// Show loading animation for AI messages
msgDiv.innerHTML = '<div class="loading">' +
'<div class="loading-dot"></div>' +
'<div class="loading-dot"></div>' +
'<div class="loading-dot"></div>' +
'</div>';
outputContainer.appendChild(msgDiv);

// Simulate processing delay
setTimeout(() => {
// Clear loading animation
msgDiv.innerHTML = '';

if (message.startsWith('```') && message.endsWith('```')) {
// Code block handling
const codeContent = message.substring(3, message.length - 3);
const escapedCode = codeContent.replace(/</g, '&lt;').replace(/>/g, '&gt;');
msgDiv.innerHTML = `<pre><code class="hljs">${escapedCode}</code></pre>`;
window.hljs.highlightBlock(msgDiv.querySelector('code'));

// Add Copy button
const copyButton = document.createElement('button');
copyButton.innerText = 'Copy code';
copyButton.onclick = function() {
navigator.clipboard.writeText(codeContent).then(() => {
// Change text to show confirmation instead of using alert
copyButton.innerText = 'Copied!';
// Optional: revert the button text back to "Copy code" after 2 seconds
setTimeout(() => {
copyButton.innerText = 'Copy code';
}, 2000);
}, (err) => {
console.error('Failed to copy text: ', err);
});
};

msgDiv.appendChild(copyButton);

} else {
// For regular messages
msgDiv.innerHTML = marked.parse(message);
}

// Scroll the output container to the bottom to ensure the latest message is visible
outputContainer.scrollTop = outputContainer.scrollHeight;
}, 1500); // Adjust the delay as needed
} else {
// User messages are displayed immediately without the loading animation
msgDiv.innerHTML = message.startsWith('```') && message.endsWith('```') ?
`<pre><code class="hljs">${message.substring(3, message.length - 3)}</code></pre>` :
marked.parse(message);

// If it's a code block, enable syntax highlighting and add a copy button
if (message.startsWith('```') && message.endsWith('```')) {
const codeBlock = msgDiv.querySelector('pre code');
window.hljs.highlightBlock(codeBlock);
const copyButton = document.createElement('button');
copyButton.innerText = 'Copy code';
copyButton.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(() => {
alert('Code copied to clipboard!');
}, (err) => {
alert('Failed to copy text: ', err);
});
};
msgDiv.appendChild(copyButton);
}

outputContainer.appendChild(msgDiv);
}

// Ensure the latest message is visible
outputContainer.scrollTop = outputContainer.scrollHeight;
}

function clearInputs() {
document.getElementById('prompt-input').value = '';
document.getElementById('image-input').value = '';
clearImagePreview();
}

document.getElementById('generate-btn').addEventListener('click', async () => {
const prompt = document.getElementById('prompt-input').value;
const files = document.getElementById('image-input').files;
const imageParts = await Promise.all([...files].map(fileToGenerativePart));
if (prompt.trim() !== '') {
displayMessage(prompt, 'user');
await sendMessage(prompt, imageParts);
}
});


function clearGreeting() {
const outputField = document.getElementById('output-field');
if (outputField) {
outputField.style.display = 'none'; // Hide the field completely
}
}


document.getElementById('image-input').addEventListener('change', function(event) {
const [file] = event.target.files;
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const previewContainer = document.getElementById('image-preview');
const previewImage = previewContainer.getElementsByTagName('img')[0];
previewImage.src = e.target.result;
previewContainer.style.display = 'flex';
};
reader.readAsDataURL(file);
}
});


document.getElementById('clear-image').addEventListener('click', function() {
// Clear the preview
const previewContainer = document.getElementById('image-preview');
const previewImage = previewContainer.getElementsByTagName('img')[0];
previewImage.src = '';
previewContainer.style.display = 'none';
// Clear the file input
document.getElementById('image-input').value = '';
});


function clearImagePreview() {
const previewContainer = document.getElementById('image-preview');
if (previewContainer) {
const previewImage = previewContainer.getElementsByTagName('img')[0];
previewImage.src = '';
previewContainer.style.display = 'none';
}
}

</script>
</body>
</html>
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
<li class="nav-item">
<a class="nav-link" style="font-weight: 500;" href="signup.html">Sign Up</a>
</li>
<li class="nav-item">
<a class="nav-link" style="font-weight: 500;" href="./chatbot.html">AI</a>
</li>
</ul>
</div>
</div>
Expand Down
97 changes: 96 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,99 @@ var swiper = new Swiper(".review-slider", {
AOS.init({
duration: 800,
delay: 400
});
});

const chatbotToggler = document.querySelector(".chatbot-toggler");
const closeBtn = document.querySelector(".close-btn");
const chatbox = document.querySelector(".chatbox .chat-messages");
const chatInput = document.querySelector(".chat-input textarea");
const sendChatBtn = document.querySelector(".send-btn");

let userMessage = null; // Variable to store user's message
const API_KEY = "YOUR_API_KEY"; // Paste your API key here
const inputInitHeight = chatInput.scrollHeight;

const createChatLi = (message, className) => {
// Create a chat <li> element with passed message and className
const chatLi = document.createElement("li");
chatLi.classList.add("chat", className);
let chatContent = className === "outgoing" ? `<p></p>` : `<span class="material-symbols-outlined">smart_toy</span><p></p>`;
chatLi.innerHTML = chatContent;
chatLi.querySelector("p").textContent = message;
return chatLi; // return chat <li> element
}

const generateResponse = (chatElement) => {
const API_URL = "https://api.openai.com/v1/chat/completions";
const messageElement = chatElement.querySelector("p");

// Define the properties and message for the API request
const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: "gpt-3.5",
messages: [{ role: "user", content: userMessage }],
})
}

// Send POST request to API, get response and set the response as paragraph text
fetch(API_URL, requestOptions)
.then(res => res.json())
.then(data => {
if (data.choices && data.choices.length > 0) {
messageElement.textContent = data.choices[0].message.content.trim();
} else {
messageElement.textContent = "No response from API.";
}
})
.catch(error => {
console.error("Error:", error);
messageElement.classList.add("error");
messageElement.textContent = "Oops! Something went wrong. Please try again.";
})
.finally(() => chatbox.scrollTo(0, chatbox.scrollHeight));
}

const handleChat = () => {
userMessage = chatInput.value.trim(); // Get user-entered message and remove extra whitespace
if (!userMessage) return;

// Clear the input textarea and set its height to default
chatInput.value = "";
chatInput.style.height = `${inputInitHeight}px`;

// Append the user's message to the chatbox
chatbox.appendChild(createChatLi(userMessage, "outgoing"));
chatbox.scrollTo(0, chatbox.scrollHeight);

setTimeout(() => {
// Display "Thinking..." message while waiting for the response
const incomingChatLi = createChatLi("Thinking...", "incoming");
chatbox.appendChild(incomingChatLi);
chatbox.scrollTo(0, chatbox.scrollHeight);
generateResponse(incomingChatLi);
}, 600);
}

chatInput.addEventListener("input", () => {
// Adjust the height of the input textarea based on its content
chatInput.style.height = `${inputInitHeight}px`;
chatInput.style.height = `${chatInput.scrollHeight}px`;
});

chatInput.addEventListener("keydown", (e) => {
// If Enter key is pressed without Shift key and the window
// width is greater than 800px, handle the chat
if (e.key === "Enter" && !e.shiftKey && window.innerWidth > 800) {
e.preventDefault();
handleChat();
}
});

sendChatBtn.addEventListener("click", handleChat);
closeBtn.addEventListener("click", () => document.body.classList.remove("show-chatbot"));
chatbotToggler.addEventListener("click", () => document.body.classList.toggle("show-chatbot"));
Loading

0 comments on commit dc48e96

Please sign in to comment.