From 984aa69acfb81b8cf51a700d31e02208d2f473b6 Mon Sep 17 00:00:00 2001 From: Willem Jiang Date: Sun, 19 Oct 2025 19:24:57 +0800 Subject: [PATCH] fix: optimize animations to prevent browser freeze with many research steps (#630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #570 where browser freezes when research plan has 8+ steps. Performance optimizations: - Add animation throttling: only animate first 10 activity items - Reduce animation durations (0.4s → 0.3s for activities, 0.2s → 0.15s for results) - Remove scale animations (GPU-intensive) from search results - Limit displayed results (20 pages, 10 images max) - Add conditional animations based on item index - Cap animation delays to prevent excessive staggering - Add React.memo to ActivityMessage and ActivityListItem components These changes significantly improve performance when rendering multiple research steps while maintaining visual appeal for smaller lists. --- .../components/research-activities-block.tsx | 157 ++++++++++-------- 1 file changed, 92 insertions(+), 65 deletions(-) diff --git a/web/src/app/chat/components/research-activities-block.tsx b/web/src/app/chat/components/research-activities-block.tsx index 3e78022..2e83196 100644 --- a/web/src/app/chat/components/research-activities-block.tsx +++ b/web/src/app/chat/components/research-activities-block.tsx @@ -7,7 +7,7 @@ import { LRUCache } from "lru-cache"; import { BookOpenText, FileText, PencilRuler, Search } from "lucide-react"; import { useTranslations } from "next-intl"; import { useTheme } from "next-themes"; -import { useMemo } from "react"; +import React, { useMemo } from "react"; import SyntaxHighlighter from "react-syntax-highlighter"; import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs"; import { dark } from "react-syntax-highlighter/dist/esm/styles/prism"; @@ -31,6 +31,10 @@ import { useMessage, useStore } from "~/core/store"; import { parseJSON } from "~/core/utils"; import { cn } from "~/lib/utils"; +// Performance optimization constants +const MAX_ANIMATED_ITEMS = 10; // Only animate first 10 items +const ANIMATION_DELAY_MULTIPLIER = 0.05; // Reduced delay between animations + export function ResearchActivitiesBlock({ className, researchId, @@ -42,27 +46,36 @@ export function ResearchActivitiesBlock({ state.researchActivityIds.get(researchId), )!; const ongoing = useStore((state) => state.ongoingResearchId === researchId); + return ( <> {ongoing && } @@ -70,7 +83,7 @@ export function ResearchActivitiesBlock({ ); } -function ActivityMessage({ messageId }: { messageId: string }) { +const ActivityMessage = React.memo(({ messageId }: { messageId: string }) => { const message = useMessage(messageId); if (message?.agent && message.content) { if (message.agent !== "reporter" && message.agent !== "planner") { @@ -84,9 +97,10 @@ function ActivityMessage({ messageId }: { messageId: string }) { } } return null; -} +}); +ActivityMessage.displayName = "ActivityMessage"; -function ActivityListItem({ messageId }: { messageId: string }) { +const ActivityListItem = React.memo(({ messageId }: { messageId: string }) => { const message = useMessage(messageId); if (message) { if (!message.isStreaming && message.toolCalls?.length) { @@ -109,7 +123,8 @@ function ActivityListItem({ messageId }: { messageId: string }) { } } return null; -} +}); +ActivityListItem.displayName = "ActivityListItem"; const __pageCache = new LRUCache({ max: 100 }); type SearchResult = @@ -187,38 +202,46 @@ function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) { ))} {pageResults .filter((result) => result.type === "page") - .map((searchResult, i) => ( - - - - {searchResult.title} - - - ))} - {imageResults.map((searchResult, i) => ( - { + const shouldAnimate = i < 6; // Only animate first 6 results + return ( + + + + {searchResult.title} + + + ); + })} + {imageResults + .slice(0, 10) // Limit displayed images for performance + .map((searchResult, i) => { + const shouldAnimate = i < 4; // Only animate first 4 images + return ( + - ))} + ); + })} )} @@ -263,10 +287,10 @@ function CrawlToolCall({ toolCall }: { toolCall: ToolCallRuntime }) {
    @@ -320,22 +344,25 @@ function RetrieverToolCall({ toolCall }: { toolCall: ToolCallRuntime }) { /> ))} - {documents?.map((doc, i) => ( - - - {doc.title} (chunk-{i},size-{doc.content.length}) - - ))} + {documents?.map((doc, i) => { + const shouldAnimate = i < 4; // Only animate first 4 documents + return ( + + + {doc.title} (chunk-{i},size-{doc.content.length}) + + ); + })}
)}