/*eslint no-unused-vars: ["error", {"varsIgnorePattern": "setupAiChatbot"}]*/ /* global showdown */ let ws = null; let request_id = Math.floor(Math.random() * 100);; const chatbotElementId = "ai-chatbot-element"; const chatbotInputElementId = "ai-chatbot-conversation-input-text"; const chatbotConversationElementId = "ai-chatbot-conversation-messages"; const chatbotMessageElementId = "ai-chatbot-message-bubble"; const chatbotAiMessageClassName = "chatbot-conversation-message flex quartergap"; const chatbotUserMessageClassName = "user-conversation-message"; const chatbotSystemMessageClassName = "chatbot-conversation-message flex quartergap"; const chatbotButtonElementId = "ai-chatbot-conversation-button"; const chatbotProgressIconId = "ai-chatbot-message-bubble-progress-icon"; let mdConverter = null; let aiMessage = ''; function showAiLoader() { const progressIcon = document.getElementById(chatbotProgressIconId); if (progressIcon) { progressIcon.style.display = 'block'; } const chatbotInputElement = document.getElementById(chatbotInputElementId); if (chatbotInputElement) { chatbotInputElement.value = ''; chatbotInputElement.disabled = true; chatbotInputElement.focus(); } } function hideAiLoader() { const progressIcon = document.getElementById(chatbotProgressIconId); if (progressIcon) { progressIcon.style.display = 'none'; } const chatbotInputElement = document.getElementById(chatbotInputElementId); if (chatbotInputElement) { chatbotInputElement.disabled = false; chatbotInputElement.focus(); } } function isAtBottom(el, threshold = 8) { return el.scrollHeight - el.scrollTop - el.clientHeight < threshold; } function scrollToBottomSmooth(el) { // rešpektuj prefers-reduced-motion const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const behavior = prefersReduced ? 'auto' : 'smooth'; el.scrollTo({ top: el.scrollHeight, behavior }); } // pri pridávaní bubliny: const chat = document.querySelector('.chatbot-conversation-messages'); // …po appendnutí novej správy: if (chat && isAtBottom(chat)) { scrollToBottomSmooth(chat); // animuje, len keď bol užívateľ „pri dne“ } else { // voliteľne zobraz „Jump to latest“ button } function setupAiChatbot() { const buttonSend = document.getElementById(chatbotButtonElementId); const inputText = document.getElementById(chatbotInputElementId); const chatbotElement = document.getElementById(chatbotElementId); if (!buttonSend || !inputText || !chatbotElement) return; mdConverter = ((Object.prototype.hasOwnProperty.call(window, 'showdown')) ? new showdown.Converter() : null); if (!mdConverter) { console.error("Showdown library not found, markdown conversion will not work."); return; } buttonSend.addEventListener('click', () => { if (inputText.value && (inputText.value.trim().length > 0)) { sendChatbotRequest(); } }); inputText.addEventListener('input', () => { connectWebSocket(); }, { once: true }); inputText.addEventListener('keyup', (event) => { if (event.defaultPrevented) { return; // Do nothing if the event was already processed } if (event.key === "Enter") { if (inputText.value && (inputText.value.trim().length > 0)) { sendChatbotRequest(); event.preventDefault(); } } }); chatbotElement.addEventListener('pagehide', () => { if (ws && ((ws.readyState === WebSocket.OPEN) || (ws.readyState === WebSocket.CONNECTING))) { ws.close(); } }); } function appendChatbotMessage(message, msg_type = 'user') { const chatbotConversationElement = document.getElementById(chatbotConversationElementId); if (!chatbotConversationElement) { console.error(`Element with ID ${chatbotConversationElementId} not found.`); return; } let messageBubble = null; let text = ""; if (msg_type === 'ai') { const message_json = JSON.parse(message); text = message_json.response; let request_id_msg = message_json.request_id; messageBubble = document.getElementById(`${chatbotMessageElementId}-${request_id_msg}`); if (!messageBubble) { messageBubble = document.createElement('div'); messageBubble.id = `${chatbotMessageElementId}-${request_id_msg}`; messageBubble.className = chatbotAiMessageClassName; let messageBubbleP = document.createElement('p'); messageBubble.appendChild(messageBubbleP); chatbotConversationElement.appendChild(messageBubble); } } else { text = message; messageBubble = document.createElement('div'); } if (!messageBubble) { console.error(`Element with ID ${messageBubble} not found.`); return; } if (msg_type === 'user') { messageBubble.className = chatbotUserMessageClassName; let messageBubbleP = document.createElement('p'); messageBubble.appendChild(messageBubbleP); messageBubbleP.textContent = text; chatbotConversationElement.appendChild(messageBubble); } else if (msg_type === 'ai') { let messageBubbleP = messageBubble.querySelector('p'); if (!messageBubbleP) { messageBubbleP = document.createElement('p'); messageBubble.appendChild(messageBubbleP); } aiMessage += text; messageBubbleP.innerHTML = `Boost: ${mdConverter.makeHtml(aiMessage)}`; } else { messageBubble.className = chatbotSystemMessageClassName; let messageBubbleP = document.createElement('p'); messageBubble.appendChild(messageBubbleP); messageBubbleP.textContent = text; chatbotConversationElement.appendChild(messageBubble); } scrollToBottomSmooth(chatbotConversationElement); } // Function to create and manage the WebSocket connection function connectWebSocket() { const host = window.location.host; const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; const wsUrl = `${protocol}://${host}/api/chatbot`; if (!ws || ((ws.readyState !== WebSocket.OPEN) && (ws.readyState !== WebSocket.CONNECTING))) { console.log(`Attempting to connect to ${wsUrl}...`); ws = new WebSocket(wsUrl); } else { console.log(`WebSocket to ${wsUrl} is already connected.`); return; } ws.onopen = () => { console.log("WebSocket connection established."); const chatbotButtonElement = document.getElementById(chatbotButtonElementId); if (chatbotButtonElement) { chatbotButtonElement.disabled = false; } }; ws.onmessage = (event) => { console.log("AI message received:", event.data); hideAiLoader(); try { appendChatbotMessage(`${event.data}`, "ai"); } catch (e) { console.error("Failed to parse message:", e); appendChatbotMessage("Error: Invalid message received.", "system"); } }; ws.onclose = () => { const chatbotButtonElement = document.getElementById(chatbotButtonElementId); if (chatbotButtonElement) { chatbotButtonElement.disabled = true; } console.log("WebSocket connection closed."); }; ws.onerror = (error) => { const chatbotButtonElement = document.getElementById(chatbotButtonElementId); if (chatbotButtonElement) { chatbotButtonElement.disabled = true; } console.error("WebSocket error:", error); appendChatbotMessage("Error: Connection with AI failed, please try to reload the page.", "system"); if (ws && ((ws.readyState === WebSocket.OPEN) || (ws.readyState === WebSocket.CONNECTING))) { ws.close(); } }; } //Asynchronously waits for the WebSocket to open function waitForWsOpen(socket) { return new Promise((resolve, reject) => { //If it's already open, resolve immediately if (socket.readyState === WebSocket.OPEN) { resolve(socket); return; } //Set up the event listeners socket.onopen = () => { //Remove the listeners to prevent memory leaks socket.onopen = null; socket.onerror = null; resolve(socket); }; socket.onerror = (error) => { //Remove the listeners socket.onopen = null; socket.onerror = null; reject(new Error(`WebSocket failed to connect: ${error}`)); }; }); } async function sendChatbotRequest() { const chatbotConversationElement = document.getElementById(chatbotConversationElementId); if (!chatbotConversationElement) { console.error(`Element with ID ${chatbotConversationElementId} not found.`); return; } const chatbotButtonElement = document.getElementById(chatbotButtonElementId); if (!chatbotButtonElement) { console.error(`Element with ID ${chatbotButtonElementId} not found.`); return; } const inputText = document.getElementById(chatbotInputElementId); if (!inputText || !inputText.value || (inputText.value.trim().length === 0)) { console.warn("Invalid input text, can't be empty."); inputText.focus(); return; } chatbotButtonElement.disabled = true; if (!ws || (ws.readyState !== WebSocket.OPEN)) { connectWebSocket(); try { console.log('Awaiting WebSocket connection...'); //This is the unblocked wait using async/await await waitForWsOpen(ws); console.log('WebSocket is OPEN! ReadyState:', ws.readyState); } catch (error) { console.error('Failed to initialize connection:', error.message); } } //Construct the message request_id++; const message = JSON.stringify({ input_text: inputText.value, request_id: request_id, }); let error_msg = ""; if (ws && (ws.readyState === WebSocket.OPEN)) { ws.send(message); } else { error_msg = "not ready, please try to repeate your question."; } appendChatbotMessage(`${inputText.value}`, "user"); inputText.value = ''; //Show AI bubble immediately let messageBubble = document.getElementById(`${chatbotMessageElementId}-${request_id}`); if (!messageBubble) { messageBubble = document.createElement('div'); messageBubble.id = `${chatbotMessageElementId}-${request_id}`; messageBubble.className = chatbotAiMessageClassName; let messageBubbleP = document.createElement('p'); let progressIcon = document.getElementById(chatbotProgressIconId); progressIcon?.remove(); progressIcon = document.createElement('div'); progressIcon.id = chatbotProgressIconId; progressIcon.className = "chatbot-progress-icon"; progressIcon.innerHTML = `Progress Icon`; messageBubble.appendChild(progressIcon); messageBubble.appendChild(messageBubbleP); aiMessage = error_msg; messageBubbleP.innerHTML = `Boost: ${aiMessage}`; chatbotConversationElement.appendChild(messageBubble); showAiLoader(); scrollToBottomSmooth(chatbotConversationElement); } chatbotButtonElement.disabled = false; }