fix(chat): show in-flight tools immediately and keep collapsed activity live (#563)

* fix(chat): render active tools before completion

* fix(chat): preserve running activity in collapsed view

* fix(chat): live-update duration for active tools

* fix: show live tool activity without timer resets

Show active tool rows before completion
Keep live duration stable during streaming updates

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Nelson Pires
2026-03-03 04:00:46 +02:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 16439d343a
commit c9bcf79ca7
3 changed files with 65 additions and 35 deletions
@@ -617,6 +617,12 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
});
}, [toolParts]);
const isActiveTool = React.useCallback((toolPart: ToolPartType): boolean => {
const state = (toolPart as Record<string, unknown>).state as Record<string, unknown> | undefined ?? {};
const status = state?.status;
return status === 'pending' || status === 'running' || status === 'started';
}, []);
const isToolFinalized = React.useCallback((toolPart: ToolPartType) => {
const state = (toolPart as Record<string, unknown>).state as Record<string, unknown> | undefined ?? {};
const status = state?.status;
@@ -635,6 +641,10 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
return true;
}, []);
const shouldShowTool = React.useCallback((toolPart: ToolPartType): boolean => {
return isActiveTool(toolPart) || isToolFinalized(toolPart);
}, [isActiveTool, isToolFinalized]);
const allToolsFinalized = React.useMemo(() => {
if (toolParts.length === 0) {
return true;
@@ -881,10 +891,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
if (isActivityStandaloneTool(toolPart.tool)) {
return false;
}
if (shouldHoldTools) {
return false;
}
return isToolFinalized(toolPart);
return shouldShowTool(toolPart);
});
displayableTools.forEach((toolPart, index) => {
@@ -895,7 +902,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
});
return connections;
}, [toolParts, shouldHoldTools, isToolFinalized]);
}, [toolParts, shouldShowTool]);
const activityPartsForTurn = React.useMemo(() => {
return turnGroupingContext?.activityParts ?? [];
@@ -1063,22 +1070,21 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
let endTime: number | null = null;
let element: React.ReactNode | null = null;
if (!shouldShowActivityGroup) {
if (activity.kind === 'tool') {
const toolPart = part as ToolPartType;
if (!shouldShowActivityGroup) {
if (activity.kind === 'tool') {
const toolPart = part as ToolPartType;
if (isActivityStandaloneTool(toolPart.tool)) {
return;
}
if (isActivityStandaloneTool(toolPart.tool)) {
return;
}
const toolState = (toolPart as { state?: { time?: { end?: number | null | undefined } | null | undefined } | null | undefined }).state;
const time = toolState?.time;
const isFinalized = isToolFinalized(toolPart);
const shouldShowTool = !shouldHoldTools && isFinalized;
const toolState = (toolPart as { state?: { time?: { end?: number | null | undefined } | null | undefined } | null | undefined }).state;
const time = toolState?.time;
const isFinalized = isToolFinalized(toolPart);
if (!shouldShowTool) {
return;
}
if (!shouldShowTool(toolPart)) {
return;
}
const connection = toolConnections[toolPart.id];
@@ -1158,7 +1164,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
onContentChange,
onShowPopup,
onToggleTool,
shouldHoldTools,
shouldShowTool,
shouldShowActivityGroup,
showReasoningTraces,
syntaxTheme,