perf: migrate chat rendering to virtua (#1651)
* refactor: migrate chat history virtualization to virtua * refactor: render loaded chat history directly * refactor: finish virtua migration * perf: defer tool body rendering * perf: queue deferred tool body mounts * perf: quiet and defer markdown file probes * perf: defer markdown code highlighting * perf: stabilize markdown plugin lists * perf: defer mermaid markdown rendering * perf: delay markdown file reference annotation * perf: attach markdown table listeners on demand * perf: trim markdown render overhead
This commit is contained in:
committed by
GitHub
parent
e372c8d8cb
commit
a45376d585
@@ -1,6 +1,5 @@
|
||||
|
||||
import React from 'react';
|
||||
import type { AnimationPlaybackControls } from 'motion';
|
||||
import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
|
||||
import { PatchDiff } from '@pierre/diffs/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -201,29 +200,59 @@ const LiveDuration: React.FC<{ start: number; end?: number; active: boolean }> =
|
||||
return <>{formatDuration(start, end, now)}</>;
|
||||
};
|
||||
|
||||
const EXPANDED_CONTENT_TRANSITION_MS = 0;
|
||||
const deferredToolBodyMounts: Array<{ active: boolean; fn: () => void }> = [];
|
||||
let deferredToolBodyFrame: number | undefined;
|
||||
|
||||
const useAnimatedExpandedContent = (isExpanded: boolean) => {
|
||||
const [shouldRender, setShouldRender] = React.useState(isExpanded);
|
||||
const flushDeferredToolBodyMounts = () => {
|
||||
while (deferredToolBodyMounts.length > 0) {
|
||||
const item = deferredToolBodyMounts.pop();
|
||||
if (!item) {
|
||||
break;
|
||||
}
|
||||
if (item.active) {
|
||||
item.fn();
|
||||
deferredToolBodyFrame = deferredToolBodyMounts.length > 0
|
||||
? window.requestAnimationFrame(flushDeferredToolBodyMounts)
|
||||
: undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deferredToolBodyFrame = undefined;
|
||||
};
|
||||
|
||||
const scheduleDeferredToolBodyMount = (fn: () => void) => {
|
||||
if (typeof window === 'undefined') {
|
||||
fn();
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
const item = { active: true, fn };
|
||||
deferredToolBodyMounts.push(item);
|
||||
|
||||
if (deferredToolBodyFrame === undefined) {
|
||||
deferredToolBodyFrame = window.requestAnimationFrame(() => {
|
||||
deferredToolBodyFrame = window.requestAnimationFrame(flushDeferredToolBodyMounts);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
item.active = false;
|
||||
};
|
||||
};
|
||||
|
||||
const useDeferredExpandedContent = (isExpanded: boolean) => {
|
||||
const [shouldRender, setShouldRender] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
setShouldRender(isExpanded);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
setShouldRender(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = window.setTimeout(() => {
|
||||
if (!isExpanded) {
|
||||
setShouldRender(false);
|
||||
}, EXPANDED_CONTENT_TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timer);
|
||||
};
|
||||
return scheduleDeferredToolBodyMount(() => {
|
||||
setShouldRender(true);
|
||||
});
|
||||
}, [isExpanded]);
|
||||
|
||||
return shouldRender;
|
||||
@@ -1573,6 +1602,7 @@ interface ToolExpandedContentProps {
|
||||
state: ToolStateUnion;
|
||||
syntaxTheme: { [key: string]: React.CSSProperties };
|
||||
currentDirectory: string;
|
||||
isExpanded: boolean;
|
||||
onShowPopup?: (content: ToolPopupContent) => void;
|
||||
}
|
||||
|
||||
@@ -1581,6 +1611,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = React.memo(({
|
||||
state,
|
||||
syntaxTheme,
|
||||
currentDirectory,
|
||||
isExpanded,
|
||||
onShowPopup,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
@@ -1873,7 +1904,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = React.memo(({
|
||||
</blockquote>
|
||||
),
|
||||
{
|
||||
maxHeightClass: 'max-h-60',
|
||||
maxHeightClass: isWriteLikeTool && writeLikeInputPatch && isExpanded ? 'max-h-[50vh]' : 'max-h-60',
|
||||
className: part.tool === 'bash' ? 'tool-input-surface p-0 rounded-none' : 'tool-input-surface',
|
||||
}
|
||||
)}
|
||||
@@ -1979,8 +2010,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
const onContentChangeRef = React.useRef(onContentChange);
|
||||
onContentChangeRef.current = onContentChange;
|
||||
const expandedContentRef = React.useRef<HTMLDivElement>(null);
|
||||
const expandedContentAnimationRef = React.useRef<AnimationPlaybackControls | null>(null);
|
||||
const expandedContentMountedRef = React.useRef(false);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (isTaskTool) {
|
||||
@@ -1992,9 +2021,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
expandedContentMountedRef.current = true;
|
||||
expandedContentAnimationRef.current?.stop();
|
||||
expandedContentAnimationRef.current = null;
|
||||
element.style.height = isExpanded ? 'auto' : '0px';
|
||||
element.style.overflow = isExpanded ? 'visible' : 'hidden';
|
||||
|
||||
@@ -2003,13 +2029,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
}
|
||||
}, [isExpanded, isTaskTool, shouldNotifyStructuralChange]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
expandedContentAnimationRef.current?.stop();
|
||||
expandedContentAnimationRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const stateWithData = state as ToolStateWithMetadata;
|
||||
const metadata = stateWithData.metadata;
|
||||
const partMetadata = (part as unknown as { metadata?: unknown }).metadata;
|
||||
@@ -2662,7 +2681,8 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
|
||||
const iconStyle = !isTaskTool && isError ? TOOL_ERROR_ICON_STYLE : TOOL_NORMAL_ICON_STYLE;
|
||||
const titleStyle = !isTaskTool && isError ? TOOL_ERROR_TITLE_STYLE : TOOL_NORMAL_TITLE_STYLE;
|
||||
const shouldRenderExpandedContent = useAnimatedExpandedContent(isExpanded);
|
||||
const shouldRenderTaskSummary = useDeferredExpandedContent(isTaskTool && (taskSummaryEntries.length > 0 || isActive || shouldTreatAsFinalized || !!taskSessionId));
|
||||
const shouldRenderExpandedContent = useDeferredExpandedContent(!isTaskTool && isExpanded);
|
||||
|
||||
if (!shouldTreatAsFinalized && !isActive && !isTaskTool) {
|
||||
return null;
|
||||
@@ -2795,7 +2815,7 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
</div>
|
||||
|
||||
{}
|
||||
{isTaskTool && (taskSummaryEntries.length > 0 || isActive || shouldTreatAsFinalized || taskSessionId) ? (
|
||||
{shouldRenderTaskSummary ? (
|
||||
<TaskToolSummary
|
||||
entries={taskSummaryEntries}
|
||||
isExpanded={isExpanded}
|
||||
@@ -2822,10 +2842,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
{shouldRenderExpandedContent ? (
|
||||
<div
|
||||
className="relative ml-2 pl-3"
|
||||
style={{
|
||||
opacity: isExpanded ? 1 : 0,
|
||||
transform: isExpanded ? 'translateY(0)' : 'translateY(-4px)',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -2837,6 +2853,7 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
||||
state={state}
|
||||
syntaxTheme={syntaxTheme}
|
||||
currentDirectory={currentDirectory}
|
||||
isExpanded={isExpanded}
|
||||
onShowPopup={onShowPopup}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user