MIF_E31230671/KecamatanKanigoro/resources/js/chatbot.js

202 lines
7.6 KiB
JavaScript

document.addEventListener("DOMContentLoaded", () => {
const chatbotToggler = document.querySelector(".chatbot-toggler");
const chatbotContainer = document.querySelector(".chatbot-container");
const closeBtn = document.querySelector(".chatbot-header .close-btn");
const chatbox = document.querySelector(".chatbox");
const chatInput = document.querySelector(".chat-input textarea");
const sendChatBtn = document.querySelector(".chat-input span");
const headerChatbotLink = document.getElementById('header-chatbot-link');
const apiUrl = document.querySelector('meta[name="chatbot-api-url"]').getAttribute('content');
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
let userMessage;
let isTyping = false;
const createChatLi = (message, className) => {
const chatLi = document.createElement("li");
chatLi.classList.add("chat", className);
let chatContent = className === "outgoing"
? `<p>${message}</p>`
: `<span class="material-symbols-rounded">smart_toy</span><div class="message-content"><p>${message}</p></div>`;
chatLi.innerHTML = chatContent;
return chatLi;
};
const saveChatHistory = () => {
localStorage.setItem("riwayat_chat_bkkbn", chatbox.innerHTML);
};
const loadChatHistory = () => {
const savedChat = localStorage.getItem("riwayat_chat_bkkbn");
if (savedChat) {
chatbox.innerHTML = savedChat;
chatbox.scrollTo(0, chatbox.scrollHeight);
}
};
loadChatHistory();
const typeText = (element, htmlString, index, speed, callback) => {
if (index < htmlString.length) {
if (htmlString.charAt(index) === '<') {
let closingTagIndex = htmlString.indexOf('>', index);
if (closingTagIndex !== -1) {
index = closingTagIndex;
}
}
element.innerHTML = htmlString.substring(0, index + 1);
chatbox.scrollTo(0, chatbox.scrollHeight);
setTimeout(() => {
typeText(element, htmlString, index + 1, speed, callback);
}, speed);
} else {
if (callback) callback();
}
};
const handleChat = () => {
if (isTyping) return;
userMessage = chatInput.value.trim();
if (!userMessage) {
alert("Peringatan: Pesan tidak boleh kosong. Silakan ketik pertanyaan Anda terlebih dahulu.");
chatInput.focus();
return;
}
isTyping = true;
chatInput.disabled = true;
chatInput.placeholder = "Tulis pertanyaan Anda di sini...";
const oldSuggestions = document.querySelectorAll('.suggestions-container');
oldSuggestions.forEach(box => box.remove());
chatInput.value = "";
chatInput.style.height = "auto";
chatbox.appendChild(createChatLi(userMessage, "outgoing"));
chatbox.scrollTo(0, chatbox.scrollHeight);
saveChatHistory();
const incomingChatLi = createChatLi('<span class="typing-animation"></span> <i class="text-xs text-slate-400">Asisten sedang mencari jawaban...</i>', "incoming");
chatbox.appendChild(incomingChatLi);
chatbox.scrollTo(0, chatbox.scrollHeight);
const waktuMulai = performance.now();
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken
},
body: JSON.stringify({ message: userMessage })
})
.then(response => response.json())
.then(data => {
const messageWrapper = incomingChatLi.querySelector(".message-content") || incomingChatLi.querySelector("p");
const waktuSelesai = performance.now();
const waktuRespons = ((waktuSelesai - waktuMulai) / 1000).toFixed(2);
console.log(`[TESTING] Waktu Respons TTFT: ${waktuRespons} detik`);
if (data.reply) {
const parsedHTML = marked.parse(data.reply);
messageWrapper.innerHTML = "";
typeText(messageWrapper, parsedHTML, 0, 5, () => {
if (data.suggestions && data.suggestions.length > 0) {
const suggestionDiv = document.createElement("div");
suggestionDiv.classList.add("suggestions-container");
chatbox.appendChild(suggestionDiv);
data.suggestions.forEach((suggestion, index) => {
const cleanSuggestion = suggestion.replace(/^\d+\.\s*/, '').replace(/[*"]/g, '');
if (cleanSuggestion.trim() !== "") {
const btn = document.createElement("button");
btn.classList.add("suggestion-btn", "animate-in");
btn.innerText = cleanSuggestion;
setTimeout(() => {
suggestionDiv.appendChild(btn);
}, index * 150);
btn.addEventListener("click", () => {
chatInput.value = cleanSuggestion;
sendChatBtn.click();
suggestionDiv.remove();
});
}
});
}
setTimeout(() => {
chatbox.scrollTo({
top: incomingChatLi.offsetTop - 20,
behavior: 'smooth'
});
}, 100);
saveChatHistory();
isTyping = false;
chatInput.disabled = false;
chatInput.focus();
});
} else {
messageWrapper.textContent = data.error || "Terjadi kesalahan.";
isTyping = false;
chatInput.disabled = false;
chatInput.placeholder = "Ketik pertanyaan Anda di sini...";
}
})
.catch(error => {
console.error('Error:', error);
const messageWrapper = incomingChatLi.querySelector(".message-content") || incomingChatLi.querySelector("p");
messageWrapper.textContent = "Maaf, tidak dapat terhubung ke asisten. Periksa koneksi Anda.";
messageWrapper.style.color = "#ef4444";
isTyping = false;
chatInput.disabled = false;
chatInput.placeholder = "Ketik pertanyaan Anda di sini...";
});
}
const handleTextareaInput = () => {
chatInput.style.height = "auto";
chatInput.style.height = `${chatInput.scrollHeight}px`;
};
const openChatFromLink = (event) => {
event.preventDefault();
document.body.classList.add("show-chatbot");
};
sendChatBtn.addEventListener("click", handleChat);
chatInput.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleChat();
}
});
chatInput.addEventListener("input", handleTextareaInput);
chatbotToggler.addEventListener("click", () =>
document.body.classList.toggle("show-chatbot"));
closeBtn.addEventListener("click", () =>
document.body.classList.remove("show-chatbot"));
if (headerChatbotLink) {
headerChatbotLink.addEventListener('click', openChatFromLink);
}
});