Update group chat metadata format (#4805)

* Migrate group metadata to group chat files

* Skip migration if chat already has metadata

* Fix active group not being set on group conversion

* Improve types in createGroup

* Fix padding in hotswap group avatars

* Fix centering of empty hotswap avatar

* Added automatic backups of migrated data

* Fix 'OVERWRITE' for GC

* Fix metadata parsing order in migration

* Remove color accents from regular migration logs

* Always set gen_id in converted message

* Clone messages before conversion

* Reduce size of add/remove buttons

* Fix group chat file size calculation
This commit is contained in:
Cohee
2025-11-25 23:48:43 +02:00
committed by GitHub
parent 051b275795
commit 929d377da8
17 changed files with 566 additions and 192 deletions
+18 -33
View File
@@ -65,7 +65,6 @@ import {
renameGroupMember,
createNewGroupChat,
getGroupAvatar,
editGroup,
deleteGroupChat,
renameGroupChat,
importGroupChat,
@@ -377,14 +376,13 @@ export let isSwipingAllowed = true; //false when a swipe is in progress, or swip
let chatSaveTimeout;
let importFlashTimeout;
export let isChatSaving = false;
let chat_create_date = '';
let firstRun = false;
let settingsReady = false;
let currentVersion = '0.0.0';
export let displayVersion = 'SillyTavern';
let generation_started = new Date();
/** @type {import('./scripts/char-data.js').v1CharData[]} */
/** @type {Character[]} */
export let characters = [];
/**
* Stringified index of a currently chosen entity in the characters array.
@@ -411,6 +409,7 @@ export const chatElement = $('#chat');
let dialogueResolve = null;
let dialogueCloseStop = false;
/** @type {ChatMetadata} */
export let chat_metadata = {};
/** @type {StreamingProcessor} */
export let streamingProcessor = null;
@@ -1295,7 +1294,7 @@ export async function deleteCharacterChatByName(characterId, fileName) {
// Make sure all the data is loaded.
await unshallowCharacter(characterId);
/** @type {import('./scripts/char-data.js').v1CharData} */
/** @type {Character} */
const character = characters[characterId];
if (!character) {
console.warn(`Character with ID ${characterId} not found.`);
@@ -6731,7 +6730,7 @@ export async function renameCharacter(name = null, { silent = false, renameChats
}
// Also rename as a group member
await renameGroupMember(oldAvatar, newAvatar, newValue);
await renameGroupMember(oldAvatar, newAvatar, newValue.toString());
const renamePastChatsConfirm = renameChats !== null
? renameChats
: silent
@@ -6860,6 +6859,11 @@ export function saveChatDebounced() {
* @returns {Promise<void>}
*/
export async function saveChat({ chatName, withMetadata, mesId, force = false } = {}) {
if (selected_group) {
toastr.error(t`Operation was aborted to prevent data corruption.`, t`saveChat called for a group chat`);
throw new Error('saveChat called for a group chat');
}
if (arguments.length > 0 && typeof arguments[0] !== 'object') {
console.trace('saveChat called with positional arguments. Please use an object instead.');
[chatName, withMetadata, mesId, force] = arguments;
@@ -6879,26 +6883,15 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false }
}
characters[this_chid]['date_last_chat'] = Date.now();
chat.forEach(function (item, i) {
if (item['is_group']) {
toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);
throw new Error('Group chat saved from saveChat');
}
});
const trimmedChat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
? chat.slice(0, Number(mesId) + 1)
: chat.slice();
const chatToSave = [
{
user_name: name1,
character_name: name2,
create_date: chat_create_date,
chat_metadata: metadata,
},
...trimmedChat,
];
/** @type {ChatHeader} */
const chatHeader = {
chat_metadata: metadata,
};
try {
const result = await fetch('/api/chats/save', {
@@ -6908,7 +6901,7 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false }
body: JSON.stringify({
ch_name: characters[this_chid].name,
file_name: fileName,
chat: chatToSave,
chat: [chatHeader, ...trimmedChat],
avatar_url: characters[this_chid].avatar,
force: force,
}),
@@ -7082,7 +7075,7 @@ export async function unshallowCharacter(characterId) {
return;
}
/** @type {import('./scripts/char-data.js').v1CharData} */
/** @type {Character} */
const character = characters[characterId];
if (!character) {
console.debug('Character not found:', characterId);
@@ -7121,13 +7114,10 @@ export async function getChat() {
});
if (response[0] !== undefined) {
chat.splice(0, chat.length, ...response);
chat_create_date = chat[0]['create_date'];
chat_metadata = chat[0]['chat_metadata'] ?? {};
chat.shift();
chat.forEach(ensureMessageMediaIsArray);
} else {
chat_create_date = humanizedDateTime();
}
if (!chat_metadata['integrity']) {
chat_metadata['integrity'] = uuidv4();
@@ -8720,12 +8710,7 @@ export async function deleteSwipe(swipeId = null, messageId = chat.length - 1) {
}
export async function saveMetadata() {
if (selected_group) {
await editGroup(selected_group, true, false);
}
else {
await saveChatConditional();
}
return await saveChatConditional();
}
export async function saveChatConditional() {
@@ -10356,7 +10341,7 @@ jQuery(async function () {
});
$('#rm_button_selected_ch').on('click', function () {
if (selected_group) {
select_group_chats(selected_group);
select_group_chats(selected_group, false);
} else {
selected_button = 'character_edit';
select_selected_character(this_chid);
@@ -11289,7 +11274,7 @@ jQuery(async function () {
$('#rm_button_group_chats').on('click', function () {
selected_button = 'group_chats';
select_group_chats();
select_group_chats(null, false);
});
$('#rm_button_back_from_group').on('click', function () {