Inhaltsverzeichnis
keine Gliederung(function() {
/* ============================================================
HELIKON - MINDTOUCH SEARCH ADD-ON v1.0
============================================================
Paste AFTER the main Helikon chat template.
What it does:
- Watches the chat for new user messages
- Searches MindTouch API: Pages + Tags + Files (JSON)
- Shows grouped, collapsible results IN the chat
- Results appear while LLM is thinking
- No modification to existing template needed
============================================================ */
var PF = '"Press Start 2P","Courier New",Courier,monospace';
var Q = String.fromCharCode(34);
var SQ = String.fromCharCode(39);
var T = String.fromCharCode(60);
var TC = String.fromCharCode(60, 47);
var G = String.fromCharCode(62);
var AMP = String.fromCharCode(38);
/* === INJECT CSS === */
var css = ''
+ '.mtSearchBlock { margin:0 0 14px 0; border:2px solid #0077aa; border-radius:8px; background:#f0f8ff; overflow:hidden; font-family:' + PF + '; }'
+ '.mtSearchHeader { background:linear-gradient(135deg, #005580, #0077aa); color:#fff; padding:10px 14px; font-size:12px; line-height:1.6; display:flex; justify-content:space-between; align-items:center; }'
+ '.mtSearchHeader .mtHdrLeft { display:flex; align-items:center; gap:6px; }'
+ '.mtSearchHeader .mtHdrBadge { background:rgba(255,255,255,0.2); padding:2px 8px; border-radius:10px; font-size:11px; }'
+ '.mtSearchGroup { border-top:1px solid #cce0ee; }'
+ '.mtGroupHead { padding:10px 14px; font-size:12px; line-height:1.6; color:#0077aa; cursor:pointer; background:#e8f4fd; transition:background 0.15s; display:flex; justify-content:space-between; align-items:center; user-select:none; }'
+ '.mtGroupHead:hover { background:#d0e8f7; }'
+ '.mtGroupHead .mtGrpLeft { display:flex; align-items:center; gap:6px; }'
+ '.mtGroupHead .mtCount { background:#0077aa; color:#fff; padding:2px 8px; border-radius:10px; font-size:10px; min-width:16px; text-align:center; }'
+ '.mtGroupHead .mtArrow { transition:transform 0.2s; display:inline-block; font-size:10px; }'
+ '.mtGroupHead.open .mtArrow { transform:rotate(90deg); }'
+ '.mtGroupBody { max-height:0; overflow:hidden; transition:max-height 0.3s ease; }'
+ '.mtGroupBody.open { max-height:800px; }'
+ '.mtResultItem { padding:8px 14px; border-top:1px solid #e0ecf4; font-size:11px; line-height:1.8; transition:background 0.1s; }'
+ '.mtResultItem:first-child { border-top:none; }'
+ '.mtResultItem:hover { background:#e8f4fd; }'
+ '.mtResultItem a { color:#0077aa; text-decoration:none; font-size:12px; font-weight:bold; }'
+ '.mtResultItem a:hover { text-decoration:underline; color:#005580; }'
+ '.mtResultItem .mtPath { color:#999; font-size:10px; display:block; margin-top:2px; word-break:break-all; }'
+ '.mtResultItem .mtSnippet { color:#666; font-size:10px; display:block; margin-top:3px; font-family:Courier,monospace; }'
+ '.mtResultItem .mtMeta { color:#999; font-size:10px; }'
+ '.mtResultItem .mtTagBadge { display:inline-block; background:#0077aa; color:#fff; padding:2px 8px; border-radius:10px; font-size:10px; margin:2px 4px 2px 0; }'
+ '.mtNoHits { padding:12px 14px; color:#999; font-size:12px; text-align:center; line-height:1.6; }'
+ '.mtSearchBlock .mtLoading { padding:12px 14px; color:#0077aa; font-size:11px; text-align:center; }'
+ '@keyframes mtPulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }'
+ '.mtLoading { animation: mtPulse 1.2s ease-in-out infinite; }';
var styleEl = document.createElement('style');
styleEl.type = 'text/css';
if (styleEl.styleSheet) { styleEl.styleSheet.cssText = css; }
else { styleEl.appendChild(document.createTextNode(css)); }
document.head.appendChild(styleEl);
/* === CONFIG === */
var MT_BASE = '/@api/deki';
var SEARCH_LIMIT = 10;
var TAG_MIN_WORD = 3;
/* === MT API (JSON) === */
function mtJson(path, callback) {
var url = MT_BASE + path;
var sep = (url.indexOf('?') === -1) ? '?' : AMP;
url += sep + 'dream.out.format=json';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try { callback(null, JSON.parse(xhr.responseText)); }
catch(e) { callback('JSON parse error'); }
} else { callback('HTTP ' + xhr.status); }
};
xhr.onerror = function() { callback('Netzwerkfehler'); };
xhr.send();
}
/* === SEARCH: Content (Pages + Files) === */
function searchContent(query, callback) {
mtJson('/site/search?q=' + encodeURIComponent(query) + AMP + 'limit=' + SEARCH_LIMIT, function(err, data) {
if (err) { callback(err, null); return; }
var result = new Object();
result.pages = new Array();
result.files = new Array();
/* Pages */
if (data.page) {
var pages = data.page;
if (!pages.length && pages['@id']) {
var tmp1 = new Array();
tmp1.push(data.page);
pages = tmp1;
}
var pi;
for (pi = 0; pi < pages.length && pi < SEARCH_LIMIT; pi++) {
var p = new Object();
p.id = pages[pi]['@id'] || '';
p.title = pages[pi].title || '';
p.path = pages[pi].path || '';
p.score = pages[pi]['@score'] || '0';
p.uri = '';
if (pages[pi]['uri.ui']) { p.uri = pages[pi]['uri.ui']; }
result.pages.push(p);
}
}
/* Files */
if (data.file) {
var files = data.file;
if (!files.length && files['@id']) {
var tmp2 = new Array();
tmp2.push(data.file);
files = tmp2;
}
var fi;
for (fi = 0; fi < files.length && fi < SEARCH_LIMIT; fi++) {
var f = new Object();
f.id = files[fi]['@id'] || '';
f.filename = files[fi].filename || '';
f.type = (files[fi].contents && files[fi].contents['@type']) ? files[fi].contents['@type'] : '';
f.size = (files[fi].contents && files[fi].contents['@size']) ? files[fi].contents['@size'] : '';
f.href = (files[fi].contents && files[fi].contents['@href']) ? files[fi].contents['@href'] : '';
f.parentTitle = (files[fi]['page.parent'] && files[fi]['page.parent'].title) ? files[fi]['page.parent'].title : '';
result.files.push(f);
}
}
callback(null, result);
});
}
/* === SEARCH: Tags === */
function searchTags(query, callback) {
mtJson('/site/tags', function(err, data) {
if (err) { callback(err, null); return; }
var matched = new Array();
var q = query.toLowerCase();
var words = q.split(' ');
if (data.tag) {
var tags = data.tag;
if (!tags.length && tags['@value']) {
var tmp3 = new Array();
tmp3.push(data.tag);
tags = tmp3;
}
var ti;
for (ti = 0; ti < tags.length; ti++) {
var tagVal = (tags[ti]['@value'] || '').toLowerCase();
var tagTitle = (tags[ti].title || '').toLowerCase();
var wi;
for (wi = 0; wi < words.length; wi++) {
if (words[wi].length >= TAG_MIN_WORD && (tagVal.indexOf(words[wi]) !== -1 || tagTitle.indexOf(words[wi]) !== -1)) {
var t = new Object();
t.value = tags[ti]['@value'] || '';
t.title = tags[ti].title || '';
t.count = tags[ti]['@count'] || '0';
t.uri = tags[ti].uri || '';
matched.push(t);
break;
}
}
}
}
callback(null, matched);
});
}
/* === RENDER: Grouped results HTML === */
function renderResults(query, pages, tags, files) {
var total = pages.length + tags.length + files.length;
var html = '';
html += T + 'div class=' + Q + 'mtSearchBlock' + Q + G;
/* Header */
html += T + 'div class=' + Q + 'mtSearchHeader' + Q + G;
html += T + 'span class=' + Q + 'mtHdrLeft' + Q + G;
html += String.fromCharCode(128269) + ' MindTouch: ' + Q;
html += escHtml(query) + Q;
html += TC + 'span' + G;
html += T + 'span class=' + Q + 'mtHdrBadge' + Q + G + total + ' Treffer' + TC + 'span' + G;
html += TC + 'div' + G;
if (total === 0) {
html += T + 'div class=' + Q + 'mtNoHits' + Q + G;
html += 'Keine internen Treffer gefunden.';
html += TC + 'div' + G;
html += TC + 'div' + G;
return html;
}
/* Group: Seiten */
if (pages.length > 0) {
html += renderGroup('mtGP' + Date.now(), String.fromCharCode(128196) + ' Seiten', pages.length, function() {
var inner = '';
var i;
for (i = 0; i < pages.length; i++) {
var pg = pages[i];
var url = pg.uri || ('/' + pg.path);
inner += T + 'div class=' + Q + 'mtResultItem' + Q + G;
inner += T + 'a href=' + Q + escAttr(url) + Q + ' target=' + Q + '_blank' + Q + G;
inner += escHtml(pg.title || pg.path);
inner += TC + 'a' + G;
if (pg.path) {
inner += T + 'span class=' + Q + 'mtPath' + Q + G + escHtml(pg.path) + TC + 'span' + G;
}
inner += TC + 'div' + G;
}
return inner;
});
}
/* Group: Tags */
if (tags.length > 0) {
html += renderGroup('mtGT' + Date.now(), String.fromCharCode(127991) + ' Tags', tags.length, function() {
var inner = '';
var i;
for (i = 0; i < tags.length; i++) {
var tg = tags[i];
inner += T + 'div class=' + Q + 'mtResultItem' + Q + G;
inner += T + 'span class=' + Q + 'mtTagBadge' + Q + G + escHtml(tg.title) + TC + 'span' + G;
inner += ' ' + T + 'span class=' + Q + 'mtMeta' + Q + G + tg.count + ' Seiten' + TC + 'span' + G;
if (tg.uri) {
inner += ' ' + T + 'a href=' + Q + escAttr(tg.uri) + Q + ' target=' + Q + '_blank' + Q + ' style=' + Q + 'font-size:10px;font-weight:normal' + Q + G + 'anzeigen' + TC + 'a' + G;
}
inner += TC + 'div' + G;
}
return inner;
});
}
/* Group: Dateien */
if (files.length > 0) {
html += renderGroup('mtGF' + Date.now(), String.fromCharCode(128190) + ' Dateien', files.length, function() {
var inner = '';
var i;
for (i = 0; i < files.length; i++) {
var fl = files[i];
var sizeStr = fl.size ? (Math.round(parseInt(fl.size, 10) / 1024)) + ' KB' : '';
inner += T + 'div class=' + Q + 'mtResultItem' + Q + G;
if (fl.href) {
inner += T + 'a href=' + Q + escAttr(fl.href) + Q + ' target=' + Q + '_blank' + Q + G;
inner += escHtml(fl.filename);
inner += TC + 'a' + G;
} else {
inner += escHtml(fl.filename);
}
inner += ' ' + T + 'span class=' + Q + 'mtMeta' + Q + G;
if (fl.type) inner += fl.type;
if (sizeStr) inner += ' | ' + sizeStr;
inner += TC + 'span' + G;
if (fl.parentTitle) {
inner += T + 'span class=' + Q + 'mtPath' + Q + G + 'auf: ' + escHtml(fl.parentTitle) + TC + 'span' + G;
}
inner += TC + 'div' + G;
}
return inner;
});
}
html += TC + 'div' + G;
return html;
}
/* === RENDER: Single collapsible group === */
function renderGroup(gid, label, count, contentFn) {
var html = '';
html += T + 'div class=' + Q + 'mtSearchGroup' + Q + G;
/* Group header with toggle */
html += T + 'div class=' + Q + 'mtGroupHead' + Q;
html += ' onclick=' + Q;
html += 'var b=document.getElementById(' + SQ + gid + SQ + ');';
html += 'if(b){b.classList.toggle(' + SQ + 'open' + SQ + ');this.classList.toggle(' + SQ + 'open' + SQ + ');}';
html += Q + G;
html += T + 'span class=' + Q + 'mtGrpLeft' + Q + G + label + TC + 'span' + G;
html += T + 'span style=' + Q + 'display:flex;align-items:center;gap:8px' + Q + G;
html += T + 'span class=' + Q + 'mtCount' + Q + G + count + TC + 'span' + G;
html += T + 'span class=' + Q + 'mtArrow' + Q + G + String.fromCharCode(9654) + TC + 'span' + G;
html += TC + 'span' + G;
html += TC + 'div' + G;
/* Group body (collapsed by default) */
html += T + 'div class=' + Q + 'mtGroupBody' + Q + ' id=' + Q + gid + Q + G;
html += contentFn();
html += TC + 'div' + G;
html += TC + 'div' + G;
return html;
}
/* === HELPERS === */
function escHtml(s) {
if (!s) return '';
return s.replace(/&/g, AMP + 'amp;').replace(/g, AMP + 'gt;');
}
function escAttr(s) {
if (!s) return '';
return s.replace(/&/g, AMP + 'amp;').replace(/"/g, AMP + 'quot;').replace(/ 0; ci--) {
if (children[ci].className && children[ci].className.indexOf('typing') !== -1) {
typingEl = children[ci];
break;
}
}
if (typingEl) {
chatLog.insertBefore(placeholder, typingEl);
} else {
chatLog.appendChild(placeholder);
}
chatLog.scrollTop = chatLog.scrollHeight;
/* Parallel search */
var pending = 2;
var contentResult = null;
var tagResult = null;
function onDone() {
pending--;
if (pending > 0) return;
var pages = (contentResult && contentResult.pages) ? contentResult.pages : new Array();
var files = (contentResult && contentResult.files) ? contentResult.files : new Array();
var tags = tagResult || new Array();
var html = renderResults(query, pages, tags, files);
placeholder.outerHTML = html;
chatLog.scrollTop = chatLog.scrollHeight;
}
searchContent(query, function(err, data) {
if (!err) contentResult = data;
onDone();
});
searchTags(query, function(err, data) {
if (!err) tagResult = data;
onDone();
});
}
/* === START === */
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(init, 500);
} else {
document.addEventListener('DOMContentLoaded', function() { setTimeout(init, 500); });
}
})();