// loader_template.js - Erweiterte Frontend-Logik mit Feedback-System var SESSION_ID = null; // Funktion zum dynamischen Laden von Scripts function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } document.addEventListener("DOMContentLoaded", async function(event) { var BASEURL = "https://chatbot.wu.ac.at/"; var LANGUAGE = "de"; var DEBUG = "false" == "true"; var INITIAL_MESSAGE = { query: "Wer bist du? Und was kannst du? Antworte sehr kurz bitte" }; var ENABLE_PIWIK = true; // Session-Management var SESSION_STATE = "conversation"; // conversation, awaiting_feedback, awaiting_text_feedback // function generateSessionId() { // return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // } try { var search = window.location.search; if (search && search.indexOf("monitoring") >= 0) { ENABLE_PIWIK = false; } } catch (e) { } try { var url_identity_map = JSON.parse(atob("")); var path = window.location.pathname; for (var map in url_identity_map) { if (path.indexOf(map) === 0) { INITIAL_MESSAGE = { question: url_identity_map[map], chatbot_language: LANGUAGE, is_init: true }; break; } }; } catch (e) { } function cl_createtag(tagname, className, parent) { var t = document.createElement(tagname); t.className = className; if (parent) { parent.appendChild(t); } return t; } var targets = document.getElementsByClassName("chatbot__body"); if (targets.length == 1) { var target = targets[0]; var lto_gaia = cl_createtag("div", "lto-gaia", target); lto_gaia.setAttribute("style", "display:none;"); var lto_content = cl_createtag("div", "lto-content", lto_gaia); cl_createtag("div", "lto-suggest", lto_gaia); var language = cl_createtag("input", "lto-language", lto_gaia); language.setAttribute("style", "display:none;"); language.setAttribute("aria-label", "not visible for screenreader (make WAVE happy)"); language.value = LANGUAGE; var textbox = cl_createtag("input", "lto-textbox", lto_gaia); if (LANGUAGE == "de") { textbox.setAttribute("aria-label", "Nachricht an den Chatbot"); } else { textbox.setAttribute("aria-label", "Message for the Chatbot"); } var autocomplete = cl_createtag("div", "lto-autocomplete lto-dropdirection-down", lto_gaia); var typing_indicator = cl_createtag("div", "lto-typing", lto_gaia); typing_indicator.innerHTML = "..."; typing_indicator.setAttribute("style", "display:none;"); var button = cl_createtag("button", "lto-invoker", lto_gaia); if (LANGUAGE == "de") { button.innerText = "Senden"; } else { button.innerText = "Send"; } // Feedback-Button Container var feedback_container = cl_createtag("div", "lto-feedback-container", lto_gaia); feedback_container.setAttribute("style", "display:none;"); var type_delay; var clear_delay; var last_bot_message_id = null; // Für Feedback-Zuordnung var show_typing_indicator = function() { if (type_delay) clearTimeout(type_delay); if (clear_delay) clearTimeout(clear_delay); type_delay = setTimeout(function() { typing_indicator.setAttribute("style", "display:block;"); }, 300); clear_delay = setTimeout(function() { hide_typing_indicator(); }, 15000); }; var hide_typing_indicator = function() { if (type_delay) clearTimeout(type_delay); if (clear_delay) clearTimeout(clear_delay); typing_indicator.setAttribute("style", "display:none;"); }; function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } waitForElm("body[class*='chatbot-open']").then((elm) => { if (ENABLE_PIWIK) { var _paq = window._paq = window._paq || []; _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function() { var u = "https://piwik.wu.ac.at/piwik/"; _paq.push(['setTrackerUrl', u + 'piwik.php']); _paq.push(['setSiteId', '111']); var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; g.type = 'text/javascript'; g.async = true; g.src = u + 'piwik.js'; s.parentNode.insertBefore(g, s); })(); } }); // Funktion zum Hinzufügen einer Nachricht zum Chat-Fenster function addMessage(text, sources, sender, buttons) { autocomplete.innerHTML = ''; var target = "lto-left"; if (sender == "user") { target = "lto-right"; } var now = new Date(); var hours = String(now.getHours()).padStart(2, '0'); var minutes = String(now.getMinutes()).padStart(2, '0'); var timestamp = hours + ":" + minutes; var msg = document.createElement('div'); msg.className = 'lto-container'; msg.setAttribute('tabindex', '0'); var innerDiv = document.createElement('div'); innerDiv.setAttribute('tabindex', '0'); var iconDiv = document.createElement('div'); iconDiv.className = 'lto-icon ' + target; var labelDiv = document.createElement('div'); labelDiv.className = 'lto-label ' + target; if (sender === "bot") { labelDiv.innerHTML = text; if (sources && sources.length > 0) { var sourceContainer = document.createElement("div"); sourceContainer.className = "source-container"; sourceContainer.style.display = "none"; sources.forEach(function(source, index) { var sourceEl = document.createElement("a"); sourceEl.target = '_blank'; sourceEl.style.color = "blue"; sourceEl.href = source.url; sourceEl.innerText = source.title; sourceContainer.appendChild(sourceEl) sourceContainer.appendChild(document.createElement("br")); }); var toggleSourcesButton = document.createElement("button"); toggleSourcesButton.innerText = "Sources"; toggleSourcesButton.className = "toggle-sources-btn"; toggleSourcesButton.addEventListener("click", function(e) { if (sourceContainer.style.display === "none") { sourceContainer.style.display = "block"; } else { sourceContainer.style.display = "none"; } }) labelDiv.appendChild(document.createElement("br")); labelDiv.appendChild(toggleSourcesButton); labelDiv.appendChild(sourceContainer); } } else { labelDiv.innerHTML = text; } // labelDiv.innerHTML += '' + timestamp + ''; innerDiv.appendChild(iconDiv); innerDiv.appendChild(labelDiv); msg.appendChild(innerDiv); // Buttons als echte Buttons unterhalb der Nachricht anzeigen if (buttons && buttons.length > 0) { var buttonContainer = document.createElement('div'); buttonContainer.className = 'lto-button-container'; buttons.forEach(function(btnConfig) { var btn = document.createElement('button'); btn.type = "button"; btn.className = 'lto-feedback-btn btn'; // <--- btn ergänzt btn.innerHTML = btnConfig.emoji ? btnConfig.emoji : btnConfig.text; btn.onclick = function() { handleButtonClick(btnConfig, btn); }; buttonContainer.appendChild(btn); }); msg.appendChild(buttonContainer); } // Separatoren wie gehabt var separator1 = document.createElement('div'); separator1.className = 'lto-separator'; var separator2 = document.createElement('div'); separator2.className = 'lto-separator'; msg.appendChild(separator1); msg.appendChild(separator2); lto_content.appendChild(msg); lto_content.scrollTop = lto_content.scrollHeight; } // Button-Click Handler function handleButtonClick(buttonConfig, buttonElement) { // Button deaktivieren nach Klick buttonElement.disabled = true; buttonElement.style.opacity = '0.6'; if (buttonConfig.type === "feedback") { handleFeedbackClick(buttonConfig.value); } else if (buttonConfig.type === "text_feedback_decision") { handleTextFeedbackDecision(buttonConfig.value); } // Alle Buttons in diesem Container deaktivieren var container = buttonElement.parentElement; var allButtons = container.querySelectorAll('button'); allButtons.forEach(function(btn) { btn.disabled = true; btn.style.opacity = '0.6'; }); } // Feedback-Click Handler async function handleFeedbackClick(feedbackType) { try { const response = await fetch(BASEURL + "feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: SESSION_ID, feedback_type: feedbackType, message_id: last_bot_message_id }) }); if (!response.ok) { throw new Error("Feedback konnte nicht übertragen werden"); } const data = await response.json(); console.log("Feedback response:", data); // Bot-Antwort anzeigen if (data.buttons && data.buttons.length > 0) { addMessage(data.message, null, "bot", data.buttons); } else { addMessage(data.message, null, "bot"); } SESSION_STATE = data.session_state || "conversation"; } catch (error) { console.error("Feedback-Fehler:", error); addMessage("Entschuldigung, es gab ein Problem beim Übertragen des Feedbacks.", null, "bot"); } } // Nach Klick auf "Ja" für Textfeedback: async function handleTextFeedbackDecision(decision) { try { const response = await fetch(BASEURL + "text_feedback_decision", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: SESSION_ID, decision: decision }) }); if (!response.ok) { throw new Error("Entscheidung konnte nicht übertragen werden"); } const data = await response.json(); console.log("Text feedback decision response:", data); addMessage(data.message, null, "bot"); SESSION_STATE = data.session_state || "conversation"; if (decision === "yes") { // Zeige Textfeld für schriftliches Feedback an showTextFeedbackInput(); } } catch (error) { console.error("Text-Feedback-Entscheidung-Fehler:", error); addMessage("Entschuldigung, es gab ein Problem.", null, "bot"); } } function showTextFeedbackInput() { const feedbackInput = document.createElement('textarea'); feedbackInput.className = 'lto-text-feedback-input form-control'; // <--- form-control ergänzt feedbackInput.placeholder = 'Dein Feedback...'; const sendBtn = document.createElement('button'); sendBtn.innerText = 'Absenden'; sendBtn.className = 'btn'; // <--- btn ergänzt sendBtn.onclick = async function() { const feedbackText = feedbackInput.value.trim(); if (!feedbackText) return; await sendTextFeedback(feedbackText); feedbackInput.remove(); sendBtn.remove(); }; lto_content.appendChild(feedbackInput); lto_content.appendChild(sendBtn); feedbackInput.focus(); } async function sendTextFeedback(feedbackText) { await fetch(BASEURL + "text_feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: SESSION_ID, feedback_text: feedbackText }) }); addMessage("Vielen Dank für dein Feedback!", null, "bot"); // Neue Nachfrage nach weiteren Themen: addMessage("Gibt es noch andere Themen, bei denen ich behilflich sein kann?", null, "bot"); } function userMessage() { const question = textbox.value.trim(); if (!question) return; addMessage(question, null, "user"); textbox.value = ""; sendMessage({ query: question, session_id: SESSION_ID, session_state: SESSION_STATE }); } var autocompletes = []; var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var respObj = JSON.parse(this.responseText); autocompletes = respObj[LANGUAGE]; } }; xhttp.open("GET", BASEURL + "autocompletes/autocomplete_" + LANGUAGE + ".json", true); xhttp.send(); function autoMessage() { var question = this.innerText; addMessage(question, null, "user"); textbox.value = ""; sendMessage({ query: question, session_id: SESSION_ID, session_state: SESSION_STATE }); } function fuzzyMatchWithScore(query, target) { if (!query) return { match: true, score: 0 }; if (!target) return { match: false, score: 0 }; let queryIndex = 0; let targetIndex = 0; let score = 0; let consecutiveMatch = 0; while (targetIndex < target.length) { if (queryIndex < query.length && query[queryIndex].toLowerCase() === target[targetIndex].toLowerCase()) { score += 10 + consecutiveMatch * 5; consecutiveMatch++; queryIndex++; } else { consecutiveMatch = 0; } targetIndex++; } const match = queryIndex === query.length; return { match, score: match ? score : 0 }; } function getTopMatches(query, texts, topN = 5) { const results = texts.map(text => { const { score } = fuzzyMatchWithScore(query, text); return { text, score }; }); return results .filter(result => result.score > 0) .sort((a, b) => b.score - a.score) .slice(0, topN); } function checkAutoComplete() { const question = textbox.value.trim(); autocomplete.innerHTML = ''; var matches = getTopMatches(question, autocompletes); matches.forEach(function(el) { var autosuggest = document.createElement('div'); autosuggest.className = 'dropdown-item'; autosuggest.innerText = el.text; autosuggest.addEventListener("click", autoMessage); autocomplete.appendChild(autosuggest); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Erweiterte sendMessage Funktion async function sendMessage(messageData) { show_typing_indicator(); try { const response = await fetch(BASEURL + "query", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(messageData) }); if (!response.ok) { if (response.status = 429) { addMessage("Du hast zu viele Anfragen gesendet. Bitte versuche es in einer Minute erneut.", null, "bot"); addMessage("You have sent too many requests. Please try again in one minute.", null, "bot"); return; } addMessage("Es ist ein Fehler aufgetreten. Bitte versuche es erneut.", null, "bot"); addMessage("An error occured. Please try again.", null, "bot"); return; } const data = await response.json(); console.log("Bot response:", data); if (data.session_id) { SESSION_ID = data.session_id; } else { console.log("No session_id found, generating a new one."); // If generateSessionId is needed, define it or handle accordingly // SESSION_ID = generateSessionId(); } // Bot-Antwort mit möglichen Buttons anzeigen if (data.buttons && data.buttons.length > 0) { addMessage(data.response.message.content, data.sources, "bot", data.buttons); } else { addMessage(data.response.message.content, data.sources, "bot"); } // NEU: Prüfe auf follow_up_message und zeige diese als zweite Bubble an if (data.follow_up_message) { addMessage( data.follow_up_message.content, null, "bot", data.follow_up_message.buttons ); } // Session-State aktualisieren SESSION_STATE = data.session_state || "conversation"; // Message-ID für Feedback speichern if (data.message_id) { last_bot_message_id = data.message_id; } } catch (error) { console.error("Sende-Fehler:", error); addMessage(`Fehler: ${error.message}`, null, "bot"); } finally { hide_typing_indicator(); } } // Event-Listener button.addEventListener("click", userMessage); textbox.addEventListener("keypress", function(event) { checkAutoComplete(); if (event.key === "Enter") { userMessage(); } }); // Initial message senden sendMessage({ ...INITIAL_MESSAGE, session_id: SESSION_ID, session_state: SESSION_STATE }); try { var search = window.location.search; if (search && search.indexOf("openchatbot") >= 0) { toggleChatbot(); changeTitle(); adjustChatbotHeight(); } } catch (e) { } } });