skinthinc / static /js /chatbot.js
fii00's picture
Update static/js/chatbot.js
1402455 verified
// =================================================================================
// RESPONSIVE LAYOUT & SIDEBAR TOGGLE
// =================================================================================
// Fungsi untuk memeriksa ukuran layar dan menyesuaikan tampilan
function periksaUkuranLayar() {
const leftContainer = document.getElementById("leftContainer");
const msgerBox = document.getElementById("msgerBox");
const toggleButton = document.getElementById("toggleButton");
const isSmallScreen = window.innerWidth <= 670;
// Sembunyikan leftContainer jika layar <= 670px
leftContainer.classList.toggle("hidden", isSmallScreen);
// Lebarkan msgerBox jika layar > 670px dan leftContainer disembunyikan
msgerBox.classList.toggle("full-width", window.innerWidth > 670 && leftContainer.classList.contains("hidden"));
// Ubah teks tombol sesuai ukuran layar
toggleButton.textContent = isSmallScreen ? ">" : "<";
}
// Panggil fungsi saat resize atau load halaman
window.addEventListener("resize", periksaUkuranLayar);
window.addEventListener("load", periksaUkuranLayar);
// Event listener untuk tombol toggle sidebar
document.getElementById("toggleButton").addEventListener("click", function() {
const leftContainer = document.getElementById("leftContainer");
const msgerBox = document.getElementById("msgerBox");
leftContainer.classList.toggle("hidden");
if (window.innerWidth > 670) {
msgerBox.classList.toggle("full-width", leftContainer.classList.contains("hidden"));
}
this.textContent = leftContainer.classList.contains("hidden") ? ">" : "<";
});
// =================================================================================
// DOM ELEMENTS & CONSTANTS
// =================================================================================
const msgerBox = document.getElementById("msgerBox");
const clearButton = document.getElementById("clearButton");
const confirmationModal = document.getElementById("confirmationModal");
const confirmDeleteButton = document.getElementById("confirmDelete");
const cancelDeleteButton = document.getElementById("cancelDelete");
const msgerForm = document.querySelector(".msger-inputarea");
const msgerInput = document.getElementById("textInput");
const sendButton = document.getElementById("sendButton");
const msgerChat = document.querySelector(".msger-chat");
const BOT_IMG = "../static/img/headbot.png";
const PERSON_IMG = "../static/img/headuser.png";
const BOT_NAME = "SkinThinc";
const PERSON_NAME = "Anda";
let lastUserMessage = "";
let isSpeaking = false; // Flag untuk melacak apakah text-to-speech aktif
let isVoiceInput = false; // Flag untuk mendeteksi apakah input berasal dari suara
// =================================================================================
// CHAT HISTORY & CLEARING
// =================================================================================
// Tampilkan modal konfirmasi saat tombol "Hapus Percakapan" diklik
clearButton.addEventListener("click", () => {
confirmationModal.style.display = "flex";
confirmationModal.classList.add("fade-in");
});
// Jika pengguna mengonfirmasi untuk menghapus
confirmDeleteButton.addEventListener("click", () => {
msgerChat.innerHTML = ""; // Hapus percakapan di front-end
appendWelcomeMessage(); // Tampilkan pesan selamat datang lagi
// Kirim permintaan ke server untuk menghapus riwayat sesi
fetch("/clear_history", {
method: "POST"
})
.then(response => response.json())
.then(data => {
console.log(data.message); // Log respons server
})
.catch(error => {
console.error("Error saat menghapus riwayat:", error);
});
// Tutup modal
confirmationModal.style.display = "none";
});
// Jika pengguna membatalkan, tutup modal
cancelDeleteButton.addEventListener("click", () => {
confirmationModal.style.display = "none";
});
// Fungsi untuk memuat riwayat percakapan dari server
function loadChatHistory() {
fetch("/load_history")
.then(response => response.json())
.then(history => {
history.forEach(entry => {
const {
sender,
message
} = entry;
const name = sender === "user" ? PERSON_NAME : BOT_NAME;
const img = sender === "user" ? PERSON_IMG : BOT_IMG;
const side = sender === "user" ? "right" : "left";
if (sender === "bot") {
const {
formattedResponse,
plainText
} = formatResponse(message);
appendMessage(BOT_NAME, BOT_IMG, "left", formattedResponse, plainText);
} else {
appendMessage(name, img, side, message, message);
}
});
})
.catch(error => {
console.error("Error saat memuat riwayat percakapan:", error);
});
}
// =================================================================================
// MESSAGE SENDING & HANDLING
// =================================================================================
// Event listener untuk form pengiriman pesan
msgerForm.addEventListener("submit", event => {
event.preventDefault();
sendMessage();
});
// Event listener untuk tombol kirim
sendButton.addEventListener("click", () => {
sendMessage();
});
// Event listener untuk auto-resize textarea
textInput.addEventListener("input", () => {
textInput.style.height = '50px';
textInput.style.height = Math.min(textInput.scrollHeight, 120) + 'px';
});
// Event listener untuk kirim pesan dengan tombol Enter
textInput.addEventListener("keypress", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Fungsi utama untuk mengirim pesan
function sendMessage() {
const msgText = msgerInput.value.trim();
if (!msgText) return;
lastUserMessage = msgText;
appendMessage(PERSON_NAME, PERSON_IMG, "right", msgText, msgText);
msgerInput.value = "";
textInput.style.height = "50px";
botResponse(msgText);
}
// Fungsi untuk mendapatkan dan menampilkan respons dari bot
function botResponse(rawText) {
appendTypingIndicator(); // Tampilkan indikator mengetik
setTimeout(() => {
$.get("/get", {
msg: rawText
}).done(function(data) {
const {
formattedResponse,
plainText
} = formatResponse(data);
removeTypingIndicator();
appendMessage(BOT_NAME, BOT_IMG, "left", formattedResponse, plainText);
// Jika input berasal dari suara, bacakan responsnya secara otomatis
if (isVoiceInput) {
speak(plainText);
isVoiceInput = false; // Reset flag
}
});
}, 2500); // Delay untuk simulasi bot "berpikir"
}
// =================================================================================
// SPEECH RECOGNITION (VOICE-TO-TEXT)
// =================================================================================
document.addEventListener("DOMContentLoaded", function() {
const micButton = document.getElementById("micButton");
const textInput = document.getElementById("textInput");
const micAlert = document.getElementById("micAlert");
const micStatus = document.getElementById("micStatus");
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
console.warn("Browser Anda tidak mendukung fitur Speech Recognition.");
return;
}
const recognition = new SpeechRecognition();
recognition.lang = "id-ID";
recognition.continuous = true;
recognition.interimResults = true;
let isListening = false;
let timeout;
let isMessageSent = false;
const showMicAlert = (status) => {
micStatus.textContent = status;
micAlert.style.display = "block";
};
const hideMicAlert = () => {
micAlert.style.display = "none";
};
micButton.addEventListener("click", () => {
if (isListening) {
recognition.stop();
micButton.style.color = "#c3cfe2";
hideMicAlert();
isListening = false;
isMessageSent = true;
clearTimeout(timeout);
textInput.value = "";
} else {
recognition.start();
micButton.style.color = "#ffff";
showMicAlert("Mikrofon sedang digunakan...");
isListening = true;
isMessageSent = false;
}
});
recognition.onresult = function(event) {
let transcript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
transcript += event.results[i][0].transcript;
}
if (isListening) {
textInput.value = transcript;
}
isVoiceInput = true;
clearTimeout(timeout);
// Deteksi jika pengguna berhenti berbicara selama 3.5 detik
timeout = setTimeout(() => {
if (!isMessageSent) {
recognition.stop();
micButton.style.color = "#c3cfe2";
hideMicAlert();
isListening = false;
isMessageSent = true;
sendMessage();
}
}, 3500);
};
});
// =================================================================================
// MESSAGE APPENDING & UI
// =================================================================================
// Fungsi untuk menambahkan pesan selamat datang
function appendWelcomeMessage() {
const welcomeHTML = `
<div class="msg left-msg">
<div class="msg-img" style="background-image: url(${BOT_IMG})"></div>
<div class="msg-bubble">
<div class="msg-info">
<div class="msg-info-name">${BOT_NAME}</div>
<div class="msg-info-time">${formatDate(new Date())}</div>
</div>
<div class="msg-text">
Halo, saya SkinThinc..! Asisten skincare yang akan membantu untuk menentukan komposisi skincare terbaik Anda.
</div>
<button id="volumeButton" class="volume-btn">
<i class="fa-solid fa-volume-high volume-icon"></i>
</button>
<button id="copyButton" class="copy-btn">
<i class="fas fa-copy copy-icon"></i>
</button>
</div>
</div>`;
msgerChat.insertAdjacentHTML("beforeend", welcomeHTML);
msgerChat.scrollTop += 500;
// Event listener untuk tombol suara pada pesan selamat datang
const volumeButton = document.getElementById("volumeButton");
volumeButton.addEventListener("click", () => {
if (isSpeaking) {
speechSynthesis.cancel();
isSpeaking = false;
volumeButton.innerHTML = '<i class="fa-solid fa-volume-high volume-icon"></i>';
} else {
const messageText = "Halo, saya SkinThinc..! Asisten skincare yang akan membantu untuk menentukan komposisi skincare terbaik Anda.";
speak(messageText);
volumeButton.innerHTML = '<i class="fa-solid fa-circle-stop stop-icon"></i>';
}
});
}
// Fungsi untuk menambahkan pesan baru ke chat window
function appendMessage(name, img, side, text, plainText) {
const msgHTML = `
<div class="msg ${side}-msg">
<div class="msg-img" style="background-image: url(${img})"></div>
<div class="msg-bubble">
<div class="msg-info">
<div class="msg-info-name">${name}</div>
<div class="msg-info-time">${formatDate(new Date())}</div>
</div>
<div class="msg-text">${text}</div>
${side === 'left' ? `
<button class="volume-btn">
<i class="fa-solid ${isVoiceInput ? 'fa-circle-stop stop-icon' : 'fa-volume-high volume-icon'}"></i>
</button>
<button class="copy-btn">
<i class="fas fa-copy copy-icon"></i>
</button>` : ''
}
</div>
</div>`;
msgerChat.insertAdjacentHTML("beforeend", msgHTML);
msgerChat.scrollTop += 500;
// Tambahkan event listener untuk tombol suara jika pesan dari bot
if (side === 'left') {
const volumeButton = msgerChat.lastElementChild.querySelector(".volume-btn");
volumeButton.addEventListener("click", () => {
if (isSpeaking) {
speechSynthesis.cancel();
isSpeaking = false;
volumeButton.innerHTML = '<i class="fa-solid fa-volume-high volume-icon"></i>';
} else {
speak(plainText); // Gunakan plain text untuk dibacakan
isSpeaking = true;
volumeButton.innerHTML = '<i class="fa-solid fa-circle-stop stop-icon"></i>';
}
});
}
}
// Fungsi untuk menampilkan indikator bot sedang mengetik
function appendTypingIndicator() {
const typingHTML = `
<div class="msg left-msg typing" id="typing-indicator">
<div class="msg-img" style="background-image: url(${BOT_IMG})"></div>
<div class="msg-bubble">
<div class="msg-info">
<div class="msg-info-name">${BOT_NAME}</div>
<div class="msg-info-time">${formatDate(new Date())}</div>
</div>
<div class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
</div>`;
msgerChat.insertAdjacentHTML("beforeend", typingHTML);
msgerChat.scrollTop += 500;
}
// Fungsi untuk menghapus indikator mengetik
function removeTypingIndicator() {
const typingIndicator = document.getElementById("typing-indicator");
if (typingIndicator) {
typingIndicator.remove();
}
}
// =================================================================================
// UTILITIES (Formatting, Copy, Speak)
// =================================================================================
// Event delegation untuk tombol copy
document.addEventListener("click", function(event) {
const copyButton = event.target.closest(".copy-btn");
if (copyButton) {
const msgTextElement = copyButton.closest(".msg-bubble").querySelector(".msg-text");
const iconElement = copyButton.querySelector("i");
if (msgTextElement && iconElement) {
copyMessage(msgTextElement.innerText, iconElement);
}
}
});
// Fungsi untuk menyalin teks ke clipboard
function copyMessage(text, iconElement) {
const tempTextarea = document.createElement("textarea");
tempTextarea.value = text;
document.body.appendChild(tempTextarea);
tempTextarea.select();
document.execCommand("copy");
document.body.removeChild(tempTextarea);
iconElement.className = 'fas fa-check check-icon'; // Ubah ikon menjadi centang
setTimeout(() => {
iconElement.className = 'fas fa-copy copy-icon'; // Kembalikan ikon copy setelah 2 detik
}, 2000);
}
// Fungsi untuk memformat respons dari bot (Markdown-like)
function formatResponse(response) {
const lines = response.split('\n').filter(line => line.trim() !== '');
let formattedResponse = '<div data-testid="stMarkdownContainer" class="st-emotion-cache-1sno8jx e1nzilvr4">';
let plainText = '';
let isOrderedList = false;
let isUnorderedList = false;
function applyBoldFormatting(text) {
return text.replace(/\*(.*?)\*/g, '<strong>$1</strong>').replace(/\*/g, '');
}
lines.forEach(line => {
const formattedLine = applyBoldFormatting(line);
if (/^\d+\./.test(line.trim())) { // Ordered list
if (!isOrderedList) {
if (isUnorderedList) {
formattedResponse += '</ul>';
isUnorderedList = false;
}
formattedResponse += '<ol>';
isOrderedList = true;
}
const listItem = formattedLine.replace(/^\d+\.\s*/, '');
formattedResponse += `<li>${listItem}</li>`;
plainText += `${line.trim()}\n`;
} else if (/^\*/.test(line.trim())) { // Unordered list
if (!isUnorderedList) {
if (isOrderedList) {
formattedResponse += '</ol>';
isOrderedList = false;
}
formattedResponse += '<ul>';
isUnorderedList = true;
}
const listItem = formattedLine.replace(/^\*\s*/, '');
formattedResponse += `<li>${listItem}</li>`;
plainText += `${line.trim()}\n`;
} else { // Paragraph
if (isOrderedList) {
formattedResponse += '</ol>';
isOrderedList = false;
}
if (isUnorderedList) {
formattedResponse += '</ul>';
isUnorderedList = false;
}
formattedResponse += `<p>${formattedLine}</p>`;
plainText += `${line}\n`;
}
});
if (isOrderedList) formattedResponse += '</ol>';
if (isUnorderedList) formattedResponse += '</ul>';
formattedResponse += '</div>';
return {
formattedResponse,
plainText
};
}
// Fungsi text-to-speech
function speak(text) {
text = text.replace(/\*/g, ''); // Hapus semua karakter '*'
const parts = text.match(/[^.:!?]+[.:!?]*/g)?.map(part => part.trim()) || [];
let current = 0;
function speakNext() {
if (current >= parts.length) {
document.querySelectorAll(".volume-btn").forEach(button => {
button.innerHTML = '<i class="fa-solid fa-volume-high volume-icon"></i>';
});
isSpeaking = false;
return;
}
const utterance = new SpeechSynthesisUtterance(parts[current++]);
utterance.lang = 'id-ID';
utterance.onend = speakNext;
speechSynthesis.speak(utterance);
}
isSpeaking = true;
speakNext();
}
// Fungsi untuk format tanggal (HH:MM)
function formatDate(date) {
const h = "0" + date.getHours();
const m = "0" + date.getMinutes();
return `${h.slice(-2)}:${m.slice(-2)}`;
}
// =================================================================================
// INITIALIZATION
// =================================================================================
document.addEventListener("DOMContentLoaded", function() {
appendWelcomeMessage();
loadChatHistory();
});