Add ability to omit settings from Connection Profiles
| @@ -0,0 +1,16 @@ | |||
| 1 | <div> | ||
| 2 | <h3>Included settings:</h3> | ||
| 3 | <div class="justifyLeft flex-container flexFlowColumn flexNoGap"> | ||
| 4 | {{#each settings}} | ||
| 5 | <label class="checkbox_label"> | ||
| 6 | {{#if this}} | ||
| 7 | <input type="checkbox" value="{{@key}}" name="exclude" checked> | ||
| 8 | {{else}} | ||
| 9 | <input type="checkbox" value="{{@key}}" name="exclude"> | ||
| 10 | {{/if}} | ||
| 11 | <span>{{@key}}</span> | ||
| 12 | </label> | ||
| 13 | {{/each}} | ||
| 14 | </div> | ||
| 15 | <h3>Profile name:</h3> | ||
| 16 | </div> | ||
| @@ -135,6 +135,7 @@ const profilesProvider = () => [ | |||
| 135 | * @property {string} [context] Context Template | 135 | * @property {string} [context] Context Template |
| 136 | * @property {string} [instruct-state] Instruct Mode | 136 | * @property {string} [instruct-state] Instruct Mode |
| 137 | * @property {string} [tokenizer] Tokenizer | 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 | async function readProfileFromCommands(mode, profile, cleanUp = false) { | 172 | async function readProfileFromCommands(mode, profile, cleanUp = false) { |
| 172 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; | 173 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; |
| 173 | const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS; | 174 | const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS; |
| 175 | const excludeList = Array.isArray(profile.exclude) ? profile.exclude : []; | ||
| 174 | for (const command of commands) { | 176 | for (const command of commands) { |
| 175 | try { | 177 | try { |
| 178 | if (excludeList.includes(command)) { | ||
| 179 | continue; | ||
| 180 | } | ||
| 181 | |||
| 176 | const args = getNamedArguments(); | 182 | const args = getNamedArguments(); |
| 177 | const result = await SlashCommandParser.commands[command].callback(args, ''); | 183 | const result = await SlashCommandParser.commands[command].callback(args, ''); |
| 178 | if (result) { | 184 | if (result) { |
| @@ -208,15 +214,36 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) { | |||
| 208 | async function createConnectionProfile(forceName = null) { | 214 | async function createConnectionProfile(forceName = null) { |
| 209 | const mode = main_api === 'openai' ? 'cc' : 'tc'; | 215 | const mode = main_api === 'openai' ? 'cc' : 'tc'; |
| 210 | const id = uuidv4(); | 216 | const id = uuidv4(); |
| 217 | /** @type {ConnectionProfile} */ | ||
| 211 | const profile = { | 218 | const profile = { |
| 212 | id, | 219 | id, |
| 213 | mode, | 220 | mode, |
| 221 | exclude: [], | ||
| 214 | }; | 222 | }; |
| 215 | 223 | ||
| 216 | await readProfileFromCommands(mode, profile); | 224 | await readProfileFromCommands(mode, profile); |
| 217 | 225 | ||
| 218 | const profileForDisplay = makeFancyProfile(profile); | 226 | const profileForDisplay = makeFancyProfile(profile); |
| 219 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay }); | 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 | profile.exclude.splice(profile.exclude.indexOf(keyName), 1); | ||
| 245 | } | ||
| 246 | }); | ||
| 220 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); | 247 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); |
| 221 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); | 248 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); |
| 222 | const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 }); | 249 | const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 }); |
| @@ -230,7 +257,13 @@ async function createConnectionProfile(forceName = null) { | |||
| 230 | return null; | 257 | return null; |
| 231 | } | 258 | } |
| 232 | 259 | ||
| 233 | profile.name = name; | 260 | if (Array.isArray(profile.exclude)) { |
| 261 | for (const command of profile.exclude) { | ||
| 262 | delete profile[command]; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | profile.name = String(name); | ||
| 234 | return profile; | 267 | return profile; |
| 235 | } | 268 | } |
| 236 | 269 | ||
| @@ -357,6 +390,9 @@ async function renderDetailsContent(detailsContent) { | |||
| 357 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); | 390 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); |
| 358 | if (profile) { | 391 | if (profile) { |
| 359 | const profileForDisplay = makeFancyProfile(profile); | 392 | const profileForDisplay = makeFancyProfile(profile); |
| 393 | if (Array.isArray(profile.exclude) && profile.exclude.length > 0) { | ||
| 394 | profileForDisplay['Omitted Settings'] = profile.exclude.map(e => FANCY_NAMES[e]).join(', '); | ||
| 395 | } | ||
| 360 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay }); | 396 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay }); |
| 361 | detailsContent.innerHTML = template; | 397 | detailsContent.innerHTML = template; |
| 362 | } else { | 398 | } else { |
| @@ -472,8 +508,8 @@ async function renderDetailsContent(detailsContent) { | |||
| 472 | await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE); | 508 | await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE); |
| 473 | }); | 509 | }); |
| 474 | 510 | ||
| 475 | const renameButton = document.getElementById('rename_connection_profile'); | 511 | const editButton = document.getElementById('edit_connection_profile'); |
| 476 | renameButton.addEventListener('click', async () => { | 512 | editButton.addEventListener('click', async () => { |
| 477 | const selectedProfile = extension_settings.connectionManager.selectedProfile; | 513 | const selectedProfile = extension_settings.connectionManager.selectedProfile; |
| 478 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); | 514 | const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile); |
| 479 | if (!profile) { | 515 | if (!profile) { |
| @@ -481,20 +517,48 @@ async function renderDetailsContent(detailsContent) { | |||
| 481 | return; | 517 | return; |
| 482 | } | 518 | } |
| 483 | 519 | ||
| 484 | const newName = await Popup.show.input('Enter a new name', null, profile.name, { rows: 2 }); | 520 | if (!Array.isArray(profile.exclude)) { |
| 521 | profile.exclude = []; | ||
| 522 | } | ||
| 523 | |||
| 524 | const commands = profile.mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; | ||
| 525 | const settings = commands.reduce((acc, command) => { | ||
| 526 | const fancyName = FANCY_NAMES[command]; | ||
| 527 | acc[fancyName] = !profile.exclude.includes(command); | ||
| 528 | return acc; | ||
| 529 | }, {}); | ||
| 530 | const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings })); | ||
| 531 | const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name); | ||
| 532 | |||
| 485 | if (!newName) { | 533 | if (!newName) { |
| 486 | return; | 534 | return; |
| 487 | } | 535 | } |
| 488 | 536 | ||
| 489 | if (extension_settings.connectionManager.profiles.some(p => p.name === newName)) { | 537 | if (profile.name !== newName && extension_settings.connectionManager.profiles.some(p => p.name === newName)) { |
| 490 | toastr.error('A profile with the same name already exists.'); | 538 | toastr.error('A profile with the same name already exists.'); |
| 491 | return; | 539 | return; |
| 492 | } | 540 | } |
| 493 | 541 | ||
| 494 | profile.name = newName; | 542 | const newExcludeList = template.find('input[name="exclude"]:not(:checked)').map(function () { |
| 543 | return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0]; | ||
| 544 | }).get(); | ||
| 545 | |||
| 546 | if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) { | ||
| 547 | profile.exclude = newExcludeList; | ||
| 548 | for (const command of newExcludeList) { | ||
| 549 | delete profile[command]; | ||
| 550 | } | ||
| 551 | toastr.info('Press "Update" to record them into the profile.', 'Included settings list updated'); | ||
| 552 | } | ||
| 553 | |||
| 554 | if (profile.name !== newName) { | ||
| 555 | toastr.success('Connection profile renamed.'); | ||
| 556 | profile.name = String(newName); | ||
| 557 | } | ||
| 558 | |||
| 495 | saveSettingsDebounced(); | 559 | saveSettingsDebounced(); |
| 496 | renderConnectionProfiles(profiles); | 560 | renderConnectionProfiles(profiles); |
| 497 | toastr.success('Connection profile renamed', '', { timeOut: 1500 }); | 561 | await renderDetailsContent(detailsContent); |
| 498 | }); | 562 | }); |
| 499 | 563 | ||
| 500 | /** @type {HTMLElement} */ | 564 | /** @type {HTMLElement} */ |
| @@ -2,11 +2,18 @@ | |||
| 2 | <h2 data-i18n="Creating a Connection Profile"> | 2 | <h2 data-i18n="Creating a Connection Profile"> |
| 3 | Creating a Connection Profile | 3 | Creating a Connection Profile |
| 4 | </h2> | 4 | </h2> |
| 5 | <ul class="justifyLeft"> | 5 | <div class="justifyLeft flex-container flexFlowColumn flexNoGap"> |
| 6 | {{#each profile}} | 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 | {{/each}} | 11 | {{/each}} |
| 9 | </ul> | 12 | </div> |
| 13 | <small> | ||
| 14 | <b>Hint:</b> | ||
| 15 | <i>Click on the setting name to omit it from the profile.</i> | ||
| 16 | </small> | ||
| 10 | <h3 data-i18n="Enter a name:"> | 17 | <h3 data-i18n="Enter a name:"> |
| 11 | Enter a name: | 18 | Enter a name: |
| 12 | </h3> | 19 | </h3> |
| @@ -13,7 +13,7 @@ | |||
| 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> | 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 | <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> | 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 | <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> | 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 | <i id="rename_connection_profile" class="menu_button fa-solid fa-pencil" title="Rename a connection profile" data-i18n="[title]Rename a connection profile"></i> | 16 | <i id="edit_connection_profile" class="menu_button fa-solid fa-pencil" title="Edit a connection profile" data-i18n="[title]Edit a connection profile"></i> |
| 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> | 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 | <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> | 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 | </div> | 19 | </div> |