Kildekode: ai_chat_widget.html (API Klient & GUI)
Det grafiske grensesnittet for sluttbrukerne, holdt isolert i Vanilla JS.
Mål og Intensjon
- Isolasjon: 100% fritt for rammeverk for å unngå scriptkonflikter i ClubSite-maler.
- UX Design: Styrer visning av chatboks, animasjoner, scroll til bunn og feilhåndtering.
- API Klient: Sørger for POST-request via fetchet mot FastAPI-serveren og holder rede på historikk med rollene 'user' og 'model'.
Kildekode
<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Chatbot Test - SKI Golfklubb</title>
<style>
/* =========================================
CSS FOR CLUBSITE AI CHATBOT
========================================= */
#clubsite-ai-wrapper {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 999999;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* Den runde knappen */
#ai-chat-button {
width: 65px;
height: 65px;
border-radius: 50%;
background-color: #0D2D8F; /* Ski GK Blå */
background-image:
radial-gradient(circle at 30% 30%, rgba(255,255,255,0.4) 0%, transparent 40%),
radial-gradient(circle, rgba(0,0,0,0.1) 10%, transparent 20%);
background-size: 100% 100%, 10px 10px;
box-shadow: inset -5px -5px 15px rgba(0,0,0,0.3), 0 5px 10px rgba(0,0,0,0.3);
color: white;
font-size: 32px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s ease, background-color 0.2s;
border: 2px solid #0D2D8F;
}
#ai-chat-button:hover {
transform: scale(1.05);
background-color: #09216d; /* Mørkere blå på hover */
}
/* Selve Chat-vinduet */
#ai-chat-window {
display: none; /* Skjult som standard */
position: absolute;
bottom: 80px;
right: 0;
width: 350px;
height: 500px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
overflow: hidden;
opacity: 0;
transform: translateY(20px);
transition: opacity 0.3s ease, transform 0.3s ease;
pointer-events: none;
}
#ai-chat-window.open {
display: flex;
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
/* Header i chatvinduet */
#ai-chat-header {
background: #0D2D8F; /* Ski GK Blå */
color: white;
padding: 15px;
font-size: 16px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
#ai-chat-close {
cursor: pointer;
font-size: 20px;
background: none;
border: none;
color: white;
}
/* Tidslinje for meldinger */
#ai-chat-messages {
flex: 1;
padding: 15px;
overflow-y: auto;
background: #f9f9f9;
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-bubble {
max-width: 80%;
padding: 10px 14px;
border-radius: 15px;
font-size: 14px;
line-height: 1.4;
word-wrap: break-word;
}
.bubble-bot {
background: #EAEAEA; /* Nøytral grå bakgrunn */
color: #000000; /* Ski GK Svart */
align-self: flex-start;
border-bottom-left-radius: 2px;
}
.bubble-user {
background: #0D2D8F; /* Ski GK Blå */
color: white;
align-self: flex-end;
border-bottom-right-radius: 2px;
}
.bubble-typing {
color: #888;
font-style: italic;
font-size: 12px;
background: transparent;
padding: 5px;
}
/* Input område */
#ai-chat-input-area {
display: flex;
padding: 10px;
background: white;
border-top: 1px solid #ddd;
}
#ai-chat-input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 20px;
outline: none;
}
#ai-chat-send {
background: #0D2D8F; /* Ski GK Blå */
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
margin-left: 8px;
cursor: pointer;
font-weight: bold;
}
/* MOBIL TILPASSING */
@media (max-width: 600px) {
#ai-chat-window {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
border-radius: 0;
}
}
</style>
</head>
<body style="background-color: #e0e0e0; padding: 20px; font-family: sans-serif;">
<h1>Visuell Test av AI Chatbot</h1>
<p>Dette er en ren HTML-fil for å teste integrasjonen med FastAPI-backend. Klikk på den grønne golfballen nede i høyre hjørne!</p>
<!-- INKLUDER DETTE I CLASSIC ASP BÅNN (f.eks. footer.inc) -->
<div id="clubsite-ai-wrapper">
<div id="ai-chat-window">
<div id="ai-chat-header">
<div style="display:flex; align-items:center; gap: 10px;">
<img id="ai-robot-avatar" src="" alt="Bot" style="width:24px; height:24px; border-radius:50%; display:none; background:white; padding:2px;">
<span id="ai-tenant-name">Klubbassistent</span>
</div>
<button id="ai-chat-close">✖</button>
</div>
<div id="ai-chat-messages">
<div class="chat-bubble bubble-bot">Hei! Jeg er den nye AI-assistenten. Hva kan jeg hjelpe deg med?</div>
</div>
<div id="ai-chat-input-area">
<input type="text" id="ai-chat-input" placeholder="Skriv spørsmålet ditt her..." onkeypress="handleEnter(event)">
<button id="ai-chat-send" onclick="sendMessage()">➤</button>
</div>
</div>
<div id="ai-chat-button" onclick="toggleChat()">?</div>
</div>
<script>
/* =========================================
JAVASCRIPT FOR CLUBSITE AI CHATBOT
========================================= */
// ASP-utvikler merknad: Disse verdiene skrives ut dynamisk fra VBScript.
// Hentes antakelig fra Setup_AIChatbot tabellen i SQL serveren.
const TENANT_ID = "SKI";
const BOT_NAME = "Ski Golfklubb";
const THEME_COLOR = "#0D2D8F"; // Eksakt Ski GK Blå (#0D2D8F)
// Her er klubbens opplastede logo/icon!
const BOT_LOGO_URL = "SKI/files/images/logo-header.png"; // Forhåndsvisningslenke til lokalt bilde
const WELCOME_MSG = "Hei! Jeg er den nye AI-assistenten til Ski GK. Hva lurer du på?";
// Fast backend URL for utvikling:
const API_URL = "http://127.0.0.1:8000/api/chat";
const chatWindow = document.getElementById("ai-chat-window");
const messagesContainer = document.getElementById("ai-chat-messages");
const inputField = document.getElementById("ai-chat-input");
// Bruk av design-konfigurasjoner hentet fra databasen
document.getElementById("ai-tenant-name").innerText = BOT_NAME;
document.getElementById("ai-chat-header").style.backgroundColor = THEME_COLOR;
document.getElementById("ai-chat-button").style.backgroundColor = THEME_COLOR;
document.getElementById("ai-chat-send").style.backgroundColor = THEME_COLOR;
// Sjekk om klubben har en logo for boten (ellers bruk default styling)
if(BOT_LOGO_URL) {
document.getElementById("ai-chat-button").innerHTML = `<img src="${BOT_LOGO_URL}" style="width:100%; height:100%; object-fit:cover; border-radius:50%;">`;
document.getElementById("ai-robot-avatar").src = BOT_LOGO_URL;
document.getElementById("ai-robot-avatar").style.display = "block";
}
// Sett inn custom velkomstmelding
document.querySelector(".bubble-bot").innerText = WELCOME_MSG;
// Holder på chatbot-historikken slik at den "husker" konteksten
let chatHistory = [];
function toggleChat() {
chatWindow.classList.toggle("open");
if(chatWindow.classList.contains("open")) {
inputField.focus();
}
}
// Lukk-knapp funksjon
document.getElementById("ai-chat-close").addEventListener("click", () => {
chatWindow.classList.remove("open");
});
function handleEnter(e) {
if (e.key === 'Enter') {
sendMessage();
}
}
async function sendMessage() {
const message = inputField.value.trim();
if (!message) return;
// 1. Vis brukerens melding
addMessage(message, "user");
inputField.value = "";
// 2. Vis "skriver..."
const typingInd = addMessage("Golf-boten tenker...", "typing");
try {
// 3. Kall vårt FastAPI-grensesnitt og send med hele samtalehistorikken
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
tenant_id: TENANT_ID,
message: message,
history: chatHistory
})
});
const data = await response.json();
// Fjern "skriver..."
typingInd.remove();
if (data.status === "success") {
addMessage(data.response, "bot");
// Lagre meldingene i historikken slik at konteksten opprettholdes i neste query
chatHistory.push({ role: "user", content: message });
chatHistory.push({ role: "model", content: data.response });
} else {
addMessage("Beklager, fikk en feil fra serveren.", "bot");
}
} catch (error) {
typingInd.remove();
addMessage("Nettverksfeil: Kunne ikke koble til AI-serveren.", "bot");
console.error("Chatbot Feil:", error);
}
}
// Hjelpefunksjon for å tegne opp meldinger i DOM (med Typewriter-effekt for boten)
function addMessage(text, sender) {
const msgDiv = document.createElement("div");
msgDiv.className = `chat-bubble bubble-`;
messagesContainer.appendChild(msgDiv);
// Hvis det er brukeren eller "skriver..."-statusen, post ut teksten med en gang
if (sender === "user" || sender === "typing") {
msgDiv.innerHTML = text.replace(/\n/g, "<br>");
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return msgDiv;
}
// Typewriter-effekt bare for Botens endelige svar ("Streaming-look")
// Erstatter markdown bold (**) med HTML <b> først for at det ikke skal "poppe inn" stygt
let formattedText = text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
let i = 0;
let tempHTML = "";
let htmlTagMode = false; // Holder styr på om vi er inne i en <b> eller <br>
function typeWriter() {
if (i < formattedText.length) {
let char = formattedText.charAt(i);
tempHTML += char;
// Enkel håndtering av linjeskift
if (char === '\n') {
tempHTML += "<br>";
}
if(char === '<') htmlTagMode = true;
if(char === '>') htmlTagMode = false;
msgDiv.innerHTML = tempHTML.replace(/\n/g, "");
messagesContainer.scrollTop = messagesContainer.scrollHeight;
i++;
let speed = htmlTagMode ? 0 : (Math.random() * 15 + 10); // 10-25ms per tegn
setTimeout(typeWriter, speed);
}
}
typeWriter();
return msgDiv;
}
</script>
</body>
</html>