diff --git a/public/index.html b/public/index.html index fa0a0b7c9..cbb5382da 100644 --- a/public/index.html +++ b/public/index.html @@ -135,16 +135,19 @@ -
- - - - - - - +
+ + + + + + + +
@@ -154,45 +157,53 @@ -
- - - - - - - +
+ + + + + + + +

Chat Completion Presets

-
+
- - - - - - +
+ + + + + + +

Text Completion presets

-
+
- - - - - - +
+ + + + + + + +
@@ -2150,7 +2161,7 @@ - + @@ -2241,7 +2252,7 @@ - + diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 2505d22bd..760a364b6 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -117,10 +117,11 @@ class PresetManager { } async savePresetAs() { + const inputValue = this.getSelectedPresetName(); const popupText = `

Preset name:

${!this.isNonGenericApi() ? '

Hint: Use a character/group name to bind preset to a specific chat.

' : ''}`; - const name = await callPopup(popupText, 'input'); + const name = await callPopup(popupText, 'input', inputValue); if (!name) { console.log('Preset name not provided'); @@ -314,11 +315,22 @@ class PresetManager { body: JSON.stringify({ name: nameToDelete, apiId: this.apiId }), }); + return response.ok; + } + + async getDefaultPreset(name) { + const response = await fetch('/api/presets/restore', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ name, apiId: this.apiId }), + }); + if (!response.ok) { - toastr.warning('Preset was not deleted from server'); - } else { - toastr.success('Preset deleted'); + toastr.error('Failed to restore default preset'); + return; } + + return await response.json(); } } @@ -417,7 +429,65 @@ jQuery(async () => { return; } - await presetManager.deleteCurrentPreset(); + const result = await presetManager.deleteCurrentPreset(); + + if (result) { + toastr.success('Preset deleted'); + } else { + toastr.warning('Preset was not deleted from server'); + } + saveSettingsDebounced(); }); + + $(document).on('click', '[data-preset-manager-restore]', async function() { + const apiId = $(this).data('preset-manager-restore'); + const presetManager = getPresetManager(apiId); + + if (!presetManager) { + console.warn(`Preset Manager not found for API: ${apiId}`); + return; + } + + const name = presetManager.getSelectedPresetName(); + const data = await presetManager.getDefaultPreset(name); + + if (name == 'gui') { + toastr.info('Cannot restore GUI preset'); + return; + } + + if (!data) { + return; + } + + if (data.isDefault) { + if (Object.keys(data.preset).length === 0) { + toastr.error('Default preset cannot be restored'); + return; + } + + const confirm = await callPopup('

Are you sure?

Resetting a default preset will restore the default settings.', 'confirm'); + + if (!confirm) { + return; + } + + await presetManager.deleteCurrentPreset(); + await presetManager.savePreset(name, data.preset); + const option = presetManager.findPreset(name); + presetManager.selectPreset(option); + toastr.success('Default preset restored'); + } else { + const confirm = await callPopup('

Are you sure?

Resetting a custom preset will restore to the last saved state.', 'confirm'); + + if (!confirm) { + return; + } + + const option = presetManager.findPreset(name); + presetManager.selectPreset(option); + toastr.success('Preset restored'); + } + }); }); diff --git a/src/content-manager.js b/src/content-manager.js index f97225b39..955e9fc63 100644 --- a/src/content-manager.js +++ b/src/content-manager.js @@ -7,11 +7,56 @@ const contentDirectory = path.join(process.cwd(), 'default/content'); const contentLogPath = path.join(contentDirectory, 'content.log'); const contentIndexPath = path.join(contentDirectory, 'index.json'); const { DIRECTORIES } = require('./constants'); +const presetFolders = [DIRECTORIES.koboldAI_Settings, DIRECTORIES.openAI_Settings, DIRECTORIES.novelAI_Settings, DIRECTORIES.textGen_Settings]; +/** + * Gets the default presets from the content directory. + * @returns {object[]} Array of default presets + */ +function getDefaultPresets() { + try { + const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8'); + const contentIndex = JSON.parse(contentIndexText); + + const presets = []; + + for (const contentItem of contentIndex) { + if (contentItem.type.endsWith('_preset')) { + contentItem.name = path.parse(contentItem.filename).name; + contentItem.folder = getTargetByType(contentItem.type); + presets.push(contentItem); + } + } + + return presets; + } catch (err) { + console.log('Failed to get default presets', err); + return []; + } +} + +/** + * Gets a default JSON file from the content directory. + * @param {string} filename Name of the file to get + * @returns {object | null} JSON object or null if the file doesn't exist + */ +function getDefaultPresetFile(filename) { + try { + const contentPath = path.join(contentDirectory, filename); + + if (!fs.existsSync(contentPath)) { + return null; + } + + const fileContent = fs.readFileSync(contentPath, 'utf8'); + return JSON.parse(fileContent); + } catch (err) { + console.log(`Failed to get default file ${filename}`, err); + return null; + } +} function migratePresets() { - const presetFolders = [DIRECTORIES.koboldAI_Settings, DIRECTORIES.openAI_Settings, DIRECTORIES.novelAI_Settings, DIRECTORIES.textGen_Settings]; - for (const presetFolder of presetFolders) { const presetPath = path.join(process.cwd(), presetFolder); const presetFiles = fs.readdirSync(presetPath); @@ -20,9 +65,12 @@ function migratePresets() { const presetFilePath = path.join(presetPath, presetFile); const newFileName = presetFile.replace('.settings', '.json'); const newFilePath = path.join(presetPath, newFileName); + const backupFileName = presetFolder.replace('/', '_') + '_' + presetFile; + const backupFilePath = path.join(DIRECTORIES.backups, backupFileName); if (presetFilePath.endsWith('.settings')) { if (!fs.existsSync(newFilePath)) { + fs.cpSync(presetFilePath, backupFilePath); fs.cpSync(presetFilePath, newFilePath); console.log(`Migrated ${presetFilePath} to ${newFilePath}`); } @@ -310,4 +358,6 @@ function registerEndpoints(app, jsonParser) { module.exports = { checkForNewContent, registerEndpoints, + getDefaultPresets, + getDefaultPresetFile, }; diff --git a/src/presets.js b/src/presets.js index eee9034c7..24eecf917 100644 --- a/src/presets.js +++ b/src/presets.js @@ -3,6 +3,7 @@ const path = require('path'); const sanitize = require('sanitize-filename'); const writeFileAtomicSync = require('write-file-atomic').sync; const { DIRECTORIES } = require('./constants'); +const { getDefaultPresetFile, getDefaultPresets } = require('./content-manager'); /** * Gets the folder and extension for the preset settings based on the API source ID. @@ -76,6 +77,28 @@ function registerEndpoints(app, jsonParser) { } }); + app.post('/api/presets/restore', jsonParser, function (request, response) { + try { + const settings = getPresetSettingsByAPI(request.body.apiId); + const name = sanitize(request.body.name); + const defaultPresets = getDefaultPresets(); + + const defaultPreset = defaultPresets.find(p => p.name === name && p.folder === settings.folder); + + const result = { isDefault: false, preset: {} }; + + if (defaultPreset) { + result.isDefault = true; + result.preset = getDefaultPresetFile(defaultPreset.filename) || {}; + } + + return response.send(result); + } catch (error) { + console.log(error); + return response.sendStatus(500); + } + }); + // TODO: Merge with /api/presets/save app.post('/api/presets/save-openai', jsonParser, function (request, response) { if (!request.body || typeof request.query.name !== 'string') return response.sendStatus(400);