:lightning: optimize chat streaming

This commit is contained in:
2026-03-14 14:00:45 +10:00
parent 1a71a94831
commit 31a359e639
5 changed files with 53 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useMemo, memo } from 'react'
import { marked } from 'marked'
import DOMPurify from 'dompurify'
import type { ChatMessage, TextAnnotation } from '../../types/editor'
@@ -30,7 +30,7 @@ interface Props {
linkedAnnotations?: TextAnnotation[]
}
export function ChatMessageItem({ message, linkedAnnotations }: Props): JSX.Element {
function ChatMessageItemInner({ message, linkedAnnotations }: Props): JSX.Element {
const { activeFilePath, markSaved } = useEditorStore()
const html = useMemo(() => {
@@ -123,3 +123,13 @@ export function ChatMessageItem({ message, linkedAnnotations }: Props): JSX.Elem
</div>
)
}
function areEqual(prev: Props, next: Props): boolean {
if (prev.message.content !== next.message.content) return false
if (prev.message.id !== next.message.id) return false
const prevIds = prev.linkedAnnotations?.map(a => a.id).join(',') ?? ''
const nextIds = next.linkedAnnotations?.map(a => a.id).join(',') ?? ''
return prevIds === nextIds
}
export const ChatMessageItem = memo(ChatMessageItemInner, areEqual)

View File

@@ -50,6 +50,7 @@ export function ChatPanel(): JSX.Element {
const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0)
const [feedbackNotice, setFeedbackNotice] = useState<string | null>(null)
const scrollRef = useRef<HTMLDivElement>(null)
const scrollRafRef = useRef<number | null>(null)
const prevAnnotationCountRef = useRef(annotations.length)
// Collapse history when switching files
@@ -119,12 +120,14 @@ export function ChatPanel(): JSX.Element {
}
}
// Auto-scroll to bottom when new content arrives
// Auto-scroll to bottom when new content arrives (coalesced to one scroll per animation frame)
useEffect(() => {
const el = scrollRef.current
if (el) {
el.scrollTop = el.scrollHeight
}
if (scrollRafRef.current !== null) return
scrollRafRef.current = requestAnimationFrame(() => {
scrollRafRef.current = null
const el = scrollRef.current
if (el) el.scrollTop = el.scrollHeight
})
}, [chatHistory])
const hasFile = Boolean(activeFilePath)