Files
picoclaw/web/frontend/src/store/chat.ts
T
程智超0668000959 7a7e205cc8 fix(context): expose history tokens and remove leaked state files
Address remaining review feedback: 1) Add HistoryTokens field to ContextUsage/ContextStats, showing history-only token count in /context and frontend UI alongside SummarizeAtTokens so users can see the actual summarization trigger comparison. 2) Remove .codebuddy/github-contribute/ state files accidentally included in the PR.
2026-06-06 00:28:32 +08:00

123 lines
2.9 KiB
TypeScript

import { atom, getDefaultStore } from "jotai"
import { atomWithStorage } from "jotai/utils"
import {
ASSISTANT_DETAIL_VISIBILITY_STORAGE_KEY,
type AssistantDetailVisibility,
DEFAULT_ASSISTANT_DETAIL_VISIBILITY,
assistantDetailVisibilityStorage,
shouldShowAssistantMessage,
} from "@/features/chat/detail-visibility"
import {
getInitialActiveSessionId,
writeStoredSessionId,
} from "@/features/chat/state"
export interface ChatAttachment {
type: "image" | "audio" | "video" | "file"
url: string
filename?: string
contentType?: string
}
export interface ChatToolCallFunction {
name?: string
arguments?: string
}
export interface ChatToolCallExtraContent {
toolFeedbackExplanation?: string
}
export interface ChatToolCall {
id?: string
type?: string
function?: ChatToolCallFunction
extraContent?: ChatToolCallExtraContent
}
export type AssistantMessageKind = "normal" | "thought" | "tool_calls"
export interface ChatMessage {
id: string
role: "user" | "assistant"
content: string
timestamp: number | string
kind?: AssistantMessageKind
modelName?: string
attachments?: ChatAttachment[]
toolCalls?: ChatToolCall[]
}
export interface ContextUsage {
used_tokens: number
total_tokens: number
history_tokens?: number
compress_at_tokens: number
summarize_at_tokens?: number
used_percent: number
}
export type ConnectionState =
| "disconnected"
| "connecting"
| "connected"
| "error"
export interface ChatStoreState {
messages: ChatMessage[]
connectionState: ConnectionState
isTyping: boolean
activeSessionId: string
hasHydratedActiveSession: boolean
contextUsage?: ContextUsage
}
type ChatStorePatch = Partial<ChatStoreState>
const DEFAULT_CHAT_STATE: ChatStoreState = {
messages: [],
connectionState: "disconnected",
isTyping: false,
activeSessionId: getInitialActiveSessionId(),
hasHydratedActiveSession: false,
}
export const chatAtom = atom<ChatStoreState>(DEFAULT_CHAT_STATE)
export const assistantDetailVisibilityAtom =
atomWithStorage<AssistantDetailVisibility>(
ASSISTANT_DETAIL_VISIBILITY_STORAGE_KEY,
DEFAULT_ASSISTANT_DETAIL_VISIBILITY,
assistantDetailVisibilityStorage,
{ getOnInit: true },
)
export const showAssistantDetailsAtom = atom(
(get) => get(assistantDetailVisibilityAtom) !== "none",
)
const store = getDefaultStore()
export function getChatState() {
return store.get(chatAtom)
}
export function updateChatStore(
patch:
| ChatStorePatch
| ((prev: ChatStoreState) => ChatStorePatch | ChatStoreState),
) {
store.set(chatAtom, (prev) => {
const nextPatch = typeof patch === "function" ? patch(prev) : patch
const next = { ...prev, ...nextPatch }
if (next.activeSessionId !== prev.activeSessionId) {
writeStoredSessionId(next.activeSessionId)
}
return next
})
}
export { shouldShowAssistantMessage, DEFAULT_ASSISTANT_DETAIL_VISIBILITY }
export type { AssistantDetailVisibility }