mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	WI import checking for existing worlds too
- WI import uses the same check as create new world - API endpoint to get server-side sanitized filenames - Small changes to toast messages
This commit is contained in:
		| @@ -1021,6 +1021,36 @@ export function extractDataFromPng(data, identifier = 'chara') { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Sends a request to the server to sanitize a given filename | ||||
|  * | ||||
|  * @param {string} fileName - The name of the file to sanitize | ||||
|  * @returns {Promise<string>} A Promise that resolves to the sanitized filename if successful, or rejects with an error message if unsuccessful | ||||
|  */ | ||||
| export async function getSanitizedFilename(fileName) { | ||||
|     try { | ||||
|         const result = await fetch('/api/files/sanitize-filename', { | ||||
|             method: 'POST', | ||||
|             headers: getRequestHeaders(), | ||||
|             body: JSON.stringify({ | ||||
|                 fileName: fileName, | ||||
|             }), | ||||
|         }); | ||||
|  | ||||
|         if (!result.ok) { | ||||
|             const error = await result.text(); | ||||
|             throw new Error(error); | ||||
|         } | ||||
|  | ||||
|         const responseData = await result.json(); | ||||
|         return responseData.fileName; | ||||
|     } catch (error) { | ||||
|         toastr.error(String(error), 'Could not sanitize fileName'); | ||||
|         console.error('Could not sanitize fileName', error); | ||||
|         throw error; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Sends a base64 encoded image to the backend to be saved as a file. | ||||
|  * | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js'; | ||||
| import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, equalsIgnoreCaseAndAccents } from './utils.js'; | ||||
| import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, equalsIgnoreCaseAndAccents, getSanitizedFilename } from './utils.js'; | ||||
| import { extension_settings, getContext } from './extensions.js'; | ||||
| import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; | ||||
| import { isMobile } from './RossAscends-mods.js'; | ||||
| @@ -2631,19 +2631,9 @@ async function createNewWorldInfo(worldInfoName, { interactive = false } = {}) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const existingWorld = world_names.find(x => equalsIgnoreCaseAndAccents(x, worldInfoName)); | ||||
|     if (existingWorld) { | ||||
|         const overwrite = interactive ? await callPopup(`<h3>Creating New World Info</h3><p>A world with the same name already exists:<br />${existingWorld}</p>Do you want to overwrite it?`, 'confirm') : false; | ||||
|  | ||||
|         if (!overwrite) { | ||||
|             toastr.warning(`World creation cancelled. A world with the same name already exists:<br />${existingWorld}`, 'Creating New World Info', { escapeHtml: false }); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         toastr.info(`Overwriting Existing World Info:<br />${existingWorld}`, 'Creating New World Info', { escapeHtml: false }); | ||||
|  | ||||
|         // Manually delete, as we want to overwrite. The name might be slightly different so file name would not be the same. | ||||
|         await deleteWorldInfo(existingWorld); | ||||
|     const allowed = await checkCanOverwriteWorldInfo(worldInfoName, { interactive: interactive, actionName: 'Create' }); | ||||
|     if (!allowed) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     await saveWorldInfo(worldInfoName, worldInfoTemplate, true); | ||||
| @@ -2659,6 +2649,37 @@ async function createNewWorldInfo(worldInfoName, { interactive = false } = {}) { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Confirms if the user wants to overwrite an existing world info with the same name. | ||||
|  * If no world info with the name exists, this simply returns true | ||||
|  * | ||||
|  * @param {string} name - The name of the world info to create | ||||
|  * @param {Object} options - Optional parameters | ||||
|  * @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world | ||||
|  * @param {string} [options.actionName='overwrite'] - The action name to display in the confirmation dialog | ||||
|  * @returns {Promise<boolean>} True if the user confirmed the overwrite, false otherwise | ||||
|  */ | ||||
| async function checkCanOverwriteWorldInfo(name, { interactive = false, actionName = 'Overwrite' } = {}) { | ||||
|     const existingWorld = world_names.find(x => equalsIgnoreCaseAndAccents(x, name)); | ||||
|     if (!existingWorld) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     const overwrite = interactive ? await callPopup(`<h3>World Info ${actionName}</h3><p>A world with the same name already exists:<br />${existingWorld}</p>Do you want to overwrite it?`, 'confirm') : false; | ||||
|     if (!overwrite) { | ||||
|         toastr.warning(`World ${actionName.toLowerCase()} cancelled. A world with the same name already exists:<br />${existingWorld}`, `World Info ${actionName}`, { escapeHtml: false }); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     toastr.info(`Overwriting Existing World Info:<br />${existingWorld}`, `World Info ${actionName}`, { escapeHtml: false }); | ||||
|  | ||||
|     // Manually delete, as we want to overwrite. The name might be slightly different so file name would not be the same. | ||||
|     await deleteWorldInfo(existingWorld); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| async function getCharacterLore() { | ||||
|     const character = characters[this_chid]; | ||||
|     const name = character?.name; | ||||
| @@ -3589,6 +3610,13 @@ export async function importWorldInfo(file) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const worldName = file.name.substr(0, file.name.lastIndexOf(".")); | ||||
|     const sanitizedWorldName = await getSanitizedFilename(worldName); | ||||
|     const allowed = await checkCanOverwriteWorldInfo(sanitizedWorldName, { interactive: true, actionName: 'Import' }); | ||||
|     if (!allowed) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     jQuery.ajax({ | ||||
|         type: 'POST', | ||||
|         url: '/api/worldinfo/import', | ||||
| @@ -3606,7 +3634,7 @@ export async function importWorldInfo(file) { | ||||
|                     $('#world_editor_select').val(newIndex).trigger('change'); | ||||
|                 } | ||||
|  | ||||
|                 toastr.info(`World Info "${data.name}" imported successfully!`); | ||||
|                 toastr.success(`World Info "${data.name}" imported successfully!`); | ||||
|             } | ||||
|         }, | ||||
|         error: (_jqXHR, _exception) => { }, | ||||
|   | ||||
| @@ -2,11 +2,28 @@ const path = require('path'); | ||||
| const fs = require('fs'); | ||||
| const writeFileSyncAtomic = require('write-file-atomic').sync; | ||||
| const express = require('express'); | ||||
| const sanitize = require('sanitize-filename'); | ||||
| const router = express.Router(); | ||||
| const { validateAssetFileName } = require('./assets'); | ||||
| const { jsonParser } = require('../express-common'); | ||||
| const { clientRelativePath } = require('../util'); | ||||
|  | ||||
| router.post('/sanitize-filename', jsonParser, async (request, response) => { | ||||
|     try { | ||||
|         const fileName = String(request.body.fileName); | ||||
|         if (!fileName) { | ||||
|             return response.status(400).send('No fileName specified'); | ||||
|         } | ||||
|  | ||||
|         const sanitizedFilename = sanitize(fileName); | ||||
|         console.debug(`Sanitized fileName: ${fileName} -> ${sanitizedFilename}`); | ||||
|         return response.send({ fileName: sanitizedFilename }); | ||||
|     } catch (error) { | ||||
|         console.log(error); | ||||
|         return response.sendStatus(500); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| router.post('/upload', jsonParser, async (request, response) => { | ||||
|     try { | ||||
|         if (!request.body.name) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user