sync.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {
  2. ChatSession,
  3. useAccessStore,
  4. useAppConfig,
  5. useChatStore,
  6. } from "../store";
  7. import { useMaskStore } from "../store/mask";
  8. import { usePromptStore } from "../store/prompt";
  9. import { StoreKey } from "../constant";
  10. import { merge } from "./merge";
  11. type NonFunctionKeys<T> = {
  12. [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K;
  13. }[keyof T];
  14. type NonFunctionFields<T> = Pick<T, NonFunctionKeys<T>>;
  15. export function getNonFunctionFileds<T extends object>(obj: T) {
  16. const ret: any = {};
  17. Object.entries(obj).map(([k, v]) => {
  18. if (typeof v !== "function") {
  19. ret[k] = v;
  20. }
  21. });
  22. return ret as NonFunctionFields<T>;
  23. }
  24. export type GetStoreState<T> = T extends { getState: () => infer U }
  25. ? NonFunctionFields<U>
  26. : never;
  27. const LocalStateSetters = {
  28. [StoreKey.Chat]: useChatStore.setState,
  29. [StoreKey.Access]: useAccessStore.setState,
  30. [StoreKey.Config]: useAppConfig.setState,
  31. [StoreKey.Mask]: useMaskStore.setState,
  32. [StoreKey.Prompt]: usePromptStore.setState,
  33. } as const;
  34. const LocalStateGetters = {
  35. [StoreKey.Chat]: () => getNonFunctionFileds(useChatStore.getState()),
  36. [StoreKey.Access]: () => getNonFunctionFileds(useAccessStore.getState()),
  37. [StoreKey.Config]: () => getNonFunctionFileds(useAppConfig.getState()),
  38. [StoreKey.Mask]: () => getNonFunctionFileds(useMaskStore.getState()),
  39. [StoreKey.Prompt]: () => getNonFunctionFileds(usePromptStore.getState()),
  40. } as const;
  41. export type AppState = {
  42. [k in keyof typeof LocalStateGetters]: ReturnType<
  43. (typeof LocalStateGetters)[k]
  44. >;
  45. };
  46. type Merger<T extends keyof AppState, U = AppState[T]> = (
  47. localState: U,
  48. remoteState: U,
  49. ) => U;
  50. type StateMerger = {
  51. [K in keyof AppState]: Merger<K>;
  52. };
  53. // we merge remote state to local state
  54. const MergeStates: StateMerger = {
  55. [StoreKey.Chat]: (localState, remoteState) => {
  56. // merge sessions
  57. const localSessions: Record<string, ChatSession> = {};
  58. localState.sessions.forEach((s) => (localSessions[s.id] = s));
  59. remoteState.sessions.forEach((remoteSession) => {
  60. // skip empty chats
  61. if (remoteSession.messages.length === 0) return;
  62. const localSession = localSessions[remoteSession.id];
  63. if (!localSession) {
  64. // if remote session is new, just merge it
  65. localState.sessions.push(remoteSession);
  66. } else {
  67. // if both have the same session id, merge the messages
  68. const localMessageIds = new Set(localSession.messages.map((v) => v.id));
  69. remoteSession.messages.forEach((m) => {
  70. if (!localMessageIds.has(m.id)) {
  71. localSession.messages.push(m);
  72. }
  73. });
  74. // sort local messages with date field in asc order
  75. localSession.messages.sort(
  76. (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
  77. );
  78. }
  79. });
  80. // sort local sessions with date field in desc order
  81. localState.sessions.sort(
  82. (a, b) =>
  83. new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(),
  84. );
  85. return localState;
  86. },
  87. [StoreKey.Prompt]: (localState, remoteState) => {
  88. localState.prompts = {
  89. ...remoteState.prompts,
  90. ...localState.prompts,
  91. };
  92. return localState;
  93. },
  94. [StoreKey.Mask]: (localState, remoteState) => {
  95. localState.masks = {
  96. ...remoteState.masks,
  97. ...localState.masks,
  98. };
  99. return localState;
  100. },
  101. [StoreKey.Config]: mergeWithUpdate<AppState[StoreKey.Config]>,
  102. [StoreKey.Access]: mergeWithUpdate<AppState[StoreKey.Access]>,
  103. };
  104. export function getLocalAppState() {
  105. const appState = Object.fromEntries(
  106. Object.entries(LocalStateGetters).map(([key, getter]) => {
  107. return [key, getter()];
  108. }),
  109. ) as AppState;
  110. return appState;
  111. }
  112. export function setLocalAppState(appState: AppState) {
  113. Object.entries(LocalStateSetters).forEach(([key, setter]) => {
  114. setter(appState[key as keyof AppState]);
  115. });
  116. }
  117. export function mergeAppState(localState: AppState, remoteState: AppState) {
  118. Object.keys(localState).forEach(<T extends keyof AppState>(k: string) => {
  119. const key = k as T;
  120. const localStoreState = localState[key];
  121. const remoteStoreState = remoteState[key];
  122. MergeStates[key](localStoreState, remoteStoreState);
  123. });
  124. return localState;
  125. }
  126. /**
  127. * Merge state with `lastUpdateTime`, older state will be override
  128. */
  129. export function mergeWithUpdate<T extends { lastUpdateTime?: number }>(
  130. localState: T,
  131. remoteState: T,
  132. ) {
  133. const localUpdateTime = localState.lastUpdateTime ?? 0;
  134. const remoteUpdateTime = localState.lastUpdateTime ?? 1;
  135. if (localUpdateTime < remoteUpdateTime) {
  136. merge(remoteState, localState);
  137. return { ...remoteState };
  138. } else {
  139. merge(localState, remoteState);
  140. return { ...localState };
  141. }
  142. }