fix: stabilize markdown code block selection and copy

This commit is contained in:
JOJO 2026-04-12 19:59:28 +08:00
parent 49bd994cfa
commit 78df2249cf

View File

@ -5,6 +5,18 @@ import renderMathInElement from 'katex/contrib/auto-render';
let latexRenderTimer: number | null = null; let latexRenderTimer: number | null = null;
const markdownCache = new Map<string, string>(); const markdownCache = new Map<string, string>();
function stableHash(input: string): string {
let hash = 5381;
for (let i = 0; i < input.length; i += 1) {
hash = (hash * 33) ^ input.charCodeAt(i);
}
return (hash >>> 0).toString(36);
}
function buildCacheKey(text: string): string {
return `md_${text.length}_${stableHash(text)}`;
}
function wrapCodeBlocks(html: string, isStreaming = false) { function wrapCodeBlocks(html: string, isStreaming = false) {
if (isStreaming) { if (isStreaming) {
return html; return html;
@ -16,7 +28,7 @@ function wrapCodeBlocks(html: string, isStreaming = false) {
(match, attributes, content) => { (match, attributes, content) => {
const langMatch = attributes.match(/class="[^"]*language-(\w+)/); const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
const language = langMatch ? langMatch[1] : 'text'; const language = langMatch ? langMatch[1] : 'text';
const blockId = `code-${Date.now()}-${counter++}`; const blockId = `code-${stableHash(`${attributes}|${content}|${counter++}`)}`;
const escapedContent = content const escapedContent = content
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
@ -50,7 +62,7 @@ export function renderMarkdown(text: string, isStreaming = false) {
}); });
if (!isStreaming) { if (!isStreaming) {
const cacheKey = `${text.length}_${text.substring(0, 100)}`; const cacheKey = buildCacheKey(text);
if (markdownCache.has(cacheKey)) { if (markdownCache.has(cacheKey)) {
return markdownCache.get(cacheKey) as string; return markdownCache.get(cacheKey) as string;
} }
@ -61,7 +73,7 @@ export function renderMarkdown(text: string, isStreaming = false) {
html = wrapCodeBlocks(html, isStreaming); html = wrapCodeBlocks(html, isStreaming);
if (!isStreaming && text.length < 10000) { if (!isStreaming && text.length < 10000) {
const cacheKey = `${text.length}_${text.substring(0, 100)}`; const cacheKey = buildCacheKey(text);
markdownCache.set(cacheKey, html); markdownCache.set(cacheKey, html);
if (markdownCache.size > 20) { if (markdownCache.size > 20) {
const firstKey = markdownCache.keys().next().value as string | undefined; const firstKey = markdownCache.keys().next().value as string | undefined;