web: nicer tables

This commit is contained in:
2026-05-02 23:17:52 -07:00
parent 2313e560e8
commit 8051dd2c71
3 changed files with 84 additions and 4 deletions

View File

@@ -50,18 +50,20 @@ export function ChatMessagesPanel({ messages, isLoading, isSending }: Props) {
const iconKind = getToolIconName(toolLogMetadata.toolName ?? message.name); const iconKind = getToolIconName(toolLogMetadata.toolName ?? message.name);
const Icon = iconKind === "search" ? Globe2 : iconKind === "fetch" ? Link2 : Wrench; const Icon = iconKind === "search" ? Globe2 : iconKind === "fetch" ? Link2 : Wrench;
const isFailed = toolLogMetadata.status === "failed"; const isFailed = toolLogMetadata.status === "failed";
const toolSummary = getToolSummary(message, toolLogMetadata);
return ( return (
<div key={message.id} className="flex justify-start"> <div key={message.id} className="flex justify-start">
<div <div
className={cn( className={cn(
"inline-flex max-w-[85%] items-center gap-3 rounded-lg border px-3.5 py-2 text-sm leading-5 shadow-[inset_0_1px_0_hsl(180_100%_88%_/_0.06)]", "inline-flex max-w-[85%] min-w-0 items-center gap-3 overflow-hidden rounded-lg border px-3.5 py-2 text-sm leading-5 shadow-[inset_0_1px_0_hsl(180_100%_88%_/_0.06)]",
isFailed isFailed
? "border-rose-500/40 bg-rose-950/18 text-rose-200" ? "border-rose-500/40 bg-rose-950/18 text-rose-200"
: "border-cyan-400/34 bg-cyan-950/18 text-cyan-100" : "border-cyan-400/34 bg-cyan-950/18 text-cyan-100"
)} )}
title={toolSummary}
> >
<Icon className="h-4 w-4 shrink-0 text-cyan-300" /> <Icon className="h-4 w-4 shrink-0 text-cyan-300" />
<span>{getToolSummary(message, toolLogMetadata)}</span> <span className="min-w-0 flex-1 truncate">{toolSummary}</span>
</div> </div>
</div> </div>
); );

View File

@@ -1,6 +1,6 @@
import { useMemo } from "preact/hooks"; import { useMemo } from "preact/hooks";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { marked } from "marked"; import { marked, Renderer } from "marked";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type MarkdownMode = "default" | "citationTokens"; type MarkdownMode = "default" | "citationTokens";
@@ -21,8 +21,15 @@ function replaceMarkdownLinksWithCitationTokens(markdown: string, resolveCitatio
}); });
} }
const markdownRenderer = new Renderer();
const renderTable = markdownRenderer.table.bind(markdownRenderer);
markdownRenderer.table = (token) => {
return `<div class="md-table-scroll">${renderTable(token)}</div>`;
};
function renderMarkdown(markdown: string) { function renderMarkdown(markdown: string) {
const rawHtml = marked.parse(markdown, { gfm: true, breaks: true }) as string; const rawHtml = marked.parse(markdown, { gfm: true, breaks: true, renderer: markdownRenderer }) as string;
return DOMPurify.sanitize(rawHtml, { ADD_ATTR: ["class", "target", "rel"] }); return DOMPurify.sanitize(rawHtml, { ADD_ATTR: ["class", "target", "rel"] });
} }

View File

@@ -83,6 +83,77 @@ textarea {
word-break: break-word; word-break: break-word;
} }
.md-table-scroll {
max-width: 100%;
margin: 0.35rem 0 1rem;
overflow-x: auto;
overflow-y: hidden;
border: 1px solid hsl(var(--border) / 0.86);
border-radius: 0.625rem;
background: hsl(246 34% 10% / 0.76);
box-shadow: inset 0 1px 0 hsl(258 80% 88% / 0.06);
}
.md-content table {
width: max-content;
min-width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: 0.94em;
line-height: 1.48;
}
.md-table-scroll::-webkit-scrollbar {
height: 0.45rem;
}
.md-table-scroll::-webkit-scrollbar-thumb {
border-radius: 9999px;
background: hsl(263 78% 72% / 0.34);
}
.md-content th,
.md-content td {
padding: 0.48rem 0.7rem;
border-right: 1px solid hsl(var(--border) / 0.72);
border-bottom: 1px solid hsl(var(--border) / 0.7);
text-align: left;
vertical-align: top;
word-break: normal;
}
.md-content th:last-child,
.md-content td:last-child {
border-right: 0;
}
.md-content tr:last-child td {
border-bottom: 0;
}
.md-content th {
background: hsl(251 40% 15% / 0.92);
color: hsl(258 36% 98%);
font-weight: 700;
white-space: nowrap;
}
.md-content td {
color: hsl(258 34% 94% / 0.96);
}
.md-content tbody tr:nth-child(odd) td {
background: hsl(242 32% 10% / 0.58);
}
.md-content tbody tr:nth-child(even) td {
background: hsl(252 36% 13% / 0.46);
}
.md-content tbody tr:hover td {
background: hsl(263 46% 20% / 0.48);
}
.md-content p + p { .md-content p + p {
margin-top: 0.85rem; margin-top: 0.85rem;
} }