// ── Markdown Rendering ── marked.setOptions({ breaks: true, gfm: true }); function renderMarkdown(text) { if (!text) return ''; var src = text.trim(); // 0) :::ASK_USER_CHOICE::: Blöcke erkennen und als Platzhalter speichern (vor Markdown-Parsing) var choiceBlocks = []; src = src.replace(/:::ASK_USER_CHOICE:::\s*\n?([\s\S]*?)\n?\s*:::END_ASK_USER_CHOICE:::/g, function(match, jsonStr) { try { var data = JSON.parse(jsonStr.trim()); choiceBlocks.push(data); return '%%CHOICE' + (choiceBlocks.length - 1) + '%%'; } catch(e) { return match; // JSON-Fehler → als Text belassen } }); // 1) LaTeX-Ausdrücke VOR dem Markdown-Parsing schützen // Reihenfolge: zuerst Block ($$...$$), dann Inline ($...$) var mathBlocks = []; // Block-Math: $$...$$ (auch mehrzeilig) src = src.replace(/\$\$([\s\S]+?)\$\$/g, function(m, latex) { mathBlocks.push({ latex: latex, displayMode: true }); return '%%MATH' + (mathBlocks.length - 1) + '%%'; }); // Inline-Math: $...$ (nicht-gierig, kein $ am Anfang/Ende) src = src.replace(/(?][\s\S]*?<\/pre>|][\s\S]*?<\/code>/gi, function(m) { codeBlocks.push(m); return '%%CODE' + (codeBlocks.length - 1) + '%%'; }); // 4a) [Review: AgentName] prefix → Review-Header html = html.replace(/\[Review:\s*([^\]]+)\]\s*/g, function(match, agentName) { return ' ' + esc(agentName.trim()) + ' '; }); // 4a2) [Finalizer: AgentName] prefix → Finalizer-Header html = html.replace(/\[Finalizer:\s*([^\]]+)\]\s*/g, function(match, agentName) { return ' ' + esc(agentName.trim()) + ' '; }); // 4a3) [Preparer: AgentName] prefix → Preparer-Header html = html.replace(/\[Preparer:\s*([^\]]+)\]\s*/g, function(match, agentName) { return ' ' + esc(agentName.trim()) + ' '; }); // 4a4) [Vorbereitet von AgentName]: → Preparer-Forwarded-Header html = html.replace(/\[Vorbereitet von\s+([^\]]+)\]:\s*/g, function(match, agentName) { return '
Vorbereitet von ' + esc(agentName.trim()) + '
'; }); // 4b) [Review-Feedback von AgentName (Runde X/Y)]: → Review-Feedback-Header html = html.replace(/\[Review-Feedback von\s+([^\(]+)\(Runde\s+(\d+)\/(\d+)\)\]:\s*/g, function(match, agentName, round, maxRounds) { return '
Feedback von ' + esc(agentName.trim()) + ' Runde ' + esc(round) + '/' + esc(maxRounds) + '
'; }); // 4c) [APPROVED], [FORWARD], [QUESTION] und [FEEDBACK] ausblenden html = html.replace(/\[APPROVED\]/g, ''); html = html.replace(/\[FORWARD\]\s*/g, ''); html = html.replace(/\[QUESTION\]\s*/g, ''); html = html.replace(/\[FEEDBACK\]\s*/g, ''); // 4d) [Krea: dateiname.png] → Inline-Thumbnail mit Klick für Vollbild html = html.replace(/\[Krea:\s*([^\]]+)\]/g, function(match, filename) { var fn = filename.trim(); var safeFn = esc(fn); var thumbUrl = '/api/images/file/' + encodeURIComponent(fn) + '/thumb'; var fullUrl = '/api/images/file/' + encodeURIComponent(fn); return '' + '' + safeFn + '' + ''; }); // 5) Unicode-Emojis durch Fluent Emoji 3D ersetzen (außerhalb von Code-Blöcken) html = replaceEmojisWithFluent(html); // 6) Code-Blöcke wiederherstellen html = html.replace(/%%CODE(\d+)%%/g, function(m, idx) { return codeBlocks[parseInt(idx)]; }); // 7) LaTeX-Platzhalter durch KaTeX-gerenderten HTML ersetzen html = html.replace(/%%MATH(\d+)%%/g, function(m, idx) { var block = mathBlocks[parseInt(idx)]; try { return katex.renderToString(block.latex.trim(), { displayMode: block.displayMode, throwOnError: false, output: 'html' }); } catch (e) { // Fallback: Originaltext anzeigen return '' + esc(block.latex) + ''; } }); // 8) Choice-Platzhalter durch interaktive Buttons ersetzen html = html.replace(/%%CHOICE(\d+)%%/g, function(m, idx) { var data = choiceBlocks[parseInt(idx)]; var q = data.question || ''; var choices = data.choices || []; var jsonAttr = esc(JSON.stringify(data)); var out = '
'; out += '
' + esc(q) + '
'; out += '
'; for (var i = 0; i < choices.length; i++) { out += ''; } out += '
'; return out; }); return html; } function wrapKreaGalleries(container) { // Findet

-Tags die NUR krea-ref(s) enthalten und gruppiert aufeinanderfolgende zu einer Gallery var children = Array.from(container.children); var i = 0; while (i < children.length) { var el = children[i]; // Prüfe ob

nur krea-refs enthält (ggf. mit Whitespace) if (el.tagName === 'P' && el.querySelector('.krea-ref') && el.textContent.trim() === '') { // Sammle aufeinanderfolgende krea-only

-Tags var group = [el]; var j = i + 1; while (j < children.length) { var next = children[j]; if (next.tagName === 'P' && next.querySelector('.krea-ref') && next.textContent.trim() === '') { group.push(next); j++; } else { break; } } // Gallery erstellen wenn mindestens 1 Bild if (group.length >= 1) { var gallery = document.createElement('div'); gallery.className = 'krea-gallery'; el.parentNode.insertBefore(gallery, group[0]); for (var k = 0; k < group.length; k++) { var refs = group[k].querySelectorAll('.krea-ref'); for (var r = 0; r < refs.length; r++) { gallery.appendChild(refs[r]); } group[k].remove(); } // children-Array aktualisieren children = Array.from(container.children); // i bleibt gleich, da gallery jetzt an der Position steht i++; } else { i = j; } } else { i++; } } }