diff --git a/src/web-ui/src/flow_chat/components/modern/ExploreGroupRenderer.tsx b/src/web-ui/src/flow_chat/components/modern/ExploreGroupRenderer.tsx index f6a7f1d0..4cc5192f 100644 --- a/src/web-ui/src/flow_chat/components/modern/ExploreGroupRenderer.tsx +++ b/src/web-ui/src/flow_chat/components/modern/ExploreGroupRenderer.tsx @@ -3,7 +3,7 @@ * Renders merged explore-only rounds as a collapsible region. */ -import React, { useRef, useMemo, useCallback, useEffect, useLayoutEffect, useState } from 'react'; +import React, { useRef, useMemo, useCallback, useEffect } from 'react'; import { ChevronRight } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import type { FlowItem, FlowToolItem, FlowTextItem, FlowThinkingItem } from '../../types/flow-chat'; @@ -38,16 +38,11 @@ export const ExploreGroupRenderer: React.FC = ({ allItems, stats, isGroupStreaming, - isFollowedByCritical, isLastGroupInTurn } = data; - const previousGroupIdRef = useRef(groupId); - - const [hasAutoCollapsed, setHasAutoCollapsed] = useState(isFollowedByCritical); const { cardRootRef, applyExpandedState, - dispatchToolCardToggle, } = useToolCardHeightContract({ toolId: groupId, toolName: 'explore-group', @@ -58,41 +53,8 @@ export const ExploreGroupRenderer: React.FC = ({ ), }); - const userExpanded = exploreGroupStates?.get(groupId) ?? false; - const shouldAutoCollapse = hasAutoCollapsed; - - const isCollapsed = shouldAutoCollapse && !userExpanded; - - useLayoutEffect(() => { - if (previousGroupIdRef.current !== groupId) { - previousGroupIdRef.current = groupId; - setHasAutoCollapsed(isFollowedByCritical); - return; - } - - if (!isFollowedByCritical || hasAutoCollapsed) { - return; - } - - if (!userExpanded) { - applyExpandedState(true, false, () => { - setHasAutoCollapsed(true); - }, { - reason: 'auto', - }); - return; - } - - setHasAutoCollapsed(true); - dispatchToolCardToggle(); - }, [ - applyExpandedState, - dispatchToolCardToggle, - groupId, - hasAutoCollapsed, - isFollowedByCritical, - userExpanded, - ]); + const isExpanded = exploreGroupStates?.get(groupId) ?? false; + const isCollapsed = !isExpanded; // Auto-scroll to bottom during streaming. useEffect(() => { @@ -143,34 +105,10 @@ export const ExploreGroupRenderer: React.FC = ({ // Build class list. const className = [ 'explore-region', - shouldAutoCollapse ? 'explore-region--collapsible' : null, + 'explore-region--collapsible', isCollapsed ? 'explore-region--collapsed' : 'explore-region--expanded', isGroupStreaming ? 'explore-region--streaming' : null, ].filter(Boolean).join(' '); - - // Non-collapsible: just render content without header (streaming, no auto-collapse yet). - if (!shouldAutoCollapse) { - return ( -
-
- {allItems.map((item, idx) => ( - - ))} -
-
- ); - } - - // Collapsible: unified header + animated content wrapper. return (
; diff --git a/src/web-ui/src/flow_chat/components/modern/useExploreGroupState.ts b/src/web-ui/src/flow_chat/components/modern/useExploreGroupState.ts index f429068b..9db53a8b 100644 --- a/src/web-ui/src/flow_chat/components/modern/useExploreGroupState.ts +++ b/src/web-ui/src/flow_chat/components/modern/useExploreGroupState.ts @@ -8,6 +8,10 @@ import type { VirtualItem } from '../../store/modernFlowChatStore'; type ExploreGroupVirtualItem = Extract; interface UseExploreGroupStateResult { + /** + * Expanded/collapsed state for each explore group. + * key: groupId, value: true means expanded. + */ exploreGroupStates: Map; onExploreGroupToggle: (groupId: string) => void; onExpandAllInTurn: (turnId: string) => void; @@ -22,7 +26,8 @@ export function useExploreGroupState( const onExploreGroupToggle = useCallback((groupId: string) => { setExploreGroupStates(prev => { const next = new Map(prev); - next.set(groupId, !prev.get(groupId)); + const currentExpanded = prev.get(groupId) ?? false; + next.set(groupId, !currentExpanded); return next; }); }, []); diff --git a/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts b/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts index 2ab5b4d3..d0de3b7c 100644 --- a/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts +++ b/src/web-ui/src/flow_chat/store/modernFlowChatStore.ts @@ -6,7 +6,7 @@ import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; -import type { Session, DialogTurn, ModelRound, FlowItem, FlowToolItem, FlowTextItem } from '../types/flow-chat'; +import type { Session, DialogTurn, ModelRound, FlowItem, FlowToolItem } from '../types/flow-chat'; import { isCollapsibleTool, READ_TOOL_NAMES, SEARCH_TOOL_NAMES } from '../tool-cards'; /** @@ -29,11 +29,6 @@ export interface ExploreGroupData { stats: ExploreGroupStats; isGroupStreaming: boolean; isLastGroupInTurn: boolean; - /** - * When true, ExploreGroupRenderer auto-collapses after a critical follow-up round (e.g. Mermaid). - * Set false if the group contains assistant `text` items so narrative stays visible. - */ - isFollowedByCritical: boolean; } /** @@ -135,18 +130,6 @@ function computeRoundStats(round: ModelRound): { readCount: number; searchCount: return { readCount, searchCount, thinkingCount }; } -/** - * True when the merged explore group includes assistant markdown/text with real content. - * Auto-collapse on "followed by critical tool" must not hide this narrative. - */ -function exploreGroupHasNarrativeText(items: FlowItem[]): boolean { - return items.some( - (item) => - item.type === 'text' && - String((item as FlowTextItem).content || '').trim().length > 0 - ); -} - let cachedSession: Session | null = null; let cachedDialogTurnsRef: DialogTurn[] | null = null; let cachedVirtualItems: VirtualItem[] = []; @@ -254,25 +237,6 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] { if (group && group.startIndex === roundIndex) { const isLastGroup = groupIndex === tempGroups.length - 1; - - let isFollowedByCritical = false; - const nextRoundIndex = group.endIndex + 1; - if (nextRoundIndex < nonEmptyRounds.length) { - const nextRound = nonEmptyRounds[nextRoundIndex]; - - const hasAnyTool = nextRound.items.some(item => item.type === 'tool'); - - if (nextRound.isStreaming && !hasAnyTool) { - isFollowedByCritical = false; - } else { - isFollowedByCritical = !isExploreOnlyRound(nextRound); - } - } - - if (exploreGroupHasNarrativeText(group.allItems)) { - isFollowedByCritical = false; - } - const isGroupStreaming = group.rounds.some(r => r.isStreaming); items.push({ @@ -285,7 +249,6 @@ export function sessionToVirtualItems(session: Session | null): VirtualItem[] { stats: { readCount: group.readCount, searchCount: group.searchCount, thinkingCount: group.thinkingCount }, isGroupStreaming, isLastGroupInTurn: isLastGroup, - isFollowedByCritical, } });