123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- 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 };
- }
- }
|