diff --git a/ui/src/pages/chat/chat-thread.test.ts b/ui/src/pages/chat/chat-thread.test.ts index 7bda56ee8622..42ff4505d024 100644 --- a/ui/src/pages/chat/chat-thread.test.ts +++ b/ui/src/pages/chat/chat-thread.test.ts @@ -23,6 +23,7 @@ const SENDER_METADATA_BLOCK = function createProps(overrides: Partial = {}): CachedChatItemsProps { return { + paneId: "pane-a", sessionKey: "main", runId: null, messages: [], @@ -2454,6 +2455,31 @@ describe("thread item cache", () => { }), ).not.toBe(first); }); + + it("keeps same-session render caches isolated between panes", () => { + resetChatThreadState(); + const messages = [ + { role: "assistant", content: "needle" }, + { role: "user", content: "other" }, + ]; + const paneA = createProps({ + paneId: "pane-a", + messages, + searchOpen: true, + searchQuery: "needle", + }); + const paneB = createProps({ paneId: "pane-b", messages }); + + const paneAItems = buildCachedChatItems(paneA); + const paneBItems = buildCachedChatItems(paneB); + + expect(buildCachedChatItems({ ...paneA })).toBe(paneAItems); + expect(buildCachedChatItems({ ...paneB })).toBe(paneBItems); + + resetChatThreadState("pane-a"); + expect(buildCachedChatItems({ ...paneA })).not.toBe(paneAItems); + expect(buildCachedChatItems({ ...paneB })).toBe(paneBItems); + }); }); function canvasBlocksIn(group: MessageGroup): unknown[] { diff --git a/ui/src/pages/chat/chat-thread.ts b/ui/src/pages/chat/chat-thread.ts index 09580fded4e7..224ef7766d56 100644 --- a/ui/src/pages/chat/chat-thread.ts +++ b/ui/src/pages/chat/chat-thread.ts @@ -46,6 +46,7 @@ import { getOrCreateSessionCacheValue } from "./session-cache.ts"; import { buildUserChatMessageContentBlocks } from "./user-message-content.ts"; type BuildChatItemsProps = { + paneId: string; sessionKey: string; runId?: string | null; /** Invalidates cached display copy when the active UI language changes. */ @@ -77,13 +78,17 @@ type StreamRunRenderItem = { parts: Array>; }; -const chatItemsBySession = new Map(); +const chatItemsByPane = new Map>(); const expandedToolCardsBySession = new Map>(); const initializedToolCardsBySession = new Map>(); const lastAutoExpandPrefBySession = new Map(); -export function resetChatThreadState(): void { - chatItemsBySession.clear(); +export function resetChatThreadState(paneId?: string): void { + if (paneId) { + chatItemsByPane.delete(paneId); + return; + } + chatItemsByPane.clear(); resetWorkingStartedAt(); expandedToolCardsBySession.clear(); initializedToolCardsBySession.clear(); @@ -1546,7 +1551,12 @@ function sameChatItemsInput(previous: BuildChatItemsProps, next: BuildChatItemsP export function buildCachedChatItems( input: BuildChatItemsProps, ): ReturnType { - const cached = getOrCreateSessionCacheValue(chatItemsBySession, input.sessionKey, () => ({ + let paneCache = chatItemsByPane.get(input.paneId); + if (!paneCache) { + paneCache = new Map(); + chatItemsByPane.set(input.paneId, paneCache); + } + const cached = getOrCreateSessionCacheValue(paneCache, input.sessionKey, () => ({ input: null, items: [], })); diff --git a/ui/src/pages/chat/components/chat-thread.ts b/ui/src/pages/chat/components/chat-thread.ts index 6f5fb70cfdc1..808c4f9a7441 100644 --- a/ui/src/pages/chat/components/chat-thread.ts +++ b/ui/src/pages/chat/components/chat-thread.ts @@ -189,6 +189,7 @@ export function resetChatThreadPresentationState(paneId?: string) { removeChatSelectionPopup(); if (paneId) { threadStates.delete(paneId); + resetChatThreadState(paneId); } else { threadStates.clear(); resetChatThreadState(); @@ -598,6 +599,7 @@ export function renderChatThread(props: ChatThreadProps) { const deleted = getDeletedMessages(props.sessionKey); const locale = i18n.getLocale(); const chatItems = buildCachedChatItems({ + paneId: props.paneId, sessionKey: props.sessionKey, runId: props.sessions?.sessions.find((row) => areUiSessionKeysEquivalent(row.key, props.sessionKey))