:lightning: further chat performance tweaks
This commit is contained in:
@@ -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 { useEditorStore } from '../../store/editorStore'
|
||||||
import { ChatMessageItem } from './ChatMessageItem'
|
import { ChatMessageItem } from './ChatMessageItem'
|
||||||
import { ChatInput } from './ChatInput'
|
import { ChatInput } from './ChatInput'
|
||||||
@@ -7,6 +8,35 @@ import { parseAnnotationsFromAIResponse } from '../../utils/annotationParser'
|
|||||||
import type { Attachment, TextAnnotation } from '../../types/editor'
|
import type { Attachment, TextAnnotation } from '../../types/editor'
|
||||||
import './Chat.css'
|
import './Chat.css'
|
||||||
|
|
||||||
|
const StreamingBubble = memo(function StreamingBubble({ scrollContainerRef }: { scrollContainerRef: React.RefObject<HTMLDivElement> }) {
|
||||||
|
const streamingContent = useEditorStore(s => s.streamingContent)
|
||||||
|
const isAILoading = useEditorStore(s => s.isAILoading)
|
||||||
|
const rafRef = useRef<number | null>(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 (
|
||||||
|
<div className="chat-message chat-message-assistant">
|
||||||
|
<div className="chat-message-label">Editor AI</div>
|
||||||
|
<div className="chat-message-content">
|
||||||
|
{streamingContent
|
||||||
|
? <span>{streamingContent}</span>
|
||||||
|
: <div className="chat-typing"><span /><span /><span /></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
function formatSessionTime(createdAt: number): string {
|
function formatSessionTime(createdAt: number): string {
|
||||||
const date = new Date(createdAt)
|
const date = new Date(createdAt)
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@@ -34,7 +64,6 @@ export function ChatPanel(): JSX.Element {
|
|||||||
addUserMessage,
|
addUserMessage,
|
||||||
finalizeAssistantMessage,
|
finalizeAssistantMessage,
|
||||||
setStreamingContent,
|
setStreamingContent,
|
||||||
streamingContent,
|
|
||||||
setAILoading,
|
setAILoading,
|
||||||
setAIError,
|
setAIError,
|
||||||
setAnnotations,
|
setAnnotations,
|
||||||
@@ -43,7 +72,29 @@ export function ChatPanel(): JSX.Element {
|
|||||||
setActiveSession,
|
setActiveSession,
|
||||||
rightPanelTab,
|
rightPanelTab,
|
||||||
setRightPanelTab,
|
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 tab = rightPanelTab
|
||||||
const setTab = setRightPanelTab
|
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(() => {
|
useEffect(() => {
|
||||||
if (scrollRafRef.current !== null) return
|
if (scrollRafRef.current !== null) return
|
||||||
scrollRafRef.current = requestAnimationFrame(() => {
|
scrollRafRef.current = requestAnimationFrame(() => {
|
||||||
@@ -146,7 +197,7 @@ export function ChatPanel(): JSX.Element {
|
|||||||
const el = scrollRef.current
|
const el = scrollRef.current
|
||||||
if (el) el.scrollTop = el.scrollHeight
|
if (el) el.scrollTop = el.scrollHeight
|
||||||
})
|
})
|
||||||
}, [chatHistory, streamingContent])
|
}, [chatHistory])
|
||||||
|
|
||||||
const hasFile = Boolean(activeFilePath)
|
const hasFile = Boolean(activeFilePath)
|
||||||
const allAnnotationsForFile: TextAnnotation[] =
|
const allAnnotationsForFile: TextAnnotation[] =
|
||||||
@@ -252,17 +303,7 @@ export function ChatPanel(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{isAILoading && (
|
<StreamingBubble scrollContainerRef={scrollRef} />
|
||||||
<div className="chat-message chat-message-assistant">
|
|
||||||
<div className="chat-message-label">Editor AI</div>
|
|
||||||
<div className="chat-message-content">
|
|
||||||
{streamingContent
|
|
||||||
? <span>{streamingContent}</span>
|
|
||||||
: <div className="chat-typing"><span /><span /><span /></div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{aiError && (
|
{aiError && (
|
||||||
<div className="chat-error">{aiError}</div>
|
<div className="chat-error">{aiError}</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user