perf(ui): reuse warm markdown blocks
This commit is contained in:
@@ -49,7 +49,10 @@ import { escapeRawMarkdownHtml, isLocalFileUrl, MARKDOWN_FORBIDDEN_TAGS } from '
|
||||
const {
|
||||
__markdownImageCandidateCacheForTests,
|
||||
extractMarkdownImageCandidates,
|
||||
getCachedMarkdownBlocks,
|
||||
renderMarkdownBlocks,
|
||||
renderMarkdownSync,
|
||||
resetMarkdownHtmlCacheForTests,
|
||||
} = await import('./markdownCore');
|
||||
const { resolveMarkdownImageSource } = await import('./markdownImageAssets');
|
||||
|
||||
@@ -90,6 +93,47 @@ describe('markdown sanitization', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('Markdown block cache reads', () => {
|
||||
test('returns all settled blocks synchronously after a full cache hit', async () => {
|
||||
resetMarkdownHtmlCacheForTests();
|
||||
const text = '**cached** settled markdown';
|
||||
|
||||
expect(getCachedMarkdownBlocks(text)).toBeNull();
|
||||
const rendered = await renderMarkdownBlocks(text, false);
|
||||
|
||||
expect(getCachedMarkdownBlocks(text)).toEqual(rendered);
|
||||
});
|
||||
|
||||
test('returns null for a cold or partial settled miss', async () => {
|
||||
resetMarkdownHtmlCacheForTests();
|
||||
const first = 'first settled block';
|
||||
const changed = 'first settled block\n\nsecond settled block';
|
||||
|
||||
await renderMarkdownBlocks(first, false);
|
||||
|
||||
expect(getCachedMarkdownBlocks(changed)).toBeNull();
|
||||
});
|
||||
|
||||
test('keeps image mode identity out of the settled full hit', async () => {
|
||||
resetMarkdownHtmlCacheForTests();
|
||||
const text = '';
|
||||
|
||||
await renderMarkdownBlocks(text, false, 'inline');
|
||||
|
||||
expect(getCachedMarkdownBlocks(text, 'label')).toBeNull();
|
||||
expect(getCachedMarkdownBlocks(text, 'inline')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('does not treat streaming live-cache entries as settled full hits', async () => {
|
||||
resetMarkdownHtmlCacheForTests();
|
||||
const text = 'streaming markdown';
|
||||
|
||||
await renderMarkdownBlocks(text, true);
|
||||
|
||||
expect(getCachedMarkdownBlocks(text)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Markdown images', () => {
|
||||
test('renders assistant images as icon-ready text without loading the source', () => {
|
||||
const html = renderMarkdownSync([
|
||||
|
||||
@@ -548,10 +548,30 @@ export const __markdownBlockCacheSizesForTests = (): { full: number; live: numbe
|
||||
live: liveBlockCache.size,
|
||||
});
|
||||
|
||||
const parseBlock = async (
|
||||
block: MarkdownBlock,
|
||||
imageMode: MarkdownImageMode,
|
||||
): Promise<string> => {
|
||||
/**
|
||||
* Read a settled render synchronously when every block is already in the full
|
||||
* cache. Cache reads retain the existing LRU `get` semantics and do not insert
|
||||
* or expand either cache.
|
||||
*/
|
||||
export const getCachedMarkdownBlocks = (
|
||||
text: string,
|
||||
imageMode: MarkdownImageMode = 'inline',
|
||||
): RenderedBlock[] | null => {
|
||||
if (!text) return [];
|
||||
|
||||
const blocks = streamBlocks(text, false);
|
||||
const rendered: RenderedBlock[] = [];
|
||||
for (const block of blocks) {
|
||||
const contentHash = contentFingerprint(block.raw);
|
||||
const id = markdownBlockCacheKey(contentHash, block.mode, block.highlight, imageMode);
|
||||
const html = fullBlockCache.get(id);
|
||||
if (html === undefined) return null;
|
||||
rendered.push({ id, html });
|
||||
}
|
||||
return rendered;
|
||||
};
|
||||
|
||||
const parseBlock = async (block: MarkdownBlock, imageMode: MarkdownImageMode): Promise<string> => {
|
||||
const parser = imageMode === 'label' ? imageLabelParser : inlineImageParser;
|
||||
const parsed = await Promise.resolve(parser.parse(block.src));
|
||||
const withMath = renderMathExpressions(parsed);
|
||||
|
||||
Reference in New Issue
Block a user