feat: improve message body rendering with enhanced expansion handling and animation triggers

This commit is contained in:
Bohdan Triapitsyn
2025-12-17 02:38:33 +02:00
parent 6adb53fe61
commit 38fb154ffe
2 changed files with 22 additions and 13 deletions
@@ -44,7 +44,6 @@ const useMigrationTimer = (
React.useEffect(() => { React.useEffect(() => {
if (!turnGroupingContext) return; if (!turnGroupingContext) return;
if (!turnGroupingContext.isWorking) return; if (!turnGroupingContext.isWorking) return;
if (turnGroupingContext.isGroupExpanded) return;
if (!hasPreviewableParts) return; if (!hasPreviewableParts) return;
if (timerStartedRef.current) return; if (timerStartedRef.current) return;
@@ -71,7 +70,7 @@ const useMigrationTimer = (
React.useEffect(() => { React.useEffect(() => {
if (!turnGroupingContext) return; if (!turnGroupingContext) return;
if (!turnGroupingContext.isWorking || turnGroupingContext.isGroupExpanded || !hasPreviewableParts) { if (!turnGroupingContext.isWorking || !hasPreviewableParts) {
if (timerRef.current) { if (timerRef.current) {
window.clearTimeout(timerRef.current); window.clearTimeout(timerRef.current);
timerRef.current = null; timerRef.current = null;
@@ -611,7 +610,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
const previewableActivityPartsForMessage = React.useMemo(() => { const previewableActivityPartsForMessage = React.useMemo(() => {
if (!turnGroupingContext) return []; if (!turnGroupingContext) return [];
if (!shouldShowActivityGroup) return []; if (!shouldShowActivityGroup) return [];
if (!turnGroupingContext.isWorking || turnGroupingContext.isGroupExpanded) { if (!turnGroupingContext.isWorking) {
return []; return [];
} }
@@ -99,30 +99,38 @@ const ProgressiveGroup: React.FC<ProgressiveGroupProps> = ({
}) => { }) => {
const previousExpandedRef = React.useRef<boolean | undefined>(isExpanded); const previousExpandedRef = React.useRef<boolean | undefined>(isExpanded);
// Track expansion count to force re-mount of items when group expands from collapsed
const [expansionKey, setExpansionKey] = React.useState(0);
React.useEffect(() => { React.useEffect(() => {
if (previousExpandedRef.current === isExpanded) return; if (previousExpandedRef.current === isExpanded) return;
const wasCollapsed = previousExpandedRef.current === false;
previousExpandedRef.current = isExpanded; previousExpandedRef.current = isExpanded;
onContentChange?.('structural'); onContentChange?.('structural');
// Increment key when expanding to trigger fresh animations
if (isExpanded && wasCollapsed) {
setExpansionKey((k) => k + 1);
}
}, [isExpanded, onContentChange]); }, [isExpanded, onContentChange]);
const displayParts = React.useMemo(() => { const displayParts = React.useMemo(() => {
if (!isWorking) { if (!isWorking) {
return sortPartsByTime(parts);
}
if (isExpanded) {
return sortPartsByTime(parts); return sortPartsByTime(parts);
} }
// While turn is working, only show parts that have been "previewed".
// Collapsed mode previews them in-chat first, then migrates into Activity.
// Summary/Detailed modes skip in-chat preview, but still use the same migration gate.
return sortPartsByTime( return sortPartsByTime(
parts.filter((activity) => { parts.filter((activity) => {
const partId = activity.part.id; const partId = activity.part.id;
return partId && previewedPartIds.has(activity.id); return partId && previewedPartIds.has(activity.id);
}) })
); );
}, [parts, isWorking, isExpanded, previewedPartIds]); }, [parts, isWorking, previewedPartIds]);
const summary = getGroupSummary(displayParts); const summary = getGroupSummary(displayParts);
const toolConnections = getToolConnections(displayParts); const toolConnections = getToolConnections(displayParts);
@@ -206,10 +214,12 @@ const ProgressiveGroup: React.FC<ProgressiveGroupProps> = ({
const partId = activity.part.id || `group-part-${index}`; const partId = activity.part.id || `group-part-${index}`;
const connection = toolConnections[partId]; const connection = toolConnections[partId];
const animationKey = `${partId}-exp${expansionKey}`;
switch (activity.kind) { switch (activity.kind) {
case 'tool': case 'tool':
return ( return (
<FadeInOnReveal key={partId}> <FadeInOnReveal key={animationKey}>
<ToolPart <ToolPart
part={activity.part as ToolPartType} part={activity.part as ToolPartType}
isExpanded={expandedTools.has(partId)} isExpanded={expandedTools.has(partId)}
@@ -225,7 +235,7 @@ const ProgressiveGroup: React.FC<ProgressiveGroupProps> = ({
case 'reasoning': case 'reasoning':
return ( return (
<FadeInOnReveal key={partId}> <FadeInOnReveal key={animationKey}>
<ReasoningPart <ReasoningPart
part={activity.part} part={activity.part}
messageId={activity.messageId} messageId={activity.messageId}
@@ -236,7 +246,7 @@ const ProgressiveGroup: React.FC<ProgressiveGroupProps> = ({
case 'justification': case 'justification':
return ( return (
<FadeInOnReveal key={partId}> <FadeInOnReveal key={animationKey}>
<JustificationBlock <JustificationBlock
part={activity.part} part={activity.part}
messageId={activity.messageId} messageId={activity.messageId}