agent-zero/plugins/_model_config/webui/main.html
frdel 15a2b553be Unify global model presets
Make model presets a single global collection and treat presets as reusable setups (main, utility, embedding). Persisted scope configs now store only a preset name (Default is always present and immutable); project-scoped preset definitions were removed and attempts to edit/reset project presets are rejected. Add embedding-model support throughout: include embedding slot in presets, surface embedding in the switcher and override APIs, and notify extensions when the effective embedding model changes. Implement rename/retire propagation to update plugin configs, saved chats, and live AgentContext objects when preset names change or presets are removed. Update CLI/integration commands to use an "inherit" action for returning to the scoped preset and to report the effective scoped-or-chat preset. Add startup migration/bootstrap to initialize presets from remote or bundled fallback and numerous docs/UI text updates to reflect the new preset model. Extra: refactor config resolution helpers and add safety checks (validation, atomic writes) when saving presets.
2026-07-17 12:16:10 +02:00

192 lines
6.7 KiB
HTML

<html>
<head>
<title>Edit Model Presets</title>
<script type="module">
import { store } from "/plugins/_model_config/webui/model-config-store.js";
</script>
</head>
<body>
<div x-data
x-init="
await $store.modelConfig.ensureLoaded();
$store.modelConfig.resetApiKeyDrafts();
await $store.modelConfig.refreshApiKeyStatus();
await $store.modelConfig.loadGlobalPresets();
await $store.modelConfig.preparePresetEditor($store.chats?.selected || '');
">
<template x-if="$store.modelConfig._loaded && $store.modelConfig._presetsLoaded">
<div class="preset-editor"
x-data="$store.modelConfig.createPresetEditor($store.modelConfig.presetEditorInitialName)">
<div class="preset-editor-intro">
<div class="field-title">Model Presets</div>
<div class="field-description">
Each preset contains the complete main, utility, and embedding model setup. Default is always available.
</div>
</div>
<div class="preset-editor-toolbar">
<label class="preset-editor-select">
<span class="field-title">Preset</span>
<select x-model.number="selectedKey">
<template x-for="preset in presets" :key="preset._key">
<option :value="preset._key" x-text="preset.name"></option>
</template>
</select>
</label>
<button type="button" class="button" @click="addPreset()">
<span class="icon material-symbols-outlined">add</span>
Add
</button>
<button type="button" class="button"
x-show="canDeleteSelected"
@click="$confirmClick($event, () => removeSelectedPreset())">
<span class="icon material-symbols-outlined">delete</span>
Delete
</button>
</div>
<template x-if="selectedPreset">
<div class="preset-editor-body">
<template x-if="canRenameSelected">
<div class="field preset-name-field">
<div class="field-label">
<div class="field-title">Preset name</div>
<div class="field-description">Shown in settings and the chat model switcher.</div>
</div>
<div class="field-control">
<input type="text"
x-model="selectedPreset.name"
placeholder="Preset name">
</div>
</div>
</template>
<section class="preset-model-section">
<div class="preset-model-heading">
<div class="section-title">Main Model</div>
<div class="section-description">Primary model for conversations, reasoning, and tools.</div>
</div>
<div x-data="{ get model() { return selectedPreset.chat; }, modelType: 'chat', providers: $store.modelConfig.chatProviders, searchType: 'chat', apiKeyMode: 'store' }">
<x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
</div>
</section>
<section class="preset-model-section">
<div class="preset-model-heading">
<div class="section-title">Utility Model</div>
<div class="section-description">Faster helper model for summaries and background work.</div>
</div>
<div x-data="{ get model() { return selectedPreset.utility; }, modelType: 'utility', providers: $store.modelConfig.chatProviders, searchType: 'chat', apiKeyMode: 'store', get providerFallback() { return selectedPreset.chat.provider; }, get apiBaseFallback() { return selectedPreset.chat.api_base; } }">
<x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
</div>
</section>
<section class="preset-model-section">
<div class="preset-model-heading">
<div class="section-title">Embedding Model</div>
<div class="section-description">Creates vectors for memory and knowledge retrieval.</div>
</div>
<div x-data="{ get model() { return selectedPreset.embedding; }, modelType: 'embedding', providers: $store.modelConfig.embeddingProviders, searchType: 'embedding', apiKeyMode: 'store' }">
<x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
</div>
</section>
</div>
</template>
<div class="preset-editor-secondary-actions">
<button type="button" class="text-button" @click="$store.modelConfig.openApiKeysFromSummary()">
<span class="material-symbols-outlined">key</span>
API Keys
</button>
<button type="button" class="text-button"
@click="$confirmClick($event, () => resetPresets())">
<span class="material-symbols-outlined">restart_alt</span>
Restore bundled presets
</button>
</div>
<div class="modal-footer" data-modal-footer>
<button type="button" class="btn btn-ok"
@click="if (await savePresets()) window.closeModal?.()">Save</button>
<button type="button" class="btn btn-cancel"
@click="window.closeModal?.()">Close</button>
</div>
</div>
</template>
</div>
<style>
.preset-editor,
.preset-editor-body {
display: flex;
flex-direction: column;
gap: 1rem;
}
.preset-editor-intro .field-title {
font-size: 1rem;
}
.preset-editor-toolbar {
display: flex;
align-items: flex-end;
gap: 0.5rem;
padding: 0.8rem;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-input);
}
.preset-editor-select {
display: flex;
flex: 1 1 auto;
flex-direction: column;
gap: 0.35rem;
min-width: 0;
margin: 0;
}
.preset-editor-select select {
width: 100%;
}
.preset-name-field,
.preset-model-section {
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 8px;
}
.preset-model-heading {
margin-bottom: 0.75rem;
}
.preset-model-heading .section-title {
margin-top: 0;
}
.preset-editor-secondary-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
@media (max-width: 640px) {
.preset-editor-toolbar {
align-items: stretch;
flex-wrap: wrap;
}
.preset-editor-select {
flex-basis: 100%;
}
.preset-editor-toolbar .button {
flex: 1 1 auto;
justify-content: center;
}
}
</style>
</body>
</html>