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] 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 5b4adca86..c329c563a 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 5892ef0c8..554b077ff 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 2f2c8ae95..741540552 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 1acfc1289..1c8f0a11b 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) => {