refactor: ui.js, chat.js, history.js in 13 modulare Dateien aufteilen
Die monolithische ui.js (1534 Zeilen) zusammen mit chat.js (612) und history.js (626) in fokussierte Module aufgeteilt: Neue Dateien aus ui.js: - ui-helpers.js (126) - DOM-Utilities, $, esc, truncate, scrollDown, Toast - markdown.js (143) - renderMarkdown, wrapKreaGalleries, LaTeX/Code-Schutz - cards.js (90) - buildSessionCard, buildAgentCard - msg-actions.js (208) - Delete, Mark, Share, Reply-Button, Timestamp - tool-ui.js (332) - Tool-Boxes, Diffs, Todo-Liste, Visibility - modals.js (190) - System-Prompt, Command-Output, Krea-Image Modals - stream-ui.js (124) - Stream-Bubble, Review-Streaming - bubbles.js (312) - User/Assistant/Error/Warning/Choice/Review Bubbles Neue Dateien aus chat.js: - reply.js (54) - Reply/Quote-Handling mit pendingReply State - files.js (110) - File-Upload, Workspace-Refs mit pendingFiles/Refs State Neue Datei aus history.js: - archive.js (173) - Archiv-Loading mit loadedArchiveCount State Verbleibend: - chat.js (448) - Stream-Events, sendMessage, handleChoiceClick, Plan-Mode - history.js (464) - History laden, Polling, Message-Rendering Kein Code verändert, nur verschoben. Alle 94 Funktionen verifiziert. Globale Variablen in die Module verschoben, wo sie primär genutzt werden.
This commit is contained in:
1 parent
a82d8cfb05
commit
0dcc05ebf9
15 files changed
+1885
-1873
No files matched your search
@@ -0,0 +1,143 @@
|
||||
// ── 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] und [QUESTION] ausblenden
|
||||
html = html.replace(/\[APPROVED\]/g, '');
|
||||
html = html.replace(/\[FORWARD\]\s*/g, '');
|
||||
html = html.replace(/\[QUESTION\]\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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user