From ffbdbf46b65af724dcf6d254c99aee5970db24f6 Mon Sep 17 00:00:00 2001 From: how2ice Date: Fri, 29 May 2026 18:08:38 +0900 Subject: [PATCH] chore: test deploy snapshot --- src/app/main/MainChatPanel.tsx | 108 ++++++++++++++---- .../mainChatPanel/ChatConversationView.tsx | 53 ++++++++- 2 files changed, 133 insertions(+), 28 deletions(-) diff --git a/src/app/main/MainChatPanel.tsx b/src/app/main/MainChatPanel.tsx index 09a8461..37a9646 100644 --- a/src/app/main/MainChatPanel.tsx +++ b/src/app/main/MainChatPanel.tsx @@ -3049,6 +3049,19 @@ export function MainChatPanel({ const requestItems = Array.isArray(requestItemsState) ? requestItemsState : []; const isCreatingImportedDraftConversationRef = useRef(false); const hasAttemptedInitialConversationRef = useRef(false); + const pendingConversationSummaryUpdatesRef = useRef( + new Map< + string, + { + incomingMessage: ChatMessage; + hasMeaningfulCodexResponse: boolean; + hasPendingAttentionResponse: boolean; + isForegroundSession: boolean; + responseTimestamp: string; + } + >(), + ); + const conversationSummaryUpdateFrameRef = useRef(null); const setDraft = useCallback((value: SetStateAction) => { const nextValue = typeof value === 'function' ? value(draftRef.current) : value; draftRef.current = nextValue; @@ -3066,6 +3079,63 @@ export function MainChatPanel({ const setDraftValue = useCallback((value: string) => { setDraft(value); }, [setDraft]); + + const enqueueConversationSummaryUpdate = useCallback( + (payload: { + sessionId: string; + incomingMessage: ChatMessage; + hasMeaningfulCodexResponse: boolean; + hasPendingAttentionResponse: boolean; + isForegroundSession: boolean; + responseTimestamp: string; + }) => { + pendingConversationSummaryUpdatesRef.current.set(payload.sessionId, payload); + + if (conversationSummaryUpdateFrameRef.current !== null) { + return; + } + + conversationSummaryUpdateFrameRef.current = window.requestAnimationFrame(() => { + conversationSummaryUpdateFrameRef.current = null; + const batchedUpdates = new Map(pendingConversationSummaryUpdatesRef.current); + pendingConversationSummaryUpdatesRef.current.clear(); + + if (batchedUpdates.size === 0) { + return; + } + + setConversationItems((previous) => + sortChatConversationSummaries( + previous.map((item) => { + const update = batchedUpdates.get(item.sessionId); + + if (!update) { + return item; + } + + return { + ...item, + lastMessagePreview: createConversationPreviewText(update.incomingMessage.text), + lastResponsePreview: + update.incomingMessage.author === 'codex' && !isPreparingChatReplyText(update.incomingMessage.text) + ? createConversationPreviewText(update.incomingMessage.text) + : item.lastResponsePreview, + lastMessageAt: update.responseTimestamp, + updatedAt: update.responseTimestamp, + hasUnreadResponse: + update.hasMeaningfulCodexResponse && !update.isForegroundSession ? true : item.hasUnreadResponse, + hasPendingAttention: + update.hasPendingAttentionResponse && update.incomingMessage.author === 'codex' + ? true + : item.hasPendingAttention, + }; + }), + ), + ); + }); + }, + [setConversationItems], + ); const setRequestItems = useCallback((next: SetStateAction) => { setRequestItemsState((previous) => { const safePrevious = Array.isArray(previous) ? previous : []; @@ -3092,6 +3162,14 @@ export function MainChatPanel({ activeSessionIdRef.current = activeSessionId; }, [activeSessionId]); + useEffect(() => { + return () => { + if (conversationSummaryUpdateFrameRef.current !== null) { + window.cancelAnimationFrame(conversationSummaryUpdateFrameRef.current); + } + }; + }, []); + useEffect(() => { if (mode !== 'live' || location.pathname !== chatRoutePath) { return; @@ -4337,28 +4415,14 @@ export function MainChatPanel({ }); const responseTimestamp = new Date().toISOString(); - setConversationItems((previous) => - sortChatConversationSummaries( - previous.map((item) => - item.sessionId === sessionId - ? { - ...item, - lastMessagePreview: createConversationPreviewText(incomingMessage.text), - lastResponsePreview: - incomingMessage.author === 'codex' && !isPreparingChatReplyText(incomingMessage.text) - ? createConversationPreviewText(incomingMessage.text) - : item.lastResponsePreview, - lastMessageAt: responseTimestamp, - updatedAt: responseTimestamp, - hasUnreadResponse: - hasMeaningfulCodexResponse && !isForegroundSession ? true : item.hasUnreadResponse, - hasPendingAttention: - hasPendingAttentionResponse && incomingMessage.author === 'codex' ? true : item.hasPendingAttention, - } - : item, - ), - ), - ); + enqueueConversationSummaryUpdate({ + sessionId, + incomingMessage, + hasMeaningfulCodexResponse, + hasPendingAttentionResponse, + isForegroundSession, + responseTimestamp, + }); const eventConversation = conversationItemsRef.current.find((item) => item.sessionId === sessionId) ?? null; diff --git a/src/app/main/mainChatPanel/ChatConversationView.tsx b/src/app/main/mainChatPanel/ChatConversationView.tsx index 8028c11..182ce4b 100644 --- a/src/app/main/mainChatPanel/ChatConversationView.tsx +++ b/src/app/main/mainChatPanel/ChatConversationView.tsx @@ -521,6 +521,12 @@ type MessageRenderPayload = { promptTargets: Extract[]; }; +type MessageRenderPayloadCacheEntry = { + messageText: string; + messageParts: ChatMessagePart[] | undefined; + payload: MessageRenderPayload; +}; + const RANK_LINE_PATTERN = /(?:\b(?:rank|score)\b|랭크|점수)\s*[:=]?\s*[-+]?\d+(?:\.\d+)?(?:e[-+]?\d+)?\b/i; const TITLE_VALUE_PATTERN = /^(?:[-*]\s*)?(?:\d+\.\s*)?(?:title|제목)\s*[:=-]\s*(.+)$/i; const LINK_VALUE_PATTERN = /^(?:[-*]\s*)?(?:\d+\.\s*)?(?:link|url|href|링크)\s*[:=-]\s*(https?:\/\/\S+|\/\S+)$/i; @@ -3654,6 +3660,7 @@ export function ChatConversationView({ const suppressImmediateSendClickRef = useRef(false); const composerDraftValueRef = useRef(draft); const lastSyncedComposerDraftRef = useRef(draft); + const messageRenderPayloadCacheRef = useRef(new Map()); const setComposerDraftValue = (nextValue: string) => { composerDraftValueRef.current = nextValue; @@ -3788,6 +3795,10 @@ export function ChatConversationView({ forceComposerDraftSync(); }; + useEffect(() => { + messageRenderPayloadCacheRef.current.clear(); + }, [sessionId]); + const shouldShowConversationLoadingOverlay = isConversationLoading && visibleMessages.length === 0; const orderedMessages = useMemo(() => { @@ -3954,14 +3965,44 @@ export function ChatConversationView({ setExpandedSystemExecutionActivityRequestId(null); }, [systemExecutionDisplayMode]); - const messageRenderPayloadById = useMemo( - () => - new Map( - orderedMessages.map((message) => [message.id, extractMessageRenderPayload(message)] as const), - ), - [orderedMessages], + const getCachedMessageRenderPayload = useCallback( + (message: ChatMessage) => { + const cached = messageRenderPayloadCacheRef.current.get(message.id); + + if (cached && cached.messageText === message.text && cached.messageParts === message.parts) { + return cached.payload; + } + + const payload = extractMessageRenderPayload(message); + messageRenderPayloadCacheRef.current.set(message.id, { + messageText: message.text, + messageParts: message.parts, + payload, + }); + return payload; + }, + [], ); + const messageRenderPayloadById = useMemo(() => { + const activeMessageIds = new Set(); + const nextMap = new Map(); + + orderedMessages.forEach((message) => { + activeMessageIds.add(message.id); + nextMap.set(message.id, getCachedMessageRenderPayload(message)); + }); + + const cachedMessageIds = Array.from(messageRenderPayloadCacheRef.current.keys()); + cachedMessageIds.forEach((messageId) => { + if (!activeMessageIds.has(messageId)) { + messageRenderPayloadCacheRef.current.delete(messageId); + } + }); + + return nextMap; + }, [orderedMessages, getCachedMessageRenderPayload]); + useEffect(() => { setOpenedPreviewUrls([]); setExpandedGroupIds([]);