Fix bulk delete async hell (#2730) * Fix bulk delete async hell * Remove refresh flag (always refresh) * Don't throw on deletion fetch failed * Clear toast on bulk finish

1fa9710a5cc59fd1a6be7f7c5113f43fd13075b2

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +44 -39Showing whitespace changes
public/script.js+33 -28
@@ -488,14 +488,6 @@ let default_user_name = 'User';
488488export let name1 = default_user_name;
489489export let name2 = 'SillyTavern System';
490490export let chat = [];
491-let safetychat = [
492- {
493- name: systemUserName,
494- is_user: false,
495- create_date: 0,
496- mes: 'You deleted a character/chat and arrived back here for safety reasons! Pick another character!',
497- },
498-];
499491let chatSaveTimeout;
500492let importFlashTimeout;
501493export let isChatSaving = false;
@@ -594,6 +586,17 @@ export const extension_prompt_roles = {
594586
595587export const MAX_INJECTION_DEPTH = 1000;
596588
589+const SAFETY_CHAT = [
590+ {
591+ name: systemUserName,
592+ force_avatar: system_avatar,
593+ is_system: true,
594+ is_user: false,
595+ create_date: 0,
596+ mes: 'You deleted a character/chat and arrived back here for safety reasons! Pick another character!',
597+ },
598+];
599+
597600export let system_messages = {};
598601
599602async function getSystemMessages() {
@@ -5680,7 +5683,7 @@ export function resetChatState() {
56805683 // replaces deleted charcter name with system user since it will be displayed next.
56815684 name2 = systemUserName;
56825685 // sets up system user to tell user about having deleted a character
56835686 chat.splice(0, =chat.length, [...safetychat]SAFETY_CHAT);
56845687 // resets chat metadata
56855688 chat_metadata = {};
56865689 // resets the characters array, forcing getcharacters to reset
@@ -8841,16 +8844,21 @@ export async function handleDeleteCharacter(this_chid, delete_chats) {
88418844/**
88428845 * Deletes a character completely, including associated chats if specified
88438846 *
88448847 * @param {string|string[]} characterKey - The key (avatar) of the character to be deleted
88458848 * @param {Object} [options] - Optional parameters for the deletion
88468849 * @param {boolean} [options.deleteChats=true] - Whether to delete associated chats or not
88478850 * @return {Promise<void>} - A promise that resolves when the character is successfully deleted
88488851 */
88498852export async function deleteCharacter(characterKey, { deleteChats = true } = {}) {
8850- const character = characters.find(x => x.avatar == characterKey);
8853+ if (!Array.isArray(characterKey)) {
8854+ characterKey = [characterKey];
8855+ }
8856+
8857+ for (const key of characterKey) {
8858+ const character = characters.find(x => x.avatar == key);
88518859 if (!character) {
88528860 toastr.warning(`Character ${characterKeykey} not found. Cannot beSkipping deleteddeletion.`);
8853- return;
8861+ continue;
88548862 }
88558863
88568864 const chid = characters.indexOf(character);
@@ -8866,10 +8874,12 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
88668874 });
88678875
88688876 if (!response.ok) {
88698877 throw new Error toastr.error(`Failed to delete character: ${response.status} ${response.statusText}`, 'Failed to delete character');
8878+ continue;
88708879 }
88718880
8872- await removeCharacterFromUI(character.name, character.avatar);
8881+ delete tag_map[character.avatar];
8882+ select_rm_info('char_delete', character.name);
88738883
88748884 if (deleteChats) {
88758885 for (const chat of pastChats) {
@@ -8878,35 +8888,30 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
88788888 }
88798889 }
88808890
88818891 await eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chidchid, character: characters[this_chid]character });
8892+ }
8893+
8894+ await removeCharacterFromUI();
88828895}
88838896
88848897/**
88858898 * Function to delete a character from UI after character deletion API success.
88868899 * It manages necessary UI changes such as closing advanced editing popup, unsetting
88878900 * character ID, resetting characters array and chat metadata, deselecting character's tab
88888901 * panel, removing character name from navigation tabs, clearing chat, removingfetching character'supdated list of characters.
8889- * avatar from tag_map, fetching updated list of characters and updating the 'deleted
8890- * character' message.
88918902 * It also ensures to save the settings after all the operations.
8892- *
8893- * @param {string} name - The name of the character to be deleted.
8894- * @param {string} avatar - The avatar URL of the character to be deleted.
8895- * @param {boolean} reloadCharacters - Whether the character list should be refreshed after deletion.
88968903 */
88978904async function removeCharacterFromUI(name, avatar, reloadCharacters = true) {
88988905 await clearChat();
88998906 $('#character_cross').click();
89008907 this_chid = undefined;
89018908 characters.length = 0;
89028909 name2 = systemUserName;
89038910 chat.splice(0, =chat.length, [...safetychat]SAFETY_CHAT);
89048911 chat_metadata = {};
89058912 $(document.getElementById('rm_button_selected_ch')).children('h2').text('');
89068913 this_chid = undefined;
8907- delete tag_map[avatar];
8914+ await getCharacters();
8908- if (reloadCharacters) await getCharacters();
8909- select_rm_info('char_delete', name);
89108915 await printMessages();
89118916 saveSettingsDebounced();
89128917}
public/scripts/BulkEditOverlay.js+11 -11
@@ -108,14 +108,12 @@ class CharacterContextMenu {
108108 * Delete one or more characters,
109109 * opens a popup.
110110 *
111111 * @param {numberstring|string[]} characterIdcharacterKey
112112 * @param {boolean} [deleteChats]
113113 * @returns {Promise<void>}
114114 */
115115 static delete = async (characterIdcharacterKey, deleteChats = false) => {
116- const character = CharacterContextMenu.#getCharacter(characterId);
116+ await deleteCharacter(characterKey, { deleteChats: deleteChats });
117-
118- await deleteCharacter(character.avatar, { deleteChats: deleteChats });
119117 };
120118
121119 static #getCharacter = (characterId) => characters[characterId] ?? null;
@@ -599,8 +597,7 @@ class BulkEditOverlay {
599597
600598 this.container.removeEventListener('mouseup', cancelHold);
601599 this.container.removeEventListener('touchend', cancelHold);
602600 }, BulkEditOverlay.longPressDelay);
603- BulkEditOverlay.longPressDelay);
604601 };
605602
606603 handleLongPressEnd = (event) => {
@@ -847,11 +844,14 @@ class BulkEditOverlay {
847844 const deleteChats = document.getElementById('del_char_checkbox').checked ?? false;
848845
849846 showLoader();
850847 const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it');
851- return Promise.allSettled(characterIds.map(async characterId => CharacterContextMenu.delete(characterId, deleteChats)))
848+ const avatarList = characterIds.map(id => characters[id]?.avatar).filter(a => a);
852- .then(() => getCharacters())
849+ return CharacterContextMenu.delete(avatarList, deleteChats)
853850 .then(() => this.browseState())
854851 .finally(() => hideLoader());{
852+ toastr.clear(toast);
853+ hideLoader();
854+ });
855855 });
856856
857857 // At this moment the popup is already changed in the dom, but not yet closed/resolved. We build the avatar list here