diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 1acfc1289de..d4fa9c85f1d 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -1,165 +1 @@ -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 = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K; -}[keyof T]; -type NonFunctionFields = Pick>; - -export function getNonFunctionFileds(obj: T) { - const ret: any = {}; - - Object.entries(obj).map(([k, v]) => { - if (typeof v !== "function") { - ret[k] = v; - } - }); - - return ret as NonFunctionFields; -} - -export type GetStoreState = T extends { getState: () => infer U } - ? NonFunctionFields - : 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 = ( - localState: U, - remoteState: U, -) => U; - -type StateMerger = { - [K in keyof AppState]: Merger; -}; - -// we merge remote state to local state -const MergeStates: StateMerger = { - [StoreKey.Chat]: (localState, remoteState) => { - // merge sessions - const localSessions: Record = {}; - 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, - [StoreKey.Access]: mergeWithUpdate, -}; - -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((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( - 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 }; - } -} +aW1wb3J0IHsKICBDaGF0U2Vzc2lvbiwKICB1c2VBY2Nlc3NTdG9yZSwKICB1c2VBcHBDb25maWcsCiAgdXNlQ2hhdFN0b3JlLAp9IGZyb20gIi4uL3N0b3JlIjsKaW1wb3J0IHsgdXNlTWFza1N0b3JlIH0gZnJvbSAiLi4vc3RvcmUvbWFzayI7CmltcG9ydCB7IHVzZVByb21wdFN0b3JlIH0gZnJvbSAiLi4vc3RvcmUvcHJvbXB0IjsKaW1wb3J0IHsgU3RvcmVLZXkgfSBmcm9tICIuLi9jb25zdGFudCI7CmltcG9ydCB7IG1lcmdlIH0gZnJvbSAiLi9tZXJnZSI7Cgp0eXBlIE5vbkZ1bmN0aW9uS2V5czxUPiA9IHsKICBbSyBpbiBrZXlvZiBUXTogVFtLXSBleHRlbmRzICguLi5hcmdzOiBhbnlbXSkgPT4gYW55ID8gbmV2ZXIgOiBLOwp9W2tleW9mIFRdOwp0eXBlIE5vbkZ1bmN0aW9uRmllbGRzPFQ+ID0gUGljazxULCBOb25GdW5jdGlvbktleXM8VD4+OwoKZXhwb3J0IGZ1bmN0aW9uIGdldE5vbkZ1bmN0aW9uRmlsZWRzPFQgZXh0ZW5kcyBvYmplY3Q+KG9iajogVCkgewogIGNvbnN0IHJldDogYW55ID0ge307CgogIE9iamVjdC5lbnRyaWVzKG9iaikubWFwKChbaywgdl0pID0+IHsKICAgIGlmICh0eXBlb2YgdiAhPT0gImZ1bmN0aW9uIikgewogICAgICByZXRba10gPSB2OwogICAgfQogIH0pOwoKICByZXR1cm4gcmV0IGFzIE5vbkZ1bmN0aW9uRmllbGRzPFQ+Owp9CgpleHBvcnQgdHlwZSBHZXRTdG9yZVN0YXRlPFQ+ID0gVCBleHRlbmRzIHsgZ2V0U3RhdGU6ICgpID0+IGluZmVyIFUgfQogID8gTm9uRnVuY3Rpb25GaWVsZHM8VT4KICA6IG5ldmVyOwoKY29uc3QgTG9jYWxTdGF0ZVNldHRlcnMgPSB7CiAgW1N0b3JlS2V5LkNoYXRdOiB1c2VDaGF0U3RvcmUuc2V0U3RhdGUsCiAgW1N0b3JlS2V5LkFjY2Vzc106IHVzZUFjY2Vzc1N0b3JlLnNldFN0YXRlLAogIFtTdG9yZUtleS5Db25maWddOiB1c2VBcHBDb25maWcuc2V0U3RhdGUsCiAgW1N0b3JlS2V5Lk1hc2tdOiB1c2VNYXNrU3RvcmUuc2V0U3RhdGUsCiAgW1N0b3JlS2V5LlByb21wdF06IHVzZVByb21wdFN0b3JlLnNldFN0YXRlLAp9IGFzIGNvbnN0OwoKY29uc3QgTG9jYWxTdGF0ZUdldHRlcnMgPSB7CiAgW1N0b3JlS2V5LkNoYXRdOiAoKSA9PiBnZXROb25GdW5jdGlvbkZpbGVkcyh1c2VDaGF0U3RvcmUuZ2V0U3RhdGUoKSksCiAgW1N0b3JlS2V5LkFjY2Vzc106ICgpID0+IGdldE5vbkZ1bmN0aW9uRmlsZWRzKHVzZUFjY2Vzc1N0b3JlLmdldFN0YXRlKCkpLAogIFtTdG9yZUtleS5Db25maWddOiAoKSA9PiBnZXROb25GdW5jdGlvbkZpbGVkcyh1c2VBcHBDb25maWcuZ2V0U3RhdGUoKSksCiAgW1N0b3JlS2V5Lk1hc2tdOiAoKSA9PiBnZXROb25GdW5jdGlvbkZpbGVkcyh1c2VNYXNrU3RvcmUuZ2V0U3RhdGUoKSksCiAgW1N0b3JlS2V5LlByb21wdF06ICgpID0+IGdldE5vbkZ1bmN0aW9uRmlsZWRzKHVzZVByb21wdFN0b3JlLmdldFN0YXRlKCkpLAp9IGFzIGNvbnN0OwoKZXhwb3J0IHR5cGUgQXBwU3RhdGUgPSB7CiAgW2sgaW4ga2V5b2YgdHlwZW9mIExvY2FsU3RhdGVHZXR0ZXJzXTogUmV0dXJuVHlwZTwKICAgICh0eXBlb2YgTG9jYWxTdGF0ZUdldHRlcnMpW2tdCiAgPjsKfTsKCnR5cGUgTWVyZ2VyPFQgZXh0ZW5kcyBrZXlvZiBBcHBTdGF0ZSwgVSA9IEFwcFN0YXRlW1RdPiA9ICgKICBsb2NhbFN0YXRlOiBVLAogIHJlbW90ZVN0YXRlOiBVLAopID0+IFU7Cgp0eXBlIFN0YXRlTWVyZ2VyID0gewogIFtLIGluIGtleW9mIEFwcFN0YXRlXTogTWVyZ2VyPEs+Owp9OwoKLy8gd2UgbWVyZ2UgcmVtb3RlIHN0YXRlIHRvIGxvY2FsIHN0YXRlCmNvbnN0IE1lcmdlU3RhdGVzOiBTdGF0ZU1lcmdlciA9IHsKICBbU3RvcmVLZXkuQ2hhdF06IChsb2NhbFN0YXRlLCByZW1vdGVTdGF0ZSkgPT4gewogICAgLy8gbWVyZ2Ugc2Vzc2lvbnMKICAgIGNvbnN0IGxvY2FsU2Vzc2lvbnM6IFJlY29yZDxzdHJpbmcsIENoYXRTZXNzaW9uPiA9IHt9OwogICAgbG9jYWxTdGF0ZS5zZXNzaW9ucy5mb3JFYWNoKChzKSA9PiAobG9jYWxTZXNzaW9uc1tzLmlkXSA9IHMpKTsKCiAgICByZW1vdGVTdGF0ZS5zZXNzaW9ucy5mb3JFYWNoKChyZW1vdGVTZXNzaW9uKSA9PiB7CiAgICAgIC8vIHNraXAgZW1wdHkgY2hhdHMKICAgICAgaWYgKHJlbW90ZVNlc3Npb24ubWVzc2FnZXMubGVuZ3RoID09PSAwKSByZXR1cm47CgogICAgICBjb25zdCBsb2NhbFNlc3Npb24gPSBsb2NhbFNlc3Npb25zW3JlbW90ZVNlc3Npb24uaWRdOwogICAgICBpZiAoIWxvY2FsU2Vzc2lvbikgewogICAgICAgIC8vIGlmIHJlbW90ZSBzZXNzaW9uIGlzIG5ldywganVzdCBtZXJnZSBpdAogICAgICAgIGxvY2FsU3RhdGUuc2Vzc2lvbnMucHVzaChyZW1vdGVTZXNzaW9uKTsKICAgICAgfSBlbHNlIHsKICAgICAgICAvLyBpZiBib3RoIGhhdmUgdGhlIHNhbWUgc2Vzc2lvbiBpZCwgbWVyZ2UgdGhlIG1lc3NhZ2VzCiAgICAgICAgY29uc3QgbG9jYWxNZXNzYWdlSWRzID0gbmV3IFNldChsb2NhbFNlc3Npb24ubWVzc2FnZXMubWFwKCh2KSA9PiB2LmlkKSk7CiAgICAgICAgcmVtb3RlU2Vzc2lvbi5tZXNzYWdlcy5mb3JFYWNoKChtKSA9PiB7CiAgICAgICAgICBpZiAoIWxvY2FsTWVzc2FnZUlkcy5oYXMobS5pZCkpIHsKICAgICAgICAgICAgbG9jYWxTZXNzaW9uLm1lc3NhZ2VzLnB1c2gobSk7CiAgICAgICAgICB9CiAgICAgICAgfSk7CgogICAgICAgIC8vIHNvcnQgbG9jYWwgbWVzc2FnZXMgd2l0aCBkYXRlIGZpZWxkIGluIGFzYyBvcmRlcgogICAgICAgIGxvY2FsU2Vzc2lvbi5tZXNzYWdlcy5zb3J0KAogICAgICAgICAgKGEsIGIpID0+IG5ldyBEYXRlKGEuZGF0ZSkuZ2V0VGltZSgpIC0gbmV3IERhdGUoYi5kYXRlKS5nZXRUaW1lKCksCiAgICAgICAgKTsKICAgICAgfQogICAgfSk7CgogICAgLy8gc29ydCBsb2NhbCBzZXNzaW9ucyB3aXRoIGRhdGUgZmllbGQgaW4gZGVzYyBvcmRlcgogICAgbG9jYWxTdGF0ZS5zZXNzaW9ucy5zb3J0KAogICAgICAoYSwgYikgPT4KICAgICAgICBuZXcgRGF0ZShiLmxhc3RVcGRhdGUpLmdldFRpbWUoKSAtIG5ldyBEYXRlKGEubGFzdFVwZGF0ZSkuZ2V0VGltZSgpLAogICAgKTsKCiAgICByZXR1cm4gbG9jYWxTdGF0ZTsKICB9LAogIFtTdG9yZUtleS5Qcm9tcHRdOiAobG9jYWxTdGF0ZSwgcmVtb3RlU3RhdGUpID0+IHsKICAgIGxvY2FsU3RhdGUucHJvbXB0cyA9IHsKICAgICAgLi4ucmVtb3RlU3RhdGUucHJvbXB0cywKICAgICAgLi4ubG9jYWxTdGF0ZS5wcm9tcHRzLAogICAgfTsKICAgIHJldHVybiBsb2NhbFN0YXRlOwogIH0sCiAgW1N0b3JlS2V5Lk1hc2tdOiAobG9jYWxTdGF0ZSwgcmVtb3RlU3RhdGUpID0+IHsKICAgIGxvY2FsU3RhdGUubWFza3MgPSB7CiAgICAgIC4uLnJlbW90ZVN0YXRlLm1hc2tzLAogICAgICAuLi5sb2NhbFN0YXRlLm1hc2tzLAogICAgfTsKICAgIHJldHVybiBsb2NhbFN0YXRlOwogIH0sCiAgW1N0b3JlS2V5LkNvbmZpZ106IG1lcmdlV2l0aFVwZGF0ZTxBcHBTdGF0ZVtTdG9yZUtleS5Db25maWddPiwKICBbU3RvcmVLZXkuQWNjZXNzXTogbWVyZ2VXaXRoVXBkYXRlPEFwcFN0YXRlW1N0b3JlS2V5LkFjY2Vzc10+LAp9OwoKZXhwb3J0IGZ1bmN0aW9uIGdldExvY2FsQXBwU3RhdGUoKSB7CiAgY29uc3QgYXBwU3RhdGUgPSBPYmplY3QuZnJvbUVudHJpZXMoCiAgICBPYmplY3QuZW50cmllcyhMb2NhbFN0YXRlR2V0dGVycykubWFwKChba2V5LCBnZXR0ZXJdKSA9PiB7CiAgICAgIHJldHVybiBba2V5LCBnZXR0ZXIoKV07CiAgICB9KSwKICApIGFzIEFwcFN0YXRlOwoKICByZXR1cm4gYXBwU3RhdGU7Cn0KCmV4cG9ydCBmdW5jdGlvbiBzZXRMb2NhbEFwcFN0YXRlKGFwcFN0YXRlOiBBcHBTdGF0ZSkgewogIE9iamVjdC5lbnRyaWVzKExvY2FsU3RhdGVTZXR0ZXJzKS5mb3JFYWNoKChba2V5LCBzZXR0ZXJdKSA9PiB7CiAgICBzZXR0ZXIoYXBwU3RhdGVba2V5IGFzIGtleW9mIEFwcFN0YXRlXSk7CiAgfSk7Cn0KCmV4cG9ydCBmdW5jdGlvbiBtZXJnZUFwcFN0YXRlKGxvY2FsU3RhdGU6IEFwcFN0YXRlLCByZW1vdGVTdGF0ZTogQXBwU3RhdGUpIHsKICBPYmplY3Qua2V5cyhsb2NhbFN0YXRlKS5mb3JFYWNoKDxUIGV4dGVuZHMga2V5b2YgQXBwU3RhdGU+KGs6IHN0cmluZykgPT4gewogICAgY29uc3Qga2V5ID0gayBhcyBUOwogICAgY29uc3QgbG9jYWxTdG9yZVN0YXRlID0gbG9jYWxTdGF0ZVtrZXldOwogICAgY29uc3QgcmVtb3RlU3RvcmVTdGF0ZSA9IHJlbW90ZVN0YXRlW2tleV07CiAgICBNZXJnZVN0YXRlc1trZXldKGxvY2FsU3RvcmVTdGF0ZSwgcmVtb3RlU3RvcmVTdGF0ZSk7CiAgfSk7CgogIHJldHVybiBsb2NhbFN0YXRlOwp9CgovKioKICogTWVyZ2Ugc3RhdGUgd2l0aCBgbGFzdFVwZGF0ZVRpbWVgLCBuZXdlciBzdGF0ZSB3aWxsIG92ZXJyaWRlCiAqCiAqIOWQiOW5tuaWueWQke+8mui+g+aWsOiAhSBpbi1wbGFjZSBpbi1wbGFjZSDopobnm5YgbG9jYWxTdGF0Ze+8jOWboOS4uuWQjue7rSBzZXRMb2NhbEFwcFN0YXRlIOS4jgogKiBjbGllbnQuc2V0IOmDveS7pSBsb2NhbFN0YXRlIOS4uuWHhuS4iuS8oOS6keerr+OAggogKiAtIHJlbW90ZSDmm7TmlrDvvJpyZW1vdGUg6KaG55uWIGxvY2Fs77yI5YaZ5YWlIGxvY2FsU3RhdGXvvInvvIzmnKzlnLDmi7/liLDov5znq6/mlrDlgLzlubbkuIrkvKAKICogLSBsb2NhbCDmm7TmlrDmiJbnm7jnrYnvvJrkv53nlZkgbG9jYWzvvIzkuI3orqnov5znq6/ml6flgLzopobnm5bvvIzmnKzlnLDmlrDlgLzmraPluLjkuIrkvKAKICovCmV4cG9ydCBmdW5jdGlvbiBtZXJnZVdpdGhVcGRhdGU8VCBleHRlbmRzIHsgbGFzdFVwZGF0ZVRpbWU/OiBudW1iZXIgfT4oCiAgbG9jYWxTdGF0ZTogVCwKICByZW1vdGVTdGF0ZTogVCwKKSB7CiAgY29uc3QgbG9jYWxVcGRhdGVUaW1lID0gbG9jYWxTdGF0ZS5sYXN0VXBkYXRlVGltZSA/PyAwOwogIGNvbnN0IHJlbW90ZVVwZGF0ZVRpbWUgPSByZW1vdGVTdGF0ZS5sYXN0VXBkYXRlVGltZSA/PyAwOwoKICBpZiAobG9jYWxVcGRhdGVUaW1lIDwgcmVtb3RlVXBkYXRlVGltZSkgewogICAgLy8gcmVtb3RlIOi+g+aWsO+8muiuqSByZW1vdGUg6KaG55uWIGxvY2Fs77yIaW4tcGxhY2Ug5L+u5pS5IGxvY2FsU3RhdGXvvIkKICAgIG1lcmdlKGxvY2FsU3RhdGUsIHJlbW90ZVN0YXRlKTsKICB9CiAgLy8gbG9jYWwg6L6D5paw5oiW55u4562J77ya5L+d55WZIGxvY2Fs77yM5LiN5ZCI5bm2IHJlbW90Ze+8jOmBv+WFjei/nOerr+aXp+WAvOimhuebluacrOWcsOaWsOWAvAogIHJldHVybiB7IC4uLmxvY2FsU3RhdGUgfTsKfQo= \ No newline at end of file