/* === 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; }
}
(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'); }
});
});
})();