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
@@ -28,6 +28,7 @@ import { useMessageTTS } from '@/hooks/useMessageTTS';
import { useConfigStore } from '@/stores/useConfigStore';
import { TextSelectionMenu } from './TextSelectionMenu';
import { copyTextToClipboard } from '@/lib/clipboard';
import { formatTimestampForDisplay } from './timeFormat';
type SubtaskPartLike = Part & {
type: 'subtask';
@@ -257,6 +258,7 @@ interface MessageBodyProps {
isMessageCompleted: boolean;
messageFinish?: string;
messageCompletedAt?: number;
messageCreatedAt?: number;
syntaxTheme: { [key: string]: React.CSSProperties };
@@ -508,6 +510,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
isMessageCompleted,
messageFinish,
messageCompletedAt,
messageCreatedAt,
syntaxTheme,
isMobile,
@@ -1123,6 +1126,16 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
return formatTurnDuration(messageCompletedAt - userCreatedAt);
}, [isLastAssistantInTurn, hasStopFinish, turnGroupingContext?.userMessageCreatedAt, messageCompletedAt]);
const footerTimestamp = React.useMemo(() => {
const timestamp = typeof messageCompletedAt === 'number' && messageCompletedAt > 0
? messageCompletedAt
: (typeof messageCreatedAt === 'number' && messageCreatedAt > 0 ? messageCreatedAt : null);
if (timestamp === null) return null;
const formatted = formatTimestampForDisplay(timestamp);
return formatted.length > 0 ? formatted : null;
}, [messageCompletedAt, messageCreatedAt]);
const footerButtons = (
<>
{onCopyMessage && (
@@ -1259,12 +1272,17 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
<div className="flex items-center gap-1.5">
{footerButtons}
</div>
{turnDurationText ? (
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
<RiHourglassLine className="h-3.5 w-3.5" />
{turnDurationText}
</span>
) : null}
<div className="flex items-center gap-1.5">
{turnDurationText ? (
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
<RiHourglassLine className="h-3.5 w-3.5" />
{turnDurationText}
</span>
) : null}
{footerTimestamp ? (
<span className="text-sm text-muted-foreground/60 tabular-nums">{footerTimestamp}</span>
) : null}
</div>
</div>
)}
</div>
@@ -1277,12 +1295,17 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
<div className="flex items-center gap-1.5">
{footerButtons}
</div>
{turnDurationText ? (
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
<RiHourglassLine className="h-3.5 w-3.5" />
{turnDurationText}
</span>
) : null}
<div className="flex items-center gap-1.5">
{turnDurationText ? (
<span className="text-sm text-muted-foreground/60 tabular-nums flex items-center gap-1">
<RiHourglassLine className="h-3.5 w-3.5" />
{turnDurationText}
</span>
) : null}
{footerTimestamp ? (
<span className="text-sm text-muted-foreground/60 tabular-nums">{footerTimestamp}</span>
) : null}
</div>
</div>
)}