@cloudbase/agent-react-ui 1.0.1-alpha.32

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