feat: Add automatic data synchronization settings and implementation, enabling auto-sync after completing replies or deleting conversations
This commit is contained in:
parent
4f876f3e65
commit
4b22aaf979
|
@ -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}
|
||||||
|
|
|
@ -204,6 +204,10 @@ const cn = {
|
||||||
Title: "同步类型",
|
Title: "同步类型",
|
||||||
SubTitle: "选择喜爱的同步服务器",
|
SubTitle: "选择喜爱的同步服务器",
|
||||||
},
|
},
|
||||||
|
EnableAutoSync: {
|
||||||
|
Title: "自动同步设置",
|
||||||
|
SubTitle: "在回复完成或删除消息后自动同步数据",
|
||||||
|
},
|
||||||
Proxy: {
|
Proxy: {
|
||||||
Title: "启用代理",
|
Title: "启用代理",
|
||||||
SubTitle: "在浏览器中同步时,必须启用代理以避免跨域限制",
|
SubTitle: "在浏览器中同步时,必须启用代理以避免跨域限制",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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[]) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue