From bdaaa83bf63736c7131369ed839cb449e91bae3b Mon Sep 17 00:00:00 2001 From: aaron <> Date: Thu, 30 Apr 2026 23:43:22 +0800 Subject: [PATCH] 1 --- frontend/src/app/globals.css | 95 +++++++++++++++++++++++ frontend/src/lib/markdown.ts | 142 ++++++++++++++++++++++++++++------- 2 files changed, 209 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index bf8408c2..9631949c 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -327,3 +327,98 @@ body::after { .score-bar-gradient-low { background: linear-gradient(90deg, #585860, #3a3a40); } + +/* Markdown rendered inside AI responses */ +.prose h1, +.prose h2, +.prose h3, +.prose h4 { + color: var(--text-primary); + font-weight: 700; + line-height: 1.35; +} + +.prose h1 { + margin: 1rem 0 0.5rem; + font-size: 1.05rem; +} + +.prose h2 { + margin: 0.9rem 0 0.45rem; + font-size: 0.98rem; +} + +.prose h3 { + margin: 0.8rem 0 0.4rem; + font-size: 0.92rem; +} + +.prose h4 { + margin: 0.7rem 0 0.35rem; + font-size: 0.86rem; +} + +.prose p { + color: var(--text-secondary); +} + +.prose code { + border: 1px solid var(--border-subtle); + border-radius: 6px; + background: var(--surface-2); + padding: 0.1rem 0.3rem; + color: var(--accent-cyan); + font-size: 0.9em; +} + +.prose pre { + overflow-x: auto; + border: 1px solid var(--border-subtle); + border-radius: 12px; + background: var(--surface-1); + padding: 0.75rem; +} + +.prose pre code { + border: 0; + background: transparent; + padding: 0; + color: var(--text-secondary); +} + +.markdown-table-wrap { + margin: 0.75rem 0; + max-width: 100%; + overflow-x: auto; + border: 1px solid var(--border-subtle); + border-radius: 12px; +} + +.markdown-table-wrap table { + width: 100%; + min-width: 520px; + border-collapse: collapse; + font-size: 0.78rem; +} + +.markdown-table-wrap th, +.markdown-table-wrap td { + border-bottom: 1px solid var(--border-subtle); + padding: 0.55rem 0.65rem; + text-align: left; + vertical-align: top; +} + +.markdown-table-wrap th { + background: var(--surface-2); + color: var(--text-primary); + font-weight: 700; +} + +.markdown-table-wrap td { + color: var(--text-secondary); +} + +.markdown-table-wrap tr:last-child td { + border-bottom: 0; +} diff --git a/frontend/src/lib/markdown.ts b/frontend/src/lib/markdown.ts index 8dd486fa..ae8a5791 100644 --- a/frontend/src/lib/markdown.ts +++ b/frontend/src/lib/markdown.ts @@ -1,34 +1,120 @@ -export function markdownToHtml(md: string): string { - return md +function escapeHtml(value: string): string { + return value .replace(/&/g, "&") .replace(//g, ">") - .replace(/^## (.+)$/gm, "
") - .replace(/^(?!<[hulo])/gm, "
") - .replace(/(?])$/gm, "
") - .replace(/<\/p>/g, "") - .replace(/
( ( ${lines.map((line) => formatInline(line.trim())).join(")/g, "$1")
- .replace(/(<\/ul>)<\/p>/g, "$1");
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+function formatInline(value: string): string {
+ return escapeHtml(value)
+ .replace(/`([^`]+)`/g, "
$1")
+ .replace(/\*\*([^*]+)\*\*/g, "$1");
+}
+
+function isTableSeparator(line: string): boolean {
+ const cells = line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|");
+ return cells.length > 1 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
+}
+
+function splitTableRow(line: string): string[] {
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
+}
+
+function renderTable(lines: string[]): string {
+ const headers = splitTableRow(lines[0] ?? "");
+ const rows = lines.slice(2).map(splitTableRow);
+ const head = headers.map((cell) => `${formatInline(cell)} `).join("");
+ const body = rows
+ .map((row) => `${row.map((cell) => ` `)
+ .join("");
+ return `${formatInline(cell)} `).join("")}${head} ${body}${items.map((item) => `
`;
+}
+
+function renderParagraph(lines: string[]): string {
+ return `
")}
`);
+ continue;
+ }
+
+ const heading = /^(#{1,4})\s+(.+)$/.exec(trimmed);
+ if (heading) {
+ const level = heading[1].length;
+ blocks.push(`${escapeHtml(codeLines.join("\n"))}
");
- // Wrap consecutive
- html = html.replace(/(
)?)+/g, (match) => {
- return "" + match.replace(/
";
- });
- return html;
-}
\ No newline at end of file
+ return markdownToHtml(text);
+}
/g, "") + "