mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
feat(web): render markdown for system and agent event details
Bring back the Marked-based renderer for system prompt and assistant message bodies in the event details panel so headings, lists, inline code, and fenced blocks render as formatted prose instead of a single preformatted block. Tool input/result and command scripts continue to render as fixed-width code since they're JSON/shell. Same URL/HTML sanitization policy as the prior markdown integration: protocol- relative and non-http(s)/mailto links are rewritten to empty hrefs, and raw HTML tokens are dropped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
de9656dfb7
commit
036ee0472f
1 changed files with 46 additions and 1 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { Marked } from "marked";
|
||||
|
||||
import { StageSidebar } from "../components/stage-sidebar";
|
||||
import type { Stage } from "../components/stage-sidebar";
|
||||
|
|
@ -167,6 +168,50 @@ function oneLine(text: string): string {
|
|||
return `${collapsed.slice(0, SUMMARY_MAX_CHARS - 1)}…`;
|
||||
}
|
||||
|
||||
const SAFE_HTTP_URL_RE = /^https?:\/\//i;
|
||||
const SAFE_MAILTO_URL_RE = /^mailto:/i;
|
||||
|
||||
function isSafeMarkdownHref(href: string): boolean {
|
||||
return (
|
||||
SAFE_HTTP_URL_RE.test(href) ||
|
||||
SAFE_MAILTO_URL_RE.test(href) ||
|
||||
href.startsWith("#") ||
|
||||
(href.startsWith("/") && !href.startsWith("//"))
|
||||
);
|
||||
}
|
||||
|
||||
const markedSafe = new Marked();
|
||||
markedSafe.use({
|
||||
async: false,
|
||||
walkTokens(token) {
|
||||
if (
|
||||
(token.type === "link" || token.type === "image") &&
|
||||
typeof token.href === "string" &&
|
||||
!isSafeMarkdownHref(token.href)
|
||||
) {
|
||||
token.href = "";
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
html() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function Markdown({ content }: { content: string }) {
|
||||
const html = useMemo(
|
||||
() => markedSafe.parse(content, { async: false }) as string,
|
||||
[content],
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className="prose prose-sm max-w-none text-fg-3 prose-headings:text-fg-2 prose-strong:text-fg-2 prose-code:rounded prose-code:bg-overlay-strong prose-code:px-1 prose-code:py-0.5 prose-code:text-[0.8em] prose-code:font-mono prose-code:text-fg-3 prose-code:before:content-none prose-code:after:content-none prose-pre:bg-overlay-strong prose-pre:text-fg-3 prose-a:text-teal-500"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const TOOL_NAME_DISPLAY: Record<string, string> = {
|
||||
read_file: "Read",
|
||||
write_file: "Write",
|
||||
|
|
@ -296,7 +341,7 @@ function EventDetails({ turn, runStart }: { turn: TurnType; runStart: string | u
|
|||
|
||||
{(turn.kind === "system" || turn.kind === "assistant") && (
|
||||
<DetailField label="Content">
|
||||
<CodeBlock>{turn.content}</CodeBlock>
|
||||
<Markdown content={turn.content} />
|
||||
</DetailField>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue