Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/features/message/MessageRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -513,9 +514,13 @@ const AssistantMessageView = memo(function AssistantMessageView({
{messageError && <MessageErrorView error={messageError} />}

{(showTurnDurationFooter || showCompletedAtFooter) && (
<div className="flex items-center gap-3 text-[length:var(--fs-xxs)] text-text-500 pl-5 py-0.5">
{showTurnDurationFooter && <span>{formatDuration(turnDuration!)} total</span>}
{showCompletedAtFooter && <span>{formatCompletedAt(completed!, completedAtFormat)}</span>}
<div className="flex items-center gap-3 py-0.5 text-[length:var(--fs-xxs)] text-text-500">
{showTurnDurationFooter && (
<span>{t('stepFinish.totalDuration', { duration: formatDuration(turnDuration!) })}</span>
)}
{showCompletedAtFooter && (
<span title={formatDetailedDateTime(completed!)}>{formatCompletedAt(completed!, completedAtFormat)}</span>
)}
</div>
)}

Expand Down
12 changes: 10 additions & 2 deletions src/features/message/parts/StepFinishPartView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,7 +74,9 @@ export const StepFinishPartView = memo(function StepFinishPartView({
{show.turnDuration && turnDuration != null && turnDuration > 0 && (
<span>{t('stepFinish.totalDuration', { duration: formatDuration(turnDuration) })}</span>
)}
{show.completedAt && completedAt != null && <span>{formatCompletedAt(completedAt, completedAtFormat)}</span>}
{show.completedAt && completedAt != null && (
<span title={formatDetailedDateTime(completedAt)}>{formatCompletedAt(completedAt, completedAtFormat)}</span>
)}
</div>
)
})
7 changes: 6 additions & 1 deletion src/utils/formatUtils.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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')
Expand Down
12 changes: 12 additions & 0 deletions src/utils/formatUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading