refactor(webui): disable React StrictMode and enhance Markdown rendering

This commit is contained in:
Xubin Ren
2026-05-16 08:33:15 +00:00
parent 2144af7cd0
commit cf09a8d691
7 changed files with 123 additions and 34 deletions
+39 -5
View File
@@ -1,3 +1,4 @@
import { Children, isValidElement } from "react";
import ReactMarkdown from "react-markdown";
import rehypeKatex from "rehype-katex";
import remarkGfm from "remark-gfm";
@@ -46,11 +47,19 @@ export default function MarkdownTextRenderer({
components={{
code({ className: cls, children: kids, ...props }) {
const match = /language-(\w+)/.exec(cls || "");
if (!match) {
if (match) {
const code = String(kids).replace(/\n$/, "");
return <CodeBlock language={match[1]} code={code} className="my-3" />;
}
const raw = String(kids).replace(/\n$/, "");
/** Plain fenced ``` blocks (no language) & wide one-liners: block monospace, not inline pill. */
const widePlainBlock = raw.includes("\n") || raw.length > 120;
if (widePlainBlock) {
return (
<code
className={cn(
"rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]",
"block min-w-0 whitespace-pre bg-transparent p-0 font-mono text-[0.8125rem]",
"leading-snug text-inherit",
cls,
)}
{...props}
@@ -59,11 +68,36 @@ export default function MarkdownTextRenderer({
</code>
);
}
const code = String(kids).replace(/\n$/, "");
return <CodeBlock language={match[1]} code={code} className="my-3" />;
return (
<code
className={cn(
"rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]",
cls,
)}
{...props}
>
{kids}
</code>
);
},
pre({ children: markdownChildren }) {
return <>{markdownChildren}</>;
const kids = Children.toArray(markdownChildren);
const lone = kids.length === 1 ? kids[0] : null;
/** Highlighted fences render ``CodeBlock`` (block shell); skip invalid ``<pre><div>``. */
if (lone != null && isValidElement(lone) && lone.type === CodeBlock) {
return <>{markdownChildren}</>;
}
return (
<pre
className={cn(
"my-3 overflow-x-auto rounded-lg border border-border/60 bg-muted/35",
"p-3 font-mono text-[0.8125rem] leading-snug text-foreground/90",
"whitespace-pre [overflow-wrap:normal]",
)}
>
{markdownChildren}
</pre>
);
},
a({ href, children: markdownChildren, ...props }) {
return (
+1
View File
@@ -101,6 +101,7 @@ export function MessageBubble({
const reasoning = message.role === "assistant" ? message.reasoning ?? "" : "";
const reasoningStreaming = !!(message.role === "assistant" && message.reasoningStreaming);
const hasReasoning = reasoning.length > 0 || reasoningStreaming;
const showAssistantActions = message.role === "assistant" && !message.isStreaming && !empty;
const showCopyButton = showAssistantCopyAction && showAssistantActions;
const latencyMs = message.latencyMs;
+3 -5
View File
@@ -114,10 +114,8 @@ export function ThreadShell({
return messageCacheRef.current.get(chatId) ?? historical;
}, [chatId, historical]);
const handleTurnEnd = useCallback(() => {
if (chatId) pendingCanonicalHydrateRef.current.add(chatId);
refreshHistory();
onTurnEnd?.();
}, [chatId, onTurnEnd, refreshHistory]);
}, [onTurnEnd]);
const {
messages,
isStreaming,
@@ -147,8 +145,8 @@ export function ThreadShell({
// When the user switches away and back, keep the local in-memory thread
// state (including not-yet-persisted messages) instead of replacing it with
// whatever the history endpoint currently knows about. Once a fresh
// canonical replay arrives after turn_end, prefer it so live Markdown/tool
// rendering converges to the same shape as a manual refresh.
// canonical replay arrives (e.g. after ``session_updated`` refresh), prefer it
// so rendering converges to the same shape as a manual refresh.
setMessages((prev) => {
if (hasNewCanonicalHistory && historical.length > 0) {
pendingCanonicalHydrateRef.current.delete(chatId);