feat(chat): align activity timing UI with hover-only end timestamps (#557)

* feat(chat): add shared activity timestamp formatter

* feat(chat): pass assistant created timestamp to message body

* feat(chat): render footer timestamp beside elapsed time

* feat(chat): pass timing metadata in reasoning text blocks

* feat(chat): propagate justification timing to timeline block

* feat(chat): show thinking duration with hover end time

* feat(chat): reveal tool end time on activity hover
This commit is contained in:
Nelson Pires
2026-02-28 20:16:31 +02:00
committed by GitHub
parent a1f01cf5bd
commit be413771dc
7 changed files with 164 additions and 19 deletions
@@ -4,6 +4,7 @@ import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
import { RiAiAgentLine, RiArrowDownSLine, RiArrowRightSLine, RiBookLine, RiExternalLinkLine, RiFileEditLine, RiFileList2Line, RiFileSearchLine, RiFileTextLine, RiFolder6Line, RiGitBranchLine, RiGlobalLine, RiListCheck2, RiListCheck3, RiMenuSearchLine, RiPencilLine, RiSurveyLine, RiTaskLine, RiTerminalBoxLine, RiToolsLine } from '@remixicon/react';
import { File as PierreFile, PatchDiff } from '@pierre/diffs/react';
import { cn } from '@/lib/utils';
import { formatTimestampForDisplay } from '../timeFormat';
import { SimpleMarkdownRenderer } from '../../MarkdownRenderer';
import { getToolMetadata, getLanguageFromExtension, isImageFile, getImageMimeType } from '@/lib/toolHelpers';
import type { ToolPart as ToolPartType, ToolState as ToolStateUnion } from '@opencode-ai/sdk/v2';
@@ -1419,6 +1420,15 @@ const ToolPart: React.FC<ToolPartProps> = ({
const effectiveTimeStart = isTaskTool ? (pinnedTaskTime.start ?? time?.start) : time?.start;
const effectiveTimeEnd = isTaskTool ? (pinnedTaskTime.end ?? time?.end) : time?.end;
const endedTimestampText = React.useMemo(() => {
if (typeof effectiveTimeEnd !== 'number' || !Number.isFinite(effectiveTimeEnd)) {
return null;
}
const formatted = formatTimestampForDisplay(effectiveTimeEnd);
return formatted.length > 0 ? formatted : null;
}, [effectiveTimeEnd]);
const taskOutputString = React.useMemo(() => {
return typeof stateWithData.output === 'string' ? stateWithData.output : undefined;
}, [stateWithData.output]);
@@ -1667,7 +1677,7 @@ const ToolPart: React.FC<ToolPartProps> = ({
</span>
)}
{typeof effectiveTimeStart === 'number' && (
<span className="text-muted-foreground/80 flex-shrink-0">
<span className="text-muted-foreground/80 flex-shrink-0 tabular-nums">
<LiveDuration
start={effectiveTimeStart}
end={typeof effectiveTimeEnd === 'number' ? effectiveTimeEnd : undefined}
@@ -1675,6 +1685,16 @@ const ToolPart: React.FC<ToolPartProps> = ({
/>
</span>
)}
{endedTimestampText ? (
<span
className={cn(
'text-muted-foreground/70 flex-shrink-0 tabular-nums transition-opacity duration-150',
'opacity-0 group-hover/tool:opacity-100'
)}
>
{endedTimestampText}
</span>
) : null}
</div>
</div>