simplify preset editing screen

Nicolas Leão committed Mar 31, 2026 at 14:08 UTC b531e5ee42481e45155ea875609451edba282a1c
2 files changed +183 -147
plugins/_model_config/webui/main.html
-1
@@ -110,7 +110,6 @@
110 .preset-card {
111 border: 1px solid var(--color-border);
112 border-radius: 6px;
113 - overflow: hidden;
113 }
114 .preset-card-header {
115 display: flex;
plugins/_model_config/webui/model-field.html
+183 -146
@@ -71,180 +71,194 @@
71 </div>
72 </div>
73
74 - <!-- API key (store mode: config.html) -->
75 - <template x-if="apiKeyMode === 'store'">
76 - <div class="field">
77 - <div class="field-label">
78 - <div class="field-title">API key</div>
79 - <div class="field-description">Authentication key for this provider. Shared across all model slots using the same provider.</div>
80 - </div>
81 - <div class="field-control" style="position:relative;" x-data="{ showKey: false }">
82 - <input :type="showKey ? 'text' : 'password'"
83 - :value="$store.modelConfig.apiKeyValues[_prov]"
84 - :placeholder="$store.modelConfig.apiKeyStatus[_prov] ? '&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;' : ''"
85 - autocomplete="off"
86 - @input="$store.modelConfig.setApiKeyValue(_prov, $el.value)"
87 - style="padding-right:32px;" />
88 - <span class="material-symbols-outlined eye-toggle"
89 - @click="
90 - showKey = !showKey;
91 - const prov = _prov;
92 - if (showKey && !$store.modelConfig.apiKeyValues[prov] && $store.modelConfig.apiKeyStatus[prov]) {
93 - $store.modelConfig.revealApiKey(prov).then(v => { if (v) $store.modelConfig.apiKeyValues[prov] = v; });
94 - }
95 - "
96 - x-text="showKey ? 'visibility' : 'visibility_off'"></span>
97 - </div>
74 + <!-- Advanced Settings (collapsed by default) -->
75 + <div class="advanced-section" x-data="{ advOpen: false }">
76 + <div class="advanced-toggle" @click="advOpen = !advOpen">
77 + <span class="material-symbols-outlined advanced-toggle-icon"
78 + :style="advOpen ? 'transform:rotate(90deg)' : ''"
79 + >chevron_right</span>
80 + <span>Advanced Settings</span>
81 </div>
99 - </template>
82
101 - <!-- API key (inline mode: presets) -->
102 - <template x-if="apiKeyMode === 'inline'">
103 - <div class="field">
104 - <div class="field-label">
105 - <div class="field-title">API key</div>
106 - <div class="field-description">Authentication key for this provider. Shared across all model slots using the same provider.</div>
107 - </div>
108 - <div class="field-control" style="position:relative;" x-data="{ showKey: false, _revealed: '' }">
109 - <input :type="showKey ? 'text' : 'password'" x-model="model.api_key" autocomplete="off"
110 - :placeholder="$store.modelConfig.apiKeyStatus[_prov] ? '&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;' : ''"
111 - style="padding-right:32px;" />
112 - <span class="material-symbols-outlined eye-toggle"
113 - @click="
114 - showKey = !showKey;
115 - if (showKey && !model.api_key && $store.modelConfig.apiKeyStatus[_prov]) {
116 - $store.modelConfig.revealApiKey(_prov).then(v => { if (v) { model.api_key = v; _revealed = v; } });
117 - }
118 - if (!showKey && _revealed && model.api_key === _revealed) {
119 - model.api_key = ''; _revealed = '';
120 - }
121 - "
122 - x-text="showKey ? 'visibility' : 'visibility_off'"></span>
123 - </div>
124 - </div>
125 - </template>
83 + <div class="advanced-body" x-show="advOpen" x-transition.opacity>
84
127 - <!-- API base URL -->
128 - <div class="field">
129 - <div class="field-label">
130 - <div class="field-title">API base URL</div>
131 - <div class="field-description">Custom endpoint URL. Leave empty to use the provider's default.</div>
132 - </div>
133 - <div class="field-control">
134 - <input type="text" x-model="model.api_base" />
135 - </div>
136 - </div>
85 + <!-- API key (store mode: config.html) -->
86 + <template x-if="apiKeyMode === 'store'">
87 + <div class="field">
88 + <div class="field-label">
89 + <div class="field-title">API key</div>
90 + <div class="field-description">Authentication key for this provider. Shared across all model slots using the same provider.</div>
91 + </div>
92 + <div class="field-control" style="position:relative;" x-data="{ showKey: false }">
93 + <input :type="showKey ? 'text' : 'password'"
94 + :value="$store.modelConfig.apiKeyValues[_prov]"
95 + :placeholder="$store.modelConfig.apiKeyStatus[_prov] ? '&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;' : ''"
96 + autocomplete="off"
97 + @input="$store.modelConfig.setApiKeyValue(_prov, $el.value)"
98 + style="padding-right:32px;" />
99 + <span class="material-symbols-outlined eye-toggle"
100 + @click="
101 + showKey = !showKey;
102 + const prov = _prov;
103 + if (showKey && !$store.modelConfig.apiKeyValues[prov] && $store.modelConfig.apiKeyStatus[prov]) {
104 + $store.modelConfig.revealApiKey(prov).then(v => { if (v) $store.modelConfig.apiKeyValues[prov] = v; });
105 + }
106 + "
107 + x-text="showKey ? 'visibility' : 'visibility_off'"></span>
108 + </div>
109 + </div>
110 + </template>
111
138 - <!-- Context length (not for embedding) -->
139 - <template x-if="modelType !== 'embedding'">
140 - <div class="field">
141 - <div class="field-label">
142 - <div class="field-title">Context length</div>
143 - <div class="field-description">Maximum number of tokens in the context window. System prompt, chat history, RAG and response all count towards this limit.</div>
144 - </div>
145 - <div class="field-control">
146 - <input type="number" x-model.number="model.ctx_length" />
112 + <!-- API key (inline mode: presets) -->
113 + <template x-if="apiKeyMode === 'inline'">
114 + <div class="field">
115 + <div class="field-label">
116 + <div class="field-title">API key</div>
117 + <div class="field-description">Authentication key for this provider. Shared across all model slots using the same provider.</div>
118 + </div>
119 + <div class="field-control" style="position:relative;" x-data="{ showKey: false, _revealed: '' }">
120 + <input :type="showKey ? 'text' : 'password'" x-model="model.api_key" autocomplete="off"
121 + :placeholder="$store.modelConfig.apiKeyStatus[_prov] ? '&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;' : ''"
122 + style="padding-right:32px;" />
123 + <span class="material-symbols-outlined eye-toggle"
124 + @click="
125 + showKey = !showKey;
126 + if (showKey && !model.api_key && $store.modelConfig.apiKeyStatus[_prov]) {
127 + $store.modelConfig.revealApiKey(_prov).then(v => { if (v) { model.api_key = v; _revealed = v; } });
128 + }
129 + if (!showKey && _revealed && model.api_key === _revealed) {
130 + model.api_key = ''; _revealed = '';
131 + }
132 + "
133 + x-text="showKey ? 'visibility' : 'visibility_off'"></span>
134 + </div>
135 + </div>
136 + </template>
137 +
138 + <!-- API base URL -->
139 + <div class="field">
140 + <div class="field-label">
141 + <div class="field-title">API base URL</div>
142 + <div class="field-description">Custom endpoint URL. Leave empty to use the provider's default.</div>
143 + </div>
144 + <div class="field-control">
145 + <input type="text" x-model="model.api_base" />
146 + </div>
147 </div>
148 - </div>
149 - </template>
148
151 - <!-- Chat-specific: ctx_history, vision, max_embeds -->
152 - <template x-if="modelType === 'chat'">
153 - <div>
149 + <!-- Context length (not for embedding) -->
150 + <template x-if="modelType !== 'embedding'">
151 + <div class="field">
152 + <div class="field-label">
153 + <div class="field-title">Context length</div>
154 + <div class="field-description">Maximum number of tokens in the context window. System prompt, chat history, RAG and response all count towards this limit.</div>
155 + </div>
156 + <div class="field-control">
157 + <input type="number" x-model.number="model.ctx_length" />
158 + </div>
159 + </div>
160 + </template>
161 +
162 + <!-- Chat-specific: ctx_history, vision, max_embeds -->
163 + <template x-if="modelType === 'chat'">
164 + <div>
165 + <div class="field">
166 + <div class="field-label">
167 + <div class="field-title">Context window space for chat history</div>
168 + <div class="field-description">Portion of context window dedicated to chat history visible to the agent. Smaller size will result in shorter and more summarized history.</div>
169 + </div>
170 + <div class="field-control">
171 + <input type="range" min="0.01" max="1" step="0.01" x-model.number="model.ctx_history" />
172 + <span class="range-value" x-text="model.ctx_history"></span>
173 + </div>
174 + </div>
175 + <div class="field">
176 + <div class="field-label">
177 + <div class="field-title">Supports Vision</div>
178 + <div class="field-description">Models capable of Vision can for example natively see the content of image attachments.</div>
179 + </div>
180 + <div class="field-control">
181 + <label class="toggle">
182 + <input type="checkbox" x-model="model.vision" />
183 + <span class="toggler"></span>
184 + </label>
185 + </div>
186 + </div>
187 + <template x-if="model.vision">
188 + <div class="field">
189 + <div class="field-label">
190 + <div class="field-title">Max embeds</div>
191 + <div class="field-description">Maximum number of embedded images used by the chat model. Set to 0 for unlimited.</div>
192 + </div>
193 + <div class="field-control">
194 + <input type="number" min="0" x-model.number="model.max_embeds" x-init="if (!model.max_embeds) model.max_embeds = 10" />
195 + </div>
196 + </div>
197 + </template>
198 + </div>
199 + </template>
200 +
201 + <!-- Utility-specific: ctx_input slider -->
202 + <template x-if="modelType === 'utility'">
203 + <div class="field">
204 + <div class="field-label">
205 + <div class="field-title">Context window space for utility model input</div>
206 + <div class="field-description">Portion of context window used for utility model input messages.</div>
207 + </div>
208 + <div class="field-control">
209 + <input type="range" min="0.01" max="1" step="0.01" x-model.number="model.ctx_input" />
210 + <span class="range-value" x-text="model.ctx_input"></span>
211 + </div>
212 + </div>
213 + </template>
214 +
215 + <!-- Rate limits -->
216 <div class="field">
217 <div class="field-label">
156 - <div class="field-title">Context window space for chat history</div>
157 - <div class="field-description">Portion of context window dedicated to chat history visible to the agent. Smaller size will result in shorter and more summarized history.</div>
218 + <div class="field-title">Requests per minute limit</div>
219 + <div class="field-description">Limits the number of requests per minute. Waits if the limit is exceeded. Set to 0 to disable.</div>
220 </div>
221 <div class="field-control">
160 - <input type="range" min="0.01" max="1" step="0.01" x-model.number="model.ctx_history" />
161 - <span class="range-value" x-text="model.ctx_history"></span>
222 + <input type="number" x-model.number="model.rl_requests" />
223 </div>
224 </div>
225 +
226 <div class="field">
227 <div class="field-label">
166 - <div class="field-title">Supports Vision</div>
167 - <div class="field-description">Models capable of Vision can for example natively see the content of image attachments.</div>
228 + <div class="field-title">Input tokens per minute limit</div>
229 + <div class="field-description">Limits the number of input tokens per minute. Waits if the limit is exceeded. Set to 0 to disable.</div>
230 </div>
231 <div class="field-control">
170 - <label class="toggle">
171 - <input type="checkbox" x-model="model.vision" />
172 - <span class="toggler"></span>
173 - </label>
232 + <input type="number" x-model.number="model.rl_input" />
233 </div>
234 </div>
176 - <template x-if="model.vision">
235 +
236 + <template x-if="modelType !== 'embedding'">
237 <div class="field">
238 <div class="field-label">
179 - <div class="field-title">Max embeds</div>
180 - <div class="field-description">Maximum number of embedded images used by the chat model. Set to 0 for unlimited.</div>
239 + <div class="field-title">Output tokens per minute limit</div>
240 + <div class="field-description">Limits the number of output tokens per minute. Waits if the limit is exceeded. Set to 0 to disable.</div>
241 </div>
242 <div class="field-control">
183 - <input type="number" min="0" x-model.number="model.max_embeds" x-init="if (!model.max_embeds) model.max_embeds = 10" />
243 + <input type="number" x-model.number="model.rl_output" />
244 </div>
245 </div>
246 </template>
187 - </div>
188 - </template>
189 -
190 - <!-- Utility-specific: ctx_input slider -->
191 - <template x-if="modelType === 'utility'">
192 - <div class="field">
193 - <div class="field-label">
194 - <div class="field-title">Context window space for utility model input</div>
195 - <div class="field-description">Portion of context window used for utility model input messages.</div>
196 - </div>
197 - <div class="field-control">
198 - <input type="range" min="0.01" max="1" step="0.01" x-model.number="model.ctx_input" />
199 - <span class="range-value" x-text="model.ctx_input"></span>
200 - </div>
201 - </div>
202 - </template>
247
204 - <!-- Rate limits -->
205 - <div class="field">
206 - <div class="field-label">
207 - <div class="field-title">Requests per minute limit</div>
208 - <div class="field-description">Limits the number of requests per minute. Waits if the limit is exceeded. Set to 0 to disable.</div>
209 - </div>
210 - <div class="field-control">
211 - <input type="number" x-model.number="model.rl_requests" />
212 - </div>
213 - </div>
214 -
215 - <div class="field">
216 - <div class="field-label">
217 - <div class="field-title">Input tokens per minute limit</div>
218 - <div class="field-description">Limits the number of input tokens per minute. Waits if the limit is exceeded. Set to 0 to disable.</div>
219 - </div>
220 - <div class="field-control">
221 - <input type="number" x-model.number="model.rl_input" />
222 - </div>
223 - </div>
224 -
225 - <template x-if="modelType !== 'embedding'">
226 - <div class="field">
227 - <div class="field-label">
228 - <div class="field-title">Output tokens per minute limit</div>
229 - <div class="field-description">Limits the number of output tokens per minute. Waits if the limit is exceeded. Set to 0 to disable.</div>
230 - </div>
231 - <div class="field-control">
232 - <input type="number" x-model.number="model.rl_output" />
248 + <!-- Additional parameters -->
249 + <div class="field field-full">
250 + <div class="field-label">
251 + <div class="field-title">Additional parameters</div>
252 + <div class="field-description">
253 + Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines. Value can be JSON objects; unquoted is treated as object/number, quoted as string.
254 + </div>
255 + </div>
256 + <div class="field-control">
257 + <textarea x-model="model._kwargs_text"
258 + @change="model.kwargs = $store.modelConfig.textToKwargs(model._kwargs_text)"></textarea>
259 + </div>
260 </div>
234 - </div>
235 - </template>
261
237 - <!-- Additional parameters -->
238 - <div class="field field-full">
239 - <div class="field-label">
240 - <div class="field-title">Additional parameters</div>
241 - <div class="field-description">
242 - Any other parameters supported by <a href='https://docs.litellm.ai/docs/set_keys' target='_blank'>LiteLLM</a>. Format is KEY=VALUE on individual lines. Value can be JSON objects; unquoted is treated as object/number, quoted as string.
243 - </div>
244 - </div>
245 - <div class="field-control">
246 - <textarea x-model="model._kwargs_text"
247 - @change="model.kwargs = $store.modelConfig.textToKwargs(model._kwargs_text)"></textarea>
262 </div>
263 </div>
264 </div>
@@ -334,6 +348,29 @@
348 .model-search-item.disabled:hover {
349 background: transparent;
350 }
351 +
352 + .advanced-section {
353 + margin-top: 4px;
354 + }
355 + .advanced-toggle {
356 + display: flex;
357 + align-items: center;
358 + gap: 4px;
359 + font-size: 0.78rem;
360 + opacity: 0.6;
361 + cursor: pointer;
362 + user-select: none;
363 + padding: 4px 0;
364 + }
365 + .advanced-toggle:hover {
366 + opacity: 1;
367 + }
368 + .advanced-toggle-icon {
369 + font-size: 16px;
370 + transition: transform 0.15s ease;
371 + }
372 + .advanced-body {
373 + }
374 </style>
375 </body>
376 </html>