@ouraihub/aether-ui 0.0.0-stage → 0.1.1

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