Disable syntax highlighting while tool calls stream
This commit is contained in:
@@ -6,6 +6,7 @@ interface MarkdownProps {
|
|||||||
part: TextPart
|
part: TextPart
|
||||||
isDark?: boolean
|
isDark?: boolean
|
||||||
size?: "base" | "sm" | "tight"
|
size?: "base" | "sm" | "tight"
|
||||||
|
disableHighlight?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Markdown(props: MarkdownProps) {
|
export function Markdown(props: MarkdownProps) {
|
||||||
@@ -19,11 +20,30 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
const text = decodeHtmlEntities(rawText)
|
const text = decodeHtmlEntities(rawText)
|
||||||
const dark = Boolean(props.isDark)
|
const dark = Boolean(props.isDark)
|
||||||
const themeKey = dark ? "dark" : "light"
|
const themeKey = dark ? "dark" : "light"
|
||||||
|
const highlightEnabled = !props.disableHighlight
|
||||||
|
|
||||||
latestRequestedText = text
|
latestRequestedText = text
|
||||||
|
|
||||||
await initMarkdown(dark)
|
await initMarkdown(dark)
|
||||||
|
|
||||||
|
if (!highlightEnabled) {
|
||||||
|
part.renderCache = undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rendered = await renderMarkdown(text, { suppressHighlight: true })
|
||||||
|
|
||||||
|
if (latestRequestedText === text) {
|
||||||
|
setHtml(rendered)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to render markdown:", error)
|
||||||
|
if (latestRequestedText === text) {
|
||||||
|
setHtml(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const cache = part.renderCache
|
const cache = part.renderCache
|
||||||
if (cache && cache.text === text && cache.theme === themeKey) {
|
if (cache && cache.text === text && cache.theme === themeKey) {
|
||||||
setHtml(cache.html)
|
setHtml(cache.html)
|
||||||
@@ -72,6 +92,10 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
|
|
||||||
// Register listener for language loading completion
|
// Register listener for language loading completion
|
||||||
const cleanupLanguageListener = onLanguagesLoaded(async () => {
|
const cleanupLanguageListener = onLanguagesLoaded(async () => {
|
||||||
|
if (props.disableHighlight) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const part = props.part
|
const part = props.part
|
||||||
const rawText = typeof part.text === "string" ? part.text : ""
|
const rawText = typeof part.text === "string" ? part.text : ""
|
||||||
const text = decodeHtmlEntities(rawText)
|
const text = decodeHtmlEntities(rawText)
|
||||||
|
|||||||
@@ -290,10 +290,15 @@ export default function ToolCall(props: ToolCallProps) {
|
|||||||
|
|
||||||
const isLarge = toolName === "edit" || toolName === "write" || toolName === "patch"
|
const isLarge = toolName === "edit" || toolName === "write" || toolName === "patch"
|
||||||
const messageClass = `message-text tool-call-markdown${isLarge ? " tool-call-markdown-large" : ""}`
|
const messageClass = `message-text tool-call-markdown${isLarge ? " tool-call-markdown-large" : ""}`
|
||||||
|
const disableHighlight = state?.status === "running"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={messageClass}>
|
<div class={messageClass}>
|
||||||
<Markdown part={{ type: "text", text: content }} isDark={isDark()} />
|
<Markdown
|
||||||
|
part={{ type: "text", text: content }}
|
||||||
|
isDark={isDark()}
|
||||||
|
disableHighlight={disableHighlight}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ let highlighter: Highlighter | null = null
|
|||||||
let highlighterPromise: Promise<Highlighter> | null = null
|
let highlighterPromise: Promise<Highlighter> | null = null
|
||||||
let currentTheme: "light" | "dark" = "light"
|
let currentTheme: "light" | "dark" = "light"
|
||||||
let isInitialized = false
|
let isInitialized = false
|
||||||
|
let highlightSuppressed = false
|
||||||
|
|
||||||
// Track loaded languages and queue for on-demand loading
|
// Track loaded languages and queue for on-demand loading
|
||||||
const loadedLanguages = new Set<string>()
|
const loadedLanguages = new Set<string>()
|
||||||
@@ -81,6 +82,9 @@ function resolveLanguage(token: string): { canonical: string | null; raw: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function ensureLanguages(content: string) {
|
async function ensureLanguages(content: string) {
|
||||||
|
if (highlightSuppressed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Parse code fences to extract language tokens
|
// Parse code fences to extract language tokens
|
||||||
// Updated regex to capture optional language tokens and handle trailing annotations
|
// Updated regex to capture optional language tokens and handle trailing annotations
|
||||||
const codeBlockRegex = /```[ \t]*([A-Za-z0-9_.+#-]+)?[^`]*?```/g
|
const codeBlockRegex = /```[ \t]*([A-Za-z0-9_.+#-]+)?[^`]*?```/g
|
||||||
@@ -228,6 +232,10 @@ function setupRenderer(isDark: boolean) {
|
|||||||
</div>
|
</div>
|
||||||
`.trim()
|
`.trim()
|
||||||
|
|
||||||
|
if (highlightSuppressed) {
|
||||||
|
return `<div class="markdown-code-block" data-language="${escapedLang}" data-code="${encodedCode}">${header}<pre><code class="language-${escapedLang}">${escapeHtml(decodedCode)}</code></pre></div>`
|
||||||
|
}
|
||||||
|
|
||||||
// Skip highlighting for "text" language or when highlighter is not available
|
// Skip highlighting for "text" language or when highlighter is not available
|
||||||
if (resolvedLang === "text" || !highlighter) {
|
if (resolvedLang === "text" || !highlighter) {
|
||||||
return `<div class="markdown-code-block" data-language="${escapedLang}" data-code="${encodedCode}">${header}<pre><code>${escapeHtml(decodedCode)}</code></pre></div>`
|
return `<div class="markdown-code-block" data-language="${escapedLang}" data-code="${encodedCode}">${header}<pre><code>${escapeHtml(decodedCode)}</code></pre></div>`
|
||||||
@@ -281,18 +289,33 @@ export function isMarkdownReady(): boolean {
|
|||||||
return isInitialized && highlighter !== null
|
return isInitialized && highlighter !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderMarkdown(content: string): Promise<string> {
|
export async function renderMarkdown(
|
||||||
|
content: string,
|
||||||
|
options?: {
|
||||||
|
suppressHighlight?: boolean
|
||||||
|
},
|
||||||
|
): Promise<string> {
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
await initMarkdown(currentTheme === "dark")
|
await initMarkdown(currentTheme === "dark")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const suppressHighlight = options?.suppressHighlight ?? false
|
||||||
const decoded = decodeHtmlEntities(content)
|
const decoded = decodeHtmlEntities(content)
|
||||||
|
|
||||||
// Queue language loading but don't wait for it to complete
|
if (!suppressHighlight) {
|
||||||
await ensureLanguages(decoded)
|
// Queue language loading but don't wait for it to complete
|
||||||
|
await ensureLanguages(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
// Proceed to parse immediately - highlighting will be available on next render
|
const previousSuppressed = highlightSuppressed
|
||||||
return marked.parse(decoded) as Promise<string>
|
highlightSuppressed = suppressHighlight
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Proceed to parse immediately - highlighting will be available on next render
|
||||||
|
return marked.parse(decoded) as Promise<string>
|
||||||
|
} finally {
|
||||||
|
highlightSuppressed = previousSuppressed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSharedHighlighter(): Promise<Highlighter> {
|
export async function getSharedHighlighter(): Promise<Highlighter> {
|
||||||
|
|||||||
Reference in New Issue
Block a user