Files
portfoliowai/assets/js/assistant-ui.js
T
2026-06-06 09:33:32 +08:00

164 lines
5.5 KiB
JavaScript

const toggleBtn = document.getElementById("chatbot-toggle-btn");
const closeBtn = document.getElementById("chatbot-close-btn");
const chatWindow = document.getElementById("chatbot-window");
const chatForm = document.getElementById("chatbot-input-form");
const inputField = document.getElementById("chatbot-input-field");
const messagesContainer = document.getElementById("chatbot-messages");
const pulseDot = document.querySelector(".pulse-notification-dot");
// Initialize the new JavaScript intent matching engine using injected PHP data
// We add a safety check in case chatbot-engine.js failed to load
if (typeof window.PortfolioChatEngine === 'undefined') {
console.error("Chat engine not loaded");
} else {
const engine = new window.PortfolioChatEngine(window.portfolioData || {});
let isWaiting = false;
// Toggle Chat Window
if (toggleBtn) {
toggleBtn.addEventListener("click", () => {
chatWindow.classList.toggle("hidden");
chatWindow.classList.toggle("active");
if (chatWindow.classList.contains("active")) {
inputField.focus();
if (pulseDot) pulseDot.style.display = "none";
}
});
}
if (closeBtn) {
closeBtn.addEventListener("click", () => {
chatWindow.classList.add("hidden");
chatWindow.classList.remove("active");
});
}
// Handle form submit
if (chatForm) {
chatForm.addEventListener("submit", async (e) => {
e.preventDefault();
if (isWaiting) return;
const message = inputField.value.trim();
if (!message) return;
// Add user message to UI
addMessage(message, "user-msg");
inputField.value = "";
isWaiting = true;
// Show typing indicator
const typingId = showTypingIndicator();
// Simulate network delay for natural feel (600ms - 1200ms)
const delay = Math.floor(Math.random() * 600) + 600;
setTimeout(() => {
// Remove typing indicator
removeTypingIndicator(typingId);
// Get response from our JS engine instead of API
const responseText = engine.match(message);
// Display formatted markdown response
addMessage(responseText, "bot-msg", true);
isWaiting = false;
}, delay);
});
}
// -------------------------------------------------------------
// UI Helpers
// -------------------------------------------------------------
function addMessage(text, typeClass, isMarkdown = false) {
const msgDiv = document.createElement("div");
msgDiv.className = `chat-message ${typeClass}`;
const bubble = document.createElement("div");
bubble.className = "msg-bubble";
if (isMarkdown && typeClass !== "user-msg") {
bubble.innerHTML = parseSimpleMarkdown(text);
} else {
bubble.textContent = text;
}
msgDiv.appendChild(bubble);
messagesContainer.appendChild(msgDiv);
scrollToBottom();
}
function showTypingIndicator() {
const msgDiv = document.createElement("div");
msgDiv.className = "chat-message bot-msg typing-msg";
const id = "typing-" + Date.now();
msgDiv.id = id;
const bubble = document.createElement("div");
bubble.className = "msg-bubble typing-bubble";
bubble.innerHTML = `
<span class="dot-bounce"></span>
<span class="dot-bounce"></span>
<span class="dot-bounce"></span>
`;
msgDiv.appendChild(bubble);
messagesContainer.appendChild(msgDiv);
scrollToBottom();
return id;
}
function removeTypingIndicator(id) {
const el = document.getElementById(id);
if (el) el.remove();
}
function scrollToBottom() {
messagesContainer.scrollTo({
top: messagesContainer.scrollHeight,
behavior: "smooth"
});
}
function parseSimpleMarkdown(text) {
// Bold: **text**
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Italic: *text*
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
// Links: [text](url)
text = text.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" style="color: var(--accent-2); text-decoration: underline;">$1</a>');
// Bullet points: - text or * text
// If there are multiple bullets, wrap in <ul>
if (text.match(/^[-*]\s/m)) {
const lines = text.split('\n');
let inList = false;
let result = [];
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.match(/^[-*]\s(.*)/)) {
if (!inList) { result.push('<ul>'); inList = true; }
result.push(`<li>${line.replace(/^[-*]\s(.*)/, '$1')}</li>`);
} else {
if (inList) { result.push('</ul>'); inList = false; }
if (line.trim() !== '') {
result.push(line + '<br>');
}
}
}
if (inList) result.push('</ul>');
text = result.join('\n');
} else {
// Normal newlines
text = text.replace(/\n/g, '<br>');
}
return text;
}
}