Skip to content

Instantly share code, notes, and snippets.

@paulwababu
Last active June 30, 2024 03:43
Show Gist options
  • Save paulwababu/b7b4972e2026509b0cb4808a821948cb to your computer and use it in GitHub Desktop.
Save paulwababu/b7b4972e2026509b0cb4808a821948cb to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ask Friendly</title>
<script src="https://cdn.tailwindcss.com"></script>
<link
rel="icon"
href="https://res.cloudinary.com/prometheusapi/image/upload/v1710534811/image_3_uqxyey.png"
type="image/x-icon"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/leaflet.css"
/>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
#map {
height: 350px;
}
.leaflet-container {
border-radius: 8px;
}
.chat-container {
max-height: 300px;
overflow-y: auto;
}
.chat-container.expanded {
padding-top: 1.5rem;
}
.user-message, .ai-message {
padding: 1rem;
margin: 0.5rem 0;
border-radius: 10px;
border: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.user-message {
background-color: #e0e0e0; /* Light gray background for user message */
color: #333; /* Dark text for non-dark mode */
text-align: left;
}
.ai-message {
background-color: #f5f5f5; /* Slightly darker gray background for contrast */
color: #333;
text-align: left; /* Change this to left */
display: flex; /* Ensure flex display is applied */
flex-direction: column; /* Align content vertically */
align-items: flex-start; /* Align items to the start */
}
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap");
body {
font-family: "Open Sans", sans-serif;
}
html,
body {
height: 100%;
margin: 0;
}
.container {
height: 100vh;
}
.sidebar {
background-color: #f3f4f6;
padding: 1rem;
border-right: 1px solid #e5e7eb;
overflow-y: auto;
transition: transform 0.3s ease-in-out;
}
.sidebar.collapsed {
transform: translateX(-100%);
}
.sidebar-header {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
}
.sidebar-item {
display: flex;
align-items: center;
padding: 0.5rem;
border-radius: 0.375rem;
margin-bottom: 0.5rem;
cursor: pointer;
}
.sidebar-item:hover {
background-color: #e5e7eb;
}
.sidebar-item-icon {
width: 2rem;
height: 2rem;
border-radius: 50%;
margin-right: 0.75rem;
}
.sidebar-item-text {
font-size: 0.875rem;
}
.sidebar-divider {
border-top: 1px solid #e5e7eb;
margin: 1rem 0;
}
.user-icon {
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: #8b5cf6;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
margin-right: 0.75rem;
}
.logged-in-user {
display: flex;
align-items: center;
padding: 0.5rem;
border-radius: 0.375rem;
cursor: pointer;
}
.logged-in-user:hover {
background-color: #e5e7eb;
}
.toggle-sidebar-btn {
position: absolute;
top: 10px;
right: 10px;
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
padding: 0.5rem;
cursor: pointer;
z-index: 1000;
}
.sidebar-container {
width: 20vw;
}
.main-container {
width: 60vw;
}
@media (min-width: 1000px) {
#toggle-sidebar-btn-2 {
display: none;
}
}
@media (max-width: 1000px) {
.sidebar-container {
position: fixed;
height: 100vh;
z-index: 10000;
width: 20rem;
}
.main-container {
margin: 0 auto;
width: 70vw;
}
}
@media (max-width: 600px) {
.main-container {
width: 80vw;
}
}
@media (max-width: 450px) {
.main-container {
width: 90vw;
}
}
@media (max-height: 675px) {
#map {
height: 245px;
}
}
@media (max-height: 585px) {
#map {
height: 180px;
}
}
@media (max-height: 520px) {
#map {
height: 120px;
}
}
@media (max-height: 460px) {
#map {
height: 90px;
}
}
.submit-button {
transition: transform 0.3s ease, background-color 0.3s ease;
}
.submit-button:hover {
transform: scale(1.05);
background-color: #3b82f6;
}
.submit-button:active {
animation: jump 0.3s ease;
}
@keyframes jump {
0% {
transform: scale(1);
}
50% {
transform: scale(0.95) translateY(-5px);
}
100% {
transform: scale(1);
}
}
</style>
<script>
// Function to fetch conversations from the server
async function fetchConversations() {
try {
const sessionID = sessionStorage.getItem("sessionID");
const response = await fetch(`/chat-history/${sessionID}`);
const conversations = await response.json();
updateConversationList(conversations);
} catch (error) {
console.error("Error fetching conversations:", error);
}
}
// Function to update the conversation list UI with the fetched data
function updateConversationList(conversations) {
const conversationList = document.getElementById("conversation-list");
conversationList.innerHTML = "";
conversations.forEach((conversation) => {
const conversationElement = document.createElement("div");
conversationElement.className = "sidebar-item conversation";
conversationElement.textContent = conversation.latest_message;
conversationElement.setAttribute(
"data-conversation-id",
conversation.conversation_id
);
conversationElement.addEventListener("click", () => {
fetchConversationMessages(conversation.conversation_id);
});
conversationList.appendChild(conversationElement);
});
}
// Function to fetch conversation messages from the server
async function fetchConversationMessages(conversationId) {
try {
const sessionID = sessionStorage.getItem("sessionID");
const response = await fetch(
`/chat-conversation/${sessionID}/${conversationId}`
);
const messages = await response.json();
displayConversationMessages(messages);
} catch (error) {
console.error("Error fetching conversation messages:", error);
}
}
// Function to display conversation messages in the chat container
function displayConversationMessages(messages) {
const chatContainer = document.getElementById("chat-container");
chatContainer.innerHTML = "";
messages.forEach((message) => {
const messageElement = document.createElement("div");
messageElement.classList.add("mb-4", "p-4", "rounded-lg", "chat-bubble");
if (message.role === "user") {
messageElement.classList.add("user-message",);
} else {
messageElement.classList.add("ai-message", "flex", "flex-col", "items-start");
}
messageElement.textContent = message.content;
chatContainer.appendChild(messageElement);
});
}
// Load conversations when the page is loaded
window.addEventListener("load", fetchConversations);
</script>
<style>
.chat-bubble {
border: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.bg-custom-brue {
text-align: left;
color: #fff; /* Slightly lighter background for contrast */
background-color: #616161;
}
.bg-custom-blue {
text-align: left;
background-color: #424242;
color: #fff;
}
</style>
</head>
<body class="bg-white-100">
<div class="flex" style="height: 100vh;">
<div class="sidebar-container sidebar collapsed flex flex-col">
<div class="flex-grow overflow-y-auto">
<div class="sidebar-header">
<div class="text-lg font-semibold flex items-center">
<img
src="https://res.cloudinary.com/prometheusapi/image/upload/v1710534811/image_3_uqxyey.png"
alt="ChatBot Icon"
class="rounded-full h-12 w-12"
/>
<span class="ml-2">Ask Friendly</span>
</div>
</div>
<div class="sidebar-divider"></div>
<div class="sidebar-header">Conversations</div>
<div id="conversation-list"></div>
</div>
<div class="flex items-center justify-center mt-4">
<button
id="dark-mode-toggle"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center"
>
<i class="fas fa-moon mr-2"></i> Dark Mode
</button>
</div>
<div class="sidebar-item">
<a href="http://127.0.0.1:8000" target="_blank" class="flex items-center">
<i class="fas fa-cog mr-2"></i>
<span class="sidebar-item-text">Admin Panel</span>
</a>
</div>
<div class="logged-in-user">
<div class="user-icon" id="user-initials"></div>
<span class="sidebar-item-text" id="user-name"></span>
</div>
</div>
<div class="main-container flex flex-col">
<button
id="show-map-btn"
class="text-gray-400 hover:text-gray-600"
style="display: none"
>
<i class="fas fa-map-marker-alt fa-lg"></i>
</button>
<script>
$("#dark-mode-toggle").on("click", function () {
$("body").toggleClass("dark-mode");
// Save the user's preference in local storage
if ($("body").hasClass("dark-mode")) {
localStorage.setItem("darkMode", "true");
} else {
localStorage.setItem("darkMode", "false");
}
});
// Load the user's preference from local storage on page load
const darkModeEnabled = localStorage.getItem("darkMode") === "true";
if (darkModeEnabled) {
$("body").addClass("dark-mode");
}
</script>
<style>
/* Dark mode styles for the form area */
body.dark-mode .bg-white {
background-color: #212121;
}
body.dark-mode input,
body.dark-mode textarea,
body.dark-mode button {
border-color: #4a4a4a;
}
/* Dark mode styles for input form background color */
body.dark-mode input,
body.dark-mode textarea {
background-color: #3e3e3e;
color: #fff;
}
body.dark-mode input::placeholder,
body.dark-mode textarea::placeholder {
color: #888;
}
body.dark-mode button {
background-color: #4a4a4a;
}
body.dark-mode button:hover {
background-color: #5c5c5c;
}
/* Dark mode styles */
body.dark-mode {
background-color: #212121;
}
body.dark-mode .sidebar {
background-color: #171717;
}
body.dark-mode .sidebar-item {
background-color: #1e1e1e;
color: #fff;
}
body.dark-mode .sidebar-item:hover {
background-color: #2a2a2a;
}
body.dark-mode .sidebar-divider {
border-top: 1px solid #2a2a2a;
}
body.dark-mode .user-icon {
background-color: #8b5cf6;
}
body.dark-mode .logged-in-user {
background-color: #1e1e1e;
color: #fff;
}
body.dark-mode .sidebar-header {
color: #fff;
}
body.dark-mode .conversation {
background-color: #1e1e1e;
color: #fff;
}
body.dark-mode .conversation:hover {
background-color: #2a2a2a;
}
body.dark-mode .chat-container {
background-color: #3E3E3E;
}
body.dark-mode .user-message {
background-color: #424242; /* Darker background for user message */
color: #fff; /* Light text for dark mode */
}
body.dark-mode .ai-message {
background-color: #616161; /* Slightly lighter background for contrast */
color: #fff;
}
/* You may need to adjust other colors for text, icons, etc. to fit the dark mode theme */
#show-map-btn {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
margin: 0 10px;
}
</style>
<script>
$("#show-map-btn").on("click", function () {
$("#map-container").show();
});
</script>
<div id="map-container" class="relative mb-4">
<div id="toggle-sidebar-btn-container">
<button class="toggle-sidebar-btn" id="toggle-sidebar-btn">
<i class="fas fa-bars"></i>
</button>
</div>
<div
class="flex justify-between items-center border-b-2 border-gray-200 pb-4"
>
<!-- Removed the toggle-sidebar-btn from here -->
<div class="flex-grow"></div>
<div class="flex justify-end items-center">
<button
id="cancel-map-btn"
class="text-gray-400 hover:text-gray-600"
>
<i class="fas fa-times fa-lg"></i>
</button>
</div>
</div>
<div id="map" class="h-96 rounded-lg"></div>
</div>
<div class="flex justify-between items-center mt-4">
<div class="flex items-center">
<i class="fas fa-coins mr-2 coin-icon"></i>
<span id="token-count">Loading tokens...</span>
</div>
</div>
<style>
body.dark-mode .coin-icon {
color: #fff;
}
body.dark-mode .icon-circle {
background-color: #333;
color: #ccc;
}
body.dark-mode #token-count {
color: #fff;
}
body.dark-mode .icon-circle:hover {
background-color: #444;
color: #fff;
}
#ask-friendly-logo {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
width: 200px; /* Adjust the width as needed */
height: auto; /* Maintain the aspect ratio */
pointer-events: none; /* Ignore mouse events to allow interaction with content above the logo */
}
#toggle-sidebar-btn-container {
position: absolute;
top: -10px; /* Keep this the same if the vertical position is good */
left: 45px; /* Increase if you need it to move right, or decrease to move left */
z-index: 1001; /* Keep it above Leaflet's controls */
}
.hidden {
display: none;
}
</style>
<div id="chat-container" class="flex-grow overflow-y-auto">
<div class="user-message">
Hi, what's happening with the wildlife in Maui?
</div>
<div class="ai-message">
There's a significant event involving wildlife. We're gathering more
information and will update shortly.
</div>
</div>
<div class="text-lg text-gray-500 text-right">
Call 911 for life-threatening emergencies. Assistant is not perfect
and can make mistakes or misunderstand information.
</div>
<div class="bg-white shadow rounded-lg p-4">
<div class="flex items-center justify-between">
<input id="message" type="text" placeholder="What can I do to assist you? 😊" class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none">
<button
id="submit-button"
class="ml-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
<i id="submit-icon" class="fas fa-paper-plane"></i>
<i id="pause-icon" class="fas fa-pause hidden"></i>
</button>
</div>
<div class="flex items-center justify-between mt-4">
<div class="flex space-x-2">
<button class="icon-circle text-gray-500 hover:text-gray-700">
<i class="fas fa-plus"></i>
</button>
<button class="icon-circle text-gray-500 hover:text-gray-700">
<i class="fas fa-font"></i>
</button>
<button class="icon-circle text-gray-500 hover:text-gray-700">
<i class="far fa-smile"></i>
</button>
<button class="icon-circle text-gray-500 hover:text-gray-700">
<i class="fas fa-at"></i>
</button>
<button class="icon-circle text-gray-500 hover:text-gray-700">
<i class="fas fa-video"></i>
</button>
</div>
<div class="flex-grow text-center">
<div class="mic-container">
<div class="circle">
<i class="fas fa-microphone"></i>
</div>
<span id="listening-indicator" style="display: none; margin-left: 10px; color: gray;">Listening...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.Speech = (function() {
var self;
return self = {
initialized: false,
init: function() {
self.initialized = true;
self.initSpeechRecognition();
self.responseField = document.getElementById('response');
self.listening = false;
self.spoke = false;
self.runningAvg = null;
self.avgCount = 0;
self.spokeCount = 0;
self.finished = false;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
navigator.getUserMedia({
audio: true
}, self.initStream, self.onError);
self.listen();
},
onError: function(err) {
return console.log(err);
},
initStream: function(stream) {
console.info('initStream');
var source;
window.AudioContext = window.AudioContext || window.webkitAudioContext;
self.audioContext = new AudioContext();
self.analyser = self.audioContext.createAnalyser();
source = self.audioContext.createMediaStreamSource(stream);
return source.connect(self.analyser);
},
checkVolume: function(level) {
console.info('checkVolume');
if (Synth.speaking || self.finished) {
return;
}
if (self.runningAvg == null) {
return self.runningAvg = level;
}
self.avgCount++;
self.runningAvg = ((self.runningAvg * self.avgCount) + level) / (self.avgCount + 1);
// Update the circle scale based on the volume level
const scaleFactor = 1 + (level / 100);
document.querySelector('.circle').style.transform = `scale(${scaleFactor})`;
if (level > self.runningAvg + 12) {
self.startSpokeTimeoutHandler();
}
},
startSpokeTimeoutHandler: function() {
console.info('startSpokeTimeoutHandler');
clearTimeout(self.spokeTimeout);
return self.spokeTimeout = setTimeout(self.spokeTimeoutHandler, 2000);
},
spokeTimeoutHandler: function() {
console.info('spokeTimeoutHandler');
if (self.finished) {
return;
}
self.spokeCount++;
},
clearSpokeCount: function() {
console.info('clearSpokeCount');
return self.spokeCount = 0;
},
processData: function() {
console.info('processData');
var avg, freqByteData, i, total;
if (!self.initialized) {
return;
}
if (self.finished) {
return;
}
freqByteData = new Uint8Array(self.analyser.frequencyBinCount);
self.analyser.getByteFrequencyData(freqByteData);
i = parseInt(freqByteData.length / 3);
total = 0;
while (i--) {
total += freqByteData[i];
}
avg = total / freqByteData.length;
self.checkVolume(avg);
},
initSpeechRecognition: function() {
console.info('initSpeechRecognition');
if (typeof webkitSpeechRecognition !== "function") {
console.log("Speech recognition not supported");
return;
}
self.recognition = new webkitSpeechRecognition();
self.recognition.interimResults = true;
self.recognition.onend = function(e) {
console.log('recognition.onend');
if (self.listening) {
return self.listen();
}
};
return self.recognition.onresult = function(e) {
console.log('on result');
var confidence, i, isFinal, primary, result, transcript;
clearTimeout(self.spokeTimeout);
self.spoke = false;
confidence = 0;
transcript = '';
isFinal = false;
for (i in e.results) {
result = e.results[i];
if (!result.length) {
break;
}
primary = result[0];
confidence = Math.max(primary.confidence, confidence);
if (result.isFinal) {
isFinal = result.isFinal;
}
if (i === '0' || primary.transcript.substr(0, 1) === ' ') {
transcript += primary.transcript;
}
}
if (isFinal) {
return self.test(transcript);
}
};
},
listen: function() {
console.info('listen');
self.listening = true;
try {
return self.recognition.start();
} catch (_error) {
console.info('error listen');
}
},
stopListening: function() {
console.info('stopListening');
self.listening = false;
if (self.recognition) {
return self.recognition.abort();
}
},
finish: function() {
console.info('finish');
self.stopListening();
return self.finished = true;
},
test: function(searchString) {
console.info('test: ' + searchString);
document.getElementById('message').value += ' ' + searchString;
}
};
})();
const micContainer = document.querySelector('.mic-container');
const circle = document.querySelector('.circle');
const listeningIndicator = document.getElementById('listening-indicator');
micContainer.addEventListener('click', async () => {
try {
// Check if we are already listening
if (!circle.classList.contains('active')) {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// If permission is granted and stream is received
if (stream) {
circle.classList.add('active'); // Activate pulsating effect
listeningIndicator.style.display = 'inline'; // Show listening indicator
Speech.init(); // Initialize speech recognition or any other functionality
}
} else {
circle.classList.remove('active'); // Deactivate pulsating effect
listeningIndicator.style.display = 'none'; // Hide listening indicator
Speech.finish(); // Stop speech recognition or any other functionality
}
} catch (error) {
console.error('Microphone access denied or error: ', error);
circle.classList.remove('active'); // Ensure circle is not active if access is denied
listeningIndicator.style.display = 'none'; // Ensure indicator is not showing if access is denied
Speech.finish(); // Clean up or reset speech recognition setup if needed
}
});
! function() {
for (var a = 0, b = ["ms", "moz", "webkit", "o"], d = 0; d < b.length && !window.requestAnimationFrame; ++d) window.requestAnimationFrame = window[b[d] + "RequestAnimationFrame"], window.cancelAnimationFrame = window[b[d] + "CancelAnimationFrame"] || window[b[d] + "CancelRequestAnimationFrame"];
window.requestAnimationFrame || (window.requestAnimationFrame = function(b, d) {
var e = (new Date).getTime(),
g = Math.max(0, 16 - (e - a)),
h = window.setTimeout(function() {
b(e + g)
}, g);
a = e + g;
return h
});
window.cancelAnimationFrame || (window.cancelAnimationFrame =
function(a) {
clearTimeout(a)
})
}();
</script>
<style>
#listening-indicator {
font-size: 14px;
color: #666;
transition: opacity 0.3s ease;
}
.icon-circle {
display: inline-flex; /* Ensures the icon is centered inside the circle */
align-items: center; /* Centers the icon vertically */
justify-content: center; /* Centers the icon horizontally */
width: 36px; /* Size of the circle - adjust as necessary */
height: 36px; /* Size of the circle - adjust as necessary */
background-color: #333; /* Background color of the circle */
color: #ccc; /* Icon color */
border-radius: 50%; /* Makes the background a circle */
cursor: pointer;
transition: background-color 0.3s, color 0.3s; /* Smooth transition for hover effects */
}
.icon-circle:hover {
background-color: #444; /* Slightly lighter background on hover */
color: #fff; /* White color on hover for better visibility */
}
.circle {
width: 50px;
height: 50px;
border-radius: 50%;
background: #ffffff;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
transition: transform 0.2s ease-in-out;
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12), 0 3px 5px -1px rgba(0, 0, 0, .2);
cursor: pointer;
}
.circle i {
color: #b2b1b1;
font-size: 23px;
transition: .9s;
}
.circle:before {
content: '';
width: 80px;
height: 80px;
border-radius: 50%;
opacity: .2;
z-index: -1;
position: absolute;
}
.circle.active {
background: #1d4ed8;
}
.circle.active:before {
background: gray;
animation: bounce .8s ease-in-out infinite .5s;
}
.circle.active i {
color: #ffffff;
}
.mic-container {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 0; /* Ensures it does not grow */
margin-left: auto; /* Pushes the microphone container slightly to the left */
margin-right: 80px; /* Adjust this value to move more or less */
}
@keyframes bounce {
0% {
transform: scale(1);
}
25% {
transform: scale(1.4);
}
75% {
transform: scale(1);
}
100% {
transform: scale(1.3);
}
}
</style>
<!-- Confirmation Modal -->
<div
id="confirmation-modal"
class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden justify-center items-center"
style="z-index: 1000"
>
<div class="bg-white rounded-lg p-4 max-w-sm mx-auto">
<div class="flex flex-col items-center justify-center">
<img
src="https://res.cloudinary.com/prometheusapi/image/upload/v1710534811/image_3_uqxyey.png"
alt="ChatBot Icon"
class="rounded-full h-12 w-12 mb-4"
/>
<p class="text-gray-800 font-semibold">
Are you sure you want to clear the chat?
</p>
<div class="mt-4 flex justify-center space-x-4">
<button
id="confirm-clear"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded inline-flex items-center"
>
<i class="fas fa-trash mr-2"></i> Confirm
</button>
<button
id="cancel-clear"
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center"
>
<i class="fas fa-times mr-2"></i> Cancel
</button>
</div>
</div>
</div>
</div>
<style>
.custom-popup {
width: 300px;
font-family: Arial, sans-serif;
}
.custom-popup .header {
padding: 5px;
color: white;
text-align: center;
font-weight: bold;
background-color: red; /* Updated */
}
.custom-popup .body {
padding: 10px;
display: flex;
flex-direction: column; /* Updated */
align-items: center; /* Updated */
}
.custom-popup .body .profile-img {
width: 50px;
height: 50px;
border-radius: 50%;
margin-bottom: 10px; /* Updated */
}
.custom-popup .body .user-info { /* Added */
text-align: center;
}
.custom-popup .body p {
margin: 0;
font-size: 12px;
}
.custom-popup .body p strong {
font-size: 14px;
}
.custom-popup .footer {
padding: 5px;
background: #f1f1f1;
font-size: 12px;
text-align: center;
}
.custom-popup .footer p {
margin: 0;
}
.custom-popup .interactive-icons { /* Added */
display: flex;
justify-content: center;
margin-top: 5px;
}
.custom-popup .interactive-icons i { /* Added */
margin: 0 5px;
cursor: pointer;
}
</style>
<script>
$(document).ready(function () {
// Function to handle successful location retrieval
function handleLocationSuccess(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
// Store location information for later use
window.userLocation = `Latitude: ${latitude}, Longitude: ${longitude}`;
getLocationName(latitude, longitude).then((locationName) => {
userLocationName = locationName;
console.log(userLocationName);
});
}
function getLocationName(latitude, longitude) {
const apiKey = "93e4e817d64d43b1966d7b22059698b5";
const url = `https://api.opencagedata.com/geocode/v1/json?q=${latitude}+${longitude}&key=${apiKey}`;
return fetch(url)
.then((response) => response.json())
.then((data) => {
if (data.status.code === 200 && data.results.length > 0) {
return data.results[0].formatted;
} else {
return "Unknown location";
}
})
.catch((error) => {
console.error("Error:", error);
return "Unknown location";
});
}
// Function to handle location retrieval errors
function handleLocationError(error) {
console.warn(`ERROR(${error.code}): ${error.message}`);
window.userLocation = "Location not available";
}
// Request user's location
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
handleLocationSuccess,
handleLocationError
);
} else {
console.log("Geolocation is not supported by this browser.");
window.userLocation = "Location not supported";
}
$("#message").on("focus", function () {
const text = $(this).val();
if (text.length === 0) {
$("#suggestions").show(); // Show suggestions if text area is empty
}
});
$("#message").on("input", function () {
if ($(this).val().length === 0) {
$("#suggestions").show(); // Show suggestions if text area is emptied
} else {
$("#suggestions").hide(); // Hide suggestions if text area is not empty
}
});
$("#message").on("blur", function () {
// Hide suggestions when the text area loses focus
// Use a timeout to allow click event to process first
setTimeout(function () {
$("#suggestions").hide();
}, 100);
});
$(".suggestion").on("click", function () {
const suggestion = $(this).text();
$("#message").val(suggestion).focus(); // Set the clicked suggestion text to the text area and focus
$("#suggestions").hide(); // Hide the suggestions
});
// Toggle sidebar on button click
$("#toggle-sidebar-btn").on("click", function () {
$(".sidebar").toggleClass("collapsed");
});
// Close sidebar when clicking outside of it (mobile)
$(document).on("click", function (event) {
if (!$(event.target).closest(".sidebar, #toggle-sidebar-btn").length) {
$(".sidebar").addClass("collapsed");
}
});
// Cancel map on button click
$("#cancel-map-btn").on("click", function () {
$("#map-container").hide();
$("#show-map-btn").show();
});
});
</script>
<script>
let userLocationName;
window.onload = function () {
// Extract the entire query string from the URL
const fullUrl = window.location.href;
// Find the start of the 'creator' parameter and extract the substring from there
const creatorParamStart = fullUrl.indexOf("?creator=") + "?creator=".length;
if (creatorParamStart === "?creator=".length - 1) {
// 'creator' parameter is missing in the URL
alert("Please log in to FriendlyForce first and then access the Ask Friendly platform.");
// Redirect the user to the FriendlyForce login page
window.location.href = "https://friendlyforce.live/login";
return;
}
let creatorJson = fullUrl.substring(creatorParamStart);
// Assume that 'creator' is the last parameter in the URL
// Replace any '&' beyond the first occurrence in the JSON string (caused by incorrect URL parsing)
creatorJson = creatorJson.replace(/&(?=\[^&\]\*$)/, "%26");
// Decode the URI component to handle URL encoding
creatorJson = decodeURIComponent(creatorJson);
try {
// Parse the JSON string to an object
const creator = JSON.parse(creatorJson);
// Extract the 'firstName' and 'lastName' properties
const firstName = creator.firstName;
const lastName = creator.lastName;
const creatorID = creator.id;
console.log("Creator ID:", creatorID);
// Step 3: Update the user initials and name in the HTML
const userInitials = document.getElementById("user-initials");
const userName = document.getElementById("user-name");
userInitials.textContent = `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
userName.textContent = `${firstName} ${lastName}`;
// Now you can use firstName and lastName as needed
console.log("First Name:", firstName);
console.log("Last Name:", lastName);
console.log("Creator ID:", creatorID);
// Get the current user token
getCurrentUserToken(creator);
} catch (error) {
console.error("Error parsing JSON from URL:", error);
// Handle the case when the 'creator' parameter is present but has an invalid format
alert("Invalid 'creator' parameter. Please log in to FriendlyForce and access the Ask Friendly platform again.");
// Redirect the user to the FriendlyForce login page
window.location.href = "https://friendlyforce.live/login";
}
};
function getCurrentUserToken(creator) {
// Make an API call to check if the user exists
fetch(`https://admindash.friendlyforce.live/friendlyforce/chat/api/v1/check-user-exists/?creator=${encodeURIComponent(JSON.stringify(creator))}`)
.then(response => response.json())
.then(data => {
if (data.user_exists) {
// User exists, retrieve the token count
fetch(`https://admindash.friendlyforce.live/friendlyforce/chat/api/v1/tokens/?creator=${encodeURIComponent(JSON.stringify(creator))}`)
.then(response => response.json())
.then(data => {
// Extract the remaining tokens from the API response
const remainingTokens = data.remaining_tokens;
// Update the token count in the HTML
const tokenCountElement = document.getElementById("token-count");
tokenCountElement.textContent = `${remainingTokens} tokens available`;
})
.catch(error => {
console.error("Error retrieving user token count:", error);
});
} else {
// User doesn't exist, create the user
fetch('https://admindash.friendlyforce.live/friendlyforce/chat/api/v1/create-user/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: creator.id,
tokens: 1000000,
avatar: creator.avatar,
first_name: creator.firstName,
last_name: creator.lastName,
}),
})
.then(response => response.json())
.then(data => {
// User created successfully, update the token count in the HTML
const tokenCountElement = document.getElementById("token-count");
tokenCountElement.textContent = `${data.tokens} tokens available`;
})
.catch(error => {
console.error("Error creating user:", error);
});
}
})
.catch(error => {
console.error("Error checking user existence:", error);
});
}
</script>
<script>
var map = L.map("map").setView([20.798363, -156.331925], 10); // Default location
$(document).ready(function () {
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap contributors",
}).addTo(map);
// Fetch data and add markers
function loadMarkers() {
fetch("http://localhost:3003/events") // Adjust URL as needed
.then((response) => response.json())
.then((data) => {
console.log(data); // Log the data to see what's actually being returned
// Assuming the data is an object with a "data" key which is an array
data.data.forEach((entry) => {
const lat = entry.lat || entry.location.latitude;
const lng = entry.lng || entry.location.longitude;
if (!lat || !lng) {
console.error("Missing latitude or longitude:", entry);
} else {
var marker = L.marker([lat, lng]).addTo(map);
const popupContent = `
<div class="custom-popup">
<div class="header" style="background-color: red; color: white;">
<h3>Severe</h3>
</div>
<div class="body">
<img src="${entry.eventHostProfileImageURL}" alt="${entry.eventHostName}" class="profile-img">
<div class="user-info">
<p><strong>${entry.eventHostName}</strong></p>
<p>${new Date(entry.createdDate).toLocaleString()}</p>
</div>
<p>${entry.name || "No description available"}</p>
</div>
<div class="footer">
<p>${entry.location.formattedAddr}</p>
<p>${entry.lat}, ${entry.lng}</p>
<div class="interactive-icons">
<i class="fas fa-comment"></i>
<i class="fas fa-thumbs-up"></i>
<i class="fas fa-share"></i>
</div>
</div>
</div>
`;
marker.bindPopup(popupContent);
}
});
})
.catch((error) => console.error("Error loading markers:", error));
}
loadMarkers(); // Call function to load markers
// Function to parse the creator data from the URL
function parseCreatorData() {
const fullUrl = window.location.href;
const creatorParamStart =
fullUrl.indexOf("?creator=") + "?creator=".length;
let creatorJson = fullUrl.substring(creatorParamStart);
creatorJson = creatorJson.replace(/&(?=[^&]*$)/, "%26");
creatorJson = decodeURIComponent(creatorJson);
try {
return JSON.parse(creatorJson);
} catch (error) {
console.error("Error parsing JSON from URL:", error);
return null; // Return null or a default object
}
}
const creatorData = parseCreatorData();
// Function to create an SVG icon with the user's initials
function createUserIcon(firstName, lastName) {
const initials = `${firstName.charAt(0)}${lastName.charAt(0)}`;
const svgIcon = encodeURI(
`data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50'><circle cx='25' cy='25' r='20' fill='#5679C0' /><text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' fill='white' font-size='16' font-family='Arial, sans-serif'>${initials}</text></svg>`
).replace("#", "%23");
return L.icon({
iconUrl: svgIcon,
iconSize: [50, 50], // Size of the icon
iconAnchor: [25, 50], // Point of the icon which will correspond to marker's location
popupAnchor: [0, -50], // Point from which the popup should open relative to the iconAnchor
});
}
if (creatorData) {
var userIcon = createUserIcon(
creatorData.firstName,
creatorData.lastName
);
function handleLocationSuccess(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
// Fly to the user's location once
map.flyTo(new L.LatLng(latitude, longitude), 13, {
animate: true,
duration: 5, // Duration in seconds for the flying effect
});
setTimeout(function () {
var marker = L.marker([latitude, longitude], {
icon: userIcon,
}).addTo(map);
marker
.bindPopup(
`<b>${creatorData.firstName} ${creatorData.lastName} are here!</b>`
)
.openPopup();
// Add bouncing effect
bounceMarker(marker);
}, 3500); // Delay the marker appearance slightly after the map flies to the location
}
function handleLocationError(error) {
console.warn(`ERROR(${error.code}): ${error.message}`);
}
// Function to add a bouncing effect to the marker
function bounceMarker(marker) {
var originalLatLng = marker.getLatLng();
var bounceHeight = 0.0001;
var bounceRate = 400; // Bounce every 400ms
setInterval(function () {
var newLatLng = [
originalLatLng.lat + (Math.random() - 0.5) * bounceHeight,
originalLatLng.lng + (Math.random() - 0.5) * bounceHeight,
];
marker.setLatLng(newLatLng);
}, bounceRate);
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
handleLocationSuccess,
handleLocationError
);
} else {
console.log("Geolocation is not supported by this browser.");
}
} else {
console.log("Failed to parse creator data from URL.");
}
});
</script>
<!-- Integrate the script into the HTML code -->
<script>
// Your script code here
const messageInput = document.getElementById("message");
const submitButton = document.getElementById("submit-button");
const chatContainer = document.getElementById("chat-container");
const retryButton = document.getElementById("retry-button");
const undoButton = document.getElementById("undo-button");
const clearButton = document.getElementById("clear-button");
const confirmationModal = document.getElementById("confirmation-modal");
const confirmClearButton = document.getElementById("confirm-clear");
const cancelClearButton = document.getElementById("cancel-clear");
let creatorId;
let creator;
let isVoiceToTextClicked = false; // Flag to track if voice-to-text was clicked
window.onload = function () {
document.getElementById("chat-container").innerHTML = "";
// Extract the entire query string from the URL
const fullUrl = window.location.href;
// Find the start of the 'creator' parameter and extract the substring from there
const creatorParamStart = fullUrl.indexOf("?creator=") + "?creator=".length;
if (creatorParamStart === "?creator=".length - 1) {
// 'creator' parameter is missing in the URL
alert("Please log in to FriendlyForce first and then access the Ask Friendly platform.");
// Redirect the user to the FriendlyForce login page
window.location.href = "https://friendlyforce.live/login";
return;
}
let creatorJson = fullUrl.substring(creatorParamStart);
// Assume that 'creator' is the last parameter in the URL
// Replace any '&' beyond the first occurrence in the JSON string (caused by incorrect URL parsing)
creatorJson = creatorJson.replace(/&(?=\[^&\]\*$)/, "%26");
// Decode the URI component to handle URL encoding
creatorJson = decodeURIComponent(creatorJson);
try {
// Parse the JSON string to an object
creator = JSON.parse(creatorJson);
// Extract the 'firstName' and 'lastName' properties
const firstName = creator.firstName;
const lastName = creator.lastName;
const creatorId = creator.id;
// Step 3: Update the user initials and name in the HTML
const userInitials = document.getElementById("user-initials");
const userName = document.getElementById("user-name");
userInitials.textContent = `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
userName.textContent = `${firstName} ${lastName}`;
// Now you can use firstName and lastName as needed
console.log("First Name:", firstName);
console.log("Last Name:", lastName);
console.log("Creator ID:", creatorId);
// Get the current user token
getCurrentUserToken(creator);
} catch (error) {
console.error("Error parsing JSON from URL:", error);
// Handle the case when the 'creator' parameter is present but has an invalid format
alert("Invalid 'creator' parameter. Please log in to FriendlyForce and access the Ask Friendly platform again.");
// Redirect the user to the FriendlyForce login page
window.location.href = "https://friendlyforce.live/login";
}
};
let lastUserMessage;
let lastAIResponseElement;
let mapIdCounter = 0; // Counter to assign unique IDs for map elements
messageInput.addEventListener("input", () => {
messageInput.style.height = "auto";
messageInput.style.height = `${messageInput.scrollHeight}px`;
});
messageInput.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submitButton.click();
scrollToBottom();
}
});
function updateTokenCount(creator) {
// Make an AJAX request to retrieve the current token count
$.ajax({
url: 'http://127.0.0.1:8000/friendlyforce/chat/api/v1/tokens/?creator=' + encodeURIComponent(JSON.stringify(creator)),
method: 'GET',
success: function (data) {
// Update the token count in the HTML
const tokenCountElement = document.getElementById("token-count");
tokenCountElement.textContent = data.remaining_tokens + " tokens available";
},
error: function (error) {
console.error("Error retrieving token count:", error);
}
});
}
async function typeResponse(element, text) {
console.log("Hello here is the text:", text);
let index = 0;
while (index < text.length && !isStopped) {
if (text.charAt(index) === "\n") {
element.innerHTML += "<br>";
} else {
element.innerHTML += text.charAt(index);
}
index++;
await new Promise((resolve) => setTimeout(resolve, 50));
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// Call the updateTokenCount function after processing the AI response
console.log("updating token count");
updateTokenCount(creator);
}
async function convertTextToSpeech(text) {
if (!isVoiceToTextClicked) {
return; // Do not convert to speech if the voice-to-text button was not clicked
}
try {
const response = await fetch('/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: text,
}),
});
if (response.ok) {
const audioData = await response.arrayBuffer();
const audioContext = new AudioContext();
const audioBuffer = await audioContext.decodeAudioData(audioData);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();
} else {
console.error('Error converting text to speech:', response.statusText);
}
} catch (error) {
console.error('Error converting text to speech:', error);
}
}
function scrollToBottom() {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function scrollToTop() {
chatContainer.scrollTop = 0;
}
let isStopped = false;
submitButton.addEventListener("click", async () => {
let message = messageInput.value.trim();
if (!message) {
return;
}
if (submitButton.classList.contains("bg-red-500")) {
isStopped = true;
}
submitButton.classList.add("bg-red-500", "hover:bg-red-700");
submitButton.classList.remove("bg-blue-500", "hover:bg-blue-700");
document.getElementById("submit-icon").classList.add("hidden");
document.getElementById("pause-icon").classList.remove("hidden");
scrollToTop();
const userMessageDiv = document.createElement("div");
userMessageDiv.className =
"mb-4 user-message p-4 rounded-lg chat-bubble";
userMessageDiv.innerHTML = message;
chatContainer.appendChild(userMessageDiv);
lastUserMessage = message;
scrollToBottom();
const aiResponseDiv = document.createElement("div");
aiResponseDiv.className =
"mb-4 bg-gray-100 p-4 rounded-lg flex flex-col items-start ai-message"; // Change to flex-col
const loaderHTML = `
<div class="loader flex space-x-2">
<div class="animate-pulse w-2 h-2 rounded-full bg-gray-800"></div>
<div class="animate-pulse w-2 h-2 rounded-full bg-gray-800"></div>
<div class="animate-pulse w-2 h-2 rounded-full bg-gray-800"></div>
</div>`;
aiResponseDiv.innerHTML = loaderHTML;
chatContainer.appendChild(aiResponseDiv);
// Function to generate a simple UUID. This is a basic version for demonstration.
function generateUUID() {
let dt = new Date().getTime();
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
const r = (dt + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt / 16);
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
}
);
return uuid;
}
// Check if session ID exists in sessionStorage
let sessionID = sessionStorage.getItem("sessionID");
console.log(sessionID);
// If session ID does not exist, generate a new one and store it in sessionStorage
if (!sessionID) {
sessionID = generateUUID();
sessionStorage.setItem("sessionID", sessionID);
}
// Set an expiration time for the session ID (5 minutes)
const expirationTime = 5 * 60 * 1000; // 5 minutes in milliseconds
setTimeout(() => {
sessionStorage.removeItem("sessionID");
}, expirationTime);
console.log(sessionID);
// Define the API URL
let apiUrl = "/submit-message";
// Check if the message query includes the word 'map'
if (message.toLowerCase().includes("map")) {
apiUrl = "/dummy-message"; // Use the dummy endpoint
}
if (message.toLowerCase().includes("report")) {
apiUrl = "/reportinformation"; // Use the dummy endpoint
}
try {
let response;
if (apiUrl === "/dummy-message") {
// Construct query string
const queryString = new URLSearchParams({
question: message,
}).toString();
const urlWithParams = `${apiUrl}?${queryString}`;
const creatorIdd = creator.id;
// Use GET request for dummy message
response = await fetch(urlWithParams, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-User-ID": creatorIdd, // Include user ID in request headers
},
// Do not use 'params' with fetch; query parameters should be in the URL
});
}
if (apiUrl === "/submit-message") {
let requestBody = {
message: message,
userLocation: null,
userDetails: null,
};
//const locationKeywords = ['where', 'near', 'around', 'location', 'area'];
//if (locationKeywords.some(keyword => message.toLowerCase().includes(keyword))) {
//requestBody.userLocation = userLocationName;
//}
const urlParams = new URLSearchParams(window.location.search);
requestBody.userDetails = urlParams.toString();
const creatorId = creator.id;
response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Session-ID": sessionID,
"X-User-ID": creatorId // Include user ID in request headers
},
body: JSON.stringify(requestBody),
});
}
if (apiUrl === "/reportinformation") {
// Use POST request for the actual API
response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Session-ID": sessionID,
},
body: JSON.stringify({ message: message }),
});
}
if (!response.ok) {
throw new Error(
"Network response was not ok " + response.statusText
);
}
const data = await response.json();
// Clearing the loader and setting up the response
aiResponseDiv.innerHTML = "";
const aiResponseText = document.createElement("div");
aiResponseText.className = "response-text mb-2";
aiResponseDiv.appendChild(aiResponseText);
// Convert AI response to speech
await convertTextToSpeech(data.message);
console.log(data);
// Typing out the AI response
if (!isStopped) {
await typeResponse(aiResponseText, data.message);
}
if (data.map && typeof data.map.latitude !== "undefined" && typeof data.map.longitude !== "undefined") {
// Move the main map to the new coordinates
map.flyTo([data.map.latitude, data.map.longitude], 13);
// Clear existing markers if any
map.eachLayer(function (layer) {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
// Define the icon for the new marker
const customIcon = L.icon({
iconUrl: data.map.iconUrl,
iconSize: [35, 50],
iconAnchor: [17, 42], // Adjust if necessary to position the icon correctly
});
// Create a marker at the new location with a popup
const marker = L.marker([data.map.latitude, data.map.longitude], {
icon: customIcon,
}).addTo(map);
// Log the data object for debugging
console.log(data.map);
const eventHostProfileImageURL = data.map.eventHostProfileImageURL || 'default-image-url.jpg';
const eventHostName = data.map.eventHostName || 'No Host Name';
const createdDate = data.map.createdDate ? new Date(data.map.createdDate).toLocaleString() : 'No Date Available';
const eventType = data.map.eventType || 'No Event Type';
const popupContent = `
<div class="custom-popup">
<div class="header" style="background-color: red; color: white;">
<h3>Severe</h3>
</div>
<div class="body">
<img src="${eventHostProfileImageURL}" alt="${eventHostName}" class="profile-img">
<div class="user-info">
<p><strong>${eventHostName}</strong></p>
<p>${createdDate}</p>
</div>
<p>${eventType}</p>
</div>
<div class="footer">
<p>${data.map.latitude}, ${data.map.longitude}</p>
<div class="interactive-icons">
<i class="fas fa-comment"></i>
<i class="fas fa-thumbs-up"></i>
<i class="fas fa-share"></i>
</div>
</div>
</div>
`;
marker.bindPopup(popupContent).openPopup();
// Create a small map container and append it below the AI response text
const smallMapContainer = document.createElement("div");
smallMapContainer.id = `small-map-${mapIdCounter}`; // Assign a unique ID
smallMapContainer.style.width = "100%";
smallMapContainer.style.height = "200px";
smallMapContainer.style.marginTop = "10px";
aiResponseDiv.appendChild(smallMapContainer);
// Create a new map instance for the small map
const smallMap = L.map(smallMapContainer.id).setView([data.map.latitude, data.map.longitude], 13);
// Add the tile layer to the small map
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap contributors",
}).addTo(smallMap);
// Add the marker to the small map with the same popup
L.marker([data.map.latitude, data.map.longitude], {
icon: customIcon,
}).addTo(smallMap).bindPopup(popupContent).openPopup();
mapIdCounter++; // Increment the counter for the next map
}
else {
console.log("No map data available for this response.");
}
} catch (error) {
console.error("Error:", error);
aiResponseDiv.innerHTML = error;
}
finally {
submitButton.classList.remove("bg-red-500", "hover:bg-red-700");
submitButton.classList.add("bg-blue-500", "hover:bg-blue-700");
document.getElementById("submit-icon").classList.remove("hidden");
document.getElementById("pause-icon").classList.add("hidden");
isStopped = false;
}
messageInput.value = "";
messageInput.style.height = "auto";
chatContainer.scrollTop = chatContainer.scrollHeight;
});
// Handle Retry button click
retryButton.addEventListener("click", async () => {
if (lastUserMessage) {
messageInput.value = lastUserMessage;
try {
const response = await fetch("/submit-message", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message: lastUserMessage }),
});
const data = await response.json();
const messageText = JSON.stringify(data);
console.log("Hello here is the data:", messageText);
// Replace the existing AI response with the new response
lastAIResponseElement.textContent = "";
await typeResponse(lastAIResponseElement, messageText);
} catch (error) {
console.error("Error:", error);
}
}
});
// Handle Undo button click
undoButton.addEventListener("click", () => {
// Remove the last user message and AI response from the chat container
const lastUserMessage = chatContainer.lastElementChild;
const lastAIResponse =
chatContainer.lastElementChild.previousElementSibling;
if (lastUserMessage && lastAIResponse) {
chatContainer.removeChild(lastUserMessage);
chatContainer.removeChild(lastAIResponse);
lastUserMessage = null;
lastAIResponseElement = null; // Add this line
}
});
// Modify the Clear button event listener to show the confirmation modal
clearButton.addEventListener("click", () => {
confirmationModal.classList.remove("hidden");
});
// Clear the chat and hide the modal when Confirm is clicked
confirmClearButton.addEventListener("click", () => {
chatContainer.innerHTML = "";
lastUserMessage = null;
lastAIResponseElement = null;
confirmationModal.classList.add("hidden");
});
// Hide the modal without clearing the chat when Cancel is clicked
cancelClearButton.addEventListener("click", () => {
confirmationModal.classList.add("hidden");
});
// Voice-to-text button event listener
//const micContainer = document.querySelector('.mic-container');
//const circle = document.querySelector('.circle');
//const listeningIndicator = document.getElementById('listening-indicator');
micContainer.addEventListener('click', async () => {
try {
if (!circle.classList.contains('active')) {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
if (stream) {
circle.classList.add('active');
listeningIndicator.style.display = 'inline';
Speech.init();
isVoiceToTextClicked = true; // Set the flag to true when clicked
}
} else {
circle.classList.remove('active');
listeningIndicator.style.display = 'none';
Speech.finish();
isVoiceToTextClicked = false; // Reset the flag when finished
}
} catch (error) {
console.error('Microphone access denied or error: ', error);
circle.classList.remove('active');
listeningIndicator.style.display = 'none';
Speech.finish();
isVoiceToTextClicked = false;
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment