From 9ca1ab624ce416e8e28ca6020d94813cc7976dad Mon Sep 17 00:00:00 2001 From: dingyi Date: Sun, 17 May 2026 08:14:16 +0800 Subject: [PATCH] fix: show detailed completion time on hover --- src/features/message/MessageRenderer.tsx | 13 +++++++++---- src/features/message/parts/StepFinishPartView.tsx | 12 ++++++++++-- src/utils/formatUtils.test.ts | 7 ++++++- src/utils/formatUtils.ts | 12 ++++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/features/message/MessageRenderer.tsx b/src/features/message/MessageRenderer.tsx index 92a26d62..0db992f9 100644 --- a/src/features/message/MessageRenderer.tsx +++ b/src/features/message/MessageRenderer.tsx @@ -37,7 +37,7 @@ import type { AssistantMessageInfo, } from '../../types/message' import { isToolPart, isVisibleReasoningPart, isVisibleTextPart } from '../../types/message' -import { formatDuration, formatCompletedAt } from '../../utils/formatUtils' +import { formatDuration, formatCompletedAt, formatDetailedDateTime } from '../../utils/formatUtils' interface MessageRendererProps { message: Message @@ -376,6 +376,7 @@ const AssistantMessageView = memo(function AssistantMessageView({ forkMessageId?: string onEnsureParts?: (messageId: string) => void }) { + const { t } = useTranslation('message') const { parts, isStreaming, info } = message const { stepFinishDisplay, completedAtFormat } = useTheme() @@ -513,9 +514,13 @@ const AssistantMessageView = memo(function AssistantMessageView({ {messageError && } {(showTurnDurationFooter || showCompletedAtFooter) && ( -
- {showTurnDurationFooter && {formatDuration(turnDuration!)} total} - {showCompletedAtFooter && {formatCompletedAt(completed!, completedAtFormat)}} +
+ {showTurnDurationFooter && ( + {t('stepFinish.totalDuration', { duration: formatDuration(turnDuration!) })} + )} + {showCompletedAtFooter && ( + {formatCompletedAt(completed!, completedAtFormat)} + )}
)} diff --git a/src/features/message/parts/StepFinishPartView.tsx b/src/features/message/parts/StepFinishPartView.tsx index a8aeb611..642937ec 100644 --- a/src/features/message/parts/StepFinishPartView.tsx +++ b/src/features/message/parts/StepFinishPartView.tsx @@ -2,7 +2,13 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' import { useTheme } from '../../../hooks/useTheme' import type { StepFinishPart } from '../../../types/message' -import { formatNumber, formatCost, formatDuration, formatCompletedAt } from '../../../utils/formatUtils' +import { + formatNumber, + formatCost, + formatDuration, + formatCompletedAt, + formatDetailedDateTime, +} from '../../../utils/formatUtils' interface StepFinishPartViewProps { part: StepFinishPart @@ -68,7 +74,9 @@ export const StepFinishPartView = memo(function StepFinishPartView({ {show.turnDuration && turnDuration != null && turnDuration > 0 && ( {t('stepFinish.totalDuration', { duration: formatDuration(turnDuration) })} )} - {show.completedAt && completedAt != null && {formatCompletedAt(completedAt, completedAtFormat)}} + {show.completedAt && completedAt != null && ( + {formatCompletedAt(completedAt, completedAtFormat)} + )}
) }) diff --git a/src/utils/formatUtils.test.ts b/src/utils/formatUtils.test.ts index c8d6e5c4..e619a03c 100644 --- a/src/utils/formatUtils.test.ts +++ b/src/utils/formatUtils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { formatCompletedAt, formatDateTime, formatDuration, formatTime } from './formatUtils' +import { formatCompletedAt, formatDateTime, formatDetailedDateTime, formatDuration, formatTime } from './formatUtils' describe('formatDuration', () => { it('formats milliseconds and seconds', () => { @@ -34,6 +34,11 @@ describe('completed time formatting', () => { expect(formatDateTime(ms)).toBe('2026-04-17 10:30') }) + it('formats detailed local date and time with seconds', () => { + const ms = new Date(2026, 3, 17, 10, 30, 45).getTime() + expect(formatDetailedDateTime(ms)).toBe('2026-04-17 10:30:45') + }) + it('switches output based on completedAt format', () => { const ms = new Date(2026, 3, 17, 10, 30, 45).getTime() expect(formatCompletedAt(ms, 'time')).toBe('10:30') diff --git a/src/utils/formatUtils.ts b/src/utils/formatUtils.ts index 8fdb0e35..7c8bd950 100644 --- a/src/utils/formatUtils.ts +++ b/src/utils/formatUtils.ts @@ -54,6 +54,18 @@ export function formatDateTime(ms: number): string { return `${y}-${mon}-${d} ${formatTime(ms)}` } +/** Format a timestamp (ms) to local YYYY-MM-DD HH:MM:SS string for detail tooltips */ +export function formatDetailedDateTime(ms: number): string { + const date = new Date(ms) + const y = date.getFullYear().toString().padStart(4, '0') + const mon = (date.getMonth() + 1).toString().padStart(2, '0') + const d = date.getDate().toString().padStart(2, '0') + const h = date.getHours().toString().padStart(2, '0') + const m = date.getMinutes().toString().padStart(2, '0') + const s = date.getSeconds().toString().padStart(2, '0') + return `${y}-${mon}-${d} ${h}:${m}:${s}` +} + /** Format completed time according to the selected display mode */ export function formatCompletedAt(ms: number, format: CompletedAtFormat): string { return format === 'dateTime' ? formatDateTime(ms) : formatTime(ms)