From 4fe21949e218d57bba89d201b1b0d412b9cf6d9e Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 03:53:52 +0300 Subject: [PATCH] perf(renderer): cache activity item parsing --- .../components/team/activity/ActivityItem.tsx | 118 ++++++++++++++---- 1 file changed, 91 insertions(+), 27 deletions(-) diff --git a/src/renderer/components/team/activity/ActivityItem.tsx b/src/renderer/components/team/activity/ActivityItem.tsx index e02f49ee..ad7718f5 100644 --- a/src/renderer/components/team/activity/ActivityItem.tsx +++ b/src/renderer/components/team/activity/ActivityItem.tsx @@ -280,14 +280,87 @@ function getStringField(obj: StructuredMessage, key: string): string | null { return typeof value === 'string' && value.trim() !== '' ? value : null; } +const EMPTY_MEMBER_COLOR_MAP = new Map(); +const MAX_ACTIVITY_ITEM_CACHE_ENTRIES = 500; +const activityTimestampCache = new Map(); +const activityDisplayTextCache = new Map(); +const activityStructuredMessageCache = new Map(); +const activityIdleSemanticCache = new Map>(); +const activityNoiseMessageCache = new Map(); +const activityStrippedTextCache = new Map(); + +function getCachedActivityValue(cache: Map, key: string, buildValue: () => T): T { + if (cache.has(key)) return cache.get(key) as T; + + const value = buildValue(); + if (cache.size >= MAX_ACTIVITY_ITEM_CACHE_ENTRIES) { + const oldestKey = cache.keys().next().value; + if (oldestKey !== undefined) cache.delete(oldestKey); + } + cache.set(key, value); + return value; +} + +function parseStructuredAgentMessageCached(text: string): StructuredMessage | null { + return getCachedActivityValue(activityStructuredMessageCache, text, () => + parseStructuredAgentMessage(text) + ); +} + +function classifyIdleNotificationCached( + message: InboxMessage +): ReturnType { + return getCachedActivityValue(activityIdleSemanticCache, message.text, () => + classifyIdleNotification(message) + ); +} + +function getStrippedActivityTextCached({ + message, + structured, + hasBootstrapDisplay, + isCrossTeamAny, +}: { + message: InboxMessage; + structured: StructuredMessage | null; + hasBootstrapDisplay: boolean; + isCrossTeamAny: boolean; +}): string | null { + if (structured) return null; + + const cacheKey = encodeCacheParts([ + message.text ?? '', + message.from ?? '', + message.to ?? '', + message.source ?? '', + hasBootstrapDisplay ? '1' : '0', + isCrossTeamAny ? '1' : '0', + ]); + + return getCachedActivityValue(activityStrippedTextCache, cacheKey, () => { + let stripped = getSanitizedInboxMessageText(message).trim(); + if (!hasBootstrapDisplay) { + stripped = stripAgentBlocks(stripped).trim(); + } + if (!stripped) return null; + if (isCrossTeamAny) { + stripped = stripCrossTeamPrefix(stripped); + } + return stripped.replace(/\\n/g, '\n').replace(/\\t/g, '\t'); + }); +} + /** Check if a message renders as a compact noise row (idle, shutdown, etc.). */ export function isNoiseMessage(text: string): boolean { - return ( - getIdleNoiseLabel(text) !== null || - ((): boolean => { - const parsed = parseStructuredAgentMessage(text); - return parsed !== null && getNoiseLabel(parsed) !== null; - })() + return getCachedActivityValue( + activityNoiseMessageCache, + text, + () => + getIdleNoiseLabel(text) !== null || + (() => { + const parsed = parseStructuredAgentMessageCached(text); + return parsed !== null && getNoiseLabel(parsed) !== null; + })() ); } @@ -717,10 +790,6 @@ const AUTH_ERROR_PATTERNS = [ /unauthorized/i, ]; -const EMPTY_MEMBER_COLOR_MAP = new Map(); -const activityTimestampCache = new Map(); -const activityDisplayTextCache = new Map(); - function getLocalDayCacheKey(date: Date): string { return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; } @@ -919,7 +988,7 @@ export const ActivityItem = memo( [message.timestamp] ); - const structured = parseStructuredAgentMessage(message.text); + const structured = parseStructuredAgentMessageCached(message.text); const bootstrapDisplay = getBootstrapPromptDisplay(message); const bootstrapAcknowledgement = getBootstrapAcknowledgementDisplay(message); // Only flag agent messages as rate-limited, not user's own quotes @@ -930,7 +999,7 @@ export const ActivityItem = memo( const isAuthError = isApiError && AUTH_ERROR_PATTERNS.some((p) => p.test(message.text)); // Never collapse rate limit messages as noise — they must be visible const noiseLabel = structured && !rateLimited ? getNoiseLabel(structured) : null; - const idleSemantic = classifyIdleNotification(message); + const idleSemantic = classifyIdleNotificationCached(message); const systemLabel = !structured && !rateLimited ? getSystemMessageLabel(message.text) : null; const isManaged = collapseMode === 'managed'; @@ -973,21 +1042,16 @@ export const ActivityItem = memo( const isSystemMessage = message.from === 'system'; // Strip agent-only blocks + normalize escape sequences (before linkification) - const strippedText = useMemo(() => { - if (structured) return null; - let stripped = getSanitizedInboxMessageText(message).trim(); - if (!bootstrapDisplay) { - stripped = stripAgentBlocks(stripped).trim(); - } - if (!stripped) return null; // All content was agent-only blocks → show summary instead - // Strip cross-team metadata tag (e.g. `\n`) - // — kept in stored text for CLI agents / durable artifacts. - if (isCrossTeamAny) { - stripped = stripCrossTeamPrefix(stripped); - } - // Normalize literal \n from historical CLI-produced text to real newlines - return stripped.replace(/\\n/g, '\n').replace(/\\t/g, '\t'); - }, [structured, message, bootstrapDisplay, isCrossTeamAny]); + const strippedText = useMemo( + () => + getStrippedActivityTextCached({ + message, + structured, + hasBootstrapDisplay: bootstrapDisplay !== null, + isCrossTeamAny, + }), + [structured, message, bootstrapDisplay, isCrossTeamAny] + ); const standaloneSlashCommand = useMemo( () => (strippedText ? parseStandaloneSlashCommand(strippedText) : null), [strippedText]