初始化模版工程

This commit is contained in:
Cloud Bot
2026-03-20 07:33:46 +00:00
commit 23717e0ecd
386 changed files with 51675 additions and 0 deletions

View 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)