mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
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
This commit is contained in:
@@ -86,6 +86,12 @@
|
||||
:on-open-media="onOpenMedia"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="text-xs text-muted-foreground/80 mt-1"
|
||||
:title="fullTimestamp"
|
||||
>
|
||||
{{ relativeTimestamp }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Assistant message blocks -->
|
||||
@@ -148,6 +154,12 @@
|
||||
/>
|
||||
{{ $t('chat.thinking') }}
|
||||
</div>
|
||||
<p
|
||||
class="text-xs text-muted-foreground/80 mt-1"
|
||||
:title="fullTimestamp"
|
||||
>
|
||||
{{ relativeTimestamp }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,6 +188,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { formatRelativeTime, formatDateTime } from '@/utils/date-time'
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@memoh/ui'
|
||||
import MarkdownRender, { enableKatex, enableMermaid } from 'markstream-vue'
|
||||
import ThinkingBlock from './thinking-block.vue'
|
||||
@@ -253,4 +266,11 @@ const contentClass = computed(() => {
|
||||
if (props.message.role === 'user') return 'max-w-[80%]'
|
||||
return 'flex-1 max-w-full'
|
||||
})
|
||||
|
||||
const relativeTimestamp = computed(() =>
|
||||
formatRelativeTime(props.message.timestamp),
|
||||
)
|
||||
const fullTimestamp = computed(() =>
|
||||
formatDateTime(props.message.timestamp.toISOString()),
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user