Recent chats: add delete and rename buttons (#4051) * [wip] Add rename/delete for recent chats * Implement deleteCharacterChatByName * Fix character name usage in deleteCharacterChatByName function
Signed| @@ -142,12 +142,28 @@ body.hideChatAvatars .welcomePanel .recentChatList .recentChat .avatar { | ||
| 142 | 142 | justify-content: space-between; |
| 143 | 143 | align-items: baseline; |
| 144 | 144 | font-size: calc(var(--mainFontSize) * 1); |
| 145 | + gap: 5px; | |
| 145 | 146 | } |
| 146 | 147 | |
| 147 | 148 | .welcomeRecent .recentChatList .recentChat .chatNameContainer .chatName { |
| 148 | 149 | white-space: nowrap; |
| 149 | 150 | text-overflow: ellipsis; |
| 150 | 151 | overflow: hidden; |
| 152 | + flex: 1; | |
| 153 | +} | |
| 154 | + | |
| 155 | +.welcomeRecent .recentChatList .recentChat .chatActions { | |
| 156 | + display: flex; | |
| 157 | + flex-direction: row; | |
| 158 | + align-items: center; | |
| 159 | + justify-content: center; | |
| 160 | + gap: 5px; | |
| 161 | +} | |
| 162 | + | |
| 163 | +.welcomeRecent .recentChatList .recentChat .chatActions button { | |
| 164 | + margin: 0; | |
| 165 | + font-size: 0.8em; | |
| 166 | + cursor: pointer; | |
| 151 | 167 | } |
| 152 | 168 | |
| 153 | 169 | .welcomeRecent .recentChatList .recentChat .chatMessageContainer { |
| @@ -1880,6 +1880,52 @@ async function delChat(chatfile) { | ||
| 1880 | 1880 | } |
| 1881 | 1881 | } |
| 1882 | 1882 | |
| 1883 | +/** | |
| 1884 | + * Deletes a character chat by its name. | |
| 1885 | + * @param {string} characterId Character ID to delete chat for | |
| 1886 | + * @param {string} fileName Name of the chat file to delete (without .jsonl extension) | |
| 1887 | + * @returns {Promise<void>} A promise that resolves when the chat is deleted. | |
| 1888 | + */ | |
| 1889 | +export async function deleteCharacterChatByName(characterId, fileName) { | |
| 1890 | + // Make sure all the data is loaded. | |
| 1891 | + await unshallowCharacter(characterId); | |
| 1892 | + | |
| 1893 | + /** @type {import('./scripts/char-data.js').v1CharData} */ | |
| 1894 | + const character = characters[characterId]; | |
| 1895 | + if (!character) { | |
| 1896 | + console.warn(`Character with ID ${characterId} not found.`); | |
| 1897 | + return; | |
| 1898 | + } | |
| 1899 | + | |
| 1900 | + const response = await fetch('/api/chats/delete', { | |
| 1901 | + method: 'POST', | |
| 1902 | + headers: getRequestHeaders(), | |
| 1903 | + body: JSON.stringify({ | |
| 1904 | + chatfile: `${fileName}.jsonl`, | |
| 1905 | + avatar_url: character.avatar, | |
| 1906 | + }), | |
| 1907 | + }); | |
| 1908 | + | |
| 1909 | + if (!response.ok) { | |
| 1910 | + console.error('Failed to delete chat for character.'); | |
| 1911 | + return; | |
| 1912 | + } | |
| 1913 | + | |
| 1914 | + if (fileName === character.chat) { | |
| 1915 | + const chatsResponse = await fetch('/api/characters/chats', { | |
| 1916 | + method: 'POST', | |
| 1917 | + headers: getRequestHeaders(), | |
| 1918 | + body: JSON.stringify({ avatar_url: character.avatar }), | |
| 1919 | + }); | |
| 1920 | + const chats = Object.values(await chatsResponse.json()); | |
| 1921 | + chats.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes))); | |
| 1922 | + const newChatName = chats.length && typeof chats[0] === 'object' ? chats[0].file_name.replace('.jsonl', '') : `${character.name} - ${humanizedDateTime()}`; | |
| 1923 | + await updateRemoteChatName(characterId, newChatName); | |
| 1924 | + } | |
| 1925 | + | |
| 1926 | + await eventSource.emit(event_types.CHAT_DELETED, fileName); | |
| 1927 | +} | |
| 1928 | + | |
| 1883 | 1929 | export async function replaceCurrentChat() { |
| 1884 | 1930 | await clearChat(); |
| 1885 | 1931 | chat.length = 0; |
| @@ -9985,16 +10031,21 @@ async function doRenameChat(_, chatName) { | ||
| 9985 | 10031 | } |
| 9986 | 10032 | |
| 9987 | 10033 | /** |
| 9988 | 10034 | * Renames thea currentlygroup selectedor character chat. |
| 9989 | 10035 | * @param {stringobject} oldFileName Oldparam nameParameters offor therenaming chat (no JSONL extension) |
| 9990 | 10036 | * @param {string} newName[param.characterId] NewCharacter nameID forto therename chat (no JSONL extension)for |
| 10037 | + * @param {string} [param.groupId] Group ID to rename chat for | |
| 10038 | + * @param {string} param.oldFileName Old name of the chat (no JSONL extension) | |
| 10039 | + * @param {string} param.newFileName New name for the chat (no JSONL extension) | |
| 10040 | + * @param {boolean} [param.loader=true] Whether to show loader during the operation | |
| 9991 | 10041 | */ |
| 9992 | 10042 | export async function renameChatrenameGroupOrCharacterChat({ characterId, groupId, oldFileName, newNamenewFileName, loader }) { |
| 10043 | + const currentChatId = getCurrentChatId(); | |
| 9993 | 10044 | const body = { |
| 9994 | 10045 | is_group: !!selected_groupgroupId, |
| 9995 | 10046 | avatar_url: characters[this_chidcharacterId]?.avatar, |
| 9996 | 10047 | original_file: `${oldFileName}.jsonl`, |
| 9997 | 10048 | renamed_file: `${newNamenewFileName.trim()}.jsonl`, |
| 9998 | 10049 | }; |
| 9999 | 10050 | |
| 10000 | 10051 | if (body.original_file === body.renamed_file) { |
| @@ -10007,7 +10058,8 @@ export async function renameChat(oldFileName, newName) { | ||
| 10007 | 10058 | } |
| 10008 | 10059 | |
| 10009 | 10060 | try { |
| 10010 | 10061 | loader && showLoader(); |
| 10062 | + | |
| 10011 | 10063 | const response = await fetch('/api/chats/rename', { |
| 10012 | 10064 | method: 'POST', |
| 10013 | 10065 | body: JSON.stringify(body), |
| @@ -10025,27 +10077,69 @@ export async function renameChat(oldFileName, newName) { | ||
| 10025 | 10077 | } |
| 10026 | 10078 | |
| 10027 | 10079 | if (data.sanitizedFileName) { |
| 10028 | 10080 | newNamenewFileName = data.sanitizedFileName; |
| 10029 | 10081 | } |
| 10030 | 10082 | |
| 10031 | 10083 | if (selected_groupgroupId) { |
| 10032 | 10084 | await renameGroupChat(selected_groupgroupId, oldFileName, newNamenewFileName); |
| 10033 | 10085 | } |
| 10034 | - else { | |
| 10086 | + else if (characterId !== undefined && String(characterId) === String(this_chid) && characters[characterId]?.chat === oldFileName) { | |
| 10035 | 10087 | if (characters[this_chidcharacterId].chat == oldFileName) {newFileName; |
| 10036 | - characters[this_chid].chat = newName; | |
| 10088 | + $('#selected_chat_pole').val(characters[characterId].chat); | |
| 10037 | - $('#selected_chat_pole').val(characters[this_chid].chat); | |
| 10038 | 10089 | await createOrEditCharacter(); |
| 10039 | 10090 | } |
| 10040 | - } | |
| 10041 | 10091 | |
| 10092 | + if (currentChatId) { | |
| 10042 | 10093 | await reloadCurrentChat(); |
| 10094 | + } | |
| 10043 | 10095 | } catch { |
| 10044 | 10096 | loader && hideLoader(); |
| 10045 | 10097 | await delay(500); |
| 10046 | 10098 | await callPopupcallGenericPopup('An error has occurred. Chat was not renamed.', 'text'POPUP_TYPE.TEXT); |
| 10047 | 10099 | } finally { |
| 10048 | 10100 | loader && hideLoader(); |
| 10101 | + } | |
| 10102 | +} | |
| 10103 | + | |
| 10104 | +/** | |
| 10105 | + * Renames the currently selected chat. | |
| 10106 | + * @param {string} oldFileName Old name of the chat (no JSONL extension) | |
| 10107 | + * @param {string} newName New name for the chat (no JSONL extension) | |
| 10108 | + */ | |
| 10109 | +export async function renameChat(oldFileName, newName) { | |
| 10110 | + return await renameGroupOrCharacterChat({ | |
| 10111 | + characterId: this_chid, | |
| 10112 | + groupId: selected_group, | |
| 10113 | + oldFileName: oldFileName, | |
| 10114 | + newFileName: newName, | |
| 10115 | + loader: true, | |
| 10116 | + }); | |
| 10117 | +} | |
| 10118 | + | |
| 10119 | +/** | |
| 10120 | + * Forces the update of the chat name for a remote character. | |
| 10121 | + * @param {string|number} characterId Character ID to update chat name for | |
| 10122 | + * @param {string} newName New name for the chat | |
| 10123 | + * @returns {Promise<void>} | |
| 10124 | + */ | |
| 10125 | +export async function updateRemoteChatName(characterId, newName) { | |
| 10126 | + const character = characters[characterId]; | |
| 10127 | + if (!character) { | |
| 10128 | + console.warn(`Character not found for ID: ${characterId}`); | |
| 10129 | + return; | |
| 10130 | + } | |
| 10131 | + character.chat = newName; | |
| 10132 | + const mergeRequest = { | |
| 10133 | + avatar: character.avatar, | |
| 10134 | + chat: newName, | |
| 10135 | + }; | |
| 10136 | + const mergeResponse = await fetch('/api/characters/merge-attributes', { | |
| 10137 | + method: 'POST', | |
| 10138 | + headers: getRequestHeaders(), | |
| 10139 | + body: JSON.stringify(mergeRequest), | |
| 10140 | + }); | |
| 10141 | + if (!mergeResponse.ok) { | |
| 10142 | + console.error('Failed to save extension field', mergeResponse.statusText); | |
| 10049 | 10143 | } |
| 10050 | 10144 | } |
| 10051 | 10145 | |
| @@ -1966,6 +1966,51 @@ export async function renameGroupChat(groupId, oldChatId, newChatId) { | ||
| 1966 | 1966 | await editGroup(groupId, true, true); |
| 1967 | 1967 | } |
| 1968 | 1968 | |
| 1969 | +/** | |
| 1970 | + * Deletes a group chat by its name. Doesn't affect displayed chat. | |
| 1971 | + * @param {string} groupId Group ID | |
| 1972 | + * @param {string} chatName Name of the chat to delete | |
| 1973 | + * @returns {Promise<void>} | |
| 1974 | + */ | |
| 1975 | +export async function deleteGroupChatByName(groupId, chatName) { | |
| 1976 | + const group = groups.find(x => x.id === groupId); | |
| 1977 | + if (!group || !group.chats.includes(chatName)) { | |
| 1978 | + return; | |
| 1979 | + } | |
| 1980 | + | |
| 1981 | + if (typeof group.past_metadata !== 'object') { | |
| 1982 | + group.past_metadata = {}; | |
| 1983 | + } | |
| 1984 | + | |
| 1985 | + group.chats.splice(group.chats.indexOf(chatName), 1); | |
| 1986 | + delete group.past_metadata[chatName]; | |
| 1987 | + | |
| 1988 | + const response = await fetch('/api/chats/group/delete', { | |
| 1989 | + method: 'POST', | |
| 1990 | + headers: getRequestHeaders(), | |
| 1991 | + body: JSON.stringify({ id: chatName }), | |
| 1992 | + }); | |
| 1993 | + | |
| 1994 | + if (!response.ok) { | |
| 1995 | + toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Group chat could not be deleted`); | |
| 1996 | + console.error('Group chat could not be deleted'); | |
| 1997 | + return; | |
| 1998 | + } | |
| 1999 | + | |
| 2000 | + // If the deleted chat was the current chat, switch to the last chat in the group | |
| 2001 | + if (group.chat_id === chatName) { | |
| 2002 | + group.chat_id = ''; | |
| 2003 | + group.chat_metadata = {}; | |
| 2004 | + | |
| 2005 | + const newChatName = group.chats.length ? group.chats[group.chats.length - 1] : humanizedDateTime(); | |
| 2006 | + group.chat_id = newChatName; | |
| 2007 | + group.chat_metadata = group.past_metadata[newChatName] || {}; | |
| 2008 | + } | |
| 2009 | + | |
| 2010 | + await editGroup(groupId, true, true); | |
| 2011 | + await eventSource.emit(event_types.GROUP_CHAT_DELETED, chatName); | |
| 2012 | +} | |
| 2013 | + | |
| 1969 | 2014 | export async function deleteGroupChat(groupId, chatId) { |
| 1970 | 2015 | const group = groups.find(x => x.id === groupId); |
| 1971 | 2016 | |
| @@ -55,6 +55,14 @@ | ||
| 55 | 55 | <span>{{chat_name}}</span> |
| 56 | 56 | </div> |
| 57 | 57 | <small class="chatDate" title="{{date_long}}">{{date_short}}</small> |
| 58 | + <div class="chatActions"> | |
| 59 | + <button class="menu_button menu_button_icon renameChat" title="Rename chat" data-i18n="[title]Rename chat"> | |
| 60 | + <i class="fa-solid fa-pen-to-square fa-fw"></i> | |
| 61 | + </button> | |
| 62 | + <button class="menu_button menu_button_icon deleteChat" title="Delete chat" data-i18n="[title]Delete chat"> | |
| 63 | + <i class="fa-solid fa-trash fa-fw"></i> | |
| 64 | + </button> | |
| 65 | + </div> | |
| 58 | 66 | </div> |
| 59 | 67 | <div class="chatMessageContainer"> |
| 60 | 68 | <div class="chatMessage" title="{{mes}}"> |
| @@ -2,6 +2,7 @@ import { | ||
| 2 | 2 | addOneMessage, |
| 3 | 3 | characters, |
| 4 | 4 | chat, |
| 5 | + deleteCharacterChatByName, | |
| 5 | 6 | displayVersion, |
| 6 | 7 | doNewChat, |
| 7 | 8 | event_types, |
| @@ -16,15 +17,18 @@ import { | ||
| 16 | 17 | newAssistantChat, |
| 17 | 18 | openCharacterChat, |
| 18 | 19 | printCharactersDebounced, |
| 20 | + renameGroupOrCharacterChat, | |
| 19 | 21 | selectCharacterById, |
| 20 | 22 | system_avatar, |
| 21 | 23 | system_message_types, |
| 22 | 24 | this_chid, |
| 23 | 25 | unshallowCharacter, |
| 26 | + updateRemoteChatName, | |
| 24 | 27 | } from '../script.js'; |
| 25 | 28 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 26 | 29 | import { deleteGroupChatByName, getGroupAvatar, groups, is_group_generating, openGroupById, openGroupChat } from './group-chats.js'; |
| 27 | 30 | import { t } from './i18n.js'; |
| 31 | +import { callGenericPopup, POPUP_TYPE } from './popup.js'; | |
| 28 | 32 | import { getMessageTimeStamp } from './RossAscends-mods.js'; |
| 29 | 33 | import { renderTemplateAsync } from './templates.js'; |
| 30 | 34 | import { accountStorage } from './util/AccountStorage.js'; |
| @@ -51,9 +55,16 @@ export function getPermanentAssistantAvatar() { | ||
| 51 | 55 | return assistantAvatar; |
| 52 | 56 | } |
| 53 | 57 | |
| 54 | -export async function openWelcomeScreen() { | |
| 58 | +/** | |
| 59 | + * Opens a welcome screen if no chat is currently active. | |
| 60 | + * @param {object} param Additional parameters | |
| 61 | + * @param {boolean} [param.force] If true, forces clearing of the welcome screen. | |
| 62 | + * @param {boolean} [param.expand] If true, expands the recent chats section. | |
| 63 | + * @returns {Promise<void>} | |
| 64 | + */ | |
| 65 | +export async function openWelcomeScreen({ force = false, expand = false } = {}) { | |
| 55 | 66 | const currentChatId = getCurrentChatId(); |
| 56 | 67 | if (currentChatId !== undefined || (chat.length > 0 && !force)) { |
| 57 | 68 | return; |
| 58 | 69 | } |
| 59 | 70 | |
| @@ -64,7 +75,13 @@ export async function openWelcomeScreen() { | ||
| 64 | 75 | return; |
| 65 | 76 | } |
| 66 | 77 | |
| 67 | - await sendWelcomePanel(recentChats); | |
| 78 | + if (chatAfterFetch === undefined && force) { | |
| 79 | + console.debug('Forcing welcome screen open.'); | |
| 80 | + chat.splice(0, chat.length); | |
| 81 | + $('#chat').empty(); | |
| 82 | + } | |
| 83 | + | |
| 84 | + await sendWelcomePanel(recentChats, expand); | |
| 68 | 85 | await unshallowPermanentAssistant(); |
| 69 | 86 | sendAssistantMessage(); |
| 70 | 87 | sendWelcomePrompt(); |
| @@ -131,8 +148,9 @@ function sendWelcomePrompt() { | ||
| 131 | 148 | /** |
| 132 | 149 | * Sends the welcome panel to the chat. |
| 133 | 150 | * @param {RecentChat[]} chats List of recent chats |
| 151 | + * @param {boolean} [expand=false] If true, expands the recent chats section | |
| 134 | 152 | */ |
| 135 | 153 | async function sendWelcomePanel(chats, expand = false) { |
| 136 | 154 | try { |
| 137 | 155 | const chatElement = document.getElementById('chat'); |
| 138 | 156 | const sendTextArea = document.getElementById('send_textarea'); |
| @@ -215,7 +233,50 @@ async function sendWelcomePanel(chats) { | ||
| 215 | 233 | $(avatar).replaceWith(groupAvatar); |
| 216 | 234 | } |
| 217 | 235 | }); |
| 236 | + fragment.querySelectorAll('.recentChat .renameChat').forEach((renameButton) => { | |
| 237 | + renameButton.addEventListener('click', (event) => { | |
| 238 | + event.stopPropagation(); | |
| 239 | + const chatItem = renameButton.closest('.recentChat'); | |
| 240 | + if (!chatItem) { | |
| 241 | + return; | |
| 242 | + } | |
| 243 | + const avatarId = chatItem.getAttribute('data-avatar'); | |
| 244 | + const groupId = chatItem.getAttribute('data-group'); | |
| 245 | + const fileName = chatItem.getAttribute('data-file'); | |
| 246 | + if (avatarId && fileName) { | |
| 247 | + void renameRecentCharacterChat(avatarId, fileName); | |
| 248 | + } | |
| 249 | + if (groupId && fileName) { | |
| 250 | + void renameRecentGroupChat(groupId, fileName); | |
| 251 | + } | |
| 252 | + }); | |
| 253 | + }); | |
| 254 | + fragment.querySelectorAll('.recentChat .deleteChat').forEach((deleteButton) => { | |
| 255 | + deleteButton.addEventListener('click', (event) => { | |
| 256 | + event.stopPropagation(); | |
| 257 | + const chatItem = deleteButton.closest('.recentChat'); | |
| 258 | + if (!chatItem) { | |
| 259 | + return; | |
| 260 | + } | |
| 261 | + const avatarId = chatItem.getAttribute('data-avatar'); | |
| 262 | + const groupId = chatItem.getAttribute('data-group'); | |
| 263 | + const fileName = chatItem.getAttribute('data-file'); | |
| 264 | + if (avatarId && fileName) { | |
| 265 | + void deleteRecentCharacterChat(avatarId, fileName); | |
| 266 | + } | |
| 267 | + if (groupId && fileName) { | |
| 268 | + void deleteRecentGroupChat(groupId, fileName); | |
| 269 | + } | |
| 270 | + }); | |
| 271 | + }); | |
| 218 | 272 | chatElement.append(fragment.firstChild); |
| 273 | + if (expand) { | |
| 274 | + chatElement.querySelectorAll('button.showMoreChats').forEach((button) => { | |
| 275 | + if (button instanceof HTMLButtonElement) { | |
| 276 | + button.click(); | |
| 277 | + } | |
| 278 | + }); | |
| 279 | + } | |
| 219 | 280 | } catch (error) { |
| 220 | 281 | console.error('Welcome screen error:', error); |
| 221 | 282 | } |
| @@ -274,6 +335,144 @@ async function openRecentGroupChat(groupId, fileName) { | ||
| 274 | 335 | } |
| 275 | 336 | |
| 276 | 337 | /** |
| 338 | + * Renames a recent character chat. | |
| 339 | + * @param {string} avatarId Avatar file name | |
| 340 | + * @param {string} fileName Chat file name | |
| 341 | + */ | |
| 342 | +async function renameRecentCharacterChat(avatarId, fileName) { | |
| 343 | + const characterId = characters.findIndex(x => x.avatar === avatarId); | |
| 344 | + if (characterId === -1) { | |
| 345 | + console.error(`Character not found for avatar ID: ${avatarId}`); | |
| 346 | + return; | |
| 347 | + } | |
| 348 | + try { | |
| 349 | + const popupText = await renderTemplateAsync('chatRename'); | |
| 350 | + const newName = await callGenericPopup(popupText, POPUP_TYPE.INPUT, fileName); | |
| 351 | + if (!newName || typeof newName !== 'string' || newName === fileName) { | |
| 352 | + console.log('No new name provided, aborting'); | |
| 353 | + return; | |
| 354 | + } | |
| 355 | + await renameGroupOrCharacterChat({ | |
| 356 | + characterId: String(characterId), | |
| 357 | + oldFileName: fileName, | |
| 358 | + newFileName: newName, | |
| 359 | + loader: false, | |
| 360 | + }); | |
| 361 | + await updateRemoteChatName(characterId, newName); | |
| 362 | + await refreshWelcomeScreen(); | |
| 363 | + toastr.success(t`Chat renamed.`); | |
| 364 | + } catch (error) { | |
| 365 | + console.error('Error renaming recent character chat:', error); | |
| 366 | + toastr.error(t`Failed to rename recent chat. See console for details.`); | |
| 367 | + } | |
| 368 | +} | |
| 369 | + | |
| 370 | +/** | |
| 371 | + * Renames a recent group chat. | |
| 372 | + * @param {string} groupId Group ID | |
| 373 | + * @param {string} fileName Chat file name | |
| 374 | + */ | |
| 375 | +async function renameRecentGroupChat(groupId, fileName) { | |
| 376 | + const group = groups.find(x => x.id === groupId); | |
| 377 | + if (!group) { | |
| 378 | + console.error(`Group not found for ID: ${groupId}`); | |
| 379 | + return; | |
| 380 | + } | |
| 381 | + try { | |
| 382 | + const popupText = await renderTemplateAsync('chatRename'); | |
| 383 | + const newName = await callGenericPopup(popupText, POPUP_TYPE.INPUT, fileName); | |
| 384 | + if (!newName || newName === fileName) { | |
| 385 | + console.log('No new name provided, aborting'); | |
| 386 | + return; | |
| 387 | + } | |
| 388 | + await renameGroupOrCharacterChat({ | |
| 389 | + groupId: String(groupId), | |
| 390 | + oldFileName: fileName, | |
| 391 | + newFileName: String(newName), | |
| 392 | + loader: false, | |
| 393 | + }); | |
| 394 | + await refreshWelcomeScreen(); | |
| 395 | + toastr.success(t`Group chat renamed.`); | |
| 396 | + } catch (error) { | |
| 397 | + console.error('Error renaming recent group chat:', error); | |
| 398 | + toastr.error(t`Failed to rename recent group chat. See console for details.`); | |
| 399 | + } | |
| 400 | +} | |
| 401 | + | |
| 402 | +/** | |
| 403 | + * Deletes a recent character chat. | |
| 404 | + * @param {string} avatarId Avatar file name | |
| 405 | + * @param {string} fileName Chat file name | |
| 406 | + */ | |
| 407 | +async function deleteRecentCharacterChat(avatarId, fileName) { | |
| 408 | + const characterId = characters.findIndex(x => x.avatar === avatarId); | |
| 409 | + if (characterId === -1) { | |
| 410 | + console.error(`Character not found for avatar ID: ${avatarId}`); | |
| 411 | + return; | |
| 412 | + } | |
| 413 | + try { | |
| 414 | + const confirm = await callGenericPopup(t`Delete the Chat File?`, POPUP_TYPE.CONFIRM); | |
| 415 | + if (!confirm) { | |
| 416 | + console.log('Deletion cancelled by user'); | |
| 417 | + return; | |
| 418 | + } | |
| 419 | + await deleteCharacterChatByName(String(characterId), fileName); | |
| 420 | + await refreshWelcomeScreen(); | |
| 421 | + toastr.success(t`Chat deleted.`); | |
| 422 | + } catch (error) { | |
| 423 | + console.error('Error deleting recent character chat:', error); | |
| 424 | + toastr.error(t`Failed to delete recent chat. See console for details.`); | |
| 425 | + } | |
| 426 | +} | |
| 427 | + | |
| 428 | +/** | |
| 429 | + * Deletes a recent group chat. | |
| 430 | + * @param {string} groupId Group ID | |
| 431 | + * @param {string} fileName Chat file name | |
| 432 | + */ | |
| 433 | +async function deleteRecentGroupChat(groupId, fileName) { | |
| 434 | + const group = groups.find(x => x.id === groupId); | |
| 435 | + if (!group) { | |
| 436 | + console.error(`Group not found for ID: ${groupId}`); | |
| 437 | + return; | |
| 438 | + } | |
| 439 | + try { | |
| 440 | + const confirm = await callGenericPopup(t`Delete the Chat File?`, POPUP_TYPE.CONFIRM); | |
| 441 | + if (!confirm) { | |
| 442 | + console.log('Deletion cancelled by user'); | |
| 443 | + return; | |
| 444 | + } | |
| 445 | + await deleteGroupChatByName(groupId, fileName); | |
| 446 | + await refreshWelcomeScreen(); | |
| 447 | + toastr.success(t`Group chat deleted.`); | |
| 448 | + } catch (error) { | |
| 449 | + console.error('Error deleting recent group chat:', error); | |
| 450 | + toastr.error(t`Failed to delete recent group chat. See console for details.`); | |
| 451 | + } | |
| 452 | +} | |
| 453 | + | |
| 454 | +/** | |
| 455 | + * Reopens the welcome screen and restores the scroll position. | |
| 456 | + * @returns {Promise<void>} | |
| 457 | + */ | |
| 458 | +async function refreshWelcomeScreen() { | |
| 459 | + const chatElement = document.getElementById('chat'); | |
| 460 | + if (!chatElement) { | |
| 461 | + console.error('Chat element not found'); | |
| 462 | + return; | |
| 463 | + } | |
| 464 | + | |
| 465 | + const scrollTop = chatElement.scrollTop; | |
| 466 | + const scrollHeight = chatElement.scrollHeight; | |
| 467 | + const expand = chatElement.querySelectorAll('button.showMoreChats.rotated').length > 0; | |
| 468 | + | |
| 469 | + await openWelcomeScreen({ force: true, expand }); | |
| 470 | + | |
| 471 | + // Restore scroll position | |
| 472 | + chatElement.scrollTop = scrollTop + (chatElement.scrollHeight - scrollHeight); | |
| 473 | +} | |
| 474 | + | |
| 475 | +/** | |
| 277 | 476 | * Gets the list of recent chats from the server. |
| 278 | 477 | * @returns {Promise<RecentChat[]>} List of recent chats |
| 279 | 478 | * |