Add a context window usage indicator to the web chat UI and a /context slash command that works across all channels. Backend: - Add computeContextUsage() estimating history + system + tool tokens - Attach ContextUsage to outbound messages via the pico WebSocket protocol - Add /context command showing context stats as formatted text - Add EstimateSystemTokens() on ContextBuilder for system prompt estimation Frontend: - Add ContextUsageRing component (SVG ring + hover/tap popover) - Show usage percentage, token counts, and compression threshold - Hover on desktop (150ms leave delay), tap on mobile - "View Details" sends /context with 1s cooldown - i18n support (en/zh) for popover labels Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
1.6 KiB
TypeScript
71 lines
1.6 KiB
TypeScript
import dayjs from "dayjs"
|
|
import { useAtomValue } from "jotai"
|
|
|
|
import {
|
|
newChatSession,
|
|
sendChatMessage,
|
|
switchChatSession,
|
|
} from "@/features/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)
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
if (isThisYear) {
|
|
return date.format("MMM D LT")
|
|
}
|
|
|
|
return date.format("ll LT")
|
|
}
|
|
|
|
export function usePicoChat() {
|
|
const { messages, connectionState, isTyping, activeSessionId, contextUsage } =
|
|
useAtomValue(chatAtom)
|
|
|
|
return {
|
|
messages,
|
|
connectionState,
|
|
isTyping,
|
|
activeSessionId,
|
|
contextUsage,
|
|
sendMessage: sendChatMessage,
|
|
switchSession: switchChatSession,
|
|
newChat: newChatSession,
|
|
}
|
|
}
|