mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-10-19 07:23:51 +00:00
The preset editor builds its draft as soon as it renders. The store's
loaded flags stay set after the first open, so later opens showed the
previous preset list and fell back to Default when the requested preset
was new. Saving that stale draft deleted presets created elsewhere:
another tab, the API, or a plugin.
Reload the global presets and resolve the initial preset in an
open_modal_before hook, so every open starts from fresh data, including
/presets, which opens the modal without openPresetEditor(). The modal
now appears after that reload; the editor still renders at once from the
fresh store data, so its footer stays pinned.
Verified with a behavioural regression test and the full suite (2171
passed, including the new test; the same 5 failures as 0efb0d4f).
Fixes #1928
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
3.1 KiB
Python
67 lines
3.1 KiB
Python
import base64
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).parents[1]
|
|
|
|
|
|
def module_url(path: str, prelude: str) -> str:
|
|
source = re.sub(r"^import .*?;\n", "", (ROOT / path).read_text(), flags=re.MULTILINE)
|
|
return "data:text/javascript;base64," + base64.b64encode((prelude + source).encode()).decode()
|
|
|
|
|
|
@pytest.mark.skipif(not shutil.which("node"), reason="node is required")
|
|
def test_preset_editor_opens_on_presets_changed_outside_it():
|
|
store_url = module_url(
|
|
"plugins/_model_config/webui/model-config-store.js",
|
|
"const createStore = (_name, value) => value;\n"
|
|
"const fetchApi = (...args) => globalThis.fetchApi(...args);\n"
|
|
"const apiKeysState = {}, apiKeysMethods = {}, switcherState = {}, switcherMethods = {};\n",
|
|
)
|
|
hook_url = module_url(
|
|
"plugins/_model_config/extensions/webui/open_modal_before/refresh-preset-editor.js",
|
|
"const modelConfigStore = globalThis.modelConfigStore, chatsStore = globalThis.chatsStore;\n",
|
|
)
|
|
script = r'''
|
|
import assert from 'node:assert/strict';
|
|
const preset = name => ({name, chat: {provider: 'p', name: 'm'}, utility: {provider: 'p', name: 'u'},
|
|
embedding: {provider: 'p', name: 'e'}});
|
|
let presets = [preset('Default')], selected = 'Default';
|
|
globalThis.fetchApi = async (url, options) => {
|
|
const body = JSON.parse(options.body);
|
|
const data = url.endsWith('/model_presets') && body.action === 'get' ? {presets: structuredClone(presets)}
|
|
: url.endsWith('/model_config_get') ? {selected_preset: selected} : assert.fail(url);
|
|
return {ok: true, json: async () => data};
|
|
};
|
|
const { store } = await import(STORE_URL);
|
|
Object.assign(globalThis, {window: globalThis, modelConfigStore: store, chatsStore: {selected: 'chat'}});
|
|
const { default: refreshPresetEditor } = await import(HOOK_URL);
|
|
let editor;
|
|
// modals.js runs open_modal_before first. main.html then builds the draft at once
|
|
// when presets were loaded before, otherwise after its own x-init load.
|
|
globalThis.openModal = async modalPath => {
|
|
await refreshPresetEditor({modalPath, modal: null, cancel: false});
|
|
if (!store._presetsLoaded) await store.loadGlobalPresets();
|
|
editor = store.createPresetEditor(store.presetEditorInitialName);
|
|
};
|
|
const shown = () => [editor.presets.map(p => p.name).join(), editor.selectedPreset?.name];
|
|
|
|
await store.openPresetEditor('Default');
|
|
assert.deepEqual(shown(), ['Default', 'Default']);
|
|
|
|
// Another client adds a preset: the editor lists it and opens on it.
|
|
presets = [...presets, preset('probe')];
|
|
await store.openPresetEditor('probe');
|
|
assert.deepEqual(shown(), ['Default,probe', 'probe']);
|
|
|
|
// /presets opens the modal directly: the editor opens on the chat's active preset.
|
|
presets = [...presets, preset('probe2')];
|
|
selected = 'probe2';
|
|
await globalThis.openModal('/plugins/_model_config/webui/main.html');
|
|
assert.deepEqual(shown(), ['Default,probe,probe2', 'probe2']);
|
|
'''.replace("STORE_URL", repr(store_url)).replace("HOOK_URL", repr(hook_url))
|
|
subprocess.run(["node", "--input-type=module", "-e", script], check=True)
|