@vvedantb/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.
- package/LICENSE +21 -0
- package/package.json +124 -4
- package/src/ai-elements/artifact.tsx +99 -0
- package/src/ai-elements/attachments.tsx +366 -0
- package/src/ai-elements/chain-of-thought.tsx +212 -0
- package/src/ai-elements/code-block.tsx +478 -0
- package/src/ai-elements/conversation.tsx +157 -0
- package/src/ai-elements/image.tsx +25 -0
- package/src/ai-elements/inline-citation.tsx +252 -0
- package/src/ai-elements/message.tsx +269 -0
- package/src/ai-elements/model-selector.tsx +211 -0
- package/src/ai-elements/prompt-input.tsx +1018 -0
- package/src/ai-elements/queue.tsx +251 -0
- package/src/ai-elements/reasoning.tsx +178 -0
- package/src/ai-elements/shimmer.tsx +56 -0
- package/src/ai-elements/sources.tsx +77 -0
- package/src/ai-elements/speech-input.tsx +290 -0
- package/src/ai-elements/suggestion.tsx +191 -0
- package/src/css.d.ts +2 -0
- package/src/index.ts +96 -0
- package/src/motion/presets.ts +35 -0
- package/src/motion/presets.vibot.ts +105 -0
- package/src/ui/_menu-classes.ts +30 -0
- package/src/ui/_menu-classes.vibot.ts +33 -0
- package/src/ui/accordion.tsx +81 -0
- package/src/ui/accordion.verve.tsx +56 -0
- package/src/ui/badge.tsx +34 -0
- package/src/ui/breadcrumb.tsx +83 -0
- package/src/ui/button-group.tsx +84 -0
- package/src/ui/button.tsx +57 -0
- package/src/ui/card.tsx +27 -0
- package/src/ui/carousel.tsx +211 -0
- package/src/ui/checkbox.tsx +27 -0
- package/src/ui/clear-input.tsx +170 -0
- package/src/ui/clear-input.vibot.tsx +76 -0
- package/src/ui/clearInputDissolve.ts +214 -0
- package/src/ui/collapsible.tsx +7 -0
- package/src/ui/command.tsx +148 -0
- package/src/ui/context-menu.tsx +173 -0
- package/src/ui/dialog.tsx +149 -0
- package/src/ui/dropdown-menu.tsx +178 -0
- package/src/ui/hover-card.tsx +31 -0
- package/src/ui/input-group.tsx +162 -0
- package/src/ui/input-group.verve.tsx +150 -0
- package/src/ui/input.tsx +21 -0
- package/src/ui/kbd.tsx +18 -0
- package/src/ui/label.tsx +20 -0
- package/src/ui/labeled-switch-row.tsx +53 -0
- package/src/ui/modalTransition.ts +85 -0
- package/src/ui/pagination.tsx +47 -0
- package/src/ui/popover.tsx +33 -0
- package/src/ui/progress.tsx +25 -0
- package/src/ui/radix-select.tsx +202 -0
- package/src/ui/scroll-area.tsx +55 -0
- package/src/ui/scroll-area.verve.tsx +43 -0
- package/src/ui/search-input.tsx +29 -0
- package/src/ui/select.tsx +163 -0
- package/src/ui/separator.tsx +28 -0
- package/src/ui/sheet.tsx +87 -0
- package/src/ui/skeleton.tsx +18 -0
- package/src/ui/sonner.css +16 -0
- package/src/ui/sonner.tsx +47 -0
- package/src/ui/spinner.tsx +27 -0
- package/src/ui/surface-classes.ts +55 -0
- package/src/ui/switch.tsx +26 -0
- package/src/ui/table.tsx +123 -0
- package/src/ui/tabs.tsx +140 -0
- package/src/ui/tabsSliding.ts +56 -0
- package/src/ui/textarea.tsx +21 -0
- package/src/ui/time-picker.tsx +170 -0
- package/src/ui/tooltip.tsx +25 -0
- package/src/ui/visually-hidden.tsx +3 -0
- package/src/utils/cn.ts +16 -0
- package/src/utils/ref.ts +10 -0
- package/README.md +0 -3
|
@@ -0,0 +1,1018 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { ChatStatus, FileUIPart, SourceDocumentUIPart } from "ai";
|
|
3
|
+
import type {
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
ChangeEventHandler,
|
|
6
|
+
ClipboardEventHandler,
|
|
7
|
+
ComponentProps,
|
|
8
|
+
FormEvent,
|
|
9
|
+
FormEventHandler,
|
|
10
|
+
HTMLAttributes,
|
|
11
|
+
KeyboardEventHandler,
|
|
12
|
+
MouseEvent,
|
|
13
|
+
PropsWithChildren,
|
|
14
|
+
ReactNode,
|
|
15
|
+
RefObject,
|
|
16
|
+
} from "react";
|
|
17
|
+
import {
|
|
18
|
+
Command,
|
|
19
|
+
CommandEmpty,
|
|
20
|
+
CommandGroup,
|
|
21
|
+
CommandInput,
|
|
22
|
+
CommandItem,
|
|
23
|
+
CommandList,
|
|
24
|
+
CommandSeparator,
|
|
25
|
+
} from "../ui/command";
|
|
26
|
+
import {
|
|
27
|
+
DropdownMenu as Dropdown,
|
|
28
|
+
DropdownMenuTrigger as DropdownTrigger,
|
|
29
|
+
DropdownMenuContent as DropdownMenu,
|
|
30
|
+
DropdownMenuItem as DropdownItem,
|
|
31
|
+
} from "../ui/dropdown-menu";
|
|
32
|
+
import {
|
|
33
|
+
HoverCard,
|
|
34
|
+
HoverCardContent,
|
|
35
|
+
HoverCardTrigger,
|
|
36
|
+
} from "../ui/hover-card";
|
|
37
|
+
import {
|
|
38
|
+
InputGroup,
|
|
39
|
+
InputGroupAddon,
|
|
40
|
+
InputGroupButton,
|
|
41
|
+
InputGroupTextarea,
|
|
42
|
+
} from "../ui/input-group";
|
|
43
|
+
import {
|
|
44
|
+
Select,
|
|
45
|
+
SelectContent,
|
|
46
|
+
SelectItem,
|
|
47
|
+
SelectTrigger,
|
|
48
|
+
SelectValue,
|
|
49
|
+
} from "../ui/radix-select";
|
|
50
|
+
import { Spinner } from "../ui/spinner";
|
|
51
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
52
|
+
import { cn } from "../utils/cn";
|
|
53
|
+
import { segmentExpandedStateClass } from "../ui/surface-classes";
|
|
54
|
+
import {
|
|
55
|
+
IconCornerDownLeft,
|
|
56
|
+
IconPhoto,
|
|
57
|
+
IconPlus,
|
|
58
|
+
IconSquare,
|
|
59
|
+
IconX,
|
|
60
|
+
} from "@tabler/icons-react";
|
|
61
|
+
import { nanoid } from "nanoid";
|
|
62
|
+
import {
|
|
63
|
+
Children,
|
|
64
|
+
createContext,
|
|
65
|
+
useContext,
|
|
66
|
+
useEffect,
|
|
67
|
+
useRef,
|
|
68
|
+
useState,
|
|
69
|
+
} from "react";
|
|
70
|
+
const convertBlobUrlToDataUrl = async (url: string): Promise<string | null> => {
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(url);
|
|
73
|
+
const blob = await response.blob();
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const reader = new FileReader();
|
|
76
|
+
reader.onloadend = () => resolve(reader.result as string);
|
|
77
|
+
reader.onerror = () => resolve(null);
|
|
78
|
+
reader.readAsDataURL(blob);
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
export interface AttachmentsContext {
|
|
85
|
+
files: (FileUIPart & {
|
|
86
|
+
id: string;
|
|
87
|
+
})[];
|
|
88
|
+
add: (files: File[] | FileList) => void;
|
|
89
|
+
remove: (id: string) => void;
|
|
90
|
+
clear: () => void;
|
|
91
|
+
openFileDialog: () => void;
|
|
92
|
+
fileInputRef: RefObject<HTMLInputElement | null>;
|
|
93
|
+
}
|
|
94
|
+
export interface TextInputContext {
|
|
95
|
+
value: string;
|
|
96
|
+
setInput: (v: string) => void;
|
|
97
|
+
clear: () => void;
|
|
98
|
+
}
|
|
99
|
+
export interface PromptInputControllerProps {
|
|
100
|
+
textInput: TextInputContext;
|
|
101
|
+
attachments: AttachmentsContext;
|
|
102
|
+
__registerFileInput: (
|
|
103
|
+
ref: RefObject<HTMLInputElement | null>,
|
|
104
|
+
open: () => void,
|
|
105
|
+
) => void;
|
|
106
|
+
}
|
|
107
|
+
const PromptInputController = createContext<PromptInputControllerProps | null>(
|
|
108
|
+
null,
|
|
109
|
+
);
|
|
110
|
+
const ProviderAttachmentsContext = createContext<AttachmentsContext | null>(
|
|
111
|
+
null,
|
|
112
|
+
);
|
|
113
|
+
export const usePromptInputController = () => {
|
|
114
|
+
const ctx = useContext(PromptInputController);
|
|
115
|
+
if (!ctx) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Wrap your component inside <PromptInputProvider> to use usePromptInputController().",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return ctx;
|
|
121
|
+
};
|
|
122
|
+
const useOptionalPromptInputController = () =>
|
|
123
|
+
useContext(PromptInputController);
|
|
124
|
+
export const useProviderAttachments = () => {
|
|
125
|
+
const ctx = useContext(ProviderAttachmentsContext);
|
|
126
|
+
if (!ctx) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"Wrap your component inside <PromptInputProvider> to use useProviderAttachments().",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return ctx;
|
|
132
|
+
};
|
|
133
|
+
const useOptionalProviderAttachments = () =>
|
|
134
|
+
useContext(ProviderAttachmentsContext);
|
|
135
|
+
export type PromptInputProviderProps = PropsWithChildren<{
|
|
136
|
+
initialInput?: string;
|
|
137
|
+
}>;
|
|
138
|
+
export const PromptInputProvider = ({
|
|
139
|
+
initialInput: initialTextInput = "",
|
|
140
|
+
children,
|
|
141
|
+
}: PromptInputProviderProps) => {
|
|
142
|
+
const [textInput, setTextInput] = useState(initialTextInput);
|
|
143
|
+
const clearInput = () => setTextInput("");
|
|
144
|
+
const [attachmentFiles, setAttachmentFiles] = useState<
|
|
145
|
+
(FileUIPart & {
|
|
146
|
+
id: string;
|
|
147
|
+
})[]
|
|
148
|
+
>([]);
|
|
149
|
+
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
150
|
+
const openRef = useRef<() => void>(() => {});
|
|
151
|
+
const add = (files: File[] | FileList) => {
|
|
152
|
+
const incoming = [...files];
|
|
153
|
+
if (incoming.length === 0) return;
|
|
154
|
+
setAttachmentFiles((prev) => [
|
|
155
|
+
...prev,
|
|
156
|
+
...incoming.map((file) => ({
|
|
157
|
+
filename: file.name,
|
|
158
|
+
id: nanoid(),
|
|
159
|
+
mediaType: file.type,
|
|
160
|
+
type: "file" as const,
|
|
161
|
+
url: URL.createObjectURL(file),
|
|
162
|
+
})),
|
|
163
|
+
]);
|
|
164
|
+
};
|
|
165
|
+
const remove = (id: string) => {
|
|
166
|
+
setAttachmentFiles((prev) => {
|
|
167
|
+
const found = prev.find((f) => f.id === id);
|
|
168
|
+
if (found?.url) URL.revokeObjectURL(found.url);
|
|
169
|
+
return prev.filter((f) => f.id !== id);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const clear = () => {
|
|
173
|
+
setAttachmentFiles((prev) => {
|
|
174
|
+
for (const f of prev) {
|
|
175
|
+
if (f.url) URL.revokeObjectURL(f.url);
|
|
176
|
+
}
|
|
177
|
+
return [];
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
const attachmentsRef = useRef(attachmentFiles);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
attachmentsRef.current = attachmentFiles;
|
|
183
|
+
}, [attachmentFiles]);
|
|
184
|
+
useEffect(
|
|
185
|
+
() => () => {
|
|
186
|
+
for (const f of attachmentsRef.current) {
|
|
187
|
+
if (f.url) URL.revokeObjectURL(f.url);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[],
|
|
191
|
+
);
|
|
192
|
+
const openFileDialog = () => {
|
|
193
|
+
openRef.current?.();
|
|
194
|
+
};
|
|
195
|
+
const attachments = {
|
|
196
|
+
add,
|
|
197
|
+
clear,
|
|
198
|
+
fileInputRef,
|
|
199
|
+
files: attachmentFiles,
|
|
200
|
+
openFileDialog,
|
|
201
|
+
remove,
|
|
202
|
+
};
|
|
203
|
+
const __registerFileInput = (
|
|
204
|
+
ref: RefObject<HTMLInputElement | null>,
|
|
205
|
+
open: () => void,
|
|
206
|
+
) => {
|
|
207
|
+
fileInputRef.current = ref.current;
|
|
208
|
+
openRef.current = open;
|
|
209
|
+
};
|
|
210
|
+
const controller = {
|
|
211
|
+
__registerFileInput,
|
|
212
|
+
attachments,
|
|
213
|
+
textInput: {
|
|
214
|
+
clear: clearInput,
|
|
215
|
+
setInput: setTextInput,
|
|
216
|
+
value: textInput,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
return (
|
|
220
|
+
<PromptInputController.Provider value={controller}>
|
|
221
|
+
<ProviderAttachmentsContext.Provider value={attachments}>
|
|
222
|
+
{children}
|
|
223
|
+
</ProviderAttachmentsContext.Provider>
|
|
224
|
+
</PromptInputController.Provider>
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
const LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);
|
|
228
|
+
export const usePromptInputAttachments = () => {
|
|
229
|
+
const provider = useOptionalProviderAttachments();
|
|
230
|
+
const local = useContext(LocalAttachmentsContext);
|
|
231
|
+
const context = local ?? provider;
|
|
232
|
+
if (!context) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
"usePromptInputAttachments must be used within a PromptInput or PromptInputProvider",
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return context;
|
|
238
|
+
};
|
|
239
|
+
export interface ReferencedSourcesContext {
|
|
240
|
+
sources: (SourceDocumentUIPart & {
|
|
241
|
+
id: string;
|
|
242
|
+
})[];
|
|
243
|
+
add: (sources: SourceDocumentUIPart[] | SourceDocumentUIPart) => void;
|
|
244
|
+
remove: (id: string) => void;
|
|
245
|
+
clear: () => void;
|
|
246
|
+
}
|
|
247
|
+
export const LocalReferencedSourcesContext =
|
|
248
|
+
createContext<ReferencedSourcesContext | null>(null);
|
|
249
|
+
export const usePromptInputReferencedSources = () => {
|
|
250
|
+
const ctx = useContext(LocalReferencedSourcesContext);
|
|
251
|
+
if (!ctx) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
"usePromptInputReferencedSources must be used within a LocalReferencedSourcesContext.Provider",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return ctx;
|
|
257
|
+
};
|
|
258
|
+
export type PromptInputActionAddAttachmentsProps = ComponentProps<
|
|
259
|
+
typeof DropdownItem
|
|
260
|
+
> & {
|
|
261
|
+
label?: string;
|
|
262
|
+
};
|
|
263
|
+
export const PromptInputActionAddAttachments = ({
|
|
264
|
+
label = "Add photos or files",
|
|
265
|
+
...props
|
|
266
|
+
}: PromptInputActionAddAttachmentsProps) => {
|
|
267
|
+
const attachments = usePromptInputAttachments();
|
|
268
|
+
const handleClick = (e: MouseEvent<HTMLDivElement>) => {
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
attachments.openFileDialog();
|
|
271
|
+
};
|
|
272
|
+
return (
|
|
273
|
+
<DropdownItem {...props} onClick={handleClick}>
|
|
274
|
+
<IconPhoto className="mr-2 size-4" /> {label}
|
|
275
|
+
</DropdownItem>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
export interface PromptInputMessage {
|
|
279
|
+
text: string;
|
|
280
|
+
files: FileUIPart[];
|
|
281
|
+
}
|
|
282
|
+
export type PromptInputProps = Omit<
|
|
283
|
+
HTMLAttributes<HTMLFormElement>,
|
|
284
|
+
"onSubmit" | "onError"
|
|
285
|
+
> & {
|
|
286
|
+
accept?: string;
|
|
287
|
+
multiple?: boolean;
|
|
288
|
+
globalDrop?: boolean;
|
|
289
|
+
syncHiddenInput?: boolean;
|
|
290
|
+
maxFiles?: number;
|
|
291
|
+
maxFileSize?: number;
|
|
292
|
+
containerClassName?: string;
|
|
293
|
+
onError?: (err: {
|
|
294
|
+
code: "max_files" | "max_file_size" | "accept";
|
|
295
|
+
message: string;
|
|
296
|
+
}) => void;
|
|
297
|
+
onSubmit: (
|
|
298
|
+
message: PromptInputMessage,
|
|
299
|
+
event: FormEvent<HTMLFormElement>,
|
|
300
|
+
) => void | Promise<void>;
|
|
301
|
+
};
|
|
302
|
+
export const PromptInput = ({
|
|
303
|
+
className,
|
|
304
|
+
accept,
|
|
305
|
+
multiple,
|
|
306
|
+
globalDrop,
|
|
307
|
+
syncHiddenInput,
|
|
308
|
+
maxFiles,
|
|
309
|
+
maxFileSize,
|
|
310
|
+
containerClassName,
|
|
311
|
+
onError,
|
|
312
|
+
onSubmit,
|
|
313
|
+
children,
|
|
314
|
+
...props
|
|
315
|
+
}: PromptInputProps) => {
|
|
316
|
+
const controller = useOptionalPromptInputController();
|
|
317
|
+
const usingProvider = !!controller;
|
|
318
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
319
|
+
const formRef = useRef<HTMLFormElement | null>(null);
|
|
320
|
+
const [items, setItems] = useState<
|
|
321
|
+
(FileUIPart & {
|
|
322
|
+
id: string;
|
|
323
|
+
})[]
|
|
324
|
+
>([]);
|
|
325
|
+
const files = usingProvider ? controller.attachments.files : items;
|
|
326
|
+
const [referencedSources, setReferencedSources] = useState<
|
|
327
|
+
(SourceDocumentUIPart & {
|
|
328
|
+
id: string;
|
|
329
|
+
})[]
|
|
330
|
+
>([]);
|
|
331
|
+
const filesRef = useRef(files);
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
filesRef.current = files;
|
|
334
|
+
}, [files]);
|
|
335
|
+
const openFileDialogLocal = () => {
|
|
336
|
+
inputRef.current?.click();
|
|
337
|
+
};
|
|
338
|
+
const matchesAccept = (f: File) => {
|
|
339
|
+
if (!accept || accept.trim() === "") return true;
|
|
340
|
+
const patterns = accept
|
|
341
|
+
.split(",")
|
|
342
|
+
.map((s) => s.trim())
|
|
343
|
+
.filter(Boolean);
|
|
344
|
+
return patterns.some((pattern) => {
|
|
345
|
+
if (pattern.endsWith("/*")) {
|
|
346
|
+
const prefix = pattern.slice(0, -1);
|
|
347
|
+
return f.type.startsWith(prefix);
|
|
348
|
+
}
|
|
349
|
+
return f.type === pattern;
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
const addLocal = (fileList: File[] | FileList) => {
|
|
353
|
+
const incoming = [...fileList];
|
|
354
|
+
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
355
|
+
if (incoming.length && accepted.length === 0) {
|
|
356
|
+
onError?.({
|
|
357
|
+
code: "accept",
|
|
358
|
+
message: "No files match the accepted types.",
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const withinSize = (f: File) =>
|
|
363
|
+
maxFileSize ? f.size <= maxFileSize : true;
|
|
364
|
+
const sized = accepted.filter(withinSize);
|
|
365
|
+
if (accepted.length > 0 && sized.length === 0) {
|
|
366
|
+
onError?.({
|
|
367
|
+
code: "max_file_size",
|
|
368
|
+
message: "All files exceed the maximum size.",
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
setItems((prev) => {
|
|
373
|
+
const capacity =
|
|
374
|
+
typeof maxFiles === "number"
|
|
375
|
+
? Math.max(0, maxFiles - prev.length)
|
|
376
|
+
: undefined;
|
|
377
|
+
const capped =
|
|
378
|
+
typeof capacity === "number" ? sized.slice(0, capacity) : sized;
|
|
379
|
+
if (typeof capacity === "number" && sized.length > capacity) {
|
|
380
|
+
onError?.({
|
|
381
|
+
code: "max_files",
|
|
382
|
+
message: "Too many files. Some were not added.",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
const next: (FileUIPart & {
|
|
386
|
+
id: string;
|
|
387
|
+
})[] = [];
|
|
388
|
+
for (const file of capped) {
|
|
389
|
+
next.push({
|
|
390
|
+
filename: file.name,
|
|
391
|
+
id: nanoid(),
|
|
392
|
+
mediaType: file.type,
|
|
393
|
+
type: "file",
|
|
394
|
+
url: URL.createObjectURL(file),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return [...prev, ...next];
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
const removeLocal = (id: string) =>
|
|
401
|
+
setItems((prev) => {
|
|
402
|
+
const found = prev.find((file) => file.id === id);
|
|
403
|
+
if (found?.url) URL.revokeObjectURL(found.url);
|
|
404
|
+
return prev.filter((file) => file.id !== id);
|
|
405
|
+
});
|
|
406
|
+
const addWithProviderValidation = (fileList: File[] | FileList) => {
|
|
407
|
+
const incoming = [...fileList];
|
|
408
|
+
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
409
|
+
if (incoming.length && accepted.length === 0) {
|
|
410
|
+
onError?.({
|
|
411
|
+
code: "accept",
|
|
412
|
+
message: "No files match the accepted types.",
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const withinSize = (f: File) =>
|
|
417
|
+
maxFileSize ? f.size <= maxFileSize : true;
|
|
418
|
+
const sized = accepted.filter(withinSize);
|
|
419
|
+
if (accepted.length > 0 && sized.length === 0) {
|
|
420
|
+
onError?.({
|
|
421
|
+
code: "max_file_size",
|
|
422
|
+
message: "All files exceed the maximum size.",
|
|
423
|
+
});
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const currentCount = files.length;
|
|
427
|
+
const capacity =
|
|
428
|
+
typeof maxFiles === "number"
|
|
429
|
+
? Math.max(0, maxFiles - currentCount)
|
|
430
|
+
: undefined;
|
|
431
|
+
const capped =
|
|
432
|
+
typeof capacity === "number" ? sized.slice(0, capacity) : sized;
|
|
433
|
+
if (typeof capacity === "number" && sized.length > capacity) {
|
|
434
|
+
onError?.({
|
|
435
|
+
code: "max_files",
|
|
436
|
+
message: "Too many files. Some were not added.",
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (capped.length > 0) {
|
|
440
|
+
controller?.attachments.add(capped);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const clearAttachments = () =>
|
|
444
|
+
usingProvider
|
|
445
|
+
? controller?.attachments.clear()
|
|
446
|
+
: setItems((prev) => {
|
|
447
|
+
for (const file of prev) {
|
|
448
|
+
if (file.url) URL.revokeObjectURL(file.url);
|
|
449
|
+
}
|
|
450
|
+
return [];
|
|
451
|
+
});
|
|
452
|
+
const clearReferencedSources = () => setReferencedSources([]);
|
|
453
|
+
const add = usingProvider ? addWithProviderValidation : addLocal;
|
|
454
|
+
const remove = usingProvider ? controller.attachments.remove : removeLocal;
|
|
455
|
+
const openFileDialog = usingProvider
|
|
456
|
+
? controller.attachments.openFileDialog
|
|
457
|
+
: openFileDialogLocal;
|
|
458
|
+
const clear = () => {
|
|
459
|
+
clearAttachments();
|
|
460
|
+
clearReferencedSources();
|
|
461
|
+
};
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
if (!usingProvider) return;
|
|
464
|
+
controller.__registerFileInput(inputRef, () => inputRef.current?.click());
|
|
465
|
+
}, [usingProvider, controller]);
|
|
466
|
+
useEffect(() => {
|
|
467
|
+
if (syncHiddenInput && inputRef.current && files.length === 0) {
|
|
468
|
+
inputRef.current.value = "";
|
|
469
|
+
}
|
|
470
|
+
}, [files, syncHiddenInput]);
|
|
471
|
+
useEffect(() => {
|
|
472
|
+
const form = formRef.current;
|
|
473
|
+
if (!form) return;
|
|
474
|
+
if (globalDrop) return;
|
|
475
|
+
const onDragOver = (e: DragEvent) => {
|
|
476
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
477
|
+
};
|
|
478
|
+
const onDrop = (e: DragEvent) => {
|
|
479
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
480
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
|
481
|
+
add(e.dataTransfer.files);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
form.addEventListener("dragover", onDragOver);
|
|
485
|
+
form.addEventListener("drop", onDrop);
|
|
486
|
+
return () => {
|
|
487
|
+
form.removeEventListener("dragover", onDragOver);
|
|
488
|
+
form.removeEventListener("drop", onDrop);
|
|
489
|
+
};
|
|
490
|
+
}, [add, globalDrop]);
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (!globalDrop) return;
|
|
493
|
+
const onDragOver = (e: DragEvent) => {
|
|
494
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
495
|
+
};
|
|
496
|
+
const onDrop = (e: DragEvent) => {
|
|
497
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
498
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
|
499
|
+
add(e.dataTransfer.files);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
document.addEventListener("dragover", onDragOver);
|
|
503
|
+
document.addEventListener("drop", onDrop);
|
|
504
|
+
return () => {
|
|
505
|
+
document.removeEventListener("dragover", onDragOver);
|
|
506
|
+
document.removeEventListener("drop", onDrop);
|
|
507
|
+
};
|
|
508
|
+
}, [add, globalDrop]);
|
|
509
|
+
useEffect(
|
|
510
|
+
() => () => {
|
|
511
|
+
if (!usingProvider) {
|
|
512
|
+
for (const f of filesRef.current) {
|
|
513
|
+
if (f.url) URL.revokeObjectURL(f.url);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
[usingProvider],
|
|
518
|
+
);
|
|
519
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
520
|
+
if (event.currentTarget.files) add(event.currentTarget.files);
|
|
521
|
+
event.currentTarget.value = "";
|
|
522
|
+
};
|
|
523
|
+
const attachmentsCtx = {
|
|
524
|
+
add,
|
|
525
|
+
clear: clearAttachments,
|
|
526
|
+
fileInputRef: inputRef,
|
|
527
|
+
files: files.map((item) => ({ ...item, id: item.id })),
|
|
528
|
+
openFileDialog,
|
|
529
|
+
remove,
|
|
530
|
+
};
|
|
531
|
+
const refsCtx = {
|
|
532
|
+
add: (incoming: SourceDocumentUIPart[] | SourceDocumentUIPart) => {
|
|
533
|
+
const array = Array.isArray(incoming) ? incoming : [incoming];
|
|
534
|
+
setReferencedSources((prev) => [
|
|
535
|
+
...prev,
|
|
536
|
+
...array.map((s) => ({ ...s, id: nanoid() })),
|
|
537
|
+
]);
|
|
538
|
+
},
|
|
539
|
+
clear: clearReferencedSources,
|
|
540
|
+
remove: (id: string) => {
|
|
541
|
+
setReferencedSources((prev) => prev.filter((s) => s.id !== id));
|
|
542
|
+
},
|
|
543
|
+
sources: referencedSources,
|
|
544
|
+
};
|
|
545
|
+
const handleSubmit: FormEventHandler<HTMLFormElement> = async (event) => {
|
|
546
|
+
event.preventDefault();
|
|
547
|
+
const form = event.currentTarget;
|
|
548
|
+
const text = usingProvider
|
|
549
|
+
? controller.textInput.value
|
|
550
|
+
: (() => {
|
|
551
|
+
const formData = new FormData(form);
|
|
552
|
+
return (formData.get("message") as string) || "";
|
|
553
|
+
})();
|
|
554
|
+
if (!usingProvider) form.reset();
|
|
555
|
+
try {
|
|
556
|
+
const convertedFiles: FileUIPart[] = await Promise.all(
|
|
557
|
+
files.map(async ({ id: _id, ...item }) => {
|
|
558
|
+
if (item.url?.startsWith("blob:")) {
|
|
559
|
+
const dataUrl = await convertBlobUrlToDataUrl(item.url);
|
|
560
|
+
return { ...item, url: dataUrl ?? item.url };
|
|
561
|
+
}
|
|
562
|
+
return item;
|
|
563
|
+
}),
|
|
564
|
+
);
|
|
565
|
+
const result = onSubmit({ files: convertedFiles, text }, event);
|
|
566
|
+
if (result instanceof Promise) {
|
|
567
|
+
try {
|
|
568
|
+
await result;
|
|
569
|
+
clear();
|
|
570
|
+
if (usingProvider) controller.textInput.clear();
|
|
571
|
+
} catch {
|
|
572
|
+
/* Don't clear on error */
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
clear();
|
|
576
|
+
if (usingProvider) controller.textInput.clear();
|
|
577
|
+
}
|
|
578
|
+
} catch {
|
|
579
|
+
/* Don't clear on error */
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const inner = (
|
|
583
|
+
<>
|
|
584
|
+
<input
|
|
585
|
+
accept={accept}
|
|
586
|
+
aria-label="Upload files"
|
|
587
|
+
className="hidden"
|
|
588
|
+
multiple={multiple}
|
|
589
|
+
onChange={handleChange}
|
|
590
|
+
ref={inputRef}
|
|
591
|
+
title="Upload files"
|
|
592
|
+
type="file"
|
|
593
|
+
/>
|
|
594
|
+
<form
|
|
595
|
+
className={cn("w-full", className)}
|
|
596
|
+
onSubmit={handleSubmit}
|
|
597
|
+
ref={formRef}
|
|
598
|
+
{...props}
|
|
599
|
+
>
|
|
600
|
+
<InputGroup className={cn("overflow-hidden", containerClassName)}>
|
|
601
|
+
{children}
|
|
602
|
+
</InputGroup>
|
|
603
|
+
</form>
|
|
604
|
+
</>
|
|
605
|
+
);
|
|
606
|
+
const withReferencedSources = (
|
|
607
|
+
<LocalReferencedSourcesContext.Provider value={refsCtx}>
|
|
608
|
+
{inner}
|
|
609
|
+
</LocalReferencedSourcesContext.Provider>
|
|
610
|
+
);
|
|
611
|
+
return (
|
|
612
|
+
<LocalAttachmentsContext.Provider value={attachmentsCtx}>
|
|
613
|
+
{withReferencedSources}
|
|
614
|
+
</LocalAttachmentsContext.Provider>
|
|
615
|
+
);
|
|
616
|
+
};
|
|
617
|
+
export type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;
|
|
618
|
+
export const PromptInputBody = ({
|
|
619
|
+
className,
|
|
620
|
+
...props
|
|
621
|
+
}: PromptInputBodyProps) => (
|
|
622
|
+
<div className={cn("contents", className)} {...props} />
|
|
623
|
+
);
|
|
624
|
+
export type PromptInputTextareaProps = ComponentProps<
|
|
625
|
+
typeof InputGroupTextarea
|
|
626
|
+
>;
|
|
627
|
+
export const PromptInputTextarea = ({
|
|
628
|
+
onChange,
|
|
629
|
+
onKeyDown,
|
|
630
|
+
className,
|
|
631
|
+
placeholder = "What would you like to know?",
|
|
632
|
+
...props
|
|
633
|
+
}: PromptInputTextareaProps) => {
|
|
634
|
+
const controller = useOptionalPromptInputController();
|
|
635
|
+
const attachments = usePromptInputAttachments();
|
|
636
|
+
const [isComposing, setIsComposing] = useState(false);
|
|
637
|
+
const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
|
|
638
|
+
onKeyDown?.(e);
|
|
639
|
+
if (e.defaultPrevented) return;
|
|
640
|
+
if (e.key === "Enter") {
|
|
641
|
+
if (isComposing || e.nativeEvent.isComposing) return;
|
|
642
|
+
if (e.shiftKey) return;
|
|
643
|
+
e.preventDefault();
|
|
644
|
+
const { form } = e.currentTarget;
|
|
645
|
+
const submitButton = form?.querySelector(
|
|
646
|
+
'button[type="submit"]',
|
|
647
|
+
) as HTMLButtonElement | null;
|
|
648
|
+
if (submitButton?.disabled) return;
|
|
649
|
+
form?.requestSubmit();
|
|
650
|
+
}
|
|
651
|
+
if (
|
|
652
|
+
e.key === "Backspace" &&
|
|
653
|
+
e.currentTarget.value === "" &&
|
|
654
|
+
attachments.files.length > 0
|
|
655
|
+
) {
|
|
656
|
+
e.preventDefault();
|
|
657
|
+
const lastAttachment = attachments.files.at(-1);
|
|
658
|
+
if (lastAttachment) attachments.remove(lastAttachment.id);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = (event) => {
|
|
662
|
+
const items = event.clipboardData?.items;
|
|
663
|
+
if (!items) return;
|
|
664
|
+
const pastedFiles: File[] = [];
|
|
665
|
+
for (const item of items) {
|
|
666
|
+
if (item.kind === "file") {
|
|
667
|
+
const file = item.getAsFile();
|
|
668
|
+
if (file) pastedFiles.push(file);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (pastedFiles.length > 0) {
|
|
672
|
+
event.preventDefault();
|
|
673
|
+
attachments.add(pastedFiles);
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
const handleCompositionEnd = () => setIsComposing(false);
|
|
677
|
+
const handleCompositionStart = () => setIsComposing(true);
|
|
678
|
+
const controlledProps = controller
|
|
679
|
+
? {
|
|
680
|
+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
681
|
+
controller.textInput.setInput(e.currentTarget.value);
|
|
682
|
+
onChange?.(e);
|
|
683
|
+
},
|
|
684
|
+
value: controller.textInput.value,
|
|
685
|
+
}
|
|
686
|
+
: { onChange };
|
|
687
|
+
return (
|
|
688
|
+
<InputGroupTextarea
|
|
689
|
+
className={cn("field-sizing-content max-h-48 min-h-16", className)}
|
|
690
|
+
name="message"
|
|
691
|
+
onCompositionEnd={handleCompositionEnd}
|
|
692
|
+
onCompositionStart={handleCompositionStart}
|
|
693
|
+
onKeyDown={handleKeyDown}
|
|
694
|
+
onPaste={handlePaste}
|
|
695
|
+
placeholder={placeholder}
|
|
696
|
+
{...props}
|
|
697
|
+
{...controlledProps}
|
|
698
|
+
/>
|
|
699
|
+
);
|
|
700
|
+
};
|
|
701
|
+
export type PromptInputHeaderProps = Omit<
|
|
702
|
+
ComponentProps<typeof InputGroupAddon>,
|
|
703
|
+
"align"
|
|
704
|
+
>;
|
|
705
|
+
export const PromptInputHeader = ({
|
|
706
|
+
className,
|
|
707
|
+
...props
|
|
708
|
+
}: PromptInputHeaderProps) => (
|
|
709
|
+
<InputGroupAddon
|
|
710
|
+
align="block-end"
|
|
711
|
+
className={cn("order-first flex-wrap gap-1", className)}
|
|
712
|
+
{...props}
|
|
713
|
+
/>
|
|
714
|
+
);
|
|
715
|
+
export type PromptInputFooterProps = Omit<
|
|
716
|
+
ComponentProps<typeof InputGroupAddon>,
|
|
717
|
+
"align"
|
|
718
|
+
>;
|
|
719
|
+
export const PromptInputFooter = ({
|
|
720
|
+
className,
|
|
721
|
+
...props
|
|
722
|
+
}: PromptInputFooterProps) => (
|
|
723
|
+
<InputGroupAddon
|
|
724
|
+
align="block-end"
|
|
725
|
+
className={cn("justify-between gap-1", className)}
|
|
726
|
+
{...props}
|
|
727
|
+
/>
|
|
728
|
+
);
|
|
729
|
+
export type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;
|
|
730
|
+
export const PromptInputTools = ({
|
|
731
|
+
className,
|
|
732
|
+
...props
|
|
733
|
+
}: PromptInputToolsProps) => (
|
|
734
|
+
<div
|
|
735
|
+
className={cn("flex min-w-0 items-center gap-1", className)}
|
|
736
|
+
{...props}
|
|
737
|
+
/>
|
|
738
|
+
);
|
|
739
|
+
export type PromptInputButtonTooltip =
|
|
740
|
+
| string
|
|
741
|
+
| {
|
|
742
|
+
content: ReactNode;
|
|
743
|
+
shortcut?: string;
|
|
744
|
+
side?: ComponentProps<typeof TooltipContent>["side"];
|
|
745
|
+
};
|
|
746
|
+
export type PromptInputButtonProps = ComponentProps<typeof InputGroupButton> & {
|
|
747
|
+
tooltip?: PromptInputButtonTooltip;
|
|
748
|
+
};
|
|
749
|
+
export const PromptInputButton = ({
|
|
750
|
+
variant = "ghost",
|
|
751
|
+
className,
|
|
752
|
+
size,
|
|
753
|
+
tooltip,
|
|
754
|
+
...props
|
|
755
|
+
}: PromptInputButtonProps) => {
|
|
756
|
+
const newSize =
|
|
757
|
+
size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");
|
|
758
|
+
const button = (
|
|
759
|
+
<InputGroupButton
|
|
760
|
+
className={cn(className)}
|
|
761
|
+
size={newSize}
|
|
762
|
+
type="button"
|
|
763
|
+
variant={variant}
|
|
764
|
+
{...props}
|
|
765
|
+
/>
|
|
766
|
+
);
|
|
767
|
+
if (!tooltip) return button;
|
|
768
|
+
const tooltipContent =
|
|
769
|
+
typeof tooltip === "string" ? tooltip : tooltip.content;
|
|
770
|
+
const shortcut = typeof tooltip === "string" ? undefined : tooltip.shortcut;
|
|
771
|
+
const side = typeof tooltip === "string" ? "top" : tooltip.side ?? "top";
|
|
772
|
+
return (
|
|
773
|
+
<Tooltip>
|
|
774
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
775
|
+
<TooltipContent side={side}>
|
|
776
|
+
{tooltipContent}
|
|
777
|
+
{shortcut && <span className="ml-2 text-muted">{shortcut}</span>}
|
|
778
|
+
</TooltipContent>
|
|
779
|
+
</Tooltip>
|
|
780
|
+
);
|
|
781
|
+
};
|
|
782
|
+
export type PromptInputActionMenuProps = ComponentProps<typeof Dropdown>;
|
|
783
|
+
export const PromptInputActionMenu = (props: PromptInputActionMenuProps) => (
|
|
784
|
+
<Dropdown {...props} />
|
|
785
|
+
);
|
|
786
|
+
export type PromptInputActionMenuTriggerProps = PromptInputButtonProps;
|
|
787
|
+
export const PromptInputActionMenuTrigger = ({
|
|
788
|
+
className,
|
|
789
|
+
children,
|
|
790
|
+
...props
|
|
791
|
+
}: PromptInputActionMenuTriggerProps) => (
|
|
792
|
+
<DropdownTrigger>
|
|
793
|
+
<PromptInputButton className={className} {...props}>
|
|
794
|
+
{children ?? <IconPlus className="size-4" />}
|
|
795
|
+
</PromptInputButton>
|
|
796
|
+
</DropdownTrigger>
|
|
797
|
+
);
|
|
798
|
+
export type PromptInputActionMenuContentProps = ComponentProps<
|
|
799
|
+
typeof DropdownMenu
|
|
800
|
+
>;
|
|
801
|
+
export const PromptInputActionMenuContent = ({
|
|
802
|
+
className,
|
|
803
|
+
...props
|
|
804
|
+
}: PromptInputActionMenuContentProps) => (
|
|
805
|
+
<DropdownMenu align="start" className={cn(className)} {...props} />
|
|
806
|
+
);
|
|
807
|
+
export type PromptInputActionMenuItemProps = ComponentProps<
|
|
808
|
+
typeof DropdownItem
|
|
809
|
+
>;
|
|
810
|
+
export const PromptInputActionMenuItem = ({
|
|
811
|
+
className,
|
|
812
|
+
...props
|
|
813
|
+
}: PromptInputActionMenuItemProps) => (
|
|
814
|
+
<DropdownItem className={cn(className)} {...props} />
|
|
815
|
+
);
|
|
816
|
+
export type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
|
|
817
|
+
status?: ChatStatus;
|
|
818
|
+
onStop?: () => void;
|
|
819
|
+
};
|
|
820
|
+
export const PromptInputSubmit = ({
|
|
821
|
+
className,
|
|
822
|
+
variant = "default",
|
|
823
|
+
size = "icon-sm",
|
|
824
|
+
status,
|
|
825
|
+
onStop,
|
|
826
|
+
onClick,
|
|
827
|
+
children,
|
|
828
|
+
...props
|
|
829
|
+
}: PromptInputSubmitProps) => {
|
|
830
|
+
const isGenerating = status === "submitted" || status === "streaming";
|
|
831
|
+
let Icon = <IconCornerDownLeft className="size-4" />;
|
|
832
|
+
if (status === "submitted") {
|
|
833
|
+
Icon = <Spinner size="sm" />;
|
|
834
|
+
} else if (status === "streaming") {
|
|
835
|
+
Icon = <IconSquare className="size-4" />;
|
|
836
|
+
} else if (status === "error") {
|
|
837
|
+
Icon = <IconX className="size-4" />;
|
|
838
|
+
}
|
|
839
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
840
|
+
if (isGenerating && onStop) {
|
|
841
|
+
e.preventDefault();
|
|
842
|
+
onStop();
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
onClick?.(e);
|
|
846
|
+
};
|
|
847
|
+
return (
|
|
848
|
+
<InputGroupButton
|
|
849
|
+
aria-label={isGenerating ? "Stop" : "Submit"}
|
|
850
|
+
className={cn(className)}
|
|
851
|
+
onClick={handleClick}
|
|
852
|
+
size={size}
|
|
853
|
+
type={isGenerating && onStop ? "button" : "submit"}
|
|
854
|
+
variant={variant}
|
|
855
|
+
{...props}
|
|
856
|
+
>
|
|
857
|
+
{children ?? Icon}
|
|
858
|
+
</InputGroupButton>
|
|
859
|
+
);
|
|
860
|
+
};
|
|
861
|
+
export type PromptInputModelSelectProps = ComponentProps<typeof Select>;
|
|
862
|
+
export const PromptInputModelSelect = (props: PromptInputModelSelectProps) => (
|
|
863
|
+
<Select {...props} />
|
|
864
|
+
);
|
|
865
|
+
export type PromptInputModelSelectTriggerProps = ComponentProps<
|
|
866
|
+
typeof SelectTrigger
|
|
867
|
+
>;
|
|
868
|
+
export const PromptInputModelSelectTrigger = ({
|
|
869
|
+
className,
|
|
870
|
+
...props
|
|
871
|
+
}: PromptInputModelSelectTriggerProps) => (
|
|
872
|
+
<SelectTrigger
|
|
873
|
+
className={cn(
|
|
874
|
+
"border-none bg-transparent font-medium text-muted shadow-none transition-[color,transform] duration-150 ease-out active:scale-[0.97]",
|
|
875
|
+
"hover:bg-surface-secondary hover:text-foreground",
|
|
876
|
+
segmentExpandedStateClass,
|
|
877
|
+
className,
|
|
878
|
+
)}
|
|
879
|
+
{...props}
|
|
880
|
+
/>
|
|
881
|
+
);
|
|
882
|
+
export type PromptInputModelSelectContentProps = ComponentProps<
|
|
883
|
+
typeof SelectContent
|
|
884
|
+
>;
|
|
885
|
+
export const PromptInputModelSelectContent = ({
|
|
886
|
+
className,
|
|
887
|
+
...props
|
|
888
|
+
}: PromptInputModelSelectContentProps) => (
|
|
889
|
+
<SelectContent className={cn(className)} {...props} />
|
|
890
|
+
);
|
|
891
|
+
export type PromptInputModelSelectItemProps = ComponentProps<typeof SelectItem>;
|
|
892
|
+
export const PromptInputModelSelectItem = ({
|
|
893
|
+
className,
|
|
894
|
+
...props
|
|
895
|
+
}: PromptInputModelSelectItemProps) => (
|
|
896
|
+
<SelectItem className={cn(className)} {...props} />
|
|
897
|
+
);
|
|
898
|
+
export type PromptInputModelSelectValueProps = ComponentProps<
|
|
899
|
+
typeof SelectValue
|
|
900
|
+
>;
|
|
901
|
+
export const PromptInputModelSelectValue = ({
|
|
902
|
+
className,
|
|
903
|
+
...props
|
|
904
|
+
}: PromptInputModelSelectValueProps) => (
|
|
905
|
+
<SelectValue className={cn(className)} {...props} />
|
|
906
|
+
);
|
|
907
|
+
export type PromptInputHoverCardProps = ComponentProps<typeof HoverCard>;
|
|
908
|
+
export const PromptInputHoverCard = ({
|
|
909
|
+
openDelay = 0,
|
|
910
|
+
closeDelay = 0,
|
|
911
|
+
...props
|
|
912
|
+
}: PromptInputHoverCardProps) => (
|
|
913
|
+
<HoverCard closeDelay={closeDelay} openDelay={openDelay} {...props} />
|
|
914
|
+
);
|
|
915
|
+
export type PromptInputHoverCardTriggerProps = ComponentProps<
|
|
916
|
+
typeof HoverCardTrigger
|
|
917
|
+
>;
|
|
918
|
+
export const PromptInputHoverCardTrigger = (
|
|
919
|
+
props: PromptInputHoverCardTriggerProps,
|
|
920
|
+
) => <HoverCardTrigger {...props} />;
|
|
921
|
+
export type PromptInputHoverCardContentProps = ComponentProps<
|
|
922
|
+
typeof HoverCardContent
|
|
923
|
+
>;
|
|
924
|
+
export const PromptInputHoverCardContent = ({
|
|
925
|
+
align = "start",
|
|
926
|
+
...props
|
|
927
|
+
}: PromptInputHoverCardContentProps) => (
|
|
928
|
+
<HoverCardContent align={align} {...props} />
|
|
929
|
+
);
|
|
930
|
+
export type PromptInputTabsListProps = HTMLAttributes<HTMLDivElement>;
|
|
931
|
+
export const PromptInputTabsList = ({
|
|
932
|
+
className,
|
|
933
|
+
...props
|
|
934
|
+
}: PromptInputTabsListProps) => <div className={cn(className)} {...props} />;
|
|
935
|
+
export type PromptInputTabProps = HTMLAttributes<HTMLDivElement>;
|
|
936
|
+
export const PromptInputTab = ({
|
|
937
|
+
className,
|
|
938
|
+
...props
|
|
939
|
+
}: PromptInputTabProps) => <div className={cn(className)} {...props} />;
|
|
940
|
+
export type PromptInputTabLabelProps = HTMLAttributes<HTMLHeadingElement>;
|
|
941
|
+
export const PromptInputTabLabel = ({
|
|
942
|
+
className,
|
|
943
|
+
...props
|
|
944
|
+
}: PromptInputTabLabelProps) => (
|
|
945
|
+
<h3
|
|
946
|
+
className={cn("mb-2 px-3 font-medium text-muted text-xs", className)}
|
|
947
|
+
{...props}
|
|
948
|
+
/>
|
|
949
|
+
);
|
|
950
|
+
export type PromptInputTabBodyProps = HTMLAttributes<HTMLDivElement>;
|
|
951
|
+
export const PromptInputTabBody = ({
|
|
952
|
+
className,
|
|
953
|
+
...props
|
|
954
|
+
}: PromptInputTabBodyProps) => (
|
|
955
|
+
<div className={cn("space-y-1", className)} {...props} />
|
|
956
|
+
);
|
|
957
|
+
export type PromptInputTabItemProps = HTMLAttributes<HTMLDivElement>;
|
|
958
|
+
export const PromptInputTabItem = ({
|
|
959
|
+
className,
|
|
960
|
+
...props
|
|
961
|
+
}: PromptInputTabItemProps) => (
|
|
962
|
+
<div
|
|
963
|
+
className={cn(
|
|
964
|
+
"flex items-center gap-2 rounded-md px-3 py-2 text-xs transition-[background-color,transform] duration-150 ease-out active:scale-[0.99] hover:bg-surface-secondary",
|
|
965
|
+
className,
|
|
966
|
+
)}
|
|
967
|
+
{...props}
|
|
968
|
+
/>
|
|
969
|
+
);
|
|
970
|
+
export type PromptInputCommandProps = ComponentProps<typeof Command>;
|
|
971
|
+
export const PromptInputCommand = ({
|
|
972
|
+
className,
|
|
973
|
+
...props
|
|
974
|
+
}: PromptInputCommandProps) => <Command className={cn(className)} {...props} />;
|
|
975
|
+
export type PromptInputCommandInputProps = ComponentProps<typeof CommandInput>;
|
|
976
|
+
export const PromptInputCommandInput = ({
|
|
977
|
+
className,
|
|
978
|
+
...props
|
|
979
|
+
}: PromptInputCommandInputProps) => (
|
|
980
|
+
<CommandInput className={cn(className)} {...props} />
|
|
981
|
+
);
|
|
982
|
+
export type PromptInputCommandListProps = ComponentProps<typeof CommandList>;
|
|
983
|
+
export const PromptInputCommandList = ({
|
|
984
|
+
className,
|
|
985
|
+
...props
|
|
986
|
+
}: PromptInputCommandListProps) => (
|
|
987
|
+
<CommandList className={cn(className)} {...props} />
|
|
988
|
+
);
|
|
989
|
+
export type PromptInputCommandEmptyProps = ComponentProps<typeof CommandEmpty>;
|
|
990
|
+
export const PromptInputCommandEmpty = ({
|
|
991
|
+
className,
|
|
992
|
+
...props
|
|
993
|
+
}: PromptInputCommandEmptyProps) => (
|
|
994
|
+
<CommandEmpty className={cn(className)} {...props} />
|
|
995
|
+
);
|
|
996
|
+
export type PromptInputCommandGroupProps = ComponentProps<typeof CommandGroup>;
|
|
997
|
+
export const PromptInputCommandGroup = ({
|
|
998
|
+
className,
|
|
999
|
+
...props
|
|
1000
|
+
}: PromptInputCommandGroupProps) => (
|
|
1001
|
+
<CommandGroup className={cn(className)} {...props} />
|
|
1002
|
+
);
|
|
1003
|
+
export type PromptInputCommandItemProps = ComponentProps<typeof CommandItem>;
|
|
1004
|
+
export const PromptInputCommandItem = ({
|
|
1005
|
+
className,
|
|
1006
|
+
...props
|
|
1007
|
+
}: PromptInputCommandItemProps) => (
|
|
1008
|
+
<CommandItem className={cn(className)} {...props} />
|
|
1009
|
+
);
|
|
1010
|
+
export type PromptInputCommandSeparatorProps = ComponentProps<
|
|
1011
|
+
typeof CommandSeparator
|
|
1012
|
+
>;
|
|
1013
|
+
export const PromptInputCommandSeparator = ({
|
|
1014
|
+
className,
|
|
1015
|
+
...props
|
|
1016
|
+
}: PromptInputCommandSeparatorProps) => (
|
|
1017
|
+
<CommandSeparator className={cn(className)} {...props} />
|
|
1018
|
+
);
|