From 1d0a40b9e876fb7edb76480b8242cc79d03c6bab Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Sun, 31 Dec 2023 19:44:51 +0800 Subject: [PATCH 01/18] chore: low the google safety setting to avoid unexpected blocking --- app/client/platforms/google.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts index 1dfcb660d3b..f0f63659f2b 100644 --- a/app/client/platforms/google.ts +++ b/app/client/platforms/google.ts @@ -59,6 +59,24 @@ export class GeminiProApi implements LLMApi { topP: modelConfig.top_p, // "topK": modelConfig.top_k, }, + safetySettings: [ + { + category: "HARM_CATEGORY_HARASSMENT", + threshold: "BLOCK_ONLY_HIGH", + }, + { + category: "HARM_CATEGORY_HATE_SPEECH", + threshold: "BLOCK_ONLY_HIGH", + }, + { + category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", + threshold: "BLOCK_ONLY_HIGH", + }, + { + category: "HARM_CATEGORY_DANGEROUS_CONTENT", + threshold: "BLOCK_ONLY_HIGH", + }, + ], }; console.log("[Request] google payload: ", requestPayload); From 22f61295bc495da4e18a834cafddee7e34fad131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Fri, 2 Aug 2024 21:05:21 +0800 Subject: [PATCH 02/18] fix: Fixed an issue where the sample of the reply content was displayed out of order (cherry picked from commit 8498cadae8f394c680be6addf35a489e75d33954) --- app/components/chat.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index bb4b611ad79..5b4adca862a 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -1470,6 +1470,7 @@ function _Chat() { )}
Date: Sat, 3 Aug 2024 12:40:33 +0800 Subject: [PATCH 03/18] fix: Fixed the issue that WebDAV synchronization could not check the status and failed during the first backup (cherry picked from commit 716899c030646402fd5562496a12e2cd385d169d) --- app/api/webdav/[...path]/route.ts | 17 +++++++++++------ app/utils/cloud/webdav.ts | 14 +++++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/api/webdav/[...path]/route.ts b/app/api/webdav/[...path]/route.ts index 1f58a884fe3..9f96cbfcf74 100644 --- a/app/api/webdav/[...path]/route.ts +++ b/app/api/webdav/[...path]/route.ts @@ -29,6 +29,7 @@ async function handle( const requestUrl = new URL(req.url); let endpoint = requestUrl.searchParams.get("endpoint"); + let proxy_method = requestUrl.searchParams.get("proxy_method") || req.method; // Validate the endpoint to prevent potential SSRF attacks if ( @@ -65,7 +66,11 @@ async function handle( const targetPath = `${endpoint}${endpointPath}`; // only allow MKCOL, GET, PUT - if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") { + if ( + proxy_method !== "MKCOL" && + proxy_method !== "GET" && + proxy_method !== "PUT" + ) { return NextResponse.json( { error: true, @@ -78,7 +83,7 @@ async function handle( } // for MKCOL request, only allow request ${folder} - if (req.method === "MKCOL" && !targetPath.endsWith(folder)) { + if (proxy_method === "MKCOL" && !targetPath.endsWith(folder)) { return NextResponse.json( { error: true, @@ -91,7 +96,7 @@ async function handle( } // for GET request, only allow request ending with fileName - if (req.method === "GET" && !targetPath.endsWith(fileName)) { + if (proxy_method === "GET" && !targetPath.endsWith(fileName)) { return NextResponse.json( { error: true, @@ -104,7 +109,7 @@ async function handle( } // for PUT request, only allow request ending with fileName - if (req.method === "PUT" && !targetPath.endsWith(fileName)) { + if (proxy_method === "PUT" && !targetPath.endsWith(fileName)) { return NextResponse.json( { error: true, @@ -118,7 +123,7 @@ async function handle( const targetUrl = targetPath; - const method = req.method; + const method = proxy_method || req.method; const shouldNotHaveBody = ["get", "head"].includes( method?.toLowerCase() ?? "", ); @@ -143,7 +148,7 @@ async function handle( "[Any Proxy]", targetUrl, { - method: req.method, + method: method, }, { status: fetchResult?.status, diff --git a/app/utils/cloud/webdav.ts b/app/utils/cloud/webdav.ts index 0ca781b7584..aa42649ca11 100644 --- a/app/utils/cloud/webdav.ts +++ b/app/utils/cloud/webdav.ts @@ -14,8 +14,8 @@ export function createWebDavClient(store: SyncStore) { return { async check() { try { - const res = await fetch(this.path(folder, proxyUrl), { - method: "MKCOL", + const res = await fetch(this.path(folder, proxyUrl, "MKCOL"), { + method: "GET", headers: this.headers(), }); const success = [201, 200, 404, 405, 301, 302, 307, 308].includes( @@ -42,6 +42,10 @@ export function createWebDavClient(store: SyncStore) { console.log("[WebDav] get key = ", key, res.status, res.statusText); + if (404 == res.status) { + return ""; + } + return await res.text(); }, @@ -62,7 +66,7 @@ export function createWebDavClient(store: SyncStore) { authorization: `Basic ${auth}`, }; }, - path(path: string, proxyUrl: string = "") { + path(path: string, proxyUrl: string = "", proxyMethod: string = "") { if (path.startsWith("/")) { path = path.slice(1); } @@ -78,9 +82,13 @@ export function createWebDavClient(store: SyncStore) { let u = new URL(proxyUrl + pathPrefix + path); // add query params u.searchParams.append("endpoint", config.endpoint); + proxyMethod && u.searchParams.append("proxy_method", proxyMethod); url = u.toString(); } catch (e) { url = pathPrefix + path + "?endpoint=" + config.endpoint; + if (proxyMethod) { + url += "&proxy_method=" + proxyMethod; + } } return url; From 22c79595fb2ed28c21017fd63a42cfdf1bf70863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Sat, 3 Aug 2024 20:53:36 +0800 Subject: [PATCH 04/18] feat: The cloud synchronization feature is enhanced to support the synchronization of deleted conversations and deleted messages --- app/components/chat.tsx | 19 ++++++++++++---- app/store/chat.ts | 24 +++++++++++++++++++-- app/utils.ts | 13 +++++++++++ app/utils/sync.ts | 48 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 5b4adca862a..c329c563af1 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -60,6 +60,7 @@ import { getMessageTextContent, getMessageImages, isVisionModel, + removeOutdatedEntries, } from "../utils"; import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; @@ -923,10 +924,20 @@ function _Chat() { }; const deleteMessage = (msgId?: string) => { - chatStore.updateCurrentSession( - (session) => - (session.messages = session.messages.filter((m) => m.id !== msgId)), - ); + chatStore.updateCurrentSession((session) => { + session.deletedMessageIds && + removeOutdatedEntries(session.deletedMessageIds); + session.messages = session.messages.filter((m) => { + if (m.id !== msgId) { + return true; + } + if (!session.deletedMessageIds) { + session.deletedMessageIds = {} as Record; + } + session.deletedMessageIds[m.id] = Date.now(); + return false; + }); + }); }; const onDelete = (msgId: string) => { diff --git a/app/store/chat.ts b/app/store/chat.ts index 5892ef0c8c6..554b077ff08 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -1,4 +1,8 @@ -import { trimTopic, getMessageTextContent } from "../utils"; +import { + trimTopic, + getMessageTextContent, + removeOutdatedEntries, +} from "../utils"; import Locale, { getLang } from "../locales"; import { showToast } from "../components/ui-lib"; @@ -61,6 +65,7 @@ export interface ChatSession { lastUpdate: number; lastSummarizeIndex: number; clearContextIndex?: number; + deletedMessageIds?: Record; mask: Mask; } @@ -84,6 +89,7 @@ function createEmptySession(): ChatSession { }, lastUpdate: Date.now(), lastSummarizeIndex: 0, + deletedMessageIds: {}, mask: createEmptyMask(), }; @@ -164,6 +170,7 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) { const DEFAULT_CHAT_STATE = { sessions: [createEmptySession()], currentSessionIndex: 0, + deletedSessionIds: {} as Record, }; export const useChatStore = createPersistStore( @@ -252,7 +259,18 @@ export const useChatStore = createPersistStore( if (!deletedSession) return; const sessions = get().sessions.slice(); - sessions.splice(index, 1); + const deletedSessionIds = { ...get().deletedSessionIds }; + + removeOutdatedEntries(deletedSessionIds); + + const hasDelSessions = sessions.splice(index, 1); + if (hasDelSessions?.length) { + hasDelSessions.forEach((session) => { + if (session.messages.length > 0) { + deletedSessionIds[session.id] = Date.now(); + } + }); + } const currentIndex = get().currentSessionIndex; let nextIndex = Math.min( @@ -269,11 +287,13 @@ export const useChatStore = createPersistStore( const restoreState = { currentSessionIndex: get().currentSessionIndex, sessions: get().sessions.slice(), + deletedSessionIds: get().deletedSessionIds, }; set(() => ({ currentSessionIndex: nextIndex, sessions, + deletedSessionIds, })); showToast( diff --git a/app/utils.ts b/app/utils.ts index 2f2c8ae95ab..741540552d6 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -265,3 +265,16 @@ export function isVisionModel(model: string) { visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo ); } + +export function removeOutdatedEntries( + timeMap: Record, +): Record { + const oneMonthAgo = Date.now() - 30 * 24 * 60 * 60 * 1000; + // Delete data from a month ago + Object.keys(timeMap).forEach((id) => { + if (timeMap[id] < oneMonthAgo) { + delete timeMap[id]; + } + }); + return timeMap; +} diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 1acfc1289de..1c8f0a11bf9 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -8,6 +8,7 @@ import { useMaskStore } from "../store/mask"; import { usePromptStore } from "../store/prompt"; import { StoreKey } from "../constant"; import { merge } from "./merge"; +import { removeOutdatedEntries } from "@/app/utils"; type NonFunctionKeys = { [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K; @@ -66,6 +67,7 @@ const MergeStates: StateMerger = { [StoreKey.Chat]: (localState, remoteState) => { // merge sessions const localSessions: Record = {}; + const localDeletedSessionIds = localState.deletedSessionIds || {}; localState.sessions.forEach((s) => (localSessions[s.id] = s)); remoteState.sessions.forEach((remoteSession) => { @@ -75,29 +77,71 @@ const MergeStates: StateMerger = { const localSession = localSessions[remoteSession.id]; if (!localSession) { // if remote session is new, just merge it - localState.sessions.push(remoteSession); + if ( + (localDeletedSessionIds[remoteSession.id] || -1) < + remoteSession.lastUpdate + ) { + 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)); + const localDeletedMessageIds = localSession.deletedMessageIds || {}; remoteSession.messages.forEach((m) => { if (!localMessageIds.has(m.id)) { - localSession.messages.push(m); + if ( + !localDeletedMessageIds[m.id] || + new Date(localDeletedMessageIds[m.id]).toLocaleString() < m.date + ) { + localSession.messages.push(m); + } } }); + const remoteDeletedMessageIds = remoteSession.deletedMessageIds || {}; + localSession.messages = localSession.messages.filter((localMessage) => { + return ( + !remoteDeletedMessageIds[localMessage.id] || + new Date(localDeletedMessageIds[localMessage.id]).toLocaleString() < + localMessage.date + ); + }); + // sort local messages with date field in asc order localSession.messages.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), ); + + const deletedMessageIds = { + ...remoteDeletedMessageIds, + ...localDeletedMessageIds, + }; + removeOutdatedEntries(deletedMessageIds); + localSession.deletedMessageIds = deletedMessageIds; } }); + const remoteDeletedSessionIds = remoteState.deletedSessionIds || {}; + localState.sessions = localState.sessions.filter((localSession) => { + return ( + (remoteDeletedSessionIds[localSession.id] || -1) <= + localSession.lastUpdate + ); + }); + // sort local sessions with date field in desc order localState.sessions.sort( (a, b) => new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), ); + const deletedSessionIds = { + ...remoteDeletedSessionIds, + ...localDeletedSessionIds, + }; + removeOutdatedEntries(deletedSessionIds); + localState.deletedSessionIds = deletedSessionIds; + return localState; }, [StoreKey.Prompt]: (localState, remoteState) => { From 648e60028dedc9db2a8c0b53e5d7c92ddcaf4baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Sat, 3 Aug 2024 20:53:36 +0800 Subject: [PATCH 05/18] feat: The cloud synchronization feature is enhanced to support the synchronization of deleted conversations and deleted messages --- app/components/chat.tsx | 19 ++++++++++++---- app/store/chat.ts | 24 +++++++++++++++++++-- app/utils.ts | 13 +++++++++++ app/utils/sync.ts | 48 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index b18c86708a7..3ff6e939249 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -62,6 +62,7 @@ import { getMessageImages, isVisionModel, isDalle3, + removeOutdatedEntries, } from "../utils"; import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; @@ -959,10 +960,20 @@ function _Chat() { }; const deleteMessage = (msgId?: string) => { - chatStore.updateCurrentSession( - (session) => - (session.messages = session.messages.filter((m) => m.id !== msgId)), - ); + chatStore.updateCurrentSession((session) => { + session.deletedMessageIds && + removeOutdatedEntries(session.deletedMessageIds); + session.messages = session.messages.filter((m) => { + if (m.id !== msgId) { + return true; + } + if (!session.deletedMessageIds) { + session.deletedMessageIds = {} as Record; + } + session.deletedMessageIds[m.id] = Date.now(); + return false; + }); + }); }; const onDelete = (msgId: string) => { diff --git a/app/store/chat.ts b/app/store/chat.ts index 653926d1b02..10c95ee86e5 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -1,4 +1,8 @@ -import { trimTopic, getMessageTextContent } from "../utils"; +import { + trimTopic, + getMessageTextContent, + removeOutdatedEntries, +} from "../utils"; import Locale, { getLang } from "../locales"; import { showToast } from "../components/ui-lib"; @@ -62,6 +66,7 @@ export interface ChatSession { lastUpdate: number; lastSummarizeIndex: number; clearContextIndex?: number; + deletedMessageIds?: Record; mask: Mask; } @@ -85,6 +90,7 @@ function createEmptySession(): ChatSession { }, lastUpdate: Date.now(), lastSummarizeIndex: 0, + deletedMessageIds: {}, mask: createEmptyMask(), }; @@ -165,6 +171,7 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) { const DEFAULT_CHAT_STATE = { sessions: [createEmptySession()], currentSessionIndex: 0, + deletedSessionIds: {} as Record, }; export const useChatStore = createPersistStore( @@ -253,7 +260,18 @@ export const useChatStore = createPersistStore( if (!deletedSession) return; const sessions = get().sessions.slice(); - sessions.splice(index, 1); + const deletedSessionIds = { ...get().deletedSessionIds }; + + removeOutdatedEntries(deletedSessionIds); + + const hasDelSessions = sessions.splice(index, 1); + if (hasDelSessions?.length) { + hasDelSessions.forEach((session) => { + if (session.messages.length > 0) { + deletedSessionIds[session.id] = Date.now(); + } + }); + } const currentIndex = get().currentSessionIndex; let nextIndex = Math.min( @@ -270,11 +288,13 @@ export const useChatStore = createPersistStore( const restoreState = { currentSessionIndex: get().currentSessionIndex, sessions: get().sessions.slice(), + deletedSessionIds: get().deletedSessionIds, }; set(() => ({ currentSessionIndex: nextIndex, sessions, + deletedSessionIds, })); showToast( diff --git a/app/utils.ts b/app/utils.ts index 2a292290755..e49d272826b 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -270,3 +270,16 @@ export function isVisionModel(model: string) { export function isDalle3(model: string) { return "dall-e-3" === model; } + +export function removeOutdatedEntries( + timeMap: Record, +): Record { + const oneMonthAgo = Date.now() - 30 * 24 * 60 * 60 * 1000; + // Delete data from a month ago + Object.keys(timeMap).forEach((id) => { + if (timeMap[id] < oneMonthAgo) { + delete timeMap[id]; + } + }); + return timeMap; +} diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 1acfc1289de..1c8f0a11bf9 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -8,6 +8,7 @@ import { useMaskStore } from "../store/mask"; import { usePromptStore } from "../store/prompt"; import { StoreKey } from "../constant"; import { merge } from "./merge"; +import { removeOutdatedEntries } from "@/app/utils"; type NonFunctionKeys = { [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K; @@ -66,6 +67,7 @@ const MergeStates: StateMerger = { [StoreKey.Chat]: (localState, remoteState) => { // merge sessions const localSessions: Record = {}; + const localDeletedSessionIds = localState.deletedSessionIds || {}; localState.sessions.forEach((s) => (localSessions[s.id] = s)); remoteState.sessions.forEach((remoteSession) => { @@ -75,29 +77,71 @@ const MergeStates: StateMerger = { const localSession = localSessions[remoteSession.id]; if (!localSession) { // if remote session is new, just merge it - localState.sessions.push(remoteSession); + if ( + (localDeletedSessionIds[remoteSession.id] || -1) < + remoteSession.lastUpdate + ) { + 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)); + const localDeletedMessageIds = localSession.deletedMessageIds || {}; remoteSession.messages.forEach((m) => { if (!localMessageIds.has(m.id)) { - localSession.messages.push(m); + if ( + !localDeletedMessageIds[m.id] || + new Date(localDeletedMessageIds[m.id]).toLocaleString() < m.date + ) { + localSession.messages.push(m); + } } }); + const remoteDeletedMessageIds = remoteSession.deletedMessageIds || {}; + localSession.messages = localSession.messages.filter((localMessage) => { + return ( + !remoteDeletedMessageIds[localMessage.id] || + new Date(localDeletedMessageIds[localMessage.id]).toLocaleString() < + localMessage.date + ); + }); + // sort local messages with date field in asc order localSession.messages.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), ); + + const deletedMessageIds = { + ...remoteDeletedMessageIds, + ...localDeletedMessageIds, + }; + removeOutdatedEntries(deletedMessageIds); + localSession.deletedMessageIds = deletedMessageIds; } }); + const remoteDeletedSessionIds = remoteState.deletedSessionIds || {}; + localState.sessions = localState.sessions.filter((localSession) => { + return ( + (remoteDeletedSessionIds[localSession.id] || -1) <= + localSession.lastUpdate + ); + }); + // sort local sessions with date field in desc order localState.sessions.sort( (a, b) => new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), ); + const deletedSessionIds = { + ...remoteDeletedSessionIds, + ...localDeletedSessionIds, + }; + removeOutdatedEntries(deletedSessionIds); + localState.deletedSessionIds = deletedSessionIds; + return localState; }, [StoreKey.Prompt]: (localState, remoteState) => { From 4b22aaf979b6f61ba8a7246fabbb27aa9225955e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Thu, 15 Aug 2024 22:39:30 +0800 Subject: [PATCH 06/18] feat: Add automatic data synchronization settings and implementation, enabling auto-sync after completing replies or deleting conversations --- app/components/settings.tsx | 15 +++++++++++++++ app/locales/cn.ts | 4 ++++ app/locales/en.ts | 5 +++++ app/store/chat.ts | 14 ++++++++++++++ app/store/sync.ts | 22 +++++++++++++++++++--- app/utils/sync.ts | 8 ++++---- 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 71fd2d8398e..ffd0a4a8da5 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -355,6 +355,21 @@ function SyncConfigModal(props: { onClose?: () => void }) { + + { + syncStore.update( + (config) => (config.enableAutoSync = e.currentTarget.checked), + ); + }} + > + + { + syncStore.autoSync(); + }, 500); +} + const DEFAULT_CHAT_STATE = { sessions: [createEmptySession()], currentSessionIndex: 0, @@ -297,12 +307,15 @@ export const useChatStore = createPersistStore( deletedSessionIds, })); + noticeCloudSync(); + showToast( Locale.Home.DeleteToast, { text: Locale.Home.Revert, onClick() { set(() => restoreState); + noticeCloudSync(); }, }, 5000, @@ -330,6 +343,7 @@ export const useChatStore = createPersistStore( }); get().updateStat(message); get().summarizeSession(); + noticeCloudSync(); }, async onUserInput(content: string, attachImages?: string[]) { diff --git a/app/store/sync.ts b/app/store/sync.ts index d3582e3c935..e20f42bf596 100644 --- a/app/store/sync.ts +++ b/app/store/sync.ts @@ -26,6 +26,7 @@ export type SyncStore = GetStoreState; const DEFAULT_SYNC_STATE = { provider: ProviderType.WebDAV, + enableAutoSync: true, useProxy: true, proxyUrl: corsPath(ApiPath.Cors), @@ -91,6 +92,11 @@ export const useSyncStore = createPersistStore( }, async sync() { + const enableAutoSync = get().enableAutoSync; + if (!enableAutoSync) { + return; + } + const localState = getLocalAppState(); const provider = get().provider; const config = get()[provider]; @@ -100,15 +106,17 @@ export const useSyncStore = createPersistStore( const remoteState = await client.get(config.username); if (!remoteState || remoteState === "") { await client.set(config.username, JSON.stringify(localState)); - console.log("[Sync] Remote state is empty, using local state instead."); - return + console.log( + "[Sync] Remote state is empty, using local state instead.", + ); + return; } else { const parsedRemoteState = JSON.parse( await client.get(config.username), ) as AppState; mergeAppState(localState, parsedRemoteState); setLocalAppState(localState); - } + } } catch (e) { console.log("[Sync] failed to get remote state", e); throw e; @@ -123,6 +131,14 @@ export const useSyncStore = createPersistStore( const client = this.getClient(); return await client.check(); }, + + async autoSync() { + const { lastSyncTime, provider } = get(); + const syncStore = useSyncStore.getState(); + if (lastSyncTime && syncStore.cloudSync()) { + syncStore.sync(); + } + }, }), { name: StoreKey.Sync, diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 1c8f0a11bf9..0943260abc2 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -130,10 +130,10 @@ const MergeStates: StateMerger = { }); // sort local sessions with date field in desc order - localState.sessions.sort( - (a, b) => - new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), - ); + // localState.sessions.sort( + // (a, b) => + // new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), + // ); const deletedSessionIds = { ...remoteDeletedSessionIds, From eae593d660fb04acb99ecae40719e9e4b47404c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Thu, 15 Aug 2024 22:39:30 +0800 Subject: [PATCH 07/18] feat: Add automatic data synchronization settings and implementation, enabling auto-sync after completing replies or deleting conversations --- app/components/settings.tsx | 15 +++++++++++++++ app/locales/cn.ts | 4 ++++ app/locales/en.ts | 5 +++++ app/store/chat.ts | 14 ++++++++++++++ app/store/sync.ts | 22 +++++++++++++++++++--- app/utils/sync.ts | 8 ++++---- 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index ca0a5a18796..b265d78ded2 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -357,6 +357,21 @@ function SyncConfigModal(props: { onClose?: () => void }) { + + { + syncStore.update( + (config) => (config.enableAutoSync = e.currentTarget.checked), + ); + }} + > + + { + syncStore.autoSync(); + }, 500); +} + const DEFAULT_CHAT_STATE = { sessions: [createEmptySession()], currentSessionIndex: 0, @@ -297,12 +307,15 @@ export const useChatStore = createPersistStore( deletedSessionIds, })); + noticeCloudSync(); + showToast( Locale.Home.DeleteToast, { text: Locale.Home.Revert, onClick() { set(() => restoreState); + noticeCloudSync(); }, }, 5000, @@ -330,6 +343,7 @@ export const useChatStore = createPersistStore( }); get().updateStat(message); get().summarizeSession(); + noticeCloudSync(); }, async onUserInput(content: string, attachImages?: string[]) { diff --git a/app/store/sync.ts b/app/store/sync.ts index d3582e3c935..e20f42bf596 100644 --- a/app/store/sync.ts +++ b/app/store/sync.ts @@ -26,6 +26,7 @@ export type SyncStore = GetStoreState; const DEFAULT_SYNC_STATE = { provider: ProviderType.WebDAV, + enableAutoSync: true, useProxy: true, proxyUrl: corsPath(ApiPath.Cors), @@ -91,6 +92,11 @@ export const useSyncStore = createPersistStore( }, async sync() { + const enableAutoSync = get().enableAutoSync; + if (!enableAutoSync) { + return; + } + const localState = getLocalAppState(); const provider = get().provider; const config = get()[provider]; @@ -100,15 +106,17 @@ export const useSyncStore = createPersistStore( const remoteState = await client.get(config.username); if (!remoteState || remoteState === "") { await client.set(config.username, JSON.stringify(localState)); - console.log("[Sync] Remote state is empty, using local state instead."); - return + console.log( + "[Sync] Remote state is empty, using local state instead.", + ); + return; } else { const parsedRemoteState = JSON.parse( await client.get(config.username), ) as AppState; mergeAppState(localState, parsedRemoteState); setLocalAppState(localState); - } + } } catch (e) { console.log("[Sync] failed to get remote state", e); throw e; @@ -123,6 +131,14 @@ export const useSyncStore = createPersistStore( const client = this.getClient(); return await client.check(); }, + + async autoSync() { + const { lastSyncTime, provider } = get(); + const syncStore = useSyncStore.getState(); + if (lastSyncTime && syncStore.cloudSync()) { + syncStore.sync(); + } + }, }), { name: StoreKey.Sync, diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 1c8f0a11bf9..0943260abc2 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -130,10 +130,10 @@ const MergeStates: StateMerger = { }); // sort local sessions with date field in desc order - localState.sessions.sort( - (a, b) => - new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), - ); + // localState.sessions.sort( + // (a, b) => + // new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), + // ); const deletedSessionIds = { ...remoteDeletedSessionIds, From b2336f5ed94721771a4e4e858af69bd81114ff40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= <1887944+ahzmr@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:55:55 +0800 Subject: [PATCH 08/18] =?UTF-8?q?=E6=9B=B4=E6=96=B0docker.yml,=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=87=AA=E5=8A=A8=E7=BC=96=E8=AF=91=E7=9A=84=E9=95=9C?= =?UTF-8?q?=E5=83=8F=E4=B8=BA=E8=87=AA=E5=B7=B1=E7=9A=84=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8ac96f19356..d9c21a0116c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,7 +25,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: yidadaa/chatgpt-next-web + images: ahzmr/chatgpt-next-web tags: | type=raw,value=latest type=ref,event=tag From e515f0f957763e638aa2595e764e6b5dcd4348a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= <1887944+ahzmr@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:55:55 +0800 Subject: [PATCH 09/18] =?UTF-8?q?=E6=9B=B4=E6=96=B0docker.yml,=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=87=AA=E5=8A=A8=E7=BC=96=E8=AF=91=E7=9A=84=E9=95=9C?= =?UTF-8?q?=E5=83=8F=E4=B8=BA=E8=87=AA=E5=B7=B1=E7=9A=84=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit b2336f5ed94721771a4e4e858af69bd81114ff40) --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8ac96f19356..d9c21a0116c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,7 +25,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: yidadaa/chatgpt-next-web + images: ahzmr/chatgpt-next-web tags: | type=raw,value=latest type=ref,event=tag From fdb89af355737cc47f072d5f0dfc58c3eea4d93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= <1887944+ahzmr@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:19:06 +0800 Subject: [PATCH 10/18] =?UTF-8?q?=E6=9B=B4=E6=96=B0docker.yml=EF=BC=8C?= =?UTF-8?q?=E4=BD=BFimage=E5=90=8D=E8=87=AA=E9=80=82=E5=BA=94=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=BD=B1=E5=93=8D=E4=B8=BB=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d9c21a0116c..65ae617f55c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,26 +19,26 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - - + + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v4 with: - images: ahzmr/chatgpt-next-web + images: ${{ secrets.DOCKER_USERNAME }}/chatgpt-next-web tags: | type=raw,value=latest type=ref,event=tag - - - + + - name: Set up QEMU uses: docker/setup-qemu-action@v2 - - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - - + + - name: Build and push Docker image uses: docker/build-push-action@v4 with: @@ -49,4 +49,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - + From fc97c4b06fe8b6ed769697402fc1060f6e1de6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= <1887944+ahzmr@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:19:06 +0800 Subject: [PATCH 11/18] =?UTF-8?q?=E6=9B=B4=E6=96=B0docker.yml=EF=BC=8C?= =?UTF-8?q?=E4=BD=BFimage=E5=90=8D=E8=87=AA=E9=80=82=E5=BA=94=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=BD=B1=E5=93=8D=E4=B8=BB=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit fdb89af355737cc47f072d5f0dfc58c3eea4d93e) --- .github/workflows/docker.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d9c21a0116c..65ae617f55c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,26 +19,26 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - - + + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v4 with: - images: ahzmr/chatgpt-next-web + images: ${{ secrets.DOCKER_USERNAME }}/chatgpt-next-web tags: | type=raw,value=latest type=ref,event=tag - - - + + - name: Set up QEMU uses: docker/setup-qemu-action@v2 - - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - - + + - name: Build and push Docker image uses: docker/build-push-action@v4 with: @@ -49,4 +49,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - + From d0b7ddc1d68e3cc4dec7341f6ce14277b2346f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Tue, 20 Aug 2024 21:18:28 +0800 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=88=97=E8=A1=A8=E6=8C=89=E6=9C=80=E5=90=8E=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=97=B6=E9=97=B4=E5=80=92=E5=BA=8F=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B9=E4=BE=BF=E6=9F=A5=E7=9C=8B=E4=B8=8E?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/store/chat.ts | 19 +++++++++++++++++++ app/store/sync.ts | 11 ++++++++--- app/utils/sync.ts | 20 ++++++++++++++++---- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/store/chat.ts b/app/store/chat.ts index 3dbda736246..4aa58a07376 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -336,6 +336,24 @@ export const useChatStore = createPersistStore( return session; }, + sortSessions() { + const currentSession = get().currentSession(); + const sessions = get().sessions.slice(); + + sessions.sort( + (a, b) => + new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), + ); + const currentSessionIndex = sessions.findIndex((session) => { + return session && currentSession && session.id === session.id; + }); + + set((state) => ({ + currentSessionIndex, + sessions, + })); + }, + onNewMessage(message: ChatMessage) { get().updateCurrentSession((session) => { session.messages = session.messages.concat(); @@ -343,6 +361,7 @@ export const useChatStore = createPersistStore( }); get().updateStat(message); get().summarizeSession(); + get().sortSessions(); noticeCloudSync(); }, diff --git a/app/store/sync.ts b/app/store/sync.ts index e20f42bf596..ca3f3283d09 100644 --- a/app/store/sync.ts +++ b/app/store/sync.ts @@ -46,6 +46,8 @@ const DEFAULT_SYNC_STATE = { lastProvider: "", }; +let lastSyncTime = 0; + export const useSyncStore = createPersistStore( DEFAULT_SYNC_STATE, (set, get) => ({ @@ -92,6 +94,11 @@ export const useSyncStore = createPersistStore( }, async sync() { + if (lastSyncTime && lastSyncTime >= Date.now() - 800) { + return; + } + lastSyncTime = Date.now(); + const enableAutoSync = get().enableAutoSync; if (!enableAutoSync) { return; @@ -111,9 +118,7 @@ export const useSyncStore = createPersistStore( ); return; } else { - const parsedRemoteState = JSON.parse( - await client.get(config.username), - ) as AppState; + const parsedRemoteState = JSON.parse(remoteState) as AppState; mergeAppState(localState, parsedRemoteState); setLocalAppState(localState); } diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 0943260abc2..576c18b58fd 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -66,6 +66,8 @@ type StateMerger = { const MergeStates: StateMerger = { [StoreKey.Chat]: (localState, remoteState) => { // merge sessions + const currentSession = useChatStore.getState().currentSession(); + const localSessions: Record = {}; const localDeletedSessionIds = localState.deletedSessionIds || {}; localState.sessions.forEach((s) => (localSessions[s.id] = s)); @@ -111,6 +113,10 @@ const MergeStates: StateMerger = { localSession.messages.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), ); + localSession.lastUpdate = Math.max( + remoteSession.lastUpdate, + localSession.lastUpdate, + ); const deletedMessageIds = { ...remoteDeletedMessageIds, @@ -130,10 +136,10 @@ const MergeStates: StateMerger = { }); // sort local sessions with date field in desc order - // localState.sessions.sort( - // (a, b) => - // new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), - // ); + localState.sessions.sort( + (a, b) => + new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), + ); const deletedSessionIds = { ...remoteDeletedSessionIds, @@ -142,6 +148,12 @@ const MergeStates: StateMerger = { removeOutdatedEntries(deletedSessionIds); localState.deletedSessionIds = deletedSessionIds; + localState.currentSessionIndex = localState.sessions.findIndex( + (session) => { + return session && currentSession && session.id === session.id; + }, + ); + return localState; }, [StoreKey.Prompt]: (localState, remoteState) => { From 5c51fd2ed8075de501df55b0a0b7bf1c3b554c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Tue, 20 Aug 2024 21:18:28 +0800 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=88=97=E8=A1=A8=E6=8C=89=E6=9C=80=E5=90=8E=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=97=B6=E9=97=B4=E5=80=92=E5=BA=8F=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B9=E4=BE=BF=E6=9F=A5=E7=9C=8B=E4=B8=8E?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/store/chat.ts | 19 +++++++++++++++++++ app/store/sync.ts | 11 ++++++++--- app/utils/sync.ts | 20 ++++++++++++++++---- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/store/chat.ts b/app/store/chat.ts index 3dbda736246..4aa58a07376 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -336,6 +336,24 @@ export const useChatStore = createPersistStore( return session; }, + sortSessions() { + const currentSession = get().currentSession(); + const sessions = get().sessions.slice(); + + sessions.sort( + (a, b) => + new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), + ); + const currentSessionIndex = sessions.findIndex((session) => { + return session && currentSession && session.id === session.id; + }); + + set((state) => ({ + currentSessionIndex, + sessions, + })); + }, + onNewMessage(message: ChatMessage) { get().updateCurrentSession((session) => { session.messages = session.messages.concat(); @@ -343,6 +361,7 @@ export const useChatStore = createPersistStore( }); get().updateStat(message); get().summarizeSession(); + get().sortSessions(); noticeCloudSync(); }, diff --git a/app/store/sync.ts b/app/store/sync.ts index e20f42bf596..ca3f3283d09 100644 --- a/app/store/sync.ts +++ b/app/store/sync.ts @@ -46,6 +46,8 @@ const DEFAULT_SYNC_STATE = { lastProvider: "", }; +let lastSyncTime = 0; + export const useSyncStore = createPersistStore( DEFAULT_SYNC_STATE, (set, get) => ({ @@ -92,6 +94,11 @@ export const useSyncStore = createPersistStore( }, async sync() { + if (lastSyncTime && lastSyncTime >= Date.now() - 800) { + return; + } + lastSyncTime = Date.now(); + const enableAutoSync = get().enableAutoSync; if (!enableAutoSync) { return; @@ -111,9 +118,7 @@ export const useSyncStore = createPersistStore( ); return; } else { - const parsedRemoteState = JSON.parse( - await client.get(config.username), - ) as AppState; + const parsedRemoteState = JSON.parse(remoteState) as AppState; mergeAppState(localState, parsedRemoteState); setLocalAppState(localState); } diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 0943260abc2..576c18b58fd 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -66,6 +66,8 @@ type StateMerger = { const MergeStates: StateMerger = { [StoreKey.Chat]: (localState, remoteState) => { // merge sessions + const currentSession = useChatStore.getState().currentSession(); + const localSessions: Record = {}; const localDeletedSessionIds = localState.deletedSessionIds || {}; localState.sessions.forEach((s) => (localSessions[s.id] = s)); @@ -111,6 +113,10 @@ const MergeStates: StateMerger = { localSession.messages.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), ); + localSession.lastUpdate = Math.max( + remoteSession.lastUpdate, + localSession.lastUpdate, + ); const deletedMessageIds = { ...remoteDeletedMessageIds, @@ -130,10 +136,10 @@ const MergeStates: StateMerger = { }); // sort local sessions with date field in desc order - // localState.sessions.sort( - // (a, b) => - // new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), - // ); + localState.sessions.sort( + (a, b) => + new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), + ); const deletedSessionIds = { ...remoteDeletedSessionIds, @@ -142,6 +148,12 @@ const MergeStates: StateMerger = { removeOutdatedEntries(deletedSessionIds); localState.deletedSessionIds = deletedSessionIds; + localState.currentSessionIndex = localState.sessions.findIndex( + (session) => { + return session && currentSession && session.id === session.id; + }, + ); + return localState; }, [StoreKey.Prompt]: (localState, remoteState) => { From 2fdb35bcc86ef18d3f01b5ccbb979dbb0d7384e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Tue, 20 Aug 2024 22:21:38 +0800 Subject: [PATCH 14/18] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=88=97=E8=A1=A8=E6=8C=89=E6=9C=80=E6=96=B0=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=B6=E9=97=B4=E5=80=92=E5=BA=8F=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=EF=BC=8C=E5=BD=93=E5=89=8D=E4=BC=9A=E8=AF=9D=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/store/chat.ts | 2 +- app/utils/sync.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/store/chat.ts b/app/store/chat.ts index 4aa58a07376..5db48d2c94e 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -345,7 +345,7 @@ export const useChatStore = createPersistStore( new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), ); const currentSessionIndex = sessions.findIndex((session) => { - return session && currentSession && session.id === session.id; + return session && currentSession && session.id === currentSession.id; }); set((state) => ({ diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 576c18b58fd..645d8ac1151 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -150,7 +150,7 @@ const MergeStates: StateMerger = { localState.currentSessionIndex = localState.sessions.findIndex( (session) => { - return session && currentSession && session.id === session.id; + return session && currentSession && session.id === currentSession.id; }, ); From 31baa10363baa13ebff2e9d7f328c4f95d4d7536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=B6=85?= Date: Tue, 20 Aug 2024 22:21:38 +0800 Subject: [PATCH 15/18] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=88=97=E8=A1=A8=E6=8C=89=E6=9C=80=E6=96=B0=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=B6=E9=97=B4=E5=80=92=E5=BA=8F=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=EF=BC=8C=E5=BD=93=E5=89=8D=E4=BC=9A=E8=AF=9D=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/store/chat.ts | 2 +- app/utils/sync.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/store/chat.ts b/app/store/chat.ts index 4aa58a07376..5db48d2c94e 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -345,7 +345,7 @@ export const useChatStore = createPersistStore( new Date(b.lastUpdate).getTime() - new Date(a.lastUpdate).getTime(), ); const currentSessionIndex = sessions.findIndex((session) => { - return session && currentSession && session.id === session.id; + return session && currentSession && session.id === currentSession.id; }); set((state) => ({ diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 576c18b58fd..645d8ac1151 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -150,7 +150,7 @@ const MergeStates: StateMerger = { localState.currentSessionIndex = localState.sessions.findIndex( (session) => { - return session && currentSession && session.id === session.id; + return session && currentSession && session.id === currentSession.id; }, ); From ccacfec918dede1906d72f9366c973e3de96178b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= Date: Thu, 5 Sep 2024 21:19:03 +0800 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=AA=97=E5=8F=A3=EF=BC=8C=E4=BD=BF=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E4=BC=9A=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/command.ts | 1 + app/components/chat.tsx | 1 + app/locales/cn.ts | 1 + app/locales/en.ts | 1 + app/store/chat.ts | 22 ++++++++++++++++++++++ 5 files changed, 26 insertions(+) diff --git a/app/command.ts b/app/command.ts index bea4e06f381..0e4bba85afe 100644 --- a/app/command.ts +++ b/app/command.ts @@ -35,6 +35,7 @@ export function useCommand(commands: Commands = {}) { interface ChatCommands { new?: Command; newm?: Command; + copy?: Command; next?: Command; prev?: Command; clear?: Command; diff --git a/app/components/chat.tsx b/app/components/chat.tsx index a2eaba36f7d..b25675ac54f 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -894,6 +894,7 @@ function _Chat() { const chatCommands = useChatCommand({ new: () => chatStore.newSession(), newm: () => navigate(Path.NewChat), + copy: () => chatStore.copySession(), prev: () => chatStore.nextSession(-1), next: () => chatStore.nextSession(1), clear: () => diff --git a/app/locales/cn.ts b/app/locales/cn.ts index edc087ef133..dcb8f1295ae 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -47,6 +47,7 @@ const cn = { Commands: { new: "新建聊天", newm: "从面具新建聊天", + copy: "复制当前聊天", next: "下一个聊天", prev: "上一个聊天", clear: "清除上下文", diff --git a/app/locales/en.ts b/app/locales/en.ts index d19fc76a06a..4843f3977f8 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -49,6 +49,7 @@ const en: LocaleType = { Commands: { new: "Start a new chat", newm: "Start a new chat with mask", + copy: "Copy the current Chat", next: "Next Chat", prev: "Previous Chat", clear: "Clear Context", diff --git a/app/store/chat.ts b/app/store/chat.ts index 5db48d2c94e..59fedcfef87 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -208,6 +208,28 @@ export const useChatStore = createPersistStore( }); }, + copySession() { + set((state) => { + const { sessions, currentSessionIndex } = state; + const emptySession = createEmptySession(); + + // copy the session + const curSession = JSON.parse( + JSON.stringify(sessions[currentSessionIndex]), + ); + curSession.id = emptySession.id; + curSession.lastUpdate = emptySession.lastUpdate; + + const newSessions = [...sessions]; + newSessions.splice(0, 0, curSession); + + return { + currentSessionIndex: 0, + sessions: newSessions, + }; + }); + }, + moveSession(from: number, to: number) { set((state) => { const { sessions, currentSessionIndex: oldIndex } = state; From 6dc868154dea4a30ce8733844280c0b0dc8bc7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= Date: Thu, 5 Sep 2024 21:52:25 +0800 Subject: [PATCH 17/18] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=BA=91?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BD=BFaccess?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=8C=89=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=90=88=E5=B9=B6=EF=BC=8C=E8=A7=A3=E5=86=B3=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E5=9C=A8=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=90=8E=E4=B8=A2=E5=A4=B1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/store/access.ts | 2 +- app/utils/sync.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/store/access.ts b/app/store/access.ts index b89b080d80e..30f8ce1463e 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -210,7 +210,7 @@ export const useAccessStore = createPersistStore( }) .then((res: DangerConfig) => { console.log("[Config] got config from server", res); - set(() => ({ ...res })); + set(() => ({ lastUpdateTime: Date.now(), ...res })); }) .catch(() => { console.error("[Config] failed to fetch config"); diff --git a/app/utils/sync.ts b/app/utils/sync.ts index 645d8ac1151..fed0306798a 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -128,7 +128,13 @@ const MergeStates: StateMerger = { }); const remoteDeletedSessionIds = remoteState.deletedSessionIds || {}; + + const finalIds: Record = {}; localState.sessions = localState.sessions.filter((localSession) => { + if (finalIds[localSession.id]) { + return false; + } + finalIds[localSession.id] = true; return ( (remoteDeletedSessionIds[localSession.id] || -1) <= localSession.lastUpdate @@ -209,9 +215,9 @@ export function mergeWithUpdate( remoteState: T, ) { const localUpdateTime = localState.lastUpdateTime ?? 0; - const remoteUpdateTime = localState.lastUpdateTime ?? 1; + const remoteUpdateTime = remoteState.lastUpdateTime ?? 1; - if (localUpdateTime < remoteUpdateTime) { + if (localUpdateTime >= remoteUpdateTime) { merge(remoteState, localState); return { ...remoteState }; } else { From 5ae4921ee07027d7dcf025cd0a6b8fd3fc87ec3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E6=A2=A6=E4=BA=BA?= Date: Fri, 6 Sep 2024 20:59:53 +0800 Subject: [PATCH 18/18] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=BA=91?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=E6=8E=89=E9=9D=9E=E9=A6=96=E4=B8=AA=E7=A9=BA?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=EF=BC=8C=E9=81=BF=E5=85=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E7=A9=BA=E4=BC=9A=E8=AF=9D=E5=9C=A8=E4=B8=AD=E9=97=B4=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B9=E4=BE=BF=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/sync.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/utils/sync.ts b/app/utils/sync.ts index fed0306798a..db2cbd37e68 100644 --- a/app/utils/sync.ts +++ b/app/utils/sync.ts @@ -131,10 +131,21 @@ const MergeStates: StateMerger = { const finalIds: Record = {}; localState.sessions = localState.sessions.filter((localSession) => { + // 去除掉重复的会话 if (finalIds[localSession.id]) { return false; } finalIds[localSession.id] = true; + + // 去除掉非首个空会话,避免多个空会话在中间,不方便管理 + if ( + localSession.messages.length === 0 && + localSession != localState.sessions[0] + ) { + return false; + } + + // 去除云端删除并且删除时间小于本地修改时间的会话 return ( (remoteDeletedSessionIds[localSession.id] || -1) <= localSession.lastUpdate