|
|
@@ -105,8 +105,22 @@ export const renderMarkdownCached = (
|
|
|
// 从 markdown 文本中提取链接(与 renderMarkdown 共用同一套 marked 解析,
|
|
|
// 仅收集 link token,代码块/图片等不会被误收)
|
|
|
|
|
|
+// [审计-第四轮 R4-2] extractMarkdownLinks 的按内容 LRU:
|
|
|
+// 原行为:每次调用都 marked.lexer 全量解析文本。流式期间胶囊 computed(buildCapsuleLinks 等)
|
|
|
+// 每个 token 都会全量重扫所有 AI 消息,lexer 是全模块最重的解析,CPU 随会话长度线性放大。
|
|
|
+// 现行为:与 renderMarkdownCached 同型(MD_CACHE_MAX=200 的 FIFO Map),以原文为 key 记忆化;
|
|
|
+// 纯函数(入参相同输出相同),命中即返回上次结果。
|
|
|
+// 为什么:流式期每 token 的链接提取从 O(全部 AI 消息全文) 降为 O(1)(未变化消息全部命中)。
|
|
|
+// 注意:返回的数组被缓存共享,调用方只读遍历(lib/capsule.ts 仅 for...of),不得原地修改。
|
|
|
+const MD_LINK_CACHE_MAX = 200;
|
|
|
+
|
|
|
+const mdLinkCache = new Map<string, Array<{ title: string; href: string }>>();
|
|
|
+
|
|
|
export const extractMarkdownLinks = (text: string): Array<{ title: string; href: string }> => {
|
|
|
if (!text) return [];
|
|
|
+ const cached = mdLinkCache.get(text);
|
|
|
+ if (cached !== undefined) return cached;
|
|
|
+
|
|
|
const links: Array<{ title: string; href: string }> = [];
|
|
|
try {
|
|
|
const tokens = marked.lexer(text);
|
|
|
@@ -122,6 +136,12 @@ export const extractMarkdownLinks = (text: string): Array<{ title: string; href:
|
|
|
} catch {
|
|
|
// 文本解析异常时返回已收集到的部分链接
|
|
|
}
|
|
|
+ // 简单 FIFO 淘汰,缓存条目数有上限(与 mdCache 同策略)
|
|
|
+ if (mdLinkCache.size >= MD_LINK_CACHE_MAX) {
|
|
|
+ const oldestKey = mdLinkCache.keys().next().value as string | undefined;
|
|
|
+ if (oldestKey !== undefined) mdLinkCache.delete(oldestKey);
|
|
|
+ }
|
|
|
+ mdLinkCache.set(text, links);
|
|
|
return links;
|
|
|
};
|
|
|
|