From 567a1f37615e205b16d8da46a4807f834552f18c Mon Sep 17 00:00:00 2001 From: RoomWithOutRoof <166608075+Jah-yee@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:09:01 +0800 Subject: [PATCH] feat(chat): show message timestamp (relative time, full datetime on hover) (#157) - Add formatRelativeTime() to date-time utils (Intl.RelativeTimeFormat, locale-aware) - Display relative time under each message in message-item.vue - Show full datetime in title attribute on hover Made-with: Cursor --- .../pages/chat/components/message-item.vue | 20 +++++++++++++ packages/web/src/utils/date-time.ts | 28 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/web/src/pages/chat/components/message-item.vue b/packages/web/src/pages/chat/components/message-item.vue index 36e4ec41..e9a71697 100644 --- a/packages/web/src/pages/chat/components/message-item.vue +++ b/packages/web/src/pages/chat/components/message-item.vue @@ -86,6 +86,12 @@ :on-open-media="onOpenMedia" /> +
+ {{ relativeTimestamp }} +
@@ -148,6 +154,12 @@ /> {{ $t('chat.thinking') }} ++ {{ relativeTimestamp }} +
@@ -176,6 +188,7 @@ diff --git a/packages/web/src/utils/date-time.ts b/packages/web/src/utils/date-time.ts index 1cab1f14..9fe2b314 100644 --- a/packages/web/src/utils/date-time.ts +++ b/packages/web/src/utils/date-time.ts @@ -53,3 +53,31 @@ export function formatDateTimeSeconds( const seconds = String(parsed.getSeconds()).padStart(2, '0') return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` } + +/** + * Returns a locale-aware relative time string such as "3 minutes ago" or + * "in 2 days". Falls back to `toLocaleDateString()` for dates older than a + * week. Accepts either an ISO string or a `Date` object. + * + * Uses `Intl.RelativeTimeFormat` so the output language follows the browser + * locale automatically — no hardcoded English strings. + */ +export function formatRelativeTime( + value: string | Date | null | undefined, + options: FormatDateOptions = {}, +): string { + if (!value) return options.fallback ?? '' + const date = value instanceof Date ? value : parseDate(value) + if (!date) return options.fallback ?? '' + + const diffMs = date.getTime() - Date.now() + const absDiffSec = Math.abs(diffMs) / 1000 + const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }) + + if (absDiffSec < 60) return rtf.format(Math.round(diffMs / 1000), 'second') + if (absDiffSec < 3_600) return rtf.format(Math.round(diffMs / 60_000), 'minute') + if (absDiffSec < 86_400) return rtf.format(Math.round(diffMs / 3_600_000), 'hour') + if (absDiffSec < 604_800) return rtf.format(Math.round(diffMs / 86_400_000), 'day') + + return date.toLocaleDateString() +}