Spaces:
Build error
Build error
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 }; | |
} | |
} | |