Move model config snapshot sync into store

Keep model config markup focused on setup and let the store own settings-object lifecycle hooks. This initializes UI-only kwargs fields after scope loads while preserving dirty tracking for reset/default changes.

Alessandro committed Jun 2, 2026 at 00:29 UTC b152030a951757e3575c529a7049dab58ce340d1
3 files changed +44 -20
plugins/_model_config/webui/config.html
+2 -14
@@ -7,24 +7,12 @@
7 </head>
8
9 <body>
10 - <div x-data="{ lastConfig: null }"
11 - x-effect="
12 - if (config && config !== lastConfig) {
13 - lastConfig = config;
14 - const snapshotBeforeInit = typeof context !== 'undefined'
15 - ? context._toComparableJson(config)
16 - : null;
17 - $store.modelConfig.initConfigFields(config);
18 - if (typeof context !== 'undefined' && context.settingsSnapshotJson === snapshotBeforeInit) {
19 - context.settingsSnapshotJson = context._toComparableJson(config);
20 - }
21 - }
22 - "
10 + <div x-data
11 x-init="
12 await $store.modelConfig.ensureLoaded();
13 $store.modelConfig.resetApiKeyDrafts();
14 await $store.modelConfig.refreshApiKeyStatus();
27 - $store.modelConfig.installSettingsHooks(context, config);
15 + $store.modelConfig.installSettingsHooks(context);
16 ">
17 <template x-if="config && $store.modelConfig._loaded">
18 <div class="model-config-sections">
plugins/_model_config/webui/model-config-store.js
+36 -4
@@ -235,6 +235,25 @@ export const store = createStore("modelConfig", {
235 if (config?.embedding_model) config.embedding_model._kwargs_text = kwargsToText(config.embedding_model.kwargs);
236 },
237
238 + syncContextConfigFields(context, refreshCleanSnapshot = false) {
239 + const config = context?.settings;
240 + if (!config || typeof config !== 'object') return;
241 +
242 + const snapshotBeforeInit = refreshCleanSnapshot && typeof context._toComparableJson === 'function'
243 + ? context._toComparableJson(config)
244 + : null;
245 +
246 + this.initConfigFields(config);
247 +
248 + if (
249 + refreshCleanSnapshot &&
250 + typeof context._toComparableJson === 'function' &&
251 + context.settingsSnapshotJson === snapshotBeforeInit
252 + ) {
253 + context.settingsSnapshotJson = context._toComparableJson(config);
254 + }
255 + },
256 +
257 // Global presets
258 async loadGlobalPresets() {
259 try {
@@ -300,13 +319,25 @@ export const store = createStore("modelConfig", {
319 },
320
321 /**
303 - * Install save and reset hooks on the plugin settings context.
304 - * - Save: persists dirty API keys before the normal config save.
305 - * - Reset: reloads global presets when settings are reset to defaults.
322 + * Install hooks on the plugin settings context.
323 + * - Load/reset: initialize UI-only model fields when the settings object changes.
324 + * - Save: persist dirty API keys before the normal config save.
325 + * - Reset: reload global presets when settings are reset to defaults.
326 */
307 - installSettingsHooks(context, config) {
327 + installSettingsHooks(context) {
328 if (!context || context.__modelConfigHooksInstalled) return;
329
330 + this.syncContextConfigFields(context, true);
331 +
332 + const originalLoadSettings = context.loadSettings?.bind(context);
333 + if (originalLoadSettings) {
334 + context.loadSettings = async (...args) => {
335 + const result = await originalLoadSettings(...args);
336 + this.syncContextConfigFields(context, true);
337 + return result;
338 + };
339 + }
340 +
341 const originalSave = context.save.bind(context);
342 context.save = async () => {
343 context.error = null;
@@ -324,6 +355,7 @@ export const store = createStore("modelConfig", {
355 const before = context.settings;
356 await originalReset();
357 if (context.settings !== before) {
358 + this.syncContextConfigFields(context);
359 await this.resetGlobalPresets();
360 }
361 };
tests/test_model_config_api_keys.py
+6 -2
@@ -120,10 +120,14 @@ def test_model_config_frontend_tracks_inline_api_key_edits():
120
121 def test_model_config_snapshot_sync_only_adjusts_clean_loaded_configs():
122 config_path = PROJECT_ROOT / "plugins" / "_model_config" / "webui" / "config.html"
123 + store_path = PROJECT_ROOT / "plugins" / "_model_config" / "webui" / "model-config-store.js"
124 config_content = config_path.read_text(encoding="utf-8")
125 + store_content = store_path.read_text(encoding="utf-8")
126
125 - assert "const snapshotBeforeInit" in config_content
126 - assert "context.settingsSnapshotJson === snapshotBeforeInit" in config_content
127 + assert "x-effect" not in config_content
128 + assert "syncContextConfigFields(context, true)" in store_content
129 + assert "context.loadSettings = async" in store_content
130 + assert "context.settingsSnapshotJson === snapshotBeforeInit" in store_content
131
132
133 def test_model_switcher_frontend_renders_custom_overrides():