@openconsole/shadcn 0.2.2 → 0.2.5

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 (118) hide show
  1. package/README.md +460 -380
  2. package/components/ai-elements/agent.tsx +141 -0
  3. package/components/ai-elements/artifact.tsx +148 -0
  4. package/components/ai-elements/attachments.tsx +426 -0
  5. package/components/ai-elements/audio-player.tsx +231 -0
  6. package/components/ai-elements/canvas.tsx +26 -0
  7. package/components/ai-elements/chain-of-thought.tsx +222 -0
  8. package/components/ai-elements/checkpoint.tsx +71 -0
  9. package/components/ai-elements/code-block.tsx +562 -0
  10. package/components/ai-elements/commit.tsx +458 -0
  11. package/components/ai-elements/confirmation.tsx +174 -0
  12. package/components/ai-elements/connection.tsx +28 -0
  13. package/components/ai-elements/context.tsx +409 -0
  14. package/components/ai-elements/controls.tsx +18 -0
  15. package/components/ai-elements/conversation.tsx +168 -0
  16. package/components/ai-elements/edge.tsx +143 -0
  17. package/components/ai-elements/environment-variables.tsx +324 -0
  18. package/components/ai-elements/file-tree.tsx +304 -0
  19. package/components/ai-elements/image.tsx +24 -0
  20. package/components/ai-elements/index.ts +51 -0
  21. package/components/ai-elements/inline-citation.tsx +296 -0
  22. package/components/ai-elements/jsx-preview.tsx +310 -0
  23. package/components/ai-elements/message.tsx +360 -0
  24. package/components/ai-elements/mic-selector.tsx +375 -0
  25. package/components/ai-elements/model-selector.tsx +213 -0
  26. package/components/ai-elements/node.tsx +71 -0
  27. package/components/ai-elements/open-in-chat.tsx +370 -0
  28. package/components/ai-elements/package-info.tsx +239 -0
  29. package/components/ai-elements/panel.tsx +15 -0
  30. package/components/ai-elements/persona.tsx +306 -0
  31. package/components/ai-elements/plan.tsx +147 -0
  32. package/components/ai-elements/prompt-input.tsx +1463 -0
  33. package/components/ai-elements/queue.tsx +274 -0
  34. package/components/ai-elements/reasoning.tsx +228 -0
  35. package/components/ai-elements/sandbox.tsx +132 -0
  36. package/components/ai-elements/schema-display.tsx +471 -0
  37. package/components/ai-elements/shimmer.tsx +77 -0
  38. package/components/ai-elements/snippet.tsx +145 -0
  39. package/components/ai-elements/sources.tsx +77 -0
  40. package/components/ai-elements/speech-input.tsx +323 -0
  41. package/components/ai-elements/stack-trace.tsx +528 -0
  42. package/components/ai-elements/suggestion.tsx +57 -0
  43. package/components/ai-elements/task.tsx +87 -0
  44. package/components/ai-elements/terminal.tsx +273 -0
  45. package/components/ai-elements/test-results.tsx +496 -0
  46. package/components/ai-elements/tool.tsx +173 -0
  47. package/components/ai-elements/toolbar.tsx +16 -0
  48. package/components/ai-elements/transcription.tsx +125 -0
  49. package/components/ai-elements/voice-selector.tsx +524 -0
  50. package/components/ai-elements/web-preview.tsx +281 -0
  51. package/components/index.ts +3 -0
  52. package/{accordion.tsx → components/ui/accordion.tsx} +66 -66
  53. package/{alert-dialog.tsx → components/ui/alert-dialog.tsx} +196 -196
  54. package/{alert.tsx → components/ui/alert.tsx} +66 -66
  55. package/{aspect-ratio.tsx → components/ui/aspect-ratio.tsx} +11 -11
  56. package/{avatar.tsx → components/ui/avatar.tsx} +53 -53
  57. package/{badge.tsx → components/ui/badge.tsx} +46 -46
  58. package/{breadcrumb.tsx → components/ui/breadcrumb.tsx} +109 -109
  59. package/{button-group.tsx → components/ui/button-group.tsx} +83 -83
  60. package/{button.tsx → components/ui/button.tsx} +60 -60
  61. package/{calendar.tsx → components/ui/calendar.tsx} +219 -219
  62. package/{card.tsx → components/ui/card.tsx} +92 -92
  63. package/{carousel.tsx → components/ui/carousel.tsx} +241 -241
  64. package/{chart.tsx → components/ui/chart.tsx} +374 -374
  65. package/{checkbox.tsx → components/ui/checkbox.tsx} +32 -32
  66. package/{collapsible.tsx → components/ui/collapsible.tsx} +33 -33
  67. package/{command.tsx → components/ui/command.tsx} +184 -184
  68. package/{context-menu.tsx → components/ui/context-menu.tsx} +252 -252
  69. package/{dialog.tsx → components/ui/dialog.tsx} +143 -143
  70. package/{direction.tsx → components/ui/direction.tsx} +22 -22
  71. package/{drawer.tsx → components/ui/drawer.tsx} +135 -135
  72. package/{dropdown-menu.tsx → components/ui/dropdown-menu.tsx} +257 -257
  73. package/{empty.tsx → components/ui/empty.tsx} +104 -104
  74. package/{field.tsx → components/ui/field.tsx} +248 -248
  75. package/{form.tsx → components/ui/form.tsx} +167 -167
  76. package/{hover-card.tsx → components/ui/hover-card.tsx} +44 -44
  77. package/components/ui/icon.tsx +55 -0
  78. package/components/ui/index.ts +59 -0
  79. package/{input-group.tsx → components/ui/input-group.tsx} +170 -170
  80. package/{input-otp.tsx → components/ui/input-otp.tsx} +77 -77
  81. package/{input.tsx → components/ui/input.tsx} +21 -21
  82. package/{item.tsx → components/ui/item.tsx} +193 -193
  83. package/{kbd.tsx → components/ui/kbd.tsx} +28 -28
  84. package/{label.tsx → components/ui/label.tsx} +24 -24
  85. package/{menubar.tsx → components/ui/menubar.tsx} +276 -276
  86. package/{native-select.tsx → components/ui/native-select.tsx} +62 -62
  87. package/{navigation-menu.tsx → components/ui/navigation-menu.tsx} +168 -168
  88. package/{pagination.tsx → components/ui/pagination.tsx} +127 -127
  89. package/{popover.tsx → components/ui/popover.tsx} +89 -89
  90. package/{progress.tsx → components/ui/progress.tsx} +31 -31
  91. package/{radio-group.tsx → components/ui/radio-group.tsx} +45 -45
  92. package/{resizable.tsx → components/ui/resizable.tsx} +53 -53
  93. package/{scroll-area.tsx → components/ui/scroll-area.tsx} +58 -58
  94. package/{select.tsx → components/ui/select.tsx} +187 -187
  95. package/{separator.tsx → components/ui/separator.tsx} +28 -28
  96. package/{sheet.tsx → components/ui/sheet.tsx} +139 -139
  97. package/{sidebar.tsx → components/ui/sidebar.tsx} +724 -724
  98. package/{skeleton.tsx → components/ui/skeleton.tsx} +13 -13
  99. package/{slider.tsx → components/ui/slider.tsx} +63 -63
  100. package/{sonner.tsx → components/ui/sonner.tsx} +40 -40
  101. package/{spinner.tsx → components/ui/spinner.tsx} +16 -16
  102. package/{switch.tsx → components/ui/switch.tsx} +35 -35
  103. package/{table.tsx → components/ui/table.tsx} +116 -116
  104. package/{tabs.tsx → components/ui/tabs.tsx} +66 -66
  105. package/{textarea.tsx → components/ui/textarea.tsx} +18 -18
  106. package/{toggle-group.tsx → components/ui/toggle-group.tsx} +83 -83
  107. package/{toggle.tsx → components/ui/toggle.tsx} +47 -47
  108. package/{tooltip.tsx → components/ui/tooltip.tsx} +61 -61
  109. package/hooks/index.ts +1 -1
  110. package/hooks/use-mobile.ts +19 -19
  111. package/index.ts +3 -59
  112. package/lib/index.ts +1 -1
  113. package/lib/utils.ts +6 -6
  114. package/package.json +79 -1
  115. package/styles.css +124 -124
  116. package/icon.tsx +0 -21
  117. package/tsconfig.json +0 -12
  118. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,375 @@
1
+ "use client";
2
+
3
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
4
+ import { Button } from "../ui/button";
5
+ import {
6
+ Command,
7
+ CommandEmpty,
8
+ CommandInput,
9
+ CommandItem,
10
+ CommandList,
11
+ } from "../ui/command";
12
+ import {
13
+ Popover,
14
+ PopoverContent,
15
+ PopoverTrigger,
16
+ } from "../ui/popover";
17
+ import { cn } from "../../lib/utils";
18
+ import { ChevronsUpDownIcon } from "lucide-react";
19
+ import type { ComponentProps, ReactNode } from "react";
20
+ import {
21
+ createContext,
22
+ useCallback,
23
+ useContext,
24
+ useEffect,
25
+ useMemo,
26
+ useRef,
27
+ useState,
28
+ } from "react";
29
+
30
+ const deviceIdRegex = /\(([\da-fA-F]{4}:[\da-fA-F]{4})\)$/;
31
+
32
+ interface MicSelectorContextType {
33
+ data: MediaDeviceInfo[];
34
+ value: string | undefined;
35
+ onValueChange?: (value: string) => void;
36
+ open: boolean;
37
+ onOpenChange?: (open: boolean) => void;
38
+ width: number;
39
+ setWidth?: (width: number) => void;
40
+ }
41
+
42
+ const MicSelectorContext = createContext<MicSelectorContextType>({
43
+ data: [],
44
+ onOpenChange: undefined,
45
+ onValueChange: undefined,
46
+ open: false,
47
+ setWidth: undefined,
48
+ value: undefined,
49
+ width: 200,
50
+ });
51
+
52
+ export const useAudioDevices = () => {
53
+ const [devices, setDevices] = useState<MediaDeviceInfo[]>([]);
54
+ const [loading, setLoading] = useState(true);
55
+ const [error, setError] = useState<string | null>(null);
56
+ const [hasPermission, setHasPermission] = useState(false);
57
+
58
+ const loadDevicesWithoutPermission = useCallback(async () => {
59
+ try {
60
+ setLoading(true);
61
+ setError(null);
62
+
63
+ const deviceList = await navigator.mediaDevices.enumerateDevices();
64
+ const audioInputs = deviceList.filter(
65
+ (device) => device.kind === "audioinput"
66
+ );
67
+
68
+ setDevices(audioInputs);
69
+ } catch (caughtError) {
70
+ const message =
71
+ caughtError instanceof Error
72
+ ? caughtError.message
73
+ : "Failed to get audio devices";
74
+
75
+ setError(message);
76
+ console.error("Error getting audio devices:", message);
77
+ } finally {
78
+ setLoading(false);
79
+ }
80
+ }, []);
81
+
82
+ const loadDevicesWithPermission = useCallback(async () => {
83
+ if (loading) {
84
+ return;
85
+ }
86
+
87
+ try {
88
+ setLoading(true);
89
+ setError(null);
90
+
91
+ const tempStream = await navigator.mediaDevices.getUserMedia({
92
+ audio: true,
93
+ });
94
+
95
+ for (const track of tempStream.getTracks()) {
96
+ track.stop();
97
+ }
98
+
99
+ const deviceList = await navigator.mediaDevices.enumerateDevices();
100
+ const audioInputs = deviceList.filter(
101
+ (device) => device.kind === "audioinput"
102
+ );
103
+
104
+ setDevices(audioInputs);
105
+ setHasPermission(true);
106
+ } catch (caughtError) {
107
+ const message =
108
+ caughtError instanceof Error
109
+ ? caughtError.message
110
+ : "Failed to get audio devices";
111
+
112
+ setError(message);
113
+ console.error("Error getting audio devices:", message);
114
+ } finally {
115
+ setLoading(false);
116
+ }
117
+ }, [loading]);
118
+
119
+ useEffect(() => {
120
+ loadDevicesWithoutPermission();
121
+ }, [loadDevicesWithoutPermission]);
122
+
123
+ useEffect(() => {
124
+ const handleDeviceChange = () => {
125
+ if (hasPermission) {
126
+ loadDevicesWithPermission();
127
+ } else {
128
+ loadDevicesWithoutPermission();
129
+ }
130
+ };
131
+
132
+ navigator.mediaDevices.addEventListener("devicechange", handleDeviceChange);
133
+
134
+ return () => {
135
+ navigator.mediaDevices.removeEventListener(
136
+ "devicechange",
137
+ handleDeviceChange
138
+ );
139
+ };
140
+ }, [hasPermission, loadDevicesWithPermission, loadDevicesWithoutPermission]);
141
+
142
+ return {
143
+ devices,
144
+ error,
145
+ hasPermission,
146
+ loadDevices: loadDevicesWithPermission,
147
+ loading,
148
+ };
149
+ };
150
+
151
+ export type MicSelectorProps = ComponentProps<typeof Popover> & {
152
+ defaultValue?: string;
153
+ value?: string | undefined;
154
+ onValueChange?: (value: string | undefined) => void;
155
+ open?: boolean;
156
+ onOpenChange?: (open: boolean) => void;
157
+ };
158
+
159
+ export const MicSelector = ({
160
+ defaultValue,
161
+ value: controlledValue,
162
+ onValueChange: controlledOnValueChange,
163
+ defaultOpen = false,
164
+ open: controlledOpen,
165
+ onOpenChange: controlledOnOpenChange,
166
+ ...props
167
+ }: MicSelectorProps) => {
168
+ const [value, onValueChange] = useControllableState<string | undefined>({
169
+ defaultProp: defaultValue,
170
+ onChange: controlledOnValueChange,
171
+ prop: controlledValue,
172
+ });
173
+ const [open, onOpenChange] = useControllableState({
174
+ defaultProp: defaultOpen,
175
+ onChange: controlledOnOpenChange,
176
+ prop: controlledOpen,
177
+ });
178
+ const [width, setWidth] = useState(200);
179
+ const { devices, loading, hasPermission, loadDevices } = useAudioDevices();
180
+
181
+ useEffect(() => {
182
+ if (open && !hasPermission && !loading) {
183
+ loadDevices();
184
+ }
185
+ }, [open, hasPermission, loading, loadDevices]);
186
+
187
+ const contextValue = useMemo(
188
+ () => ({
189
+ data: devices,
190
+ onOpenChange,
191
+ onValueChange,
192
+ open,
193
+ setWidth,
194
+ value,
195
+ width,
196
+ }),
197
+ [devices, onOpenChange, onValueChange, open, setWidth, value, width]
198
+ );
199
+
200
+ return (
201
+ <MicSelectorContext.Provider value={contextValue}>
202
+ <Popover {...props} onOpenChange={onOpenChange} open={open} />
203
+ </MicSelectorContext.Provider>
204
+ );
205
+ };
206
+
207
+ export type MicSelectorTriggerProps = ComponentProps<typeof Button>;
208
+
209
+ export const MicSelectorTrigger = ({
210
+ children,
211
+ ...props
212
+ }: MicSelectorTriggerProps) => {
213
+ const { setWidth } = useContext(MicSelectorContext);
214
+ const ref = useRef<HTMLButtonElement>(null);
215
+
216
+ useEffect(() => {
217
+ // Create a ResizeObserver to detect width changes
218
+ const resizeObserver = new ResizeObserver((entries) => {
219
+ for (const entry of entries) {
220
+ const newWidth = (entry.target as HTMLElement).offsetWidth;
221
+ if (newWidth) {
222
+ setWidth?.(newWidth);
223
+ }
224
+ }
225
+ });
226
+
227
+ if (ref.current) {
228
+ resizeObserver.observe(ref.current);
229
+ }
230
+
231
+ // Clean up the observer when component unmounts
232
+ return () => {
233
+ resizeObserver.disconnect();
234
+ };
235
+ }, [setWidth]);
236
+
237
+ return (
238
+ <PopoverTrigger asChild>
239
+ <Button variant="outline" {...props} ref={ref}>
240
+ {children}
241
+ <ChevronsUpDownIcon
242
+ className="shrink-0 text-muted-foreground"
243
+ size={16}
244
+ />
245
+ </Button>
246
+ </PopoverTrigger>
247
+ );
248
+ };
249
+
250
+ export type MicSelectorContentProps = ComponentProps<typeof Command> & {
251
+ popoverOptions?: ComponentProps<typeof PopoverContent>;
252
+ };
253
+
254
+ export const MicSelectorContent = ({
255
+ className,
256
+ popoverOptions,
257
+ ...props
258
+ }: MicSelectorContentProps) => {
259
+ const { width, onValueChange, value } = useContext(MicSelectorContext);
260
+
261
+ return (
262
+ <PopoverContent
263
+ className={cn("p-0", className)}
264
+ style={{ width }}
265
+ {...popoverOptions}
266
+ >
267
+ <Command onValueChange={onValueChange} value={value} {...props} />
268
+ </PopoverContent>
269
+ );
270
+ };
271
+
272
+ export type MicSelectorInputProps = ComponentProps<typeof CommandInput> & {
273
+ value?: string;
274
+ defaultValue?: string;
275
+ onValueChange?: (value: string) => void;
276
+ };
277
+
278
+ export const MicSelectorInput = ({ ...props }: MicSelectorInputProps) => (
279
+ <CommandInput placeholder="Search microphones..." {...props} />
280
+ );
281
+
282
+ export type MicSelectorListProps = Omit<
283
+ ComponentProps<typeof CommandList>,
284
+ "children"
285
+ > & {
286
+ children: (devices: MediaDeviceInfo[]) => ReactNode;
287
+ };
288
+
289
+ export const MicSelectorList = ({
290
+ children,
291
+ ...props
292
+ }: MicSelectorListProps) => {
293
+ const { data } = useContext(MicSelectorContext);
294
+
295
+ return <CommandList {...props}>{children(data)}</CommandList>;
296
+ };
297
+
298
+ export type MicSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
299
+
300
+ export const MicSelectorEmpty = ({
301
+ children = "No microphone found.",
302
+ ...props
303
+ }: MicSelectorEmptyProps) => <CommandEmpty {...props}>{children}</CommandEmpty>;
304
+
305
+ export type MicSelectorItemProps = ComponentProps<typeof CommandItem>;
306
+
307
+ export const MicSelectorItem = (props: MicSelectorItemProps) => {
308
+ const { onValueChange, onOpenChange } = useContext(MicSelectorContext);
309
+
310
+ const handleSelect = useCallback(
311
+ (currentValue: string) => {
312
+ onValueChange?.(currentValue);
313
+ onOpenChange?.(false);
314
+ },
315
+ [onValueChange, onOpenChange]
316
+ );
317
+
318
+ return <CommandItem onSelect={handleSelect} {...props} />;
319
+ };
320
+
321
+ export type MicSelectorLabelProps = ComponentProps<"span"> & {
322
+ device: MediaDeviceInfo;
323
+ };
324
+
325
+ export const MicSelectorLabel = ({
326
+ device,
327
+ className,
328
+ ...props
329
+ }: MicSelectorLabelProps) => {
330
+ const matches = device.label.match(deviceIdRegex);
331
+
332
+ if (!matches) {
333
+ return (
334
+ <span className={className} {...props}>
335
+ {device.label}
336
+ </span>
337
+ );
338
+ }
339
+
340
+ const [, deviceId] = matches;
341
+ const name = device.label.replace(deviceIdRegex, "");
342
+
343
+ return (
344
+ <span className={className} {...props}>
345
+ <span>{name}</span>
346
+ <span className="text-muted-foreground"> ({deviceId})</span>
347
+ </span>
348
+ );
349
+ };
350
+
351
+ export type MicSelectorValueProps = ComponentProps<"span">;
352
+
353
+ export const MicSelectorValue = ({
354
+ className,
355
+ ...props
356
+ }: MicSelectorValueProps) => {
357
+ const { data, value } = useContext(MicSelectorContext);
358
+ const currentDevice = data.find((d) => d.deviceId === value);
359
+
360
+ if (!currentDevice) {
361
+ return (
362
+ <span className={cn("flex-1 text-left", className)} {...props}>
363
+ Select microphone...
364
+ </span>
365
+ );
366
+ }
367
+
368
+ return (
369
+ <MicSelectorLabel
370
+ className={cn("flex-1 text-left", className)}
371
+ device={currentDevice}
372
+ {...props}
373
+ />
374
+ );
375
+ };
@@ -0,0 +1,213 @@
1
+ import {
2
+ Command,
3
+ CommandDialog,
4
+ CommandEmpty,
5
+ CommandGroup,
6
+ CommandInput,
7
+ CommandItem,
8
+ CommandList,
9
+ CommandSeparator,
10
+ CommandShortcut,
11
+ } from "../ui/command";
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogTitle,
16
+ DialogTrigger,
17
+ } from "../ui/dialog";
18
+ import { cn } from "../../lib/utils";
19
+ import type { ComponentProps, ReactNode } from "react";
20
+
21
+ export type ModelSelectorProps = ComponentProps<typeof Dialog>;
22
+
23
+ export const ModelSelector = (props: ModelSelectorProps) => (
24
+ <Dialog {...props} />
25
+ );
26
+
27
+ export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
28
+
29
+ export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
30
+ <DialogTrigger {...props} />
31
+ );
32
+
33
+ export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
34
+ title?: ReactNode;
35
+ };
36
+
37
+ export const ModelSelectorContent = ({
38
+ className,
39
+ children,
40
+ title = "Model Selector",
41
+ ...props
42
+ }: ModelSelectorContentProps) => (
43
+ <DialogContent
44
+ aria-describedby={undefined}
45
+ className={cn(
46
+ "outline! border-none! p-0 outline-border! outline-solid!",
47
+ className
48
+ )}
49
+ {...props}
50
+ >
51
+ <DialogTitle className="sr-only">{title}</DialogTitle>
52
+ <Command className="**:data-[slot=command-input-wrapper]:h-auto">
53
+ {children}
54
+ </Command>
55
+ </DialogContent>
56
+ );
57
+
58
+ export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
59
+
60
+ export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
61
+ <CommandDialog {...props} />
62
+ );
63
+
64
+ export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
65
+
66
+ export const ModelSelectorInput = ({
67
+ className,
68
+ ...props
69
+ }: ModelSelectorInputProps) => (
70
+ <CommandInput className={cn("h-auto py-3.5", className)} {...props} />
71
+ );
72
+
73
+ export type ModelSelectorListProps = ComponentProps<typeof CommandList>;
74
+
75
+ export const ModelSelectorList = (props: ModelSelectorListProps) => (
76
+ <CommandList {...props} />
77
+ );
78
+
79
+ export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
80
+
81
+ export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => (
82
+ <CommandEmpty {...props} />
83
+ );
84
+
85
+ export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
86
+
87
+ export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => (
88
+ <CommandGroup {...props} />
89
+ );
90
+
91
+ export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
92
+
93
+ export const ModelSelectorItem = (props: ModelSelectorItemProps) => (
94
+ <CommandItem {...props} />
95
+ );
96
+
97
+ export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
98
+
99
+ export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
100
+ <CommandShortcut {...props} />
101
+ );
102
+
103
+ export type ModelSelectorSeparatorProps = ComponentProps<
104
+ typeof CommandSeparator
105
+ >;
106
+
107
+ export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
108
+ <CommandSeparator {...props} />
109
+ );
110
+
111
+ export type ModelSelectorLogoProps = Omit<
112
+ ComponentProps<"img">,
113
+ "src" | "alt"
114
+ > & {
115
+ provider:
116
+ | "moonshotai-cn"
117
+ | "lucidquery"
118
+ | "moonshotai"
119
+ | "zai-coding-plan"
120
+ | "alibaba"
121
+ | "xai"
122
+ | "vultr"
123
+ | "nvidia"
124
+ | "upstage"
125
+ | "groq"
126
+ | "github-copilot"
127
+ | "mistral"
128
+ | "vercel"
129
+ | "nebius"
130
+ | "deepseek"
131
+ | "alibaba-cn"
132
+ | "google-vertex-anthropic"
133
+ | "venice"
134
+ | "chutes"
135
+ | "cortecs"
136
+ | "github-models"
137
+ | "togetherai"
138
+ | "azure"
139
+ | "baseten"
140
+ | "huggingface"
141
+ | "opencode"
142
+ | "fastrouter"
143
+ | "google"
144
+ | "google-vertex"
145
+ | "cloudflare-workers-ai"
146
+ | "inception"
147
+ | "wandb"
148
+ | "openai"
149
+ | "zhipuai-coding-plan"
150
+ | "perplexity"
151
+ | "openrouter"
152
+ | "zenmux"
153
+ | "v0"
154
+ | "iflowcn"
155
+ | "synthetic"
156
+ | "deepinfra"
157
+ | "zhipuai"
158
+ | "submodel"
159
+ | "zai"
160
+ | "inference"
161
+ | "requesty"
162
+ | "morph"
163
+ | "lmstudio"
164
+ | "anthropic"
165
+ | "aihubmix"
166
+ | "fireworks-ai"
167
+ | "modelscope"
168
+ | "llama"
169
+ | "scaleway"
170
+ | "amazon-bedrock"
171
+ | "cerebras"
172
+ // oxlint-disable-next-line typescript-eslint(ban-types) -- intentional pattern for autocomplete-friendly string union
173
+ | (string & {});
174
+ };
175
+
176
+ export const ModelSelectorLogo = ({
177
+ provider,
178
+ className,
179
+ ...props
180
+ }: ModelSelectorLogoProps) => (
181
+ <img
182
+ {...props}
183
+ alt={`${provider} logo`}
184
+ className={cn("size-3 dark:invert", className)}
185
+ height={12}
186
+ src={`https://models.dev/logos/${provider}.svg`}
187
+ width={12}
188
+ />
189
+ );
190
+
191
+ export type ModelSelectorLogoGroupProps = ComponentProps<"div">;
192
+
193
+ export const ModelSelectorLogoGroup = ({
194
+ className,
195
+ ...props
196
+ }: ModelSelectorLogoGroupProps) => (
197
+ <div
198
+ className={cn(
199
+ "flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 dark:[&>img]:bg-foreground",
200
+ className
201
+ )}
202
+ {...props}
203
+ />
204
+ );
205
+
206
+ export type ModelSelectorNameProps = ComponentProps<"span">;
207
+
208
+ export const ModelSelectorName = ({
209
+ className,
210
+ ...props
211
+ }: ModelSelectorNameProps) => (
212
+ <span className={cn("flex-1 truncate text-left", className)} {...props} />
213
+ );
@@ -0,0 +1,71 @@
1
+ import {
2
+ Card,
3
+ CardAction,
4
+ CardContent,
5
+ CardDescription,
6
+ CardFooter,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from "../ui/card";
10
+ import { cn } from "../../lib/utils";
11
+ import { Handle, Position } from "@xyflow/react";
12
+ import type { ComponentProps } from "react";
13
+
14
+ export type NodeProps = ComponentProps<typeof Card> & {
15
+ handles: {
16
+ target: boolean;
17
+ source: boolean;
18
+ };
19
+ };
20
+
21
+ export const Node = ({ handles, className, ...props }: NodeProps) => (
22
+ <Card
23
+ className={cn(
24
+ "node-container relative size-full h-auto w-sm gap-0 rounded-md p-0",
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {handles.target && <Handle position={Position.Left} type="target" />}
30
+ {handles.source && <Handle position={Position.Right} type="source" />}
31
+ {props.children}
32
+ </Card>
33
+ );
34
+
35
+ export type NodeHeaderProps = ComponentProps<typeof CardHeader>;
36
+
37
+ export const NodeHeader = ({ className, ...props }: NodeHeaderProps) => (
38
+ <CardHeader
39
+ className={cn("gap-0.5 rounded-t-md border-b bg-secondary p-3!", className)}
40
+ {...props}
41
+ />
42
+ );
43
+
44
+ export type NodeTitleProps = ComponentProps<typeof CardTitle>;
45
+
46
+ export const NodeTitle = (props: NodeTitleProps) => <CardTitle {...props} />;
47
+
48
+ export type NodeDescriptionProps = ComponentProps<typeof CardDescription>;
49
+
50
+ export const NodeDescription = (props: NodeDescriptionProps) => (
51
+ <CardDescription {...props} />
52
+ );
53
+
54
+ export type NodeActionProps = ComponentProps<typeof CardAction>;
55
+
56
+ export const NodeAction = (props: NodeActionProps) => <CardAction {...props} />;
57
+
58
+ export type NodeContentProps = ComponentProps<typeof CardContent>;
59
+
60
+ export const NodeContent = ({ className, ...props }: NodeContentProps) => (
61
+ <CardContent className={cn("p-3", className)} {...props} />
62
+ );
63
+
64
+ export type NodeFooterProps = ComponentProps<typeof CardFooter>;
65
+
66
+ export const NodeFooter = ({ className, ...props }: NodeFooterProps) => (
67
+ <CardFooter
68
+ className={cn("rounded-b-md border-t bg-secondary p-3!", className)}
69
+ {...props}
70
+ />
71
+ );