"Bind preset to connection" toggle (#3999)

* Implement THE TOGGLE

* Don't force reconnect on preset switch if toggle off

* Don't clear custom models list either
This commit is contained in:
Cohee
2025-05-17 20:40:58 +03:00
committed by GitHub
parent 213a619b33
commit 864a733663
4 changed files with 171 additions and 95 deletions

View File

@ -498,3 +498,15 @@ label[for="trim_spaces"]:not(:has(input:checked)) small {
#banned_tokens_block_ooba:not(:has(#send_banned_tokens_textgenerationwebui:checked)) #banned_tokens_controls_ooba { #banned_tokens_block_ooba:not(:has(#send_banned_tokens_textgenerationwebui:checked)) #banned_tokens_controls_ooba {
filter: brightness(0.5); filter: brightness(0.5);
} }
#bind_preset_to_connection:checked ~ .toggleOff {
display: none;
}
#bind_preset_to_connection:not(:checked) ~ .toggleOn {
display: none;
}
label[for="bind_preset_to_connection"]:has(input:checked) {
color: var(--active);
}

View File

@ -176,6 +176,11 @@
</strong> </strong>
<div class="flex-container gap3px"> <div class="flex-container gap3px">
<label for="bind_preset_to_connection" class="margin0 menu_button menu_button_icon" title="Bind presets to API connections" data-i18n="[title]Bind presets to API connections">
<input id="bind_preset_to_connection" type="checkbox" class="displayNone" />
<i class="fa-fw fa-solid fa-link toggleOn"></i>
<i class="fa-fw fa-solid fa-link-slash toggleOff"></i>
</label>
<div id="import_oai_preset" class="margin0 menu_button menu_button_icon" title="Import preset" data-i18n="[title]Import preset"> <div id="import_oai_preset" class="margin0 menu_button menu_button_icon" title="Import preset" data-i18n="[title]Import preset">
<i class="fa-fw fa-solid fa-file-import"></i> <i class="fa-fw fa-solid fa-file-import"></i>
</div> </div>

View File

@ -71,7 +71,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { Popup, POPUP_RESULT } from './popup.js'; import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { ToolManager } from './tool-calling.js'; import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js'; import { accountStorage } from './util/AccountStorage.js';
@ -236,87 +236,87 @@ const sensitiveFields = [
]; ];
/** /**
* preset_name -> [selector, setting_name, is_checkbox] * preset_name -> [selector, setting_name, is_checkbox, is_connection]
* @type {Record<string, [string, string, boolean]>} * @type {Record<string, [string, string, boolean, boolean]>}
*/ */
export const settingsToUpdate = { export const settingsToUpdate = {
chat_completion_source: ['#chat_completion_source', 'chat_completion_source', false], chat_completion_source: ['#chat_completion_source', 'chat_completion_source', false, true],
temperature: ['#temp_openai', 'temp_openai', false], temperature: ['#temp_openai', 'temp_openai', false, false],
frequency_penalty: ['#freq_pen_openai', 'freq_pen_openai', false], frequency_penalty: ['#freq_pen_openai', 'freq_pen_openai', false, false],
presence_penalty: ['#pres_pen_openai', 'pres_pen_openai', false], presence_penalty: ['#pres_pen_openai', 'pres_pen_openai', false, false],
top_p: ['#top_p_openai', 'top_p_openai', false], top_p: ['#top_p_openai', 'top_p_openai', false, false],
top_k: ['#top_k_openai', 'top_k_openai', false], top_k: ['#top_k_openai', 'top_k_openai', false, false],
top_a: ['#top_a_openai', 'top_a_openai', false], top_a: ['#top_a_openai', 'top_a_openai', false, false],
min_p: ['#min_p_openai', 'min_p_openai', false], min_p: ['#min_p_openai', 'min_p_openai', false, false],
repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false], repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false],
max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true], max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false],
openai_model: ['#model_openai_select', 'openai_model', false], openai_model: ['#model_openai_select', 'openai_model', false, true],
claude_model: ['#model_claude_select', 'claude_model', false], claude_model: ['#model_claude_select', 'claude_model', false, true],
windowai_model: ['#model_windowai_select', 'windowai_model', false], windowai_model: ['#model_windowai_select', 'windowai_model', false, true],
openrouter_model: ['#model_openrouter_select', 'openrouter_model', false], openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true],
openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true], openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true],
openrouter_group_models: ['#openrouter_group_models', 'openrouter_group_models', false], openrouter_group_models: ['#openrouter_group_models', 'openrouter_group_models', false, true],
openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false], openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false, true],
openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false], openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true],
openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true], openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true],
openrouter_middleout: ['#openrouter_middleout', 'openrouter_middleout', false], openrouter_middleout: ['#openrouter_middleout', 'openrouter_middleout', false, true],
ai21_model: ['#model_ai21_select', 'ai21_model', false], ai21_model: ['#model_ai21_select', 'ai21_model', false, true],
mistralai_model: ['#model_mistralai_select', 'mistralai_model', false], mistralai_model: ['#model_mistralai_select', 'mistralai_model', false, true],
cohere_model: ['#model_cohere_select', 'cohere_model', false], cohere_model: ['#model_cohere_select', 'cohere_model', false, true],
perplexity_model: ['#model_perplexity_select', 'perplexity_model', false], perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
groq_model: ['#model_groq_select', 'groq_model', false], groq_model: ['#model_groq_select', 'groq_model', false, true],
nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false], nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],
deepseek_model: ['#model_deepseek_select', 'deepseek_model', false], deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false], zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false, true],
xai_model: ['#model_xai_select', 'xai_model', false], xai_model: ['#model_xai_select', 'xai_model', false, true],
pollinations_model: ['#model_pollinations_select', 'pollinations_model', false], pollinations_model: ['#model_pollinations_select', 'pollinations_model', false, true],
custom_model: ['#custom_model_id', 'custom_model', false], custom_model: ['#custom_model_id', 'custom_model', false, true],
custom_url: ['#custom_api_url_text', 'custom_url', false], custom_url: ['#custom_api_url_text', 'custom_url', false, true],
custom_include_body: ['#custom_include_body', 'custom_include_body', false], custom_include_body: ['#custom_include_body', 'custom_include_body', false, true],
custom_exclude_body: ['#custom_exclude_body', 'custom_exclude_body', false], custom_exclude_body: ['#custom_exclude_body', 'custom_exclude_body', false, true],
custom_include_headers: ['#custom_include_headers', 'custom_include_headers', false], custom_include_headers: ['#custom_include_headers', 'custom_include_headers', false, true],
custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false], custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false, true],
google_model: ['#model_google_select', 'google_model', false], google_model: ['#model_google_select', 'google_model', false, true],
openai_max_context: ['#openai_max_context', 'openai_max_context', false], openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false], openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true], wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],
names_behavior: ['#names_behavior', 'names_behavior', false], names_behavior: ['#names_behavior', 'names_behavior', false, false],
send_if_empty: ['#send_if_empty_textarea', 'send_if_empty', false], send_if_empty: ['#send_if_empty_textarea', 'send_if_empty', false, false],
impersonation_prompt: ['#impersonation_prompt_textarea', 'impersonation_prompt', false], impersonation_prompt: ['#impersonation_prompt_textarea', 'impersonation_prompt', false, false],
new_chat_prompt: ['#newchat_prompt_textarea', 'new_chat_prompt', false], new_chat_prompt: ['#newchat_prompt_textarea', 'new_chat_prompt', false, false],
new_group_chat_prompt: ['#newgroupchat_prompt_textarea', 'new_group_chat_prompt', false], new_group_chat_prompt: ['#newgroupchat_prompt_textarea', 'new_group_chat_prompt', false, false],
new_example_chat_prompt: ['#newexamplechat_prompt_textarea', 'new_example_chat_prompt', false], new_example_chat_prompt: ['#newexamplechat_prompt_textarea', 'new_example_chat_prompt', false, false],
continue_nudge_prompt: ['#continue_nudge_prompt_textarea', 'continue_nudge_prompt', false], continue_nudge_prompt: ['#continue_nudge_prompt_textarea', 'continue_nudge_prompt', false, false],
bias_preset_selected: ['#openai_logit_bias_preset', 'bias_preset_selected', false], bias_preset_selected: ['#openai_logit_bias_preset', 'bias_preset_selected', false, false],
reverse_proxy: ['#openai_reverse_proxy', 'reverse_proxy', false], reverse_proxy: ['#openai_reverse_proxy', 'reverse_proxy', false, true],
wi_format: ['#wi_format_textarea', 'wi_format', false], wi_format: ['#wi_format_textarea', 'wi_format', false, false],
scenario_format: ['#scenario_format_textarea', 'scenario_format', false], scenario_format: ['#scenario_format_textarea', 'scenario_format', false, false],
personality_format: ['#personality_format_textarea', 'personality_format', false], personality_format: ['#personality_format_textarea', 'personality_format', false, false],
group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false], group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false, false],
stream_openai: ['#stream_toggle', 'stream_openai', true], stream_openai: ['#stream_toggle', 'stream_openai', true, false],
prompts: ['', 'prompts', false], prompts: ['', 'prompts', false, false],
prompt_order: ['', 'prompt_order', false], prompt_order: ['', 'prompt_order', false, false],
api_url_scale: ['#api_url_scale', 'api_url_scale', false], api_url_scale: ['#api_url_scale', 'api_url_scale', false, true],
show_external_models: ['#openai_show_external_models', 'show_external_models', true], show_external_models: ['#openai_show_external_models', 'show_external_models', true, true],
proxy_password: ['#openai_proxy_password', 'proxy_password', false], proxy_password: ['#openai_proxy_password', 'proxy_password', false, true],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false], assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false, false],
assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false], assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false, false],
claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true], claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true, false],
use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true], use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true, false],
use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true], use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true, true],
squash_system_messages: ['#squash_system_messages', 'squash_system_messages', true], squash_system_messages: ['#squash_system_messages', 'squash_system_messages', true, false],
image_inlining: ['#openai_image_inlining', 'image_inlining', true], image_inlining: ['#openai_image_inlining', 'image_inlining', true, false],
inline_image_quality: ['#openai_inline_image_quality', 'inline_image_quality', false], inline_image_quality: ['#openai_inline_image_quality', 'inline_image_quality', false, false],
continue_prefill: ['#continue_prefill', 'continue_prefill', true], continue_prefill: ['#continue_prefill', 'continue_prefill', true, false],
continue_postfix: ['#continue_postfix', 'continue_postfix', false], continue_postfix: ['#continue_postfix', 'continue_postfix', false, false],
function_calling: ['#openai_function_calling', 'function_calling', true], function_calling: ['#openai_function_calling', 'function_calling', true, false],
show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true], show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false],
reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false], reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false],
enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true], enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true, false],
seed: ['#seed_openai', 'seed', false], seed: ['#seed_openai', 'seed', false, false],
n: ['#n_openai', 'n', false], n: ['#n_openai', 'n', false, false],
bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true], bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true, true],
request_images: ['#openai_request_images', 'request_images', true], request_images: ['#openai_request_images', 'request_images', true, false],
}; };
const default_settings = { const default_settings = {
@ -399,6 +399,7 @@ const default_settings = {
request_images: false, request_images: false,
seed: -1, seed: -1,
n: 1, n: 1,
bind_preset_to_connection: true,
}; };
const oai_settings = { const oai_settings = {
@ -481,6 +482,7 @@ const oai_settings = {
request_images: false, request_images: false,
seed: -1, seed: -1,
n: 1, n: 1,
bind_preset_to_connection: true,
}; };
export let proxies = [ export let proxies = [
@ -3395,6 +3397,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.continue_postfix = settings.continue_postfix ?? default_settings.continue_postfix; oai_settings.continue_postfix = settings.continue_postfix ?? default_settings.continue_postfix;
oai_settings.function_calling = settings.function_calling ?? default_settings.function_calling; oai_settings.function_calling = settings.function_calling ?? default_settings.function_calling;
oai_settings.openrouter_providers = settings.openrouter_providers ?? default_settings.openrouter_providers; oai_settings.openrouter_providers = settings.openrouter_providers ?? default_settings.openrouter_providers;
oai_settings.bind_preset_to_connection = settings.bind_preset_to_connection ?? default_settings.bind_preset_to_connection;
// Migrate from old settings // Migrate from old settings
if (settings.names_in_completion === true) { if (settings.names_in_completion === true) {
@ -3511,6 +3514,7 @@ function loadOpenAISettings(data, settings) {
$('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts); $('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts);
$('#openai_enable_web_search').prop('checked', oai_settings.enable_web_search); $('#openai_enable_web_search').prop('checked', oai_settings.enable_web_search);
$('#openai_request_images').prop('checked', oai_settings.request_images); $('#openai_request_images').prop('checked', oai_settings.request_images);
$('#bind_preset_to_connection').prop('checked', oai_settings.bind_preset_to_connection);
$('#openai_reasoning_effort').val(oai_settings.reasoning_effort); $('#openai_reasoning_effort').val(oai_settings.reasoning_effort);
$(`#openai_reasoning_effort option[value="${oai_settings.reasoning_effort}"]`).prop('selected', true); $(`#openai_reasoning_effort option[value="${oai_settings.reasoning_effort}"]`).prop('selected', true);
@ -4039,20 +4043,33 @@ async function onExportPresetClick() {
const preset = structuredClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]); const preset = structuredClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]);
const fieldValues = sensitiveFields.filter(field => preset[field]).map(field => `<b>${field}</b>: <code>${preset[field]}</code>`); const fieldValues = sensitiveFields.filter(field => preset[field]).map(field => `<b>${field}</b>: <code>${preset[field]}</code>`);
const shouldConfirm = fieldValues.length > 0; if (fieldValues.length > 0) {
const textHeader = t`Your preset contains proxy and/or custom endpoint settings.`; const textHeader = t`Your preset contains proxy and/or custom endpoint settings.`;
const textMessage = '<div>' + t`Do you want to remove these fields before exporting?` + `</div><br>${DOMPurify.sanitize(fieldValues.join('<br>'))}`; const textMessage = '<div>' + t`Do you want to remove these fields before exporting?` + `</div><br>${DOMPurify.sanitize(fieldValues.join('<br>'))}`;
const cancelButton = { text: 'Cancel', result: POPUP_RESULT.CANCELLED, appendAtEnd: true }; const cancelButton = { text: 'Cancel', result: POPUP_RESULT.CANCELLED, appendAtEnd: true };
const popupOptions = { customButtons: [cancelButton] }; const popupOptions = { customButtons: [cancelButton] };
const popupResult = await Popup.show.confirm(textHeader, textMessage, popupOptions); const popupResult = await Popup.show.confirm(textHeader, textMessage, popupOptions);
if (popupResult === POPUP_RESULT.CANCELLED) { if (popupResult === POPUP_RESULT.CANCELLED) {
console.log('Export cancelled by user'); console.log('Export cancelled by user');
return; return;
}
if (popupResult === POPUP_RESULT.AFFIRMATIVE) {
sensitiveFields.forEach(field => delete preset[field]);
}
} }
if (!shouldConfirm || popupResult === POPUP_RESULT.AFFIRMATIVE) { const exportConnectionTemplate = $(await renderTemplateAsync('exportPreset'));
sensitiveFields.forEach(field => delete preset[field]); await new Popup(exportConnectionTemplate, POPUP_TYPE.TEXT).show();
const removeConnectionData = exportConnectionTemplate.find('input[name="export_connection_data"]:checked').val() === 'false';
if (removeConnectionData) {
for (const [, [, settingName, , isConnection]] of Object.entries(settingsToUpdate)) {
if (isConnection) {
delete preset[settingName];
}
}
} }
await eventSource.emit(event_types.OAI_PRESET_EXPORT_READY, preset); await eventSource.emit(event_types.OAI_PRESET_EXPORT_READY, preset);
@ -4200,9 +4217,15 @@ function onSettingsPresetChange() {
savePreset: saveOpenAIPreset, savePreset: saveOpenAIPreset,
presetNameBefore: presetNameBefore, presetNameBefore: presetNameBefore,
}).finally(r => { }).finally(r => {
$('.model_custom_select').empty(); if (oai_settings.bind_preset_to_connection) {
$('.model_custom_select').empty();
}
for (const [key, [selector, setting, isCheckbox, isConnection]] of Object.entries(settingsToUpdate)) {
if (isConnection && !oai_settings.bind_preset_to_connection) {
continue;
}
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
if (preset[key] !== undefined) { if (preset[key] !== undefined) {
if (isCheckbox) { if (isCheckbox) {
updateCheckbox(selector, preset[key]); updateCheckbox(selector, preset[key]);
@ -4213,9 +4236,13 @@ function onSettingsPresetChange() {
} }
} }
$('#chat_completion_source').trigger('change'); // These cannot be changed via preset if unbound to connection
if (oai_settings.bind_preset_to_connection) {
$('#chat_completion_source').trigger('change');
$('#openrouter_providers_chat').trigger('change');
}
$('#openai_logit_bias_preset').trigger('change'); $('#openai_logit_bias_preset').trigger('change');
$('#openrouter_providers_chat').trigger('change');
saveSettingsDebounced(); saveSettingsDebounced();
eventSource.emit(event_types.OAI_PRESET_CHANGED_AFTER); eventSource.emit(event_types.OAI_PRESET_CHANGED_AFTER);
@ -5848,6 +5875,11 @@ export function initOpenAI() {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#bind_preset_to_connection').on('input', function () {
oai_settings.bind_preset_to_connection = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#api_button_openai').on('click', onConnectButtonClick); $('#api_button_openai').on('click', onConnectButtonClick);
$('#openai_reverse_proxy').on('input', onReverseProxyInput); $('#openai_reverse_proxy').on('input', onReverseProxyInput);
$('#model_openai_select').on('change', onModelChange); $('#model_openai_select').on('change', onModelChange);

View File

@ -0,0 +1,27 @@
<div class="flex-container flexFlowColumn marginBot10">
<h3 data-i18n="Do you want to export connection data with the preset?">
Do you want to export connection data with the preset?
</h3>
<div data-i18n="This includes the selected source, models, and other preferences set in the API Connections panel.">
This includes the selected source, models, and other preferences set in the API Connections panel.
</div>
<strong data-i18n="Your stored API keys are never exported.">
Your stored API keys are never exported.
</strong>
</div>
<div class="flex-container flexFlowColumn">
<label class="checkbox_label" for="export_connection_data_yes">
<input type="radio" id="export_connection_data_yes" name="export_connection_data" value="true">
<span data-i18n="Export connection data">
Export connection data
</span>
</label>
<label class="checkbox_label" for="export_connection_data_no">
<input type="radio" id="export_connection_data_no" name="export_connection_data" value="false" checked>
<span data-i18n="Do not export connection data">
Do not export connection data
</span>
</label>
</div>