MCP-Server erweitert um ask_user_choice (Long-Poll), memory_read, memory_write und memory_list Tools. MCP-Config wird jetzt immer generiert (nicht nur bei Remote-Verbindung). InternalEndpoint um PendingChoices + choice-respond Endpoint ergänzt. Frontend differenziert CLI-Modus (choice-respond API) vs. API-Modus. Gesamter :::ASK_USER_CHOICE::: Text-Marker-Mechanismus aus ClaudeCliService, AnthropicService, markdown.js und stream-ui.js entfernt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
145 lines
5.8 KiB
JavaScript
145 lines
5.8 KiB
JavaScript
// ── Markdown Rendering ──
|
|
|
|
marked.setOptions({ breaks: true, gfm: true });
|
|
|
|
function renderMarkdown(text) {
|
|
if (!text) return '';
|
|
var src = text.trim();
|
|
|
|
// 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(/(?<!\$)\$(?!\$)(.+?)(?<!\$)\$(?!\$)/g, function(m, latex) {
|
|
mathBlocks.push({ latex: latex, displayMode: false });
|
|
return '%%MATH' + (mathBlocks.length - 1) + '%%';
|
|
});
|
|
|
|
// 2) Markdown parsen
|
|
var html = marked.parse(src);
|
|
|
|
// 3) Code-Blöcke schützen (damit Emojis/Krea dort nicht greifen)
|
|
var codeBlocks = [];
|
|
html = html.replace(/<pre[\s>][\s\S]*?<\/pre>|<code[\s>][\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 '<span class="review-inline-header"><i class="fa fa-check-circle-o"></i> <strong>' + esc(agentName.trim()) + '</strong></span> ';
|
|
});
|
|
|
|
// 4a2) [Finalizer: AgentName] prefix → Finalizer-Header
|
|
html = html.replace(/\[Finalizer:\s*([^\]]+)\]\s*/g, function(match, agentName) {
|
|
return '<span class="review-inline-header finalizer-inline-header"><i class="fa fa-flag-checkered"></i> <strong>' + esc(agentName.trim()) + '</strong></span> ';
|
|
});
|
|
|
|
// 4a3) [Preparer: AgentName] prefix → Preparer-Header
|
|
html = html.replace(/\[Preparer:\s*([^\]]+)\]\s*/g, function(match, agentName) {
|
|
return '<span class="review-inline-header preparer-inline-header"><i class="fa fa-filter"></i> <strong>' + esc(agentName.trim()) + '</strong></span> ';
|
|
});
|
|
|
|
// 4a4) [Vorbereitet von AgentName]: → Preparer-Forwarded-Header
|
|
html = html.replace(/\[Vorbereitet von\s+([^\]]+)\]:\s*/g, function(match, agentName) {
|
|
return '<div class="preparer-forwarded-header"><i class="fa fa-forward"></i> <strong>Vorbereitet von ' + esc(agentName.trim()) + '</strong></div>';
|
|
});
|
|
|
|
// 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 '<div class="review-inline-feedback"><i class="fa fa-repeat"></i> <strong>Feedback von ' + esc(agentName.trim()) + '</strong> <span class="review-round">Runde ' + esc(round) + '/' + esc(maxRounds) + '</span></div>';
|
|
});
|
|
|
|
// 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 '<span class="krea-ref" data-filename="' + safeFn + '" data-fullurl="' + fullUrl + '" title="Klicken für Vollbild">'
|
|
+ '<img class="krea-thumb" src="' + thumbUrl + '" alt="' + safeFn + '" loading="lazy">'
|
|
+ '</span>';
|
|
});
|
|
|
|
// 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 '<code class="katex-error">' + esc(block.latex) + '</code>';
|
|
}
|
|
});
|
|
|
|
return html;
|
|
}
|
|
|
|
function wrapKreaGalleries(container) {
|
|
// Findet <p>-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 <p> nur krea-refs enthält (ggf. mit Whitespace)
|
|
if (el.tagName === 'P' && el.querySelector('.krea-ref') && el.textContent.trim() === '') {
|
|
// Sammle aufeinanderfolgende krea-only <p>-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++;
|
|
}
|
|
}
|
|
}
|