diff --git a/public/index.html b/public/index.html index 81d9d5551..abb82f628 100644 --- a/public/index.html +++ b/public/index.html @@ -536,6 +536,19 @@ +
+
+ Repetition Penalty +
+
+
+ +
+
+ +
+
+
Min P @@ -1315,7 +1328,7 @@
--> -
+

@@ -5248,4 +5261,4 @@ - \ No newline at end of file + diff --git a/public/script.js b/public/script.js index 571b95d2f..b34c88d58 100644 --- a/public/script.js +++ b/public/script.js @@ -78,6 +78,7 @@ import { ui_mode, switchSimpleMode, flushEphemeralStoppingStrings, + context_presets, } from './scripts/power-user.js'; import { @@ -178,6 +179,9 @@ import { getInstructStoppingSequences, autoSelectInstructPreset, formatInstructModeSystemPrompt, + selectInstructPreset, + instruct_presets, + selectContextPreset, } from './scripts/instruct-mode.js'; import { applyLocale, initLocales } from './scripts/i18n.js'; import { getFriendlyTokenizerName, getTokenCount, getTokenizerModel, initTokenizers, saveTokenCache } from './scripts/tokenizers.js'; @@ -331,6 +335,7 @@ export const event_types = { CHAT_DELETED: 'chat_deleted', GROUP_CHAT_DELETED: 'group_chat_deleted', GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', + GROUP_MEMBER_DRAFTED: 'group_member_drafted', }; export const eventSource = new EventEmitter(); @@ -7401,6 +7406,54 @@ const CONNECT_API_MAP = { }, }; +async function selectContextCallback(_, name) { + if (!name) { + toastr.warning('Context preset name is required'); + return ''; + } + + const contextNames = context_presets.map(preset => preset.name); + const fuse = new Fuse(contextNames); + const result = fuse.search(name); + + if (result.length === 0) { + toastr.warning(`Context preset "${name}" not found`); + return ''; + } + + const foundName = result[0].item; + selectContextPreset(foundName); + return foundName; +} + +async function selectInstructCallback(_, name) { + if (!name) { + toastr.warning('Instruct preset name is required'); + return ''; + } + + const instructNames = instruct_presets.map(preset => preset.name); + const fuse = new Fuse(instructNames); + const result = fuse.search(name); + + if (result.length === 0) { + toastr.warning(`Instruct preset "${name}" not found`); + return ''; + } + + const foundName = result[0].item; + selectInstructPreset(foundName); + return foundName; +} + +async function enableInstructCallback() { + $('#instruct_enabled').prop('checked', true).trigger('change'); +} + +async function disableInstructCallback() { + $('#instruct_enabled').prop('checked', false).trigger('change'); +} + /** * @param {string} text API name */ @@ -7433,7 +7486,7 @@ async function connectAPISlash(_, text) { toastr.info(`API set to ${text}, trying to connect..`); try { - await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); + await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100); console.log('Connection successful'); } catch { console.log('Could not connect after 5 seconds, skipping.'); @@ -7698,6 +7751,10 @@ jQuery(async function () { registerSlashCommand('closechat', doCloseChat, [], '– closes the current chat', true, true); registerSlashCommand('panels', doTogglePanels, ['togglepanels'], '– toggle UI panels on/off', true, true); registerSlashCommand('forcesave', doForceSave, [], '– forces a save of the current chat and settings', true, true); + registerSlashCommand('instruct', selectInstructCallback, [], '(name) – selects instruct mode preset by name', true, true); + registerSlashCommand('instruct-on', enableInstructCallback, [], '– enables instruct mode', true, true); + registerSlashCommand('instruct-off', disableInstructCallback, [], '– disables instruct mode', true, true); + registerSlashCommand('context', selectContextCallback, [], '(name) – selects context template by name', true, true); setTimeout(function () { $('#groupControlsToggle').trigger('click'); diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 125140b6d..67fd1f1f0 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -167,9 +167,12 @@ async function doExtrasFetch(endpoint, args) { if (!args.headers) { args.headers = {}; } - Object.assign(args.headers, { - 'Authorization': `Bearer ${extension_settings.apiKey}`, - }); + + if (extension_settings.apiKey) { + Object.assign(args.headers, { + 'Authorization': `Bearer ${extension_settings.apiKey}`, + }); + } const response = await fetch(endpoint, args); return response; diff --git a/public/scripts/extensions/quick-reply/api/QuickReplyApi.js b/public/scripts/extensions/quick-reply/api/QuickReplyApi.js index 712b10d58..375c5b9ef 100644 --- a/public/scripts/extensions/quick-reply/api/QuickReplyApi.js +++ b/public/scripts/extensions/quick-reply/api/QuickReplyApi.js @@ -190,6 +190,7 @@ export class QuickReplyApi { * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded + * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected * @returns {QuickReply} the new quick reply */ createQuickReply(setName, label, { @@ -200,6 +201,7 @@ export class QuickReplyApi { executeOnUser, executeOnAi, executeOnChatChange, + executeOnGroupMemberDraft, } = {}) { const set = this.getSetByName(setName); if (!set) { @@ -214,6 +216,7 @@ export class QuickReplyApi { qr.executeOnUser = executeOnUser ?? false; qr.executeOnAi = executeOnAi ?? false; qr.executeOnChatChange = executeOnChatChange ?? false; + qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false; qr.onUpdate(); return qr; } @@ -232,6 +235,7 @@ export class QuickReplyApi { * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded + * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected * @returns {QuickReply} the altered quick reply */ updateQuickReply(setName, label, { @@ -243,19 +247,21 @@ export class QuickReplyApi { executeOnUser, executeOnAi, executeOnChatChange, + executeOnGroupMemberDraft, } = {}) { const qr = this.getQrByLabel(setName, label); if (!qr) { throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); } - qr.label = newLabel ?? qr.label; - qr.message = message ?? qr.message; - qr.title = title ?? qr.title; + qr.updateLabel(newLabel ?? qr.label); + qr.updateMessage(message ?? qr.message); + qr.updateTitle(title ?? qr.title); qr.isHidden = isHidden ?? qr.isHidden; qr.executeOnStartup = executeOnStartup ?? qr.executeOnStartup; qr.executeOnUser = executeOnUser ?? qr.executeOnUser; qr.executeOnAi = executeOnAi ?? qr.executeOnAi; qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange; + qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft; qr.onUpdate(); return qr; } diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html index a676381ac..ad5304c22 100644 --- a/public/scripts/extensions/quick-reply/html/qrEditor.html +++ b/public/scripts/extensions/quick-reply/html/qrEditor.html @@ -66,6 +66,10 @@ Execute on opening chat +
diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index 376fd7152..735337795 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -58,6 +58,10 @@ const defaultSettings = { }; +/** @type {Boolean}*/ +let isReady = false; +/** @type {Function[]}*/ +let executeQueue = []; /** @type {QuickReplySettings}*/ let settings; /** @type {SettingsUi} */ @@ -99,6 +103,7 @@ const loadSets = async () => { qr.executeOnUser = slot.autoExecute_userMessage ?? false; qr.executeOnAi = slot.autoExecute_botMessage ?? false; qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; + qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; qr.contextList = (slot.contextMenu ?? []).map(it=>({ set: it.preset, isChained: it.chain, @@ -144,6 +149,16 @@ const loadSettings = async () => { } }; +const executeIfReadyElseQueue = async (functionToCall, args) => { + if (isReady) { + log('calling', { functionToCall, args }); + await functionToCall(...args); + } else { + log('queueing', { functionToCall, args }); + executeQueue.push(async()=>await functionToCall(...args)); + } +}; + @@ -183,9 +198,23 @@ const init = async () => { slash.init(); autoExec = new AutoExecuteHandler(settings); - await autoExec.handleStartup(); + eventSource.on(event_types.APP_READY, async()=>await finalizeInit()); }; -eventSource.on(event_types.APP_READY, init); +const finalizeInit = async () => { + log('executing startup'); + await autoExec.handleStartup(); + log('/executing startup'); + + log(`executing queue (${executeQueue.length} items)`); + while (executeQueue.length > 0) { + const func = executeQueue.shift(); + await func(); + } + log('/executing queue'); + isReady = true; + log('READY'); +}; +await init(); const onChatChanged = async (chatIdx) => { log('CHAT_CHANGED', chatIdx); @@ -199,14 +228,19 @@ const onChatChanged = async (chatIdx) => { await autoExec.handleChatChanged(); }; -eventSource.on(event_types.CHAT_CHANGED, onChatChanged); +eventSource.on(event_types.CHAT_CHANGED, (...args)=>executeIfReadyElseQueue(onChatChanged, args)); const onUserMessage = async () => { await autoExec.handleUser(); }; -eventSource.on(event_types.USER_MESSAGE_RENDERED, onUserMessage); +eventSource.on(event_types.USER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onUserMessage, args)); const onAiMessage = async () => { await autoExec.handleAi(); }; -eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, onAiMessage); +eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args)); + +const onGroupMemberDraft = async () => { + await autoExec.handleGroupMemberDraft(); +}; +eventSource.on(event_types.GROUP_MEMBER_DRAFTED, (...args) => executeIfReadyElseQueue(onGroupMemberDraft, args)); diff --git a/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js b/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js index 4d53b23dd..06656d51b 100644 --- a/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js +++ b/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js @@ -73,4 +73,13 @@ export class AutoExecuteHandler { ]; await this.performAutoExecute(qrList); } + + async handleGroupMemberDraft() { + if (!this.checkExecute()) return; + const qrList = [ + ...this.settings.config.setList.map(link=>link.set.qrList.filter(qr=>qr.executeOnGroupMemberDraft)).flat(), + ...(this.settings.chatConfig?.setList?.map(link=>link.set.qrList.filter(qr=>qr.executeOnGroupMemberDraft))?.flat() ?? []), + ]; + await this.performAutoExecute(qrList); + } } diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index e247aa940..a5ebb1f66 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -30,6 +30,7 @@ export class QuickReply { /**@type {Boolean}*/ executeOnUser = false; /**@type {Boolean}*/ executeOnAi = false; /**@type {Boolean}*/ executeOnChatChange = false; + /**@type {Boolean}*/ executeOnGroupMemberDraft = false; /**@type {Function}*/ onExecute; /**@type {Function}*/ onDelete; @@ -351,7 +352,13 @@ export class QuickReply { this.executeOnChatChange = executeOnChatChange.checked; this.updateContext(); }); - + /**@type {HTMLInputElement}*/ + const executeOnGroupMemberDraft = dom.querySelector('#qr--executeOnGroupMemberDraft'); + executeOnGroupMemberDraft.checked = this.executeOnGroupMemberDraft; + executeOnGroupMemberDraft.addEventListener('click', ()=>{ + this.executeOnGroupMemberDraft = executeOnGroupMemberDraft.checked; + this.updateContext(); + }); /**@type {HTMLElement}*/ const executeErrors = dom.querySelector('#qr--modal-executeErrors'); @@ -484,6 +491,7 @@ export class QuickReply { executeOnUser: this.executeOnUser, executeOnAi: this.executeOnAi, executeOnChatChange: this.executeOnChatChange, + executeOnGroupMemberDraft: this.executeOnGroupMemberDraft, }; } } diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 5a7ac617e..9ad225091 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -1,4 +1,5 @@ import { registerSlashCommand } from '../../../slash-commands.js'; +import { isTrueBoolean } from '../../../utils.js'; // eslint-disable-next-line no-unused-vars import { QuickReplyApi } from '../api/QuickReplyApi.js'; @@ -35,7 +36,8 @@ export class SlashCommandHandler { user - bool - auto execute on user message, e.g., user=true bot - bool - auto execute on AI message, e.g., bot=true load - bool - auto execute on chat load, e.g., load=true - title - bool - title / tooltip to be shown on button, e.g., title="My Fancy Button" + group - bool - auto execute on group member selection, e.g., group=true + title - string - title / tooltip to be shown on button, e.g., title="My Fancy Button" `.trim(); const qrUpdateArgs = ` newlabel - string - new text for the button, e.g. newlabel=MyRenamedButton @@ -90,14 +92,14 @@ export class SlashCommandHandler { toggleGlobalSet(name, args = {}) { try { - this.api.toggleGlobalSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.toggleGlobalSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } } addGlobalSet(name, args = {}) { try { - this.api.addGlobalSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.addGlobalSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } @@ -113,14 +115,14 @@ export class SlashCommandHandler { toggleChatSet(name, args = {}) { try { - this.api.toggleChatSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.toggleChatSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } } addChatSet(name, args = {}) { try { - this.api.addChatSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.addChatSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } @@ -142,11 +144,12 @@ export class SlashCommandHandler { { message: message ?? '', title: args.title, - isHidden: JSON.parse(args.hidden ?? 'false') === true, - executeOnStartup: JSON.parse(args.startup ?? 'false') === true, - executeOnUser: JSON.parse(args.user ?? 'false') === true, - executeOnAi: JSON.parse(args.bot ?? 'false') === true, - executeOnChatChange: JSON.parse(args.load ?? 'false') === true, + isHidden: isTrueBoolean(args.hidden), + executeOnStartup: isTrueBoolean(args.startup), + executeOnUser: isTrueBoolean(args.user), + executeOnAi: isTrueBoolean(args.bot), + executeOnChatChange: isTrueBoolean(args.load), + executeOnGroupMemberDraft: isTrueBoolean(args.group), }, ); } catch (ex) { @@ -162,11 +165,12 @@ export class SlashCommandHandler { newLabel: args.newlabel, message: (message ?? '').trim().length > 0 ? message : undefined, title: args.title, - isHidden: args.hidden, - executeOnStartup: args.startup, - executeOnUser: args.user, - executeOnAi: args.bot, - executeOnChatChange: args.load, + isHidden: args.hidden === undefined ? undefined : isTrueBoolean(args.hidden), + executeOnStartup: args.startup === undefined ? undefined : isTrueBoolean(args.startup), + executeOnUser: args.user === undefined ? undefined : isTrueBoolean(args.user), + executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot), + executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load), + executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group), }, ); } catch (ex) { @@ -188,7 +192,7 @@ export class SlashCommandHandler { args.set, args.label, name, - JSON.parse(args.chain ?? 'false') === true, + isTrueBoolean(args.chain), ); } catch (ex) { toastr.error(ex.message); @@ -215,9 +219,9 @@ export class SlashCommandHandler { this.api.createSet( args.name ?? name ?? '', { - disableSend: JSON.parse(args.nosend ?? 'false') === true, - placeBeforeInput: JSON.parse(args.before ?? 'false') === true, - injectInput: JSON.parse(args.inject ?? 'false') === true, + disableSend: isTrueBoolean(args.nosend), + placeBeforeInput: isTrueBoolean(args.before), + injectInput: isTrueBoolean(args.inject), }, ); } catch (ex) { @@ -229,9 +233,9 @@ export class SlashCommandHandler { this.api.updateSet( args.name ?? name ?? '', { - disableSend: args.nosend !== undefined ? JSON.parse(args.nosend ?? 'false') === true : undefined, - placeBeforeInput: args.before !== undefined ? JSON.parse(args.before ?? 'false') === true : undefined, - injectInput: args.inject !== undefined ? JSON.parse(args.inject ?? 'false') === true : undefined, + disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined, + placeBeforeInput: args.before !== undefined ? isTrueBoolean(args.before) : undefined, + injectInput: args.inject !== undefined ? isTrueBoolean(args.inject) : undefined, }, ); } catch (ex) { diff --git a/public/scripts/extensions/tts/alltalk.js b/public/scripts/extensions/tts/alltalk.js new file mode 100644 index 000000000..f70daf5ad --- /dev/null +++ b/public/scripts/extensions/tts/alltalk.js @@ -0,0 +1,833 @@ +import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js'; +import { saveTtsProviderSettings } from './index.js'; + +export { AllTalkTtsProvider }; + +class AllTalkTtsProvider { + //########// + // Config // + //########// + + settings = {}; + constructor() { + // Initialize with default settings if they are not already set + this.settings = { + provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', + language: this.settings.language || 'en', + voiceMap: this.settings.voiceMap || {}, + at_generation_method: this.settings.at_generation_method || 'standard_generation', + narrator_enabled: this.settings.narrator_enabled || 'false', + at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator', + narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav', + finetuned_model: this.settings.finetuned_model || 'false' + }; + // Separate property for dynamically updated settings from the server + this.dynamicSettings = { + modelsAvailable: [], + currentModel: '', + deepspeed_available: false, + deepSpeedEnabled: false, + lowVramEnabled: false, + }; + } + ready = false; + voices = []; + separator = '. '; + audioElement = document.createElement('audio'); + + languageLabels = { + 'Arabic': 'ar', + 'Brazilian Portuguese': 'pt', + 'Chinese': 'zh-cn', + 'Czech': 'cs', + 'Dutch': 'nl', + 'English': 'en', + 'French': 'fr', + 'German': 'de', + 'Italian': 'it', + 'Polish': 'pl', + 'Russian': 'ru', + 'Spanish': 'es', + 'Turkish': 'tr', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Hungarian': 'hu', + 'Hindi': 'hi', + }; + + get settingsHtml() { + let html = `
AllTalk Settings
`; + + html += `
+ +
+ + +
+
`; + + html += `
+ +
+ + +
+ +
+ + +
+ +
`; + + html += `
+
+ + +
+
+ + +
+
`; + + + html += `
+
+ + +
+ +
+ + +
+
`; + + + html += `
+
+ + +
+
+ + +
+
+ Status: Ready +
+
+ +
+
`; + + + html += `
+
+ AllTalk Config & Docs. +
+ +
+ AllTalk Website. +
+
`; + + html += `
+
+Text-generation-webui users - Uncheck Enable TTS in Text-generation-webui. +
+
`; + + return html; + } + + + //#################// + // Startup ST & AT // + //#################// + + async loadSettings(settings) { + updateStatus('Offline'); + + if (Object.keys(settings).length === 0) { + console.info('Using default AllTalk TTS Provider settings'); + } else { + // Populate settings with provided values, ignoring server-provided settings + for (const key in settings) { + if (key in this.settings) { + this.settings[key] = settings[key]; + } else { + console.debug(`Ignoring non-user-configurable setting: ${key}`); + } + } + } + + // Update UI elements to reflect the loaded settings + $('#at_server').val(this.settings.provider_endpoint); + $('#language_options').val(this.settings.language); + //$('#voicemap').val(this.settings.voiceMap); + $('#at_generation_method').val(this.settings.at_generation_method); + $('#at_narrator_enabled').val(this.settings.narrator_enabled); + $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside); + $('#narrator_voice').val(this.settings.narrator_voice_gen); + + console.debug('AllTalkTTS: Settings loaded'); + try { + // Check if TTS provider is ready + await this.checkReady(); + await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server + await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready + this.updateNarratorVoicesDropdown(); + this.updateLanguageDropdown(); + this.setupEventListeners(); + this.applySettingsToHTML(); + updateStatus('Ready'); + } catch (error) { + console.error("Error loading settings:", error); + updateStatus('Offline'); + } + } + + applySettingsToHTML() { + // Apply loaded settings or use defaults + const narratorVoiceSelect = document.getElementById('narrator_voice'); + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + const generationMethodSelect = document.getElementById('at_generation_method'); + this.settings.narrator_voice = this.settings.narrator_voice_gen; + // Apply settings to Narrator Voice dropdown + if (narratorVoiceSelect && this.settings.narrator_voice) { + narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', ''); + } + // Apply settings to AT Narrator Enabled dropdown + if (atNarratorSelect) { + // Sync the state with the checkbox in index.js + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + // Sync the state with the checkbox in index.js + if (this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (this.settings.narrator_enabledd) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + atNarratorSelect.value = this.settings.narrator_enabled.toString(); + this.settings.narrator_enabled = this.settings.narrator_enabled.toString(); + } + // Apply settings to the Language dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect && this.settings.language) { + languageSelect.value = this.settings.language; + } + // Apply settings to Text Not Inside dropdown + if (textNotInsideSelect && this.settings.text_not_inside) { + textNotInsideSelect.value = this.settings.text_not_inside; + this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; + } + // Apply settings to Generation Method dropdown + if (generationMethodSelect && this.settings.at_generation_method) { + generationMethodSelect.value = this.settings.at_generation_method; + } + // Additional logic to disable/enable dropdowns based on the selected generation method + const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; + if (isStreamingEnabled) { + // Disable certain dropdowns when streaming is enabled + if (atNarratorSelect) atNarratorSelect.disabled = true; + if (textNotInsideSelect) textNotInsideSelect.disabled = true; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = true; + } else { + // Enable dropdowns for standard generation + if (atNarratorSelect) atNarratorSelect.disabled = false; + if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled; + } + const modelSelect = document.getElementById('switch_model'); + if (this.settings.finetuned_model === 'true') { + const ftOption = document.createElement('option'); + ftOption.value = 'XTTSv2 FT'; + ftOption.textContent = 'XTTSv2 FT'; + modelSelect.appendChild(ftOption); + } + } + + //##############################// + // Check AT Server is Available // + //##############################// + + async checkReady() { + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/ready`); + // Check if the HTTP request was successful + if (!response.ok) { + throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`); + } + const statusText = await response.text(); + // Check if the response is 'Ready' + if (statusText === 'Ready') { + this.ready = true; // Set the ready flag to true + console.log('TTS service is ready.'); + } else { + this.ready = false; + console.log('TTS service is not ready.'); + } + } catch (error) { + console.error('Error checking TTS service readiness:', error); + this.ready = false; // Ensure ready flag is set to false in case of error + } + } + + //######################// + // Get Available Voices // + //######################// + + async fetchTtsVoiceObjects() { + const response = await fetch(`${this.settings.provider_endpoint}/api/voices`); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const voices = data.voices.map(filename => { + const voiceName = filename.replace('.wav', ''); + return { + name: voiceName, + voice_id: voiceName, + preview_url: null, // Preview URL will be dynamically generated + lang: 'en' // Default language + }; + }); + this.voices = voices; // Assign to the class property + return voices; // Also return this list + } + + //##########################################// + // Get Current AT Server Config & Update ST // + //##########################################// + + async updateSettingsFromServer() { + try { + // Fetch current settings + const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`); + if (!response.ok) { + throw new Error(`Failed to fetch current settings: ${response.statusText}`); + } + const currentSettings = await response.json(); + // Update internal settings + this.settings.modelsAvailable = currentSettings.models_available; + this.settings.currentModel = currentSettings.current_model_loaded; + this.settings.deepspeed_available = currentSettings.deepspeed_available; + this.settings.deepSpeedEnabled = currentSettings.deepspeed_status; + this.settings.lowVramEnabled = currentSettings.low_vram_status; + this.settings.finetuned_model = currentSettings.finetuned_model + // Update HTML elements + this.updateModelDropdown(); + this.updateCheckboxes(); + } catch (error) { + console.error(`Error updating settings from server: ${error}`); + } + } + + //###################################################// + // Get Current AT Server Config & Update ST (Models) // + //###################################################// + + updateModelDropdown() { + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + modelSelect.innerHTML = ''; // Clear existing options + this.settings.modelsAvailable.forEach(model => { + const option = document.createElement('option'); + option.value = model.model_name; + option.textContent = model.model_name; // Use model_name instead of name + option.selected = model.model_name === this.settings.currentModel; + modelSelect.appendChild(option); + }); + } + } + + //#######################################################// + // Get Current AT Server Config & Update ST (DS and LVR) // + //#######################################################// + + updateCheckboxes() { + const deepspeedCheckbox = document.getElementById('deepspeed'); + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled; + if (deepspeedCheckbox) { + deepspeedCheckbox.checked = this.settings.deepSpeedEnabled; + deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available + } + } + + //###############################################################// + // Get Current AT Server Config & Update ST (AT Narrator Voices) // + //###############################################################// + + updateNarratorVoicesDropdown() { + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect && this.voices) { + // Clear existing options + narratorVoiceSelect.innerHTML = ''; + // Add new options + for (let voice of this.voices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + narratorVoiceSelect.appendChild(option); + } + } + } + + //######################################################// + // Get Current AT Server Config & Update ST (Languages) // + //######################################################// + + updateLanguageDropdown() { + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + // Ensure default language is set + this.settings.language = this.settings.language; + + languageSelect.innerHTML = ''; + for (let language in this.languageLabels) { + const option = document.createElement('option'); + option.value = this.languageLabels[language]; + option.textContent = language; + if (this.languageLabels[language] === this.settings.language) { + option.selected = true; + } + languageSelect.appendChild(option); + } + } + } + + //########################################// + // Start AT TTS extenstion page listeners // + //########################################// + + setupEventListeners() { + + let debounceTimeout; + const debounceDelay = 500; // Milliseconds + + // Define the event handler function + const onModelSelectChange = async (event) => { + console.log("Model select change event triggered"); // Debugging statement + const selectedModel = event.target.value; + console.log(`Selected model: ${selectedModel}`); // Debugging statement + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { + method: 'POST' + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log("POST response data:", data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + + // Handle response or error + }; + + const debouncedModelSelectChange = (event) => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(() => { + onModelSelectChange(event); + }, debounceDelay); + }; + + // Switch Model Listener + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + // Remove the event listener if it was previously added + modelSelect.removeEventListener('change', debouncedModelSelectChange); + // Add the debounced event listener + modelSelect.addEventListener('change', debouncedModelSelectChange); + } + + // DeepSpeed Listener + const deepspeedCheckbox = document.getElementById('deepspeed'); + if (deepspeedCheckbox) { + deepspeedCheckbox.addEventListener('change', async (event) => { + const deepSpeedValue = event.target.checked ? 'True' : 'False'; + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, { + method: 'POST' + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log("POST response data:", data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Low VRAM Listener + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) { + lowVramCheckbox.addEventListener('change', async (event) => { + const lowVramValue = event.target.checked ? 'True' : 'False'; + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, { + method: 'POST' + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log("POST response data:", data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Narrator Voice Dropdown Listener + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect) { + narratorVoiceSelect.addEventListener('change', (event) => { + this.settings.narrator_voice_gen = `${event.target.value}.wav`; + this.onSettingsChange(); // Save the settings after change + }); + } + + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + if (textNotInsideSelect) { + textNotInsideSelect.addEventListener('change', (event) => { + this.settings.text_not_inside = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + // AT Narrator Dropdown Listener + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + + if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { + atNarratorSelect.addEventListener('change', (event) => { + const isNarratorEnabled = event.target.value === 'true'; + this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here + textNotInsideSelect.disabled = !isNarratorEnabled; + narratorVoiceSelect.disabled = !isNarratorEnabled; + + // Sync the state with the checkbox in index.js + if (isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (isNarratorEnabled) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + this.onSettingsChange(); // Save the settings after change + }); + } + + + // Event Listener for AT Generation Method Dropdown + const atGenerationMethodSelect = document.getElementById('at_generation_method'); + const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); + if (atGenerationMethodSelect) { + atGenerationMethodSelect.addEventListener('change', (event) => { + const selectedMethod = event.target.value; + + if (selectedMethod === 'streaming_enabled') { + // Disable and unselect AT Narrator + atNarratorEnabledSelect.disabled = true; + atNarratorEnabledSelect.value = 'false'; + textNotInsideSelect.disabled = true; + narratorVoiceSelect.disabled = true; + } else if (selectedMethod === 'standard_generation') { + // Enable AT Narrator + atNarratorEnabledSelect.disabled = false; + } + this.settings.at_generation_method = selectedMethod; // Update the setting here + this.onSettingsChange(); // Save the settings after change + }); + } + + // Listener for Language Dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + languageSelect.addEventListener('change', (event) => { + this.settings.language = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + // Listener for AllTalk Endpoint Input + const atServerInput = document.getElementById('at_server'); + if (atServerInput) { + atServerInput.addEventListener('input', (event) => { + this.settings.provider_endpoint = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + } + + //#############################// + // Store ST interface settings // + //#############################// + + onSettingsChange() { + // Update settings based on the UI elements + //this.settings.provider_endpoint = $('#at_server').val(); + this.settings.language = $('#language_options').val(); + //this.settings.voiceMap = $('#voicemap').val(); + this.settings.at_generation_method = $('#at_generation_method').val(); + this.settings.narrator_enabled = $('#at_narrator_enabled').val(); + this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val(); + this.settings.narrator_voice_gen = $('#narrator_voice').val(); + // Save the updated settings + saveTtsProviderSettings(); + } + + //#########################// + // ST Handle Reload button // + //#########################// + + async onRefreshClick() { + await this.checkReady(); // Check if the TTS provider is ready + await this.loadSettings(this.settings); // Reload the settings + // Additional actions as needed + } + + //##################// + // Preview AT Voice // + //##################// + + async previewTtsVoice(voiceName) { + try { + // Prepare data for POST request + const postData = new URLSearchParams(); + postData.append("voice", `${voiceName}.wav`); + // Making the POST request + const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: postData, + }); + if (!response.ok) { + const errorText = await response.text(); + console.error(`[previewTtsVoice] Error Response Text:`, errorText); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + // Assuming the server returns a URL to the .wav file + const data = await response.json(); + if (data.output_file_url) { + // Use an audio element to play the .wav file + const audioElement = new Audio(data.output_file_url); + audioElement.play().catch(e => console.error("Error playing audio:", e)); + } else { + console.warn("[previewTtsVoice] No output file URL received in the response"); + throw new Error("No output file URL received in the response"); + } + + } catch (error) { + console.error("[previewTtsVoice] Exception caught during preview generation:", error); + throw error; + } + } + + //#####################// + // Populate ST voices // + //#####################// + + async getVoice(voiceName, generatePreview = false) { + // Ensure this.voices is populated + if (this.voices.length === 0) { + // Fetch voice objects logic + } + // Find the object where the name matches voiceName + const match = this.voices.find(voice => voice.name === voiceName); + if (!match) { + // Error handling + } + // Generate preview URL only if requested + if (!match.preview_url && generatePreview) { + // Generate preview logic + } + return match; // Return the found voice object + } + + //##########################################// + // Generate TTS Streaming or call Standard // + //##########################################// + + async generateTts(inputText, voiceId) { + try { + if (this.settings.at_generation_method === 'streaming_enabled') { + // Construct the streaming URL + const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; + console.log("Streaming URL:", streamingUrl); + + // Return the streaming URL directly + return streamingUrl; + } else { + // For standard method + const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); + const audioResponse = await fetch(outputUrl); + if (!audioResponse.ok) { + throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); + } + return audioResponse; // Return the fetch response directly + } + } catch (error) { + console.error("Error in generateTts:", error); + throw error; + } + } + + + //####################// + // Generate Standard // + //####################// + + async fetchTtsGeneration(inputText, voiceId) { + // Prepare the request payload + const requestBody = new URLSearchParams({ + 'text_input': inputText, + 'text_filtering': "standard", + 'character_voice_gen': voiceId + ".wav", + 'narrator_enabled': this.settings.narrator_enabled, + 'narrator_voice_gen': this.settings.narrator_voice_gen + ".wav", + 'text_not_inside': this.settings.at_narrator_text_not_inside, + 'language': this.settings.language, + 'output_file_name': "st_output", + 'output_file_timestamp': "true", + 'autoplay': "false", + 'autoplay_volume': "0.8" + }).toString(); + + try { + const response = await doExtrasFetch( + `${this.settings.provider_endpoint}/api/tts-generate`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + }, + body: requestBody + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); + // toastr.error(response.statusText, 'TTS Generation Failed'); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const outputUrl = data.output_file_url; + return outputUrl; // Return only the output_file_url + } catch (error) { + console.error("[fetchTtsGeneration] Exception caught:", error); + throw error; // Rethrow the error for further handling + } + } +} + +//#########################// +// Update Status Messages // +//#########################// + +function updateStatus(message) { + const statusElement = document.getElementById('status_info'); + if (statusElement) { + statusElement.textContent = message; + switch (message) { + case 'Offline': + statusElement.style.color = 'red'; + break; + case 'Ready': + statusElement.style.color = 'lightgreen'; + break; + case 'Processing': + statusElement.style.color = 'blue'; + break; + case 'Error': + statusElement.style.color = 'red'; + break; + } + } +} \ No newline at end of file diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 1b4020a50..36a80ecc5 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -11,6 +11,7 @@ import { power_user } from '../../power-user.js'; import { registerSlashCommand } from '../../slash-commands.js'; import { OpenAITtsProvider } from './openai.js'; import { XTTSTtsProvider } from './xtts.js'; +import { AllTalkTtsProvider } from './alltalk.js'; export { talkingAnimation }; const UPDATE_INTERVAL = 1000; @@ -74,6 +75,7 @@ let ttsProviders = { Edge: EdgeTtsProvider, Novel: NovelTtsProvider, OpenAI: OpenAITtsProvider, + AllTalk: AllTalkTtsProvider, }; let ttsProvider; let ttsProviderName; @@ -516,9 +518,11 @@ async function processTtsQueue() { text = text.replace(/```.*?```/gs, '').trim(); } - text = extension_settings.tts.narrate_dialogues_only - ? text.replace(/\*[^*]*?(\*|$)/g, '').trim() // remove asterisks content - : text.replaceAll('*', '').trim(); // remove just the asterisks + if (!extension_settings.tts.pass_asterisks) { + text = extension_settings.tts.narrate_dialogues_only + ? text.replace(/\*[^*]*?(\*|$)/g, '').trim() // remove asterisks content + : text.replaceAll('*', '').trim(); // remove just the asterisks + } if (extension_settings.tts.narrate_quoted_only) { const special_quotes = /[“”]/g; // Extend this regex to include other special quotes @@ -600,6 +604,7 @@ function loadSettings() { $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); + $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); $('body').toggleClass('tts', extension_settings.tts.enabled); } @@ -678,6 +683,12 @@ function onSkipCodeblocksClick() { saveSettingsDebounced(); } +function onPassAsterisksClick() { + extension_settings.tts.pass_asterisks = !!$('#tts_pass_asterisks').prop('checked'); + saveSettingsDebounced(); + console.log("setting pass asterisks", extension_settings.tts.pass_asterisks) +} + //##############// // TTS Provider // //##############// @@ -995,6 +1006,10 @@ $(document).ready(function () { Skip codeblocks +
@@ -1016,6 +1031,7 @@ $(document).ready(function () { $('#tts_narrate_quoted').on('click', onNarrateQuotedClick); $('#tts_narrate_translated_only').on('click', onNarrateTranslatedOnlyClick); $('#tts_skip_codeblocks').on('click', onSkipCodeblocksClick); + $('#tts_pass_asterisks').on('click', onPassAsterisksClick); $('#tts_auto_generation').on('click', onAutoGenerationClick); $('#tts_narrate_user').on('click', onNarrateUserClick); $('#tts_voices').on('click', onTtsVoicesClick); diff --git a/public/scripts/extensions/tts/style.css b/public/scripts/extensions/tts/style.css index 0f4a2c70c..f92688715 100644 --- a/public/scripts/extensions/tts/style.css +++ b/public/scripts/extensions/tts/style.css @@ -88,3 +88,72 @@ gap: 5px; margin-bottom: 5px; } + +.at-settings-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.at-settings-option { + flex: 1; + margin: 0 10px; +} + +.at-endpoint-option { + flex: 1; + margin: 0 10px; + margin-right: 25px; + width: 38%; +} + +.at-website-row { + display: flex; + justify-content: start; + align-items: center; + margin-top: 10px; + margin-bottom: 10px; +} + +.at-website-option { + flex: 1; + margin-right: 10px; + margin-left: 10px; +} + +.at-settings-separator { + margin-top: 10px; + margin-bottom: 10px; + padding: 18x; + font-weight: bold; + border-top: 1px solid #e1e1e1; /* Grey line */ + border-bottom: 1px solid #e1e1e1; /* Grey line */ + text-align: center; +} + +.at-status-message { + flex: 1; + margin: 0 10px; +} + +.at-model-endpoint-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.at-model-option, .endpoint-option { + flex: 1; + margin: 0 10px; + margin-left: 10px; +} + +.at-endpoint-option { + width: 38%; +} + +#at-status_info { + color: lightgreen; +} \ No newline at end of file diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index bfd49f7c8..d43322e0b 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -729,6 +729,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { const generateType = type == 'swipe' || type == 'impersonate' || type == 'quiet' || type == 'continue' ? type : 'group_chat'; setCharacterId(chId); setCharacterName(characters[chId].name); + await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId); if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { // update indicator and scroll down diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index 05f7b8068..ed17866f8 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -83,7 +83,7 @@ function highlightDefaultPreset() { * Select context template if not already selected. * @param {string} preset Preset name. */ -function selectContextPreset(preset) { +export function selectContextPreset(preset) { // If context template is not already selected, select it if (preset !== power_user.context.preset) { $('#context_presets').val(preset).trigger('change'); diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 099b3d5f9..c16280491 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -190,6 +190,7 @@ const default_settings = { top_k_openai: 0, min_p_openai: 0, top_a_openai: 1, + repetition_penalty_openai: 1, stream_openai: false, openai_max_context: max_4k, openai_max_tokens: 300, @@ -257,6 +258,7 @@ const oai_settings = { top_k_openai: 0, min_p_openai: 0, top_a_openai: 1, + repetition_penalty_openai: 1, stream_openai: false, openai_max_context: max_4k, openai_max_tokens: 300, @@ -1605,6 +1607,7 @@ async function sendOpenAIRequest(type, messages, signal) { if (isOpenRouter) { generate_data['top_k'] = Number(oai_settings.top_k_openai); generate_data['min_p'] = Number(oai_settings.min_p_openai); + generate_data['repetition_penalty'] = Number(oai_settings.repetition_penalty_openai); generate_data['top_a'] = Number(oai_settings.top_a_openai); generate_data['use_fallback'] = oai_settings.openrouter_use_fallback; @@ -2369,6 +2372,7 @@ function loadOpenAISettings(data, settings) { oai_settings.top_k_openai = settings.top_k_openai ?? default_settings.top_k_openai; oai_settings.top_a_openai = settings.top_a_openai ?? default_settings.top_a_openai; oai_settings.min_p_openai = settings.min_p_openai ?? default_settings.min_p_openai; + oai_settings.repetition_penalty_openai = settings.repetition_penalty_openai ?? default_settings.repetition_penalty_openai; oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai; oai_settings.openai_max_context = settings.openai_max_context ?? default_settings.openai_max_context; oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens; @@ -2504,6 +2508,8 @@ function loadOpenAISettings(data, settings) { $('#top_a_counter_openai').val(Number(oai_settings.top_a_openai)); $('#min_p_openai').val(oai_settings.min_p_openai); $('#min_p_counter_openai').val(Number(oai_settings.min_p_openai)); + $('#repetition_penalty_openai').val(oai_settings.repetition_penalty_openai); + $('#repetition_penalty_counter_openai').val(Number(oai_settings.repetition_penalty_openai)); $('#seed_openai').val(oai_settings.seed); if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy; @@ -2650,6 +2656,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { top_k: settings.top_k_openai, top_a: settings.top_a_openai, min_p: settings.min_p_openai, + repetition_penalty: settings.repetition_penalty_openai, openai_max_context: settings.openai_max_context, openai_max_tokens: settings.openai_max_tokens, wrap_in_quotes: settings.wrap_in_quotes, @@ -3011,6 +3018,7 @@ function onSettingsPresetChange() { top_k: ['#top_k_openai', 'top_k_openai', false], top_a: ['#top_a_openai', 'top_a_openai', false], min_p: ['#min_p_openai', 'min_p_openai', false], + repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false], max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true], openai_model: ['#model_openai_select', 'openai_model', false], claude_model: ['#model_claude_select', 'claude_model', false], @@ -3747,6 +3755,12 @@ $(document).ready(async function () { saveSettingsDebounced(); }); + $('#repetition_penalty_openai').on('input', function () { + oai_settings.repetition_penalty_openai = Number($(this).val()); + $('#repetition_penalty_counter_openai').val(Number($(this).val())); + saveSettingsDebounced(); + }); + $('#openai_max_context').on('input', function () { oai_settings.openai_max_context = Number($(this).val()); $('#openai_max_context_counter').val(`${$(this).val()}`); diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index e1a1717b2..fd6ae3b9b 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -456,7 +456,7 @@ async function presetCommandCallback(_, name) { */ async function waitForConnection() { try { - await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); + await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100); } catch { console.log('Timeout waiting for API to connect'); } diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 2bbf275f6..ff9d6bd42 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -116,7 +116,7 @@ function addLocalVariable(name, value) { const parsedValue = JSON.parse(currentValue); if (Array.isArray(parsedValue)) { parsedValue.push(value); - setGlobalVariable(name, JSON.stringify(parsedValue)); + setLocalVariable(name, JSON.stringify(parsedValue)); return parsedValue; } } catch { diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index cef8b906d..7fc294e04 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -730,6 +730,10 @@ router.post('/generate', jsonParser, function (request, response) { bodyParams['top_a'] = request.body.top_a; } + if (request.body.repetition_penalty !== undefined) { + bodyParams['repetition_penalty'] = request.body.repetition_penalty; + } + if (request.body.use_fallback) { bodyParams['route'] = 'fallback'; }