Merge upstream/main into feat/shiki-re-highlighting-performance-dd3a
Conflict: packages/ui/src/components/chat/markdown/markdownCore.ts main added per-image-mode markdown parsers (`imageMode` threaded through `parseBlock` and into the block cache key); this branch replaced the identity-keyed block cache with a content-addressed LRU. Resolution keeps the content-addressed cache and folds `imageMode` into the content key, so the `inline` and `label` renderings of the same source cannot answer for each other.
This commit is contained in:
@@ -19,7 +19,7 @@ import { isDesktopLocalOriginActive, isDesktopShell, isVSCodeRuntime } from '@/l
|
||||
import { isMobileSurfaceRuntime } from '@/lib/runtimeSurface';
|
||||
import { ensureOutsideFileGrantForDesktop } from '@/lib/outsideFileGrants';
|
||||
import { getDirectoryForFilePath, isFilePathWithinDirectory, toAbsoluteFilePath } from '@/lib/path-utils';
|
||||
import { renderMarkdownBlocks, renderMarkdownSync } from './markdown/markdownCore';
|
||||
import { renderMarkdownBlocks, renderMarkdownSync, type MarkdownImageMode } from './markdown/markdownCore';
|
||||
import { ensureMarkdownShikiTheme } from './markdown/markdownTheme';
|
||||
import { getMarkdownSyntaxVars } from './markdown/markdownSyntaxVars';
|
||||
import {
|
||||
@@ -37,6 +37,7 @@ import { createMermaidViewerRegistry, MERMAID_BLOCK_SELECTOR, shouldRefreshMerma
|
||||
import {
|
||||
BLOCK_PATH_TOKEN_RE,
|
||||
isAbsoluteReferencePath,
|
||||
localPathFromFileUrl,
|
||||
normalizeReferencePath,
|
||||
parseFileReference,
|
||||
type ParsedFileReference,
|
||||
@@ -245,6 +246,10 @@ const unwrapBlockCodePathTokens = (container: HTMLElement): void => {
|
||||
const extractPathCandidateFromElement = (element: HTMLElement): string => {
|
||||
if (element.tagName.toLowerCase() === 'a') {
|
||||
const href = element.getAttribute('href')?.trim();
|
||||
const fileUrlPath = href ? localPathFromFileUrl(href) : null;
|
||||
if (fileUrlPath) {
|
||||
return fileUrlPath;
|
||||
}
|
||||
if (href && isLikelyFilePath(href)) {
|
||||
return href;
|
||||
}
|
||||
@@ -831,6 +836,7 @@ const useMorphdomMarkdown = ({
|
||||
text,
|
||||
streaming,
|
||||
cacheKey,
|
||||
imageMode = 'inline',
|
||||
syntaxVars,
|
||||
ctx,
|
||||
}: {
|
||||
@@ -838,6 +844,7 @@ const useMorphdomMarkdown = ({
|
||||
text: string;
|
||||
streaming: boolean;
|
||||
cacheKey: string;
|
||||
imageMode?: MarkdownImageMode;
|
||||
syntaxVars: Record<string, string>;
|
||||
ctx: DecorateContext;
|
||||
}) => {
|
||||
@@ -876,7 +883,7 @@ const useMorphdomMarkdown = ({
|
||||
// `display:contents` keeps margin-collapsing/spacing identical to a flat
|
||||
// HTML body — the wrapper exists only for per-block reconciliation.
|
||||
block.style.display = 'contents';
|
||||
block.innerHTML = renderMarkdownSync(text);
|
||||
block.innerHTML = renderMarkdownSync(text, imageMode);
|
||||
// Decorate synchronously too: wrap code blocks in their framed card,
|
||||
// mark inline code, build table controls, etc. The async pass re-decorates
|
||||
// its own DOM before morphing, so without this the first paint shows bare
|
||||
@@ -888,7 +895,7 @@ const useMorphdomMarkdown = ({
|
||||
refreshMermaidViewers();
|
||||
}
|
||||
}
|
||||
}, [containerRef, text, ctx, refreshMermaidViewers]);
|
||||
}, [containerRef, text, imageMode, ctx, refreshMermaidViewers]);
|
||||
|
||||
React.useEffect(() => () => {
|
||||
mermaidViewerRef.current?.cleanup();
|
||||
@@ -901,7 +908,7 @@ const useMorphdomMarkdown = ({
|
||||
const target = container.querySelector<HTMLElement>('[data-markdown-content]') ?? container;
|
||||
let active = true;
|
||||
|
||||
void renderMarkdownBlocks(text, streaming, cacheKey).then((blocks) => {
|
||||
void renderMarkdownBlocks(text, streaming, cacheKey, imageMode).then((blocks) => {
|
||||
if (!active) return;
|
||||
const existing = Array.from(target.children) as HTMLElement[];
|
||||
|
||||
@@ -952,7 +959,7 @@ const useMorphdomMarkdown = ({
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [containerRef, text, streaming, cacheKey, ctx, refreshMermaidViewers]);
|
||||
}, [containerRef, text, streaming, cacheKey, imageMode, ctx, refreshMermaidViewers]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
@@ -1035,7 +1042,15 @@ const MarkdownRendererImpl: React.FC<MarkdownRendererProps> = ({
|
||||
const ctx = useDecorateContext(currentTheme, live, effectiveDirectory ? handlePreviewLoopback : undefined, DEFAULT_MERMAID_CONTROLS);
|
||||
const cacheKey = `markdown-${part?.id ? `part-${part.id}` : `message-${messageId}`}`;
|
||||
|
||||
useMorphdomMarkdown({ containerRef, text: content, streaming: live, cacheKey, syntaxVars, ctx });
|
||||
useMorphdomMarkdown({
|
||||
containerRef,
|
||||
text: content,
|
||||
streaming: live,
|
||||
cacheKey,
|
||||
imageMode: variant === 'assistant' ? 'label' : 'inline',
|
||||
syntaxVars,
|
||||
ctx,
|
||||
});
|
||||
|
||||
const markdownContent = (
|
||||
<div className={cn('break-words w-full min-w-0', className)} ref={containerRef}>
|
||||
|
||||
Reference in New Issue
Block a user