mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix: fix topic messages display error when switch topic quickly (#11542)
* fix Switch topic issues * clean document * add abortable mode
This commit is contained in:
@@ -48,8 +48,8 @@ const Conversation = memo(() => {
|
||||
context={context}
|
||||
hasInitMessages={!!messages}
|
||||
messages={messages}
|
||||
onMessagesChange={(messages) => {
|
||||
replaceMessages(messages, { context });
|
||||
onMessagesChange={(messages, ctx) => {
|
||||
replaceMessages(messages, { context: ctx });
|
||||
}}
|
||||
operationState={operationState}
|
||||
>
|
||||
|
||||
@@ -58,8 +58,8 @@ const Conversation = memo<ConversationAreaProps>(({ mobile = false }) => {
|
||||
hasInitMessages={!!messages}
|
||||
// hooks={groupHooks}
|
||||
messages={messages}
|
||||
onMessagesChange={(messages) => {
|
||||
replaceMessages(messages, { context });
|
||||
onMessagesChange={(messages, ctx) => {
|
||||
replaceMessages(messages, { context: ctx });
|
||||
}}
|
||||
operationState={operationState}
|
||||
>
|
||||
|
||||
@@ -47,8 +47,8 @@ const AgentBuilderProvider = memo<AgentBuilderProviderProps>(({ agentId, childre
|
||||
context={context}
|
||||
hasInitMessages={!!messages}
|
||||
messages={messages}
|
||||
onMessagesChange={(msgs) => {
|
||||
replaceMessages(msgs, { context });
|
||||
onMessagesChange={(msgs, ctx) => {
|
||||
replaceMessages(msgs, { context: ctx });
|
||||
}}
|
||||
operationState={operationState}
|
||||
>
|
||||
|
||||
@@ -40,8 +40,8 @@ const SharedMessageList = memo<SharedMessageListProps>(({ agentId, groupId, shar
|
||||
context={context}
|
||||
hasInitMessages={!!messages}
|
||||
messages={messages}
|
||||
onMessagesChange={(messages) => {
|
||||
replaceMessages(messages, { context });
|
||||
onMessagesChange={(messages, ctx) => {
|
||||
replaceMessages(messages, { context: ctx });
|
||||
}}
|
||||
>
|
||||
<Flexbox flex={1}>
|
||||
|
||||
@@ -46,8 +46,8 @@ const AgentBuilderProvider = memo<AgentBuilderProviderProps>(({ agentId, childre
|
||||
context={context}
|
||||
hasInitMessages={!!messages}
|
||||
messages={messages}
|
||||
onMessagesChange={(msgs) => {
|
||||
replaceMessages(msgs, { context });
|
||||
onMessagesChange={(msgs, ctx) => {
|
||||
replaceMessages(msgs, { context: ctx });
|
||||
}}
|
||||
operationState={operationState}
|
||||
>
|
||||
|
||||
@@ -42,8 +42,9 @@ export interface ConversationProviderProps {
|
||||
* Use this to sync messages back to external state (e.g., ChatStore)
|
||||
*
|
||||
* @param messages - The updated messages array
|
||||
* @param context - The context that this data belongs to (prevents race conditions)
|
||||
*/
|
||||
onMessagesChange?: (messages: UIChatMessage[]) => void;
|
||||
onMessagesChange?: (messages: UIChatMessage[], context: ConversationContext) => void;
|
||||
/**
|
||||
* External operation state (from ChatStore)
|
||||
*
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface StoreUpdaterProps {
|
||||
/**
|
||||
* Callback when messages are fetched or changed internally
|
||||
*/
|
||||
onMessagesChange?: (messages: UIChatMessage[]) => void;
|
||||
onMessagesChange?: (messages: UIChatMessage[], context: ConversationContext) => void;
|
||||
/**
|
||||
* External operation state (from ChatStore)
|
||||
*/
|
||||
|
||||
@@ -33,8 +33,10 @@ export interface State extends DataState, InputState, MessageStateState, VirtuaL
|
||||
|
||||
/**
|
||||
* Callback when messages are fetched or changed internally
|
||||
* @param messages - The updated messages array
|
||||
* @param context - The context that this data belongs to (prevents race conditions)
|
||||
*/
|
||||
onMessagesChange?: (messages: UIChatMessage[]) => void;
|
||||
onMessagesChange?: (messages: UIChatMessage[], context: ConversationContext) => void;
|
||||
|
||||
/**
|
||||
* External operation state (from ChatStore)
|
||||
|
||||
@@ -74,7 +74,7 @@ export const dataSlice: StateCreator<
|
||||
});
|
||||
|
||||
// Sync changes to external store (ChatStore)
|
||||
get().onMessagesChange?.(newDbMessages);
|
||||
get().onMessagesChange?.(newDbMessages, get().context);
|
||||
},
|
||||
|
||||
replaceMessages: (messages) => {
|
||||
@@ -84,7 +84,7 @@ export const dataSlice: StateCreator<
|
||||
set({ dbMessages: messages, displayMessages: flatList }, false, 'replaceMessages');
|
||||
|
||||
// Sync changes to external store (ChatStore)
|
||||
get().onMessagesChange?.(messages);
|
||||
get().onMessagesChange?.(messages, get().context);
|
||||
},
|
||||
|
||||
switchMessageBranch: async (messageId, branchIndex) => {
|
||||
@@ -106,7 +106,6 @@ export const dataSlice: StateCreator<
|
||||
// Also skip fetch when topicId is null (new conversation state) - there's no server data,
|
||||
// only local optimistic updates. Fetching would return empty array and overwrite local data.
|
||||
const shouldFetch = !skipFetch && !!context.agentId && !!context.topicId;
|
||||
|
||||
return useClientDataSWRWithSync<UIChatMessage[]>(
|
||||
shouldFetch ? ['CONVERSATION_FETCH_MESSAGES', context] : null,
|
||||
|
||||
@@ -116,6 +115,7 @@ export const dataSlice: StateCreator<
|
||||
{
|
||||
onData: (data) => {
|
||||
if (!data) return;
|
||||
if (!context.topicId) return;
|
||||
|
||||
// Parse messages using conversation-flow
|
||||
const { flatList } = parse(data);
|
||||
@@ -126,8 +126,9 @@ export const dataSlice: StateCreator<
|
||||
messagesInit: true,
|
||||
});
|
||||
|
||||
// Call onMessagesChange callback if provided
|
||||
get().onMessagesChange?.(data);
|
||||
// Call onMessagesChange callback with the request context (not current context)
|
||||
// This ensures data is stored to the correct topic even if user switched topics
|
||||
get().onMessagesChange?.(data, context);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -49,8 +49,8 @@ const PageAgentProvider = memo<PageAgentProviderProps>(({ pageAgentId, children
|
||||
context={context}
|
||||
hasInitMessages={!!messages}
|
||||
messages={messages}
|
||||
onMessagesChange={(msgs) => {
|
||||
replaceMessages(msgs, { context });
|
||||
onMessagesChange={(msgs, ctx) => {
|
||||
replaceMessages(msgs, { context: ctx });
|
||||
}}
|
||||
operationState={operationState}
|
||||
>
|
||||
|
||||
@@ -204,8 +204,8 @@ const ThreadChat = memo(() => {
|
||||
hasInitMessages={!!messages}
|
||||
hooks={hooks}
|
||||
messages={messages}
|
||||
onMessagesChange={(msgs) => {
|
||||
replaceMessages(msgs, { context });
|
||||
onMessagesChange={(msgs, ctx) => {
|
||||
replaceMessages(msgs, { context: ctx });
|
||||
}}
|
||||
operationState={operationState}
|
||||
skipFetch={isCreatingNewThread}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { type DocumentItem } from '@lobechat/database/schemas';
|
||||
|
||||
import { lambdaClient } from '@/libs/trpc/client';
|
||||
|
||||
import { abortableRequest } from '../utils/abortableRequest';
|
||||
|
||||
export interface CreateDocumentParams {
|
||||
content?: string;
|
||||
editorData: string;
|
||||
@@ -41,7 +43,15 @@ export class DocumentService {
|
||||
return lambdaClient.document.queryDocuments.query(params);
|
||||
}
|
||||
|
||||
async getDocumentById(id: string): Promise<DocumentItem | undefined> {
|
||||
async getDocumentById(id: string, uniqueKey?: string): Promise<DocumentItem | undefined> {
|
||||
if (uniqueKey) {
|
||||
// Use fixed key so switching documents cancels the previous request
|
||||
// This prevents race conditions where old document's data overwrites new document's editor
|
||||
return abortableRequest.execute(uniqueKey, async (signal) =>
|
||||
lambdaClient.document.getDocumentById.query({ id }, { signal }),
|
||||
);
|
||||
}
|
||||
|
||||
return lambdaClient.document.getDocumentById.query({ id });
|
||||
}
|
||||
|
||||
|
||||
@@ -47,10 +47,6 @@ export interface CrudAction {
|
||||
* Duplicate an existing page
|
||||
*/
|
||||
duplicatePage: (pageId: string) => Promise<{ [key: string]: any; id: string }>;
|
||||
/**
|
||||
* Fetch full page detail by ID and update documents array
|
||||
*/
|
||||
fetchPageDetail: (pageId: string) => Promise<void>;
|
||||
navigateToPage: (pageId: string | null) => void;
|
||||
/**
|
||||
* Remove a page (deletes from documents table)
|
||||
@@ -240,50 +236,6 @@ export const createCrudSlice: StateCreator<
|
||||
return newPage;
|
||||
},
|
||||
|
||||
fetchPageDetail: async (pageId) => {
|
||||
try {
|
||||
const document = await documentService.getDocumentById(pageId);
|
||||
|
||||
if (!document) {
|
||||
console.warn(`[fetchPageDetail] Page not found: ${pageId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fullPage: LobeDocument = {
|
||||
content: document.content || null,
|
||||
createdAt: document.createdAt ? new Date(document.createdAt) : new Date(),
|
||||
editorData:
|
||||
typeof document.editorData === 'string'
|
||||
? JSON.parse(document.editorData)
|
||||
: document.editorData || null,
|
||||
fileType: document.fileType,
|
||||
filename: document.title || document.filename || 'Untitled',
|
||||
id: document.id,
|
||||
metadata: document.metadata || {},
|
||||
source: 'document',
|
||||
sourceType: DocumentSourceType.EDITOR,
|
||||
title: document.title || '',
|
||||
totalCharCount: document.content?.length || 0,
|
||||
totalLineCount: 0,
|
||||
updatedAt: document.updatedAt ? new Date(document.updatedAt) : new Date(),
|
||||
};
|
||||
|
||||
// Update document via internal dispatch
|
||||
const { documents } = get();
|
||||
if (documents?.some((doc) => doc.id === pageId)) {
|
||||
get().internal_dispatchDocuments({
|
||||
document: fullPage,
|
||||
id: pageId,
|
||||
type: 'updateDocument',
|
||||
});
|
||||
} else {
|
||||
get().internal_dispatchDocuments({ document: fullPage, type: 'addDocument' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[fetchPageDetail] Failed to fetch page:', error);
|
||||
}
|
||||
},
|
||||
|
||||
navigateToPage: (pageId) => {
|
||||
if (!pageId) {
|
||||
get().navigate?.('/page');
|
||||
|
||||
Reference in New Issue
Block a user