mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-08 09:23:57 +08:00
feat: close #2754 add import/export to file
This commit is contained in:
162
app/utils/sync.ts
Normal file
162
app/utils/sync.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
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) => {
|
||||
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 };
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user