@ouraihub/aether-ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +789 -0
  2. package/fixtures/model-classify.json +42 -0
  3. package/fixtures/pricing-cost.json +56 -0
  4. package/fixtures/reasoning-infer.json +44 -0
  5. package/fixtures/sse/anthropic-messages/chinese.sse +24 -0
  6. package/fixtures/sse/anthropic-messages/multi-tool.sse +33 -0
  7. package/fixtures/sse/anthropic-messages/single-tool.sse +39 -0
  8. package/fixtures/sse/anthropic-messages/text.sse +27 -0
  9. package/fixtures/sse/anthropic-messages/truncated.sse +21 -0
  10. package/fixtures/sse/anthropic-messages/upstream-error.sse +12 -0
  11. package/fixtures/sse/expect.json +20 -0
  12. package/fixtures/sse/openai-chat/chinese.sse +14 -0
  13. package/fixtures/sse/openai-chat/multi-tool.sse +18 -0
  14. package/fixtures/sse/openai-chat/single-tool.sse +22 -0
  15. package/fixtures/sse/openai-chat/text.sse +14 -0
  16. package/fixtures/sse/openai-chat/truncated.sse +12 -0
  17. package/fixtures/sse/openai-chat/upstream-error.sse +6 -0
  18. package/fixtures/sse/openai-responses/chinese.sse +33 -0
  19. package/fixtures/sse/openai-responses/multi-tool.sse +36 -0
  20. package/fixtures/sse/openai-responses/single-tool.sse +27 -0
  21. package/fixtures/sse/openai-responses/text.sse +33 -0
  22. package/fixtures/sse/openai-responses/truncated.sse +30 -0
  23. package/fixtures/sse/openai-responses/upstream-error.sse +9 -0
  24. package/package.json +71 -3
  25. package/src/CopyInput.svelte +33 -0
  26. package/src/KitNotifier.svelte +33 -0
  27. package/src/chat/ApprovalCard.svelte +23 -0
  28. package/src/chat/ChatPanel.svelte +2097 -0
  29. package/src/chat/MessageItem.svelte +583 -0
  30. package/src/chat/SkillPicker.svelte +126 -0
  31. package/src/chat/ToolCallItem.svelte +23 -0
  32. package/src/chat/UsageDetails.svelte +138 -0
  33. package/src/chat/index.ts +2 -0
  34. package/src/chat/run-session.svelte.ts +556 -0
  35. package/src/components/ui/alert/alert-action.svelte +20 -0
  36. package/src/components/ui/alert/alert-description.svelte +23 -0
  37. package/src/components/ui/alert/alert-title.svelte +23 -0
  38. package/src/components/ui/alert/alert.svelte +43 -0
  39. package/src/components/ui/alert/index.ts +17 -0
  40. package/src/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
  41. package/src/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
  42. package/src/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
  43. package/src/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  44. package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
  45. package/src/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  46. package/src/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
  47. package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
  48. package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  49. package/src/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  50. package/src/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  51. package/src/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  52. package/src/components/ui/alert-dialog/index.ts +40 -0
  53. package/src/components/ui/button/button.svelte +81 -0
  54. package/src/components/ui/button/index.ts +17 -0
  55. package/src/components/ui/dialog/dialog-close.svelte +11 -0
  56. package/src/components/ui/dialog/dialog-content.svelte +48 -0
  57. package/src/components/ui/dialog/dialog-description.svelte +17 -0
  58. package/src/components/ui/dialog/dialog-footer.svelte +32 -0
  59. package/src/components/ui/dialog/dialog-header.svelte +20 -0
  60. package/src/components/ui/dialog/dialog-overlay.svelte +17 -0
  61. package/src/components/ui/dialog/dialog-portal.svelte +7 -0
  62. package/src/components/ui/dialog/dialog-title.svelte +17 -0
  63. package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
  64. package/src/components/ui/dialog/dialog.svelte +7 -0
  65. package/src/components/ui/dialog/index.ts +34 -0
  66. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  67. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  68. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  69. package/src/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  70. package/src/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  71. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  72. package/src/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  73. package/src/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  74. package/src/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +17 -0
  75. package/src/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  76. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  77. package/src/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  78. package/src/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  79. package/src/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  80. package/src/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  81. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  82. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  83. package/src/components/ui/dropdown-menu/index.ts +54 -0
  84. package/src/components/ui/empty/empty-content.svelte +23 -0
  85. package/src/components/ui/empty/empty-description.svelte +23 -0
  86. package/src/components/ui/empty/empty-header.svelte +20 -0
  87. package/src/components/ui/empty/empty-media.svelte +41 -0
  88. package/src/components/ui/empty/empty-title.svelte +20 -0
  89. package/src/components/ui/empty/empty.svelte +23 -0
  90. package/src/components/ui/empty/index.ts +22 -0
  91. package/src/components/ui/field/field-content.svelte +20 -0
  92. package/src/components/ui/field/field-description.svelte +25 -0
  93. package/src/components/ui/field/field-error.svelte +58 -0
  94. package/src/components/ui/field/field-group.svelte +23 -0
  95. package/src/components/ui/field/field-label.svelte +25 -0
  96. package/src/components/ui/field/field-legend.svelte +24 -0
  97. package/src/components/ui/field/field-separator.svelte +35 -0
  98. package/src/components/ui/field/field-set.svelte +20 -0
  99. package/src/components/ui/field/field-title.svelte +20 -0
  100. package/src/components/ui/field/field.svelte +47 -0
  101. package/src/components/ui/field/index.ts +33 -0
  102. package/src/components/ui/input/index.ts +7 -0
  103. package/src/components/ui/input/input.svelte +48 -0
  104. package/src/components/ui/label/index.ts +7 -0
  105. package/src/components/ui/label/label.svelte +20 -0
  106. package/src/components/ui/select/index.ts +40 -0
  107. package/src/components/ui/select/select-content.svelte +45 -0
  108. package/src/components/ui/select/select-group-heading.svelte +21 -0
  109. package/src/components/ui/select/select-group.svelte +17 -0
  110. package/src/components/ui/select/select-item.svelte +43 -0
  111. package/src/components/ui/select/select-label.svelte +20 -0
  112. package/src/components/ui/select/select-portal.svelte +7 -0
  113. package/src/components/ui/select/select-scroll-down-button.svelte +20 -0
  114. package/src/components/ui/select/select-scroll-up-button.svelte +20 -0
  115. package/src/components/ui/select/select-separator.svelte +18 -0
  116. package/src/components/ui/select/select-trigger.svelte +29 -0
  117. package/src/components/ui/select/select-value.svelte +7 -0
  118. package/src/components/ui/select/select.svelte +11 -0
  119. package/src/components/ui/separator/index.ts +7 -0
  120. package/src/components/ui/separator/separator.svelte +21 -0
  121. package/src/components/ui/sonner/index.ts +1 -0
  122. package/src/components/ui/sonner/sonner.svelte +40 -0
  123. package/src/components/ui/switch/index.ts +7 -0
  124. package/src/components/ui/switch/switch.svelte +34 -0
  125. package/src/components/ui/textarea/index.ts +7 -0
  126. package/src/components/ui/textarea/textarea.svelte +22 -0
  127. package/src/context.ts +34 -0
  128. package/src/dev/Demo.svelte +45 -0
  129. package/src/dev/main.ts +7 -0
  130. package/src/i18n/en.ts +617 -0
  131. package/src/i18n/index.ts +23 -0
  132. package/src/i18n/zh-CN.ts +599 -0
  133. package/src/index.ts +10 -0
  134. package/src/logs/LogPanel.svelte +97 -0
  135. package/src/logs/LogSettings.svelte +56 -0
  136. package/src/logs/index.ts +5 -0
  137. package/src/logs/logger.ts +96 -0
  138. package/src/markdown.ts +8 -0
  139. package/src/mcp/McpClientSettings.svelte +491 -0
  140. package/src/mcp/McpServerSettings.svelte +327 -0
  141. package/src/mcp/config.ts +26 -0
  142. package/src/mcp/index.ts +3 -0
  143. package/src/mcp/input.ts +73 -0
  144. package/src/notify.svelte.ts +43 -0
  145. package/src/ports/contract.ts +165 -0
  146. package/src/ports/fake.ts +484 -0
  147. package/src/ports/index.ts +7 -0
  148. package/src/ports/search.ts +47 -0
  149. package/src/ports/skill-sources.ts +45 -0
  150. package/src/providers/EndpointKeysDialog.svelte +343 -0
  151. package/src/providers/EndpointPanel.svelte +491 -0
  152. package/src/providers/ModelPickerDialog.svelte +187 -0
  153. package/src/providers/ModelSelect.svelte +434 -0
  154. package/src/providers/ModelSettingsDialog.svelte +453 -0
  155. package/src/providers/ProvidersSettings.svelte +631 -0
  156. package/src/providers/RequestConfigDialog.svelte +250 -0
  157. package/src/providers/SecretInput.svelte +276 -0
  158. package/src/providers/draft.ts +329 -0
  159. package/src/providers/edit-session.ts +146 -0
  160. package/src/providers/index.ts +5 -0
  161. package/src/providers/reasoning.ts +41 -0
  162. package/src/providers/revision.svelte.ts +4 -0
  163. package/src/search/SearchSettings.svelte +703 -0
  164. package/src/search/index.ts +13 -0
  165. package/src/search/settings-flow.ts +62 -0
  166. package/src/session.ts +20 -0
  167. package/src/skills/SkillAdd.svelte +347 -0
  168. package/src/skills/SkillAuthoring.svelte +381 -0
  169. package/src/skills/SkillEditor.svelte +94 -0
  170. package/src/skills/SkillPreviewDialog.svelte +122 -0
  171. package/src/skills/SkillsSettings.svelte +140 -0
  172. package/src/skills/error.ts +32 -0
  173. package/src/skills/index.ts +4 -0
  174. package/src/styles/kit.css +137 -0
  175. package/src/styles/tokens.css +22 -0
  176. package/src/tauri/index.ts +282 -0
  177. package/src/utils.ts +22 -0
  178. package/src/web/index.ts +376 -0
  179. package/README.md +0 -3
@@ -0,0 +1,434 @@
1
+ <script lang="ts">
2
+ // Host-owned model setting (docs/16 §4.1): the value is stored by the app; a deleted model shows as invalid.
3
+ // Model picker: search, capability filters, provider groups (label up to its last ` · `).
4
+ import { Command, Popover } from 'bits-ui'
5
+ import EyeIcon from '@lucide/svelte/icons/eye'
6
+ import BrainIcon from '@lucide/svelte/icons/brain'
7
+ import WrenchIcon from '@lucide/svelte/icons/wrench'
8
+ import GlobeIcon from '@lucide/svelte/icons/globe'
9
+ import SearchIcon from '@lucide/svelte/icons/search'
10
+ import Settings2Icon from '@lucide/svelte/icons/settings-2'
11
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'
12
+ import { Button } from '#lib/components/ui/button/index.js'
13
+ import { getKitContext } from '../context'
14
+ import type { Snippet } from 'svelte'
15
+ import type { ModelFilter, ModelOption, ModelRef } from '../ports'
16
+ import { providersRevision } from './revision.svelte'
17
+
18
+ let {
19
+ value,
20
+ filter = {},
21
+ onchange,
22
+ placeholder,
23
+ disabled = false,
24
+ options: suppliedOptions,
25
+ onOpenSettings,
26
+ trigger,
27
+ }: {
28
+ value: ModelRef | null
29
+ filter?: ModelFilter
30
+ onchange: (value: ModelRef, option: ModelOption | null) => void
31
+ placeholder?: string
32
+ disabled?: boolean
33
+ /** Reuses capabilities already loaded by a containing panel; null means loading. */
34
+ options?: ModelOption[] | null
35
+ /** Shows a footer link to the model settings. */
36
+ onOpenSettings?: () => void
37
+ /** Replaces the default trigger button; spread `props` onto the element. */
38
+ trigger?: Snippet<[{ props: Record<string, unknown> }]>
39
+ } = $props()
40
+
41
+ const { ports, i18n } = getKitContext()
42
+ const { t } = i18n
43
+ let loadedOptions = $state<ModelOption[] | null>(null)
44
+ let failed = $state(false)
45
+ let reload = $state(0)
46
+ let open = $state(false)
47
+ let highlighted = $state('')
48
+ let active = $state<Cap[]>([])
49
+ const options = $derived(suppliedOptions === undefined ? loadedOptions : suppliedOptions)
50
+
51
+ const key = (r: ModelRef) => `${r.endpointId}\n${r.modelId}`
52
+ // label is `{provider} · {slug} · {modelId}`; a model's display name never enters it.
53
+ const group = (o: ModelOption) => o.label.slice(0, Math.max(0, o.label.lastIndexOf(' · '))) || o.label
54
+ type Cap = 'vision' | 'reasoning' | 'tools' | 'web'
55
+ const CAPS = [
56
+ { cap: 'vision', icon: EyeIcon, has: (o: ModelOption) => o.canVision },
57
+ { cap: 'reasoning', icon: BrainIcon, has: (o: ModelOption) => o.reasoning !== null },
58
+ { cap: 'tools', icon: WrenchIcon, has: (o: ModelOption) => o.supportsTools === true },
59
+ { cap: 'web', icon: GlobeIcon, has: (o: ModelOption) => o.nativeWebSearch },
60
+ ] as const
61
+ const caps = $derived(CAPS.filter((c) => options?.some(c.has)))
62
+ const groups = $derived.by(() => {
63
+ const out: [string, ModelOption[]][] = []
64
+ for (const o of options ?? []) {
65
+ if (!CAPS.every((c) => !active.includes(c.cap) || c.has(o))) continue
66
+ const name = group(o)
67
+ const found = out.find(([n]) => n === name)
68
+ if (found) found[1].push(o)
69
+ else out.push([name, [o]])
70
+ }
71
+ return out
72
+ })
73
+ const match = (_: string, search: string, keywords: string[] = []) =>
74
+ keywords.join(' ').toLowerCase().includes(search.trim().toLowerCase()) ? 1 : 0
75
+
76
+ $effect(() => {
77
+ if (suppliedOptions !== undefined) return
78
+ void reload
79
+ void providersRevision.value
80
+ loadedOptions = null
81
+ failed = false
82
+ let alive = true
83
+ void ports.providers
84
+ ?.modelOptions(filter)
85
+ .then((list) => {
86
+ if (alive) loadedOptions = list
87
+ })
88
+ .catch(() => {
89
+ if (alive) failed = true
90
+ })
91
+ return () => {
92
+ alive = false
93
+ }
94
+ })
95
+ const current = $derived(value && options?.find((o) => key(o) === key(value)))
96
+ const invalid = $derived(!failed && value !== null && options !== null && !current)
97
+
98
+ function pick(option: ModelOption) {
99
+ open = false
100
+ onchange({ endpointId: option.endpointId, modelId: option.modelId }, option)
101
+ }
102
+ </script>
103
+
104
+ {#if ports.providers}
105
+ {#if failed}
106
+ <div class="model-error" role="alert">
107
+ <span>{t('chat.modelsFailed')}</span>
108
+ <Button variant="outline" size="sm" {disabled} onclick={() => reload++}>{t('chat.retry')}</Button>
109
+ </div>
110
+ {:else}
111
+ <Popover.Root
112
+ bind:open
113
+ onOpenChange={(next) => {
114
+ if (next) highlighted = value ? key(value) : ''
115
+ }}
116
+ >
117
+ <Popover.Trigger disabled={disabled || options === null || options.length === 0}>
118
+ {#snippet child({ props })}
119
+ {#if trigger}{@render trigger({ props })}{:else}
120
+ <Button
121
+ {...props}
122
+ variant="outline"
123
+ class="model-trigger"
124
+ aria-invalid={invalid}
125
+ aria-label={t('providers.model_placeholder')}
126
+ title={current?.label}
127
+ >
128
+ {#if options === null}
129
+ <span class="model-name muted">{t('chat.modelsLoading')}</span>
130
+ {:else if current}
131
+ <span class="model-name">{current.name ?? current.modelId}</span>
132
+ <span class="model-group">| {group(current)}</span>
133
+ {:else}
134
+ <span class="model-name muted"
135
+ >{invalid ? t('providers.model_invalid') : (placeholder ?? t('providers.model_placeholder'))}</span
136
+ >
137
+ {/if}
138
+ <ChevronDownIcon class="model-chevron" />
139
+ </Button>
140
+ {/if}
141
+ {/snippet}
142
+ </Popover.Trigger>
143
+ <Popover.Content
144
+ class="model-popover"
145
+ side="bottom"
146
+ align="start"
147
+ sideOffset={4}
148
+ collisionPadding={8}
149
+ onEscapeKeydown={(event) => {
150
+ if (event.isComposing) event.preventDefault()
151
+ }}
152
+ >
153
+ <Command.Root bind:value={highlighted} filter={match} label={t('providers.model_placeholder')}>
154
+ <div class="model-search">
155
+ <SearchIcon class="model-search-icon" />
156
+ <Command.Input class="model-search-input" placeholder={t('providers.model_search')} />
157
+ </div>
158
+ {#if caps.length > 0}
159
+ <div class="model-caps">
160
+ {#each caps as c (c.cap)}
161
+ <button
162
+ type="button"
163
+ class="cap-chip cap-{c.cap}"
164
+ aria-pressed={active.includes(c.cap)}
165
+ onclick={() =>
166
+ (active = active.includes(c.cap) ? active.filter((x) => x !== c.cap) : [...active, c.cap])}
167
+ ><c.icon />{t(`providers.cap_${c.cap}`)}</button
168
+ >
169
+ {/each}
170
+ </div>
171
+ {/if}
172
+ <Command.List class="model-list" aria-label={t('providers.model_placeholder')}>
173
+ <Command.Empty class="model-empty">{t('providers.model_no_match')}</Command.Empty>
174
+ {#each groups as [name, items] (name)}
175
+ <Command.Group value={`group\n${name}`}>
176
+ <Command.GroupHeading class="model-heading">{name}</Command.GroupHeading>
177
+ <Command.GroupItems>
178
+ {#each items as option (key(option))}
179
+ <Command.Item
180
+ class="model-item"
181
+ value={key(option)}
182
+ keywords={[option.modelId, option.name ?? '', name]}
183
+ aria-current={value !== null && key(option) === key(value) ? 'true' : undefined}
184
+ onSelect={() => pick(option)}
185
+ >
186
+ <span class="model-avatar" aria-hidden="true"
187
+ >{(option.name ?? option.modelId).charAt(0).toUpperCase()}</span
188
+ >
189
+ <span class="model-name" title={option.label}>{option.name ?? option.modelId}</span>
190
+ <span class="model-tags">
191
+ {#each CAPS.filter((c) => c.has(option)) as c (c.cap)}
192
+ <span class="cap-tag cap-{c.cap}" title={t(`providers.cap_${c.cap}`)}
193
+ ><c.icon aria-label={t(`providers.cap_${c.cap}`)} /></span
194
+ >
195
+ {/each}
196
+ </span>
197
+ </Command.Item>
198
+ {/each}
199
+ </Command.GroupItems>
200
+ </Command.Group>
201
+ {/each}
202
+ </Command.List>
203
+ {#if onOpenSettings}
204
+ <button
205
+ type="button"
206
+ class="model-footer"
207
+ onclick={() => {
208
+ open = false
209
+ onOpenSettings()
210
+ }}><Settings2Icon />{t('providers.model_configure')}</button
211
+ >
212
+ {/if}
213
+ </Command.Root>
214
+ </Popover.Content>
215
+ </Popover.Root>
216
+ {/if}
217
+ {/if}
218
+
219
+ <style>
220
+ .model-error {
221
+ display: flex;
222
+ flex-wrap: wrap;
223
+ align-items: center;
224
+ gap: 0.5rem;
225
+ color: var(--color-muted-foreground);
226
+ }
227
+ :global(.model-trigger) {
228
+ width: 100%;
229
+ min-width: 0;
230
+ justify-content: flex-start;
231
+ gap: 0.375rem;
232
+ font-weight: 400;
233
+ }
234
+ .model-name {
235
+ min-width: 0;
236
+ flex: 0 1 auto;
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ white-space: nowrap;
240
+ }
241
+ .model-group {
242
+ min-width: 0;
243
+ flex: 1 999 0%;
244
+ overflow: hidden;
245
+ text-overflow: ellipsis;
246
+ white-space: nowrap;
247
+ color: var(--color-muted-foreground);
248
+ text-align: left;
249
+ }
250
+ .muted {
251
+ color: var(--color-muted-foreground);
252
+ }
253
+ :global(.model-chevron) {
254
+ margin-left: auto;
255
+ flex-shrink: 0;
256
+ opacity: 0.6;
257
+ }
258
+ :global(.model-popover) {
259
+ z-index: 50;
260
+ box-sizing: border-box;
261
+ width: min(25rem, calc(100vw - 1rem));
262
+ display: flex;
263
+ flex-direction: column;
264
+ border: 1px solid var(--color-border);
265
+ border-radius: var(--kit-radius, var(--radius));
266
+ background: var(--color-popover);
267
+ color: var(--color-popover-foreground);
268
+ box-shadow: 0 4px 12px color-mix(in oklch, var(--color-foreground) 12%, transparent);
269
+ font-size: var(--kit-font-size-sm, 0.8125rem);
270
+ overflow: hidden;
271
+ }
272
+ .model-search {
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 0.5rem;
276
+ padding: 0.5rem 0.75rem;
277
+ border-bottom: 1px solid var(--color-border);
278
+ }
279
+ :global(.model-search-icon) {
280
+ width: 1rem;
281
+ height: 1rem;
282
+ flex-shrink: 0;
283
+ color: var(--color-muted-foreground);
284
+ }
285
+ :global(.model-search-input) {
286
+ flex: 1;
287
+ min-width: 0;
288
+ border: 0;
289
+ background: transparent;
290
+ color: inherit;
291
+ font: inherit;
292
+ outline: none;
293
+ }
294
+ .model-caps {
295
+ display: flex;
296
+ gap: 0.375rem;
297
+ overflow-x: auto;
298
+ padding: 0.5rem 0.75rem;
299
+ border-bottom: 1px solid var(--color-border);
300
+ scrollbar-width: none;
301
+ }
302
+ .cap-chip {
303
+ display: inline-flex;
304
+ flex-shrink: 0;
305
+ align-items: center;
306
+ gap: 0.25rem;
307
+ padding: 0.125rem 0.5rem;
308
+ border: 1px solid var(--color-border);
309
+ border-radius: 999px;
310
+ background: transparent;
311
+ color: var(--color-muted-foreground);
312
+ font-size: 0.75rem;
313
+ cursor: pointer;
314
+ }
315
+ .cap-chip[aria-pressed='true'] {
316
+ border-color: var(--cap);
317
+ background: color-mix(in oklch, var(--cap) 15%, transparent);
318
+ color: var(--cap);
319
+ }
320
+ .cap-chip:focus-visible,
321
+ .model-footer:focus-visible {
322
+ outline: 2px solid var(--color-ring);
323
+ outline-offset: 1px;
324
+ }
325
+ .cap-chip :global(svg),
326
+ .cap-tag :global(svg),
327
+ .model-footer :global(svg) {
328
+ width: 0.875rem;
329
+ height: 0.875rem;
330
+ }
331
+ .cap-vision {
332
+ --cap: oklch(0.62 0.15 155);
333
+ }
334
+ .cap-reasoning {
335
+ --cap: oklch(0.6 0.16 290);
336
+ }
337
+ .cap-tools {
338
+ --cap: oklch(0.68 0.16 55);
339
+ }
340
+ .cap-web {
341
+ --cap: oklch(0.62 0.14 235);
342
+ }
343
+ :global(.model-list) {
344
+ max-height: min(22rem, 50dvh);
345
+ overflow-y: auto;
346
+ padding: 0.25rem;
347
+ scroll-padding-block: 1.75rem 0.25rem;
348
+ }
349
+ :global(.model-heading) {
350
+ position: sticky;
351
+ top: -0.25rem;
352
+ z-index: 1;
353
+ padding: 0.5rem 0.5rem 0.25rem;
354
+ background: var(--color-popover);
355
+ color: var(--color-muted-foreground);
356
+ font-size: 0.75rem;
357
+ }
358
+ :global(.model-empty) {
359
+ padding: 1.5rem 0.5rem;
360
+ text-align: center;
361
+ color: var(--color-muted-foreground);
362
+ }
363
+ :global(.model-item) {
364
+ position: relative;
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 0.5rem;
368
+ min-height: 2.25rem;
369
+ padding: 0.25rem 0.5rem;
370
+ border-radius: calc(var(--kit-radius, var(--radius)) - 2px);
371
+ cursor: pointer;
372
+ user-select: none;
373
+ }
374
+ :global(.model-item[aria-current='true']:not([data-selected])) {
375
+ background: color-mix(in oklch, var(--color-accent) 60%, transparent);
376
+ }
377
+ :global(.model-item[data-selected]) {
378
+ background: var(--color-accent);
379
+ color: var(--color-accent-foreground);
380
+ }
381
+ :global(.model-item[aria-current='true'])::before {
382
+ content: '';
383
+ position: absolute;
384
+ left: 0;
385
+ top: 0.5rem;
386
+ bottom: 0.5rem;
387
+ width: 3px;
388
+ border-radius: 3px;
389
+ background: var(--color-primary);
390
+ }
391
+ .model-avatar {
392
+ display: grid;
393
+ flex-shrink: 0;
394
+ place-items: center;
395
+ width: 1.5rem;
396
+ height: 1.5rem;
397
+ border: 1px solid var(--color-border);
398
+ border-radius: 999px;
399
+ background: var(--color-muted);
400
+ font-size: 0.6875rem;
401
+ font-weight: 600;
402
+ }
403
+ .model-tags {
404
+ display: flex;
405
+ flex-shrink: 0;
406
+ gap: 0.25rem;
407
+ margin-left: auto;
408
+ }
409
+ .cap-tag {
410
+ display: inline-grid;
411
+ place-items: center;
412
+ width: 1.5rem;
413
+ height: 1.125rem;
414
+ border-radius: 0.375rem;
415
+ background: color-mix(in oklch, var(--cap) 15%, transparent);
416
+ color: var(--cap);
417
+ }
418
+ .model-footer {
419
+ display: flex;
420
+ align-items: center;
421
+ gap: 0.5rem;
422
+ padding: 0.5rem 0.75rem;
423
+ border: 0;
424
+ border-top: 1px solid var(--color-border);
425
+ background: transparent;
426
+ color: var(--color-muted-foreground);
427
+ font: inherit;
428
+ text-align: left;
429
+ cursor: pointer;
430
+ }
431
+ .model-footer:hover {
432
+ color: var(--color-foreground);
433
+ }
434
+ </style>