-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from Yaser-123/main
Add AI Chatbot Feature to Enhance User Engagement on Study Website
- Loading branch information
Showing
4 changed files
with
562 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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;">×</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, '<').replace(/>/g, '>'); | ||
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.