Add bulk and drag&drop to chats import (#4652) * Add bulk and drag-drop to chats import * Perform list refresh only once

3234045d64ffbd09f474cdc968f7f835f5b41bb7

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

Signed
3 files changed, +40 -38Showing whitespace changes
public/index.html+1 -1
@@ -6248,7 +6248,7 @@
62486248 <div name="selectChatPopupHeader" class="flex-container alignitemscenter justifySpaceBetween flexGap10">
62496249 <div id="select_chat_import"> <!-- import chat popup header -->
62506250 <form id="form_import_chat" action="javascript:void(null);" method="post" enctype="multipart/form-data" style="display: none;">
62516251 <input type="file" id="chat_import_file" accept=".json, .jsonl" multiple name="avatar">
62526252 <input id="chat_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off" style="display: none;">
62536253 <input id="chat_import_avatar_url" name="avatar_url" class="text_pole" value="" autocomplete="off" style="display: none;">
62546254 <input id="chat_import_character_name" name="character_name" class="text_pole" value="" autocomplete="off" style="display: none;">
public/script.js+35 -32
@@ -409,6 +409,7 @@ let fav_ch_checked = false;
409409let scrollLock = false;
410410export let abortStatusCheck = new AbortController();
411411export let charDragDropHandler = null;
412+export let chatDragDropHandler = null;
412413
413414/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */
414415export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;
@@ -8050,9 +8051,10 @@ export async function saveChatConditional() {
80508051/**
80518052 * Saves the chat to the server.
80528053 * @param {FormData} formData Form data to send to the server.
80538054 * @param {EventTargetobject} eventTarget Event target[options={}] toOptions triggerfor the event on.import
8055+ * @param {boolean} [options.refresh] Whether to refresh the group chat list after import
80548056 */
80558057async function importCharacterChat(formData, eventTarget{ refresh = true } = {}) {
80568058 const fetchResult = await fetch('/api/chats/import', {
80578059 method: 'POST',
80588060 body: formData,
@@ -8062,14 +8064,10 @@ async function importCharacterChat(formData, eventTarget) {
80628064
80638065 if (fetchResult.ok) {
80648066 const data = await fetchResult.json();
80658067 if (data.res && refresh) {
80668068 await displayPastChats();
80678069 }
80688070 }
8069-
8070- if (eventTarget instanceof HTMLInputElement) {
8071- eventTarget.value = '';
8072- }
80738071}
80748072
80758073function updateViewMessageIds(startFromZero = false) {
@@ -10612,41 +10610,38 @@ jQuery(async function () {
1061210610 });
1061310611
1061410612 $('#chat_import_file').on('change', async function (e) {
1061510613 const targetElement = /** @type {HTMLInputElement} */ (e.target);
10616- if (!(targetElement instanceof HTMLInputElement)) {
10614+ const formElement = document.getElementById('form_import_chat');
10617- return;
10615+ if (!(targetElement instanceof HTMLInputElement) || !(formElement instanceof HTMLFormElement)) {
10618- }
10619- const file = targetElement.files[0];
10620-
10621- if (!file) {
1062210616 return;
1062310617 }
1062410618
10619+ for (const file of targetElement.files) {
1062510620 const ext = file.name.match(/\.(\w+)$/);
10626- if (
10621+ const format = ext?.[1]?.toLowerCase();
10627- !ext ||
10628- (ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'jsonl')
10629- ) {
10630- return;
10631- }
1063210622
10633- if (selected_group && file.name.endsWith('.json')) {
10623+ if (!['json', 'jsonl'].includes(format)) {
1063410624 toastr.warning('t`Only SillyTavern\'sJSON ownand formatJSONL isfiles are supported for group chat imports. Sorry!'`);
10635- return;
10625+ continue;
1063610626 }
1063710627
10638- const format = ext[1].toLowerCase();
10628+ if (selected_group && format === 'json') {
10639- $('#chat_import_file_type').val(format);
10629+ toastr.warning(t`Only SillyTavern's own format is supported for group chat imports. Sorry!`);
10630+ continue;
10631+ }
1064010632
10641- const formData = new FormData(/** @type {HTMLFormElement} */($('#form_import_chat').get(0)));
10633+ const formData = new FormData(formElement);
1064210634 formData.appendset('user_namefile_type', name1format);
10643- $('#select_chat_div').html('');
10635+ formData.set('avatar', file);
10636+ formData.set('user_name', name1);
1064410637
10645- if (selected_group) {
10638+ const importFn = selected_group ? importGroupChat : importCharacterChat;
1064610639 await importGroupChatimportFn(formData, e.originalEvent.target{ refresh: false });
10647- } else {
10648- await importCharacterChat(formData, e.originalEvent.target);
1064910640 }
10641+
10642+ await displayPastChats();
10643+
10644+ targetElement.value = '';
1065010645 });
1065110646
1065210647 $('#rm_button_group_chats').on('click', function () {
@@ -11092,6 +11087,14 @@ jQuery(async function () {
1109211087 await processDroppedFiles(files);
1109311088 }, { noAnimation: true });
1109411089
11090+ chatDragDropHandler = new DragAndDropHandler('#select_chat_popup', async (_, event) => {
11091+ const importFile = document.getElementById('chat_import_file');
11092+ if (importFile instanceof HTMLInputElement) {
11093+ importFile.files = event.originalEvent.dataTransfer.files;
11094+ $(importFile).trigger('change');
11095+ }
11096+ });
11097+
1109511098 $('#charListGridToggle').on('click', async () => {
1109611099 doCharListDisplaySwitch();
1109711100 });
public/scripts/group-chats.js+4 -5
@@ -2064,9 +2064,10 @@ export async function deleteGroupChat(groupId, chatId, { jumpToNewChat = true }
20642064/**
20652065 * Imports a group chat from a file and adds it to the group.
20662066 * @param {FormData} formData Form data to send to the server
20672067 * @param {EventTargetobject} eventTarget Element[options={}] thatOptions triggeredfor the import
2068+ * @param {boolean} [options.refresh] Whether to refresh the group chat list after import
20682069 */
20692070export async function importGroupChat(formData, eventTarget{ refresh = true } = {}) {
20702071 const fetchResult = await fetch('/api/chats/group/import', {
20712072 method: 'POST',
20722073 headers: getRequestHeaders({ omitContentType: true }),
@@ -2083,13 +2084,11 @@ export async function importGroupChat(formData, eventTarget) {
20832084 if (group) {
20842085 group.chats.push(chatId);
20852086 await editGroup(selected_group, true, true);
2087+ if (refresh) {
20862088 await displayPastChats();
20872089 }
20882090 }
20892091 }
2090-
2091- if (eventTarget instanceof HTMLInputElement) {
2092- eventTarget.value = '';
20932092 }
20942093}
20952094