main
html 1,273 lines 39.4 KB
Raw
1 <html>
2 <head>
3 <title>OAuth Connections</title>
4 <script type="module">
5 import { store } from "/plugins/_oauth/webui/oauth-config-store.js";
6 </script>
7 </head>
8
9 <body>
10 <div x-data>
11 <template x-if="$store.oauthConfig && config">
12 <div
13 class="oauth"
14 x-init="$store.oauthConfig.init(config, context)"
15 x-effect="$store.oauthConfig.bindConfig(config)"
16 x-destroy="$store.oauthConfig.cleanup()"
17 >
18 <section class="oauth-provider-list">
19 <div class="oauth-section-head">
20 <div>
21 <h3>Providers</h3>
22 <p>Each account can be connected independently.</p>
23 </div>
24 <button class="oauth-icon-button" type="button" @click="$store.oauthConfig.loadStatus()" title="Refresh status" aria-label="Refresh status">
25 <x-icon name="refresh"></x-icon>
26 </button>
27 </div>
28
29 <div class="oauth-provider-rows">
30 <template x-for="card in $store.oauthConfig.providerCards()" :key="card.provider_id">
31 <article
32 class="oauth-provider-row"
33 :class="{ selected: $store.oauthConfig.selectedProviderId === card.provider_id, connected: card.connected }"
34 @click="$store.oauthConfig.selectProvider(card.provider_id)"
35 >
36 <span class="oauth-mark" :class="{ connected: card.connected }">
37 <x-icon :name="card.connected ? 'check' : card.mark"></x-icon>
38 </span>
39 <div class="oauth-provider-row-copy">
40 <strong x-text="card.display_name"></strong>
41 <span x-text="$store.oauthConfig.providerReadinessLabel(card.provider_id)"></span>
42 </div>
43 <span class="oauth-account-state connected" x-show="card.connected">Connected</span>
44 <div class="oauth-row-actions">
45 <button
46 class="oauth-connect compact"
47 type="button"
48 @click.stop="$store.oauthConfig.handleProviderPrimary(card.provider_id)"
49 :disabled="$store.oauthConfig.providerPrimaryDisabled(card.provider_id)"
50 x-show="!card.connected"
51 >
52 <x-icon :name="$store.oauthConfig.connectingProvider === card.provider_id ? 'progress_activity' : ($store.oauthConfig.providerSetupReady(card.provider_id) ? 'login' : 'tune')"></x-icon>
53 <span x-text="$store.oauthConfig.providerPrimaryLabel(card.provider_id)"></span>
54 </button>
55 <button
56 class="oauth-connect danger compact"
57 type="button"
58 @click.stop="$store.oauthConfig.disconnectProvider(card.provider_id)"
59 :disabled="$store.oauthConfig.disconnectingProvider === card.provider_id"
60 x-show="card.connected"
61 >
62 <x-icon :name="$store.oauthConfig.disconnectingProvider === card.provider_id ? 'progress_activity' : 'link_off'"></x-icon>
63 <span x-text="$store.oauthConfig.disconnectingProvider === card.provider_id ? 'Disconnecting' : 'Disconnect'"></span>
64 </button>
65 </div>
66 <div class="oauth-provider-usage" x-show="$store.oauthConfig.usageWindows(card.provider_id).length">
67 <template x-for="window in $store.oauthConfig.usageWindows(card.provider_id)" :key="`${card.provider_id}-${window.key}`">
68 <div class="oauth-usage-window">
69 <div class="oauth-usage-head">
70 <span>
71 <span x-text="`${card.short_name || card.display_name || 'Account'} ${window.title}`"></span>
72 <small x-show="$store.oauthConfig.formatWindowLabel(window)" x-text="$store.oauthConfig.formatWindowLabel(window)"></small>
73 </span>
74 <strong x-text="$store.oauthConfig.formatRemainingPercent(window)"></strong>
75 </div>
76 <div class="oauth-usage-bar" aria-hidden="true">
77 <i :style="{ width: $store.oauthConfig.usageWidth(window) }"></i>
78 </div>
79 <p x-show="$store.oauthConfig.formatReset(window)" x-text="`Resets in ${$store.oauthConfig.formatReset(window)}`"></p>
80 </div>
81 </template>
82 </div>
83
84 <div class="oauth-provider-row-detail" x-show="$store.oauthConfig.providerDetailOpen(card.provider_id)" @click.stop>
85 <div class="oauth-detail-grid">
86 <label class="oauth-provider-input" x-show="$store.oauthConfig.providerShowSetupFields(card.provider_id) && card.supports_enterprise_domain">
87 <span>Enterprise domain</span>
88 <input
89 type="text"
90 x-model="$store.oauthConfig.providerUiFor(card.provider_id).enterprise_domain"
91 name="enterprise_domain"
92 placeholder="github.com"
93 />
94 </label>
95
96 <div class="oauth-provider-fields" x-show="$store.oauthConfig.providerShowSetupFields(card.provider_id) && card.supports_oauth_client_config">
97 <label class="oauth-provider-input">
98 <span>OAuth client ID</span>
99 <input
100 type="text"
101 x-model="$store.oauthConfig.providerUiFor(card.provider_id).client_id"
102 name="oauth_client_id"
103 placeholder="Google Cloud OAuth client ID"
104 />
105 </label>
106 <label class="oauth-provider-input">
107 <span>OAuth client secret</span>
108 <input
109 type="text"
110 class="masked-text-input"
111 x-model="$store.oauthConfig.providerUiFor(card.provider_id).client_secret"
112 name="oauth_client_secret"
113 autocomplete="off"
114 placeholder="Google Cloud OAuth client secret"
115 />
116 </label>
117 <label class="oauth-provider-input" x-show="card.supports_quota_project">
118 <span>Quota project</span>
119 <input
120 type="text"
121 x-model="$store.oauthConfig.providerUiFor(card.provider_id).quota_project_id"
122 name="quota_project_id"
123 placeholder="Optional Google Cloud project ID"
124 />
125 </label>
126 </div>
127
128 <div class="oauth-response-defaults" x-show="card.provider_id === 'codex_oauth'">
129 <div class="oauth-response-defaults-head">
130 <strong>Codex response defaults</strong>
131 <span>Applied when an individual model request does not override them.</span>
132 </div>
133 <label class="oauth-provider-input">
134 <span>Reasoning effort</span>
135 <select x-model="$store.oauthConfig.codex().reasoning_effort" name="codex_reasoning_effort">
136 <option value="default">Model default</option>
137 <option value="minimal">Minimal</option>
138 <option value="low">Low</option>
139 <option value="medium">Medium</option>
140 <option value="high">High</option>
141 <option value="xhigh">Extra high</option>
142 </select>
143 </label>
144 <label class="oauth-provider-input">
145 <span>Reasoning summary</span>
146 <select x-model="$store.oauthConfig.codex().reasoning_summary" name="codex_reasoning_summary">
147 <option value="auto">Auto</option>
148 <option value="concise">Concise</option>
149 <option value="detailed">Detailed</option>
150 <option value="off">Off</option>
151 </select>
152 </label>
153 <label class="oauth-provider-input">
154 <span>Answer verbosity</span>
155 <select x-model="$store.oauthConfig.codex().text_verbosity" name="codex_text_verbosity">
156 <option value="default">Model default</option>
157 <option value="low">Low</option>
158 <option value="medium">Medium</option>
159 <option value="high">High</option>
160 </select>
161 </label>
162 <p class="oauth-provider-note">Effort controls reasoning depth, summaries appear while the agent works, and verbosity shapes the final answer.</p>
163 </div>
164
165 <div class="oauth-device" x-show="$store.oauthConfig.providerDevice(card.provider_id)?.user_code">
166 <span>Enter this code</span>
167 <strong x-text="$store.oauthConfig.providerDevice(card.provider_id)?.user_code"></strong>
168 <button class="text-button" type="button" @click="$store.oauthConfig.cancelConnect(card.provider_id)">
169 <x-icon name="close"></x-icon>
170 <span>Cancel</span>
171 </button>
172 </div>
173
174 <div class="oauth-manual-callback" x-show="card.supports_manual_callback && $store.oauthConfig.providerDevice(card.provider_id)?.flow === 'browser_pkce' && !card.connected">
175 <input
176 type="text"
177 x-model="$store.oauthConfig.providerUiFor(card.provider_id).manualCallback"
178 placeholder="Paste callback URL, query string, or code"
179 />
180 <button type="button" class="oauth-connect secondary" @click="$store.oauthConfig.submitManualCallback(card.provider_id)">
181 <x-icon name="check"></x-icon>
182 <span>Submit</span>
183 </button>
184 <button type="button" class="oauth-connect secondary" @click="$store.oauthConfig.cancelConnect(card.provider_id)">
185 <x-icon name="close"></x-icon>
186 <span>Cancel</span>
187 </button>
188 </div>
189
190 <div class="oauth-auth-attempt" x-show="$store.oauthConfig.providerDevice(card.provider_id) && !$store.oauthConfig.providerDevice(card.provider_id)?.user_code && !card.supports_manual_callback">
191 <span>Sign-in started</span>
192 <button class="text-button" type="button" @click="$store.oauthConfig.cancelConnect(card.provider_id)">
193 <x-icon name="close"></x-icon>
194 <span>Cancel</span>
195 </button>
196 </div>
197
198 <p class="oauth-provider-note" x-show="$store.oauthConfig.providerShowNote(card.provider_id)" x-text="card.warning || card.note"></p>
199 </div>
200 </div>
201 </article>
202 </template>
203 </div>
204 </section>
205
206 <section class="oauth-model-config">
207 <div class="oauth-section-head">
208 <div>
209 <h3>Choose your models</h3>
210 </div>
211 <span class="oauth-save-chip" x-show="$store.oauthConfig.modelConfigDirty">Pending changes</span>
212 </div>
213
214 <div class="oauth-model-loading" x-show="$store.oauthConfig.modelConfigLoading">
215 Loading model configuration...
216 </div>
217
218 <template x-if="$store.oauthConfig.modelConfig">
219 <div class="oauth-model-grid">
220 <template x-for="slot in $store.oauthConfig.modelSlots" :key="slot.key">
221 <div class="oauth-model-card">
222 <div class="oauth-model-head">
223 <x-icon class="oauth-model-icon" :name="slot.icon"></x-icon>
224 <div class="oauth-model-title">
225 <strong x-text="slot.title"></strong>
226 <span x-show="$store.oauthConfig.slotStatusLabel(slot.key)" x-text="$store.oauthConfig.slotStatusLabel(slot.key)"></span>
227 </div>
228 <div class="oauth-model-actions">
229 <button
230 class="oauth-model-action icon"
231 type="button"
232 title="Copy Main model"
233 aria-label="Copy Main model"
234 x-show="slot.key === 'utility_model' && $store.oauthConfig.slotUsesOauth('chat_model')"
235 @click="$store.oauthConfig.copyMainToUtility()"
236 >
237 <x-icon name="content_copy"></x-icon>
238 </button>
239 </div>
240 </div>
241
242 <div class="oauth-model-picker" @click.outside="$store.oauthConfig.closeModelDropdown(slot.key)">
243 <label class="oauth-model-provider-field">
244 <select
245 :aria-label="`${slot.title} provider`"
246 :value="$store.oauthConfig.slotCanUseModels(slot.key) ? $store.oauthConfig.modelSlot(slot.key).provider : ''"
247 @change="$store.oauthConfig.useProviderForSlot(slot.key, $event.target.value)"
248 :disabled="!$store.oauthConfig.connectedProviderCards().length"
249 >
250 <option value="" disabled>Choose connected provider</option>
251 <template x-for="provider in $store.oauthConfig.slotProviderChoices(slot.key)" :key="`${slot.key}-${provider.provider_id}`">
252 <option :value="provider.provider_id" x-text="$store.oauthConfig.modelProviderOptionLabel(provider)"></option>
253 </template>
254 </select>
255 </label>
256
257 <div class="oauth-model-input-row">
258 <input
259 type="text"
260 x-model="$store.oauthConfig.modelSlot(slot.key).name"
261 @input="$store.oauthConfig.markModelDirty(slot.key)"
262 @click="$store.oauthConfig.openModelDropdown(slot.key, $el)"
263 :disabled="!$store.oauthConfig.slotCanUseModels(slot.key)"
264 :placeholder="$store.oauthConfig.slotCanUseModels(slot.key) ? 'Search or enter a provider model' : 'Choose a connected provider first'"
265 />
266 <button
267 class="oauth-model-search"
268 type="button"
269 title="Browse available models"
270 aria-label="Browse available models"
271 @click="$store.oauthConfig.openModelDropdown(slot.key, $el)"
272 :disabled="!$store.oauthConfig.slotCanUseModels(slot.key) || $store.oauthConfig.loadingModelsProvider === $store.oauthConfig.modelSlot(slot.key).provider"
273 >
274 <x-icon :name="$store.oauthConfig.loadingModelsProvider === $store.oauthConfig.modelSlot(slot.key).provider ? 'progress_activity' : 'search'"></x-icon>
275 </button>
276 </div>
277
278 <div
279 class="oauth-model-dropdown"
280 x-show="$store.oauthConfig.modelDropdown[slot.key]?.open && !$store.oauthConfig.loadingModelsProvider"
281 x-transition.opacity
282 >
283 <template x-for="model in $store.oauthConfig.filteredModels(slot.key)" :key="slot.key + model">
284 <button
285 type="button"
286 class="oauth-model-item"
287 @click="$store.oauthConfig.selectModel(slot.key, model)"
288 >
289 <span
290 class="oauth-model-name"
291 x-text="model"
292 ></span>
293 <small
294 x-show="$store.oauthConfig.modelDescription(slot.provider, model)"
295 x-text="$store.oauthConfig.modelDescription(slot.provider, model)"
296 ></small>
297 </button>
298 </template>
299 <div class="oauth-model-item muted" x-show="$store.oauthConfig.filteredModels(slot.key).length === 0">
300 No models found. You can still type the model name manually.
301 </div>
302 </div>
303 </div>
304 </div>
305 </template>
306 </div>
307 </template>
308 </section>
309
310 <section class="oauth-models-panel" x-show="$store.oauthConfig.activeProviderModels().length">
311 <div class="oauth-section-head">
312 <div>
313 <h3>Available models</h3>
314 <p x-text="$store.oauthConfig.activeModelsDescription()"></p>
315 </div>
316 </div>
317 <div class="oauth-models">
318 <template x-for="model in $store.oauthConfig.activeProviderModels()" :key="model">
319 <span
320 :title="$store.oauthConfig.modelDescription($store.oauthConfig.activeModelProvider, model)"
321 x-text="model"
322 ></span>
323 </template>
324 </div>
325 </section>
326
327 <details class="oauth-advanced">
328 <summary>Advanced</summary>
329
330 <div class="oauth-details">
331 <div>
332 <span>Codex endpoint</span>
333 <code x-text="$store.oauthConfig.providerEndpointUrl('codex_oauth')"></code>
334 </div>
335 <div>
336 <span>Auth file</span>
337 <code x-text="$store.oauthConfig.status?.codex?.auth_file_path || 'Agent Zero private store'"></code>
338 </div>
339 </div>
340
341 <div class="oauth-grid">
342 <label>
343 <span>Auth file path</span>
344 <input type="text" x-model="$store.oauthConfig.codex().auth_file_path" placeholder="Agent Zero private store" />
345 <small>Use an Agent Zero-owned file. Shared Codex CLI auth files are rejected.</small>
346 </label>
347 <label>
348 <span>Codex issuer</span>
349 <input type="text" x-model="$store.oauthConfig.codex().issuer" />
350 </label>
351 <label>
352 <span>Codex token URL</span>
353 <input type="text" x-model="$store.oauthConfig.codex().token_url" />
354 </label>
355 <label>
356 <span>Codex base path</span>
357 <input type="text" x-model="$store.oauthConfig.codex().proxy_base_path" />
358 </label>
359 <label>
360 <span>Codex upstream URL</span>
361 <input type="text" x-model="$store.oauthConfig.codex().upstream_base_url" />
362 </label>
363 <label>
364 <span>Codex version</span>
365 <input type="text" x-model="$store.oauthConfig.codex().codex_version" placeholder="Auto" />
366 </label>
367 <label class="oauth-switch">
368 <span>Codex proxy token required</span>
369 <input type="checkbox" x-model="$store.oauthConfig.codex().require_proxy_token" />
370 </label>
371 <label>
372 <span>Codex proxy token</span>
373 <input type="text" class="masked-text-input" x-model="$store.oauthConfig.codex().proxy_token" autocomplete="off" />
374 </label>
375 </div>
376 </details>
377 </div>
378 </template>
379 </div>
380
381 <style>
382 .oauth {
383 display: flex;
384 flex-direction: column;
385 gap: 14px;
386 color: var(--color-text);
387 }
388
389 .oauth-provider-list {
390 display: grid;
391 gap: 12px;
392 padding: 0;
393 border: 0;
394 border-radius: 0;
395 background: transparent;
396 }
397
398 .oauth-provider-row-copy strong {
399 overflow: hidden;
400 display: block;
401 font-size: 0.88rem;
402 text-overflow: ellipsis;
403 white-space: nowrap;
404 }
405
406 .oauth-provider-rows {
407 display: grid;
408 gap: 8px;
409 }
410
411 .oauth-provider-row {
412 display: grid;
413 grid-template-columns: auto minmax(0, 1fr) auto auto;
414 align-items: center;
415 gap: 10px;
416 min-height: 62px;
417 padding: 10px 12px;
418 border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
419 border-radius: 8px;
420 background: color-mix(in srgb, var(--color-background) 24%, transparent);
421 cursor: pointer;
422 transition: border-color .18s ease, background .18s ease;
423 }
424
425 .oauth-provider-row:hover,
426 .oauth-provider-row.selected {
427 border-color: color-mix(in srgb, var(--color-primary) 62%, var(--color-border));
428 background: color-mix(in srgb, var(--color-primary) 7%, var(--color-panel));
429 }
430
431 .oauth-provider-row-copy {
432 display: grid;
433 min-width: 0;
434 gap: 3px;
435 }
436
437 .oauth-provider-row-copy span {
438 overflow: hidden;
439 color: var(--color-text-secondary);
440 font-size: 0.74rem;
441 font-weight: 700;
442 text-overflow: ellipsis;
443 white-space: nowrap;
444 }
445
446 .oauth-account-state {
447 display: inline-flex;
448 align-items: center;
449 justify-content: center;
450 min-height: 24px;
451 padding: 2px 8px;
452 border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
453 border-radius: 999px;
454 color: var(--color-text-secondary);
455 font-size: 0.68rem;
456 font-weight: 800;
457 white-space: nowrap;
458 }
459
460 .oauth-account-state.connected {
461 border-color: color-mix(in srgb, #35d07f 46%, var(--color-border));
462 color: var(--color-text);
463 }
464
465 .oauth .oauth-provider-row.connected .oauth-provider-row-copy strong {
466 color: var(--color-text, #fff);
467 }
468
469 .oauth .oauth-provider-row.connected .oauth-provider-row-copy span {
470 color: var(--color-text-secondary, rgba(255, 255, 255, .72));
471 }
472
473 .oauth-row-actions {
474 display: flex;
475 flex-wrap: wrap;
476 align-items: center;
477 justify-content: flex-end;
478 gap: 8px;
479 }
480
481 .oauth-provider-usage {
482 display: grid;
483 grid-column: 2 / -1;
484 grid-template-columns: repeat(2, minmax(0, 1fr));
485 gap: 10px;
486 min-width: 0;
487 padding: 4px 0 0;
488 }
489
490 .oauth-provider-row-detail {
491 display: grid;
492 grid-column: 1 / -1;
493 min-width: 0;
494 padding-top: 2px;
495 }
496
497 .oauth-detail-grid {
498 display: grid;
499 gap: 12px;
500 }
501
502 .oauth-provider-grid {
503 display: grid;
504 grid-template-columns: repeat(3, minmax(0, 1fr));
505 gap: 12px;
506 }
507
508 .oauth-provider-card {
509 display: grid;
510 align-content: start;
511 gap: 14px;
512 min-width: 0;
513 min-height: 188px;
514 padding: 16px;
515 border: 1px solid var(--color-border);
516 border-radius: 8px;
517 background: color-mix(in srgb, var(--color-panel) 86%, transparent);
518 }
519
520 .oauth-provider-card.is-connected .oauth-mark {
521 color: #08120c;
522 background: #35d07f;
523 }
524
525 .oauth-mark.connected {
526 color: #08120c;
527 background: #35d07f;
528 }
529
530 .oauth-provider-head {
531 display: grid;
532 grid-template-columns: 48px minmax(0, 1fr);
533 align-items: center;
534 gap: 12px;
535 }
536
537 .oauth-mark {
538 display: grid;
539 width: 48px;
540 height: 48px;
541 place-items: center;
542 border-radius: 50%;
543 background: color-mix(in srgb, var(--color-border) 52%, transparent);
544 }
545
546 .oauth-mark .material-symbols-outlined {
547 font-size: 26px;
548 }
549
550 .oauth-copy h2 {
551 margin: 0 0 4px;
552 font-size: 1rem;
553 letter-spacing: 0;
554 }
555
556 .oauth-copy p {
557 overflow: hidden;
558 margin: 0;
559 color: var(--color-text-secondary);
560 font-size: 0.82rem;
561 line-height: 1.35;
562 text-overflow: ellipsis;
563 white-space: nowrap;
564 }
565
566 .oauth-primary {
567 display: flex;
568 flex-wrap: wrap;
569 justify-content: flex-start;
570 gap: 8px;
571 }
572
573 .oauth-connect {
574 display: inline-flex;
575 align-items: center;
576 justify-content: center;
577 gap: 8px;
578 min-width: 112px;
579 min-height: 38px;
580 padding: 0 13px;
581 border: 0;
582 border-radius: 8px;
583 background: #f5f7fa;
584 color: #111418;
585 font: inherit;
586 font-size: 0.84rem;
587 font-weight: 750;
588 cursor: pointer;
589 }
590
591 .oauth-connect.secondary {
592 border: 1px solid color-mix(in srgb, var(--color-border) 76%, transparent);
593 background: color-mix(in srgb, var(--color-border) 44%, transparent);
594 color: var(--color-text);
595 }
596
597 .oauth-connect.danger {
598 border: 1px solid color-mix(in srgb, #f06464 40%, var(--color-border));
599 background: color-mix(in srgb, #f06464 16%, var(--color-panel));
600 color: var(--color-text);
601 }
602
603 .oauth-connect:disabled {
604 cursor: default;
605 opacity: .65;
606 }
607
608 .oauth-connect.compact {
609 min-width: 0;
610 min-height: 34px;
611 padding: 0 10px;
612 font-size: 0.76rem;
613 white-space: nowrap;
614 }
615
616 .oauth-provider-input,
617 .oauth-provider-fields,
618 .oauth-manual-callback,
619 .oauth-auth-attempt {
620 display: grid;
621 min-width: 0;
622 gap: 6px;
623 }
624
625 .oauth-provider-fields {
626 gap: 8px;
627 }
628
629 .oauth-response-defaults {
630 display: grid;
631 grid-template-columns: repeat(3, minmax(0, 1fr));
632 gap: 10px;
633 padding: 12px;
634 border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
635 border-radius: 8px;
636 background: color-mix(in srgb, var(--color-panel) 82%, transparent);
637 }
638
639 .oauth-response-defaults-head,
640 .oauth-response-defaults .oauth-provider-note {
641 grid-column: 1 / -1;
642 }
643
644 .oauth-response-defaults-head {
645 display: grid;
646 gap: 2px;
647 }
648
649 .oauth-response-defaults-head strong {
650 font-size: 0.84rem;
651 }
652
653 .oauth-response-defaults-head span {
654 color: var(--color-text-secondary);
655 font-size: 0.76rem;
656 }
657
658 .oauth-manual-callback {
659 grid-template-columns: minmax(0, 1fr) auto auto;
660 align-items: center;
661 gap: 8px;
662 }
663
664 .oauth-auth-attempt {
665 grid-template-columns: minmax(0, 1fr) auto;
666 align-items: center;
667 padding: 10px 12px;
668 border: 1px solid color-mix(in srgb, #f5f7fa 18%, var(--color-border));
669 border-radius: 8px;
670 background: color-mix(in srgb, #f5f7fa 7%, var(--color-panel));
671 }
672
673 .oauth-provider-input span,
674 .oauth-auth-attempt span {
675 color: var(--color-text-secondary);
676 font-size: 0.76rem;
677 font-weight: 700;
678 }
679
680 .oauth-provider-input input,
681 .oauth-provider-input select,
682 .oauth-manual-callback input {
683 width: 100%;
684 min-width: 0;
685 min-height: 36px;
686 padding: 7px 10px;
687 border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
688 border-radius: 8px;
689 background: var(--color-input);
690 color: var(--color-text);
691 font: inherit;
692 font-size: 0.82rem;
693 }
694
695 .oauth-provider-note {
696 margin: 0;
697 color: var(--color-text-secondary);
698 font-size: 0.76rem;
699 line-height: 1.35;
700 }
701
702 .oauth-device {
703 display: grid;
704 grid-template-columns: minmax(0, 1fr) auto auto;
705 align-items: center;
706 gap: 10px;
707 padding: 10px 12px;
708 border: 1px solid color-mix(in srgb, #f5f7fa 18%, var(--color-border));
709 border-radius: 8px;
710 background: color-mix(in srgb, #f5f7fa 7%, var(--color-panel));
711 }
712
713 .oauth-device span {
714 color: var(--color-text-secondary);
715 font-size: 0.78rem;
716 font-weight: 700;
717 }
718
719 .oauth-device strong {
720 font-size: 1.12rem;
721 letter-spacing: 0;
722 }
723
724 .oauth-usage-window {
725 display: grid;
726 min-width: 0;
727 gap: 8px;
728 }
729
730 .oauth-usage-head {
731 display: flex;
732 align-items: center;
733 justify-content: space-between;
734 gap: 12px;
735 }
736
737 .oauth-usage-head span {
738 display: inline-flex;
739 min-width: 0;
740 align-items: center;
741 gap: 6px;
742 color: var(--color-text-secondary);
743 font-size: 0.78rem;
744 font-weight: 750;
745 }
746
747 .oauth .oauth-provider-row.connected .oauth-usage-head span {
748 color: var(--color-text-secondary, rgba(255, 255, 255, .72));
749 }
750
751 .oauth .oauth-provider-row.connected .oauth-usage-head strong {
752 color: var(--color-text, #fff);
753 }
754
755 .oauth .oauth-provider-row.connected .oauth-usage-window p {
756 color: var(--color-text-secondary, rgba(255, 255, 255, .72));
757 }
758
759 .oauth-usage-head small {
760 padding: 2px 6px;
761 border-radius: 999px;
762 background: color-mix(in srgb, var(--color-border) 55%, transparent);
763 color: var(--color-text-secondary);
764 font-size: 0.7rem;
765 line-height: 1;
766 }
767
768 .oauth-usage-head strong {
769 font-size: 0.92rem;
770 white-space: nowrap;
771 }
772
773 .oauth-usage-bar {
774 overflow: hidden;
775 height: 8px;
776 border-radius: 999px;
777 background: color-mix(in srgb, var(--color-border) 54%, transparent);
778 }
779
780 .oauth-usage-bar i {
781 display: block;
782 width: 0;
783 height: 100%;
784 border-radius: inherit;
785 background: #35d07f;
786 transition: width .22s ease;
787 }
788
789 .oauth-usage-window p {
790 margin: 0;
791 color: var(--color-text-secondary);
792 font-size: 0.74rem;
793 }
794
795 .oauth-status-row {
796 display: grid;
797 grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
798 align-items: center;
799 gap: 10px;
800 padding: 12px 14px;
801 border: 1px solid var(--color-border);
802 border-radius: 8px;
803 }
804
805 .oauth-status-row div {
806 display: flex;
807 min-width: 0;
808 flex-direction: column;
809 gap: 3px;
810 }
811
812 .oauth-status-row span,
813 .oauth-details span,
814 .oauth-grid label span {
815 color: var(--color-text-secondary);
816 font-size: 0.76rem;
817 font-weight: 700;
818 }
819
820 .oauth-status-row strong {
821 overflow: hidden;
822 font-size: 0.88rem;
823 text-overflow: ellipsis;
824 white-space: nowrap;
825 }
826
827 .oauth-icon-button {
828 display: grid;
829 width: 34px;
830 height: 34px;
831 place-items: center;
832 border: 1px solid var(--color-border);
833 border-radius: 8px;
834 background: transparent;
835 color: var(--color-text);
836 cursor: pointer;
837 }
838
839 .oauth-model-config {
840 display: grid;
841 gap: 12px;
842 padding: 0;
843 border: 0;
844 }
845
846 .oauth-models-panel {
847 display: grid;
848 gap: 10px;
849 padding: 0;
850 border: 0;
851 }
852
853 .oauth-section-head {
854 display: flex;
855 align-items: flex-start;
856 justify-content: space-between;
857 gap: 12px;
858 }
859
860 .oauth-section-head h3 {
861 margin: 0 0 4px;
862 font-size: 1rem;
863 letter-spacing: 0;
864 }
865
866 .oauth-section-head p,
867 .oauth-model-loading {
868 margin: 0;
869 color: var(--color-text-secondary);
870 font-size: 0.8rem;
871 line-height: 1.35;
872 }
873
874 .oauth-save-chip {
875 flex: 0 0 auto;
876 padding: 3px 8px;
877 border: 1px solid color-mix(in srgb, #d9ad68 46%, var(--color-border));
878 border-radius: 999px;
879 color: #d9ad68;
880 font-size: 0.72rem;
881 font-weight: 750;
882 }
883
884 .oauth-model-grid {
885 display: grid;
886 grid-template-columns: repeat(2, minmax(0, 1fr));
887 gap: 12px;
888 }
889
890 .oauth-model-card {
891 display: grid;
892 min-width: 0;
893 gap: 10px;
894 padding: 0;
895 border: 0;
896 background: transparent;
897 }
898
899 .oauth-model-head {
900 display: grid;
901 grid-template-columns: auto minmax(0, 1fr) auto;
902 align-items: center;
903 gap: 10px;
904 }
905
906 .oauth-model-icon {
907 display: grid;
908 width: 34px;
909 height: 34px;
910 place-items: center;
911 border-radius: 8px;
912 background: color-mix(in srgb, var(--color-border) 52%, transparent);
913 color: var(--color-text);
914 font-size: 19px;
915 }
916
917 .oauth-model-title {
918 display: grid;
919 min-width: 0;
920 gap: 2px;
921 }
922
923 .oauth-model-title strong {
924 overflow: hidden;
925 font-size: 0.88rem;
926 text-overflow: ellipsis;
927 white-space: nowrap;
928 }
929
930 .oauth-model-title span {
931 overflow: hidden;
932 color: var(--color-text-secondary);
933 font-size: 0.74rem;
934 font-weight: 700;
935 text-overflow: ellipsis;
936 white-space: nowrap;
937 }
938
939 .oauth-model-actions {
940 display: flex;
941 flex-wrap: wrap;
942 align-items: center;
943 justify-content: flex-end;
944 gap: 6px;
945 }
946
947 .oauth-model-action {
948 display: inline-flex;
949 align-items: center;
950 justify-content: center;
951 gap: 5px;
952 min-height: 32px;
953 padding: 0 9px;
954 border: 1px solid color-mix(in srgb, var(--color-border) 76%, transparent);
955 border-radius: 8px;
956 background: transparent;
957 color: var(--color-text);
958 font-size: 0.76rem;
959 font-weight: 750;
960 white-space: nowrap;
961 cursor: pointer;
962 }
963
964 .oauth-model-action.icon {
965 width: 34px;
966 height: 34px;
967 padding: 0;
968 }
969
970 .oauth-model-action .material-symbols-outlined {
971 font-size: 18px;
972 }
973
974 .oauth-model-action:disabled {
975 cursor: default;
976 opacity: .45;
977 }
978
979 .oauth-model-picker {
980 position: relative;
981 display: grid;
982 gap: 8px;
983 min-width: 0;
984 }
985
986 .oauth-model-provider-field {
987 display: grid;
988 min-width: 0;
989 gap: 6px;
990 }
991
992 .oauth-model-provider-field span {
993 color: var(--color-text-secondary);
994 font-size: 0.74rem;
995 font-weight: 750;
996 }
997
998 .oauth-model-provider-field select {
999 width: 100%;
1000 min-width: 0;
1001 min-height: 36px;
1002 padding: 7px 10px;
1003 border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
1004 border-radius: 8px;
1005 background: var(--color-input);
1006 color: var(--color-text);
1007 font: inherit;
1008 font-size: 0.82rem;
1009 }
1010
1011 .oauth-model-provider-field select:disabled {
1012 opacity: .58;
1013 }
1014
1015 .oauth-model-input-row {
1016 position: relative;
1017 }
1018
1019 .oauth-model-input-row input {
1020 width: 100%;
1021 min-width: 0;
1022 padding: .5rem 32px .5rem .5rem;
1023 border: 1px solid var(--color-border);
1024 border-radius: .25rem;
1025 background: var(--color-background);
1026 color: var(--color-text);
1027 font-family: "Rubik", Arial, Helvetica, sans-serif;
1028 }
1029
1030 .oauth-model-input-row input:disabled {
1031 opacity: .58;
1032 }
1033
1034 .oauth-model-search {
1035 position: absolute;
1036 z-index: 1;
1037 top: 50%;
1038 right: 8px;
1039 display: grid;
1040 width: 20px;
1041 height: 20px;
1042 padding: 0;
1043 transform: translateY(-50%);
1044 place-items: center;
1045 border: 0;
1046 background: transparent;
1047 color: var(--color-text);
1048 cursor: pointer;
1049 opacity: .6;
1050 }
1051
1052 .oauth-model-search:hover {
1053 opacity: 1;
1054 }
1055
1056 .oauth-model-search:disabled {
1057 cursor: default;
1058 opacity: .45;
1059 }
1060
1061 .oauth-model-search .material-symbols-outlined {
1062 font-size: 18px;
1063 }
1064
1065 .oauth-model-dropdown {
1066 position: absolute;
1067 z-index: 50;
1068 top: calc(100% + 4px);
1069 right: 0;
1070 left: 0;
1071 overflow: auto;
1072 max-height: 200px;
1073 padding: 4px;
1074 border: 1px solid var(--color-border);
1075 border-radius: 6px;
1076 background: var(--color-input);
1077 box-shadow: 0 4px 12px rgba(0, 0, 0, .3);
1078 }
1079
1080 .oauth-model-item {
1081 display: flex;
1082 flex-direction: column;
1083 gap: 3px;
1084 width: 100%;
1085 min-height: 30px;
1086 padding: 5px 8px;
1087 border: 0;
1088 border-radius: 4px;
1089 background: transparent;
1090 color: var(--color-text);
1091 font: inherit;
1092 font-size: 0.8rem;
1093 text-align: left;
1094 cursor: pointer;
1095 }
1096
1097 .oauth-model-name {
1098 font-weight: 650;
1099 }
1100
1101 .oauth-model-item small {
1102 overflow: hidden;
1103 color: var(--color-text-secondary);
1104 font-size: 0.72rem;
1105 line-height: 1.3;
1106 text-overflow: ellipsis;
1107 white-space: nowrap;
1108 }
1109
1110 .oauth-model-item:hover,
1111 .oauth-model-item:focus-visible {
1112 background: color-mix(in srgb, var(--color-border) 42%, transparent);
1113 outline: none;
1114 }
1115
1116 .oauth-model-item.muted {
1117 color: var(--color-text-secondary);
1118 cursor: default;
1119 }
1120
1121 .oauth-advanced {
1122 border: 0;
1123 border-radius: 0;
1124 padding: 0;
1125 }
1126
1127 .oauth-advanced summary {
1128 padding: 8px 0;
1129 color: var(--color-text-secondary);
1130 font-size: 0.84rem;
1131 font-weight: 750;
1132 cursor: pointer;
1133 }
1134
1135 .oauth-details {
1136 display: grid;
1137 gap: 8px;
1138 padding: 0 14px 14px;
1139 }
1140
1141 .oauth-details div {
1142 display: grid;
1143 grid-template-columns: 116px minmax(0, 1fr);
1144 align-items: center;
1145 gap: 10px;
1146 }
1147
1148 .oauth-details code {
1149 overflow: hidden;
1150 font-size: 0.76rem;
1151 text-overflow: ellipsis;
1152 white-space: nowrap;
1153 }
1154
1155 .oauth-grid {
1156 display: grid;
1157 grid-template-columns: repeat(2, minmax(0, 1fr));
1158 gap: 12px;
1159 padding: 0 14px 14px;
1160 }
1161
1162 .oauth-grid label {
1163 display: flex;
1164 min-width: 0;
1165 flex-direction: column;
1166 gap: 6px;
1167 }
1168
1169 .oauth-grid small {
1170 color: var(--color-text-secondary);
1171 font-size: 0.72rem;
1172 line-height: 1.35;
1173 }
1174
1175 .oauth-grid input[type="text"],
1176 .oauth-grid input[type="password"] {
1177 width: 100%;
1178 min-height: 36px;
1179 padding: 7px 10px;
1180 border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
1181 border-radius: 8px;
1182 background: var(--color-input);
1183 color: var(--color-text);
1184 font: inherit;
1185 font-size: 0.82rem;
1186 }
1187
1188 .oauth-switch {
1189 justify-content: center;
1190 }
1191
1192 .oauth-switch input {
1193 width: 18px;
1194 height: 18px;
1195 accent-color: #f5f7fa;
1196 }
1197
1198 .oauth-models {
1199 display: flex;
1200 flex-wrap: wrap;
1201 gap: 8px;
1202 }
1203
1204 .oauth-models span {
1205 padding: 5px 8px;
1206 border-radius: 999px;
1207 background: color-mix(in srgb, var(--color-border) 34%, transparent);
1208 color: var(--color-text-secondary);
1209 font-size: 0.76rem;
1210 }
1211
1212 @media (max-width: 980px) {
1213 .oauth-provider-grid {
1214 grid-template-columns: 1fr;
1215 }
1216
1217 .oauth-model-grid {
1218 grid-template-columns: 1fr;
1219 }
1220
1221 .oauth-provider-row {
1222 grid-template-columns: auto minmax(0, 1fr) auto;
1223 }
1224
1225 .oauth-row-actions {
1226 grid-column: 2 / -1;
1227 justify-content: flex-start;
1228 }
1229 }
1230
1231 @media (max-width: 720px) {
1232 .oauth-device,
1233 .oauth-response-defaults,
1234 .oauth-provider-row-detail,
1235 .oauth-provider-usage,
1236 .oauth-status-row,
1237 .oauth-model-grid,
1238 .oauth-model-head,
1239 .oauth-grid,
1240 .oauth-details div,
1241 .oauth-manual-callback,
1242 .oauth-auth-attempt,
1243 .oauth-provider-row {
1244 grid-template-columns: 1fr;
1245 }
1246
1247 .oauth-section-head {
1248 flex-direction: column;
1249 }
1250
1251 .oauth-primary {
1252 width: 100%;
1253 }
1254
1255 .oauth-row-actions {
1256 grid-column: auto;
1257 }
1258
1259 .oauth-provider-usage {
1260 grid-column: auto;
1261 }
1262
1263 .oauth-model-actions {
1264 justify-content: flex-start;
1265 }
1266
1267 .oauth-connect {
1268 width: 100%;
1269 }
1270 }
1271 </style>
1272 </body>
1273 </html>