feat: Add automatic data synchronization settings and implementation, enabling auto-sync after completing replies or deleting conversations

This commit is contained in:
李超 2024-08-15 22:39:30 +08:00
parent 4f876f3e65
commit 4b22aaf979
6 changed files with 61 additions and 7 deletions

View File

@ -355,6 +355,21 @@ function SyncConfigModal(props: { onClose?: () => void }) {
</select> </select>
</ListItem> </ListItem>
<ListItem
title={Locale.Settings.Sync.Config.EnableAutoSync.Title}
subTitle={Locale.Settings.Sync.Config.EnableAutoSync.SubTitle}
>
<input
type="checkbox"
checked={syncStore.enableAutoSync}
onChange={(e) => {
syncStore.update(
(config) => (config.enableAutoSync = e.currentTarget.checked),
);
}}
></input>
</ListItem>
<ListItem <ListItem
title={Locale.Settings.Sync.Config.Proxy.Title} title={Locale.Settings.Sync.Config.Proxy.Title}
subTitle={Locale.Settings.Sync.Config.Proxy.SubTitle} subTitle={Locale.Settings.Sync.Config.Proxy.SubTitle}

View File

@ -204,6 +204,10 @@ const cn = {
Title: "同步类型", Title: "同步类型",
SubTitle: "选择喜爱的同步服务器", SubTitle: "选择喜爱的同步服务器",
}, },
EnableAutoSync: {
Title: "自动同步设置",
SubTitle: "在回复完成或删除消息后自动同步数据",
},
Proxy: { Proxy: {
Title: "启用代理", Title: "启用代理",
SubTitle: "在浏览器中同步时,必须启用代理以避免跨域限制", SubTitle: "在浏览器中同步时,必须启用代理以避免跨域限制",

View File

@ -207,6 +207,11 @@ const en: LocaleType = {
Title: "Sync Type", Title: "Sync Type",
SubTitle: "Choose your favorite sync service", SubTitle: "Choose your favorite sync service",
}, },
EnableAutoSync: {
Title: "Auto Sync Settings",
SubTitle:
"Automatically synchronize data after replying or deleting messages",
},
Proxy: { Proxy: {
Title: "Enable CORS Proxy", Title: "Enable CORS Proxy",
SubTitle: "Enable a proxy to avoid cross-origin restrictions", SubTitle: "Enable a proxy to avoid cross-origin restrictions",

View File

@ -30,6 +30,7 @@ import { nanoid } from "nanoid";
import { createPersistStore } from "../utils/store"; import { createPersistStore } from "../utils/store";
import { collectModelsWithDefaultModel } from "../utils/model"; import { collectModelsWithDefaultModel } from "../utils/model";
import { useAccessStore } from "./access"; import { useAccessStore } from "./access";
import { useSyncStore } from "./sync";
import { isDalle3 } from "../utils"; import { isDalle3 } from "../utils";
export type ChatMessage = RequestMessage & { export type ChatMessage = RequestMessage & {
@ -168,6 +169,15 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) {
return output; return output;
} }
let cloudSyncTimer: any = null;
function noticeCloudSync(): void {
const syncStore = useSyncStore.getState();
cloudSyncTimer && clearTimeout(cloudSyncTimer);
cloudSyncTimer = setTimeout(() => {
syncStore.autoSync();
}, 500);
}
const DEFAULT_CHAT_STATE = { const DEFAULT_CHAT_STATE = {
sessions: [createEmptySession()], sessions: [createEmptySession()],
currentSessionIndex: 0, currentSessionIndex: 0,
@ -297,12 +307,15 @@ export const useChatStore = createPersistStore(
deletedSessionIds, deletedSessionIds,
})); }));
noticeCloudSync();
showToast( showToast(
Locale.Home.DeleteToast, Locale.Home.DeleteToast,
{ {
text: Locale.Home.Revert, text: Locale.Home.Revert,
onClick() { onClick() {
set(() => restoreState); set(() => restoreState);
noticeCloudSync();
}, },
}, },
5000, 5000,
@ -330,6 +343,7 @@ export const useChatStore = createPersistStore(
}); });
get().updateStat(message); get().updateStat(message);
get().summarizeSession(); get().summarizeSession();
noticeCloudSync();
}, },
async onUserInput(content: string, attachImages?: string[]) { async onUserInput(content: string, attachImages?: string[]) {

View File

@ -26,6 +26,7 @@ export type SyncStore = GetStoreState<typeof useSyncStore>;
const DEFAULT_SYNC_STATE = { const DEFAULT_SYNC_STATE = {
provider: ProviderType.WebDAV, provider: ProviderType.WebDAV,
enableAutoSync: true,
useProxy: true, useProxy: true,
proxyUrl: corsPath(ApiPath.Cors), proxyUrl: corsPath(ApiPath.Cors),
@ -91,6 +92,11 @@ export const useSyncStore = createPersistStore(
}, },
async sync() { async sync() {
const enableAutoSync = get().enableAutoSync;
if (!enableAutoSync) {
return;
}
const localState = getLocalAppState(); const localState = getLocalAppState();
const provider = get().provider; const provider = get().provider;
const config = get()[provider]; const config = get()[provider];
@ -100,15 +106,17 @@ export const useSyncStore = createPersistStore(
const remoteState = await client.get(config.username); const remoteState = await client.get(config.username);
if (!remoteState || remoteState === "") { if (!remoteState || remoteState === "") {
await client.set(config.username, JSON.stringify(localState)); await client.set(config.username, JSON.stringify(localState));
console.log("[Sync] Remote state is empty, using local state instead."); console.log(
return "[Sync] Remote state is empty, using local state instead.",
);
return;
} else { } else {
const parsedRemoteState = JSON.parse( const parsedRemoteState = JSON.parse(
await client.get(config.username), await client.get(config.username),
) as AppState; ) as AppState;
mergeAppState(localState, parsedRemoteState); mergeAppState(localState, parsedRemoteState);
setLocalAppState(localState); setLocalAppState(localState);
} }
} catch (e) { } catch (e) {
console.log("[Sync] failed to get remote state", e); console.log("[Sync] failed to get remote state", e);
throw e; throw e;
@ -123,6 +131,14 @@ export const useSyncStore = createPersistStore(
const client = this.getClient(); const client = this.getClient();
return await client.check(); return await client.check();
}, },
async autoSync() {
const { lastSyncTime, provider } = get();
const syncStore = useSyncStore.getState();
if (lastSyncTime && syncStore.cloudSync()) {
syncStore.sync();
}
},
}), }),
{ {
name: StoreKey.Sync, name: StoreKey.Sync,

View File

@ -130,10 +130,10 @@ const MergeStates: StateMerger = {
}); });
// sort local sessions with date field in desc order // sort local sessions with date field in desc order
localState.sessions.sort( // localState.sessions.sort(
(a, b) => // (a, b) =>
new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), // new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(),
); // );
const deletedSessionIds = { const deletedSessionIds = {
...remoteDeletedSessionIds, ...remoteDeletedSessionIds,