Spaces:
Paused
Paused
| import { | |
| ChatSession, | |
| useAccessStore, | |
| useAppConfig, | |
| useChatStore, | |
| } from "../store"; | |
| import { useMaskStore } from "../store/mask"; | |
| import { usePromptStore } from "../store/prompt"; | |
| import { StoreKey } from "../constant"; | |
| import { merge } from "./merge"; | |
| type NonFunctionKeys<T> = { | |
| [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K; | |
| }[keyof T]; | |
| type NonFunctionFields<T> = Pick<T, NonFunctionKeys<T>>; | |
| export function getNonFunctionFileds<T extends object>(obj: T) { | |
| const ret: any = {}; | |
| Object.entries(obj).map(([k, v]) => { | |
| if (typeof v !== "function") { | |
| ret[k] = v; | |
| } | |
| }); | |
| return ret as NonFunctionFields<T>; | |
| } | |
| export type GetStoreState<T> = T extends { getState: () => infer U } | |
| ? NonFunctionFields<U> | |
| : never; | |
| const LocalStateSetters = { | |
| [StoreKey.Chat]: useChatStore.setState, | |
| [StoreKey.Access]: useAccessStore.setState, | |
| [StoreKey.Config]: useAppConfig.setState, | |
| [StoreKey.Mask]: useMaskStore.setState, | |
| [StoreKey.Prompt]: usePromptStore.setState, | |
| } as const; | |
| const LocalStateGetters = { | |
| [StoreKey.Chat]: () => getNonFunctionFileds(useChatStore.getState()), | |
| [StoreKey.Access]: () => getNonFunctionFileds(useAccessStore.getState()), | |
| [StoreKey.Config]: () => getNonFunctionFileds(useAppConfig.getState()), | |
| [StoreKey.Mask]: () => getNonFunctionFileds(useMaskStore.getState()), | |
| [StoreKey.Prompt]: () => getNonFunctionFileds(usePromptStore.getState()), | |
| } as const; | |
| export type AppState = { | |
| [k in keyof typeof LocalStateGetters]: ReturnType< | |
| (typeof LocalStateGetters)[k] | |
| >; | |
| }; | |
| type Merger<T extends keyof AppState, U = AppState[T]> = ( | |
| localState: U, | |
| remoteState: U, | |
| ) => U; | |
| type StateMerger = { | |
| [K in keyof AppState]: Merger<K>; | |
| }; | |
| // we merge remote state to local state | |
| const MergeStates: StateMerger = { | |
| [StoreKey.Chat]: (localState, remoteState) => { | |
| // merge sessions | |
| const localSessions: Record<string, ChatSession> = {}; | |
| localState.sessions.forEach((s) => (localSessions[s.id] = s)); | |
| remoteState.sessions.forEach((remoteSession) => { | |
| // skip empty chats | |
| if (remoteSession.messages.length === 0) return; | |
| const localSession = localSessions[remoteSession.id]; | |
| if (!localSession) { | |
| // if remote session is new, just merge it | |
| localState.sessions.push(remoteSession); | |
| } else { | |
| // if both have the same session id, merge the messages | |
| const localMessageIds = new Set(localSession.messages.map((v) => v.id)); | |
| remoteSession.messages.forEach((m) => { | |
| if (!localMessageIds.has(m.id)) { | |
| localSession.messages.push(m); | |
| } | |
| }); | |
| // sort local messages with date field in asc order | |
| localSession.messages.sort( | |
| (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), | |
| ); | |
| } | |
| }); | |
| // sort local sessions with date field in desc order | |
| localState.sessions.sort( | |
| (a, b) => | |
| new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), | |
| ); | |
| return localState; | |
| }, | |
| [StoreKey.Prompt]: (localState, remoteState) => { | |
| localState.prompts = { | |
| ...remoteState.prompts, | |
| ...localState.prompts, | |
| }; | |
| return localState; | |
| }, | |
| [StoreKey.Mask]: (localState, remoteState) => { | |
| localState.masks = { | |
| ...remoteState.masks, | |
| ...localState.masks, | |
| }; | |
| return localState; | |
| }, | |
| [StoreKey.Config]: mergeWithUpdate<AppState[StoreKey.Config]>, | |
| [StoreKey.Access]: mergeWithUpdate<AppState[StoreKey.Access]>, | |
| }; | |
| export function getLocalAppState() { | |
| const appState = Object.fromEntries( | |
| Object.entries(LocalStateGetters).map(([key, getter]) => { | |
| return [key, getter()]; | |
| }), | |
| ) as AppState; | |
| return appState; | |
| } | |
| export function setLocalAppState(appState: AppState) { | |
| Object.entries(LocalStateSetters).forEach(([key, setter]) => { | |
| setter(appState[key as keyof AppState]); | |
| }); | |
| } | |
| export function mergeAppState(localState: AppState, remoteState: AppState) { | |
| Object.keys(localState).forEach(<T extends keyof AppState>(k: string) => { | |
| const key = k as T; | |
| const localStoreState = localState[key]; | |
| const remoteStoreState = remoteState[key]; | |
| MergeStates[key](localStoreState, remoteStoreState); | |
| }); | |
| return localState; | |
| } | |
| /** | |
| * Merge state with `lastUpdateTime`, older state will be override | |
| */ | |
| export function mergeWithUpdate<T extends { lastUpdateTime?: number }>( | |
| localState: T, | |
| remoteState: T, | |
| ) { | |
| const localUpdateTime = localState.lastUpdateTime ?? 0; | |
| const remoteUpdateTime = localState.lastUpdateTime ?? 1; | |
| if (localUpdateTime < remoteUpdateTime) { | |
| merge(remoteState, localState); | |
| return { ...remoteState }; | |
| } else { | |
| merge(localState, remoteState); | |
| return { ...localState }; | |
| } | |
| } | |