Merge pull request #2901 from SillyTavern/connection-profile-omit Add ability to omit settings from Connection Profile
Signed| @@ -0,0 +1,12 @@ | ||
| 1 | +<div> | |
| 2 | + <h3>Included settings:</h3> | |
| 3 | + <div class="justifyLeft flex-container flexFlowColumn flexNoGap"> | |
| 4 | + {{#each settings}} | |
| 5 | + <label class="checkbox_label"> | |
| 6 | + <input type="checkbox" value="{{@key}}" name="exclude"{{#if this}} checked{{/if}}> | |
| 7 | + <span>{{@key}}</span> | |
| 8 | + </label> | |
| 9 | + {{/each}} | |
| 10 | + </div> | |
| 11 | + <h3>Profile name:</h3> | |
| 12 | +</div> | |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js'; |
| 2 | 2 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | 3 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 4 | 4 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 5 | 5 | import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js'; |
| 6 | 6 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| @@ -135,6 +135,7 @@ const profilesProvider = () => [ | ||
| 135 | 135 | * @property {string} [context] Context Template |
| 136 | 136 | * @property {string} [instruct-state] Instruct Mode |
| 137 | 137 | * @property {string} [tokenizer] Tokenizer |
| 138 | + * @property {string[]} [exclude] Commands to exclude | |
| 138 | 139 | */ |
| 139 | 140 | |
| 140 | 141 | /** |
| @@ -171,8 +172,13 @@ function findProfileByName(value) { | ||
| 171 | 172 | async function readProfileFromCommands(mode, profile, cleanUp = false) { |
| 172 | 173 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; |
| 173 | 174 | const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS; |
| 175 | + const excludeList = Array.isArray(profile.exclude) ? profile.exclude : []; | |
| 174 | 176 | for (const command of commands) { |
| 175 | 177 | try { |
| 178 | + if (excludeList.includes(command)) { | |
| 179 | + continue; | |
| 180 | + } | |
| 181 | + | |
| 176 | 182 | const args = getNamedArguments(); |
| 177 | 183 | const result = await SlashCommandParser.commands[command].callback(args, ''); |
| 178 | 184 | if (result) { |
| @@ -208,15 +214,37 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) { | ||
| 208 | 214 | async function createConnectionProfile(forceName = null) { |
| 209 | 215 | const mode = main_api === 'openai' ? 'cc' : 'tc'; |
| 210 | 216 | const id = uuidv4(); |
| 217 | + /** @type {ConnectionProfile} */ | |
| 211 | 218 | const profile = { |
| 212 | 219 | id, |
| 213 | 220 | mode, |
| 221 | + exclude: [], | |
| 214 | 222 | }; |
| 215 | 223 | |
| 216 | 224 | await readProfileFromCommands(mode, profile); |
| 217 | 225 | |
| 218 | 226 | const profileForDisplay = makeFancyProfile(profile); |
| 219 | 227 | const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay })); |
| 228 | + template.find('input[name="exclude"]').on('input', function () { | |
| 229 | + const fancyName = String($(this).val()); | |
| 230 | + const keyName = Object.entries(FANCY_NAMES).find(x => x[1] === fancyName)?.[0]; | |
| 231 | + if (!keyName) { | |
| 232 | + console.warn('Key not found for fancy name:', fancyName); | |
| 233 | + return; | |
| 234 | + } | |
| 235 | + | |
| 236 | + if (!Array.isArray(profile.exclude)) { | |
| 237 | + profile.exclude = []; | |
| 238 | + } | |
| 239 | + | |
| 240 | + const excludeState = !$(this).prop('checked'); | |
| 241 | + if (excludeState) { | |
| 242 | + profile.exclude.push(keyName); | |
| 243 | + } else { | |
| 244 | + const index = profile.exclude.indexOf(keyName); | |
| 245 | + index !== -1 && profile.exclude.splice(index, 1); | |
| 246 | + } | |
| 247 | + }); | |
| 220 | 248 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); |
| 221 | 249 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); |
| 222 | 250 | const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 }); |
| @@ -230,7 +258,13 @@ async function createConnectionProfile(forceName = null) { | ||
| 230 | 258 | return null; |
| 231 | 259 | } |
| 232 | 260 | |
| 233 | - profile.name = name; | |
| 261 | + if (Array.isArray(profile.exclude)) { | |
| 262 | + for (const command of profile.exclude) { | |
| 263 | + delete profile[command]; | |
| 264 | + } | |
| 265 | + } | |
| 266 | + | |
| 267 | + profile.name = String(name); | |
| 234 | 268 | return profile; |
| 235 | 269 | } |
| 236 | 270 | |
| @@ -357,7 +391,11 @@ async function renderDetailsContent(detailsContent) { | ||
| 357 | 391 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); |
| 358 | 392 | if (profile) { |
| 359 | 393 | const profileForDisplay = makeFancyProfile(profile); |
| 360 | 394 | const templatetemplateParams = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay }); |
| 395 | + if (Array.isArray(profile.exclude) && profile.exclude.length > 0) { | |
| 396 | + templateParams.omitted = profile.exclude.map(e => FANCY_NAMES[e]).join(', '); | |
| 397 | + } | |
| 398 | + const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', templateParams); | |
| 361 | 399 | detailsContent.innerHTML = template; |
| 362 | 400 | } else { |
| 363 | 401 | detailsContent.textContent = 'No profile selected'; |
| @@ -472,8 +510,8 @@ async function renderDetailsContent(detailsContent) { | ||
| 472 | 510 | await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE); |
| 473 | 511 | }); |
| 474 | 512 | |
| 475 | 513 | const renameButtoneditButton = document.getElementById('rename_connection_profileedit_connection_profile'); |
| 476 | 514 | renameButtoneditButton.addEventListener('click', async () => { |
| 477 | 515 | const selectedProfile = extension_settings.connectionManager.selectedProfile; |
| 478 | 516 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); |
| 479 | 517 | if (!profile) { |
| @@ -481,20 +519,62 @@ async function renderDetailsContent(detailsContent) { | ||
| 481 | 519 | return; |
| 482 | 520 | } |
| 483 | 521 | |
| 484 | - const newName = await Popup.show.input('Enter a new name', null, profile.name, { rows: 2 }); | |
| 522 | + if (!Array.isArray(profile.exclude)) { | |
| 523 | + profile.exclude = []; | |
| 524 | + } | |
| 525 | + | |
| 526 | + let saveChanges = false; | |
| 527 | + const commands = profile.mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; | |
| 528 | + const settings = commands.reduce((acc, command) => { | |
| 529 | + const fancyName = FANCY_NAMES[command]; | |
| 530 | + acc[fancyName] = !profile.exclude.includes(command); | |
| 531 | + return acc; | |
| 532 | + }, {}); | |
| 533 | + const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings })); | |
| 534 | + const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, { | |
| 535 | + customButtons: [{ | |
| 536 | + text: 'Save and Update', | |
| 537 | + classes: ['popup-button-ok'], | |
| 538 | + result: POPUP_RESULT.AFFIRMATIVE, | |
| 539 | + action: () => { | |
| 540 | + saveChanges = true; | |
| 541 | + }, | |
| 542 | + }], | |
| 543 | + }); | |
| 544 | + | |
| 485 | 545 | if (!newName) { |
| 486 | 546 | return; |
| 487 | 547 | } |
| 488 | 548 | |
| 489 | 549 | if (profile.name !== newName && extension_settings.connectionManager.profiles.some(p => p.name === newName)) { |
| 490 | 550 | toastr.error('A profile with the same name already exists.'); |
| 491 | 551 | return; |
| 492 | 552 | } |
| 493 | 553 | |
| 494 | - profile.name = newName; | |
| 554 | + const newExcludeList = template.find('input[name="exclude"]:not(:checked)').map(function () { | |
| 555 | + return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0]; | |
| 556 | + }).get(); | |
| 557 | + | |
| 558 | + if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) { | |
| 559 | + profile.exclude = newExcludeList; | |
| 560 | + for (const command of newExcludeList) { | |
| 561 | + delete profile[command]; | |
| 562 | + } | |
| 563 | + if (saveChanges) { | |
| 564 | + await updateConnectionProfile(profile); | |
| 565 | + } else { | |
| 566 | + toastr.info('Press "Update" to record them into the profile.', 'Included settings list updated'); | |
| 567 | + } | |
| 568 | + } | |
| 569 | + | |
| 570 | + if (profile.name !== newName) { | |
| 571 | + toastr.success('Connection profile renamed.'); | |
| 572 | + profile.name = String(newName); | |
| 573 | + } | |
| 574 | + | |
| 495 | 575 | saveSettingsDebounced(); |
| 496 | 576 | renderConnectionProfiles(profiles); |
| 497 | - toastr.success('Connection profile renamed', '', { timeOut: 1500 }); | |
| 577 | + await renderDetailsContent(detailsContent); | |
| 498 | 578 | }); |
| 499 | 579 | |
| 500 | 580 | /** @type {HTMLElement} */ |
| @@ -2,11 +2,20 @@ | ||
| 2 | 2 | <h2 data-i18n="Creating a Connection Profile"> |
| 3 | 3 | Creating a Connection Profile |
| 4 | 4 | </h2> |
| 5 | 5 | <uldiv class="justifyLeft flex-container flexFlowColumn flexNoGap"> |
| 6 | 6 | {{#each profile}} |
| 7 | - <li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li> | |
| 7 | + <label class="checkbox_label"> | |
| 8 | + <input type="checkbox" value="{{@key}}" name="exclude" checked> | |
| 9 | + <span><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</span> | |
| 10 | + </label> | |
| 8 | 11 | {{/each}} |
| 9 | 12 | </uldiv> |
| 13 | + <div class="marginTop5"> | |
| 14 | + <small> | |
| 15 | + <b>Hint:</b> | |
| 16 | + <i>Click on the setting name to omit it from the profile.</i> | |
| 17 | + </small> | |
| 18 | + </div> | |
| 10 | 19 | <h3 data-i18n="Enter a name:"> |
| 11 | 20 | Enter a name: |
| 12 | 21 | </h3> |
| @@ -13,7 +13,7 @@ | ||
| 13 | 13 | <i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i> |
| 14 | 14 | <i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i> |
| 15 | 15 | <i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i> |
| 16 | 16 | <i id="rename_connection_profileedit_connection_profile" class="menu_button fa-solid fa-pencil" title="RenameEdit a connection profile" data-i18n="[title]RenameEdit a connection profile"></i> |
| 17 | 17 | <i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i> |
| 18 | 18 | <i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i> |
| 19 | 19 | </div> |
| @@ -3,3 +3,8 @@ | ||
| 3 | 3 | <li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li> |
| 4 | 4 | {{/each}} |
| 5 | 5 | </ul> |
| 6 | +{{#if omitted}} | |
| 7 | +<div class="margin5"> | |
| 8 | + <strong data-i18n="Omitted Settings:">Omitted Settings:</strong> <span>{{omitted}}</span> | |
| 9 | +</div> | |
| 10 | +{{/if}} | |