From 5a2dfee2684ff9681033f809bfe4f54d9cc0a27f Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 16 Jun 2026 21:03:52 +1000 Subject: [PATCH] :lightning: further chat performance tweaks --- src/renderer/components/AIChat/ChatPanel.tsx | 73 +++++++++++++++----- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/renderer/components/AIChat/ChatPanel.tsx b/src/renderer/components/AIChat/ChatPanel.tsx index 9d86d4e..51d4ff9 100644 --- a/src/renderer/components/AIChat/ChatPanel.tsx +++ b/src/renderer/components/AIChat/ChatPanel.tsx @@ -1,4 +1,5 @@ -import { useRef, useEffect, useState } from 'react' +import { useRef, useEffect, useState, memo } from 'react' +import { useShallow } from 'zustand/react/shallow' import { useEditorStore } from '../../store/editorStore' import { ChatMessageItem } from './ChatMessageItem' import { ChatInput } from './ChatInput' @@ -7,6 +8,35 @@ import { parseAnnotationsFromAIResponse } from '../../utils/annotationParser' import type { Attachment, TextAnnotation } from '../../types/editor' import './Chat.css' +const StreamingBubble = memo(function StreamingBubble({ scrollContainerRef }: { scrollContainerRef: React.RefObject }) { + const streamingContent = useEditorStore(s => s.streamingContent) + const isAILoading = useEditorStore(s => s.isAILoading) + const rafRef = useRef(null) + + useEffect(() => { + if (!isAILoading) return + if (rafRef.current !== null) return + rafRef.current = requestAnimationFrame(() => { + rafRef.current = null + const el = scrollContainerRef.current + if (el) el.scrollTop = el.scrollHeight + }) + }, [streamingContent, isAILoading, scrollContainerRef]) + + if (!isAILoading) return null + return ( +
+
Editor AI
+
+ {streamingContent + ? {streamingContent} + :
+ } +
+
+ ) +}) + function formatSessionTime(createdAt: number): string { const date = new Date(createdAt) const now = new Date() @@ -34,7 +64,6 @@ export function ChatPanel(): JSX.Element { addUserMessage, finalizeAssistantMessage, setStreamingContent, - streamingContent, setAILoading, setAIError, setAnnotations, @@ -43,7 +72,29 @@ export function ChatPanel(): JSX.Element { setActiveSession, rightPanelTab, setRightPanelTab, - } = useEditorStore() + } = useEditorStore(useShallow(s => ({ + chatHistory: s.chatHistory, + chatSessionsByFile: s.chatSessionsByFile, + activeSessionIdByFile: s.activeSessionIdByFile, + isAILoading: s.isAILoading, + aiError: s.aiError, + activeFileContent: s.activeFileContent, + activeFilePath: s.activeFilePath, + analysisMode: s.analysisMode, + annotations: s.annotations, + annotationsByFile: s.annotationsByFile, + addUserMessage: s.addUserMessage, + finalizeAssistantMessage: s.finalizeAssistantMessage, + setStreamingContent: s.setStreamingContent, + setAILoading: s.setAILoading, + setAIError: s.setAIError, + setAnnotations: s.setAnnotations, + linkAnnotationsToMessage: s.linkAnnotationsToMessage, + newChat: s.newChat, + setActiveSession: s.setActiveSession, + rightPanelTab: s.rightPanelTab, + setRightPanelTab: s.setRightPanelTab, + }))) const tab = rightPanelTab const setTab = setRightPanelTab @@ -138,7 +189,7 @@ export function ChatPanel(): JSX.Element { } } - // Auto-scroll to bottom when new content arrives + // Auto-scroll to bottom when a new chat message is added useEffect(() => { if (scrollRafRef.current !== null) return scrollRafRef.current = requestAnimationFrame(() => { @@ -146,7 +197,7 @@ export function ChatPanel(): JSX.Element { const el = scrollRef.current if (el) el.scrollTop = el.scrollHeight }) - }, [chatHistory, streamingContent]) + }, [chatHistory]) const hasFile = Boolean(activeFilePath) const allAnnotationsForFile: TextAnnotation[] = @@ -252,17 +303,7 @@ export function ChatPanel(): JSX.Element { /> ) })} - {isAILoading && ( -
-
Editor AI
-
- {streamingContent - ? {streamingContent} - :
- } -
-
- )} + {aiError && (
{aiError}
)}