Replace ajax with fetch (#4241) * Replace ajax with fetch Fix type errors in power-user / group-chats modules * Fix single sprite upload * Return empty object on failed upload

96bdc139371ad71e6609792f125ba60ea8e0fc66

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

Signed
10 files changed, +155 -141Showing whitespace changes
public/global.d.ts+6 -0
@@ -73,4 +73,10 @@ declare global {
73 * @param args - The arguments for the conversion function.73 * @param args - The arguments for the conversion function.
74 */74 */
75 function convertVideoToAnimatedWebp(args: ConvertVideoArgs): Promise<Uint8Array>;75 function convertVideoToAnimatedWebp(args: ConvertVideoArgs): Promise<Uint8Array>;
76
77 interface ColorPickerEvent extends JQuery.ChangeEvent<HTMLElement> {
78 detail: {
79 rgba: string;
80 };
81 }
76}82}
public/script.js+47 -37
@@ -784,11 +784,17 @@ export let active_group = '';
784784
785export const entitiesFilter = new FilterHelper(printCharactersDebounced);785export const entitiesFilter = new FilterHelper(printCharactersDebounced);
786786
787export function getRequestHeaders() {787export function getRequestHeaders({ omitContentType = false } = {}) {
788 return {788 const headers = {
789 'Content-Type': 'application/json',789 'Content-Type': 'application/json',
790 'X-CSRF-Token': token,790 'X-CSRF-Token': token,
791 };791 };
792
793 if (omitContentType) {
794 delete headers['Content-Type'];
795 }
796
797 return headers;
792}798}
793799
794export function getSlideToggleOptions() {800export function getSlideToggleOptions() {
@@ -7078,11 +7084,7 @@ export async function saveSettings(loopCounter = 0) {
7078 TempResponseLength.restore(null);7084 TempResponseLength.restore(null);
7079 }7085 }
70807086
7081 //console.log('Entering settings with name1 = '+name1);7087 const payload = {
7082 return jQuery.ajax({
7083 type: 'POST',
7084 url: '/api/settings/save',
7085 data: JSON.stringify({
7086 firstRun: firstRun,7088 firstRun: firstRun,
7087 accountStorage: accountStorage.getState(),7089 accountStorage: accountStorage.getState(),
7088 currentVersion: currentVersion,7090 currentVersion: currentVersion,
@@ -7107,21 +7109,26 @@ export async function saveSettings(loopCounter = 0) {
7107 background: background_settings,7109 background: background_settings,
7108 proxies: proxies,7110 proxies: proxies,
7109 selected_proxy: selected_proxy,7111 selected_proxy: selected_proxy,
7110 }, null, 4),7112 };
7111 beforeSend: function () { },7113
7112 cache: false,7114 try {
7113 dataType: 'json',7115 const result = await fetch('/api/settings/save', {
7114 contentType: 'application/json',7116 method: 'POST',
7115 //processData: false,7117 headers: getRequestHeaders(),
7116 success: async function (data) {7118 body: JSON.stringify(payload),
7117 eventSource.emit(event_types.SETTINGS_UPDATED);7119 cache: 'no-cache',
7118 },
7119 error: function (jqXHR, exception) {
7120 toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Settings could not be saved`);
7121 console.log(exception);
7122 console.log(jqXHR);
7123 },
7124 });7120 });
7121
7122 if (!result.ok) {
7123 throw new Error(`Failed to save settings: ${result.statusText}`);
7124 }
7125
7126 settings = payload;
7127 await eventSource.emit(event_types.SETTINGS_UPDATED);
7128 } catch (error) {
7129 console.error('Error saving settings:', error);
7130 toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Settings could not be saved`);
7131 }
7125}7132}
71267133
7127/**7134/**
@@ -8079,12 +8086,10 @@ export async function saveChatConditional() {
8079 * @param {EventTarget} eventTarget Event target to trigger the event on.8086 * @param {EventTarget} eventTarget Event target to trigger the event on.
8080 */8087 */
8081async function importCharacterChat(formData, eventTarget) {8088async function importCharacterChat(formData, eventTarget) {
8082 const headers = getRequestHeaders();
8083 delete headers['Content-Type'];
8084 const fetchResult = await fetch('/api/chats/import', {8089 const fetchResult = await fetch('/api/chats/import', {
8085 method: 'POST',8090 method: 'POST',
8086 body: formData,8091 body: formData,
8087 headers: headers,8092 headers: getRequestHeaders({ omitContentType: true }),
8088 cache: 'no-cache',8093 cache: 'no-cache',
8089 });8094 });
80908095
@@ -8388,8 +8393,7 @@ async function createOrEditCharacter(e) {
8388 formData.set('avatar', convertedFile);8393 formData.set('avatar', convertedFile);
8389 }8394 }
83908395
8391 const headers = getRequestHeaders();8396 const headers = getRequestHeaders({ omitContentType: true });
8392 delete headers['Content-Type'];
83938397
8394 if ($('#form_create').attr('actiontype') == 'createcharacter') {8398 if ($('#form_create').attr('actiontype') == 'createcharacter') {
8395 if (String($('#character_name_pole').val()).length === 0) {8399 if (String($('#character_name_pole').val()).length === 0) {
@@ -9013,19 +9017,22 @@ async function importCharacter(file, { preserveFileName = '', importTags = false
9013 formData.append('file_type', format);9017 formData.append('file_type', format);
9014 if (preserveFileName) formData.append('preserved_name', preserveFileName);9018 if (preserveFileName) formData.append('preserved_name', preserveFileName);
90159019
9016 const data = await jQuery.ajax({9020 try {
9017 type: 'POST',9021 const result = await fetch('/api/characters/import', {
9018 url: '/api/characters/import',9022 method: 'POST',
9019 data: formData,9023 body: formData,
9020 async: true,9024 headers: getRequestHeaders({ omitContentType: true }),
9021 cache: false,9025 cache: 'no-cache',
9022 contentType: false,
9023 processData: false,
9024 });9026 });
90259027
9028 if (!result.ok) {
9029 throw new Error(`Failed to import character: ${result.statusText}`);
9030 }
9031
9032 const data = await result.json();
9033
9026 if (data.error) {9034 if (data.error) {
9027 toastr.error(t`The file is likely invalid or corrupted.`, t`Could not import character`);9035 throw new Error(`Server returned an error: ${data.error}`);
9028 return;
9029 }9036 }
90309037
9031 if (data.file_name !== undefined) {9038 if (data.file_name !== undefined) {
@@ -9035,11 +9042,14 @@ async function importCharacter(file, { preserveFileName = '', importTags = false
9035 let avatarFileName = `${data.file_name}.png`;9042 let avatarFileName = `${data.file_name}.png`;
9036 if (importTags) {9043 if (importTags) {
9037 await importCharactersTags([avatarFileName]);9044 await importCharactersTags([avatarFileName]);
9038
9039 selectImportedChar(data.file_name);9045 selectImportedChar(data.file_name);
9040 }9046 }
9041 return avatarFileName;9047 return avatarFileName;
9042 }9048 }
9049 } catch (error) {
9050 console.error('Error importing character', error);
9051 toastr.error(t`The file is likely invalid or corrupted.`, t`Could not import character`);
9052 }
9043}9053}
90449054
9045async function importFromURL(items, files) {9055async function importFromURL(items, files) {
public/scripts/backgrounds.js+1 -4
@@ -636,12 +636,9 @@ async function uploadBackground(formData) {
636 return;636 return;
637 }637 }
638638
639 const headers = getRequestHeaders();
640 delete headers['Content-Type'];
641
642 const response = await fetch('/api/backgrounds/upload', {639 const response = await fetch('/api/backgrounds/upload', {
643 method: 'POST',640 method: 'POST',
644 headers: headers,641 headers: getRequestHeaders({ omitContentType: true }),
645 body: formData,642 body: formData,
646 cache: 'no-cache',643 cache: 'no-cache',
647 });644 });
public/scripts/extensions/expressions/index.js+25 -10
@@ -1758,27 +1758,38 @@ async function onExpressionFallbackChanged() {
1758 saveSettingsDebounced();1758 saveSettingsDebounced();
1759}1759}
17601760
1761/**
1762 * Handles the file upload process for a sprite image.
1763 * @param {string} url URL to upload the file to
1764 * @param {FormData} formData FormData object containing the file and other data to upload
1765 * @returns {Promise<any>} - The response data from the server
1766 */
1761async function handleFileUpload(url, formData) {1767async function handleFileUpload(url, formData) {
1762 try {1768 try {
1763 const data = await jQuery.ajax({1769 const result = await fetch(url, {
1764 type: 'POST',1770 method: 'POST',
1765 url: url,1771 headers: getRequestHeaders({ omitContentType: true }),
1766 data: formData,1772 body: formData,
1767 beforeSend: function () { },1773 cache: 'no-cache',
1768 cache: false,
1769 contentType: false,
1770 processData: false,
1771 });1774 });
17721775
1776 if (!result.ok) {
1777 throw new Error(`Upload failed with status ${result.status}`);
1778 }
1779
1780 const data = await result.json();
1781
1773 // Refresh sprites list1782 // Refresh sprites list
1774 const name = formData.get('name');1783 const name = formData.get('name').toString();
1775 delete spriteCache[name];1784 delete spriteCache[name];
1776 await fetchImagesNoCache();1785 await fetchImagesNoCache();
1777 await validateImages(name);1786 await validateImages(name);
17781787
1779 return data;1788 return data ?? {};
1780 } catch (error) {1789 } catch (error) {
1790 console.error('Error uploading image:', error);
1781 toastr.error('Failed to upload image');1791 toastr.error('Failed to upload image');
1792 return {};
1782 }1793 }
1783}1794}
17841795
@@ -1996,7 +2007,11 @@ async function onClickExpressionUploadPackButton() {
1996 const uploadToast = toastr.info('Please wait...', 'Upload is processing', { timeOut: 0, extendedTimeOut: 0 });2007 const uploadToast = toastr.info('Please wait...', 'Upload is processing', { timeOut: 0, extendedTimeOut: 0 });
1997 const { count } = await handleFileUpload('/api/sprites/upload-zip', formData);2008 const { count } = await handleFileUpload('/api/sprites/upload-zip', formData);
1998 toastr.clear(uploadToast);2009 toastr.clear(uploadToast);
2010
2011 // Only show success message if at least one image was uploaded
2012 if (count) {
1999 toastr.success(`Uploaded ${count} image(s) for ${name}`);2013 toastr.success(`Uploaded ${count} image(s) for ${name}`);
2014 }
20002015
2001 // Reset the input2016 // Reset the input
2002 e.target.form.reset();2017 e.target.form.reset();
public/scripts/group-chats.js+4 -6
@@ -311,7 +311,7 @@ export function getGroupNames() {
311 * @returns {number|Object} 0-based character ID or key-value object if full is true311 * @returns {number|Object} 0-based character ID or key-value object if full is true
312 */312 */
313export function findGroupMemberId(arg, full = false) {313export function findGroupMemberId(arg, full = false) {
314 arg = arg?.trim();314 arg = arg?.toString()?.trim();
315315
316 if (!arg) {316 if (!arg) {
317 console.warn('WARN: No argument provided for findGroupMemberId');317 console.warn('WARN: No argument provided for findGroupMemberId');
@@ -1358,7 +1358,7 @@ function isGroupMember(group, avatarId) {
1358 }1358 }
1359}1359}
13601360
1361function getGroupCharacters({ doFilter, onlyMembers } = {}) {1361function getGroupCharacters({ doFilter = false, onlyMembers = false } = {}) {
1362 function sortMembersFn(a, b) {1362 function sortMembersFn(a, b) {
1363 const membersArray = thisGroup?.members ?? newGroupMembers;1363 const membersArray = thisGroup?.members ?? newGroupMembers;
1364 const aIndex = membersArray.indexOf(a.item.avatar);1364 const aIndex = membersArray.indexOf(a.item.avatar);
@@ -1757,7 +1757,7 @@ async function onGroupActionClick(event) {
17571757
1758function updateFavButtonState(state) {1758function updateFavButtonState(state) {
1759 fav_grp_checked = state;1759 fav_grp_checked = state;
1760 $('#rm_group_fav').val(fav_grp_checked);1760 $('#rm_group_fav').val(String(fav_grp_checked));
1761 $('#group_favorite_button').toggleClass('fav_on', fav_grp_checked);1761 $('#group_favorite_button').toggleClass('fav_on', fav_grp_checked);
1762 $('#group_favorite_button').toggleClass('fav_off', !fav_grp_checked);1762 $('#group_favorite_button').toggleClass('fav_off', !fav_grp_checked);
1763}1763}
@@ -2047,11 +2047,9 @@ export async function deleteGroupChat(groupId, chatId) {
2047 * @param {EventTarget} eventTarget Element that triggered the import2047 * @param {EventTarget} eventTarget Element that triggered the import
2048 */2048 */
2049export async function importGroupChat(formData, eventTarget) {2049export async function importGroupChat(formData, eventTarget) {
2050 const headers = getRequestHeaders();
2051 delete headers['Content-Type'];
2052 const fetchResult = await fetch('/api/chats/group/import', {2050 const fetchResult = await fetch('/api/chats/group/import', {
2053 method: 'POST',2051 method: 'POST',
2054 headers: headers,2052 headers: getRequestHeaders({ omitContentType: true }),
2055 body: formData,2053 body: formData,
2056 cache: 'no-cache',2054 cache: 'no-cache',
2057 });2055 });
public/scripts/personas.js+2 -8
@@ -317,12 +317,9 @@ async function uploadUserAvatar(url, name) {
317 formData.append('overwrite_name', name);317 formData.append('overwrite_name', name);
318 }318 }
319319
320 const headers = getRequestHeaders();
321 delete headers['Content-Type'];
322
323 await fetch('/api/avatars/upload', {320 await fetch('/api/avatars/upload', {
324 method: 'POST',321 method: 'POST',
325 headers: headers,322 headers: getRequestHeaders({ omitContentType: true }),
326 cache: 'no-cache',323 cache: 'no-cache',
327 body: formData,324 body: formData,
328 });325 });
@@ -368,12 +365,9 @@ async function changeUserAvatar(e) {
368 formData.set('avatar', convertedFile);365 formData.set('avatar', convertedFile);
369 }366 }
370367
371 const headers = getRequestHeaders();
372 delete headers['Content-Type'];
373
374 const response = await fetch(url, {368 const response = await fetch(url, {
375 method: 'POST',369 method: 'POST',
376 headers: headers,370 headers: getRequestHeaders({ omitContentType:true }),
377 cache: 'no-cache',371 cache: 'no-cache',
378 body: formData,372 body: formData,
379 });373 });
public/scripts/power-user.js+44 -57
@@ -61,18 +61,6 @@ import { accountStorage } from './util/AccountStorage.js';
61import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';61import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
62import { bindModelTemplates } from './chat-templates.js';62import { bindModelTemplates } from './chat-templates.js';
6363
64export {
65 loadPowerUserSettings,
66 loadMovingUIState,
67 collapseNewlines,
68 playMessageSound,
69 fixMarkdown,
70 power_user,
71 send_on_enter_options,
72 getContextSettings,
73 applyPowerUserSettings,
74};
75
76export const toastPositionClasses = [64export const toastPositionClasses = [
77 'toast-top-left',65 'toast-top-left',
78 'toast-top-center',66 'toast-top-center',
@@ -108,7 +96,7 @@ export const chat_styles = {
108 DOCUMENT: 2,96 DOCUMENT: 2,
109};97};
11098
111const send_on_enter_options = {99export const send_on_enter_options = {
112 DISABLED: -1,100 DISABLED: -1,
113 AUTO: 0,101 AUTO: 0,
114 ENABLED: 1,102 ENABLED: 1,
@@ -126,7 +114,7 @@ export const persona_description_positions = {
126 NONE: 9,114 NONE: 9,
127};115};
128116
129let power_user = {117export const power_user = {
130 charListGrid: false,118 charListGrid: false,
131 tokenizer: tokenizers.BEST_MATCH,119 tokenizer: tokenizers.BEST_MATCH,
132 token_padding: 64,120 token_padding: 64,
@@ -370,7 +358,7 @@ const debug_functions = [];
370358
371const setHotswapsDebounced = debounce(favsToHotswap);359const setHotswapsDebounced = debounce(favsToHotswap);
372360
373function playMessageSound() {361export function playMessageSound() {
374 if (!power_user.play_message_sound) {362 if (!power_user.play_message_sound) {
375 return;363 return;
376 }364 }
@@ -395,7 +383,7 @@ function playMessageSound() {
395 * @example383 * @example
396 * collapseNewlines("\n\n\n"); // "\n"384 * collapseNewlines("\n\n\n"); // "\n"
397 */385 */
398function collapseNewlines(x) {386export function collapseNewlines(x) {
399 return x.replaceAll(/\n+/g, '\n');387 return x.replaceAll(/\n+/g, '\n');
400}388}
401389
@@ -416,7 +404,7 @@ function collapseNewlines(x) {
416 * // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line404 * // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
417 * "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n"405 * "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n"
418 */406 */
419function fixMarkdown(text, forDisplay) {407export function fixMarkdown(text, forDisplay) {
420 // Find pairs of formatting characters and capture the text in between them408 // Find pairs of formatting characters and capture the text in between them
421 const format = /([*_]{1,2})([\s\S]*?)\1/gm;409 const format = /([*_]{1,2})([\s\S]*?)\1/gm;
422 let matches = [];410 let matches = [];
@@ -862,13 +850,13 @@ async function CreateZenSliders(elmnt) {
862 })850 })
863 //trigger slider changes when user clicks away851 //trigger slider changes when user clicks away
864 .on('mouseup blur', function () {852 .on('mouseup blur', function () {
865 let manualInput = parseFloat(handle.text()).toFixed(decimals);853 let manualInput = parseFloat(parseFloat(handle.text()).toFixed(decimals));
866 if (isManualInput) {854 if (isManualInput) {
867 //disallow manual inputs outside acceptable range855 //disallow manual inputs outside acceptable range
868 if (manualInput >= sliderMin && manualInput <= sliderMax) {856 if (manualInput >= sliderMin && manualInput <= sliderMax) {
869 //if value is ok, assign to slider and update handle text and position857 //if value is ok, assign to slider and update handle text and position
870 newSlider.val(manualInput);858 newSlider.val(manualInput);
871 handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');859 handleSlideEvent.call(newSlider, null, { value: manualInput }, 'manual');
872 valueBeforeManualInput = manualInput;860 valueBeforeManualInput = manualInput;
873 } else {861 } else {
874 //if value not ok, warn and reset to last known valid value862 //if value not ok, warn and reset to last known valid value
@@ -890,13 +878,13 @@ async function CreateZenSliders(elmnt) {
890878
891 function handleSlideEvent(event, ui, type) {879 function handleSlideEvent(event, ui, type) {
892 var handle = $(this).find('.ui-slider-handle');880 var handle = $(this).find('.ui-slider-handle');
893 var numVal = Number(ui.value).toFixed(decimals);881 var numVal = parseFloat(Number(ui.value).toFixed(decimals));
894 offVal = Number(offVal).toFixed(decimals);882 offVal = parseFloat(Number(offVal).toFixed(decimals));
895 allVal = Number(allVal).toFixed(decimals);883 allVal = parseFloat(Number(allVal).toFixed(decimals));
896 console.log(numVal, sliderMin, sliderMax, numVal > sliderMax, numVal < sliderMin);884 console.log(numVal, sliderMin, sliderMax, numVal > sliderMax, numVal < sliderMin);
897 if (numVal > sliderMax) { numVal = sliderMax; }885 if (numVal > sliderMax) { numVal = sliderMax; }
898 if (numVal < sliderMin) { numVal = sliderMin; }886 if (numVal < sliderMin) { numVal = sliderMin; }
899 var stepNumber = ((ui.value - sliderMin) / stepScale).toFixed(0);887 var stepNumber = parseFloat(((ui.value - sliderMin) / stepScale).toFixed(0));
900 var handleText = (ui.value);888 var handleText = (ui.value);
901 var leftMargin = (stepNumber / numSteps) * 50 * -1;889 var leftMargin = (stepNumber / numSteps) * 50 * -1;
902 var perStepPercent = 1 / numSteps; //how far in % each step should be on the slider890 var perStepPercent = 1 / numSteps; //how far in % each step should be on the slider
@@ -1035,10 +1023,9 @@ function applyAvatarStyle() {
1035}1023}
10361024
1037function applyChatDisplay() {1025function applyChatDisplay() {
10381026 if ([null, undefined].includes(power_user.chat_display)) {
1039 if (!power_user.chat_display === (null || undefined)) {
1040 console.debug('applyChatDisplay: saw no chat display type defined');1027 console.debug('applyChatDisplay: saw no chat display type defined');
1041 return;1028 power_user.chat_display = chat_styles.DEFAULT;
1042 }1029 }
1043 console.debug(`poweruser.chat_display ${power_user.chat_display}`);1030 console.debug(`poweruser.chat_display ${power_user.chat_display}`);
1044 $('#chat_display').val(power_user.chat_display).prop('selected', true);1031 $('#chat_display').val(power_user.chat_display).prop('selected', true);
@@ -1417,7 +1404,7 @@ async function showDebugMenu() {
1417 callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true });1404 callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true });
1418}1405}
14191406
1420function applyPowerUserSettings() {1407export function applyPowerUserSettings() {
1421 switchUiMode();1408 switchUiMode();
1422 applyFontScale('forced');1409 applyFontScale('forced');
1423 applyThemeColor();1410 applyThemeColor();
@@ -1496,7 +1483,7 @@ function getExampleMessagesBehavior() {
1496}1483}
14971484
1498//MARK: loadPowerUser1485//MARK: loadPowerUser
1499async function loadPowerUserSettings(settings, data) {1486export async function loadPowerUserSettings(settings, data) {
1500 const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript));1487 const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript));
1501 // Load from settings.json1488 // Load from settings.json
1502 if (settings.power_user !== undefined) {1489 if (settings.power_user !== undefined) {
@@ -1552,15 +1539,15 @@ async function loadPowerUserSettings(settings, data) {
1552 context_presets = data.context;1539 context_presets = data.context;
1553 }1540 }
15541541
1555 if (power_user.chat_display === '') {1542 if (typeof power_user.chat_display !== 'number') {
1556 power_user.chat_display = chat_styles.DEFAULT;1543 power_user.chat_display = chat_styles.DEFAULT;
1557 }1544 }
15581545
1559 if (power_user.waifuMode === '') {1546 if (typeof power_user.waifuMode !== 'boolean') {
1560 power_user.waifuMode = false;1547 power_user.waifuMode = false;
1561 }1548 }
15621549
1563 if (power_user.chat_width === '') {1550 if (typeof power_user.chat_width !== 'number') {
1564 power_user.chat_width = 50;1551 power_user.chat_width = 50;
1565 }1552 }
15661553
@@ -1618,8 +1605,8 @@ async function loadPowerUserSettings(settings, data) {
1618 $('#auto_scroll_chat_to_bottom').prop('checked', power_user.auto_scroll_chat_to_bottom);1605 $('#auto_scroll_chat_to_bottom').prop('checked', power_user.auto_scroll_chat_to_bottom);
1619 $('#bogus_folders').prop('checked', power_user.bogus_folders);1606 $('#bogus_folders').prop('checked', power_user.bogus_folders);
1620 $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);1607 $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);
1621 $(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);1608 $(`#tokenizer option[value="${power_user.tokenizer}"]`).prop('selected', true);
1622 $(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr('selected', true);1609 $(`#send_on_enter option[value=${power_user.send_on_enter}]`).prop('selected', true);
1623 $('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);1610 $('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);
1624 $('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode);1611 $('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode);
1625 $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);1612 $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
@@ -1655,8 +1642,8 @@ async function loadPowerUserSettings(settings, data) {
1655 $('#enableZenSliders').prop('checked', power_user.enableZenSliders).trigger('input');1642 $('#enableZenSliders').prop('checked', power_user.enableZenSliders).trigger('input');
1656 $('#enableLabMode').prop('checked', power_user.enableLabMode).trigger('input', { fromInit: true });1643 $('#enableLabMode').prop('checked', power_user.enableLabMode).trigger('input', { fromInit: true });
1657 $(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop('checked', true);1644 $(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop('checked', true);
1658 $(`#chat_display option[value=${power_user.chat_display}]`).attr('selected', true).trigger('change');1645 $(`#chat_display option[value=${power_user.chat_display}]`).prop('selected', true).trigger('change');
1659 $(`#toastr_position option[value=${power_user.toastr_position}]`).attr('selected', true).trigger('change');1646 $(`#toastr_position option[value=${power_user.toastr_position}]`).prop('selected', true).trigger('change');
1660 $('#chat_width_slider').val(power_user.chat_width);1647 $('#chat_width_slider').val(power_user.chat_width);
1661 $('#token_padding').val(power_user.token_padding);1648 $('#token_padding').val(power_user.token_padding);
1662 $('#aux_field').val(power_user.aux_field);1649 $('#aux_field').val(power_user.aux_field);
@@ -1763,7 +1750,7 @@ function loadCharListState() {
1763 document.body.classList.toggle('charListGrid', power_user.charListGrid);1750 document.body.classList.toggle('charListGrid', power_user.charListGrid);
1764}1751}
17651752
1766function loadMovingUIState() {1753export function loadMovingUIState() {
1767 if (!isMobile()1754 if (!isMobile()
1768 && power_user.movingUIState1755 && power_user.movingUIState
1769 && power_user.movingUI === true) {1756 && power_user.movingUI === true) {
@@ -1853,7 +1840,7 @@ function switchMaxContextSize() {
1853}1840}
18541841
1855// Fetch a compiled object of all preset settings1842// Fetch a compiled object of all preset settings
1856function getContextSettings() {1843export function getContextSettings() {
1857 let compiledSettings = {};1844 let compiledSettings = {};
18581845
1859 contextControls.forEach((control) => {1846 contextControls.forEach((control) => {
@@ -2397,7 +2384,7 @@ async function saveTheme(name = undefined, theme = undefined) {
2397 }2384 }
2398 else {2385 else {
2399 themes[themeIndex] = theme;2386 themes[themeIndex] = theme;
2400 $(`#themes option[value="${name}"]`).attr('selected', true);2387 $(`#themes option[value="${name}"]`).prop('selected', true);
2401 }2388 }
24022389
2403 power_user.theme = name;2390 power_user.theme = name;
@@ -2504,7 +2491,7 @@ async function saveMovingUI() {
2504 }2491 }
2505 else {2492 else {
2506 movingUIPresets[movingUIPresetIndex] = movingUIPreset;2493 movingUIPresets[movingUIPresetIndex] = movingUIPreset;
2507 $(`#movingUIPresets option[value="${name}"]`).attr('selected', true);2494 $(`#movingUIPresets option[value="${name}"]`).prop('selected', true);
2508 }2495 }
25092496
2510 power_user.movingUIPreset = name;2497 power_user.movingUIPreset = name;
@@ -3162,7 +3149,7 @@ jQuery(() => {
3162 });3149 });
31633150
3164 const reportZoomLevelDebounced = debounce(() => {3151 const reportZoomLevelDebounced = debounce(() => {
3165 const zoomLevel = Number(window.devicePixelRatio).toFixed(2) || 1;3152 const zoomLevel = parseFloat(Number(window.devicePixelRatio).toFixed(2)) || 1;
3166 const winWidth = window.innerWidth;3153 const winWidth = window.innerWidth;
3167 const winHeight = window.innerHeight;3154 const winHeight = window.innerHeight;
3168 const originalWidth = winWidth * zoomLevel;3155 const originalWidth = winWidth * zoomLevel;
@@ -3187,8 +3174,8 @@ jQuery(() => {
31873174
3188 //attempt to scale movingUI elements naturally across window resizing/zooms3175 //attempt to scale movingUI elements naturally across window resizing/zooms
3189 //this will still break if the zoom level causes mobile styles to come into play.3176 //this will still break if the zoom level causes mobile styles to come into play.
3190 const scaleY = Number(window.innerHeight / coreTruthWinHeight).toFixed(4);3177 const scaleY = parseFloat(Number(window.innerHeight / coreTruthWinHeight).toFixed(4));
3191 const scaleX = Number(window.innerWidth / coreTruthWinWidth).toFixed(4);3178 const scaleX = parseFloat(Number(window.innerWidth / coreTruthWinWidth).toFixed(4));
31923179
3193 if (Object.keys(power_user.movingUIState).length > 0) {3180 if (Object.keys(power_user.movingUIState).length > 0) {
3194 for (var elmntName of Object.keys(power_user.movingUIState)) {3181 for (var elmntName of Object.keys(power_user.movingUIState)) {
@@ -3362,7 +3349,7 @@ jQuery(() => {
3362 });3349 });
33633350
3364 $('#waifuMode').on('change', () => {3351 $('#waifuMode').on('change', () => {
3365 power_user.waifuMode = $('#waifuMode').prop('checked');3352 power_user.waifuMode = !!$('#waifuMode').prop('checked');
3366 switchWaifuMode();3353 switchWaifuMode();
3367 saveSettingsDebounced();3354 saveSettingsDebounced();
3368 });3355 });
@@ -3410,7 +3397,7 @@ jQuery(() => {
34103397
3411 $('#chat_width_slider').on('input', function (e, data) {3398 $('#chat_width_slider').on('input', function (e, data) {
3412 const applyMode = data?.forced ? 'forced' : 'normal';3399 const applyMode = data?.forced ? 'forced' : 'normal';
3413 power_user.chat_width = Number(e.target.value);3400 power_user.chat_width = Number($(this).val());
3414 applyChatWidth(applyMode);3401 applyChatWidth(applyMode);
3415 saveSettingsDebounced();3402 saveSettingsDebounced();
3416 setHotswapsDebounced();3403 setHotswapsDebounced();
@@ -3440,81 +3427,81 @@ jQuery(() => {
34403427
3441 $('input[name="font_scale"]').on('input', async function (e, data) {3428 $('input[name="font_scale"]').on('input', async function (e, data) {
3442 const applyMode = data?.forced ? 'forced' : 'normal';3429 const applyMode = data?.forced ? 'forced' : 'normal';
3443 power_user.font_scale = Number(e.target.value);3430 power_user.font_scale = Number($(this).val());
3444 $('#font_scale_counter').val(power_user.font_scale);3431 $('#font_scale_counter').val(power_user.font_scale);
3445 applyFontScale(applyMode);3432 applyFontScale(applyMode);
3446 saveSettingsDebounced();3433 saveSettingsDebounced();
3447 });3434 });
34483435
3449 $('input[name="blur_strength"]').on('input', async function (e) {3436 $('input[name="blur_strength"]').on('input', async function (e) {
3450 power_user.blur_strength = Number(e.target.value);3437 power_user.blur_strength = Number($(this).val());
3451 $('#blur_strength_counter').val(power_user.blur_strength);3438 $('#blur_strength_counter').val(power_user.blur_strength);
3452 applyBlurStrength();3439 applyBlurStrength();
3453 saveSettingsDebounced();3440 saveSettingsDebounced();
3454 });3441 });
34553442
3456 $('input[name="shadow_width"]').on('input', async function (e) {3443 $('input[name="shadow_width"]').on('input', async function (e) {
3457 power_user.shadow_width = Number(e.target.value);3444 power_user.shadow_width = Number($(this).val());
3458 $('#shadow_width_counter').val(power_user.shadow_width);3445 $('#shadow_width_counter').val(power_user.shadow_width);
3459 applyShadowWidth();3446 applyShadowWidth();
3460 saveSettingsDebounced();3447 saveSettingsDebounced();
3461 });3448 });
34623449
3463 $('#main-text-color-picker').on('change', (evt) => {3450 $('#main-text-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3464 power_user.main_text_color = evt.detail.rgba;3451 power_user.main_text_color = evt.detail.rgba;
3465 applyThemeColor('main');3452 applyThemeColor('main');
3466 saveSettingsDebounced();3453 saveSettingsDebounced();
3467 });3454 });
34683455
3469 $('#italics-color-picker').on('change', (evt) => {3456 $('#italics-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3470 power_user.italics_text_color = evt.detail.rgba;3457 power_user.italics_text_color = evt.detail.rgba;
3471 applyThemeColor('italics');3458 applyThemeColor('italics');
3472 saveSettingsDebounced();3459 saveSettingsDebounced();
3473 });3460 });
34743461
3475 $('#underline-color-picker').on('change', (evt) => {3462 $('#underline-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3476 power_user.underline_text_color = evt.detail.rgba;3463 power_user.underline_text_color = evt.detail.rgba;
3477 applyThemeColor('underline');3464 applyThemeColor('underline');
3478 saveSettingsDebounced();3465 saveSettingsDebounced();
3479 });3466 });
34803467
3481 $('#quote-color-picker').on('change', (evt) => {3468 $('#quote-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3482 power_user.quote_text_color = evt.detail.rgba;3469 power_user.quote_text_color = evt.detail.rgba;
3483 applyThemeColor('quote');3470 applyThemeColor('quote');
3484 saveSettingsDebounced();3471 saveSettingsDebounced();
3485 });3472 });
34863473
3487 $('#blur-tint-color-picker').on('change', (evt) => {3474 $('#blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3488 power_user.blur_tint_color = evt.detail.rgba;3475 power_user.blur_tint_color = evt.detail.rgba;
3489 applyThemeColor('blurTint');3476 applyThemeColor('blurTint');
3490 saveSettingsDebounced();3477 saveSettingsDebounced();
3491 });3478 });
34923479
3493 $('#chat-tint-color-picker').on('change', (evt) => {3480 $('#chat-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3494 power_user.chat_tint_color = evt.detail.rgba;3481 power_user.chat_tint_color = evt.detail.rgba;
3495 applyThemeColor('chatTint');3482 applyThemeColor('chatTint');
3496 saveSettingsDebounced();3483 saveSettingsDebounced();
3497 });3484 });
34983485
3499 $('#user-mes-blur-tint-color-picker').on('change', (evt) => {3486 $('#user-mes-blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3500 power_user.user_mes_blur_tint_color = evt.detail.rgba;3487 power_user.user_mes_blur_tint_color = evt.detail.rgba;
3501 applyThemeColor('userMesBlurTint');3488 applyThemeColor('userMesBlurTint');
3502 saveSettingsDebounced();3489 saveSettingsDebounced();
3503 });3490 });
35043491
3505 $('#bot-mes-blur-tint-color-picker').on('change', (evt) => {3492 $('#bot-mes-blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3506 power_user.bot_mes_blur_tint_color = evt.detail.rgba;3493 power_user.bot_mes_blur_tint_color = evt.detail.rgba;
3507 applyThemeColor('botMesBlurTint');3494 applyThemeColor('botMesBlurTint');
3508 saveSettingsDebounced();3495 saveSettingsDebounced();
3509 });3496 });
35103497
3511 $('#shadow-color-picker').on('change', (evt) => {3498 $('#shadow-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3512 power_user.shadow_color = evt.detail.rgba;3499 power_user.shadow_color = evt.detail.rgba;
3513 applyThemeColor('shadow');3500 applyThemeColor('shadow');
3514 saveSettingsDebounced();3501 saveSettingsDebounced();
3515 });3502 });
35163503
3517 $('#border-color-picker').on('change', (evt) => {3504 $('#border-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
3518 power_user.border_color = evt.detail.rgba;3505 power_user.border_color = evt.detail.rgba;
3519 applyThemeColor('border');3506 applyThemeColor('border');
3520 saveSettingsDebounced();3507 saveSettingsDebounced();
public/scripts/welcome-screen.js+1 -4
@@ -577,12 +577,9 @@ async function createPermanentAssistant() {
577 console.warn('Error fetching system avatar. Fallback image will be used.', error);577 console.warn('Error fetching system avatar. Fallback image will be used.', error);
578 }578 }
579579
580 const headers = getRequestHeaders();
581 delete headers['Content-Type'];
582
583 const fetchResult = await fetch('/api/characters/create', {580 const fetchResult = await fetch('/api/characters/create', {
584 method: 'POST',581 method: 'POST',
585 headers: headers,582 headers: getRequestHeaders({ omitContentType: true }),
586 body: formData,583 body: formData,
587 cache: 'no-cache',584 cache: 'no-cache',
588 });585 });
public/scripts/world-info.js+23 -13
@@ -5199,6 +5199,10 @@ export function onWorldInfoChange(args, text) {
5199 return '';5199 return '';
5200}5200}
52015201
5202/**
5203 * Imports world info from a file.
5204 * @param {File} file File to import
5205 */
5202export async function importWorldInfo(file) {5206export async function importWorldInfo(file) {
5203 if (!file) {5207 if (!file) {
5204 return;5208 return;
@@ -5252,15 +5256,20 @@ export async function importWorldInfo(file) {
5252 return false;5256 return false;
5253 }5257 }
52545258
5255 jQuery.ajax({5259 try {
5256 type: 'POST',5260 const result = await fetch('/api/worldinfo/import', {
5257 url: '/api/worldinfo/import',5261 method: 'POST',
5258 data: formData,5262 headers: getRequestHeaders({ omitContentType: true }),
5259 beforeSend: () => { },5263 body: formData,
5260 cache: false,5264 cache: 'no-cache',
5261 contentType: false,5265 });
5262 processData: false,5266
5263 success: async function (data) {5267 if (!result.ok) {
5268 throw new Error(`Failed to import world info: ${result.statusText}`);
5269 }
5270
5271 const data = await result.json();
5272
5264 if (data.name) {5273 if (data.name) {
5265 await updateWorldInfoList();5274 await updateWorldInfoList();
52665275
@@ -5269,11 +5278,12 @@ export async function importWorldInfo(file) {
5269 $('#world_editor_select').val(newIndex).trigger('change');5278 $('#world_editor_select').val(newIndex).trigger('change');
5270 }5279 }
52715280
5272 toastr.success(`World Info "${data.name}" imported successfully!`);5281 toastr.success(t`World Info "${data.name}" imported successfully!`);
5282 }
5283 } catch (error) {
5284 console.error('Error importing world info:', error);
5285 toastr.error(t`Failed to import World Info`);
5273 }5286 }
5274 },
5275 error: (_jqXHR, _exception) => { },
5276 });
5277}5287}
52785288
5279/**5289/**
src/endpoints/sprites.js+2 -2
@@ -216,7 +216,7 @@ router.post('/upload-zip', async (request, response) => {
216216
217 // Remove uploaded ZIP file217 // Remove uploaded ZIP file
218 fs.unlinkSync(spritePackPath);218 fs.unlinkSync(spritePackPath);
219 return response.send({ count: sprites.length });219 return response.send({ ok: true, count: sprites.length });
220 } catch (error) {220 } catch (error) {
221 console.error(error);221 console.error(error);
222 return response.sendStatus(500);222 return response.sendStatus(500);
@@ -262,7 +262,7 @@ router.post('/upload', async (request, response) => {
262 fs.cpSync(spritePath, pathToFile);262 fs.cpSync(spritePath, pathToFile);
263 // Remove uploaded file263 // Remove uploaded file
264 fs.unlinkSync(spritePath);264 fs.unlinkSync(spritePath);
265 return response.sendStatus(200);265 return response.send({ ok: true });
266 } catch (error) {266 } catch (error) {
267 console.error(error);267 console.error(error);
268 return response.sendStatus(500);268 return response.sendStatus(500);