初始化模版工程
This commit is contained in:
105
components/nova-sdk/message-list/message-item/MessageFooter.tsx
Normal file
105
components/nova-sdk/message-list/message-item/MessageFooter.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { cn } from '@/utils/cn'
|
||||
|
||||
export interface MessageFooterProps {
|
||||
shouldShowTimestamp: boolean
|
||||
shouldShowCopyButton: boolean
|
||||
timestamp?: string | number
|
||||
isUserInput: boolean
|
||||
isSummary: boolean
|
||||
showSystemAttachments: boolean
|
||||
showUserFile: boolean
|
||||
onCopyMessage: () => void
|
||||
}
|
||||
|
||||
function parseTimestamp(timestamp: string | number): Date {
|
||||
if (typeof timestamp === 'number') return new Date(timestamp)
|
||||
const num = Number(timestamp)
|
||||
if (!isNaN(num)) return new Date(num)
|
||||
// 截断超过3位的小数秒(Python 微秒精度 → JS 毫秒精度)
|
||||
const normalized = timestamp.replace(/(\.\d{3})\d+/, '$1')
|
||||
return new Date(normalized)
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp: string | number): string {
|
||||
const date = parseTimestamp(timestamp)
|
||||
|
||||
const now = new Date()
|
||||
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const dateStart = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
const diffDays = Math.round(
|
||||
(todayStart.getTime() - dateStart.getTime()) / (1000 * 60 * 60 * 24),
|
||||
)
|
||||
|
||||
const hhmm = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
|
||||
if (diffDays === 0) {
|
||||
return hhmm
|
||||
} else if (diffDays >= 1) {
|
||||
return `${date.getMonth() + 1}/${date.getDate()} ${hhmm}`
|
||||
} else if (now.getFullYear() === date.getFullYear()) {
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`
|
||||
} else {
|
||||
return `${date.getFullYear()}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息底部 - 显示时间戳和复制按钮,对应 next-agent MessageFooter
|
||||
*/
|
||||
export const MessageFooter: React.FC<MessageFooterProps> = ({
|
||||
shouldShowTimestamp,
|
||||
shouldShowCopyButton,
|
||||
timestamp,
|
||||
isUserInput,
|
||||
isSummary,
|
||||
showSystemAttachments,
|
||||
showUserFile,
|
||||
onCopyMessage,
|
||||
}) => {
|
||||
// hooks 必须在任何 return 之前调用
|
||||
const displayText = useMemo(
|
||||
() => (timestamp != null ? formatTimestamp(timestamp) : ''),
|
||||
[timestamp],
|
||||
)
|
||||
|
||||
const fullTime = useMemo(() => {
|
||||
if (timestamp == null) return ''
|
||||
return parseTimestamp(timestamp).toLocaleString()
|
||||
}, [timestamp])
|
||||
|
||||
if (!shouldShowTimestamp) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'opacity-0 group-hover:opacity-100 flex items-center bottom-1 gap-3',
|
||||
{
|
||||
'right-0 absolute': isUserInput,
|
||||
'flex-row-reverse': isSummary,
|
||||
'mt-2.5': isSummary && showSystemAttachments,
|
||||
'px-3': showUserFile,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className="text-xs text-muted-foreground whitespace-nowrap"
|
||||
title={fullTime}
|
||||
>
|
||||
{displayText}
|
||||
</span>
|
||||
{shouldShowCopyButton && (
|
||||
<button
|
||||
className="w-6 h-6 rounded flex items-center justify-center hover:bg-muted transition-colors"
|
||||
onClick={onCopyMessage}
|
||||
type="button"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5 text-muted-foreground cursor-pointer" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(MessageFooter)
|
||||
Reference in New Issue
Block a user