From 5d5fbfb5f2f32bad15c933313a5e2dbce0117f04 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Fri, 27 Feb 2026 13:28:43 +0000 Subject: [PATCH] perf(ui): lazy-mount tool call details --- packages/ui/src/components/tool-call.tsx | 1298 ++++++++++++---------- 1 file changed, 687 insertions(+), 611 deletions(-) diff --git a/packages/ui/src/components/tool-call.tsx b/packages/ui/src/components/tool-call.tsx index f0086016..a1125e73 100644 --- a/packages/ui/src/components/tool-call.tsx +++ b/packages/ui/src/components/tool-call.tsx @@ -79,6 +79,635 @@ interface ToolCallProps { forceCollapsed?: boolean } +function ToolCallDetails(props: { + toolCallMemo: () => ToolCallPart + toolState: () => ToolState | undefined + toolName: () => string + toolCallIdentifier: () => string + instanceId: string + sessionId: string + messageId?: string + messageVersion?: number + partVersion?: number + onContentRendered?: () => void + preferences: ReturnType["preferences"] + setDiffViewMode: ReturnType["setDiffViewMode"] + isDark: () => boolean + t: ReturnType["t"] + store: () => ReturnType + pendingPermission: () => { permission: PermissionRequestLike; active: boolean } | undefined + pendingQuestion: () => { request: QuestionRequest; active: boolean } | undefined + isPermissionActive: () => boolean + isQuestionActive: () => boolean + hasToolInput: () => boolean + isToolInputVisible: () => boolean + toolInput: () => Record | undefined + inputSectionExpanded: () => boolean + outputSectionExpanded: () => boolean + toggleInputSection: () => void + toggleOutputSection: () => void + toolCallRootEl: () => HTMLDivElement | undefined + scrollTopSnapshot: () => number + setScrollTopSnapshot: (next: number) => void +}) { + const messageVersionAccessor = createMemo(() => props.messageVersion) + const partVersionAccessor = createMemo(() => props.partVersion) + + const cacheContext = createMemo(() => ({ + toolCallId: props.toolCallIdentifier(), + messageId: props.messageId, + partId: props.toolCallMemo()?.id ?? null, + })) + + const cacheVersion = createMemo(() => { + if (typeof props.partVersion === "number") { + return String(props.partVersion) + } + if (typeof props.messageVersion === "number") { + return String(props.messageVersion) + } + return "noversion" + }) + + const createVariantCache = (variant: string | (() => string), version?: () => string) => + useGlobalCache({ + instanceId: () => props.instanceId, + sessionId: () => props.sessionId, + scope: TOOL_CALL_CACHE_SCOPE, + cacheId: () => { + const context = cacheContext() + const resolvedVariant = typeof variant === "function" ? variant() : variant + return makeRenderCacheKey(context.toolCallId || undefined, context.messageId, context.partId, resolvedVariant) + }, + version: () => (version ? version() : cacheVersion()), + }) + + const diffCache = createVariantCache("diff") + const permissionDiffCache = createVariantCache("permission-diff") + const ansiRunningCache = createVariantCache("ansi-running", () => "running") + const ansiFinalCache = createVariantCache("ansi-final") + + const permissionDetails = createMemo(() => props.pendingPermission()?.permission) + const questionDetails = createMemo(() => props.pendingQuestion()?.request) + + const activePermissionKey = createMemo(() => { + const permission = permissionDetails() + return permission && props.isPermissionActive() ? permission.id : "" + }) + + const activeQuestionKey = createMemo(() => { + const request = questionDetails() + return request && props.isQuestionActive() ? request.id : "" + }) + + const [permissionSubmitting, setPermissionSubmitting] = createSignal(false) + const [permissionError, setPermissionError] = createSignal(null) + + const [scrollContainer, setScrollContainer] = createSignal() + const [bottomSentinel, setBottomSentinel] = createSignal(null) + const [autoScroll, setAutoScroll] = createSignal(true) + const [bottomSentinelVisible, setBottomSentinelVisible] = createSignal(true) + + let scrollContainerRef: HTMLDivElement | undefined + let detachScrollIntentListeners: (() => void) | undefined + + let pendingScrollFrame: number | null = null + let pendingAnchorScroll: number | null = null + let userScrollIntentUntil = 0 + let lastKnownScrollTop = props.scrollTopSnapshot() + + function restoreScrollPosition(forceBottom = false) { + const container = scrollContainerRef + if (!container) return + if (forceBottom) { + container.scrollTop = container.scrollHeight + lastKnownScrollTop = container.scrollTop + props.setScrollTopSnapshot(lastKnownScrollTop) + } else { + container.scrollTop = lastKnownScrollTop + } + } + + const persistScrollSnapshot = (element?: HTMLElement | null) => { + if (!element) return + lastKnownScrollTop = element.scrollTop + props.setScrollTopSnapshot(lastKnownScrollTop) + } + + function markUserScrollIntent() { + const now = typeof performance !== "undefined" ? performance.now() : Date.now() + userScrollIntentUntil = now + TOOL_SCROLL_INTENT_WINDOW_MS + } + + function hasUserScrollIntent() { + const now = typeof performance !== "undefined" ? performance.now() : Date.now() + return now <= userScrollIntentUntil + } + + function attachScrollIntentListeners(element: HTMLDivElement) { + if (detachScrollIntentListeners) { + detachScrollIntentListeners() + detachScrollIntentListeners = undefined + } + const handlePointerIntent = () => markUserScrollIntent() + const handleKeyIntent = (event: KeyboardEvent) => { + if (TOOL_SCROLL_INTENT_KEYS.has(event.key)) { + markUserScrollIntent() + } + } + element.addEventListener("wheel", handlePointerIntent, { passive: true }) + element.addEventListener("pointerdown", handlePointerIntent) + element.addEventListener("touchstart", handlePointerIntent, { passive: true }) + element.addEventListener("keydown", handleKeyIntent) + detachScrollIntentListeners = () => { + element.removeEventListener("wheel", handlePointerIntent) + element.removeEventListener("pointerdown", handlePointerIntent) + element.removeEventListener("touchstart", handlePointerIntent) + element.removeEventListener("keydown", handleKeyIntent) + } + } + + function scheduleAnchorScroll(immediate = false) { + if (!autoScroll()) return + const sentinel = bottomSentinel() + const container = scrollContainerRef + if (!sentinel || !container) return + if (pendingAnchorScroll !== null) { + cancelAnimationFrame(pendingAnchorScroll) + pendingAnchorScroll = null + } + pendingAnchorScroll = requestAnimationFrame(() => { + pendingAnchorScroll = null + const containerRect = container.getBoundingClientRect() + const sentinelRect = sentinel.getBoundingClientRect() + const delta = sentinelRect.bottom - containerRect.bottom + TOOL_SCROLL_SENTINEL_MARGIN_PX + if (Math.abs(delta) > 1) { + container.scrollBy({ top: delta, behavior: immediate ? "auto" : "smooth" }) + } + lastKnownScrollTop = container.scrollTop + props.setScrollTopSnapshot(lastKnownScrollTop) + }) + } + + function handleScroll() { + const container = scrollContainer() + if (!container) return + if (pendingScrollFrame !== null) { + cancelAnimationFrame(pendingScrollFrame) + } + const isUserScroll = hasUserScrollIntent() + pendingScrollFrame = requestAnimationFrame(() => { + pendingScrollFrame = null + const atBottom = bottomSentinelVisible() + if (isUserScroll) { + if (atBottom) { + if (!autoScroll()) setAutoScroll(true) + } else if (autoScroll()) { + setAutoScroll(false) + } + } + }) + } + + const handleScrollEvent = (event: Event & { currentTarget: HTMLDivElement }) => { + handleScroll() + persistScrollSnapshot(event.currentTarget) + } + + const handleScrollRendered = () => { + requestAnimationFrame(() => { + restoreScrollPosition(autoScroll()) + scheduleAnchorScroll(true) + }) + } + + const initializeScrollContainer = (element: HTMLDivElement | null | undefined) => { + const next = element || undefined + if (next === scrollContainerRef) { + return + } + scrollContainerRef = next + setScrollContainer(scrollContainerRef) + if (scrollContainerRef) { + // Refresh our snapshot on mount (e.g. when remounting after collapse) + lastKnownScrollTop = props.scrollTopSnapshot() + restoreScrollPosition(autoScroll()) + } + } + + const scrollHelpers: ToolScrollHelpers = { + registerContainer: (element, options) => { + if (options?.disableTracking) return + initializeScrollContainer(element) + }, + handleScroll: handleScrollEvent, + renderSentinel: (options) => { + if (options?.disableTracking) return null + return