mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(ai): make message editing safe against data loss and stale state
- insertMessageAfter: update active_message_id when appending to tail so the new reply is on the active path after reload - editAndOverwriteAll: add hasConfig + abort checks before deleteMessagesFrom to prevent data loss when LLM is unconfigured or a concurrent request starts - editCurrentRoundOnly: defer all DB mutations until after stream completes; on abort/error only in-memory state is restored - sync in-memory parentId after relinking so follow-up edits send the correct historyLeafMessageId Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
ce1065e295
commit
b3c2b20a9f
2 files changed
+36
-7
No files matched your search
@@ -738,10 +738,15 @@ export class AIConversationManager {
|
||||
|
||||
if (childRow) {
|
||||
db.prepare('UPDATE ai_message SET parent_id = ? WHERE id = ?').run(id, childRow.id)
|
||||
db.prepare('UPDATE ai_conversation SET updated_at = ? WHERE id = ?').run(now, conversationId)
|
||||
} else {
|
||||
db.prepare('UPDATE ai_conversation SET active_message_id = ?, updated_at = ? WHERE id = ?').run(
|
||||
id,
|
||||
now,
|
||||
conversationId
|
||||
)
|
||||
}
|
||||
|
||||
db.prepare('UPDATE ai_conversation SET updated_at = ? WHERE id = ?').run(now, conversationId)
|
||||
|
||||
return {
|
||||
id,
|
||||
conversationId,
|
||||
|
||||
+29
-5
@@ -1205,6 +1205,17 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
content: string
|
||||
): Promise<SendMessageResult> {
|
||||
try {
|
||||
const hasConfig = await window.llmApi.hasConfig()
|
||||
if (!hasConfig) {
|
||||
return { success: false, reason: 'no_config' }
|
||||
}
|
||||
if (state.isAborted) {
|
||||
return { success: false, reason: 'aborted' }
|
||||
}
|
||||
if (state.isAIThinking || activeTask.value) {
|
||||
return { success: false, reason: 'busy', activeTask: activeTask.value }
|
||||
}
|
||||
|
||||
await useAIService().deleteMessagesFrom(state.currentConversationId!, originalMessage.id)
|
||||
targetBuffer.messages.splice(editIndex, targetBuffer.messages.length - editIndex)
|
||||
return sendMessage(chatKey, content)
|
||||
@@ -1300,11 +1311,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
return { success: false, reason: 'busy', activeTask: activeTask.value }
|
||||
}
|
||||
|
||||
await useAIService().updateMessageContent(originalMessage.id, content)
|
||||
if (hasOldAiResponse) {
|
||||
await useAIService().deleteAndRelinkMessage(state.currentConversationId!, oldAiResponse.id)
|
||||
}
|
||||
|
||||
const context = {
|
||||
sessionId: state.sessionId,
|
||||
conversationId: state.currentConversationId!,
|
||||
@@ -1407,6 +1413,16 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
updateAIMessage({ contentBlocks: [...blocks], isStreaming: false })
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
restoreOriginal()
|
||||
return { success: false, reason: 'error' }
|
||||
}
|
||||
|
||||
await useAIService().updateMessageContent(originalMessage.id, content)
|
||||
if (hasOldAiResponse) {
|
||||
await useAIService().deleteAndRelinkMessage(state.currentConversationId!, oldAiResponse.id)
|
||||
}
|
||||
|
||||
const serializableContentBlocks = targetBuffer.messages[aiMessageIndex].contentBlocks
|
||||
? JSON.parse(JSON.stringify(targetBuffer.messages[aiMessageIndex].contentBlocks))
|
||||
: undefined
|
||||
@@ -1428,6 +1444,14 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
content,
|
||||
}
|
||||
|
||||
const nextMsgIndex = aiMessageIndex + 1
|
||||
if (nextMsgIndex < targetBuffer.messages.length) {
|
||||
targetBuffer.messages[nextMsgIndex] = {
|
||||
...targetBuffer.messages[nextMsgIndex],
|
||||
parentId: savedAiMsg.id,
|
||||
}
|
||||
}
|
||||
|
||||
targetBuffer.sessionTokenUsage = await useAIService().getConversationTokenUsage(state.currentConversationId!)
|
||||
state.sessionTokenUsage = { ...targetBuffer.sessionTokenUsage }
|
||||
return { success: true }
|
||||
|
||||
Reference in new issue
Block a user