2026-03-31 18:47:00 +03:00
|
|
|
/**
|
|
|
|
|
* Viewport Store — per-session scroll anchors, streaming state, memory.
|
|
|
|
|
* Extracted from session-ui-store for subscription isolation.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { create } from "zustand"
|
2026-06-02 00:43:05 +03:00
|
|
|
import { getRuntimeKey } from "@/lib/runtime-switch"
|
2026-03-31 18:47:00 +03:00
|
|
|
|
|
|
|
|
export type SessionMemoryState = {
|
|
|
|
|
viewportAnchor: number
|
2026-05-01 17:39:04 +08:00
|
|
|
/** Last known scrollbar pixel state — saved on every scroll event. */
|
|
|
|
|
scrollPosition?: {
|
|
|
|
|
scrollTop: number
|
|
|
|
|
scrollHeight: number
|
|
|
|
|
clientHeight: number
|
|
|
|
|
}
|
2026-03-31 18:47:00 +03:00
|
|
|
isStreaming: boolean
|
|
|
|
|
streamStartTime?: number
|
|
|
|
|
lastAccessedAt: number
|
|
|
|
|
backgroundMessageCount: number
|
|
|
|
|
loadedTurnCount?: number
|
|
|
|
|
hasMoreAbove?: boolean
|
|
|
|
|
hasMoreTurnsAbove?: boolean
|
|
|
|
|
historyLoading?: boolean
|
|
|
|
|
historyComplete?: boolean
|
|
|
|
|
historyLimit?: number
|
|
|
|
|
totalAvailableMessages?: number
|
|
|
|
|
streamingCooldownUntil?: number
|
|
|
|
|
isZombie?: boolean
|
|
|
|
|
lastUserMessageAt?: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ViewportState = {
|
|
|
|
|
sessionMemoryState: Map<string, SessionMemoryState>
|
|
|
|
|
isSyncing: boolean
|
|
|
|
|
|
2026-05-01 17:39:04 +08:00
|
|
|
updateViewportAnchor: (sessionId: string, anchor: number, scrollPosition?: SessionMemoryState['scrollPosition']) => void
|
2026-03-31 18:47:00 +03:00
|
|
|
}
|
|
|
|
|
|
2026-06-02 00:43:05 +03:00
|
|
|
export const viewportSessionKey = (sessionId: string, runtimeKey = getRuntimeKey()): string => `${runtimeKey}\n${sessionId}`
|
|
|
|
|
|
|
|
|
|
export const getViewportSessionMemory = (sessionId: string): SessionMemoryState | undefined => {
|
|
|
|
|
const state = useViewportStore.getState()
|
|
|
|
|
return state.sessionMemoryState.get(viewportSessionKey(sessionId)) ?? state.sessionMemoryState.get(sessionId)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 18:47:00 +03:00
|
|
|
export const useViewportStore = create<ViewportState>()((set) => ({
|
|
|
|
|
sessionMemoryState: new Map(),
|
|
|
|
|
isSyncing: false,
|
|
|
|
|
|
2026-05-01 17:39:04 +08:00
|
|
|
updateViewportAnchor: (sessionId, anchor, scrollPosition) =>
|
2026-03-31 18:47:00 +03:00
|
|
|
set((s) => {
|
|
|
|
|
const map = new Map(s.sessionMemoryState)
|
2026-06-02 00:43:05 +03:00
|
|
|
const key = viewportSessionKey(sessionId)
|
|
|
|
|
const existing = map.get(key) ?? map.get(sessionId) ?? {
|
2026-03-31 18:47:00 +03:00
|
|
|
viewportAnchor: 0,
|
|
|
|
|
isStreaming: false,
|
|
|
|
|
lastAccessedAt: Date.now(),
|
|
|
|
|
backgroundMessageCount: 0,
|
|
|
|
|
}
|
2026-06-02 00:43:05 +03:00
|
|
|
map.set(key, {
|
2026-05-01 17:39:04 +08:00
|
|
|
...existing,
|
|
|
|
|
viewportAnchor: anchor,
|
|
|
|
|
...(scrollPosition ? { scrollPosition } : {}),
|
|
|
|
|
lastAccessedAt: Date.now(),
|
|
|
|
|
})
|
2026-03-31 18:47:00 +03:00
|
|
|
return { sessionMemoryState: map }
|
|
|
|
|
}),
|
|
|
|
|
}))
|