Gå til innhold

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>