Preset Manager: Add methods for reading/writing custom data (#4246) * Preset Manager: Add methods for reading/writing custom data * Fix loading novel extensions on load * Move ensurePlainObject to utils * Init empty object in convertNovelPreset
Signed| @@ -44,6 +44,7 @@ export const kai_settings = { | |||
| 44 | seed: -1, | 44 | seed: -1, |
| 45 | api_server: '', | 45 | api_server: '', |
| 46 | preset_settings: 'gui', | 46 | preset_settings: 'gui', |
| 47 | extensions: {}, | ||
| 47 | }; | 48 | }; |
| 48 | 49 | ||
| 49 | /** | 50 | /** |
| @@ -130,6 +131,11 @@ export function loadKoboldSettings(data, preset, settings) { | |||
| 130 | 131 | ||
| 131 | function loadKoboldSettingsFromPreset(preset) { | 132 | function loadKoboldSettingsFromPreset(preset) { |
| 132 | for (const name of Object.keys(kai_settings)) { | 133 | for (const name of Object.keys(kai_settings)) { |
| 134 | if (name === 'extensions') { | ||
| 135 | kai_settings.extensions = preset.extensions || {}; | ||
| 136 | continue; | ||
| 137 | } | ||
| 138 | |||
| 133 | const value = preset[name] ?? defaultValues[name]; | 139 | const value = preset[name] ?? defaultValues[name]; |
| 134 | const slider = sliders.find(x => x.name === name); | 140 | const slider = sliders.find(x => x.name === name); |
| 135 | 141 | ||
| @@ -56,6 +56,7 @@ export const nai_settings = { | |||
| 56 | banned_tokens: '', | 56 | banned_tokens: '', |
| 57 | order: default_order, | 57 | order: default_order, |
| 58 | logit_bias: [], | 58 | logit_bias: [], |
| 59 | extensions: {}, | ||
| 59 | }; | 60 | }; |
| 60 | 61 | ||
| 61 | const nai_tiers = { | 62 | const nai_tiers = { |
| @@ -140,6 +141,7 @@ export function convertNovelPreset(data) { | |||
| 140 | math1_quad_entropy_scale: data.parameters.math1_quad_entropy_scale, | 141 | math1_quad_entropy_scale: data.parameters.math1_quad_entropy_scale, |
| 141 | min_p: data.parameters.min_p, | 142 | min_p: data.parameters.min_p, |
| 142 | order: Array.isArray(data.parameters.order) ? data.parameters.order.filter(s => s.enabled && Object.keys(samplers).includes(s.id)).map(s => samplers[s.id]) : default_order, | 143 | order: Array.isArray(data.parameters.order) ? data.parameters.order.filter(s => s.enabled && Object.keys(samplers).includes(s.id)).map(s => samplers[s.id]) : default_order, |
| 144 | extensions: {}, | ||
| 143 | }; | 145 | }; |
| 144 | } | 146 | } |
| 145 | 147 | ||
| @@ -205,6 +207,7 @@ export function loadNovelPreset(preset) { | |||
| 205 | nai_settings.math1_temp = preset.math1_temp || 1; | 207 | nai_settings.math1_temp = preset.math1_temp || 1; |
| 206 | nai_settings.math1_quad = preset.math1_quad || 0; | 208 | nai_settings.math1_quad = preset.math1_quad || 0; |
| 207 | nai_settings.math1_quad_entropy_scale = preset.math1_quad_entropy_scale || 0; | 209 | nai_settings.math1_quad_entropy_scale = preset.math1_quad_entropy_scale || 0; |
| 210 | nai_settings.extensions = preset.extensions || {}; | ||
| 208 | loadNovelSettingsUi(nai_settings); | 211 | loadNovelSettingsUi(nai_settings); |
| 209 | } | 212 | } |
| 210 | 213 | ||
| @@ -258,6 +261,7 @@ export function loadNovelSettings(data, settings) { | |||
| 258 | nai_settings.math1_temp = settings.math1_temp || 1; | 261 | nai_settings.math1_temp = settings.math1_temp || 1; |
| 259 | nai_settings.math1_quad = settings.math1_quad || 0; | 262 | nai_settings.math1_quad = settings.math1_quad || 0; |
| 260 | nai_settings.math1_quad_entropy_scale = settings.math1_quad_entropy_scale || 0; | 263 | nai_settings.math1_quad_entropy_scale = settings.math1_quad_entropy_scale || 0; |
| 264 | nai_settings.extensions = settings.extensions || {}; | ||
| 261 | loadNovelSettingsUi(nai_settings); | 265 | loadNovelSettingsUi(nai_settings); |
| 262 | } | 266 | } |
| 263 | 267 | ||
| @@ -332,6 +332,7 @@ export const settingsToUpdate = { | |||
| 332 | n: ['#n_openai', 'n', false, false], | 332 | n: ['#n_openai', 'n', false, false], |
| 333 | bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true, true], | 333 | bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true, true], |
| 334 | request_images: ['#openai_request_images', 'request_images', true, false], | 334 | request_images: ['#openai_request_images', 'request_images', true, false], |
| 335 | extensions: ['#NULL_SELECTOR', 'extensions', false, false], | ||
| 335 | }; | 336 | }; |
| 336 | 337 | ||
| 337 | const default_settings = { | 338 | const default_settings = { |
| @@ -421,6 +422,7 @@ const default_settings = { | |||
| 421 | seed: -1, | 422 | seed: -1, |
| 422 | n: 1, | 423 | n: 1, |
| 423 | bind_preset_to_connection: true, | 424 | bind_preset_to_connection: true, |
| 425 | extensions: {}, | ||
| 424 | }; | 426 | }; |
| 425 | 427 | ||
| 426 | const oai_settings = { | 428 | const oai_settings = { |
| @@ -510,6 +512,7 @@ const oai_settings = { | |||
| 510 | seed: -1, | 512 | seed: -1, |
| 511 | n: 1, | 513 | n: 1, |
| 512 | bind_preset_to_connection: true, | 514 | bind_preset_to_connection: true, |
| 515 | extensions: {}, | ||
| 513 | }; | 516 | }; |
| 514 | 517 | ||
| 515 | export let proxies = [ | 518 | export let proxies = [ |
| @@ -3600,6 +3603,7 @@ function loadOpenAISettings(data, settings) { | |||
| 3600 | oai_settings.function_calling = settings.function_calling ?? default_settings.function_calling; | 3603 | oai_settings.function_calling = settings.function_calling ?? default_settings.function_calling; |
| 3601 | oai_settings.openrouter_providers = settings.openrouter_providers ?? default_settings.openrouter_providers; | 3604 | oai_settings.openrouter_providers = settings.openrouter_providers ?? default_settings.openrouter_providers; |
| 3602 | oai_settings.bind_preset_to_connection = settings.bind_preset_to_connection ?? default_settings.bind_preset_to_connection; | 3605 | oai_settings.bind_preset_to_connection = settings.bind_preset_to_connection ?? default_settings.bind_preset_to_connection; |
| 3606 | oai_settings.extensions = settings.extensions ?? default_settings.extensions; | ||
| 3603 | 3607 | ||
| 3604 | // Migrate from old settings | 3608 | // Migrate from old settings |
| 3605 | if (settings.names_in_completion === true) { | 3609 | if (settings.names_in_completion === true) { |
| @@ -4024,6 +4028,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { | |||
| 4024 | request_images: settings.request_images, | 4028 | request_images: settings.request_images, |
| 4025 | seed: settings.seed, | 4029 | seed: settings.seed, |
| 4026 | n: settings.n, | 4030 | n: settings.n, |
| 4031 | extensions: settings.extensions, | ||
| 4027 | }; | 4032 | }; |
| 4028 | 4033 | ||
| 4029 | const savePresetSettings = await fetch('/api/presets/save', { | 4034 | const savePresetSettings = await fetch('/api/presets/save', { |
| @@ -4468,6 +4473,12 @@ function onSettingsPresetChange() { | |||
| 4468 | continue; | 4473 | continue; |
| 4469 | } | 4474 | } |
| 4470 | 4475 | ||
| 4476 | // Extensions don't need UI updates and shouldn't fallback to current settings | ||
| 4477 | if (key === 'extensions') { | ||
| 4478 | oai_settings.extensions = preset.extensions || {}; | ||
| 4479 | continue; | ||
| 4480 | } | ||
| 4481 | |||
| 4471 | if (preset[key] !== undefined) { | 4482 | if (preset[key] !== undefined) { |
| 4472 | if (isCheckbox) { | 4483 | if (isCheckbox) { |
| 4473 | updateCheckbox(selector, preset[key]); | 4484 | updateCheckbox(selector, preset[key]); |
| @@ -1,4 +1,4 @@ | |||
| 1 | import { Fuse } from '../lib.js'; | 1 | import { Fuse, lodash } from '../lib.js'; |
| 2 | 2 | ||
| 3 | import { | 3 | import { |
| 4 | amount_gen, | 4 | amount_gen, |
| @@ -14,6 +14,7 @@ import { | |||
| 14 | novelai_setting_names, | 14 | novelai_setting_names, |
| 15 | novelai_settings, | 15 | novelai_settings, |
| 16 | online_status, | 16 | online_status, |
| 17 | saveSettings, | ||
| 17 | saveSettingsDebounced, | 18 | saveSettingsDebounced, |
| 18 | this_chid, | 19 | this_chid, |
| 19 | } from '../script.js'; | 20 | } from '../script.js'; |
| @@ -36,7 +37,7 @@ import { | |||
| 36 | textgenerationwebui_presets, | 37 | textgenerationwebui_presets, |
| 37 | textgenerationwebui_settings as textgen_settings, | 38 | textgenerationwebui_settings as textgen_settings, |
| 38 | } from './textgen-settings.js'; | 39 | } from './textgen-settings.js'; |
| 39 | import { download, equalsIgnoreCaseAndAccents, getSanitizedFilename, parseJsonFile, waitUntilCondition } from './utils.js'; | 40 | import { download, ensurePlainObject, equalsIgnoreCaseAndAccents, getSanitizedFilename, parseJsonFile, waitUntilCondition } from './utils.js'; |
| 40 | import { t } from './i18n.js'; | 41 | import { t } from './i18n.js'; |
| 41 | import { reasoning_templates } from './reasoning.js'; | 42 | import { reasoning_templates } from './reasoning.js'; |
| 42 | 43 | ||
| @@ -391,6 +392,9 @@ class PresetManager { | |||
| 391 | $(this.select).val(value).trigger('change'); | 392 | $(this.select).val(value).trigger('change'); |
| 392 | } | 393 | } |
| 393 | 394 | ||
| 395 | /** | ||
| 396 | * Updates the preset select element with the current API presets. | ||
| 397 | */ | ||
| 394 | async updatePreset() { | 398 | async updatePreset() { |
| 395 | const selected = $(this.select).find('option:selected'); | 399 | const selected = $(this.select).find('option:selected'); |
| 396 | console.log(selected); | 400 | console.log(selected); |
| @@ -407,6 +411,9 @@ class PresetManager { | |||
| 407 | toastr.success(successToast); | 411 | toastr.success(successToast); |
| 408 | } | 412 | } |
| 409 | 413 | ||
| 414 | /** | ||
| 415 | * Saves the currently selected preset with a new name. | ||
| 416 | */ | ||
| 410 | async savePresetAs() { | 417 | async savePresetAs() { |
| 411 | const inputValue = this.getSelectedPresetName(); | 418 | const inputValue = this.getSelectedPresetName(); |
| 412 | const popupText = !this.isAdvancedFormatting() ? '<h4>' + t`Hint: Use a character/group name to bind preset to a specific chat.` + '</h4>' : ''; | 419 | const popupText = !this.isAdvancedFormatting() ? '<h4>' + t`Hint: Use a character/group name to bind preset to a specific chat.` + '</h4>' : ''; |
| @@ -423,7 +430,14 @@ class PresetManager { | |||
| 423 | toastr.success(successToast); | 430 | toastr.success(successToast); |
| 424 | } | 431 | } |
| 425 | 432 | ||
| 426 | async savePreset(name, settings) { | 433 | /** |
| 434 | * Saves a preset with the given name and settings. | ||
| 435 | * @param {string} name Name of the preset to save | ||
| 436 | * @param {object} [settings] Settings to save as the preset. If not provided, uses the current preset settings. | ||
| 437 | * @param {object} [options] Options for saving the preset | ||
| 438 | * @param {boolean} [options.skipUpdate=false] If true, skips updating the preset list after saving. | ||
| 439 | */ | ||
| 440 | async savePreset(name, settings, { skipUpdate = false } = {}) { | ||
| 427 | if (this.apiId === 'instruct' && settings) { | 441 | if (this.apiId === 'instruct' && settings) { |
| 428 | await checkForSystemPromptInInstructTemplate(name, settings); | 442 | await checkForSystemPromptInInstructTemplate(name, settings); |
| 429 | } | 443 | } |
| @@ -449,9 +463,18 @@ class PresetManager { | |||
| 449 | const data = await response.json(); | 463 | const data = await response.json(); |
| 450 | name = data.name; | 464 | name = data.name; |
| 451 | 465 | ||
| 466 | if (skipUpdate) { | ||
| 467 | console.debug(`Preset ${name} saved, but not updating the list`); | ||
| 468 | return; | ||
| 469 | } | ||
| 470 | |||
| 452 | this.updateList(name, preset); | 471 | this.updateList(name, preset); |
| 453 | } | 472 | } |
| 454 | 473 | ||
| 474 | /** | ||
| 475 | * Renames the currently selected preset. | ||
| 476 | * @param {string} newName New name for the preset | ||
| 477 | */ | ||
| 455 | async renamePreset(newName) { | 478 | async renamePreset(newName) { |
| 456 | const oldName = this.getSelectedPresetName(); | 479 | const oldName = this.getSelectedPresetName(); |
| 457 | if (equalsIgnoreCaseAndAccents(oldName, newName)) { | 480 | if (equalsIgnoreCaseAndAccents(oldName, newName)) { |
| @@ -468,9 +491,15 @@ class PresetManager { | |||
| 468 | 491 | ||
| 469 | } | 492 | } |
| 470 | 493 | ||
| 494 | /** | ||
| 495 | * Gets a list of presets for the API. | ||
| 496 | * @param {string} [api] API ID. If not specified, uses the current API ID. | ||
| 497 | * @returns {{presets: any[], preset_names: object, settings: object}} | ||
| 498 | */ | ||
| 471 | getPresetList(api) { | 499 | getPresetList(api) { |
| 472 | let presets = []; | 500 | let presets = []; |
| 473 | let preset_names = {}; | 501 | let preset_names = {}; |
| 502 | let settings = {}; | ||
| 474 | 503 | ||
| 475 | // If no API specified, use the current API | 504 | // If no API specified, use the current API |
| 476 | if (api === undefined) { | 505 | if (api === undefined) { |
| @@ -482,50 +511,69 @@ class PresetManager { | |||
| 482 | case 'kobold': | 511 | case 'kobold': |
| 483 | presets = koboldai_settings; | 512 | presets = koboldai_settings; |
| 484 | preset_names = koboldai_setting_names; | 513 | preset_names = koboldai_setting_names; |
| 514 | settings = kai_settings; | ||
| 485 | break; | 515 | break; |
| 486 | case 'novel': | 516 | case 'novel': |
| 487 | presets = novelai_settings; | 517 | presets = novelai_settings; |
| 488 | preset_names = novelai_setting_names; | 518 | preset_names = novelai_setting_names; |
| 519 | settings = nai_settings; | ||
| 489 | break; | 520 | break; |
| 490 | case 'textgenerationwebui': | 521 | case 'textgenerationwebui': |
| 491 | presets = textgenerationwebui_presets; | 522 | presets = textgenerationwebui_presets; |
| 492 | preset_names = textgenerationwebui_preset_names; | 523 | preset_names = textgenerationwebui_preset_names; |
| 524 | settings = textgen_settings; | ||
| 493 | break; | 525 | break; |
| 494 | case 'openai': | 526 | case 'openai': |
| 495 | presets = openai_settings; | 527 | presets = openai_settings; |
| 496 | preset_names = openai_setting_names; | 528 | preset_names = openai_setting_names; |
| 529 | settings = openai_settings; | ||
| 497 | break; | 530 | break; |
| 498 | case 'context': | 531 | case 'context': |
| 499 | presets = context_presets; | 532 | presets = context_presets; |
| 500 | preset_names = context_presets.map(x => x.name); | 533 | preset_names = context_presets.map(x => x.name); |
| 534 | settings = power_user.context; | ||
| 501 | break; | 535 | break; |
| 502 | case 'instruct': | 536 | case 'instruct': |
| 503 | presets = instruct_presets; | 537 | presets = instruct_presets; |
| 504 | preset_names = instruct_presets.map(x => x.name); | 538 | preset_names = instruct_presets.map(x => x.name); |
| 539 | settings = power_user.instruct; | ||
| 505 | break; | 540 | break; |
| 506 | case 'sysprompt': | 541 | case 'sysprompt': |
| 507 | presets = system_prompts; | 542 | presets = system_prompts; |
| 508 | preset_names = system_prompts.map(x => x.name); | 543 | preset_names = system_prompts.map(x => x.name); |
| 544 | settings = power_user.sysprompt; | ||
| 509 | break; | 545 | break; |
| 510 | case 'reasoning': | 546 | case 'reasoning': |
| 511 | presets = reasoning_templates; | 547 | presets = reasoning_templates; |
| 512 | preset_names = reasoning_templates.map(x => x.name); | 548 | preset_names = reasoning_templates.map(x => x.name); |
| 549 | settings = power_user.reasoning; | ||
| 513 | break; | 550 | break; |
| 514 | default: | 551 | default: |
| 515 | console.warn(`Unknown API ID ${api}`); | 552 | console.warn(`Unknown API ID ${api}`); |
| 516 | } | 553 | } |
| 517 | 554 | ||
| 518 | return { presets, preset_names }; | 555 | return { presets, preset_names, settings }; |
| 519 | } | 556 | } |
| 520 | 557 | ||
| 558 | /** | ||
| 559 | * Returns true if the API is keyed, meaning it uses a name to identify presets. | ||
| 560 | */ | ||
| 521 | isKeyedApi() { | 561 | isKeyedApi() { |
| 522 | return this.apiId == 'textgenerationwebui' || this.isAdvancedFormatting(); | 562 | return this.apiId == 'textgenerationwebui' || this.isAdvancedFormatting(); |
| 523 | } | 563 | } |
| 524 | 564 | ||
| 565 | /** | ||
| 566 | * Returns true if the API is from Advanced Formatting group. | ||
| 567 | */ | ||
| 525 | isAdvancedFormatting() { | 568 | isAdvancedFormatting() { |
| 526 | return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId); | 569 | return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId); |
| 527 | } | 570 | } |
| 528 | 571 | ||
| 572 | /** | ||
| 573 | * Updates the preset list with a new or existing preset. | ||
| 574 | * @param {string} name Name of the preset | ||
| 575 | * @param {object} preset Preset object | ||
| 576 | */ | ||
| 529 | updateList(name, preset) { | 577 | updateList(name, preset) { |
| 530 | const { presets, preset_names } = this.getPresetList(); | 578 | const { presets, preset_names } = this.getPresetList(); |
| 531 | const presetExists = this.isKeyedApi() ? preset_names.includes(name) : Object.keys(preset_names).includes(name); | 579 | const presetExists = this.isKeyedApi() ? preset_names.includes(name) : Object.keys(preset_names).includes(name); |
| @@ -561,6 +609,11 @@ class PresetManager { | |||
| 561 | } | 609 | } |
| 562 | } | 610 | } |
| 563 | 611 | ||
| 612 | /** | ||
| 613 | * Gets the preset settings for the given name. | ||
| 614 | * @param {string} name Name of the preset | ||
| 615 | * @returns {object} Preset settings object for the given name | ||
| 616 | */ | ||
| 564 | getPresetSettings(name) { | 617 | getPresetSettings(name) { |
| 565 | function getSettingsByApiId(apiId) { | 618 | function getSettingsByApiId(apiId) { |
| 566 | switch (apiId) { | 619 | switch (apiId) { |
| @@ -655,7 +708,7 @@ class PresetManager { | |||
| 655 | } | 708 | } |
| 656 | } | 709 | } |
| 657 | 710 | ||
| 658 | if (!this.isAdvancedFormatting()) { | 711 | if (!this.isAdvancedFormatting() && this.apiId !== 'openai') { |
| 659 | settings['genamt'] = amount_gen; | 712 | settings['genamt'] = amount_gen; |
| 660 | settings['max_length'] = max_context; | 713 | settings['max_length'] = max_context; |
| 661 | } | 714 | } |
| @@ -663,6 +716,11 @@ class PresetManager { | |||
| 663 | return settings; | 716 | return settings; |
| 664 | } | 717 | } |
| 665 | 718 | ||
| 719 | /** | ||
| 720 | * Retrieves a completion preset by name. | ||
| 721 | * @param {string} name Name of the preset to retrieve | ||
| 722 | * @returns {any} Preset object if found, otherwise undefined | ||
| 723 | */ | ||
| 666 | getCompletionPresetByName(name) { | 724 | getCompletionPresetByName(name) { |
| 667 | // Retrieve a completion preset by name. Return undefined if not found. | 725 | // Retrieve a completion preset by name. Return undefined if not found. |
| 668 | let { presets, preset_names } = this.getPresetList(); | 726 | let { presets, preset_names } = this.getPresetList(); |
| @@ -687,7 +745,10 @@ class PresetManager { | |||
| 687 | return preset; | 745 | return preset; |
| 688 | } | 746 | } |
| 689 | 747 | ||
| 690 | // pass no arguments to delete current preset | 748 | /** |
| 749 | * Deletes a preset by name. If not provided, deletes the currently selected preset. | ||
| 750 | * @param {string} [name] Name of the preset to delete. | ||
| 751 | */ | ||
| 691 | async deletePreset(name) { | 752 | async deletePreset(name) { |
| 692 | const { preset_names, presets } = this.getPresetList(); | 753 | const { preset_names, presets } = this.getPresetList(); |
| 693 | const value = name ? (this.isKeyedApi() ? this.findPreset(name) : name) : this.getSelectedPreset(); | 754 | const value = name ? (this.isKeyedApi() ? this.findPreset(name) : name) : this.getSelectedPreset(); |
| @@ -728,6 +789,11 @@ class PresetManager { | |||
| 728 | return response.ok; | 789 | return response.ok; |
| 729 | } | 790 | } |
| 730 | 791 | ||
| 792 | /** | ||
| 793 | * Retrieves the default preset for the API from the server. | ||
| 794 | * @param {string} name Name of the preset to restore | ||
| 795 | * @returns {Promise<any>} Default preset object, or undefined if the request fails | ||
| 796 | */ | ||
| 731 | async getDefaultPreset(name) { | 797 | async getDefaultPreset(name) { |
| 732 | const response = await fetch('/api/presets/restore', { | 798 | const response = await fetch('/api/presets/restore', { |
| 733 | method: 'POST', | 799 | method: 'POST', |
| @@ -743,6 +809,70 @@ class PresetManager { | |||
| 743 | 809 | ||
| 744 | return await response.json(); | 810 | return await response.json(); |
| 745 | } | 811 | } |
| 812 | |||
| 813 | /** | ||
| 814 | * Reads a preset extension field from the preset. | ||
| 815 | * @param {object} options | ||
| 816 | * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name. | ||
| 817 | * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'. | ||
| 818 | * @return {any} The value of the preset extension field, or null if not found. | ||
| 819 | */ | ||
| 820 | readPresetExtensionField({ name, path }) { | ||
| 821 | const { settings } = this.getPresetList(); | ||
| 822 | const selectedName = this.getSelectedPresetName(); | ||
| 823 | const presetName = name || selectedName; | ||
| 824 | |||
| 825 | // Read from settings if the selected preset is the same as the provided name | ||
| 826 | if (settings && selectedName === presetName) { | ||
| 827 | const settingsExtensions = ensurePlainObject(settings.extensions || {}); | ||
| 828 | return lodash.get(settingsExtensions, path, null); | ||
| 829 | } | ||
| 830 | |||
| 831 | // Otherwise, read from the preset by name | ||
| 832 | const preset = this.getCompletionPresetByName(presetName); | ||
| 833 | if (!preset) { | ||
| 834 | return null; | ||
| 835 | } | ||
| 836 | |||
| 837 | const presetExtensions = ensurePlainObject(preset.extensions || {}); | ||
| 838 | const value = lodash.get(presetExtensions, path, null); | ||
| 839 | return value; | ||
| 840 | } | ||
| 841 | |||
| 842 | /** | ||
| 843 | * Writes a value to a preset extension field. | ||
| 844 | * @param {object} options | ||
| 845 | * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name. | ||
| 846 | * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'. | ||
| 847 | * @param {any} options.value Value to write to the preset extension field. | ||
| 848 | * @return {Promise<void>} Resolves when the preset is saved. | ||
| 849 | */ | ||
| 850 | async writePresetExtensionField({ name, path, value }) { | ||
| 851 | const { settings } = this.getPresetList(); | ||
| 852 | const selectedName = this.getSelectedPresetName(); | ||
| 853 | const presetName = name || selectedName; | ||
| 854 | |||
| 855 | // Write to settings if the selected preset is the same as the provided name | ||
| 856 | if (settings && selectedName === presetName) { | ||
| 857 | // Set the value at the specified path | ||
| 858 | settings.extensions = ensurePlainObject(settings.extensions || {}); | ||
| 859 | lodash.set(settings.extensions, path, value); | ||
| 860 | await saveSettings(); | ||
| 861 | } | ||
| 862 | |||
| 863 | // Also update the preset by name | ||
| 864 | const preset = this.getCompletionPresetByName(presetName); | ||
| 865 | if (!preset) { | ||
| 866 | return; | ||
| 867 | } | ||
| 868 | |||
| 869 | // Set the value at the specified path | ||
| 870 | preset.extensions = ensurePlainObject(preset.extensions || {}); | ||
| 871 | lodash.set(preset.extensions, path, value); | ||
| 872 | |||
| 873 | // Save the updated preset | ||
| 874 | await this.savePreset(presetName, preset, { skipUpdate: true }); | ||
| 875 | } | ||
| 746 | } | 876 | } |
| 747 | 877 | ||
| 748 | /** | 878 | /** |
| @@ -224,6 +224,7 @@ const settings = { | |||
| 224 | min_keep: 0, | 224 | min_keep: 0, |
| 225 | featherless_model: '', | 225 | featherless_model: '', |
| 226 | generic_model: '', | 226 | generic_model: '', |
| 227 | extensions: {}, | ||
| 227 | }; | 228 | }; |
| 228 | 229 | ||
| 229 | export { | 230 | export { |
| @@ -306,6 +307,7 @@ export const setting_names = [ | |||
| 306 | 'nsigma', | 307 | 'nsigma', |
| 307 | 'min_keep', | 308 | 'min_keep', |
| 308 | 'generic_model', | 309 | 'generic_model', |
| 310 | 'extensions', | ||
| 309 | ]; | 311 | ]; |
| 310 | 312 | ||
| 311 | const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); | 313 | const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); |
| @@ -1098,6 +1100,12 @@ function insertMissingArrayItems(source, target) { | |||
| 1098 | } | 1100 | } |
| 1099 | 1101 | ||
| 1100 | function setSettingByName(setting, value, trigger) { | 1102 | function setSettingByName(setting, value, trigger) { |
| 1103 | if ('extensions' === setting) { | ||
| 1104 | value = value || {}; | ||
| 1105 | settings.extensions = value; | ||
| 1106 | return; | ||
| 1107 | } | ||
| 1108 | |||
| 1101 | if (value === null || value === undefined) { | 1109 | if (value === null || value === undefined) { |
| 1102 | return; | 1110 | return; |
| 1103 | } | 1111 | } |
| @@ -102,6 +102,19 @@ export function deepMerge(target, source) { | |||
| 102 | return output; | 102 | return output; |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | /** | ||
| 106 | * Ensures that the provided object is a plain object. | ||
| 107 | * @param {object} obj Object to ensure is a plain object | ||
| 108 | * @return {object} A plain object, or an empty object if the input is not an object. | ||
| 109 | */ | ||
| 110 | export function ensurePlainObject(obj) { | ||
| 111 | if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { | ||
| 112 | return {}; | ||
| 113 | } | ||
| 114 | |||
| 115 | return obj; | ||
| 116 | } | ||
| 117 | |||
| 105 | export function escapeHtml(str) { | 118 | export function escapeHtml(str) { |
| 106 | return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); | 119 | return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); |
| 107 | } | 120 | } |