@websolutespa/ask-ui 1.0.3 → 1.0.4
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/dist/components/accordion.d.mts +25 -0
- package/dist/components/accordion.mjs +55 -0
- package/dist/components/ai-elements/attachments.d.mts +94 -0
- package/dist/components/ai-elements/attachments.mjs +174 -0
- package/dist/components/ai-elements/conversation.d.mts +51 -0
- package/dist/components/ai-elements/conversation.mjs +85 -0
- package/dist/components/ai-elements/message.d.mts +89 -0
- package/dist/components/ai-elements/message.mjs +175 -0
- package/dist/components/ai-elements/prompt-input.d.mts +277 -0
- package/dist/components/ai-elements/prompt-input.mjs +703 -0
- package/dist/components/ai-elements/speech-input.d.mts +64 -0
- package/dist/components/ai-elements/speech-input.mjs +153 -0
- package/dist/components/alert-dialog.d.mts +60 -0
- package/dist/components/alert-dialog.mjs +101 -0
- package/dist/components/alert.d.mts +28 -0
- package/dist/components/alert.mjs +43 -0
- package/dist/components/aspect-ratio.d.mts +9 -0
- package/dist/components/aspect-ratio.mjs +12 -0
- package/dist/components/avatar.d.mts +34 -0
- package/dist/components/avatar.mjs +51 -0
- package/dist/components/badge.d.mts +19 -0
- package/dist/components/badge.mjs +27 -0
- package/dist/components/breadcrumb.d.mts +38 -0
- package/dist/components/breadcrumb.mjs +70 -0
- package/dist/components/button-group.d.mts +28 -0
- package/dist/components/button-group.mjs +38 -0
- package/dist/components/button.d.mts +21 -0
- package/dist/components/button.mjs +47 -0
- package/dist/components/calendar.d.mts +30 -0
- package/dist/components/calendar.mjs +109 -0
- package/dist/components/card.d.mts +37 -0
- package/dist/components/card.mjs +56 -0
- package/dist/components/carousel.d.mts +56 -0
- package/dist/components/carousel.mjs +132 -0
- package/dist/components/chart.d.mts +78 -0
- package/dist/components/chart.mjs +146 -0
- package/dist/components/checkbox.d.mts +11 -0
- package/dist/components/checkbox.mjs +21 -0
- package/dist/components/collapsible.d.mts +15 -0
- package/dist/components/collapsible.mjs +24 -0
- package/dist/components/combobox.d.mts +81 -0
- package/dist/components/combobox.mjs +163 -0
- package/dist/components/command.d.mts +54 -0
- package/dist/components/command.mjs +88 -0
- package/dist/components/context-menu.d.mts +85 -0
- package/dist/components/context-menu.mjs +125 -0
- package/dist/components/dialog.d.mts +51 -0
- package/dist/components/dialog.mjs +96 -0
- package/dist/components/direction.d.mts +15 -0
- package/dist/components/direction.mjs +14 -0
- package/dist/components/drawer.d.mts +47 -0
- package/dist/components/drawer.mjs +79 -0
- package/dist/components/dropdown-menu.d.mts +87 -0
- package/dist/components/dropdown-menu.mjs +131 -0
- package/dist/components/empty.d.mts +35 -0
- package/dist/components/empty.mjs +59 -0
- package/dist/components/field.d.mts +64 -0
- package/dist/components/field.mjs +110 -0
- package/dist/components/hover-card.d.mts +22 -0
- package/dist/components/hover-card.mjs +33 -0
- package/dist/components/input-group.d.mts +43 -0
- package/dist/components/input-group.mjs +79 -0
- package/dist/components/input-otp.d.mts +28 -0
- package/dist/components/input-otp.mjs +47 -0
- package/dist/components/input.d.mts +11 -0
- package/dist/components/input.mjs +14 -0
- package/dist/components/item.d.mts +62 -0
- package/dist/components/item.mjs +117 -0
- package/dist/components/kbd.d.mts +13 -0
- package/dist/components/kbd.mjs +19 -0
- package/dist/components/label.d.mts +11 -0
- package/dist/components/label.mjs +15 -0
- package/dist/components/menubar.d.mts +90 -0
- package/dist/components/menubar.mjs +135 -0
- package/dist/components/native-select.d.mts +21 -0
- package/dist/components/native-select.mjs +37 -0
- package/dist/components/navigation-menu.d.mts +46 -0
- package/dist/components/navigation-menu.mjs +80 -0
- package/dist/components/pagination.d.mts +45 -0
- package/dist/components/pagination.mjs +80 -0
- package/dist/components/popover.d.mts +34 -0
- package/dist/components/popover.mjs +56 -0
- package/dist/components/progress.d.mts +12 -0
- package/dist/components/progress.mjs +20 -0
- package/dist/components/radio-group.d.mts +15 -0
- package/dist/components/radio-group.mjs +27 -0
- package/dist/components/resizable.d.mts +20 -0
- package/dist/components/resizable.mjs +28 -0
- package/dist/components/scroll-area.d.mts +17 -0
- package/dist/components/scroll-area.mjs +37 -0
- package/dist/components/select.d.mts +53 -0
- package/dist/components/select.mjs +100 -0
- package/dist/components/separator.d.mts +13 -0
- package/dist/components/separator.mjs +17 -0
- package/dist/components/sheet.d.mts +42 -0
- package/dist/components/sheet.mjs +90 -0
- package/dist/components/sidebar.d.mts +167 -0
- package/dist/components/sidebar.mjs +374 -0
- package/dist/components/skeleton.d.mts +9 -0
- package/dist/components/skeleton.mjs +12 -0
- package/dist/components/slider.d.mts +15 -0
- package/dist/components/slider.mjs +36 -0
- package/dist/components/sonner.d.mts +9 -0
- package/dist/components/sonner.mjs +30 -0
- package/dist/components/spinner.d.mts +9 -0
- package/dist/components/spinner.mjs +14 -0
- package/dist/components/switch.d.mts +14 -0
- package/dist/components/switch.mjs +20 -0
- package/dist/components/table.d.mts +38 -0
- package/dist/components/table.mjs +67 -0
- package/dist/components/tabs.d.mts +30 -0
- package/dist/components/tabs.mjs +46 -0
- package/dist/components/textarea.d.mts +10 -0
- package/dist/components/textarea.mjs +13 -0
- package/dist/components/toggle-group.d.mts +28 -0
- package/dist/components/toggle-group.mjs +51 -0
- package/dist/components/toggle.d.mts +19 -0
- package/dist/components/toggle.mjs +37 -0
- package/dist/components/tooltip.d.mts +23 -0
- package/dist/components/tooltip.mjs +36 -0
- package/dist/exports/components.d.mts +61 -0
- package/dist/exports/components.mjs +61 -0
- package/dist/exports/hooks.d.mts +2 -0
- package/dist/exports/hooks.mjs +2 -0
- package/dist/exports/lib.d.mts +2 -0
- package/dist/exports/lib.mjs +2 -0
- package/dist/hooks/use-mobile.d.mts +4 -0
- package/dist/hooks/use-mobile.mjs +18 -0
- package/package.json +1 -1
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.mjs";
|
|
3
|
+
import { HoverCard, HoverCardContent, HoverCardTrigger } from "../hover-card.mjs";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip.mjs";
|
|
5
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "../input-group.mjs";
|
|
6
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "../command.mjs";
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../dropdown-menu.mjs";
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select.mjs";
|
|
9
|
+
import { Spinner } from "../spinner.mjs";
|
|
10
|
+
import { ArrowUpIcon, ImageIcon, Monitor, PlusIcon, SquareIcon, XIcon } from "lucide-react";
|
|
11
|
+
import { Children, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { nanoid } from "nanoid";
|
|
14
|
+
//#region src/components/ai-elements/prompt-input.tsx
|
|
15
|
+
const convertBlobUrlToDataUrl = async (url) => {
|
|
16
|
+
try {
|
|
17
|
+
const blob = await (await fetch(url)).blob();
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const reader = new FileReader();
|
|
20
|
+
reader.onloadend = () => resolve(reader.result);
|
|
21
|
+
reader.onerror = () => resolve(null);
|
|
22
|
+
reader.readAsDataURL(blob);
|
|
23
|
+
});
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const captureScreenshot = async () => {
|
|
29
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getDisplayMedia) return null;
|
|
30
|
+
let stream = null;
|
|
31
|
+
const video = document.createElement("video");
|
|
32
|
+
video.muted = true;
|
|
33
|
+
video.playsInline = true;
|
|
34
|
+
try {
|
|
35
|
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
|
36
|
+
audio: false,
|
|
37
|
+
video: true
|
|
38
|
+
});
|
|
39
|
+
video.srcObject = stream;
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
video.onloadedmetadata = () => resolve();
|
|
42
|
+
video.onerror = () => reject(/* @__PURE__ */ new Error("Failed to load screen stream"));
|
|
43
|
+
});
|
|
44
|
+
await video.play();
|
|
45
|
+
const width = video.videoWidth;
|
|
46
|
+
const height = video.videoHeight;
|
|
47
|
+
if (!width || !height) return null;
|
|
48
|
+
const canvas = document.createElement("canvas");
|
|
49
|
+
canvas.width = width;
|
|
50
|
+
canvas.height = height;
|
|
51
|
+
const context = canvas.getContext("2d");
|
|
52
|
+
if (!context) return null;
|
|
53
|
+
context.drawImage(video, 0, 0, width, height);
|
|
54
|
+
const blob = await new Promise((resolve) => {
|
|
55
|
+
canvas.toBlob(resolve, "image/png");
|
|
56
|
+
});
|
|
57
|
+
if (!blob) return null;
|
|
58
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-").replace("T", "_").replace("Z", "");
|
|
59
|
+
return new File([blob], `screenshot-${timestamp}.png`, {
|
|
60
|
+
lastModified: Date.now(),
|
|
61
|
+
type: "image/png"
|
|
62
|
+
});
|
|
63
|
+
} finally {
|
|
64
|
+
if (stream) for (const track of stream.getTracks()) track.stop();
|
|
65
|
+
video.pause();
|
|
66
|
+
video.srcObject = null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const PromptInputController = createContext(null);
|
|
70
|
+
const ProviderAttachmentsContext = createContext(null);
|
|
71
|
+
const usePromptInputController = () => {
|
|
72
|
+
const ctx = useContext(PromptInputController);
|
|
73
|
+
if (!ctx) throw new Error("Wrap your component inside <PromptInputProvider> to use usePromptInputController().");
|
|
74
|
+
return ctx;
|
|
75
|
+
};
|
|
76
|
+
const useOptionalPromptInputController = () => useContext(PromptInputController);
|
|
77
|
+
const useProviderAttachments = () => {
|
|
78
|
+
const ctx = useContext(ProviderAttachmentsContext);
|
|
79
|
+
if (!ctx) throw new Error("Wrap your component inside <PromptInputProvider> to use useProviderAttachments().");
|
|
80
|
+
return ctx;
|
|
81
|
+
};
|
|
82
|
+
const useOptionalProviderAttachments = () => useContext(ProviderAttachmentsContext);
|
|
83
|
+
/**
|
|
84
|
+
* Optional global provider that lifts PromptInput state outside of PromptInput.
|
|
85
|
+
* If you don't use it, PromptInput stays fully self-managed.
|
|
86
|
+
*/
|
|
87
|
+
const PromptInputProvider = ({ initialInput: initialTextInput = "", children }) => {
|
|
88
|
+
const [textInput, setTextInput] = useState(initialTextInput);
|
|
89
|
+
const clearInput = useCallback(() => setTextInput(""), []);
|
|
90
|
+
const [attachmentFiles, setAttachmentFiles] = useState([]);
|
|
91
|
+
const fileInputRef = useRef(null);
|
|
92
|
+
const openRef = useRef(() => {});
|
|
93
|
+
const add = useCallback((files) => {
|
|
94
|
+
const incoming = [...files];
|
|
95
|
+
if (incoming.length === 0) return;
|
|
96
|
+
setAttachmentFiles((prev) => [...prev, ...incoming.map((file) => ({
|
|
97
|
+
filename: file.name,
|
|
98
|
+
id: nanoid(),
|
|
99
|
+
mediaType: file.type,
|
|
100
|
+
type: "file",
|
|
101
|
+
url: URL.createObjectURL(file)
|
|
102
|
+
}))]);
|
|
103
|
+
}, []);
|
|
104
|
+
const remove = useCallback((id) => {
|
|
105
|
+
setAttachmentFiles((prev) => {
|
|
106
|
+
const found = prev.find((f) => f.id === id);
|
|
107
|
+
if (found?.url) URL.revokeObjectURL(found.url);
|
|
108
|
+
return prev.filter((f) => f.id !== id);
|
|
109
|
+
});
|
|
110
|
+
}, []);
|
|
111
|
+
const clear = useCallback(() => {
|
|
112
|
+
setAttachmentFiles((prev) => {
|
|
113
|
+
for (const f of prev) if (f.url) URL.revokeObjectURL(f.url);
|
|
114
|
+
return [];
|
|
115
|
+
});
|
|
116
|
+
}, []);
|
|
117
|
+
const attachmentsRef = useRef(attachmentFiles);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
attachmentsRef.current = attachmentFiles;
|
|
120
|
+
}, [attachmentFiles]);
|
|
121
|
+
useEffect(() => () => {
|
|
122
|
+
for (const f of attachmentsRef.current) if (f.url) URL.revokeObjectURL(f.url);
|
|
123
|
+
}, []);
|
|
124
|
+
const openFileDialog = useCallback(() => {
|
|
125
|
+
openRef.current?.();
|
|
126
|
+
}, []);
|
|
127
|
+
const attachments = useMemo(() => ({
|
|
128
|
+
add,
|
|
129
|
+
clear,
|
|
130
|
+
fileInputRef,
|
|
131
|
+
files: attachmentFiles,
|
|
132
|
+
openFileDialog,
|
|
133
|
+
remove
|
|
134
|
+
}), [
|
|
135
|
+
attachmentFiles,
|
|
136
|
+
add,
|
|
137
|
+
remove,
|
|
138
|
+
clear,
|
|
139
|
+
openFileDialog
|
|
140
|
+
]);
|
|
141
|
+
const __registerFileInput = useCallback((ref, open) => {
|
|
142
|
+
fileInputRef.current = ref.current;
|
|
143
|
+
openRef.current = open;
|
|
144
|
+
}, []);
|
|
145
|
+
const controller = useMemo(() => ({
|
|
146
|
+
__registerFileInput,
|
|
147
|
+
attachments,
|
|
148
|
+
textInput: {
|
|
149
|
+
clear: clearInput,
|
|
150
|
+
setInput: setTextInput,
|
|
151
|
+
value: textInput
|
|
152
|
+
}
|
|
153
|
+
}), [
|
|
154
|
+
textInput,
|
|
155
|
+
clearInput,
|
|
156
|
+
attachments,
|
|
157
|
+
__registerFileInput
|
|
158
|
+
]);
|
|
159
|
+
return /* @__PURE__ */ jsx(PromptInputController.Provider, {
|
|
160
|
+
value: controller,
|
|
161
|
+
children: /* @__PURE__ */ jsx(ProviderAttachmentsContext.Provider, {
|
|
162
|
+
value: attachments,
|
|
163
|
+
children
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
const LocalAttachmentsContext = createContext(null);
|
|
168
|
+
const usePromptInputAttachments = () => {
|
|
169
|
+
const provider = useOptionalProviderAttachments();
|
|
170
|
+
const context = useContext(LocalAttachmentsContext) ?? provider;
|
|
171
|
+
if (!context) throw new Error("usePromptInputAttachments must be used within a PromptInput or PromptInputProvider");
|
|
172
|
+
return context;
|
|
173
|
+
};
|
|
174
|
+
const LocalReferencedSourcesContext = createContext(null);
|
|
175
|
+
const usePromptInputReferencedSources = () => {
|
|
176
|
+
const ctx = useContext(LocalReferencedSourcesContext);
|
|
177
|
+
if (!ctx) throw new Error("usePromptInputReferencedSources must be used within a LocalReferencedSourcesContext.Provider");
|
|
178
|
+
return ctx;
|
|
179
|
+
};
|
|
180
|
+
const PromptInputActionAddAttachments = ({ label = "Add photos or files", ...props }) => {
|
|
181
|
+
const attachments = usePromptInputAttachments();
|
|
182
|
+
const handleSelect = useCallback((e) => {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
attachments.openFileDialog();
|
|
185
|
+
}, [attachments]);
|
|
186
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
187
|
+
...props,
|
|
188
|
+
onSelect: handleSelect,
|
|
189
|
+
children: [
|
|
190
|
+
/* @__PURE__ */ jsx(ImageIcon, { className: "mr-2 size-4" }),
|
|
191
|
+
" ",
|
|
192
|
+
label
|
|
193
|
+
]
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const PromptInputActionAddScreenshot = ({ label = "Take screenshot", onSelect, ...props }) => {
|
|
197
|
+
const attachments = usePromptInputAttachments();
|
|
198
|
+
const handleSelect = useCallback(async (event) => {
|
|
199
|
+
onSelect?.(event);
|
|
200
|
+
if (event.defaultPrevented) return;
|
|
201
|
+
try {
|
|
202
|
+
const screenshot = await captureScreenshot();
|
|
203
|
+
if (screenshot) attachments.add([screenshot]);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error instanceof DOMException && (error.name === "NotAllowedError" || error.name === "AbortError")) return;
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}, [onSelect, attachments]);
|
|
209
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
210
|
+
...props,
|
|
211
|
+
onSelect: handleSelect,
|
|
212
|
+
children: [/* @__PURE__ */ jsx(Monitor, { className: "mr-2 size-4" }), label]
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
const PromptInput = ({ className, groupClassName, accept, multiple, globalDrop, syncHiddenInput, maxFiles, maxFileSize, onError, onSubmit, children, ...props }) => {
|
|
216
|
+
const controller = useOptionalPromptInputController();
|
|
217
|
+
const usingProvider = !!controller;
|
|
218
|
+
const inputRef = useRef(null);
|
|
219
|
+
const formRef = useRef(null);
|
|
220
|
+
const [items, setItems] = useState([]);
|
|
221
|
+
const files = usingProvider ? controller.attachments.files : items;
|
|
222
|
+
const [referencedSources, setReferencedSources] = useState([]);
|
|
223
|
+
const filesRef = useRef(files);
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
filesRef.current = files;
|
|
226
|
+
}, [files]);
|
|
227
|
+
const openFileDialogLocal = useCallback(() => {
|
|
228
|
+
inputRef.current?.click();
|
|
229
|
+
}, []);
|
|
230
|
+
const matchesAccept = useCallback((f) => {
|
|
231
|
+
if (!accept || accept.trim() === "") return true;
|
|
232
|
+
return accept.split(",").map((s) => s.trim()).filter(Boolean).some((pattern) => {
|
|
233
|
+
if (pattern.endsWith("/*")) {
|
|
234
|
+
const prefix = pattern.slice(0, -1);
|
|
235
|
+
return f.type.startsWith(prefix);
|
|
236
|
+
}
|
|
237
|
+
return f.type === pattern;
|
|
238
|
+
});
|
|
239
|
+
}, [accept]);
|
|
240
|
+
const addLocal = useCallback((fileList) => {
|
|
241
|
+
const incoming = [...fileList];
|
|
242
|
+
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
243
|
+
if (incoming.length && accepted.length === 0) {
|
|
244
|
+
onError?.({
|
|
245
|
+
code: "accept",
|
|
246
|
+
message: "No files match the accepted types."
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const withinSize = (f) => maxFileSize ? f.size <= maxFileSize : true;
|
|
251
|
+
const sized = accepted.filter(withinSize);
|
|
252
|
+
if (accepted.length > 0 && sized.length === 0) {
|
|
253
|
+
onError?.({
|
|
254
|
+
code: "max_file_size",
|
|
255
|
+
message: "All files exceed the maximum size."
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
setItems((prev) => {
|
|
260
|
+
const capacity = typeof maxFiles === "number" ? Math.max(0, maxFiles - prev.length) : void 0;
|
|
261
|
+
const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;
|
|
262
|
+
if (typeof capacity === "number" && sized.length > capacity) onError?.({
|
|
263
|
+
code: "max_files",
|
|
264
|
+
message: "Too many files. Some were not added."
|
|
265
|
+
});
|
|
266
|
+
const next = [];
|
|
267
|
+
for (const file of capped) next.push({
|
|
268
|
+
filename: file.name,
|
|
269
|
+
id: nanoid(),
|
|
270
|
+
mediaType: file.type,
|
|
271
|
+
type: "file",
|
|
272
|
+
url: URL.createObjectURL(file)
|
|
273
|
+
});
|
|
274
|
+
return [...prev, ...next];
|
|
275
|
+
});
|
|
276
|
+
}, [
|
|
277
|
+
matchesAccept,
|
|
278
|
+
maxFiles,
|
|
279
|
+
maxFileSize,
|
|
280
|
+
onError
|
|
281
|
+
]);
|
|
282
|
+
const removeLocal = useCallback((id) => setItems((prev) => {
|
|
283
|
+
const found = prev.find((file) => file.id === id);
|
|
284
|
+
if (found?.url) URL.revokeObjectURL(found.url);
|
|
285
|
+
return prev.filter((file) => file.id !== id);
|
|
286
|
+
}), []);
|
|
287
|
+
const addWithProviderValidation = useCallback((fileList) => {
|
|
288
|
+
const incoming = [...fileList];
|
|
289
|
+
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
290
|
+
if (incoming.length && accepted.length === 0) {
|
|
291
|
+
onError?.({
|
|
292
|
+
code: "accept",
|
|
293
|
+
message: "No files match the accepted types."
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const withinSize = (f) => maxFileSize ? f.size <= maxFileSize : true;
|
|
298
|
+
const sized = accepted.filter(withinSize);
|
|
299
|
+
if (accepted.length > 0 && sized.length === 0) {
|
|
300
|
+
onError?.({
|
|
301
|
+
code: "max_file_size",
|
|
302
|
+
message: "All files exceed the maximum size."
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const currentCount = files.length;
|
|
307
|
+
const capacity = typeof maxFiles === "number" ? Math.max(0, maxFiles - currentCount) : void 0;
|
|
308
|
+
const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;
|
|
309
|
+
if (typeof capacity === "number" && sized.length > capacity) onError?.({
|
|
310
|
+
code: "max_files",
|
|
311
|
+
message: "Too many files. Some were not added."
|
|
312
|
+
});
|
|
313
|
+
if (capped.length > 0) controller?.attachments.add(capped);
|
|
314
|
+
}, [
|
|
315
|
+
matchesAccept,
|
|
316
|
+
maxFileSize,
|
|
317
|
+
maxFiles,
|
|
318
|
+
onError,
|
|
319
|
+
files.length,
|
|
320
|
+
controller
|
|
321
|
+
]);
|
|
322
|
+
const clearAttachments = useCallback(() => usingProvider ? controller?.attachments.clear() : setItems((prev) => {
|
|
323
|
+
for (const file of prev) if (file.url) URL.revokeObjectURL(file.url);
|
|
324
|
+
return [];
|
|
325
|
+
}), [usingProvider, controller]);
|
|
326
|
+
const clearReferencedSources = useCallback(() => setReferencedSources([]), []);
|
|
327
|
+
const add = usingProvider ? addWithProviderValidation : addLocal;
|
|
328
|
+
const remove = usingProvider ? controller.attachments.remove : removeLocal;
|
|
329
|
+
const openFileDialog = usingProvider ? controller.attachments.openFileDialog : openFileDialogLocal;
|
|
330
|
+
const clear = useCallback(() => {
|
|
331
|
+
clearAttachments();
|
|
332
|
+
clearReferencedSources();
|
|
333
|
+
}, [clearAttachments, clearReferencedSources]);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (!usingProvider) return;
|
|
336
|
+
controller.__registerFileInput(inputRef, () => inputRef.current?.click());
|
|
337
|
+
}, [usingProvider, controller]);
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
if (syncHiddenInput && inputRef.current && files.length === 0) inputRef.current.value = "";
|
|
340
|
+
}, [files, syncHiddenInput]);
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
const form = formRef.current;
|
|
343
|
+
if (!form) return;
|
|
344
|
+
if (globalDrop) return;
|
|
345
|
+
const onDragOver = (e) => {
|
|
346
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
347
|
+
};
|
|
348
|
+
const onDrop = (e) => {
|
|
349
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
350
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) add(e.dataTransfer.files);
|
|
351
|
+
};
|
|
352
|
+
form.addEventListener("dragover", onDragOver);
|
|
353
|
+
form.addEventListener("drop", onDrop);
|
|
354
|
+
return () => {
|
|
355
|
+
form.removeEventListener("dragover", onDragOver);
|
|
356
|
+
form.removeEventListener("drop", onDrop);
|
|
357
|
+
};
|
|
358
|
+
}, [add, globalDrop]);
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
if (!globalDrop) return;
|
|
361
|
+
const onDragOver = (e) => {
|
|
362
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
363
|
+
};
|
|
364
|
+
const onDrop = (e) => {
|
|
365
|
+
if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
|
|
366
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) add(e.dataTransfer.files);
|
|
367
|
+
};
|
|
368
|
+
document.addEventListener("dragover", onDragOver);
|
|
369
|
+
document.addEventListener("drop", onDrop);
|
|
370
|
+
return () => {
|
|
371
|
+
document.removeEventListener("dragover", onDragOver);
|
|
372
|
+
document.removeEventListener("drop", onDrop);
|
|
373
|
+
};
|
|
374
|
+
}, [add, globalDrop]);
|
|
375
|
+
useEffect(() => () => {
|
|
376
|
+
if (!usingProvider) {
|
|
377
|
+
for (const f of filesRef.current) if (f.url) URL.revokeObjectURL(f.url);
|
|
378
|
+
}
|
|
379
|
+
}, [usingProvider]);
|
|
380
|
+
const handleChange = useCallback((event) => {
|
|
381
|
+
if (event.currentTarget.files) add(event.currentTarget.files);
|
|
382
|
+
event.currentTarget.value = "";
|
|
383
|
+
}, [add]);
|
|
384
|
+
const attachmentsCtx = useMemo(() => ({
|
|
385
|
+
add,
|
|
386
|
+
clear: clearAttachments,
|
|
387
|
+
fileInputRef: inputRef,
|
|
388
|
+
files: files.map((item) => ({
|
|
389
|
+
...item,
|
|
390
|
+
id: item.id
|
|
391
|
+
})),
|
|
392
|
+
openFileDialog,
|
|
393
|
+
remove
|
|
394
|
+
}), [
|
|
395
|
+
files,
|
|
396
|
+
add,
|
|
397
|
+
remove,
|
|
398
|
+
clearAttachments,
|
|
399
|
+
openFileDialog
|
|
400
|
+
]);
|
|
401
|
+
const refsCtx = useMemo(() => ({
|
|
402
|
+
add: (incoming) => {
|
|
403
|
+
const array = Array.isArray(incoming) ? incoming : [incoming];
|
|
404
|
+
setReferencedSources((prev) => [...prev, ...array.map((s) => ({
|
|
405
|
+
...s,
|
|
406
|
+
id: nanoid()
|
|
407
|
+
}))]);
|
|
408
|
+
},
|
|
409
|
+
clear: clearReferencedSources,
|
|
410
|
+
remove: (id) => {
|
|
411
|
+
setReferencedSources((prev) => prev.filter((s) => s.id !== id));
|
|
412
|
+
},
|
|
413
|
+
sources: referencedSources
|
|
414
|
+
}), [referencedSources, clearReferencedSources]);
|
|
415
|
+
const handleSubmit = useCallback(async (event) => {
|
|
416
|
+
event.preventDefault();
|
|
417
|
+
const form = event.currentTarget;
|
|
418
|
+
const text = usingProvider ? controller.textInput.value : new FormData(form).get("message") || "";
|
|
419
|
+
if (!usingProvider) form.reset();
|
|
420
|
+
try {
|
|
421
|
+
const result = onSubmit({
|
|
422
|
+
files: await Promise.all(files.map(async ({ id: _id, ...item }) => {
|
|
423
|
+
if (item.url?.startsWith("blob:")) {
|
|
424
|
+
const dataUrl = await convertBlobUrlToDataUrl(item.url);
|
|
425
|
+
return {
|
|
426
|
+
...item,
|
|
427
|
+
url: dataUrl ?? item.url
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return item;
|
|
431
|
+
})),
|
|
432
|
+
text
|
|
433
|
+
}, event);
|
|
434
|
+
if (result instanceof Promise) try {
|
|
435
|
+
await result;
|
|
436
|
+
clear();
|
|
437
|
+
if (usingProvider) controller.textInput.clear();
|
|
438
|
+
} catch {}
|
|
439
|
+
else {
|
|
440
|
+
clear();
|
|
441
|
+
if (usingProvider) controller.textInput.clear();
|
|
442
|
+
}
|
|
443
|
+
} catch {}
|
|
444
|
+
}, [
|
|
445
|
+
usingProvider,
|
|
446
|
+
controller,
|
|
447
|
+
files,
|
|
448
|
+
onSubmit,
|
|
449
|
+
clear
|
|
450
|
+
]);
|
|
451
|
+
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
|
|
452
|
+
accept,
|
|
453
|
+
"aria-label": "Upload files",
|
|
454
|
+
className: "hidden",
|
|
455
|
+
multiple,
|
|
456
|
+
onChange: handleChange,
|
|
457
|
+
ref: inputRef,
|
|
458
|
+
title: "Upload files",
|
|
459
|
+
type: "file"
|
|
460
|
+
}), /* @__PURE__ */ jsx("form", {
|
|
461
|
+
className: cn("w-full", className),
|
|
462
|
+
onSubmit: handleSubmit,
|
|
463
|
+
ref: formRef,
|
|
464
|
+
...props,
|
|
465
|
+
children: /* @__PURE__ */ jsx(InputGroup, {
|
|
466
|
+
className: cn("overflow-hidden bg-white dark:bg-white", groupClassName),
|
|
467
|
+
children
|
|
468
|
+
})
|
|
469
|
+
})] });
|
|
470
|
+
const withReferencedSources = /* @__PURE__ */ jsx(LocalReferencedSourcesContext.Provider, {
|
|
471
|
+
value: refsCtx,
|
|
472
|
+
children: inner
|
|
473
|
+
});
|
|
474
|
+
return /* @__PURE__ */ jsx(LocalAttachmentsContext.Provider, {
|
|
475
|
+
value: attachmentsCtx,
|
|
476
|
+
children: withReferencedSources
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
const PromptInputBody = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
480
|
+
className: cn("contents", className),
|
|
481
|
+
...props
|
|
482
|
+
});
|
|
483
|
+
const PromptInputTextarea = ({ onChange, onKeyDown, className, placeholder = "What would you like to know?", ...props }) => {
|
|
484
|
+
const controller = useOptionalPromptInputController();
|
|
485
|
+
const attachments = usePromptInputAttachments();
|
|
486
|
+
const [isComposing, setIsComposing] = useState(false);
|
|
487
|
+
const handleKeyDown = useCallback((e) => {
|
|
488
|
+
onKeyDown?.(e);
|
|
489
|
+
if (e.defaultPrevented) return;
|
|
490
|
+
if (e.key === "Enter") {
|
|
491
|
+
if (isComposing || e.nativeEvent.isComposing) return;
|
|
492
|
+
if (e.shiftKey) return;
|
|
493
|
+
e.preventDefault();
|
|
494
|
+
const { form } = e.currentTarget;
|
|
495
|
+
if ((form?.querySelector("button[type=\"submit\"]"))?.disabled) return;
|
|
496
|
+
form?.requestSubmit();
|
|
497
|
+
}
|
|
498
|
+
if (e.key === "Backspace" && e.currentTarget.value === "" && attachments.files.length > 0) {
|
|
499
|
+
e.preventDefault();
|
|
500
|
+
const lastAttachment = attachments.files.at(-1);
|
|
501
|
+
if (lastAttachment) attachments.remove(lastAttachment.id);
|
|
502
|
+
}
|
|
503
|
+
}, [
|
|
504
|
+
onKeyDown,
|
|
505
|
+
isComposing,
|
|
506
|
+
attachments
|
|
507
|
+
]);
|
|
508
|
+
const handlePaste = useCallback((event) => {
|
|
509
|
+
const items = event.clipboardData?.items;
|
|
510
|
+
if (!items) return;
|
|
511
|
+
const files = [];
|
|
512
|
+
for (const item of items) if (item.kind === "file") {
|
|
513
|
+
const file = item.getAsFile();
|
|
514
|
+
if (file) files.push(file);
|
|
515
|
+
}
|
|
516
|
+
if (files.length > 0) {
|
|
517
|
+
event.preventDefault();
|
|
518
|
+
attachments.add(files);
|
|
519
|
+
}
|
|
520
|
+
}, [attachments]);
|
|
521
|
+
const handleCompositionEnd = useCallback(() => setIsComposing(false), []);
|
|
522
|
+
const handleCompositionStart = useCallback(() => setIsComposing(true), []);
|
|
523
|
+
const controlledProps = controller ? {
|
|
524
|
+
onChange: (e) => {
|
|
525
|
+
controller.textInput.setInput(e.currentTarget.value);
|
|
526
|
+
onChange?.(e);
|
|
527
|
+
},
|
|
528
|
+
value: controller.textInput.value
|
|
529
|
+
} : { onChange };
|
|
530
|
+
return /* @__PURE__ */ jsx(InputGroupTextarea, {
|
|
531
|
+
className: cn("field-sizing-content max-h-48 min-h-16", className),
|
|
532
|
+
name: "message",
|
|
533
|
+
onCompositionEnd: handleCompositionEnd,
|
|
534
|
+
onCompositionStart: handleCompositionStart,
|
|
535
|
+
onKeyDown: handleKeyDown,
|
|
536
|
+
onPaste: handlePaste,
|
|
537
|
+
placeholder,
|
|
538
|
+
...props,
|
|
539
|
+
...controlledProps
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
const PromptInputHeader = ({ className, ...props }) => /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
543
|
+
align: "block-end",
|
|
544
|
+
className: cn("order-first flex-wrap gap-1", className),
|
|
545
|
+
...props
|
|
546
|
+
});
|
|
547
|
+
const PromptInputFooter = ({ className, ...props }) => /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
548
|
+
align: "block-end",
|
|
549
|
+
className: cn("justify-between gap-1", className),
|
|
550
|
+
...props
|
|
551
|
+
});
|
|
552
|
+
const PromptInputTools = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
553
|
+
className: cn("flex min-w-0 items-center gap-1", className),
|
|
554
|
+
...props
|
|
555
|
+
});
|
|
556
|
+
const PromptInputButton = ({ variant = "ghost", className, size, tooltip, ...props }) => {
|
|
557
|
+
const newSize = size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");
|
|
558
|
+
const button = /* @__PURE__ */ jsx(InputGroupButton, {
|
|
559
|
+
className: cn(className),
|
|
560
|
+
size: newSize,
|
|
561
|
+
type: "button",
|
|
562
|
+
variant,
|
|
563
|
+
...props
|
|
564
|
+
});
|
|
565
|
+
if (!tooltip) return button;
|
|
566
|
+
const tooltipContent = typeof tooltip === "string" ? tooltip : tooltip.content;
|
|
567
|
+
const shortcut = typeof tooltip === "string" ? void 0 : tooltip.shortcut;
|
|
568
|
+
const side = typeof tooltip === "string" ? "top" : tooltip.side ?? "top";
|
|
569
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
570
|
+
asChild: true,
|
|
571
|
+
children: button
|
|
572
|
+
}), /* @__PURE__ */ jsxs(TooltipContent, {
|
|
573
|
+
side,
|
|
574
|
+
children: [tooltipContent, shortcut && /* @__PURE__ */ jsx("span", {
|
|
575
|
+
className: "ml-2 text-muted-foreground",
|
|
576
|
+
children: shortcut
|
|
577
|
+
})]
|
|
578
|
+
})] });
|
|
579
|
+
};
|
|
580
|
+
const PromptInputActionMenu = (props) => /* @__PURE__ */ jsx(DropdownMenu, { ...props });
|
|
581
|
+
const PromptInputActionMenuTrigger = ({ className, children, ...props }) => /* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
582
|
+
asChild: true,
|
|
583
|
+
children: /* @__PURE__ */ jsx(PromptInputButton, {
|
|
584
|
+
className,
|
|
585
|
+
...props,
|
|
586
|
+
children: children ?? /* @__PURE__ */ jsx(PlusIcon, { className: "size-4" })
|
|
587
|
+
})
|
|
588
|
+
});
|
|
589
|
+
const PromptInputActionMenuContent = ({ className, ...props }) => /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
590
|
+
align: "start",
|
|
591
|
+
className: cn(className),
|
|
592
|
+
...props
|
|
593
|
+
});
|
|
594
|
+
const PromptInputActionMenuItem = ({ className, ...props }) => /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
595
|
+
className: cn(className),
|
|
596
|
+
...props
|
|
597
|
+
});
|
|
598
|
+
const PromptInputSubmit = ({ className, variant = "default", size = "icon-sm", status, onStop, onClick, children, ...props }) => {
|
|
599
|
+
const isGenerating = status === "submitted" || status === "streaming";
|
|
600
|
+
let Icon = /* @__PURE__ */ jsx(ArrowUpIcon, { className: "size-4" });
|
|
601
|
+
if (status === "submitted") Icon = /* @__PURE__ */ jsx(Spinner, {});
|
|
602
|
+
else if (status === "streaming") Icon = /* @__PURE__ */ jsx(SquareIcon, { className: "size-4" });
|
|
603
|
+
else if (status === "error") Icon = /* @__PURE__ */ jsx(XIcon, { className: "size-4" });
|
|
604
|
+
const handleClick = useCallback((e) => {
|
|
605
|
+
if (isGenerating && onStop) {
|
|
606
|
+
e.preventDefault();
|
|
607
|
+
onStop();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
onClick?.(e);
|
|
611
|
+
}, [
|
|
612
|
+
isGenerating,
|
|
613
|
+
onStop,
|
|
614
|
+
onClick
|
|
615
|
+
]);
|
|
616
|
+
return /* @__PURE__ */ jsx(InputGroupButton, {
|
|
617
|
+
"aria-label": isGenerating ? "Stop" : "Submit",
|
|
618
|
+
className: cn(className),
|
|
619
|
+
onClick: handleClick,
|
|
620
|
+
size,
|
|
621
|
+
type: isGenerating && onStop ? "button" : "submit",
|
|
622
|
+
variant,
|
|
623
|
+
...props,
|
|
624
|
+
children: children ?? Icon
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
const PromptInputSelect = (props) => /* @__PURE__ */ jsx(Select, { ...props });
|
|
628
|
+
const PromptInputSelectTrigger = ({ className, ...props }) => /* @__PURE__ */ jsx(SelectTrigger, {
|
|
629
|
+
className: cn("border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors", "hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground", className),
|
|
630
|
+
...props
|
|
631
|
+
});
|
|
632
|
+
const PromptInputSelectContent = ({ className, ...props }) => /* @__PURE__ */ jsx(SelectContent, {
|
|
633
|
+
className: cn(className),
|
|
634
|
+
...props
|
|
635
|
+
});
|
|
636
|
+
const PromptInputSelectItem = ({ className, ...props }) => /* @__PURE__ */ jsx(SelectItem, {
|
|
637
|
+
className: cn(className),
|
|
638
|
+
...props
|
|
639
|
+
});
|
|
640
|
+
const PromptInputSelectValue = ({ className, ...props }) => /* @__PURE__ */ jsx(SelectValue, {
|
|
641
|
+
className: cn(className),
|
|
642
|
+
...props
|
|
643
|
+
});
|
|
644
|
+
const PromptInputHoverCard = ({ openDelay = 0, closeDelay = 0, ...props }) => /* @__PURE__ */ jsx(HoverCard, {
|
|
645
|
+
closeDelay,
|
|
646
|
+
openDelay,
|
|
647
|
+
...props
|
|
648
|
+
});
|
|
649
|
+
const PromptInputHoverCardTrigger = (props) => /* @__PURE__ */ jsx(HoverCardTrigger, { ...props });
|
|
650
|
+
const PromptInputHoverCardContent = ({ align = "start", ...props }) => /* @__PURE__ */ jsx(HoverCardContent, {
|
|
651
|
+
align,
|
|
652
|
+
...props
|
|
653
|
+
});
|
|
654
|
+
const PromptInputTabsList = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
655
|
+
className: cn(className),
|
|
656
|
+
...props
|
|
657
|
+
});
|
|
658
|
+
const PromptInputTab = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
659
|
+
className: cn(className),
|
|
660
|
+
...props
|
|
661
|
+
});
|
|
662
|
+
const PromptInputTabLabel = ({ className, ...props }) => /* @__PURE__ */ jsx("h3", {
|
|
663
|
+
className: cn("mb-2 px-3 font-medium text-muted-foreground text-xs", className),
|
|
664
|
+
...props
|
|
665
|
+
});
|
|
666
|
+
const PromptInputTabBody = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
667
|
+
className: cn("space-y-1", className),
|
|
668
|
+
...props
|
|
669
|
+
});
|
|
670
|
+
const PromptInputTabItem = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
671
|
+
className: cn("flex items-center gap-2 px-3 py-2 text-xs hover:bg-accent", className),
|
|
672
|
+
...props
|
|
673
|
+
});
|
|
674
|
+
const PromptInputCommand = ({ className, ...props }) => /* @__PURE__ */ jsx(Command, {
|
|
675
|
+
className: cn(className),
|
|
676
|
+
...props
|
|
677
|
+
});
|
|
678
|
+
const PromptInputCommandInput = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandInput, {
|
|
679
|
+
className: cn(className),
|
|
680
|
+
...props
|
|
681
|
+
});
|
|
682
|
+
const PromptInputCommandList = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandList, {
|
|
683
|
+
className: cn(className),
|
|
684
|
+
...props
|
|
685
|
+
});
|
|
686
|
+
const PromptInputCommandEmpty = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandEmpty, {
|
|
687
|
+
className: cn(className),
|
|
688
|
+
...props
|
|
689
|
+
});
|
|
690
|
+
const PromptInputCommandGroup = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandGroup, {
|
|
691
|
+
className: cn(className),
|
|
692
|
+
...props
|
|
693
|
+
});
|
|
694
|
+
const PromptInputCommandItem = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandItem, {
|
|
695
|
+
className: cn(className),
|
|
696
|
+
...props
|
|
697
|
+
});
|
|
698
|
+
const PromptInputCommandSeparator = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandSeparator, {
|
|
699
|
+
className: cn(className),
|
|
700
|
+
...props
|
|
701
|
+
});
|
|
702
|
+
//#endregion
|
|
703
|
+
export { LocalReferencedSourcesContext, PromptInput, PromptInputActionAddAttachments, PromptInputActionAddScreenshot, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuItem, PromptInputActionMenuTrigger, PromptInputBody, PromptInputButton, PromptInputCommand, PromptInputCommandEmpty, PromptInputCommandGroup, PromptInputCommandInput, PromptInputCommandItem, PromptInputCommandList, PromptInputCommandSeparator, PromptInputFooter, PromptInputHeader, PromptInputHoverCard, PromptInputHoverCardContent, PromptInputHoverCardTrigger, PromptInputProvider, PromptInputSelect, PromptInputSelectContent, PromptInputSelectItem, PromptInputSelectTrigger, PromptInputSelectValue, PromptInputSubmit, PromptInputTab, PromptInputTabBody, PromptInputTabItem, PromptInputTabLabel, PromptInputTabsList, PromptInputTextarea, PromptInputTools, usePromptInputAttachments, usePromptInputController, usePromptInputReferencedSources, useProviderAttachments };
|