mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-08 03:08:21 +00:00
hot-fix: chat conversation reaching max depth with update (#442)
This commit is contained in:
parent
9f548a477d
commit
a9bd64c106
1 changed files with 107 additions and 68 deletions
|
|
@ -1,43 +1,73 @@
|
|||
import type { UIMessage } from "@ai-sdk/react";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import type { UIMessage } from "@ai-sdk/react"
|
||||
import { create } from "zustand"
|
||||
import { persist } from "zustand/middleware"
|
||||
|
||||
/**
|
||||
* Deep equality check for UIMessage arrays to prevent unnecessary state updates
|
||||
*/
|
||||
function areUIMessageArraysEqual(a: UIMessage[], b: UIMessage[]): boolean {
|
||||
if (a === b) return true
|
||||
if (a.length !== b.length) return false
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const msgA = a[i]
|
||||
const msgB = b[i]
|
||||
|
||||
// Both messages should exist at this index
|
||||
if (!msgA || !msgB) return false
|
||||
|
||||
if (msgA === msgB) continue
|
||||
|
||||
if (msgA.id !== msgB.id || msgA.role !== msgB.role) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare the entire message using JSON serialization as a fallback
|
||||
// This handles all properties including parts, toolInvocations, etc.
|
||||
if (JSON.stringify(msgA) !== JSON.stringify(msgB)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export interface ConversationSummary {
|
||||
id: string;
|
||||
title?: string;
|
||||
lastUpdated: string;
|
||||
id: string
|
||||
title?: string
|
||||
lastUpdated: string
|
||||
}
|
||||
|
||||
interface ConversationRecord {
|
||||
messages: UIMessage[];
|
||||
title?: string;
|
||||
lastUpdated: string;
|
||||
messages: UIMessage[]
|
||||
title?: string
|
||||
lastUpdated: string
|
||||
}
|
||||
|
||||
interface ProjectConversationsState {
|
||||
currentChatId: string | null;
|
||||
conversations: Record<string, ConversationRecord>;
|
||||
currentChatId: string | null
|
||||
conversations: Record<string, ConversationRecord>
|
||||
}
|
||||
|
||||
interface ConversationsStoreState {
|
||||
byProject: Record<string, ProjectConversationsState>;
|
||||
setCurrentChatId: (projectId: string, chatId: string | null) => void;
|
||||
byProject: Record<string, ProjectConversationsState>
|
||||
setCurrentChatId: (projectId: string, chatId: string | null) => void
|
||||
setConversation: (
|
||||
projectId: string,
|
||||
chatId: string,
|
||||
messages: UIMessage[],
|
||||
) => void;
|
||||
deleteConversation: (projectId: string, chatId: string) => void;
|
||||
) => void
|
||||
deleteConversation: (projectId: string, chatId: string) => void
|
||||
setConversationTitle: (
|
||||
projectId: string,
|
||||
chatId: string,
|
||||
title: string | undefined,
|
||||
) => void;
|
||||
) => void
|
||||
}
|
||||
|
||||
export const usePersistentChatStore = create<ConversationsStoreState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
(set, _get) => ({
|
||||
byProject: {},
|
||||
|
||||
setCurrentChatId(projectId, chatId) {
|
||||
|
|
@ -45,29 +75,38 @@ export const usePersistentChatStore = create<ConversationsStoreState>()(
|
|||
const project = state.byProject[projectId] ?? {
|
||||
currentChatId: null,
|
||||
conversations: {},
|
||||
};
|
||||
}
|
||||
return {
|
||||
byProject: {
|
||||
...state.byProject,
|
||||
[projectId]: { ...project, currentChatId: chatId },
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setConversation(projectId, chatId, messages) {
|
||||
const now = new Date().toISOString();
|
||||
const now = new Date().toISOString()
|
||||
set((state) => {
|
||||
const project = state.byProject[projectId] ?? {
|
||||
currentChatId: null,
|
||||
conversations: {},
|
||||
};
|
||||
const existing = project.conversations[chatId];
|
||||
}
|
||||
const existing = project.conversations[chatId]
|
||||
|
||||
// Check if messages are actually different to prevent unnecessary updates
|
||||
if (
|
||||
existing &&
|
||||
areUIMessageArraysEqual(existing.messages, messages)
|
||||
) {
|
||||
return state // No change needed
|
||||
}
|
||||
|
||||
const shouldTouchLastUpdated = (() => {
|
||||
if (!existing) return messages.length > 0;
|
||||
const previousLength = existing.messages?.length ?? 0;
|
||||
return messages.length > previousLength;
|
||||
})();
|
||||
if (!existing) return messages.length > 0
|
||||
const previousLength = existing.messages?.length ?? 0
|
||||
return messages.length > previousLength
|
||||
})()
|
||||
|
||||
const record: ConversationRecord = {
|
||||
messages,
|
||||
|
|
@ -75,7 +114,7 @@ export const usePersistentChatStore = create<ConversationsStoreState>()(
|
|||
lastUpdated: shouldTouchLastUpdated
|
||||
? now
|
||||
: (existing?.lastUpdated ?? now),
|
||||
};
|
||||
}
|
||||
return {
|
||||
byProject: {
|
||||
...state.byProject,
|
||||
|
|
@ -87,8 +126,8 @@ export const usePersistentChatStore = create<ConversationsStoreState>()(
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
deleteConversation(projectId, chatId) {
|
||||
|
|
@ -96,28 +135,28 @@ export const usePersistentChatStore = create<ConversationsStoreState>()(
|
|||
const project = state.byProject[projectId] ?? {
|
||||
currentChatId: null,
|
||||
conversations: {},
|
||||
};
|
||||
const { [chatId]: _, ...rest } = project.conversations;
|
||||
}
|
||||
const { [chatId]: _, ...rest } = project.conversations
|
||||
const nextCurrent =
|
||||
project.currentChatId === chatId ? null : project.currentChatId;
|
||||
project.currentChatId === chatId ? null : project.currentChatId
|
||||
return {
|
||||
byProject: {
|
||||
...state.byProject,
|
||||
[projectId]: { currentChatId: nextCurrent, conversations: rest },
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setConversationTitle(projectId, chatId, title) {
|
||||
const now = new Date().toISOString();
|
||||
const now = new Date().toISOString()
|
||||
set((state) => {
|
||||
const project = state.byProject[projectId] ?? {
|
||||
currentChatId: null,
|
||||
conversations: {},
|
||||
};
|
||||
const existing = project.conversations[chatId];
|
||||
if (!existing) return { byProject: state.byProject };
|
||||
}
|
||||
const existing = project.conversations[chatId]
|
||||
if (!existing) return { byProject: state.byProject }
|
||||
return {
|
||||
byProject: {
|
||||
...state.byProject,
|
||||
|
|
@ -129,76 +168,76 @@ export const usePersistentChatStore = create<ConversationsStoreState>()(
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "supermemory-chats",
|
||||
},
|
||||
),
|
||||
);
|
||||
)
|
||||
|
||||
// Always scoped to the current project via useProject
|
||||
import { useProject } from ".";
|
||||
import { useProject } from "."
|
||||
|
||||
export function usePersistentChat() {
|
||||
const { selectedProject } = useProject();
|
||||
const projectId = selectedProject;
|
||||
const { selectedProject } = useProject()
|
||||
const projectId = selectedProject
|
||||
|
||||
const projectState = usePersistentChatStore((s) => s.byProject[projectId]);
|
||||
const setCurrentChatIdRaw = usePersistentChatStore((s) => s.setCurrentChatId);
|
||||
const setConversationRaw = usePersistentChatStore((s) => s.setConversation);
|
||||
const projectState = usePersistentChatStore((s) => s.byProject[projectId])
|
||||
const setCurrentChatIdRaw = usePersistentChatStore((s) => s.setCurrentChatId)
|
||||
const setConversationRaw = usePersistentChatStore((s) => s.setConversation)
|
||||
const deleteConversationRaw = usePersistentChatStore(
|
||||
(s) => s.deleteConversation,
|
||||
);
|
||||
)
|
||||
const setConversationTitleRaw = usePersistentChatStore(
|
||||
(s) => s.setConversationTitle,
|
||||
);
|
||||
)
|
||||
|
||||
const conversations: ConversationSummary[] = (() => {
|
||||
const convs = projectState?.conversations ?? {};
|
||||
const convs = projectState?.conversations ?? {}
|
||||
return Object.entries(convs).map(([id, rec]) => ({
|
||||
id,
|
||||
title: rec.title,
|
||||
lastUpdated: rec.lastUpdated,
|
||||
}));
|
||||
})();
|
||||
}))
|
||||
})()
|
||||
|
||||
const currentChatId = projectState?.currentChatId ?? null;
|
||||
const currentChatId = projectState?.currentChatId ?? null
|
||||
|
||||
function setCurrentChatId(chatId: string | null): void {
|
||||
setCurrentChatIdRaw(projectId, chatId);
|
||||
setCurrentChatIdRaw(projectId, chatId)
|
||||
}
|
||||
|
||||
function setConversation(chatId: string, messages: UIMessage[]): void {
|
||||
setConversationRaw(projectId, chatId, messages);
|
||||
setConversationRaw(projectId, chatId, messages)
|
||||
}
|
||||
|
||||
function deleteConversation(chatId: string): void {
|
||||
deleteConversationRaw(projectId, chatId);
|
||||
deleteConversationRaw(projectId, chatId)
|
||||
}
|
||||
|
||||
function setConversationTitle(
|
||||
chatId: string,
|
||||
title: string | undefined,
|
||||
): void {
|
||||
setConversationTitleRaw(projectId, chatId, title);
|
||||
setConversationTitleRaw(projectId, chatId, title)
|
||||
}
|
||||
|
||||
function getCurrentConversation(): UIMessage[] | undefined {
|
||||
const convs = projectState?.conversations ?? {};
|
||||
const id = currentChatId;
|
||||
if (!id) return undefined;
|
||||
return convs[id]?.messages;
|
||||
const convs = projectState?.conversations ?? {}
|
||||
const id = currentChatId
|
||||
if (!id) return undefined
|
||||
return convs[id]?.messages
|
||||
}
|
||||
|
||||
function getCurrentChat(): ConversationSummary | undefined {
|
||||
const id = currentChatId;
|
||||
if (!id) return undefined;
|
||||
const rec = projectState?.conversations?.[id];
|
||||
if (!rec) return undefined;
|
||||
return { id, title: rec.title, lastUpdated: rec.lastUpdated };
|
||||
const id = currentChatId
|
||||
if (!id) return undefined
|
||||
const rec = projectState?.conversations?.[id]
|
||||
if (!rec) return undefined
|
||||
return { id, title: rec.title, lastUpdated: rec.lastUpdated }
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -210,5 +249,5 @@ export function usePersistentChat() {
|
|||
setConversationTitle,
|
||||
getCurrentConversation,
|
||||
getCurrentChat,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue