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 {
7373 * @param args - The arguments for the conversion function.
7474 */
7575 function convertVideoToAnimatedWebp(args: ConvertVideoArgs): Promise<Uint8Array>;
76+
77+ interface ColorPickerEvent extends JQuery.ChangeEvent<HTMLElement> {
78+ detail: {
79+ rgba: string;
80+ };
81+ }
7682}
public/script.js+47 -37
@@ -784,11 +784,17 @@ export let active_group = '';
784784
785785export const entitiesFilter = new FilterHelper(printCharactersDebounced);
786786
787787export function getRequestHeaders({ omitContentType = false } = {}) {
788788 returnconst headers = {
789789 'Content-Type': 'application/json',
790790 'X-CSRF-Token': token,
791791 };
792+
793+ if (omitContentType) {
794+ delete headers['Content-Type'];
795+ }
796+
797+ return headers;
792798}
793799
794800export function getSlideToggleOptions() {
@@ -7078,11 +7084,7 @@ export async function saveSettings(loopCounter = 0) {
70787084 TempResponseLength.restore(null);
70797085 }
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({
70867088 firstRun: firstRun,
70877089 accountStorage: accountStorage.getState(),
70887090 currentVersion: currentVersion,
@@ -7107,21 +7109,26 @@ export async function saveSettings(loopCounter = 0) {
71077109 background: background_settings,
71087110 proxies: proxies,
71097111 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', {
71147116 contentType method: 'application/jsonPOST',
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- },
71247120 });
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+ }
71257132}
71267133
71277134/**
@@ -8079,12 +8086,10 @@ export async function saveChatConditional() {
80798086 * @param {EventTarget} eventTarget Event target to trigger the event on.
80808087 */
80818088async function importCharacterChat(formData, eventTarget) {
8082- const headers = getRequestHeaders();
8083- delete headers['Content-Type'];
80848089 const fetchResult = await fetch('/api/chats/import', {
80858090 method: 'POST',
80868091 body: formData,
8087- headers: headers,
8092+ headers: getRequestHeaders({ omitContentType: true }),
80888093 cache: 'no-cache',
80898094 });
80908095
@@ -8388,8 +8393,7 @@ async function createOrEditCharacter(e) {
83888393 formData.set('avatar', convertedFile);
83898394 }
83908395
83918396 const headers = getRequestHeaders({ omitContentType: true });
8392- delete headers['Content-Type'];
83938397
83948398 if ($('#form_create').attr('actiontype') == 'createcharacter') {
83958399 if (String($('#character_name_pole').val()).length === 0) {
@@ -9013,19 +9017,22 @@ async function importCharacter(file, { preserveFileName = '', importTags = false
90139017 formData.append('file_type', format);
90149018 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',
90199023 data body: formData,
9020- async: true,
9024+ headers: getRequestHeaders({ omitContentType: true }),
90219025 cache: false'no-cache',
9022- contentType: false,
9023- processData: false,
90249026 });
90259027
9028+ if (!result.ok) {
9029+ throw new Error(`Failed to import character: ${result.statusText}`);
9030+ }
9031+
9032+ const data = await result.json();
9033+
90269034 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;
90299036 }
90309037
90319038 if (data.file_name !== undefined) {
@@ -9035,11 +9042,14 @@ async function importCharacter(file, { preserveFileName = '', importTags = false
90359042 let avatarFileName = `${data.file_name}.png`;
90369043 if (importTags) {
90379044 await importCharactersTags([avatarFileName]);
9038-
90399045 selectImportedChar(data.file_name);
90409046 }
90419047 return avatarFileName;
90429048 }
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+ }
90439053}
90449054
90459055async function importFromURL(items, files) {
public/scripts/backgrounds.js+1 -4
@@ -636,12 +636,9 @@ async function uploadBackground(formData) {
636636 return;
637637 }
638638
639- const headers = getRequestHeaders();
640- delete headers['Content-Type'];
641-
642639 const response = await fetch('/api/backgrounds/upload', {
643640 method: 'POST',
644- headers: headers,
641+ headers: getRequestHeaders({ omitContentType: true }),
645642 body: formData,
646643 cache: 'no-cache',
647644 });
public/scripts/extensions/expressions/index.js+25 -10
@@ -1758,27 +1758,38 @@ async function onExpressionFallbackChanged() {
17581758 saveSettingsDebounced();
17591759}
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+ */
17611767async function handleFileUpload(url, formData) {
17621768 try {
17631769 const dataresult = await jQuery.ajaxfetch(url, {
17641770 typemethod: 'POST',
1765- url: url,
1771+ headers: getRequestHeaders({ omitContentType: true }),
17661772 databody: formData,
1767- beforeSend: function () { },
1773+ cache: 'no-cache',
1768- cache: false,
1769- contentType: false,
1770- processData: false,
17711774 });
17721775
1776+ if (!result.ok) {
1777+ throw new Error(`Upload failed with status ${result.status}`);
1778+ }
1779+
1780+ const data = await result.json();
1781+
17731782 // Refresh sprites list
17741783 const name = formData.get('name').toString();
17751784 delete spriteCache[name];
17761785 await fetchImagesNoCache();
17771786 await validateImages(name);
17781787
17791788 return data ?? {};
17801789 } catch (error) {
1790+ console.error('Error uploading image:', error);
17811791 toastr.error('Failed to upload image');
1792+ return {};
17821793 }
17831794}
17841795
@@ -1996,7 +2007,11 @@ async function onClickExpressionUploadPackButton() {
19962007 const uploadToast = toastr.info('Please wait...', 'Upload is processing', { timeOut: 0, extendedTimeOut: 0 });
19972008 const { count } = await handleFileUpload('/api/sprites/upload-zip', formData);
19982009 toastr.clear(uploadToast);
2010+
2011+ // Only show success message if at least one image was uploaded
2012+ if (count) {
19992013 toastr.success(`Uploaded ${count} image(s) for ${name}`);
2014+ }
20002015
20012016 // Reset the input
20022017 e.target.form.reset();
public/scripts/group-chats.js+4 -6
@@ -311,7 +311,7 @@ export function getGroupNames() {
311311 * @returns {number|Object} 0-based character ID or key-value object if full is true
312312 */
313313export function findGroupMemberId(arg, full = false) {
314314 arg = arg?.toString()?.trim();
315315
316316 if (!arg) {
317317 console.warn('WARN: No argument provided for findGroupMemberId');
@@ -1358,7 +1358,7 @@ function isGroupMember(group, avatarId) {
13581358 }
13591359}
13601360
13611361function getGroupCharacters({ doFilter = false, onlyMembers = false } = {}) {
13621362 function sortMembersFn(a, b) {
13631363 const membersArray = thisGroup?.members ?? newGroupMembers;
13641364 const aIndex = membersArray.indexOf(a.item.avatar);
@@ -1757,7 +1757,7 @@ async function onGroupActionClick(event) {
17571757
17581758function updateFavButtonState(state) {
17591759 fav_grp_checked = state;
17601760 $('#rm_group_fav').val(String(fav_grp_checked));
17611761 $('#group_favorite_button').toggleClass('fav_on', fav_grp_checked);
17621762 $('#group_favorite_button').toggleClass('fav_off', !fav_grp_checked);
17631763}
@@ -2047,11 +2047,9 @@ export async function deleteGroupChat(groupId, chatId) {
20472047 * @param {EventTarget} eventTarget Element that triggered the import
20482048 */
20492049export async function importGroupChat(formData, eventTarget) {
2050- const headers = getRequestHeaders();
2051- delete headers['Content-Type'];
20522050 const fetchResult = await fetch('/api/chats/group/import', {
20532051 method: 'POST',
2054- headers: headers,
2052+ headers: getRequestHeaders({ omitContentType: true }),
20552053 body: formData,
20562054 cache: 'no-cache',
20572055 });
public/scripts/personas.js+2 -8
@@ -317,12 +317,9 @@ async function uploadUserAvatar(url, name) {
317317 formData.append('overwrite_name', name);
318318 }
319319
320- const headers = getRequestHeaders();
321- delete headers['Content-Type'];
322-
323320 await fetch('/api/avatars/upload', {
324321 method: 'POST',
325- headers: headers,
322+ headers: getRequestHeaders({ omitContentType: true }),
326323 cache: 'no-cache',
327324 body: formData,
328325 });
@@ -368,12 +365,9 @@ async function changeUserAvatar(e) {
368365 formData.set('avatar', convertedFile);
369366 }
370367
371- const headers = getRequestHeaders();
372- delete headers['Content-Type'];
373-
374368 const response = await fetch(url, {
375369 method: 'POST',
376- headers: headers,
370+ headers: getRequestHeaders({ omitContentType:true }),
377371 cache: 'no-cache',
378372 body: formData,
379373 });
public/scripts/power-user.js+44 -57
@@ -61,18 +61,6 @@ import { accountStorage } from './util/AccountStorage.js';
6161import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
6262import { bindModelTemplates } from './chat-templates.js';
6363
64-export {
65- loadPowerUserSettings,
66- loadMovingUIState,
67- collapseNewlines,
68- playMessageSound,
69- fixMarkdown,
70- power_user,
71- send_on_enter_options,
72- getContextSettings,
73- applyPowerUserSettings,
74-};
75-
7664export const toastPositionClasses = [
7765 'toast-top-left',
7866 'toast-top-center',
@@ -108,7 +96,7 @@ export const chat_styles = {
10896 DOCUMENT: 2,
10997};
11098
11199export const send_on_enter_options = {
112100 DISABLED: -1,
113101 AUTO: 0,
114102 ENABLED: 1,
@@ -126,7 +114,7 @@ export const persona_description_positions = {
126114 NONE: 9,
127115};
128116
129117letexport const power_user = {
130118 charListGrid: false,
131119 tokenizer: tokenizers.BEST_MATCH,
132120 token_padding: 64,
@@ -370,7 +358,7 @@ const debug_functions = [];
370358
371359const setHotswapsDebounced = debounce(favsToHotswap);
372360
373361export function playMessageSound() {
374362 if (!power_user.play_message_sound) {
375363 return;
376364 }
@@ -395,7 +383,7 @@ function playMessageSound() {
395383 * @example
396384 * collapseNewlines("\n\n\n"); // "\n"
397385 */
398386export function collapseNewlines(x) {
399387 return x.replaceAll(/\n+/g, '\n');
400388}
401389
@@ -416,7 +404,7 @@ function collapseNewlines(x) {
416404 * // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line
417405 * "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n"
418406 */
419407export function fixMarkdown(text, forDisplay) {
420408 // Find pairs of formatting characters and capture the text in between them
421409 const format = /([*_]{1,2})([\s\S]*?)\1/gm;
422410 let matches = [];
@@ -862,13 +850,13 @@ async function CreateZenSliders(elmnt) {
862850 })
863851 //trigger slider changes when user clicks away
864852 .on('mouseup blur', function () {
865853 let manualInput = parseFloat(parseFloat(handle.text()).toFixed(decimals));
866854 if (isManualInput) {
867855 //disallow manual inputs outside acceptable range
868856 if (manualInput >= sliderMin && manualInput <= sliderMax) {
869857 //if value is ok, assign to slider and update handle text and position
870858 newSlider.val(manualInput);
871859 handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
872860 valueBeforeManualInput = manualInput;
873861 } else {
874862 //if value not ok, warn and reset to last known valid value
@@ -890,13 +878,13 @@ async function CreateZenSliders(elmnt) {
890878
891879 function handleSlideEvent(event, ui, type) {
892880 var handle = $(this).find('.ui-slider-handle');
893881 var numVal = parseFloat(Number(ui.value).toFixed(decimals));
894882 offVal = parseFloat(Number(offVal).toFixed(decimals));
895883 allVal = parseFloat(Number(allVal).toFixed(decimals));
896884 console.log(numVal, sliderMin, sliderMax, numVal > sliderMax, numVal < sliderMin);
897885 if (numVal > sliderMax) { numVal = sliderMax; }
898886 if (numVal < sliderMin) { numVal = sliderMin; }
899887 var stepNumber = parseFloat(((ui.value - sliderMin) / stepScale).toFixed(0));
900888 var handleText = (ui.value);
901889 var leftMargin = (stepNumber / numSteps) * 50 * -1;
902890 var perStepPercent = 1 / numSteps; //how far in % each step should be on the slider
@@ -1035,10 +1023,9 @@ function applyAvatarStyle() {
10351023}
10361024
10371025function applyChatDisplay() {
1038-
1026+ if ([null, undefined].includes(power_user.chat_display)) {
1039- if (!power_user.chat_display === (null || undefined)) {
10401027 console.debug('applyChatDisplay: saw no chat display type defined');
1041- return;
1028+ power_user.chat_display = chat_styles.DEFAULT;
10421029 }
10431030 console.debug(`poweruser.chat_display ${power_user.chat_display}`);
10441031 $('#chat_display').val(power_user.chat_display).prop('selected', true);
@@ -1417,7 +1404,7 @@ async function showDebugMenu() {
14171404 callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true });
14181405}
14191406
14201407export function applyPowerUserSettings() {
14211408 switchUiMode();
14221409 applyFontScale('forced');
14231410 applyThemeColor();
@@ -1496,7 +1483,7 @@ function getExampleMessagesBehavior() {
14961483}
14971484
14981485//MARK: loadPowerUser
14991486export async function loadPowerUserSettings(settings, data) {
15001487 const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript));
15011488 // Load from settings.json
15021489 if (settings.power_user !== undefined) {
@@ -1552,15 +1539,15 @@ async function loadPowerUserSettings(settings, data) {
15521539 context_presets = data.context;
15531540 }
15541541
15551542 if (typeof power_user.chat_display =!== 'number') {
15561543 power_user.chat_display = chat_styles.DEFAULT;
15571544 }
15581545
15591546 if (typeof power_user.waifuMode =!== 'boolean') {
15601547 power_user.waifuMode = false;
15611548 }
15621549
15631550 if (typeof power_user.chat_width =!== 'number') {
15641551 power_user.chat_width = 50;
15651552 }
15661553
@@ -1618,8 +1605,8 @@ async function loadPowerUserSettings(settings, data) {
16181605 $('#auto_scroll_chat_to_bottom').prop('checked', power_user.auto_scroll_chat_to_bottom);
16191606 $('#bogus_folders').prop('checked', power_user.bogus_folders);
16201607 $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);
16211608 $(`#tokenizer option[value="${power_user.tokenizer}"]`).attrprop('selected', true);
16221609 $(`#send_on_enter option[value=${power_user.send_on_enter}]`).attrprop('selected', true);
16231610 $('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);
16241611 $('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode);
16251612 $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
@@ -1655,8 +1642,8 @@ async function loadPowerUserSettings(settings, data) {
16551642 $('#enableZenSliders').prop('checked', power_user.enableZenSliders).trigger('input');
16561643 $('#enableLabMode').prop('checked', power_user.enableLabMode).trigger('input', { fromInit: true });
16571644 $(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop('checked', true);
16581645 $(`#chat_display option[value=${power_user.chat_display}]`).attrprop('selected', true).trigger('change');
16591646 $(`#toastr_position option[value=${power_user.toastr_position}]`).attrprop('selected', true).trigger('change');
16601647 $('#chat_width_slider').val(power_user.chat_width);
16611648 $('#token_padding').val(power_user.token_padding);
16621649 $('#aux_field').val(power_user.aux_field);
@@ -1763,7 +1750,7 @@ function loadCharListState() {
17631750 document.body.classList.toggle('charListGrid', power_user.charListGrid);
17641751}
17651752
17661753export function loadMovingUIState() {
17671754 if (!isMobile()
17681755 && power_user.movingUIState
17691756 && power_user.movingUI === true) {
@@ -1853,7 +1840,7 @@ function switchMaxContextSize() {
18531840}
18541841
18551842// Fetch a compiled object of all preset settings
18561843export function getContextSettings() {
18571844 let compiledSettings = {};
18581845
18591846 contextControls.forEach((control) => {
@@ -2397,7 +2384,7 @@ async function saveTheme(name = undefined, theme = undefined) {
23972384 }
23982385 else {
23992386 themes[themeIndex] = theme;
24002387 $(`#themes option[value="${name}"]`).attrprop('selected', true);
24012388 }
24022389
24032390 power_user.theme = name;
@@ -2504,7 +2491,7 @@ async function saveMovingUI() {
25042491 }
25052492 else {
25062493 movingUIPresets[movingUIPresetIndex] = movingUIPreset;
25072494 $(`#movingUIPresets option[value="${name}"]`).attrprop('selected', true);
25082495 }
25092496
25102497 power_user.movingUIPreset = name;
@@ -3162,7 +3149,7 @@ jQuery(() => {
31623149 });
31633150
31643151 const reportZoomLevelDebounced = debounce(() => {
31653152 const zoomLevel = parseFloat(Number(window.devicePixelRatio).toFixed(2)) || 1;
31663153 const winWidth = window.innerWidth;
31673154 const winHeight = window.innerHeight;
31683155 const originalWidth = winWidth * zoomLevel;
@@ -3187,8 +3174,8 @@ jQuery(() => {
31873174
31883175 //attempt to scale movingUI elements naturally across window resizing/zooms
31893176 //this will still break if the zoom level causes mobile styles to come into play.
31903177 const scaleY = parseFloat(Number(window.innerHeight / coreTruthWinHeight).toFixed(4));
31913178 const scaleX = parseFloat(Number(window.innerWidth / coreTruthWinWidth).toFixed(4));
31923179
31933180 if (Object.keys(power_user.movingUIState).length > 0) {
31943181 for (var elmntName of Object.keys(power_user.movingUIState)) {
@@ -3362,7 +3349,7 @@ jQuery(() => {
33623349 });
33633350
33643351 $('#waifuMode').on('change', () => {
33653352 power_user.waifuMode = !!$('#waifuMode').prop('checked');
33663353 switchWaifuMode();
33673354 saveSettingsDebounced();
33683355 });
@@ -3410,7 +3397,7 @@ jQuery(() => {
34103397
34113398 $('#chat_width_slider').on('input', function (e, data) {
34123399 const applyMode = data?.forced ? 'forced' : 'normal';
34133400 power_user.chat_width = Number(e.target$(this).valueval());
34143401 applyChatWidth(applyMode);
34153402 saveSettingsDebounced();
34163403 setHotswapsDebounced();
@@ -3440,81 +3427,81 @@ jQuery(() => {
34403427
34413428 $('input[name="font_scale"]').on('input', async function (e, data) {
34423429 const applyMode = data?.forced ? 'forced' : 'normal';
34433430 power_user.font_scale = Number(e.target$(this).valueval());
34443431 $('#font_scale_counter').val(power_user.font_scale);
34453432 applyFontScale(applyMode);
34463433 saveSettingsDebounced();
34473434 });
34483435
34493436 $('input[name="blur_strength"]').on('input', async function (e) {
34503437 power_user.blur_strength = Number(e.target$(this).valueval());
34513438 $('#blur_strength_counter').val(power_user.blur_strength);
34523439 applyBlurStrength();
34533440 saveSettingsDebounced();
34543441 });
34553442
34563443 $('input[name="shadow_width"]').on('input', async function (e) {
34573444 power_user.shadow_width = Number(e.target$(this).valueval());
34583445 $('#shadow_width_counter').val(power_user.shadow_width);
34593446 applyShadowWidth();
34603447 saveSettingsDebounced();
34613448 });
34623449
34633450 $('#main-text-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
34643451 power_user.main_text_color = evt.detail.rgba;
34653452 applyThemeColor('main');
34663453 saveSettingsDebounced();
34673454 });
34683455
34693456 $('#italics-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
34703457 power_user.italics_text_color = evt.detail.rgba;
34713458 applyThemeColor('italics');
34723459 saveSettingsDebounced();
34733460 });
34743461
34753462 $('#underline-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
34763463 power_user.underline_text_color = evt.detail.rgba;
34773464 applyThemeColor('underline');
34783465 saveSettingsDebounced();
34793466 });
34803467
34813468 $('#quote-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
34823469 power_user.quote_text_color = evt.detail.rgba;
34833470 applyThemeColor('quote');
34843471 saveSettingsDebounced();
34853472 });
34863473
34873474 $('#blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
34883475 power_user.blur_tint_color = evt.detail.rgba;
34893476 applyThemeColor('blurTint');
34903477 saveSettingsDebounced();
34913478 });
34923479
34933480 $('#chat-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
34943481 power_user.chat_tint_color = evt.detail.rgba;
34953482 applyThemeColor('chatTint');
34963483 saveSettingsDebounced();
34973484 });
34983485
34993486 $('#user-mes-blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
35003487 power_user.user_mes_blur_tint_color = evt.detail.rgba;
35013488 applyThemeColor('userMesBlurTint');
35023489 saveSettingsDebounced();
35033490 });
35043491
35053492 $('#bot-mes-blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
35063493 power_user.bot_mes_blur_tint_color = evt.detail.rgba;
35073494 applyThemeColor('botMesBlurTint');
35083495 saveSettingsDebounced();
35093496 });
35103497
35113498 $('#shadow-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
35123499 power_user.shadow_color = evt.detail.rgba;
35133500 applyThemeColor('shadow');
35143501 saveSettingsDebounced();
35153502 });
35163503
35173504 $('#border-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
35183505 power_user.border_color = evt.detail.rgba;
35193506 applyThemeColor('border');
35203507 saveSettingsDebounced();
public/scripts/welcome-screen.js+1 -4
@@ -577,12 +577,9 @@ async function createPermanentAssistant() {
577577 console.warn('Error fetching system avatar. Fallback image will be used.', error);
578578 }
579579
580- const headers = getRequestHeaders();
581- delete headers['Content-Type'];
582-
583580 const fetchResult = await fetch('/api/characters/create', {
584581 method: 'POST',
585- headers: headers,
582+ headers: getRequestHeaders({ omitContentType: true }),
586583 body: formData,
587584 cache: 'no-cache',
588585 });
public/scripts/world-info.js+23 -13
@@ -5199,6 +5199,10 @@ export function onWorldInfoChange(args, text) {
51995199 return '';
52005200}
52015201
5202+/**
5203+ * Imports world info from a file.
5204+ * @param {File} file File to import
5205+ */
52025206export async function importWorldInfo(file) {
52035207 if (!file) {
52045208 return;
@@ -5252,15 +5256,20 @@ export async function importWorldInfo(file) {
52525256 return false;
52535257 }
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,
52605264 cache: false'no-cache',
5261- contentType: false,
5265+ });
5262- processData: false,
5266+
52635267 success: async functionif (data!result.ok) {
5268+ throw new Error(`Failed to import world info: ${result.statusText}`);
5269+ }
5270+
5271+ const data = await result.json();
5272+
52645273 if (data.name) {
52655274 await updateWorldInfoList();
52665275
@@ -5269,11 +5278,12 @@ export async function importWorldInfo(file) {
52695278 $('#world_editor_select').val(newIndex).trigger('change');
52705279 }
52715280
52725281 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`);
52735286 }
5274- },
5275- error: (_jqXHR, _exception) => { },
5276- });
52775287}
52785288
52795289/**
src/endpoints/sprites.js+2 -2
@@ -216,7 +216,7 @@ router.post('/upload-zip', async (request, response) => {
216216
217217 // Remove uploaded ZIP file
218218 fs.unlinkSync(spritePackPath);
219219 return response.send({ ok: true, count: sprites.length });
220220 } catch (error) {
221221 console.error(error);
222222 return response.sendStatus(500);
@@ -262,7 +262,7 @@ router.post('/upload', async (request, response) => {
262262 fs.cpSync(spritePath, pathToFile);
263263 // Remove uploaded file
264264 fs.unlinkSync(spritePath);
265265 return response.sendStatussend(200{ ok: true });
266266 } catch (error) {
267267 console.error(error);
268268 return response.sendStatus(500);