diff --git a/public/script.js b/public/script.js index 68798deec..5e6bbdb85 100644 --- a/public/script.js +++ b/public/script.js @@ -488,14 +488,6 @@ let default_user_name = 'User'; export let name1 = default_user_name; export let name2 = 'SillyTavern System'; export let chat = []; -let safetychat = [ - { - name: systemUserName, - is_user: false, - create_date: 0, - mes: 'You deleted a character/chat and arrived back here for safety reasons! Pick another character!', - }, -]; let chatSaveTimeout; let importFlashTimeout; export let isChatSaving = false; @@ -594,6 +586,17 @@ export const extension_prompt_roles = { export const MAX_INJECTION_DEPTH = 1000; +const SAFETY_CHAT = [ + { + name: systemUserName, + force_avatar: system_avatar, + is_system: true, + is_user: false, + create_date: 0, + mes: 'You deleted a character/chat and arrived back here for safety reasons! Pick another character!', + }, +]; + export let system_messages = {}; async function getSystemMessages() { @@ -5680,7 +5683,7 @@ export function resetChatState() { // replaces deleted charcter name with system user since it will be displayed next. name2 = systemUserName; // sets up system user to tell user about having deleted a character - chat = [...safetychat]; + chat.splice(0, chat.length, ...SAFETY_CHAT); // resets chat metadata chat_metadata = {}; // resets the characters array, forcing getcharacters to reset @@ -8841,72 +8844,74 @@ export async function handleDeleteCharacter(this_chid, delete_chats) { /** * Deletes a character completely, including associated chats if specified * - * @param {string} characterKey - The key (avatar) of the character to be deleted + * @param {string|string[]} characterKey - The key (avatar) of the character to be deleted * @param {Object} [options] - Optional parameters for the deletion * @param {boolean} [options.deleteChats=true] - Whether to delete associated chats or not * @return {Promise} - A promise that resolves when the character is successfully deleted */ export async function deleteCharacter(characterKey, { deleteChats = true } = {}) { - const character = characters.find(x => x.avatar == characterKey); - if (!character) { - toastr.warning(`Character ${characterKey} not found. Cannot be deleted.`); - return; + if (!Array.isArray(characterKey)) { + characterKey = [characterKey]; } - const chid = characters.indexOf(character); - const pastChats = await getPastCharacterChats(chid); - - const msg = { avatar_url: character.avatar, delete_chats: deleteChats }; - - const response = await fetch('/api/characters/delete', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify(msg), - cache: 'no-cache', - }); - - if (!response.ok) { - throw new Error(`Failed to delete character: ${response.status} ${response.statusText}`); - } - - await removeCharacterFromUI(character.name, character.avatar); - - if (deleteChats) { - for (const chat of pastChats) { - const name = chat.file_name.replace('.jsonl', ''); - await eventSource.emit(event_types.CHAT_DELETED, name); + for (const key of characterKey) { + const character = characters.find(x => x.avatar == key); + if (!character) { + toastr.warning(`Character ${key} not found. Skipping deletion.`); + continue; } + + const chid = characters.indexOf(character); + const pastChats = await getPastCharacterChats(chid); + + const msg = { avatar_url: character.avatar, delete_chats: deleteChats }; + + const response = await fetch('/api/characters/delete', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify(msg), + cache: 'no-cache', + }); + + if (!response.ok) { + toastr.error(`${response.status} ${response.statusText}`, 'Failed to delete character'); + continue; + } + + delete tag_map[character.avatar]; + select_rm_info('char_delete', character.name); + + if (deleteChats) { + for (const chat of pastChats) { + const name = chat.file_name.replace('.jsonl', ''); + await eventSource.emit(event_types.CHAT_DELETED, name); + } + } + + await eventSource.emit(event_types.CHARACTER_DELETED, { id: chid, character: character }); } - eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] }); + await removeCharacterFromUI(); } /** * Function to delete a character from UI after character deletion API success. * It manages necessary UI changes such as closing advanced editing popup, unsetting * character ID, resetting characters array and chat metadata, deselecting character's tab - * panel, removing character name from navigation tabs, clearing chat, removing character's - * avatar from tag_map, fetching updated list of characters and updating the 'deleted - * character' message. + * panel, removing character name from navigation tabs, clearing chat, fetching updated list of characters. * It also ensures to save the settings after all the operations. - * - * @param {string} name - The name of the character to be deleted. - * @param {string} avatar - The avatar URL of the character to be deleted. - * @param {boolean} reloadCharacters - Whether the character list should be refreshed after deletion. */ -async function removeCharacterFromUI(name, avatar, reloadCharacters = true) { +async function removeCharacterFromUI() { await clearChat(); $('#character_cross').click(); this_chid = undefined; characters.length = 0; name2 = systemUserName; - chat = [...safetychat]; + chat.splice(0, chat.length, ...SAFETY_CHAT); chat_metadata = {}; $(document.getElementById('rm_button_selected_ch')).children('h2').text(''); this_chid = undefined; - delete tag_map[avatar]; - if (reloadCharacters) await getCharacters(); - select_rm_info('char_delete', name); + await getCharacters(); await printMessages(); saveSettingsDebounced(); } diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js index 28517d32f..dce7e61ab 100644 --- a/public/scripts/BulkEditOverlay.js +++ b/public/scripts/BulkEditOverlay.js @@ -108,14 +108,12 @@ class CharacterContextMenu { * Delete one or more characters, * opens a popup. * - * @param {number} characterId + * @param {string|string[]} characterKey * @param {boolean} [deleteChats] * @returns {Promise} */ - static delete = async (characterId, deleteChats = false) => { - const character = CharacterContextMenu.#getCharacter(characterId); - - await deleteCharacter(character.avatar, { deleteChats: deleteChats }); + static delete = async (characterKey, deleteChats = false) => { + await deleteCharacter(characterKey, { deleteChats: deleteChats }); }; static #getCharacter = (characterId) => characters[characterId] ?? null; @@ -344,7 +342,7 @@ class BulkTagPopupHandler { const mutualTags = this.getMutualTags(); for (const characterId of this.characterIds) { - for(const tag of mutualTags) { + for (const tag of mutualTags) { removeTagFromMap(tag.id, characterId); } } @@ -599,8 +597,7 @@ class BulkEditOverlay { this.container.removeEventListener('mouseup', cancelHold); this.container.removeEventListener('touchend', cancelHold); - }, - BulkEditOverlay.longPressDelay); + }, BulkEditOverlay.longPressDelay); }; handleLongPressEnd = (event) => { @@ -847,11 +844,14 @@ class BulkEditOverlay { const deleteChats = document.getElementById('del_char_checkbox').checked ?? false; showLoader(); - toastr.info('We\'re deleting your characters, please wait...', 'Working on it'); - return Promise.allSettled(characterIds.map(async characterId => CharacterContextMenu.delete(characterId, deleteChats))) - .then(() => getCharacters()) + const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it'); + const avatarList = characterIds.map(id => characters[id]?.avatar).filter(a => a); + return CharacterContextMenu.delete(avatarList, deleteChats) .then(() => this.browseState()) - .finally(() => hideLoader()); + .finally(() => { + toastr.clear(toast); + hideLoader(); + }); }); // At this moment the popup is already changed in the dom, but not yet closed/resolved. We build the avatar list here