Track tool part versions for SSE updates
This commit is contained in:
@@ -167,8 +167,10 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
let messageItemCache = new Map<string, MessageCacheEntry>()
|
||||
let toolItemCache = new Map<string, ToolCacheEntry>()
|
||||
let scrollAnimationFrame: number | null = null
|
||||
let lastKnownScrollTop = 0
|
||||
|
||||
const makeScrollKey = (instanceId: string, sessionId: string) => `${instanceId}:${sessionId}`
|
||||
|
||||
const scrollStateKey = () => makeScrollKey(props.instanceId, props.sessionId)
|
||||
const connectionStatus = () => sseManager.getStatus(props.instanceId)
|
||||
|
||||
@@ -180,6 +182,12 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
|
||||
function createToolContentKey(toolPart: any, messageInfo?: any): string {
|
||||
const state = toolPart?.state ?? {}
|
||||
const version = typeof toolPart?.__version === "number" ? toolPart.__version : null
|
||||
if (version !== null) {
|
||||
const status = state?.status ?? "unknown"
|
||||
return `${version}:${status}`
|
||||
}
|
||||
|
||||
const metadata = state?.metadata ?? {}
|
||||
const input = state?.input ?? {}
|
||||
const output = state?.output ?? {}
|
||||
@@ -261,18 +269,32 @@ export default function MessageStream(props: MessageStreamProps) {
|
||||
})
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
function handleScroll(event: Event) {
|
||||
if (!containerRef) return
|
||||
|
||||
if (scrollAnimationFrame !== null) {
|
||||
cancelAnimationFrame(scrollAnimationFrame)
|
||||
}
|
||||
|
||||
const isUserScroll = event.isTrusted
|
||||
|
||||
scrollAnimationFrame = requestAnimationFrame(() => {
|
||||
if (!containerRef) return
|
||||
|
||||
const currentScrollTop = containerRef.scrollTop
|
||||
const movingUp = currentScrollTop < lastKnownScrollTop - 1
|
||||
lastKnownScrollTop = currentScrollTop
|
||||
|
||||
const atBottom = isNearBottom(containerRef)
|
||||
setAutoScroll(atBottom)
|
||||
|
||||
if (isUserScroll) {
|
||||
if ((movingUp || !atBottom) && autoScroll()) {
|
||||
setAutoScroll(false)
|
||||
} else if (!movingUp && atBottom && !autoScroll()) {
|
||||
setAutoScroll(true)
|
||||
}
|
||||
}
|
||||
|
||||
updateScrollIndicators(containerRef)
|
||||
scrollAnimationFrame = null
|
||||
})
|
||||
|
||||
@@ -185,6 +185,35 @@ export function computeDisplayParts(message: Message, showThinking: boolean): Me
|
||||
return { text, tool, reasoning, combined, showThinking, version }
|
||||
}
|
||||
|
||||
function ensurePartVersionsMap(message: Message) {
|
||||
if (!message.partVersions) {
|
||||
message.partVersions = new Map()
|
||||
}
|
||||
return message.partVersions
|
||||
}
|
||||
|
||||
function initializePartVersion(message: Message, part: any) {
|
||||
const partId = typeof part?.id === "string" ? part.id : null
|
||||
if (!partId) return
|
||||
const versions = ensurePartVersionsMap(message)
|
||||
if (!versions.has(partId)) {
|
||||
versions.set(partId, 0)
|
||||
}
|
||||
const partAny = part as any
|
||||
partAny.__version = versions.get(partId)
|
||||
}
|
||||
|
||||
function bumpPartVersion(message: Message, part: any): number | undefined {
|
||||
const partId = typeof part?.id === "string" ? part.id : null
|
||||
if (!partId) return undefined
|
||||
const versions = ensurePartVersionsMap(message)
|
||||
const next = (versions.get(partId) ?? 0) + 1
|
||||
versions.set(partId, next)
|
||||
const partAny = part as any
|
||||
partAny.__version = next
|
||||
return next
|
||||
}
|
||||
|
||||
function withSession(instanceId: string, sessionId: string, updater: (session: Session) => void) {
|
||||
const instanceSessions = sessions().get(instanceId)
|
||||
if (!instanceSessions) return
|
||||
@@ -797,7 +826,7 @@ async function loadMessages(instanceId: string, sessionId: string, force = false
|
||||
messagesInfo.set(messageId, info)
|
||||
|
||||
// Normalize parts to decode entities and clear caches for text segments
|
||||
const parts = (apiMessage.parts || []).map((part: any) => normalizeMessagePart(part))
|
||||
const parts: any[] = (apiMessage.parts || []).map((part: any) => normalizeMessagePart(part))
|
||||
|
||||
const message: Message = {
|
||||
id: messageId,
|
||||
@@ -807,8 +836,11 @@ async function loadMessages(instanceId: string, sessionId: string, force = false
|
||||
timestamp: info.time?.created || Date.now(),
|
||||
status: "complete" as const,
|
||||
version: 0,
|
||||
partVersions: new Map(),
|
||||
}
|
||||
|
||||
parts.forEach((part: any) => initializePartVersion(message, part))
|
||||
|
||||
message.displayParts = computeDisplayParts(message, preferences().showThinkingBlocks)
|
||||
|
||||
return message
|
||||
@@ -924,8 +956,10 @@ function handleMessageUpdate(instanceId: string, event: any): void {
|
||||
timestamp: Date.now(),
|
||||
status: "streaming" as const,
|
||||
version: 0,
|
||||
partVersions: new Map(),
|
||||
}
|
||||
|
||||
initializePartVersion(newMessage, part)
|
||||
newMessage.displayParts = computeDisplayParts(newMessage, preferences().showThinkingBlocks)
|
||||
|
||||
let insertIndex = session.messages.length
|
||||
@@ -987,6 +1021,7 @@ function handleMessageUpdate(instanceId: string, event: any): void {
|
||||
if (part.id && typeof part.id === "string") {
|
||||
partMap.set(part.id, baseParts.length - 1)
|
||||
}
|
||||
initializePartVersion(message, part)
|
||||
shouldIncrementVersion = true
|
||||
// Clear render cache for new text parts
|
||||
if (part.type === "text") {
|
||||
@@ -1006,6 +1041,7 @@ function handleMessageUpdate(instanceId: string, event: any): void {
|
||||
}
|
||||
|
||||
baseParts[partIndex] = part
|
||||
bumpPartVersion(message, part)
|
||||
if (part.type !== "text" || !previousPart || previousPart.text !== part.text) {
|
||||
shouldIncrementVersion = true
|
||||
// Clear render cache when text changes
|
||||
@@ -1311,8 +1347,11 @@ async function sendMessage(
|
||||
timestamp: Date.now(),
|
||||
status: "sending",
|
||||
version: 0,
|
||||
partVersions: new Map(),
|
||||
}
|
||||
|
||||
optimisticParts.forEach((part: any) => initializePartVersion(optimisticMessage, part))
|
||||
|
||||
optimisticMessage.displayParts = computeDisplayParts(optimisticMessage, preferences().showThinkingBlocks)
|
||||
|
||||
withSession(instanceId, sessionId, (session) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface Message {
|
||||
timestamp: number
|
||||
status: "sending" | "sent" | "streaming" | "complete" | "error"
|
||||
version: number
|
||||
partVersions?: Map<string, number>
|
||||
displayParts?: MessageDisplayParts
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user