helikon

Von $1

    Inhaltsverzeichnis
    keine Gliederung

    Version seit 19:08, 11 Aug 2026

    zu dieser Version.

    Zurück zu Versionshistorie.

    Zeige aktuelle Version

    /* === HELIKON CHAT v3 === */
    
    #serverBuddy { display: none !important; }
    #buddySpeech { display: none !important; }
    
    #buddyChat {
        display: flex;
        gap: 24px;
        align-items: flex-start;
        width: 100%;
        font-family: 'Press Start 2P', 'Courier New', Courier, monospace;
        font-size: 8px;
        line-height: 2;
        -webkit-font-smoothing: none;
        -moz-osx-font-smoothing: unset;
        font-smooth: never;
        text-rendering: optimizeSpeed;
        -webkit-backface-visibility: hidden;
        backface-visibility: hidden;
        color: #333;
    }
    #buddyChatMain { flex: 1; min-width: 0; }
    #buddyChatSide {
        width: 280px; flex-shrink: 0;
        display: flex; flex-direction: column; align-items: center;
        position: sticky; top: 40px;
    }
    #buddyChatSide img {
        width: 280px; height: 280px;
        image-rendering: pixelated; image-rendering: crisp-edges;
        display: block; border: none; cursor: pointer;
        transition: transform 0.2s ease;
    }
    #buddyChatSide img:hover { transform: scale(1.03); }
    
    #buddyBubble {
        transform: scale(0); background: #fff; color: #333;
        padding: 12px 18px; border-radius: 14px; border: 2px solid #ccc;
        font-family: 'Press Start 2P', 'Courier New', Courier, monospace;
        font-size: 8px; line-height: 2;
        -webkit-font-smoothing: none; -moz-osx-font-smoothing: unset;
        font-smooth: never; text-rendering: optimizeSpeed;
        max-width: 280px; text-align: center;
        white-space: pre-wrap; word-wrap: break-word;
        opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 12px;
    }
    #buddyBubble.visible { opacity: 1; transform: scale(1); }
    #buddyBubble::after {
        content: ''; position: absolute; bottom: -10px; left: 50%;
        transform: translateX(-50%); width: 0; height: 0;
        border-left: 10px solid transparent; border-right: 10px solid transparent;
        border-top: 10px solid #ccc;
    }
    
    #chatLog {
        min-height: 400px; max-height: 600px; overflow-y: auto;
        padding: 20px; margin-bottom: 16px; border: 2px solid #ccc; background: #f8f8f8;
    }
    .chatMsg {
        margin-bottom: 14px; padding: 12px 16px; border-radius: 8px;
        font-size: 8px; line-height: 2; max-width: 90%;
        word-wrap: break-word; white-space: pre-wrap;
    }
    .chatMsg.user { background: #e8e8e8; color: #555; margin-left: auto; text-align: right; border: 1px solid #ddd; }
    .chatMsg.buddy { background: #fff; color: #333; margin-right: auto; border: 1px solid #ccc; }
    .chatMsg.buddy.claude { border-left: 3px solid #d97706; }
    .chatMsg.system { color: #999; font-size: 8px; text-align: center; margin: 8px auto; max-width: 100%; }
    .chatMsg.typing { color: #999; opacity: 0.6; }
    .chatMsg a { color: #0077aa; text-decoration: underline; }
    .chatMsg a:hover { color: #005580; }
    
    #chatInputWrap { display: flex; gap: 8px; border: 2px solid #ccc; background: #f8f8f8; padding: 10px; }
    #chatInput {
        flex: 1; background: #fff; border: 1px solid #ccc; color: #333;
        font-family: 'Press Start 2P', 'Courier New', Courier, monospace;
        font-size: 8px; line-height: 2;
        -webkit-font-smoothing: none; -moz-osx-font-smoothing: unset;
        font-smooth: never; text-rendering: optimizeSpeed;
        padding: 12px 14px; outline: none;
    }
    #chatInput::placeholder { color: #aaa; }
    #chatInput:focus { border-color: #0077aa; }
    #chatSend {
        background: #fff; color: #0077aa; border: 1px solid #0077aa;
        padding: 12px 24px;
        font-family: 'Press Start 2P', 'Courier New', Courier, monospace;
        font-size: 8px; -webkit-font-smoothing: none; -moz-osx-font-smoothing: unset;
        font-smooth: never; cursor: pointer;
    }
    #chatSend:hover { background: #0077aa; color: #fff; }
    #chatSend:disabled { opacity: 0.3; cursor: default; }
    
    #chatFooter {
        display: flex; justify-content: space-between; align-items: center;
        margin-top: 8px; flex-wrap: wrap; gap: 8px;
    }
    #chatStatus { font-size: 8px; color: #999; }
    .chatBtn {
        background: none; border: 1px solid #ccc; color: #888;
        padding: 6px 14px;
        font-family: 'Press Start 2P', 'Courier New', Courier, monospace;
        font-size: 8px; -webkit-font-smoothing: none; -moz-osx-font-smoothing: unset;
        font-smooth: never; cursor: pointer; border-radius: 4px;
    }
    .chatBtn:hover { background: #f0f0f0; color: #333; border-color: #999; }
    .chatBtn.active { background: #0077aa; color: #fff; border-color: #0077aa; }
    .chatBtn:disabled { opacity: 0.3; cursor: default; }
    #llmSwitch { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
    
    #aiStats {
        display: none; margin-top: 16px; padding: 16px;
        border: 2px solid #0077aa; background: #f0f8ff; border-radius: 8px;
        font-size: 8px; line-height: 2.2; color: #333; width: 100%;
    }
    #aiStats.visible { display: block; }
    .statsTitle { font-size: 8px; color: #0077aa; margin: 0 0 12px 0; padding-bottom: 8px; border-bottom: 1px solid #ccc; }
    .statRow { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px dotted #ddd; }
    .statLabel { color: #666; }
    .statValue { color: #333; text-align: right; }
    .statSection { margin-top: 12px; padding-top: 8px; border-top: 1px solid #ccc; }
    .statSection .statLabel { color: #0077aa; }
    .energyBar { display: inline-block; height: 8px; background: #4caf50; margin-left: 8px; vertical-align: middle; }
    
    #promptViewer {
        display: none; margin-top: 16px; padding: 16px;
        border: 2px solid #d97706; background: #fffbf0; border-radius: 8px;
        font-size: 8px; line-height: 2; color: #333;
        max-height: 300px; overflow-y: auto; width: 100%;
        white-space: pre-wrap; word-wrap: break-word;
    }
    #promptViewer.visible { display: block; }
    .promptTitle { font-size: 8px; color: #d97706; margin: 0 0 12px 0; padding-bottom: 8px; border-bottom: 1px solid #ddd; }
    
    @media (max-width: 768px) {
        #buddyChat { flex-direction: column-reverse; }
        #buddyChatSide { width: 100%; flex-direction: row; justify-content: center; position: static; }
        #buddyChatSide img { width: 120px; height: 120px; }
    }
     
    initialisiere...
     
     
     
     
    (function() {
    
        var fontLink = document.createElement('link');
        fontLink.rel = 'stylesheet';
        fontLink.href = 'https://fonts.googleapis.com/css2?family=Press+Start+2P';
        document.head.appendChild(fontLink);
    
        /* === CONFIG === */
        var MODEL       = 'default';
        var PROMPT_URL  = '/@api/deki/files/404/=system-prompt.txt';
        var SHORT_LEN   = 100;
        var ENERGY_PER_TOKEN = 0.00004;
    
        /* Runtime tag builder — avoids MindTouch XHTML parsing conflicts */
        var LT = String.fromCharCode(60);
        var GT = String.fromCharCode(62);
        function tag(name, cls, content) {
            var o = LT + name;
            if (cls) o += ' class="' + cls + '"';
            o += GT;
            return o + (content || '') + LT + '/' + name + GT;
        }
        function tagS(name, cls, style, content) {
            var o = LT + name;
            if (cls) o += ' class="' + cls + '"';
            if (style) o += ' style="' + style + '"';
            o += GT;
            return o + (content || '') + LT + '/' + name + GT;
        }
    
        /* === DOM === */
        var log       = document.getElementById('chatLog');
        var input     = document.getElementById('chatInput');
        var btn       = document.getElementById('chatSend');
        var status    = document.getElementById('chatStatus');
        var avatar    = document.getElementById('buddyChatImg');
        var bubble    = document.getElementById('buddyBubble');
        var clearBtn  = document.getElementById('chatClear');
        var exportBtn = document.getElementById('chatExport');
        var switchDiv = document.getElementById('llmSwitch');
        var statsDiv  = document.getElementById('aiStats');
        var promptDiv = document.getElementById('promptViewer');
        if (!log || !input || !btn) return;
    
        log.innerHTML = '';
    
        var GIFS = new Object();
        GIFS.idle    = '/@api/deki/files/403/=buddy_idle.gif';
        GIFS.sleep   = '/@api/deki/files/402/=buddy_sleeping.gif';
        GIFS.happy   = '/@api/deki/files/401/=buddy_happy.gif';
        GIFS.error   = '/@api/deki/files/400/=buddy_error.gif';
        GIFS.loading = '/@api/deki/files/399/=buddy_loading.gif';
    
        /* === STATE === */
        var history     = [];
        var busy        = false;
        var sysMsg      = '';
        var staticInfo  = '';
        var bubbleTimer = null;
        var statsVisible  = false;
        var promptVisible = false;
    
        /* === SESSION STATS === */
        var ss = new Object();
        ss.tokIn = 0; ss.tokOut = 0; ss.chIn = 0; ss.chOut = 0;
        ss.reqs = 0; ss.timeMs = 0; ss.lastMs = 0;
        ss.start = Date.now(); ss.model = ''; ss.provider = '';
    
        /* === PROVIDERS === */
        var providers = new Object();
        providers.p1 = { name: 'Local', url: '', key: '', model: 'default', ready: false, type: 'openai' };
        providers.p2 = { name: 'Claude', url: '', key: '', model: 'claude-sonnet-4-20250514', ready: false, type: 'anthropic' };
    
        var activeProvider = 'p1';
        var dualMode = false;
    
        /* === HELPERS === */
        function setGif(name) {
            if (avatar) avatar.src = GIFS[name] || GIFS.idle;
        }
    
        function showBubble(txt, duration) {
            if (!bubble) return;
            if (bubbleTimer) clearTimeout(bubbleTimer);
            bubble.textContent = txt;
            bubble.className = 'visible';
            bubbleTimer = setTimeout(function() { bubble.className = ''; }, duration || 5000);
        }
    
        function addMsg(text, cls) {
            var div = document.createElement('div');
            div.className = 'chatMsg ' + cls;
            div.innerHTML = linkify(text);
            log.appendChild(div);
            log.scrollTop = log.scrollHeight;
            return div;
        }
    
        function linkify(text) {
            var amp = String.fromCharCode(38);
            var escaped = text.replace(/&/g, amp + 'amp;')
                             .replace(new RegExp(LT, 'g'), amp + 'lt;')
                             .replace(new RegExp(GT, 'g'), amp + 'gt;');
            return escaped.replace(/(\/[A-Za-z0-9_\-\/\.\?\=\+\:\#\%]+)/g,
                LT + 'a href="$1"' + GT + '$1' + LT + '/a' + GT);
        }
    
        function setStatus(txt) { if (status) status.textContent = txt || ''; }
        function estimateTokens(text) { return Math.ceil(text.length / 4); }
    
        /* === FETCH HELPERS === */
        function fetchText(url, callback) {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', url, true);
            xhr.onload = function() {
                callback(xhr.status === 200 ? xhr.responseText.replace(/^\s+|\s+$/g, '') : null);
            };
            xhr.onerror = function() { callback(null); };
            xhr.send();
        }
    
        function fetchPageProps(callback) {
            var pid = 0;
            if (typeof Deki !== 'undefined') { if (Deki.PageId) pid = Deki.PageId; }
            if (!pid) { callback({}); return; }
    
            var url = '/@api/deki/pages/' + pid + '/properties';
            var xhr = new XMLHttpRequest();
            xhr.open('GET', url, true);
            xhr.onload = function() {
                var props = {};
                if (xhr.status !== 200) { callback(props); return; }
                try {
                    var parser = new DOMParser();
                    var xml = parser.parseFromString(xhr.responseText, 'text/xml');
                    var nodes = xml.getElementsByTagName('property');
                    var i;
                    for (i = 0; i < nodes.length; i++) {
                        var name = nodes[i].getAttribute('name') || '';
                        var contents = nodes[i].getElementsByTagName('contents');
                        if (contents.length > 0) {
                            var val = (contents[0].textContent || '').replace(/^\s+|\s+$/g, '');
                            if (name.indexOf('buddy.staticinfo') !== -1) { props.staticinfo = val; }
                            else if (name.indexOf('buddy.apiurl2') !== -1) { props.apiurl2 = val; }
                            else if (name.indexOf('buddy.apiurl') !== -1) { props.apiurl = val; }
                            else if (name.indexOf('buddy.apikey2') !== -1) { props.apikey2 = val; }
                            else if (name.indexOf('buddy.apikey') !== -1) { props.apikey = val; }
                            else if (name.indexOf('buddy.model2') !== -1) { props.model2 = val; }
                            else if (name.indexOf('buddy.name1') !== -1) { props.name1 = val; }
                            else if (name.indexOf('buddy.name2') !== -1) { props.name2 = val; }
                        }
                    }
                } catch (e) {}
                callback(props);
            };
            xhr.onerror = function() { callback({}); };
            xhr.send();
        }
    
        /* === CHAT CLEAR === */
        if (clearBtn) {
            clearBtn.addEventListener('click', function() {
                if (busy) return;
                history = [];
                log.innerHTML = '';
                ss.tokIn = 0; ss.tokOut = 0; ss.chIn = 0; ss.chOut = 0;
                ss.reqs = 0; ss.timeMs = 0; ss.lastMs = 0; ss.start = Date.now();
                setGif('happy');
                showBubble('Chat geloescht!', 3000);
                if (statsVisible) updateStats();
                setTimeout(function() { setGif('idle'); }, 2000);
            });
        }
    
        /* === EXPORT === */
        if (exportBtn) {
            exportBtn.addEventListener('click', function() {
                if (history.length === 0) { showBubble('Nichts zum Exportieren!', 3000); return; }
                var text = '=== Helikon Chat Export ===\nDatum: ' + new Date().toLocaleString() + '\n\n';
                var i;
                for (i = 0; i < history.length; i++) {
                    var role = history[i].role === 'user' ? 'Du' : 'Helikon';
                    text += role + ': ' + history[i].content + '\n\n';
                }
                var blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
                var url = URL.createObjectURL(blob);
                var a = document.createElement('a');
                a.href = url;
                a.download = 'helikon-chat-' + Date.now() + '.txt';
                a.click();
                URL.revokeObjectURL(url);
                showBubble('Exportiert!', 3000);
            });
        }
    
        /* === AI STATS PANEL === */
        function sr(label, value) {
            return tag('div', 'statRow', tag('span', 'statLabel', label) + tag('span', 'statValue', value));
        }
    
        function updateStats() {
            if (!statsDiv) return;
            var elapsed = Math.floor((Date.now() - ss.start) / 1000);
            var mins = Math.floor(elapsed / 60);
            var secs = elapsed % 60;
            var totTok = ss.tokIn + ss.tokOut;
            var kwh = totTok * ENERGY_PER_TOKEN;
            var hairdryer = (kwh / 1.5 * 60).toFixed(2);
            var led = (kwh / 0.01).toFixed(2);
            var phone = (kwh / 0.015).toFixed(2);
            var barW = Math.min(totTok / 50, 200);
            var avgMs = ss.reqs > 0 ? Math.round(ss.timeMs / ss.reqs) : 0;
    
            var h = tag('div', 'statsTitle', 'AI / LLM Stats');
            h += sr('Provider', ss.provider || '-');
            h += sr('Model', ss.model || '-');
            h += sr('Anfragen', '' + ss.reqs);
            h += sr('Latenz (letzte)', ss.lastMs + ' ms');
            h += sr('Avg Latenz', avgMs + ' ms');
            h += sr('Session', mins + 'm ' + secs + 's');
            h += tag('div', 'statSection', tag('span', 'statLabel', 'Token Stats'));
            h += sr('Tokens gesendet', '' + ss.tokIn);
            h += sr('Tokens empfangen', '' + ss.tokOut);
            h += sr('Tokens gesamt', totTok + ' ' + tagS('span', 'energyBar', 'width:' + barW + 'px', ''));
            h += tag('div', 'statSection', tag('span', 'statLabel', 'Zeichen'));
            h += sr('Zeichen gesendet', '' + ss.chIn);
            h += sr('Zeichen empfangen', '' + ss.chOut);
            h += tag('div', 'statSection', tag('span', 'statLabel', 'Energie-Vergleich'));
            h += sr('ca. kWh', kwh.toFixed(6));
            h += sr('= Haartrockner', hairdryer + ' Min');
            h += sr('= LED-Lampe', led + ' Std');
            h += sr('= Handy-Ladungen', phone + 'x');
            statsDiv.innerHTML = h;
        }
    
        if (avatar) {
            avatar.addEventListener('click', function() {
                statsVisible = !statsVisible;
                if (statsVisible) { updateStats(); statsDiv.className = 'visible'; showBubble('Nerd-Modus!', 3000); }
                else { statsDiv.className = ''; }
            });
        }
    
        /* === PROMPT VIEWER (trigger: /showprompt) === */
        function showPromptViewer() {
            if (!promptDiv) return;
            promptVisible = !promptVisible;
            if (promptVisible) {
                var full = sysMsg;
                if (staticInfo) full += '\n\n--- STATIC INFO ---\n' + staticInfo;
                var amp = String.fromCharCode(38);
                var escaped = full.replace(/&/g, amp + 'amp;')
                                  .replace(new RegExp(LT, 'g'), amp + 'lt;')
                                  .replace(new RegExp(GT, 'g'), amp + 'gt;');
                promptDiv.innerHTML = tag('div', 'promptTitle', 'System Prompt (nur fuer Admins)') + escaped;
                promptDiv.className = 'visible';
                showBubble('Prompt sichtbar!', 3000);
            } else { promptDiv.className = ''; }
        }
    
        /* === LLM SWITCH UI === */
        function updateSwitchUI() {
            if (!switchDiv) return;
            switchDiv.innerHTML = '';
            function makeBtn(id, label, isActive) {
                var b = document.createElement('button');
                b.className = 'chatBtn' + (isActive ? ' active' : '');
                b.id = id;
                b.textContent = label;
                return b;
            }
            if (providers.p1.ready) {
                var b1 = makeBtn('swP1', providers.p1.name, activeProvider === 'p1' && !dualMode);
                b1.addEventListener('click', function() { activeProvider = 'p1'; dualMode = false; updateSwitchUI(); showBubble(providers.p1.name + ' aktiv', 2000); });
                switchDiv.appendChild(b1);
            }
            if (providers.p2.ready) {
                var b2 = makeBtn('swP2', providers.p2.name, activeProvider === 'p2' && !dualMode);
                b2.addEventListener('click', function() { activeProvider = 'p2'; dualMode = false; updateSwitchUI(); showBubble(providers.p2.name + ' aktiv', 2000); });
                switchDiv.appendChild(b2);
            }
            if (providers.p1.ready && providers.p2.ready) {
                var bd = makeBtn('swDual', 'Dual', dualMode);
                bd.addEventListener('click', function() { dualMode = !dualMode; updateSwitchUI(); showBubble(dualMode ? 'Dual-Modus!' : providers[activeProvider].name + ' aktiv', 2000); });
                switchDiv.appendChild(bd);
            }
        }
    
        /* === SEND TO LLM === */
        function buildFullPrompt() {
            var full = sysMsg || 'Du bist Helikon, ein freundlicher Dokumentations-Assistent.';
            if (staticInfo) full += '\n\n' + staticInfo;
            return full;
        }
    
        function handleReply(reply, provName, provType) {
            history.push({ role: 'assistant', content: reply });
            var cls = 'buddy';
            if (provType === 'anthropic') cls += ' claude';
            if (reply.length <= SHORT_LEN && !dualMode) {
                showBubble(reply, 6000);
            } else {
                if (dualMode) { addMsg('[' + provName + '] ' + reply, cls); }
                else { addMsg(reply, cls); }
                var dot = reply.indexOf('.');
                if (dot > 0 && dot < 80) { showBubble(reply.substring(0, dot + 1), 5000); }
            }
        }
    
        function callProvider(prov, userMsg, callback) {
            var fullPrompt = buildFullPrompt();
            var startTime = Date.now();
            ss.chIn += userMsg.length;
            ss.tokIn += estimateTokens(fullPrompt + userMsg);
            ss.provider = prov.name;
            ss.model = prov.model;
    
            var xhr = new XMLHttpRequest();
            xhr.open('POST', prov.url, true);
            xhr.setRequestHeader('Content-Type', 'application/json');
    
            var body;
            if (prov.type === 'anthropic') {
                xhr.setRequestHeader('x-api-key', prov.key);
                xhr.setRequestHeader('anthropic-version', '2023-06-01');
                xhr.setRequestHeader('anthropic-dangerous-direct-browser-access', 'true');
                var msgs = [];
                var mi;
                for (mi = 0; mi < history.length; mi++) { msgs.push(history[mi]); }
                msgs.push({ role: 'user', content: userMsg });
                body = JSON.stringify({ model: prov.model, system: fullPrompt, messages: msgs, max_tokens: 1000 });
            } else {
                if (prov.key) xhr.setRequestHeader('Authorization', 'Bearer ' + prov.key);
                var messages = [{ role: 'system', content: fullPrompt }];
                var hi;
                for (hi = 0; hi < history.length; hi++) { messages.push(history[hi]); }
                messages.push({ role: 'user', content: userMsg });
                body = JSON.stringify({ model: prov.model, messages: messages, max_tokens: 1000 });
            }
    
            xhr.onload = function() {
                var latency = Date.now() - startTime;
                ss.lastMs = latency; ss.timeMs += latency; ss.reqs++;
                if (xhr.status === 200) {
                    try {
                        var data = JSON.parse(xhr.responseText);
                        var reply = '';
                        if (prov.type === 'anthropic') {
                            if (data.content && data.content.length > 0) { reply = data.content[0].text || ''; }
                        } else { reply = data.choices[0].message.content; }
                        ss.chOut += reply.length;
                        ss.tokOut += estimateTokens(reply);
                        if (statsVisible) updateStats();
                        callback(null, reply);
                    } catch (e) { callback('Parse-Fehler: ' + e.message); }
                } else { callback('API-Fehler ' + xhr.status); }
            };
            xhr.onerror = function() {
                var latency = Date.now() - startTime;
                ss.lastMs = latency; ss.timeMs += latency; ss.reqs++;
                callback('Verbindungsfehler');
            };
            xhr.send(body);
        }
    
        function send() {
            var msg = input.value.replace(/^\s+|\s+$/g, '');
            if (!msg || busy) return;
    
            /* === SPECIAL COMMANDS === */
            if (msg === '/showprompt') { input.value = ''; showPromptViewer(); return; }
            if (msg === '/stats') { input.value = ''; statsVisible = !statsVisible; if (statsVisible) { updateStats(); statsDiv.className = 'visible'; } else { statsDiv.className = ''; } return; }
            if (msg === '/clear') { input.value = ''; clearBtn.click(); return; }
            if (msg === '/help') { input.value = ''; addMsg('/clear - Chat loeschen\n/stats - AI Stats anzeigen\n/showprompt - System Prompt anzeigen\n/help - Diese Hilfe', 'system'); return; }
    
            addMsg(msg, 'user');
            input.value = '';
            history.push({ role: 'user', content: msg });
            busy = true; btn.disabled = true;
            setGif('loading'); setStatus('denkt nach...');
            var typing = addMsg('...', 'buddy typing');
    
            if (dualMode) {
                var done = 0; var total = 0;
                if (providers.p1.ready) total++;
                if (providers.p2.ready) total++;
                log.removeChild(typing);
                function checkDone() {
                    done++;
                    if (done >= total) {
                        setGif('happy'); setTimeout(function() { setGif('idle'); }, 3000);
                        setStatus(''); busy = false; btn.disabled = false; input.focus();
                    }
                }
                if (providers.p1.ready) {
                    callProvider(providers.p1, msg, function(err, reply) {
                        if (err) { addMsg('[' + providers.p1.name + '] ' + err, 'system'); }
                        else { handleReply(reply, providers.p1.name, providers.p1.type); }
                        checkDone();
                    });
                }
                if (providers.p2.ready) {
                    callProvider(providers.p2, msg, function(err, reply) {
                        if (err) { addMsg('[' + providers.p2.name + '] ' + err, 'system'); }
                        else { handleReply(reply, providers.p2.name, providers.p2.type); }
                        checkDone();
                    });
                }
            } else {
                var prov = providers[activeProvider];
                if (!prov || !prov.ready) {
                    log.removeChild(typing);
                    addMsg('Kein Provider aktiv!', 'system');
                    setGif('error'); busy = false; btn.disabled = false; return;
                }
                callProvider(prov, msg, function(err, reply) {
                    log.removeChild(typing);
                    if (err) {
                        addMsg(err, 'system'); showBubble('Fehler!', 3000);
                        setGif('error'); setTimeout(function() { setGif('idle'); }, 4000);
                        setStatus(err);
                    } else {
                        handleReply(reply, prov.name, prov.type);
                        setGif('happy'); setTimeout(function() { setGif('idle'); }, 3000);
                        setStatus('');
                    }
                    busy = false; btn.disabled = false; input.focus();
                });
            }
        }
    
        btn.addEventListener('click', send);
        input.addEventListener('keydown', function(e) { if (e.keyCode === 13) send(); });
    
        /* === INIT === */
        setGif('loading'); setStatus('lade konfiguration...');
    
        fetchPageProps(function(props) {
            if (props.apiurl) { providers.p1.url = props.apiurl; providers.p1.key = props.apikey || ''; providers.p1.ready = true; }
            if (props.name1) providers.p1.name = props.name1;
            if (props.apiurl2) { providers.p2.url = props.apiurl2; providers.p2.key = props.apikey2 || ''; providers.p2.model = props.model2 || 'claude-sonnet-4-20250514'; providers.p2.ready = true; }
            if (props.name2) providers.p2.name = props.name2;
            if (props.staticinfo) staticInfo = props.staticinfo;
    
            var sp = [];
            sp.push(providers.p1.name + ': ' + (providers.p1.ready ? 'OK' : 'FEHLT'));
            sp.push(providers.p2.name + ': ' + (providers.p2.ready ? 'OK' : 'FEHLT'));
            if (providers.p1.ready) { activeProvider = 'p1'; }
            else if (providers.p2.ready) { activeProvider = 'p2'; }
    
            fetchText(PROMPT_URL, function(val) {
                sysMsg = val || 'Du bist Helikon, ein freundlicher Dokumentations-Assistent.';
                setGif('idle'); input.disabled = false; btn.disabled = false;
                updateSwitchUI(); setStatus(sp.join(' | '));
                if (providers.p1.ready || providers.p2.ready) { showBubble('Hallo! Wie kann ich helfen?', 5000); }
                else { showBubble('Kein LLM konfiguriert!', 5000); setGif('error'); setStatus('buddy.apiurl fehlt'); }
            });
        });
    
    })();

     
    Use the language selection below for English Utilise le sélecteur de langue ci-dessous pour français Per l'italiano, utilizzare la selezione della lingua qui sotto Schön sprichst du deutsch :-)