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
This commit is contained in:
Cohee 2024-08-30 19:52:57 +03:00 committed by GitHub
parent e6021bda1c
commit 1fa9710a5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 61 deletions

View File

@ -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<void>} - 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();
}

View File

@ -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<void>}
*/
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