// 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 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";
}
// Disclaimer-Banner Container
var disclaimer_banner_container = cl_createtag("div", "lto-disclaimer-banner", lto_gaia);
disclaimer_banner_container.setAttribute("style", "");
disclaimer_banner_container.style.backgroundColor = "#FFF0C3";
disclaimer_banner_container.style.fontWeight = "500";
disclaimer_banner_container.style.marginLeft = "-21px";
disclaimer_banner_container.style.marginRight = "-21px";
disclaimer_banner_container.style.marginTop = "-21px";
disclaimer_banner_container.style.padding = "21px";
disclaimer_banner_container.style.fontSize = "initial";
disclaimer_banner_container.style.display = "flex";
// SVG-element
var disclaimer_banner_svgWarningIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
disclaimer_banner_svgWarningIcon.setAttribute('viewBox', '0 0 24 24');
disclaimer_banner_svgWarningIcon.setAttribute('fill', 'none');
disclaimer_banner_svgWarningIcon.setAttribute('stroke', 'currentColor');
disclaimer_banner_svgWarningIcon.setAttribute('stroke', '#B4821D');
disclaimer_banner_svgWarningIcon.setAttribute('stroke-width', '1.5');
disclaimer_banner_svgWarningIcon.setAttribute('stroke-linecap', 'round');
disclaimer_banner_svgWarningIcon.setAttribute('stroke-linejoin', 'round');
disclaimer_banner_svgWarningIcon.style.marginRight = '12px';
disclaimer_banner_svgWarningIcon.style.marginLeft = '-12px';
disclaimer_banner_svgWarningIcon.style.width = '160px';
disclaimer_banner_svgWarningIcon.style.height = '50px';
disclaimer_banner_svgWarningIcon.style.scale = '1';
var path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path1.setAttribute('d', 'm21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3');
disclaimer_banner_svgWarningIcon.appendChild(path1);
var path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path2.setAttribute('d', 'M12 9v4');
disclaimer_banner_svgWarningIcon.appendChild(path2);
var path3 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path3.setAttribute('d', 'M12 17h.01');
disclaimer_banner_svgWarningIcon.appendChild(path3);
// Warning Text
var disclaimer_banner_text_container = cl_createtag("div", "disclaimer-text-container", null);
disclaimer_banner_text_container.style.fontSize = "1.35rem";
disclaimer_banner_text_container.style.lineHeight = "2rem";
disclaimer_banner_text_container.innerHTML = 'Dieser Chatbot ist noch ein Prototyp und generiert Antworten mithilfe von KI.' +
' ' +
' Das bedeutet, dass die Antworten manchmal falsch oder unvollständig sein können. Checke daher immer die Quellen!';
disclaimer_banner_container.appendChild(disclaimer_banner_svgWarningIcon);
disclaimer_banner_container.appendChild(disclaimer_banner_text_container);
lto_content.appendChild(disclaimer_banner_container);
// 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("p");
sourceContainer.className = "source-container";
var ulEl = document.createElement("ul");
sourceContainer.appendChild(ulEl)
/* sourceContainer.style.display = "block";*/
sources.forEach(function(source, index) {
var sourceEl = document.createElement("a");
var liEl = document.createElement("li");
sourceEl.target = '_blank';
sourceEl.style.color = "blue";
sourceEl.href = source.url;
sourceEl.innerText = source.title;
liEl.appendChild(sourceEl);
ulEl.appendChild(liEl);
});
var toggleDetails = document.createElement("details");
toggleDetails.className = 'toggle-details';
toggleDetails.style.marginTop = "10px";
var toggleSummary = document.createElement("summary");
toggleSummary.innerText = 'Sources';
toggleSummary.style.display="list-item"
toggleDetails.appendChild(toggleSummary)
toggleDetails.appendChild(sourceContainer)
// toggleDetails.appendChild(toggleSummary);
labelDiv.appendChild(toggleDetails);
/* 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();
}
});
// add initial message
var INITIAL_MESSAGE = "Heyy, ich bin der AI-Chatbot für WU Studierende. 👋
Ich bin noch ein Prototyp.
Aber Fragen rund um IT - Themen, wie z.B.Login - Probleme, kann ich schon beantworten.
Wie lautet deine Frage ?";
addMessage(INITIAL_MESSAGE, null, "bot", null);
try {
var search = window.location.search;
if (search && search.indexOf("openchatbot") >= 0) {
toggleChatbot();
changeTitle();
adjustChatbotHeight();
}
} catch (e) { }
}
});