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)