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, +61 -56Ignore whitespace
public/script.js+49 -44
@@ -488,14 +488,6 @@ let default_user_name = 'User';
488export let name1 = default_user_name;488export let name1 = default_user_name;
489export let name2 = 'SillyTavern System';489export let name2 = 'SillyTavern System';
490export let chat = [];490export let chat = [];
491let 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];
499let chatSaveTimeout;491let chatSaveTimeout;
500let importFlashTimeout;492let importFlashTimeout;
501export let isChatSaving = false;493export let isChatSaving = false;
@@ -594,6 +586,17 @@ export const extension_prompt_roles = {
594586
595export const MAX_INJECTION_DEPTH = 1000;587export const MAX_INJECTION_DEPTH = 1000;
596588
589const 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
597export let system_messages = {};600export let system_messages = {};
598601
599async function getSystemMessages() {602async function getSystemMessages() {
@@ -5680,7 +5683,7 @@ export function resetChatState() {
5680 // replaces deleted charcter name with system user since it will be displayed next.5683 // replaces deleted charcter name with system user since it will be displayed next.
5681 name2 = systemUserName;5684 name2 = systemUserName;
5682 // sets up system user to tell user about having deleted a character5685 // sets up system user to tell user about having deleted a character
5683 chat = [...safetychat];5686 chat.splice(0, chat.length, ...SAFETY_CHAT);
5684 // resets chat metadata5687 // resets chat metadata
5685 chat_metadata = {};5688 chat_metadata = {};
5686 // resets the characters array, forcing getcharacters to reset5689 // resets the characters array, forcing getcharacters to reset
@@ -8841,72 +8844,74 @@ export async function handleDeleteCharacter(this_chid, delete_chats) {
8841/**8844/**
8842 * Deletes a character completely, including associated chats if specified8845 * Deletes a character completely, including associated chats if specified
8843 *8846 *
8844 * @param {string} characterKey - The key (avatar) of the character to be deleted8847 * @param {string|string[]} characterKey - The key (avatar) of the character to be deleted
8845 * @param {Object} [options] - Optional parameters for the deletion8848 * @param {Object} [options] - Optional parameters for the deletion
8846 * @param {boolean} [options.deleteChats=true] - Whether to delete associated chats or not8849 * @param {boolean} [options.deleteChats=true] - Whether to delete associated chats or not
8847 * @return {Promise<void>} - A promise that resolves when the character is successfully deleted8850 * @return {Promise<void>} - A promise that resolves when the character is successfully deleted
8848 */8851 */
8849export async function deleteCharacter(characterKey, { deleteChats = true } = {}) {8852export async function deleteCharacter(characterKey, { deleteChats = true } = {}) {
8850 const character = characters.find(x => x.avatar == characterKey);8853 if (!Array.isArray(characterKey)) {
8851 if (!character) {8854 characterKey = [characterKey];
8852 toastr.warning(`Character ${characterKey} not found. Cannot be deleted.`);
8853 return;
8854 }8855 }
88558856
8856 const chid = characters.indexOf(character);8857 for (const key of characterKey) {
8857 const pastChats = await getPastCharacterChats(chid);8858 const character = characters.find(x => x.avatar == key);
8859 if (!character) {
8860 toastr.warning(`Character ${key} not found. Skipping deletion.`);
8861 continue;
8862 }
88588863
8859 const msg = { avatar_url: character.avatar, delete_chats: deleteChats };8864 const chid = characters.indexOf(character);
8865 const pastChats = await getPastCharacterChats(chid);
88608866
8861 const response = await fetch('/api/characters/delete', {8867 const msg = { avatar_url: character.avatar, delete_chats: deleteChats };
8862 method: 'POST',
8863 headers: getRequestHeaders(),
8864 body: JSON.stringify(msg),
8865 cache: 'no-cache',
8866 });
88678868
8868 if (!response.ok) {8869 const response = await fetch('/api/characters/delete', {
8869 throw new Error(`Failed to delete character: ${response.status} ${response.statusText}`);8870 method: 'POST',
8870 }8871 headers: getRequestHeaders(),
8872 body: JSON.stringify(msg),
8873 cache: 'no-cache',
8874 });
8875
8876 if (!response.ok) {
8877 toastr.error(`${response.status} ${response.statusText}`, 'Failed to delete character');
8878 continue;
8879 }
88718880
8872 await removeCharacterFromUI(character.name, character.avatar);8881 delete tag_map[character.avatar];
8882 select_rm_info('char_delete', character.name);
88738883
8874 if (deleteChats) {8884 if (deleteChats) {
8875 for (const chat of pastChats) {8885 for (const chat of pastChats) {
8876 const name = chat.file_name.replace('.jsonl', '');8886 const name = chat.file_name.replace('.jsonl', '');
8877 await eventSource.emit(event_types.CHAT_DELETED, name);8887 await eventSource.emit(event_types.CHAT_DELETED, name);
8888 }
8878 }8889 }
8890
8891 await eventSource.emit(event_types.CHARACTER_DELETED, { id: chid, character: character });
8879 }8892 }
88808893
8881 eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });8894 await removeCharacterFromUI();
8882}8895}
88838896
8884/**8897/**
8885 * Function to delete a character from UI after character deletion API success.8898 * Function to delete a character from UI after character deletion API success.
8886 * It manages necessary UI changes such as closing advanced editing popup, unsetting8899 * It manages necessary UI changes such as closing advanced editing popup, unsetting
8887 * character ID, resetting characters array and chat metadata, deselecting character's tab8900 * character ID, resetting characters array and chat metadata, deselecting character's tab
8888 * panel, removing character name from navigation tabs, clearing chat, removing character's8901 * panel, removing character name from navigation tabs, clearing chat, fetching updated list of characters.
8889 * avatar from tag_map, fetching updated list of characters and updating the 'deleted
8890 * character' message.
8891 * It also ensures to save the settings after all the operations.8902 * 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.
8896 */8903 */
8897async function removeCharacterFromUI(name, avatar, reloadCharacters = true) {8904async function removeCharacterFromUI() {
8898 await clearChat();8905 await clearChat();
8899 $('#character_cross').click();8906 $('#character_cross').click();
8900 this_chid = undefined;8907 this_chid = undefined;
8901 characters.length = 0;8908 characters.length = 0;
8902 name2 = systemUserName;8909 name2 = systemUserName;
8903 chat = [...safetychat];8910 chat.splice(0, chat.length, ...SAFETY_CHAT);
8904 chat_metadata = {};8911 chat_metadata = {};
8905 $(document.getElementById('rm_button_selected_ch')).children('h2').text('');8912 $(document.getElementById('rm_button_selected_ch')).children('h2').text('');
8906 this_chid = undefined;8913 this_chid = undefined;
8907 delete tag_map[avatar];8914 await getCharacters();
8908 if (reloadCharacters) await getCharacters();
8909 select_rm_info('char_delete', name);
8910 await printMessages();8915 await printMessages();
8911 saveSettingsDebounced();8916 saveSettingsDebounced();
8912}8917}
public/scripts/BulkEditOverlay.js+12 -12
@@ -108,14 +108,12 @@ class CharacterContextMenu {
108 * Delete one or more characters,108 * Delete one or more characters,
109 * opens a popup.109 * opens a popup.
110 *110 *
111 * @param {number} characterId111 * @param {string|string[]} characterKey
112 * @param {boolean} [deleteChats]112 * @param {boolean} [deleteChats]
113 * @returns {Promise<void>}113 * @returns {Promise<void>}
114 */114 */
115 static delete = async (characterId, deleteChats = false) => {115 static delete = async (characterKey, deleteChats = false) => {
116 const character = CharacterContextMenu.#getCharacter(characterId);116 await deleteCharacter(characterKey, { deleteChats: deleteChats });
117
118 await deleteCharacter(character.avatar, { deleteChats: deleteChats });
119 };117 };
120118
121 static #getCharacter = (characterId) => characters[characterId] ?? null;119 static #getCharacter = (characterId) => characters[characterId] ?? null;
@@ -344,7 +342,7 @@ class BulkTagPopupHandler {
344 const mutualTags = this.getMutualTags();342 const mutualTags = this.getMutualTags();
345343
346 for (const characterId of this.characterIds) {344 for (const characterId of this.characterIds) {
347 for(const tag of mutualTags) {345 for (const tag of mutualTags) {
348 removeTagFromMap(tag.id, characterId);346 removeTagFromMap(tag.id, characterId);
349 }347 }
350 }348 }
@@ -599,8 +597,7 @@ class BulkEditOverlay {
599597
600 this.container.removeEventListener('mouseup', cancelHold);598 this.container.removeEventListener('mouseup', cancelHold);
601 this.container.removeEventListener('touchend', cancelHold);599 this.container.removeEventListener('touchend', cancelHold);
602 },600 }, BulkEditOverlay.longPressDelay);
603 BulkEditOverlay.longPressDelay);
604 };601 };
605602
606 handleLongPressEnd = (event) => {603 handleLongPressEnd = (event) => {
@@ -847,11 +844,14 @@ class BulkEditOverlay {
847 const deleteChats = document.getElementById('del_char_checkbox').checked ?? false;844 const deleteChats = document.getElementById('del_char_checkbox').checked ?? false;
848845
849 showLoader();846 showLoader();
850 toastr.info('We\'re deleting your characters, please wait...', 'Working on it');847 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)
853 .then(() => this.browseState())850 .then(() => this.browseState())
854 .finally(() => hideLoader());851 .finally(() => {
852 toastr.clear(toast);
853 hideLoader();
854 });
855 });855 });
856856
857 // At this moment the popup is already changed in the dom, but not yet closed/resolved. We build the avatar list here857 // At this moment the popup is already changed in the dom, but not yet closed/resolved. We build the avatar list here