main
html 209 lines 7.73 KB
Raw
1 <html>
2 <head>
3 <title>Edit Model Presets</title>
4 <script type="module">
5 import { store } from "/plugins/_model_config/webui/model-config-store.js";
6 </script>
7 </head>
8
9 <body>
10 <div x-data
11 x-init="
12 await $store.modelConfig.ensureLoaded();
13 $store.modelConfig.resetApiKeyDrafts();
14 await $store.modelConfig.refreshApiKeyStatus();
15 await $store.modelConfig.loadGlobalPresets();
16 await $store.modelConfig.preparePresetEditor($store.chats?.selected || '');
17 ">
18 <template x-if="$store.modelConfig._loaded && $store.modelConfig._presetsLoaded">
19 <div class="preset-editor"
20 x-data="$store.modelConfig.createPresetEditor($store.modelConfig.presetEditorInitialName)">
21 <div class="preset-editor-intro">
22 <div class="field-title">Model Presets</div>
23 <div class="field-description">
24 Each preset contains the main, utility, embedding, and optional vision-sidecar setup. Default is always available.
25 </div>
26 </div>
27
28 <div class="preset-editor-toolbar">
29 <label class="preset-editor-select">
30 <span class="field-title">Preset</span>
31 <select x-model.number="selectedKey" x-init="$nextTick(() => $el.focus())">
32 <template x-for="preset in presets" :key="preset._key">
33 <option :value="preset._key" x-text="preset.name"></option>
34 </template>
35 </select>
36 </label>
37 <button type="button" class="button" @click="addPreset()">
38 <x-icon class="icon" name="add"></x-icon>
39 Add
40 </button>
41 <button type="button" class="button"
42 x-show="canDeleteSelected"
43 @click="$confirmClick($event, () => removeSelectedPreset())">
44 <x-icon class="icon" name="delete"></x-icon>
45 Delete
46 </button>
47 </div>
48
49 <template x-if="selectedPreset">
50 <div class="preset-editor-body">
51 <template x-if="canRenameSelected">
52 <div class="field preset-name-field">
53 <div class="field-label">
54 <div class="field-title">Preset name</div>
55 <div class="field-description">Shown in settings and the chat model switcher.</div>
56 </div>
57 <div class="field-control">
58 <input type="text"
59 x-model="selectedPreset.name"
60 placeholder="Preset name">
61 </div>
62 </div>
63 </template>
64
65 <section class="preset-model-section">
66 <div class="preset-model-heading">
67 <div class="section-title">Main Model</div>
68 <div class="section-description">Primary model for conversations, reasoning, and tools.</div>
69 </div>
70 <div x-data="{ get model() { return selectedPreset.chat; }, get visionModel() { return selectedPreset.vision; }, modelType: 'chat', providers: $store.modelConfig.chatProviders, searchType: 'chat', apiKeyMode: 'store' }">
71 <x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
72 </div>
73 <template x-if="!selectedPreset.chat.vision || selectedPreset.vision.override_main">
74 <div class="vision-sidecar-selector">
75 <div class="field-title">Vision sidecar</div>
76 <div class="field-description"
77 x-text="selectedPreset.chat.vision
78 ? 'Overrides Main model vision for vision_load.'
79 : 'Used by vision_load while Main model vision is disabled. Leave empty to disable image analysis.'"></div>
80 <div x-data="{ get model() { return selectedPreset.vision; }, modelType: 'vision', providers: $store.modelConfig.chatProviders, searchType: 'chat', apiKeyMode: 'store' }">
81 <x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
82 </div>
83 </div>
84 </template>
85 </section>
86
87 <section class="preset-model-section">
88 <div class="preset-model-heading">
89 <div class="section-title">Utility Model</div>
90 <div class="section-description">Faster helper model for summaries and background work.</div>
91 </div>
92 <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; } }">
93 <x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
94 </div>
95 </section>
96
97 <section class="preset-model-section">
98 <div class="preset-model-heading">
99 <div class="section-title">Embedding Model</div>
100 <div class="section-description">Creates vectors for memory and knowledge retrieval.</div>
101 </div>
102 <div x-data="{ get model() { return selectedPreset.embedding; }, modelType: 'embedding', providers: $store.modelConfig.embeddingProviders, searchType: 'embedding', apiKeyMode: 'store' }">
103 <x-component path="/plugins/_model_config/webui/model-field.html"></x-component>
104 </div>
105 </section>
106 </div>
107 </template>
108
109 <div class="preset-editor-secondary-actions">
110 <button type="button" class="text-button" @click="$store.modelConfig.openApiKeysFromSummary()">
111 <x-icon name="key"></x-icon>
112 API Keys
113 </button>
114 <button type="button" class="text-button"
115 @click="$confirmClick($event, () => resetPresets())">
116 <x-icon name="restart_alt"></x-icon>
117 Restore bundled presets
118 </button>
119 </div>
120
121 <div class="modal-footer" data-modal-footer>
122 <button type="button" class="btn btn-ok"
123 @click="if (await savePresets()) window.closeModal?.()">Save</button>
124 <button type="button" class="btn btn-cancel"
125 @click="window.closeModal?.()">Close</button>
126 </div>
127 </div>
128 </template>
129 </div>
130
131 <style>
132 .preset-editor,
133 .preset-editor-body {
134 display: flex;
135 flex-direction: column;
136 gap: 1rem;
137 }
138
139 .preset-editor-intro .field-title {
140 font-size: 1rem;
141 }
142
143 .preset-editor-toolbar {
144 display: flex;
145 align-items: flex-end;
146 gap: 0.5rem;
147 padding: 0.8rem;
148 border: 1px solid var(--color-border);
149 border-radius: 8px;
150 background: var(--color-input);
151 }
152
153 .preset-editor-select {
154 display: flex;
155 flex: 1 1 auto;
156 flex-direction: column;
157 gap: 0.35rem;
158 min-width: 0;
159 margin: 0;
160 }
161
162 .preset-editor-select select {
163 width: 100%;
164 }
165
166 .preset-name-field,
167 .preset-model-section {
168 padding: 1rem;
169 border: 1px solid var(--color-border);
170 border-radius: 8px;
171 }
172
173 .preset-model-heading {
174 margin-bottom: 0.75rem;
175 }
176
177 .preset-model-heading .section-title {
178 margin-top: 0;
179 }
180
181 .vision-sidecar-selector {
182 margin: 0.75rem 0 0;
183 padding: 0.25rem 0 0;
184 }
185
186 .preset-editor-secondary-actions {
187 display: flex;
188 flex-wrap: wrap;
189 gap: 1rem;
190 }
191
192 @media (max-width: 640px) {
193 .preset-editor-toolbar {
194 align-items: stretch;
195 flex-wrap: wrap;
196 }
197
198 .preset-editor-select {
199 flex-basis: 100%;
200 }
201
202 .preset-editor-toolbar .button {
203 flex: 1 1 auto;
204 justify-content: center;
205 }
206 }
207 </style>
208 </body>
209 </html>