diff --git a/public/script.js b/public/script.js index 9219e84eb..111b7dfae 100644 --- a/public/script.js +++ b/public/script.js @@ -6086,17 +6086,52 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes, return { type, getMessage }; } -export function syncCurrentSwipeInfoExtras() { +/** + * Syncs the current message and all its data into the swipe data at the given message ID (or the last message if no ID is given). + * + * If the swipe data is invalid in some way, this function will exit out without doing anything. + * @param {number?} [messageId=null] - The ID of the message to sync with the swipe data. If no ID is given, the last message is used. + * @returns {boolean} Whether the message was successfully synced + */ +export function syncMesToSwipe(messageId = null) { if (!chat.length) { - return; + return false; } - const currentMessage = chat[chat.length - 1]; - if (currentMessage && Array.isArray(currentMessage.swipe_info) && typeof currentMessage.swipe_id === 'number') { - const swipeInfo = currentMessage.swipe_info[currentMessage.swipe_id]; - if (swipeInfo && typeof swipeInfo === 'object') { - swipeInfo.extra = structuredClone(currentMessage.extra); - } + + const targetMessageId = messageId ?? chat.length - 1; + if (chat.length > targetMessageId || targetMessageId < 0) { + console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`); + return false; } + + const targetMessage = chat[targetMessageId]; + + // No swipe data there yet, exit out + if (typeof targetMessage.swipe_id !== 'number') { + return false; + } + // If swipes structure is invalid, exit out (for now?) + if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) { + return false; + } + // If the swipe is not present yet, exit out (will likely be copied later) + if (!targetMessage.swipes[targetMessage.swipe_id] || !targetMessage.swipe_info[targetMessage.swipe_id]) { + return false; + } + + const targetSwipeInfo = targetMessage.swipe_info[targetMessage.swipe_id]; + if (typeof targetSwipeInfo !== 'object') { + return false; + } + + targetMessage.swipes[targetMessage.swipe_id] = targetMessage.mes; + + targetSwipeInfo.send_date = targetMessage.send_date; + targetSwipeInfo.gen_started = targetMessage.gen_started; + targetSwipeInfo.gen_finished = targetMessage.gen_finished; + targetSwipeInfo.extra = structuredClone(targetMessage.extra); + + return true; } function saveImageToMessage(img, mes) { @@ -6401,6 +6436,7 @@ export function saveChatDebounced() { if (chatSaveTimeout) { console.debug('Clearing chat save timeout'); clearTimeout(chatSaveTimeout); + chatSaveTimeout = null; } chatSaveTimeout = setTimeout(async () => { @@ -6417,7 +6453,7 @@ export function saveChatDebounced() { console.debug('Chat save timeout triggered'); await saveChatConditional(); console.debug('Chat saved'); - }, 1000); + }, DEFAULT_SAVE_EDIT_TIMEOUT); } export async function saveChat(chatName, withMetadata, mesId) { @@ -8032,6 +8068,12 @@ export async function saveChatConditional() { } try { + if (chatSaveTimeout) { + console.debug('Debounced chat save canceled'); + clearTimeout(chatSaveTimeout); + chatSaveTimeout = null; + } + isChatSaving = true; if (selected_group) { @@ -8568,7 +8610,7 @@ function swipe_left() { // when we swipe left..but no generation. } // Make sure ad-hoc changes to extras are saved before swiping away - syncCurrentSwipeInfoExtras(); + syncMesToSwipe(); const swipe_duration = 120; const swipe_range = '700px'; @@ -8706,7 +8748,7 @@ const swipe_right = () => { } // Make sure ad-hoc changes to extras are saved before swiping away - syncCurrentSwipeInfoExtras(); + syncMesToSwipe(); const swipe_duration = 200; const swipe_range = 700; diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js index 5851e87e4..d475ca3f9 100644 --- a/public/scripts/reasoning.js +++ b/public/scripts/reasoning.js @@ -1,7 +1,7 @@ import { moment, } from '../lib.js'; -import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveChatDebounced, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; +import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveChatDebounced, saveSettingsDebounced, substituteParams, syncMesToSwipe, updateMessageBlock } from '../script.js'; import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; import { getCurrentLocale, t, translate } from './i18n.js'; import { MacrosParser } from './macros.js'; @@ -1046,8 +1046,11 @@ function registerReasoningAppEvents() { message.mes = parsedReasoning.content; } - // Find if a message already exists in DOM and must be updated if (contentUpdated) { + syncMesToSwipe(); + saveChatDebounced(); + + // Find if a message already exists in DOM and must be updated const messageRendered = document.querySelector(`.mes[mesid="${idx}"]`) !== null; if (messageRendered) { console.debug('[Reasoning] Updating message block', idx); diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 42eb20f4a..9f0cb8f73 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -42,7 +42,7 @@ import { showMoreMessages, stopGeneration, substituteParams, - syncCurrentSwipeInfoExtras, + syncMesToSwipe, system_avatar, system_message_types, this_chid, @@ -2921,7 +2921,7 @@ async function addSwipeCallback(args, value) { if (isTrueBoolean(args.switch)) { // Make sure ad-hoc changes to extras are saved before swiping away - syncCurrentSwipeInfoExtras(); + syncMesToSwipe(); lastMessage.swipe_id = newSwipeId; lastMessage.mes = lastMessage.swipes[newSwipeId]; lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {});