picoclaw/web/frontend/src/hooks/use-pico-chat.ts
wenjie 4d8fdb0b3d
feat(web): use a global WebSocket for Pico chat sessions (#1507)
- centralize Pico chat connection and session state in a shared store
- move chat lifecycle control out of usePicoChat
- hydrate and restore the active session across the app
2026-03-13 19:04:18 +08:00

72 lines
1.6 KiB
TypeScript

import dayjs from "dayjs"
import { useAtomValue } from "jotai"
import {
newChatSession,
sendChatMessage,
switchChatSession,
} from "@/lib/pico-chat-controller"
import { chatAtom } from "@/store/chat"
const UNIX_MS_THRESHOLD = 1e12
function normalizeUnixTimestamp(timestamp: number): number {
return timestamp < UNIX_MS_THRESHOLD ? timestamp * 1000 : timestamp
}
function parseTimestamp(dateRaw: number | string | Date) {
if (typeof dateRaw === "number") {
return dayjs(normalizeUnixTimestamp(dateRaw))
}
if (typeof dateRaw === "string") {
const trimmed = dateRaw.trim()
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
const numeric = Number(trimmed)
if (Number.isFinite(numeric)) {
return dayjs(normalizeUnixTimestamp(numeric))
}
}
return dayjs(trimmed)
}
return dayjs(dateRaw)
}
// Helper to format message timestamps
export function formatMessageTime(dateRaw: number | string | Date): string {
const date = parseTimestamp(dateRaw)
if (!date.isValid()) {
return ""
}
const now = dayjs()
const isToday = date.isSame(now, "day")
const isThisYear = date.isSame(now, "year")
if (isToday) {
return date.format("LT")
}
// Cross-day formatting
if (isThisYear) {
return date.format("MMM D LT")
}
return date.format("ll LT")
}
export function usePicoChat() {
const { messages, connectionState, isTyping, activeSessionId } =
useAtomValue(chatAtom)
return {
messages,
connectionState,
isTyping,
activeSessionId,
sendMessage: sendChatMessage,
switchSession: switchChatSession,
newChat: newChatSession,
}
}