diff --git a/public/js/chat.js b/public/js/chat.js
index 97d7cc5..28f0df1 100644
--- a/public/js/chat.js
+++ b/public/js/chat.js
@@ -61,6 +61,24 @@
return s.replace(/\u0000(\d+)\u0000/g, (m, i) => parked[Number(i)]);
}
+ // Trennzeile einer Markdown-Tabelle: |---|:---:|---:| (mind. zwei Striche je
+ // Spalte, Doppelpunkte für die Ausrichtung).
+ function isTableSeparator(line) {
+ return /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(line);
+ }
+
+ function tableCells(line) {
+ return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
+ }
+
+ function alignOf(sep) {
+ const links = sep.startsWith(':');
+ const rechts = sep.endsWith(':');
+ if (links && rechts) return 'center';
+ if (rechts) return 'right';
+ return null; // linksbündig ist der Standard, kein style nötig
+ }
+
function renderMarkdown(src) {
const esc = escapeHtml(src == null ? '' : String(src)).replace(/\r\n/g, '\n');
const lines = esc.split('\n');
@@ -109,6 +127,41 @@
html += '
' + items.join('') + '
';
continue;
}
+ // Tabelle (GFM): Kopfzeile + Trennzeile (|---|:--:|), dann Datenzeilen.
+ // Das Modell nutzt Tabellen gern für Anforderung/Erfahrung-Vergleiche;
+ // ohne diesen Zweig landeten sie als Pipe-Wüste in einem Absatz.
+ if (line.indexOf('|') >= 0 && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
+ flushPara();
+ const kopf = tableCells(line);
+ const ausricht = tableCells(lines[i + 1]).map(alignOf);
+ i += 2;
+ const zeilen = [];
+ while (i < lines.length && lines[i].indexOf('|') >= 0 && !/^\s*$/.test(lines[i])) {
+ zeilen.push(tableCells(lines[i]));
+ i++;
+ }
+ const zelle = (tag, inhalt, idx) => {
+ const a = ausricht[idx];
+ return '<' + tag + (a ? ' style="text-align:' + a + '"' : '') + '>' + inlineFmt(inhalt || '') + '' + tag + '>';
+ };
+ let t = '';
+ kopf.forEach((c, idx) => { t += zelle('th', c, idx); });
+ t += '
';
+ for (const z of zeilen) {
+ t += '';
+ // An der Kopfbreite ausrichten: fehlende Zellen leer, überzählige raus —
+ // sonst zerreißt eine krumme Zeile das Tabellenraster.
+ for (let c = 0; c < kopf.length; c++) t += zelle('td', z[c], c);
+ t += '
';
+ }
+ html += t + '
';
+ continue;
+ }
+ if (/^\s*([-*_])\1{2,}\s*$/.test(line)) { // Trennlinie
+ flushPara();
+ html += '
';
+ i++; continue;
+ }
if (/^\s*$/.test(line)) { flushPara(); i++; continue; }
para.push(line.trim());
i++;
diff --git a/views/chat.ejs b/views/chat.ejs
index 3f923fd..f3675b6 100644
--- a/views/chat.ejs
+++ b/views/chat.ejs
@@ -44,6 +44,16 @@
.dark .chat-md pre { background: rgba(0,0,0,0.35); }
.chat-md pre code { background: none; padding: 0; }
.chat-md a { text-decoration: underline; }
+ .chat-md hr { border: 0; border-top: 1px solid rgba(0,0,0,0.12); margin: 0.7rem 0; }
+ .dark .chat-md hr { border-top-color: rgba(255,255,255,0.15); }
+ /* Tabellen (z. B. Anforderung/Erfahrung-Vergleiche). Der Wrapper scrollt,
+ damit eine breite Tabelle nicht die Chat-Blase auseinanderzieht. */
+ .chat-md-table { overflow-x: auto; margin: 0 0 0.5rem 0; }
+ .chat-md table { border-collapse: collapse; width: 100%; font-size: 0.875rem; }
+ .chat-md th, .chat-md td { border: 1px solid rgba(0,0,0,0.12); padding: 0.35rem 0.55rem; text-align: left; vertical-align: top; }
+ .chat-md th { background: rgba(0,0,0,0.04); font-weight: 600; }
+ .dark .chat-md th, .dark .chat-md td { border-color: rgba(255,255,255,0.15); }
+ .dark .chat-md th { background: rgba(255,255,255,0.06); }