mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(session): add per-message created_at timestamps
- Persistence layer (jsonl.go addMsg/SetHistory) normalizes CreatedAt when missing so the invariant is guaranteed at the storage boundary - API layer (session.go) exposes created_at on all transcript message types with session.updated fallback for legacy messages - Frontend uses per-message timestamps when available - messagesContentEqual ignores CreatedAt for tail-matching after JSONL roundtrip Fixes #2787
This commit is contained in:
@@ -117,12 +117,17 @@ export function AssistantMessage({
|
||||
<span className="text-muted-foreground/45">{trimmedModelName}</span>
|
||||
)}
|
||||
</div>
|
||||
<IconChevronDown
|
||||
className={cn(
|
||||
"size-3.5 opacity-0 transition-all duration-200 group-hover:opacity-100",
|
||||
isExpanded ? "rotate-180" : "",
|
||||
<div className="flex items-center gap-2">
|
||||
{formattedTimestamp && (
|
||||
<span className="opacity-50">{formattedTimestamp}</span>
|
||||
)}
|
||||
/>
|
||||
<IconChevronDown
|
||||
className={cn(
|
||||
"size-3.5 opacity-0 transition-all duration-200 group-hover:opacity-100",
|
||||
isExpanded ? "rotate-180" : "",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(!isCollapsedBlock || isExpanded) && isToolCalls && hasToolCalls && (
|
||||
|
||||
@@ -384,6 +384,7 @@ export function ChatPage() {
|
||||
<UserMessage
|
||||
content={msg.content}
|
||||
attachments={msg.attachments}
|
||||
timestamp={msg.timestamp}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import { IconCheck, IconCopy } from "@tabler/icons-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { formatMessageTime } from "@/hooks/use-pico-chat"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { ChatAttachment } from "@/store/chat"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
interface UserMessageProps {
|
||||
content: string
|
||||
attachments?: ChatAttachment[]
|
||||
timestamp?: string | number
|
||||
}
|
||||
|
||||
export function UserMessage({ content, attachments = [] }: UserMessageProps) {
|
||||
export function UserMessage({
|
||||
content,
|
||||
attachments = [],
|
||||
timestamp = "",
|
||||
}: UserMessageProps) {
|
||||
const { t } = useTranslation()
|
||||
const { copy, isCopied } = useCopyToClipboard()
|
||||
const hasText = content.trim().length > 0
|
||||
@@ -22,6 +28,8 @@ export function UserMessage({ content, attachments = [] }: UserMessageProps) {
|
||||
const copyMessageLabel = isCopied
|
||||
? t("chat.copiedLabel")
|
||||
: t("chat.copyMessage")
|
||||
const formattedTimestamp =
|
||||
timestamp !== "" ? formatMessageTime(timestamp) : ""
|
||||
|
||||
return (
|
||||
<div className="group flex w-full flex-col items-end gap-1.5">
|
||||
@@ -81,6 +89,12 @@ export function UserMessage({ content, attachments = [] }: UserMessageProps) {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formattedTimestamp && (
|
||||
<span className="px-1 text-[12px] text-zinc-400">
|
||||
{formattedTimestamp}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user