Skip to content

Commit d43b6c3

Browse files
peoraycwandev
andauthored
feat: add model selector component (#33)
Co-authored-by: Charlie Wang <[email protected]>
1 parent 06da9b9 commit d43b6c3

30 files changed

+1291
-0
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
---
2+
title: Model Selector
3+
description: A searchable command palette for selecting AI models in your chat interface.
4+
icon: lucide:square-mouse-pointer
5+
---
6+
7+
The `Model Selector` component provides a searchable command palette interface for selecting AI models. It's built on top of the cmdk library and provides a keyboard-navigable interface with search functionality.
8+
9+
:::ComponentLoader{label="Preview" componentName="ModelSelector"}
10+
:::
11+
12+
## Install using CLI
13+
14+
::tabs{variant="card"}
15+
::div{label="ai-elements-vue"}
16+
```sh
17+
npx ai-elements-vue@latest add model-selector
18+
```
19+
::
20+
::div{label="shadcn-vue"}
21+
22+
```sh
23+
npx shadcn-vue@latest add https://registry.ai-elements-vue.com/model-selector.json
24+
```
25+
::
26+
::
27+
28+
## Install Manually
29+
30+
Copy and paste the following code in the same folder.
31+
32+
:::code-group
33+
```vue [ModelSelector.vue] height=260 collapse
34+
<script setup lang="ts">
35+
import { Dialog } from '@repo/shadcn-vue/components/ui/dialog'
36+
</script>
37+
38+
<template>
39+
<Dialog v-bind="$attrs">
40+
<slot />
41+
</Dialog>
42+
</template>
43+
```
44+
45+
```vue [ModelSelectorTrigger.vue] height=260 collapse
46+
<script setup lang="ts">
47+
import { DialogTrigger } from '@repo/shadcn-vue/components/ui/dialog'
48+
</script>
49+
50+
<template>
51+
<DialogTrigger v-bind="$attrs">
52+
<slot />
53+
</DialogTrigger>
54+
</template>
55+
```
56+
57+
```vue [ModelSelectorContent.vue] height=260 collapse
58+
<script setup lang="ts">
59+
import type { HTMLAttributes } from 'vue'
60+
import { Command } from '@repo/shadcn-vue/components/ui/command'
61+
import { DialogContent, DialogTitle } from '@repo/shadcn-vue/components/ui/dialog'
62+
import { cn } from '@repo/shadcn-vue/lib/utils'
63+
64+
interface Props {
65+
title?: string
66+
class?: HTMLAttributes['class']
67+
}
68+
69+
const props = withDefaults(defineProps<Props>(), {
70+
title: 'Model Selector',
71+
})
72+
</script>
73+
74+
<template>
75+
<DialogContent
76+
:class="cn('p-0', props.class)"
77+
v-bind="$attrs"
78+
>
79+
<DialogTitle class="sr-only">
80+
{{ title }}
81+
</DialogTitle>
82+
<Command class="**:data-[slot=command-input-wrapper]:h-auto">
83+
<slot />
84+
</Command>
85+
</DialogContent>
86+
</template>
87+
```
88+
89+
```vue [ModelSelectorDialog.vue] height=260 collapse
90+
<script setup lang="ts">
91+
import { CommandDialog } from '@repo/shadcn-vue/components/ui/command'
92+
</script>
93+
94+
<template>
95+
<CommandDialog v-bind="$attrs">
96+
<slot />
97+
</CommandDialog>
98+
</template>
99+
```
100+
101+
```vue [ModelSelectorInput.vue] height=260 collapse
102+
<script setup lang="ts">
103+
import type { HTMLAttributes } from 'vue'
104+
import { CommandInput } from '@repo/shadcn-vue/components/ui/command'
105+
import { cn } from '@repo/shadcn-vue/lib/utils'
106+
107+
interface Props {
108+
class?: HTMLAttributes['class']
109+
}
110+
111+
const props = defineProps<Props>()
112+
</script>
113+
114+
<template>
115+
<CommandInput
116+
:class="cn('h-auto py-3.5', props.class)"
117+
v-bind="$attrs"
118+
/>
119+
</template>
120+
```
121+
122+
```vue [ModelSelectorList.vue] height=260 collapse
123+
<script setup lang="ts">
124+
import { CommandList } from '@repo/shadcn-vue/components/ui/command'
125+
</script>
126+
127+
<template>
128+
<CommandList v-bind="$attrs">
129+
<slot />
130+
</CommandList>
131+
</template>
132+
```
133+
134+
```vue [ModelSelectorEmpty.vue] height=260 collapse
135+
<script setup lang="ts">
136+
import { CommandEmpty } from '@repo/shadcn-vue/components/ui/command'
137+
</script>
138+
139+
<template>
140+
<CommandEmpty v-bind="$attrs">
141+
<slot />
142+
</CommandEmpty>
143+
</template>
144+
```
145+
146+
```vue [ModelSelectorGroup.vue] height=260 collapse
147+
<script setup lang="ts">
148+
import { CommandGroup } from '@repo/shadcn-vue/components/ui/command'
149+
</script>
150+
151+
<template>
152+
<CommandGroup v-bind="$attrs">
153+
<slot />
154+
</CommandGroup>
155+
</template>
156+
```
157+
158+
```vue [ModelSelectorItem.vue] height=260 collapse
159+
<script setup lang="ts">
160+
import { CommandItem } from '@repo/shadcn-vue/components/ui/command'
161+
</script>
162+
163+
<template>
164+
<CommandItem v-bind="$attrs">
165+
<slot />
166+
</CommandItem>
167+
</template>
168+
```
169+
170+
```vue [ModelSelectorShortcut.vue] height=260 collapse
171+
<script setup lang="ts">
172+
import { CommandShortcut } from '@repo/shadcn-vue/components/ui/command'
173+
</script>
174+
175+
<template>
176+
<CommandShortcut v-bind="$attrs">
177+
<slot />
178+
</CommandShortcut>
179+
</template>
180+
```
181+
182+
```vue [ModelSelectorSeparator.vue] height=260 collapse
183+
<script setup lang="ts">
184+
import { CommandSeparator } from '@repo/shadcn-vue/components/ui/command'
185+
</script>
186+
187+
<template>
188+
<CommandSeparator v-bind="$attrs" />
189+
</template>
190+
```
191+
192+
```vue [ModelSelectorLogo.vue] height=260 collapse
193+
<script setup lang="ts">
194+
import type { HTMLAttributes } from 'vue'
195+
import { cn } from '@repo/shadcn-vue/lib/utils'
196+
197+
interface Props {
198+
provider: string
199+
class?: HTMLAttributes['class']
200+
}
201+
202+
const props = defineProps<Props>()
203+
</script>
204+
205+
<template>
206+
<img
207+
v-bind="$attrs"
208+
:alt="`${props.provider} logo`"
209+
:class="cn('size-3', props.class)"
210+
height="12"
211+
:src="`https://models.dev/logos/${props.provider}.svg`"
212+
width="12"
213+
>
214+
</template>
215+
```
216+
217+
```vue [ModelSelectorLogoGroup.vue] height=260 collapse
218+
<script setup lang="ts">
219+
import type { HTMLAttributes } from 'vue'
220+
import { cn } from '@repo/shadcn-vue/lib/utils'
221+
222+
interface Props {
223+
class?: HTMLAttributes['class']
224+
}
225+
226+
const props = defineProps<Props>()
227+
</script>
228+
229+
<template>
230+
<div
231+
:class="
232+
cn(
233+
'-space-x-1 flex shrink-0 items-center [&>img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 [&>img]:ring-border',
234+
props.class,
235+
)
236+
"
237+
v-bind="$attrs"
238+
>
239+
<slot />
240+
</div>
241+
</template>
242+
```
243+
244+
```vue [ModelSelectorName.vue] height=260 collapse
245+
<script setup lang="ts">
246+
import type { HTMLAttributes } from 'vue'
247+
import { cn } from '@repo/shadcn-vue/lib/utils'
248+
249+
interface Props {
250+
class?: HTMLAttributes['class']
251+
}
252+
253+
const props = defineProps<Props>()
254+
</script>
255+
256+
<template>
257+
<span
258+
:class="cn('flex-1 truncate text-left', props.class)"
259+
v-bind="$attrs"
260+
>
261+
<slot />
262+
</span>
263+
</template>
264+
```
265+
266+
```ts [index.ts] height=260 collapse
267+
export { default as ModelSelector } from './ModelSelector.vue'
268+
export { default as ModelSelectorContent } from './ModelSelectorContent.vue'
269+
export { default as ModelSelectorDialog } from './ModelSelectorDialog.vue'
270+
export { default as ModelSelectorEmpty } from './ModelSelectorEmpty.vue'
271+
export { default as ModelSelectorGroup } from './ModelSelectorGroup.vue'
272+
export { default as ModelSelectorInput } from './ModelSelectorInput.vue'
273+
export { default as ModelSelectorItem } from './ModelSelectorItem.vue'
274+
export { default as ModelSelectorList } from './ModelSelectorList.vue'
275+
export { default as ModelSelectorLogo } from './ModelSelectorLogo.vue'
276+
export { default as ModelSelectorLogoGroup } from './ModelSelectorLogoGroup.vue'
277+
export { default as ModelSelectorName } from './ModelSelectorName.vue'
278+
export { default as ModelSelectorSeparator } from './ModelSelectorSeparator.vue'
279+
export { default as ModelSelectorShortcut } from './ModelSelectorShortcut.vue'
280+
export { default as ModelSelectorTrigger } from './ModelSelectorTrigger.vue'
281+
```
282+
:::
283+
284+
## Features
285+
286+
- Searchable interface with keyboard navigation
287+
- Fuzzy search filtering across model names
288+
- Grouped model organization by provider
289+
- Keyboard shortcuts support
290+
- Empty state handling
291+
- Customizable styling with Tailwind CSS
292+
- Built on cmdk for excellent accessibility
293+
- Support for both inline and dialog modes
294+
- TypeScript support with proper type definitions
295+
296+
## Props
297+
298+
### `<ModelSelectorContent />`
299+
300+
:::field-group
301+
::field{name="title" type="string" defaultValue="'Model Selector'"}
302+
Title of the model selector.
303+
::
304+
305+
::field{name="class" type="string"}
306+
Additional classes applied to the component.
307+
::
308+
:::
309+
310+
### `<ModelSelectorInput />`
311+
312+
:::field-group
313+
::field{name="class" type="string"}
314+
Additional classes applied to the component.
315+
::
316+
:::
317+
318+
### `<ModelSelectorLogo />`
319+
320+
:::field-group
321+
::field{name="provider" type="string"}
322+
The AI provider name. Supports major providers like "openai", "anthropic", "google", "mistral", etc.
323+
::
324+
325+
::field{name="class" type="string"}
326+
Additional classes applied to the component.
327+
::
328+
:::
329+
330+
### `<ModelSelectorLogoGroup />`
331+
332+
:::field-group
333+
::field{name="class" type="string"}
334+
Additional classes applied to the component.
335+
::
336+
:::
337+
338+
### `<ModelSelectorName />`
339+
340+
:::field-group
341+
::field{name="class" type="string"}
342+
Additional classes applied to the component.
343+
::
344+
:::

apps/www/plugins/ai-elements.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
LoaderSizes,
2121
Message,
2222
MessageMarkdown,
23+
ModelSelector,
2324
OpenInChat,
2425
Plan,
2526
PromptInput,
@@ -79,6 +80,7 @@ export default defineNuxtPlugin((nuxtApp) => {
7980
vueApp.component('CodeBlockDark', CodeBlockDark)
8081
vueApp.component('Checkpoint', Checkpoint)
8182
vueApp.component('Workflow', Workflow)
83+
vueApp.component('ModelSelector', ModelSelector)
8284
vueApp.component('Context', Context)
8385
vueApp.component('Confirmation', Confirmation)
8486
vueApp.component('ConfirmationAccepted', ConfirmationAccepted)

packages/elements/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from './image'
1111
export * from './inline-citation'
1212
export * from './loader'
1313
export * from './message'
14+
export * from './model-selector'
1415
export * from './open-in-chat'
1516
export * from './plan'
1617
export * from './prompt-input'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script setup lang="ts">
2+
import { Dialog } from '@repo/shadcn-vue/components/ui/dialog'
3+
</script>
4+
5+
<template>
6+
<Dialog v-bind="$attrs">
7+
<slot />
8+
</Dialog>
9+
</template>

0 commit comments

Comments
 (0)