diff --git a/public/index.html b/public/index.html index 04d9e1d51..7eeccf4e0 100644 --- a/public/index.html +++ b/public/index.html @@ -177,7 +177,7 @@

Chat Completion Presets

-
diff --git a/public/script.js b/public/script.js index 6b3b10e3a..bc29aff53 100644 --- a/public/script.js +++ b/public/script.js @@ -192,6 +192,7 @@ import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay import { loadMancerModels } from './scripts/mancer-settings.js'; import { appendFileContent, hasPendingFileAttachment, populateFileAttachment } from './scripts/chats.js'; import { replaceVariableMacros } from './scripts/variables.js'; +import { initPresetManager } from './scripts/preset-manager.js'; //exporting functions and vars for mods export { @@ -738,6 +739,7 @@ async function firstLoadInit() { await getCharacters(); await getBackgrounds(); await initTokenizers(); + await initPresetManager(); initBackgrounds(); initAuthorsNote(); initPersonas(); @@ -7446,7 +7448,10 @@ const swipe_right = () => { } }; -function connectAPISlash(_, text) { +/** + * @param {string} text API name + */ +async function connectAPISlash(_, text) { if (!text) return; const apiMap = { @@ -7460,7 +7465,29 @@ function connectAPISlash(_, text) { button: '#api_button_novel', }, 'ooba': { + selected: 'textgenerationwebui', button: '#api_button_textgenerationwebui', + type: textgen_types.OOBA, + }, + 'tabby': { + selected: 'textgenerationwebui', + button: '#api_button_textgenerationwebui', + type: textgen_types.TABBY, + }, + 'mancer': { + selected: 'textgenerationwebui', + button: '#api_button_textgenerationwebui', + type: textgen_types.MANCER, + }, + 'aphrodite': { + selected: 'textgenerationwebui', + button: '#api_button_textgenerationwebui', + type: textgen_types.APHRODITE, + }, + 'kcpp': { + selected: 'textgenerationwebui', + button: '#api_button_textgenerationwebui', + type: textgen_types.KOBOLDCPP, }, 'oai': { selected: 'openai', @@ -7499,7 +7526,7 @@ function connectAPISlash(_, text) { }, }; - const apiConfig = apiMap[text]; + const apiConfig = apiMap[text.toLowerCase()]; if (!apiConfig) { toastr.error(`Error: ${text} is not a valid API`); return; @@ -7513,11 +7540,23 @@ function connectAPISlash(_, text) { $('#chat_completion_source').trigger('change'); } + if (apiConfig.type) { + $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true); + $('#textgen_type').trigger('change'); + } + if (apiConfig.button) { $(apiConfig.button).trigger('click'); } toastr.info(`API set to ${text}, trying to connect..`); + + try { + await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); + console.log('Connection successful'); + } catch { + console.log('Could not connect after 5 seconds, skipping.'); + } } export async function processDroppedFiles(files) { @@ -7771,7 +7810,7 @@ jQuery(async function () { } registerSlashCommand('dupe', DupeChar, [], '– duplicates the currently selected character', true, true); - registerSlashCommand('api', connectAPISlash, [], '(kobold, horde, novel, ooba, oai, claude, windowai, openrouter, scale, ai21, palm) – connect to an API', true, true); + registerSlashCommand('api', connectAPISlash, [], '(kobold, horde, novel, ooba, tabby, mancer, aphrodite, kcpp, oai, claude, windowai, openrouter, scale, ai21, palm) – connect to an API', true, true); registerSlashCommand('impersonate', doImpersonate, ['imp'], '– calls an impersonation response', true, true); registerSlashCommand('delchat', doDeleteChat, [], '– deletes the current chat', true, true); registerSlashCommand('closechat', doCloseChat, [], '– closes the current chat', true, true); diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 12cfcd92a..0631c0a7a 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -2478,28 +2478,6 @@ function showWindowExtensionError() { }); } -function trySelectPresetByName(name) { - let preset_found = null; - for (const key in openai_setting_names) { - if (name.trim() == key.trim()) { - preset_found = key; - break; - } - } - - // Don't change if the current preset is the same - if (preset_found && preset_found === oai_settings.preset_settings_openai) { - return; - } - - if (preset_found) { - oai_settings.preset_settings_openai = preset_found; - const value = openai_setting_names[preset_found]; - $(`#settings_preset_openai option[value="${value}"]`).attr('selected', true); - $('#settings_preset_openai').val(value).trigger('change'); - } -} - /** * Persist a settings preset with the given name * @@ -3573,29 +3551,6 @@ $(document).ready(async function () { saveSettingsDebounced(); }); - // auto-select a preset based on character/group name - $(document).on('click', '.character_select', function () { - const chid = $(this).attr('chid'); - const name = characters[chid]?.name; - - if (!name) { - return; - } - - trySelectPresetByName(name); - }); - - $(document).on('click', '.group_select', function () { - const grid = $(this).data('id'); - const name = groups.find(x => x.id === grid)?.name; - - if (!name) { - return; - } - - trySelectPresetByName(name); - }); - $('#update_oai_preset').on('click', async function () { const name = oai_settings.preset_settings_openai; await saveOpenAIPreset(name, oai_settings); diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index f48ab287b..8b88f97a1 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -12,6 +12,7 @@ import { nai_settings, novelai_setting_names, novelai_settings, + online_status, saveSettingsDebounced, this_chid, } from '../script.js'; @@ -19,6 +20,7 @@ import { groups, selected_group } from './group-chats.js'; import { instruct_presets } from './instruct-mode.js'; import { kai_settings } from './kai-settings.js'; import { context_presets, getContextSettings, power_user } from './power-user.js'; +import { registerSlashCommand } from './slash-commands.js'; import { textgenerationwebui_preset_names, textgenerationwebui_presets, @@ -28,6 +30,9 @@ import { download, parseJsonFile, waitUntilCondition } from './utils.js'; const presetManagers = {}; +/** + * Automatically select a preset for current API based on character or group name. + */ function autoSelectPreset() { const presetManager = getPresetManager(); @@ -57,7 +62,12 @@ function autoSelectPreset() { } } -function getPresetManager(apiId) { +/** + * Gets a preset manager by API id. + * @param {string} apiId API id + * @returns {PresetManager} Preset manager + */ +function getPresetManager(apiId = '') { if (!apiId) { apiId = main_api == 'koboldhorde' ? 'kobold' : main_api; } @@ -69,6 +79,9 @@ function getPresetManager(apiId) { return presetManagers[apiId]; } +/** + * Registers preset managers for all select elements with data-preset-manager-for attribute. + */ function registerPresetManagers() { $('select[data-preset-manager-for]').each((_, e) => { const forData = $(e).data('preset-manager-for'); @@ -85,21 +98,46 @@ class PresetManager { this.apiId = apiId; } + /** + * Gets all preset names. + * @returns {string[]} List of preset names + */ + getAllPresets() { + return $(this.select).find('option').map((_, el) => el.text).toArray(); + } + + /** + * Finds a preset by name. + * @param {string} name Preset name + * @returns {any} Preset value + */ findPreset(name) { return $(this.select).find(`option:contains(${name})`).val(); } + /** + * Gets the selected preset value. + * @returns {any} Selected preset value + */ getSelectedPreset() { return $(this.select).find('option:selected').val(); } + /** + * Gets the selected preset name. + * @returns {string} Selected preset name + */ getSelectedPresetName() { return $(this.select).find('option:selected').text(); } - selectPreset(preset) { - $(this.select).find(`option[value=${preset}]`).prop('selected', true); - $(this.select).val(preset).trigger('change'); + /** + * Selects a preset by option value. + * @param {string} value Preset option value + */ + selectPreset(value) { + $(this.select).find(`option[value=${value}]`).prop('selected', true); + $(this.select).val(value).trigger('change'); } async updatePreset() { @@ -334,11 +372,91 @@ class PresetManager { } } -jQuery(async () => { - await waitUntilCondition(() => eventSource !== undefined); +/** + * Selects a preset by name for current API. + * @param {any} _ Named arguments + * @param {string} name Unnamed arguments + * @returns {Promise} Selected or current preset name + */ +async function presetCommandCallback(_, name) { + const shouldReconnect = online_status !== 'no_connection'; + const presetManager = getPresetManager(); + const allPresets = presetManager.getAllPresets(); + const currentPreset = presetManager.getSelectedPresetName(); + if (!presetManager) { + console.debug(`Preset Manager not found for API: ${main_api}`); + return ''; + } + + if (!name) { + console.log('No name provided for /preset command, using current preset'); + return currentPreset; + } + + if (!Array.isArray(allPresets) || allPresets.length === 0) { + console.log(`No presets found for API: ${main_api}`); + return currentPreset; + } + + // Find exact match + const exactMatch = allPresets.find(p => p.toLowerCase().trim() === name.toLowerCase().trim()); + + if (exactMatch) { + console.log('Found exact preset match', exactMatch); + + if (currentPreset !== exactMatch) { + const presetValue = presetManager.findPreset(exactMatch); + + if (presetValue) { + presetManager.selectPreset(presetValue); + shouldReconnect && await waitForConnection(); + } + } + + return exactMatch; + } else { + // Find fuzzy match + const fuse = new Fuse(allPresets); + const fuzzyMatch = fuse.search(name); + + if (!fuzzyMatch.length) { + console.warn(`WARN: Preset found with name ${name}`); + return currentPreset; + } + + const fuzzyPresetName = fuzzyMatch[0].item; + const fuzzyPresetValue = presetManager.findPreset(fuzzyPresetName); + + if (fuzzyPresetValue) { + console.log('Found fuzzy preset match', fuzzyPresetName); + + if (currentPreset !== fuzzyPresetName) { + presetManager.selectPreset(fuzzyPresetValue); + shouldReconnect && await waitForConnection(); + } + } + + return fuzzyPresetName; + } +} + +/** + * Waits for API connection to be established. + */ +async function waitForConnection() { + try { + await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); + } catch { + console.log('Timeout waiting for API to connect'); + } +} + +export async function initPresetManager() { eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset); registerPresetManagers(); + registerSlashCommand('preset', presetCommandCallback, [], '(name) – sets a preset by name for the current API', true, true); + $(document).on('click', '[data-preset-manager-update]', async function () { const apiId = $(this).data('preset-manager-update'); const presetManager = getPresetManager(apiId); @@ -440,7 +558,7 @@ jQuery(async () => { saveSettingsDebounced(); }); - $(document).on('click', '[data-preset-manager-restore]', async function() { + $(document).on('click', '[data-preset-manager-restore]', async function () { const apiId = $(this).data('preset-manager-restore'); const presetManager = getPresetManager(apiId); @@ -490,4 +608,4 @@ jQuery(async () => { toastr.success('Preset restored'); } }); -}); +}