@chat-js/cli 0.6.1 → 0.6.3
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/index.js +17065 -16667
- package/package.json +1 -1
- package/templates/chat-app/app/(auth)/login/page.tsx +3 -3
- package/templates/chat-app/app/(chat)/api/chat/route.ts +4 -60
- package/templates/chat-app/app/not-found.tsx +2 -2
- package/templates/chat-app/chat.config.ts +3 -0
- package/templates/chat-app/components/ai-elements/actions.tsx +44 -44
- package/templates/chat-app/components/ai-elements/artifact.tsx +92 -92
- package/templates/chat-app/components/ai-elements/code-block.tsx +143 -143
- package/templates/chat-app/components/ai-elements/context.tsx +313 -313
- package/templates/chat-app/components/ai-elements/conversation.tsx +65 -65
- package/templates/chat-app/components/ai-elements/extra/conversation-content-scroll-area.tsx +29 -29
- package/templates/chat-app/components/ai-elements/extra/mcp-tool-header.tsx +27 -27
- package/templates/chat-app/components/ai-elements/message.tsx +341 -344
- package/templates/chat-app/components/ai-elements/parseIncompleteMarkdown.tsx +122 -122
- package/templates/chat-app/components/ai-elements/prompt-input.tsx +1059 -1059
- package/templates/chat-app/components/ai-elements/reasoning.tsx +131 -131
- package/templates/chat-app/components/ai-elements/response.tsx +15 -12
- package/templates/chat-app/components/ai-elements/sandbox.tsx +84 -84
- package/templates/chat-app/components/ai-elements/shimmer.tsx +47 -47
- package/templates/chat-app/components/ai-elements/suggestion.tsx +33 -33
- package/templates/chat-app/components/ai-elements/tool.tsx +118 -118
- package/templates/chat-app/components/app-sidebar-history-conditional.tsx +3 -3
- package/templates/chat-app/components/app-sidebar.tsx +3 -3
- package/templates/chat-app/components/connectors-dropdown.tsx +6 -3
- package/templates/chat-app/components/deep-research-progress.tsx +1 -1
- package/templates/chat-app/components/header-breadcrumb.tsx +14 -11
- package/templates/chat-app/components/internal-link.tsx +73 -0
- package/templates/chat-app/components/login-form.tsx +5 -5
- package/templates/chat-app/components/message-parts.tsx +1 -71
- package/templates/chat-app/components/model-selector.tsx +3 -3
- package/templates/chat-app/components/new-chat-button.tsx +4 -4
- package/templates/chat-app/components/part/document-common.tsx +3 -3
- package/templates/chat-app/components/part/document-tool.tsx +3 -3
- package/templates/chat-app/components/part/message-annotations.tsx +2 -2
- package/templates/chat-app/components/part/tool-part.tsx +92 -0
- package/templates/chat-app/components/project-chat-item.tsx +2 -2
- package/templates/chat-app/components/research-progress.tsx +2 -2
- package/templates/chat-app/components/research-task.tsx +1 -1
- package/templates/chat-app/components/research-tasks.tsx +1 -1
- package/templates/chat-app/components/settings/connectors-settings.tsx +4 -4
- package/templates/chat-app/components/settings/mcp-details-page.tsx +5 -5
- package/templates/chat-app/components/settings/settings-nav.tsx +3 -3
- package/templates/chat-app/components/sidebar-chat-item.tsx +4 -12
- package/templates/chat-app/components/sidebar-project-item.tsx +4 -11
- package/templates/chat-app/components/sidebar-top-row.tsx +7 -7
- package/templates/chat-app/components/sidebar-user-nav.tsx +3 -3
- package/templates/chat-app/components/signup-form.tsx +8 -5
- package/templates/chat-app/components/source-badge.tsx +3 -9
- package/templates/chat-app/components/sources.tsx +1 -1
- package/templates/chat-app/components/ui/accordion.tsx +32 -32
- package/templates/chat-app/components/ui/alert-dialog.tsx +103 -103
- package/templates/chat-app/components/ui/alert.tsx +36 -36
- package/templates/chat-app/components/ui/avatar.tsx +28 -28
- package/templates/chat-app/components/ui/badge.tsx +22 -22
- package/templates/chat-app/components/ui/breadcrumb.tsx +72 -72
- package/templates/chat-app/components/ui/button-group.tsx +58 -58
- package/templates/chat-app/components/ui/button.tsx +45 -45
- package/templates/chat-app/components/ui/card.tsx +65 -65
- package/templates/chat-app/components/ui/checkbox.tsx +16 -16
- package/templates/chat-app/components/ui/collapsible.tsx +1 -1
- package/templates/chat-app/components/ui/command.tsx +137 -137
- package/templates/chat-app/components/ui/dialog.tsx +94 -94
- package/templates/chat-app/components/ui/drawer.tsx +68 -68
- package/templates/chat-app/components/ui/dropdown-menu.tsx +184 -184
- package/templates/chat-app/components/ui/empty.tsx +76 -76
- package/templates/chat-app/components/ui/extra/action-container.tsx +3 -3
- package/templates/chat-app/components/ui/extra/scroll-area-viewport-ref.tsx +24 -24
- package/templates/chat-app/components/ui/form.tsx +112 -112
- package/templates/chat-app/components/ui/hover-card.tsx +25 -25
- package/templates/chat-app/components/ui/input-group.tsx +126 -126
- package/templates/chat-app/components/ui/input.tsx +13 -13
- package/templates/chat-app/components/ui/label.tsx +12 -12
- package/templates/chat-app/components/ui/popover.tsx +25 -25
- package/templates/chat-app/components/ui/progress.tsx +19 -19
- package/templates/chat-app/components/ui/resizable.tsx +27 -27
- package/templates/chat-app/components/ui/scroll-area.tsx +30 -30
- package/templates/chat-app/components/ui/select.tsx +108 -108
- package/templates/chat-app/components/ui/separator.tsx +16 -16
- package/templates/chat-app/components/ui/sheet.tsx +91 -91
- package/templates/chat-app/components/ui/sidebar.tsx +615 -615
- package/templates/chat-app/components/ui/skeleton.tsx +7 -7
- package/templates/chat-app/components/ui/slider.tsx +50 -50
- package/templates/chat-app/components/ui/spinner.tsx +8 -8
- package/templates/chat-app/components/ui/switch.tsx +16 -16
- package/templates/chat-app/components/ui/table.tsx +71 -71
- package/templates/chat-app/components/ui/tabs.tsx +31 -31
- package/templates/chat-app/components/ui/textarea.tsx +10 -10
- package/templates/chat-app/components/ui/toggle.tsx +31 -31
- package/templates/chat-app/components/ui/tooltip.tsx +48 -48
- package/templates/chat-app/components/upgrade-cta/limit-display.tsx +7 -7
- package/templates/chat-app/components/upgrade-cta/login-cta-banner.tsx +3 -3
- package/templates/chat-app/components/upgrade-cta/login-prompt.tsx +3 -3
- package/templates/chat-app/hooks/use-mobile.ts +13 -13
- package/templates/chat-app/lib/ai/core-chat-agent.ts +25 -14
- package/templates/chat-app/lib/ai/eval-agent.ts +4 -5
- package/templates/chat-app/lib/ai/gateway-model-defaults.ts +24 -0
- package/templates/chat-app/lib/ai/installed-tools.ts +12 -0
- package/templates/chat-app/lib/ai/mcp/mcp-client.ts +2 -2
- package/templates/chat-app/lib/ai/models.generated.ts +4236 -4585
- package/templates/chat-app/lib/ai/tool-renderer-registry.ts +31 -0
- package/templates/chat-app/lib/ai/types.ts +15 -20
- package/templates/chat-app/lib/config-requirements.ts +11 -6
- package/templates/chat-app/lib/config-schema.ts +24 -0
- package/templates/chat-app/lib/stores/hooks-message-parts.ts +1 -1
- package/templates/chat-app/lib/utils.ts +157 -157
- package/templates/chat-app/package.json +1 -1
- package/templates/chat-app/scripts/check-env.ts +229 -2
- package/templates/chat-app/tools/chatjs/_shared/lib/tool-part.ts +5 -0
- package/templates/chat-app/{components/part/weather.tsx → tools/chatjs/get-weather/renderer.tsx} +24 -38
- package/templates/chat-app/{components/part/retrieve-url.tsx → tools/chatjs/retrieve-url/renderer.tsx} +20 -15
- package/templates/chat-app/{lib/ai/tools/retrieve-url.ts → tools/chatjs/retrieve-url/tool.ts} +46 -7
- package/templates/chat-app/tools/chatjs/tools.ts +16 -0
- package/templates/chat-app/tools/chatjs/ui.ts +17 -0
- package/templates/chat-app/tools/chatjs/word-count/renderer.tsx +50 -0
- package/templates/chat-app/tools/chatjs/word-count/tool.ts +30 -0
- package/templates/chat-app/{lib/ai/tools → tools/platform}/code-execution.ts +3 -5
- package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/deep-research.ts +2 -3
- package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/pipeline.ts +1 -1
- package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/types.ts +1 -1
- package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/utils.ts +7 -7
- package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/types.ts +1 -1
- package/templates/chat-app/{lib/ai/tools → tools/platform}/generate-video.ts +4 -6
- package/templates/chat-app/{lib/ai/tools → tools/platform}/read-document.ts +2 -2
- package/templates/chat-app/{lib/ai/tools → tools/platform}/steps/multi-query-web-search.ts +1 -1
- package/templates/chat-app/{lib/ai/tools → tools/platform}/steps/web-search.ts +1 -1
- package/templates/chat-app/{lib/ai/tools → tools/platform}/tools.ts +54 -30
- package/templates/chat-app/{lib/ai/tools → tools/platform}/web-search.ts +7 -5
- package/templates/electron/CHANGELOG.md +16 -2
- package/templates/electron/package.json +1 -1
- package/templates/chat-app/lib/ai/tools/tools-definitions.ts +0 -83
- /package/templates/chat-app/{lib/ai/tools/get-weather.ts → tools/chatjs/get-weather/tool.ts} +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/code-execution.javascript.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/code-execution.python.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/code-execution.shared.test.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/code-execution.shared.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/code-execution.types.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/configuration.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/prompts.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/researcher-agent.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/deep-research/supervisor-agent.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/code-guidelines.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/create-code-document.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/create-sheet-document.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/create-text-document.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/edit-code-document.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/edit-sheet-document.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/edit-text-document.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/sheet-guidelines.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/documents/text-guidelines.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/generate-image.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/research-updates-schema.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/steps/search-utils.ts +0 -0
- /package/templates/chat-app/{lib/ai/tools → tools/platform}/types.ts +0 -0
|
@@ -2,71 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ChatStatus, FileUIPart } from "ai";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
CornerDownLeftIcon,
|
|
6
|
+
ImageIcon,
|
|
7
|
+
Loader2Icon,
|
|
8
|
+
MicIcon,
|
|
9
|
+
PaperclipIcon,
|
|
10
|
+
PlusIcon,
|
|
11
|
+
SquareIcon,
|
|
12
|
+
XIcon,
|
|
13
13
|
} from "lucide-react";
|
|
14
14
|
import { nanoid } from "nanoid";
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
type ChangeEvent,
|
|
17
|
+
type ChangeEventHandler,
|
|
18
|
+
Children,
|
|
19
|
+
type ClipboardEventHandler,
|
|
20
|
+
type ComponentProps,
|
|
21
|
+
createContext,
|
|
22
|
+
type FormEvent,
|
|
23
|
+
type FormEventHandler,
|
|
24
|
+
Fragment,
|
|
25
|
+
type HTMLAttributes,
|
|
26
|
+
type KeyboardEventHandler,
|
|
27
|
+
type PropsWithChildren,
|
|
28
|
+
type ReactNode,
|
|
29
|
+
type RefObject,
|
|
30
|
+
useCallback,
|
|
31
|
+
useContext,
|
|
32
|
+
useEffect,
|
|
33
|
+
useMemo,
|
|
34
|
+
useRef,
|
|
35
|
+
useState,
|
|
36
36
|
} from "react";
|
|
37
37
|
import { Button } from "@/components/ui/button";
|
|
38
38
|
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
Command,
|
|
40
|
+
CommandEmpty,
|
|
41
|
+
CommandGroup,
|
|
42
|
+
CommandInput,
|
|
43
|
+
CommandItem,
|
|
44
|
+
CommandList,
|
|
45
|
+
CommandSeparator,
|
|
46
46
|
} from "@/components/ui/command";
|
|
47
47
|
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
DropdownMenu,
|
|
49
|
+
DropdownMenuContent,
|
|
50
|
+
DropdownMenuItem,
|
|
51
|
+
DropdownMenuTrigger,
|
|
52
52
|
} from "@/components/ui/dropdown-menu";
|
|
53
53
|
import {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
HoverCard,
|
|
55
|
+
HoverCardContent,
|
|
56
|
+
HoverCardTrigger,
|
|
57
57
|
} from "@/components/ui/hover-card";
|
|
58
58
|
import {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
InputGroup,
|
|
60
|
+
InputGroupAddon,
|
|
61
|
+
InputGroupButton,
|
|
62
|
+
InputGroupTextarea,
|
|
63
63
|
} from "@/components/ui/input-group";
|
|
64
64
|
import {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
Select,
|
|
66
|
+
SelectContent,
|
|
67
|
+
SelectItem,
|
|
68
|
+
SelectTrigger,
|
|
69
|
+
SelectValue,
|
|
70
70
|
} from "@/components/ui/select";
|
|
71
71
|
import { cn } from "@/lib/utils";
|
|
72
72
|
|
|
@@ -75,66 +75,66 @@ import { cn } from "@/lib/utils";
|
|
|
75
75
|
// ============================================================================
|
|
76
76
|
|
|
77
77
|
export type AttachmentsContext = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
files: (FileUIPart & { id: string })[];
|
|
79
|
+
add: (files: File[] | FileList) => void;
|
|
80
|
+
remove: (id: string) => void;
|
|
81
|
+
clear: () => void;
|
|
82
|
+
openFileDialog: () => void;
|
|
83
|
+
fileInputRef: RefObject<HTMLInputElement | null>;
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
export type TextInputContext = {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
value: string;
|
|
88
|
+
setInput: (v: string) => void;
|
|
89
|
+
clear: () => void;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
export type PromptInputControllerProps = {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
textInput: TextInputContext;
|
|
94
|
+
attachments: AttachmentsContext;
|
|
95
|
+
/** INTERNAL: Allows PromptInput to register its file textInput + "open" callback */
|
|
96
|
+
__registerFileInput: (
|
|
97
|
+
ref: RefObject<HTMLInputElement | null>,
|
|
98
|
+
open: () => void,
|
|
99
|
+
) => void;
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
const PromptInputController = createContext<PromptInputControllerProps | null>(
|
|
103
|
-
|
|
103
|
+
null,
|
|
104
104
|
);
|
|
105
105
|
const ProviderAttachmentsContext = createContext<AttachmentsContext | null>(
|
|
106
|
-
|
|
106
|
+
null,
|
|
107
107
|
);
|
|
108
108
|
|
|
109
109
|
export const usePromptInputController = () => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
const ctx = useContext(PromptInputController);
|
|
111
|
+
if (!ctx) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"Wrap your component inside <PromptInputProvider> to use usePromptInputController().",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return ctx;
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
// Optional variants (do NOT throw). Useful for dual-mode components.
|
|
120
120
|
const useOptionalPromptInputController = () =>
|
|
121
|
-
|
|
121
|
+
useContext(PromptInputController);
|
|
122
122
|
|
|
123
123
|
export const useProviderAttachments = () => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
const ctx = useContext(ProviderAttachmentsContext);
|
|
125
|
+
if (!ctx) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"Wrap your component inside <PromptInputProvider> to use useProviderAttachments().",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return ctx;
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
const useOptionalProviderAttachments = () =>
|
|
134
|
-
|
|
134
|
+
useContext(ProviderAttachmentsContext);
|
|
135
135
|
|
|
136
136
|
export type PromptInputProviderProps = PropsWithChildren<{
|
|
137
|
-
|
|
137
|
+
initialInput?: string;
|
|
138
138
|
}>;
|
|
139
139
|
|
|
140
140
|
/**
|
|
@@ -142,104 +142,104 @@ export type PromptInputProviderProps = PropsWithChildren<{
|
|
|
142
142
|
* If you don't use it, PromptInput stays fully self-managed.
|
|
143
143
|
*/
|
|
144
144
|
export function PromptInputProvider({
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
initialInput: initialTextInput = "",
|
|
146
|
+
children,
|
|
147
147
|
}: PromptInputProviderProps) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
148
|
+
// ----- textInput state
|
|
149
|
+
const [textInput, setTextInput] = useState(initialTextInput);
|
|
150
|
+
const clearInput = useCallback(() => setTextInput(""), []);
|
|
151
|
+
|
|
152
|
+
// ----- attachments state (global when wrapped)
|
|
153
|
+
const [attachements, setAttachements] = useState<
|
|
154
|
+
(FileUIPart & { id: string })[]
|
|
155
|
+
>([]);
|
|
156
|
+
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
157
|
+
const openRef = useRef<() => void>(() => {});
|
|
158
|
+
|
|
159
|
+
const add = useCallback((files: File[] | FileList) => {
|
|
160
|
+
const incoming = Array.from(files);
|
|
161
|
+
if (incoming.length === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setAttachements((prev) =>
|
|
166
|
+
prev.concat(
|
|
167
|
+
incoming.map((file) => ({
|
|
168
|
+
id: nanoid(),
|
|
169
|
+
type: "file" as const,
|
|
170
|
+
url: URL.createObjectURL(file),
|
|
171
|
+
mediaType: file.type,
|
|
172
|
+
filename: file.name,
|
|
173
|
+
})),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const remove = useCallback((id: string) => {
|
|
179
|
+
setAttachements((prev) => {
|
|
180
|
+
const found = prev.find((f) => f.id === id);
|
|
181
|
+
if (found?.url) {
|
|
182
|
+
URL.revokeObjectURL(found.url);
|
|
183
|
+
}
|
|
184
|
+
return prev.filter((f) => f.id !== id);
|
|
185
|
+
});
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
const clear = useCallback(() => {
|
|
189
|
+
setAttachements((prev) => {
|
|
190
|
+
for (const f of prev) {
|
|
191
|
+
if (f.url) {
|
|
192
|
+
URL.revokeObjectURL(f.url);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return [];
|
|
196
|
+
});
|
|
197
|
+
}, []);
|
|
198
|
+
|
|
199
|
+
const openFileDialog = useCallback(() => {
|
|
200
|
+
openRef.current?.();
|
|
201
|
+
}, []);
|
|
202
|
+
|
|
203
|
+
const attachments = useMemo<AttachmentsContext>(
|
|
204
|
+
() => ({
|
|
205
|
+
files: attachements,
|
|
206
|
+
add,
|
|
207
|
+
remove,
|
|
208
|
+
clear,
|
|
209
|
+
openFileDialog,
|
|
210
|
+
fileInputRef,
|
|
211
|
+
}),
|
|
212
|
+
[attachements, add, remove, clear, openFileDialog],
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const __registerFileInput = useCallback(
|
|
216
|
+
(ref: RefObject<HTMLInputElement | null>, open: () => void) => {
|
|
217
|
+
fileInputRef.current = ref.current;
|
|
218
|
+
openRef.current = open;
|
|
219
|
+
},
|
|
220
|
+
[],
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const controller = useMemo<PromptInputControllerProps>(
|
|
224
|
+
() => ({
|
|
225
|
+
textInput: {
|
|
226
|
+
value: textInput,
|
|
227
|
+
setInput: setTextInput,
|
|
228
|
+
clear: clearInput,
|
|
229
|
+
},
|
|
230
|
+
attachments,
|
|
231
|
+
__registerFileInput,
|
|
232
|
+
}),
|
|
233
|
+
[textInput, clearInput, attachments, __registerFileInput],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<PromptInputController.Provider value={controller}>
|
|
238
|
+
<ProviderAttachmentsContext.Provider value={attachments}>
|
|
239
|
+
{children}
|
|
240
|
+
</ProviderAttachmentsContext.Provider>
|
|
241
|
+
</PromptInputController.Provider>
|
|
242
|
+
);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
// ============================================================================
|
|
@@ -249,1134 +249,1134 @@ export function PromptInputProvider({
|
|
|
249
249
|
const LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);
|
|
250
250
|
|
|
251
251
|
export const usePromptInputAttachments = () => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
// Dual-mode: prefer provider if present, otherwise use local
|
|
253
|
+
const provider = useOptionalProviderAttachments();
|
|
254
|
+
const local = useContext(LocalAttachmentsContext);
|
|
255
|
+
const context = provider ?? local;
|
|
256
|
+
if (!context) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
"usePromptInputAttachments must be used within a PromptInput or PromptInputProvider",
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return context;
|
|
262
262
|
};
|
|
263
263
|
|
|
264
264
|
export type PromptInputAttachmentProps = HTMLAttributes<HTMLDivElement> & {
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
data: FileUIPart & { id: string };
|
|
266
|
+
className?: string;
|
|
267
267
|
};
|
|
268
268
|
|
|
269
269
|
export function PromptInputAttachment({
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
data,
|
|
271
|
+
className,
|
|
272
|
+
...props
|
|
273
273
|
}: PromptInputAttachmentProps) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
274
|
+
const attachments = usePromptInputAttachments();
|
|
275
|
+
|
|
276
|
+
const filename = data.filename || "";
|
|
277
|
+
|
|
278
|
+
const mediaType =
|
|
279
|
+
data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
|
|
280
|
+
const isImage = mediaType === "image";
|
|
281
|
+
|
|
282
|
+
const attachmentLabel = filename || (isImage ? "Image" : "Attachment");
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<PromptInputHoverCard>
|
|
286
|
+
<HoverCardTrigger asChild>
|
|
287
|
+
<div
|
|
288
|
+
className={cn(
|
|
289
|
+
"group relative flex h-8 cursor-default select-none items-center gap-1.5 rounded-md border border-border px-1.5 font-medium text-sm transition-all hover:bg-accent hover:text-accent-foreground",
|
|
290
|
+
className,
|
|
291
|
+
)}
|
|
292
|
+
key={data.id}
|
|
293
|
+
{...props}
|
|
294
|
+
>
|
|
295
|
+
<div className="relative size-5 shrink-0">
|
|
296
|
+
<div className="absolute inset-0 flex size-5 items-center justify-center overflow-hidden rounded bg-background transition-opacity group-hover:opacity-0">
|
|
297
|
+
{isImage ? (
|
|
298
|
+
<img
|
|
299
|
+
alt={filename || "attachment"}
|
|
300
|
+
className="size-5 object-cover"
|
|
301
|
+
height={20}
|
|
302
|
+
src={data.url}
|
|
303
|
+
width={20}
|
|
304
|
+
/>
|
|
305
|
+
) : (
|
|
306
|
+
<div className="flex size-5 items-center justify-center text-muted-foreground">
|
|
307
|
+
<PaperclipIcon className="size-3" />
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
<Button
|
|
312
|
+
aria-label="Remove attachment"
|
|
313
|
+
className="absolute inset-0 size-5 cursor-pointer rounded p-0 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 [&>svg]:size-2.5"
|
|
314
|
+
onClick={(e) => {
|
|
315
|
+
e.stopPropagation();
|
|
316
|
+
attachments.remove(data.id);
|
|
317
|
+
}}
|
|
318
|
+
type="button"
|
|
319
|
+
variant="ghost"
|
|
320
|
+
>
|
|
321
|
+
<XIcon />
|
|
322
|
+
<span className="sr-only">Remove</span>
|
|
323
|
+
</Button>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<span className="flex-1 truncate">{attachmentLabel}</span>
|
|
327
|
+
</div>
|
|
328
|
+
</HoverCardTrigger>
|
|
329
|
+
<PromptInputHoverCardContent className="w-auto p-2">
|
|
330
|
+
<div className="w-auto space-y-3">
|
|
331
|
+
{isImage && (
|
|
332
|
+
<div className="flex max-h-96 w-96 items-center justify-center overflow-hidden rounded-md border">
|
|
333
|
+
<img
|
|
334
|
+
alt={filename || "attachment preview"}
|
|
335
|
+
className="max-h-full max-w-full object-contain"
|
|
336
|
+
height={384}
|
|
337
|
+
src={data.url}
|
|
338
|
+
width={448}
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
342
|
+
<div className="flex items-center gap-2.5">
|
|
343
|
+
<div className="min-w-0 flex-1 space-y-1 px-0.5">
|
|
344
|
+
<h4 className="truncate font-semibold text-sm leading-none">
|
|
345
|
+
{filename || (isImage ? "Image" : "Attachment")}
|
|
346
|
+
</h4>
|
|
347
|
+
{data.mediaType && (
|
|
348
|
+
<p className="truncate font-mono text-muted-foreground text-xs">
|
|
349
|
+
{data.mediaType}
|
|
350
|
+
</p>
|
|
351
|
+
)}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</PromptInputHoverCardContent>
|
|
356
|
+
</PromptInputHoverCard>
|
|
357
|
+
);
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
export type PromptInputAttachmentsProps = Omit<
|
|
361
|
-
|
|
362
|
-
|
|
361
|
+
HTMLAttributes<HTMLDivElement>,
|
|
362
|
+
"children"
|
|
363
363
|
> & {
|
|
364
|
-
|
|
364
|
+
children: (attachment: FileUIPart & { id: string }) => ReactNode;
|
|
365
365
|
};
|
|
366
366
|
|
|
367
367
|
export function PromptInputAttachments({
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
children,
|
|
369
|
+
className,
|
|
370
|
+
...props
|
|
371
371
|
}: PromptInputAttachmentsProps) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
372
|
+
const attachments = usePromptInputAttachments();
|
|
373
|
+
|
|
374
|
+
if (!attachments.files.length) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<div
|
|
380
|
+
className={cn("flex flex-wrap items-center gap-2 p-3", className)}
|
|
381
|
+
{...props}
|
|
382
|
+
>
|
|
383
|
+
{attachments.files.map((file) => (
|
|
384
|
+
<Fragment key={file.id}>{children(file)}</Fragment>
|
|
385
|
+
))}
|
|
386
|
+
</div>
|
|
387
|
+
);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
export type PromptInputActionAddAttachmentsProps = ComponentProps<
|
|
391
|
-
|
|
391
|
+
typeof DropdownMenuItem
|
|
392
392
|
> & {
|
|
393
|
-
|
|
393
|
+
label?: string;
|
|
394
394
|
};
|
|
395
395
|
|
|
396
396
|
export const PromptInputActionAddAttachments = ({
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
label = "Add photos or files",
|
|
398
|
+
...props
|
|
399
399
|
}: PromptInputActionAddAttachmentsProps) => {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
400
|
+
const attachments = usePromptInputAttachments();
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<DropdownMenuItem
|
|
404
|
+
{...props}
|
|
405
|
+
onSelect={(e) => {
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
attachments.openFileDialog();
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
<ImageIcon className="mr-2 size-4" /> {label}
|
|
411
|
+
</DropdownMenuItem>
|
|
412
|
+
);
|
|
413
413
|
};
|
|
414
414
|
|
|
415
415
|
export type PromptInputMessage = {
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
text: string;
|
|
417
|
+
files: FileUIPart[];
|
|
418
418
|
};
|
|
419
419
|
|
|
420
420
|
export type PromptInputProps = Omit<
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
HTMLAttributes<HTMLFormElement>,
|
|
422
|
+
"onSubmit" | "onError"
|
|
423
423
|
> & {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
424
|
+
accept?: string; // e.g., "image/*" or leave undefined for any
|
|
425
|
+
multiple?: boolean;
|
|
426
|
+
// When true, accepts drops anywhere on document. Default false (opt-in).
|
|
427
|
+
globalDrop?: boolean;
|
|
428
|
+
// Render a hidden input with given name and keep it in sync for native form posts. Default false.
|
|
429
|
+
syncHiddenInput?: boolean;
|
|
430
|
+
// Minimal constraints
|
|
431
|
+
maxFiles?: number;
|
|
432
|
+
maxFileSize?: number; // bytes
|
|
433
|
+
onError?: (err: {
|
|
434
|
+
code: "max_files" | "max_file_size" | "accept";
|
|
435
|
+
message: string;
|
|
436
|
+
}) => void;
|
|
437
|
+
onSubmit: (
|
|
438
|
+
message: PromptInputMessage,
|
|
439
|
+
event: FormEvent<HTMLFormElement>,
|
|
440
|
+
) => void | Promise<void>;
|
|
441
|
+
inputGroupClassName?: string;
|
|
442
442
|
};
|
|
443
443
|
|
|
444
444
|
export const PromptInput = ({
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
445
|
+
className,
|
|
446
|
+
inputGroupClassName,
|
|
447
|
+
accept,
|
|
448
|
+
multiple,
|
|
449
|
+
globalDrop,
|
|
450
|
+
syncHiddenInput,
|
|
451
|
+
maxFiles,
|
|
452
|
+
maxFileSize,
|
|
453
|
+
onError,
|
|
454
|
+
onSubmit,
|
|
455
|
+
children,
|
|
456
|
+
...props
|
|
457
457
|
}: PromptInputProps) => {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
458
|
+
// Try to use a provider controller if present
|
|
459
|
+
const controller = useOptionalPromptInputController();
|
|
460
|
+
const usingProvider = !!controller;
|
|
461
|
+
|
|
462
|
+
// Refs
|
|
463
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
464
|
+
const anchorRef = useRef<HTMLSpanElement>(null);
|
|
465
|
+
const formRef = useRef<HTMLFormElement | null>(null);
|
|
466
|
+
|
|
467
|
+
// Find nearest form to scope drag & drop
|
|
468
|
+
useEffect(() => {
|
|
469
|
+
const root = anchorRef.current?.closest("form");
|
|
470
|
+
if (root instanceof HTMLFormElement) {
|
|
471
|
+
formRef.current = root;
|
|
472
|
+
}
|
|
473
|
+
}, []);
|
|
474
|
+
|
|
475
|
+
// ----- Local attachments (only used when no provider)
|
|
476
|
+
const [items, setItems] = useState<(FileUIPart & { id: string })[]>([]);
|
|
477
|
+
const files = usingProvider ? controller.attachments.files : items;
|
|
478
|
+
|
|
479
|
+
const openFileDialogLocal = useCallback(() => {
|
|
480
|
+
inputRef.current?.click();
|
|
481
|
+
}, []);
|
|
482
|
+
|
|
483
|
+
const matchesAccept = useCallback(
|
|
484
|
+
(f: File) => {
|
|
485
|
+
if (!accept || accept.trim() === "") {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
if (accept.includes("image/*")) {
|
|
489
|
+
return f.type.startsWith("image/");
|
|
490
|
+
}
|
|
491
|
+
// NOTE: keep simple; expand as needed
|
|
492
|
+
return true;
|
|
493
|
+
},
|
|
494
|
+
[accept],
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
const addLocal = useCallback(
|
|
498
|
+
(fileList: File[] | FileList) => {
|
|
499
|
+
const incoming = Array.from(fileList);
|
|
500
|
+
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
501
|
+
if (incoming.length && accepted.length === 0) {
|
|
502
|
+
onError?.({
|
|
503
|
+
code: "accept",
|
|
504
|
+
message: "No files match the accepted types.",
|
|
505
|
+
});
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const withinSize = (f: File) =>
|
|
509
|
+
maxFileSize ? f.size <= maxFileSize : true;
|
|
510
|
+
const sized = accepted.filter(withinSize);
|
|
511
|
+
if (accepted.length > 0 && sized.length === 0) {
|
|
512
|
+
onError?.({
|
|
513
|
+
code: "max_file_size",
|
|
514
|
+
message: "All files exceed the maximum size.",
|
|
515
|
+
});
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
setItems((prev) => {
|
|
520
|
+
const capacity =
|
|
521
|
+
typeof maxFiles === "number"
|
|
522
|
+
? Math.max(0, maxFiles - prev.length)
|
|
523
|
+
: undefined;
|
|
524
|
+
const capped =
|
|
525
|
+
typeof capacity === "number" ? sized.slice(0, capacity) : sized;
|
|
526
|
+
if (typeof capacity === "number" && sized.length > capacity) {
|
|
527
|
+
onError?.({
|
|
528
|
+
code: "max_files",
|
|
529
|
+
message: "Too many files. Some were not added.",
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
const next: (FileUIPart & { id: string })[] = [];
|
|
533
|
+
for (const file of capped) {
|
|
534
|
+
next.push({
|
|
535
|
+
id: nanoid(),
|
|
536
|
+
type: "file",
|
|
537
|
+
url: URL.createObjectURL(file),
|
|
538
|
+
mediaType: file.type,
|
|
539
|
+
filename: file.name,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return prev.concat(next);
|
|
543
|
+
});
|
|
544
|
+
},
|
|
545
|
+
[matchesAccept, maxFiles, maxFileSize, onError],
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const add = usingProvider
|
|
549
|
+
? (files: File[] | FileList) => controller.attachments.add(files)
|
|
550
|
+
: addLocal;
|
|
551
|
+
|
|
552
|
+
const remove = usingProvider
|
|
553
|
+
? (id: string) => controller.attachments.remove(id)
|
|
554
|
+
: (id: string) =>
|
|
555
|
+
setItems((prev) => {
|
|
556
|
+
const found = prev.find((file) => file.id === id);
|
|
557
|
+
if (found?.url) {
|
|
558
|
+
URL.revokeObjectURL(found.url);
|
|
559
|
+
}
|
|
560
|
+
return prev.filter((file) => file.id !== id);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const clear = usingProvider
|
|
564
|
+
? () => controller.attachments.clear()
|
|
565
|
+
: () =>
|
|
566
|
+
setItems((prev) => {
|
|
567
|
+
for (const file of prev) {
|
|
568
|
+
if (file.url) {
|
|
569
|
+
URL.revokeObjectURL(file.url);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return [];
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
const openFileDialog = usingProvider
|
|
576
|
+
? () => controller.attachments.openFileDialog()
|
|
577
|
+
: openFileDialogLocal;
|
|
578
|
+
|
|
579
|
+
// Let provider know about our hidden file input so external menus can call openFileDialog()
|
|
580
|
+
useEffect(() => {
|
|
581
|
+
if (!usingProvider) return;
|
|
582
|
+
controller.__registerFileInput(inputRef, () => inputRef.current?.click());
|
|
583
|
+
}, [usingProvider, controller]);
|
|
584
|
+
|
|
585
|
+
// Note: File input cannot be programmatically set for security reasons
|
|
586
|
+
// The syncHiddenInput prop is no longer functional
|
|
587
|
+
useEffect(() => {
|
|
588
|
+
if (syncHiddenInput && inputRef.current && files.length === 0) {
|
|
589
|
+
inputRef.current.value = "";
|
|
590
|
+
}
|
|
591
|
+
}, [files, syncHiddenInput]);
|
|
592
|
+
|
|
593
|
+
// Attach drop handlers on nearest form and document (opt-in)
|
|
594
|
+
useEffect(() => {
|
|
595
|
+
const form = formRef.current;
|
|
596
|
+
if (!form) return;
|
|
597
|
+
|
|
598
|
+
const onDragOver = (e: DragEvent) => {
|
|
599
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
600
|
+
e.preventDefault();
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const onDrop = (e: DragEvent) => {
|
|
604
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
605
|
+
e.preventDefault();
|
|
606
|
+
}
|
|
607
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
|
608
|
+
add(e.dataTransfer.files);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
form.addEventListener("dragover", onDragOver);
|
|
612
|
+
form.addEventListener("drop", onDrop);
|
|
613
|
+
return () => {
|
|
614
|
+
form.removeEventListener("dragover", onDragOver);
|
|
615
|
+
form.removeEventListener("drop", onDrop);
|
|
616
|
+
};
|
|
617
|
+
}, [add]);
|
|
618
|
+
|
|
619
|
+
useEffect(() => {
|
|
620
|
+
if (!globalDrop) return;
|
|
621
|
+
|
|
622
|
+
const onDragOver = (e: DragEvent) => {
|
|
623
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
624
|
+
e.preventDefault();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
const onDrop = (e: DragEvent) => {
|
|
628
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
629
|
+
e.preventDefault();
|
|
630
|
+
}
|
|
631
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
|
632
|
+
add(e.dataTransfer.files);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
document.addEventListener("dragover", onDragOver);
|
|
636
|
+
document.addEventListener("drop", onDrop);
|
|
637
|
+
return () => {
|
|
638
|
+
document.removeEventListener("dragover", onDragOver);
|
|
639
|
+
document.removeEventListener("drop", onDrop);
|
|
640
|
+
};
|
|
641
|
+
}, [add, globalDrop]);
|
|
642
|
+
|
|
643
|
+
useEffect(
|
|
644
|
+
() => () => {
|
|
645
|
+
if (!usingProvider) {
|
|
646
|
+
for (const f of files) {
|
|
647
|
+
if (f.url) URL.revokeObjectURL(f.url);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
[usingProvider, files],
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
655
|
+
if (event.currentTarget.files) {
|
|
656
|
+
add(event.currentTarget.files);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const convertBlobUrlToDataUrl = async (url: string): Promise<string> => {
|
|
661
|
+
const response = await fetch(url);
|
|
662
|
+
const blob = await response.blob();
|
|
663
|
+
return new Promise((resolve, reject) => {
|
|
664
|
+
const reader = new FileReader();
|
|
665
|
+
reader.onloadend = () => resolve(reader.result as string);
|
|
666
|
+
reader.onerror = reject;
|
|
667
|
+
reader.readAsDataURL(blob);
|
|
668
|
+
});
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const ctx = useMemo<AttachmentsContext>(
|
|
672
|
+
() => ({
|
|
673
|
+
files: files.map((item) => ({ ...item, id: item.id })),
|
|
674
|
+
add,
|
|
675
|
+
remove,
|
|
676
|
+
clear,
|
|
677
|
+
openFileDialog,
|
|
678
|
+
fileInputRef: inputRef,
|
|
679
|
+
}),
|
|
680
|
+
[files, add, remove, clear, openFileDialog],
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
|
684
|
+
event.preventDefault();
|
|
685
|
+
|
|
686
|
+
const form = event.currentTarget;
|
|
687
|
+
const text = usingProvider
|
|
688
|
+
? controller.textInput.value
|
|
689
|
+
: (() => {
|
|
690
|
+
const formData = new FormData(form);
|
|
691
|
+
return (formData.get("message") as string) || "";
|
|
692
|
+
})();
|
|
693
|
+
|
|
694
|
+
// Reset form immediately after capturing text to avoid race condition
|
|
695
|
+
// where user input during async blob conversion would be lost
|
|
696
|
+
if (!usingProvider) {
|
|
697
|
+
form.reset();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Convert blob URLs to data URLs asynchronously
|
|
701
|
+
Promise.all(
|
|
702
|
+
files.map(async ({ id, ...item }) => {
|
|
703
|
+
if (item.url && item.url.startsWith("blob:")) {
|
|
704
|
+
return {
|
|
705
|
+
...item,
|
|
706
|
+
url: await convertBlobUrlToDataUrl(item.url),
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return item;
|
|
710
|
+
}),
|
|
711
|
+
).then((convertedFiles: FileUIPart[]) => {
|
|
712
|
+
try {
|
|
713
|
+
const result = onSubmit({ text, files: convertedFiles }, event);
|
|
714
|
+
|
|
715
|
+
// Handle both sync and async onSubmit
|
|
716
|
+
if (result instanceof Promise) {
|
|
717
|
+
result
|
|
718
|
+
.then(() => {
|
|
719
|
+
clear();
|
|
720
|
+
if (usingProvider) {
|
|
721
|
+
controller.textInput.clear();
|
|
722
|
+
}
|
|
723
|
+
})
|
|
724
|
+
.catch(() => {
|
|
725
|
+
// Don't clear on error - user may want to retry
|
|
726
|
+
});
|
|
727
|
+
} else {
|
|
728
|
+
// Sync function completed without throwing, clear attachments
|
|
729
|
+
clear();
|
|
730
|
+
if (usingProvider) {
|
|
731
|
+
controller.textInput.clear();
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
} catch (error) {
|
|
735
|
+
// Don't clear on error - user may want to retry
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// Render with or without local provider
|
|
741
|
+
const inner = (
|
|
742
|
+
<>
|
|
743
|
+
<span aria-hidden="true" className="hidden" ref={anchorRef} />
|
|
744
|
+
<input
|
|
745
|
+
accept={accept}
|
|
746
|
+
aria-label="Upload files"
|
|
747
|
+
className="hidden"
|
|
748
|
+
multiple={multiple}
|
|
749
|
+
onChange={handleChange}
|
|
750
|
+
ref={inputRef}
|
|
751
|
+
title="Upload files"
|
|
752
|
+
type="file"
|
|
753
|
+
/>
|
|
754
|
+
<form
|
|
755
|
+
className={cn("w-full", className)}
|
|
756
|
+
onSubmit={handleSubmit}
|
|
757
|
+
{...props}
|
|
758
|
+
>
|
|
759
|
+
<InputGroup className={cn("overflow-hidden", inputGroupClassName)}>
|
|
760
|
+
{children}
|
|
761
|
+
</InputGroup>
|
|
762
|
+
</form>
|
|
763
|
+
</>
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
return usingProvider ? (
|
|
767
|
+
inner
|
|
768
|
+
) : (
|
|
769
|
+
<LocalAttachmentsContext.Provider value={ctx}>
|
|
770
|
+
{inner}
|
|
771
|
+
</LocalAttachmentsContext.Provider>
|
|
772
|
+
);
|
|
773
773
|
};
|
|
774
774
|
|
|
775
775
|
export type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;
|
|
776
776
|
|
|
777
777
|
export const PromptInputBody = ({
|
|
778
|
-
|
|
779
|
-
|
|
778
|
+
className,
|
|
779
|
+
...props
|
|
780
780
|
}: PromptInputBodyProps) => (
|
|
781
|
-
|
|
781
|
+
<div className={cn("contents", className)} {...props} />
|
|
782
782
|
);
|
|
783
783
|
|
|
784
784
|
export type PromptInputTextareaProps = ComponentProps<
|
|
785
|
-
|
|
785
|
+
typeof InputGroupTextarea
|
|
786
786
|
>;
|
|
787
787
|
|
|
788
788
|
export const PromptInputTextarea = ({
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
789
|
+
onChange,
|
|
790
|
+
className,
|
|
791
|
+
placeholder = "What would you like to know?",
|
|
792
|
+
...props
|
|
793
793
|
}: PromptInputTextareaProps) => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
794
|
+
const controller = useOptionalPromptInputController();
|
|
795
|
+
const attachments = usePromptInputAttachments();
|
|
796
|
+
const [isComposing, setIsComposing] = useState(false);
|
|
797
|
+
|
|
798
|
+
const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
|
|
799
|
+
if (e.key === "Enter") {
|
|
800
|
+
if (isComposing || e.nativeEvent.isComposing) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (e.shiftKey) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
e.preventDefault();
|
|
807
|
+
|
|
808
|
+
// Check if the submit button is disabled before submitting
|
|
809
|
+
const form = e.currentTarget.form;
|
|
810
|
+
const submitButton = form?.querySelector(
|
|
811
|
+
'button[type="submit"]',
|
|
812
|
+
) as HTMLButtonElement | null;
|
|
813
|
+
if (submitButton?.disabled) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
form?.requestSubmit();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Remove last attachment when Backspace is pressed and textarea is empty
|
|
821
|
+
if (
|
|
822
|
+
e.key === "Backspace" &&
|
|
823
|
+
e.currentTarget.value === "" &&
|
|
824
|
+
attachments.files.length > 0
|
|
825
|
+
) {
|
|
826
|
+
e.preventDefault();
|
|
827
|
+
const lastAttachment = attachments.files.at(-1);
|
|
828
|
+
if (lastAttachment) {
|
|
829
|
+
attachments.remove(lastAttachment.id);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = (event) => {
|
|
835
|
+
const items = event.clipboardData?.items;
|
|
836
|
+
|
|
837
|
+
if (!items) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const files: File[] = [];
|
|
842
|
+
|
|
843
|
+
for (const item of items) {
|
|
844
|
+
if (item.kind === "file") {
|
|
845
|
+
const file = item.getAsFile();
|
|
846
|
+
if (file) {
|
|
847
|
+
files.push(file);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (files.length > 0) {
|
|
853
|
+
event.preventDefault();
|
|
854
|
+
attachments.add(files);
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
const controlledProps = controller
|
|
859
|
+
? {
|
|
860
|
+
value: controller.textInput.value,
|
|
861
|
+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
862
|
+
controller.textInput.setInput(e.currentTarget.value);
|
|
863
|
+
onChange?.(e);
|
|
864
|
+
},
|
|
865
|
+
}
|
|
866
|
+
: {
|
|
867
|
+
onChange,
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
return (
|
|
871
|
+
<InputGroupTextarea
|
|
872
|
+
className={cn("field-sizing-content max-h-48 min-h-16", className)}
|
|
873
|
+
name="message"
|
|
874
|
+
onCompositionEnd={() => setIsComposing(false)}
|
|
875
|
+
onCompositionStart={() => setIsComposing(true)}
|
|
876
|
+
onKeyDown={handleKeyDown}
|
|
877
|
+
onPaste={handlePaste}
|
|
878
|
+
placeholder={placeholder}
|
|
879
|
+
{...props}
|
|
880
|
+
{...controlledProps}
|
|
881
|
+
/>
|
|
882
|
+
);
|
|
883
883
|
};
|
|
884
884
|
|
|
885
885
|
export type PromptInputHeaderProps = Omit<
|
|
886
|
-
|
|
887
|
-
|
|
886
|
+
ComponentProps<typeof InputGroupAddon>,
|
|
887
|
+
"align"
|
|
888
888
|
>;
|
|
889
889
|
|
|
890
890
|
export const PromptInputHeader = ({
|
|
891
|
-
|
|
892
|
-
|
|
891
|
+
className,
|
|
892
|
+
...props
|
|
893
893
|
}: PromptInputHeaderProps) => (
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
894
|
+
<InputGroupAddon
|
|
895
|
+
align="block-end"
|
|
896
|
+
className={cn("order-first flex-wrap gap-1", className)}
|
|
897
|
+
{...props}
|
|
898
|
+
/>
|
|
899
899
|
);
|
|
900
900
|
|
|
901
901
|
export type PromptInputFooterProps = Omit<
|
|
902
|
-
|
|
903
|
-
|
|
902
|
+
ComponentProps<typeof InputGroupAddon>,
|
|
903
|
+
"align"
|
|
904
904
|
>;
|
|
905
905
|
|
|
906
906
|
export const PromptInputFooter = ({
|
|
907
|
-
|
|
908
|
-
|
|
907
|
+
className,
|
|
908
|
+
...props
|
|
909
909
|
}: PromptInputFooterProps) => (
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
910
|
+
<InputGroupAddon
|
|
911
|
+
align="block-end"
|
|
912
|
+
className={cn("justify-between gap-1", className)}
|
|
913
|
+
{...props}
|
|
914
|
+
/>
|
|
915
915
|
);
|
|
916
916
|
|
|
917
917
|
export type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;
|
|
918
918
|
|
|
919
919
|
export const PromptInputTools = ({
|
|
920
|
-
|
|
921
|
-
|
|
920
|
+
className,
|
|
921
|
+
...props
|
|
922
922
|
}: PromptInputToolsProps) => (
|
|
923
|
-
|
|
923
|
+
<div className={cn("flex items-center gap-1", className)} {...props} />
|
|
924
924
|
);
|
|
925
925
|
|
|
926
926
|
export type PromptInputButtonProps = ComponentProps<typeof InputGroupButton>;
|
|
927
927
|
|
|
928
928
|
export const PromptInputButton = ({
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
929
|
+
variant = "ghost",
|
|
930
|
+
className,
|
|
931
|
+
size,
|
|
932
|
+
...props
|
|
933
933
|
}: PromptInputButtonProps) => {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
934
|
+
const newSize =
|
|
935
|
+
size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");
|
|
936
|
+
|
|
937
|
+
return (
|
|
938
|
+
<InputGroupButton
|
|
939
|
+
className={cn(className)}
|
|
940
|
+
size={newSize}
|
|
941
|
+
type="button"
|
|
942
|
+
variant={variant}
|
|
943
|
+
{...props}
|
|
944
|
+
/>
|
|
945
|
+
);
|
|
946
946
|
};
|
|
947
947
|
|
|
948
948
|
export type PromptInputActionMenuProps = ComponentProps<typeof DropdownMenu>;
|
|
949
949
|
export const PromptInputActionMenu = (props: PromptInputActionMenuProps) => (
|
|
950
|
-
|
|
950
|
+
<DropdownMenu {...props} />
|
|
951
951
|
);
|
|
952
952
|
|
|
953
953
|
export type PromptInputActionMenuTriggerProps = PromptInputButtonProps;
|
|
954
954
|
|
|
955
955
|
export const PromptInputActionMenuTrigger = ({
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
956
|
+
className,
|
|
957
|
+
children,
|
|
958
|
+
...props
|
|
959
959
|
}: PromptInputActionMenuTriggerProps) => (
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
960
|
+
<DropdownMenuTrigger asChild>
|
|
961
|
+
<PromptInputButton className={className} {...props}>
|
|
962
|
+
{children ?? <PlusIcon className="size-4" />}
|
|
963
|
+
</PromptInputButton>
|
|
964
|
+
</DropdownMenuTrigger>
|
|
965
965
|
);
|
|
966
966
|
|
|
967
967
|
export type PromptInputActionMenuContentProps = ComponentProps<
|
|
968
|
-
|
|
968
|
+
typeof DropdownMenuContent
|
|
969
969
|
>;
|
|
970
970
|
export const PromptInputActionMenuContent = ({
|
|
971
|
-
|
|
972
|
-
|
|
971
|
+
className,
|
|
972
|
+
...props
|
|
973
973
|
}: PromptInputActionMenuContentProps) => (
|
|
974
|
-
|
|
974
|
+
<DropdownMenuContent align="start" className={cn(className)} {...props} />
|
|
975
975
|
);
|
|
976
976
|
|
|
977
977
|
export type PromptInputActionMenuItemProps = ComponentProps<
|
|
978
|
-
|
|
978
|
+
typeof DropdownMenuItem
|
|
979
979
|
>;
|
|
980
980
|
export const PromptInputActionMenuItem = ({
|
|
981
|
-
|
|
982
|
-
|
|
981
|
+
className,
|
|
982
|
+
...props
|
|
983
983
|
}: PromptInputActionMenuItemProps) => (
|
|
984
|
-
|
|
984
|
+
<DropdownMenuItem className={cn(className)} {...props} />
|
|
985
985
|
);
|
|
986
986
|
|
|
987
987
|
// Note: Actions that perform side-effects (like opening a file dialog)
|
|
988
988
|
// are provided in opt-in modules (e.g., prompt-input-attachments).
|
|
989
989
|
|
|
990
990
|
export type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
|
|
991
|
-
|
|
991
|
+
status?: ChatStatus;
|
|
992
992
|
};
|
|
993
993
|
|
|
994
994
|
export const PromptInputSubmit = ({
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
995
|
+
className,
|
|
996
|
+
variant = "default",
|
|
997
|
+
size = "icon-sm",
|
|
998
|
+
status,
|
|
999
|
+
children,
|
|
1000
|
+
...props
|
|
1001
1001
|
}: PromptInputSubmitProps) => {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1002
|
+
let Icon = <CornerDownLeftIcon className="size-4" />;
|
|
1003
|
+
|
|
1004
|
+
if (status === "submitted") {
|
|
1005
|
+
Icon = <Loader2Icon className="size-4 animate-spin" />;
|
|
1006
|
+
} else if (status === "streaming") {
|
|
1007
|
+
Icon = <SquareIcon className="size-4" />;
|
|
1008
|
+
} else if (status === "error") {
|
|
1009
|
+
Icon = <XIcon className="size-4" />;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return (
|
|
1013
|
+
<InputGroupButton
|
|
1014
|
+
aria-label="Submit"
|
|
1015
|
+
className={cn(className)}
|
|
1016
|
+
size={size}
|
|
1017
|
+
type="submit"
|
|
1018
|
+
variant={variant}
|
|
1019
|
+
{...props}
|
|
1020
|
+
>
|
|
1021
|
+
{children ?? Icon}
|
|
1022
|
+
</InputGroupButton>
|
|
1023
|
+
);
|
|
1024
1024
|
};
|
|
1025
1025
|
|
|
1026
1026
|
interface SpeechRecognition extends EventTarget {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1027
|
+
continuous: boolean;
|
|
1028
|
+
interimResults: boolean;
|
|
1029
|
+
lang: string;
|
|
1030
|
+
start(): void;
|
|
1031
|
+
stop(): void;
|
|
1032
|
+
onstart: ((this: SpeechRecognition, ev: Event) => any) | null;
|
|
1033
|
+
onend: ((this: SpeechRecognition, ev: Event) => any) | null;
|
|
1034
|
+
onresult:
|
|
1035
|
+
| ((this: SpeechRecognition, ev: SpeechRecognitionEvent) => any)
|
|
1036
|
+
| null;
|
|
1037
|
+
onerror:
|
|
1038
|
+
| ((this: SpeechRecognition, ev: SpeechRecognitionErrorEvent) => any)
|
|
1039
|
+
| null;
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
1042
|
interface SpeechRecognitionEvent extends Event {
|
|
1043
|
-
|
|
1044
|
-
|
|
1043
|
+
results: SpeechRecognitionResultList;
|
|
1044
|
+
resultIndex: number;
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
1047
1047
|
type SpeechRecognitionResultList = {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1048
|
+
readonly length: number;
|
|
1049
|
+
item(index: number): SpeechRecognitionResult;
|
|
1050
|
+
[index: number]: SpeechRecognitionResult;
|
|
1051
1051
|
};
|
|
1052
1052
|
|
|
1053
1053
|
type SpeechRecognitionResult = {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1054
|
+
readonly length: number;
|
|
1055
|
+
item(index: number): SpeechRecognitionAlternative;
|
|
1056
|
+
[index: number]: SpeechRecognitionAlternative;
|
|
1057
|
+
isFinal: boolean;
|
|
1058
1058
|
};
|
|
1059
1059
|
|
|
1060
1060
|
type SpeechRecognitionAlternative = {
|
|
1061
|
-
|
|
1062
|
-
|
|
1061
|
+
transcript: string;
|
|
1062
|
+
confidence: number;
|
|
1063
1063
|
};
|
|
1064
1064
|
|
|
1065
1065
|
interface SpeechRecognitionErrorEvent extends Event {
|
|
1066
|
-
|
|
1066
|
+
error: string;
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
1069
|
declare global {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1070
|
+
interface Window {
|
|
1071
|
+
SpeechRecognition: {
|
|
1072
|
+
new (): SpeechRecognition;
|
|
1073
|
+
};
|
|
1074
|
+
webkitSpeechRecognition: {
|
|
1075
|
+
new (): SpeechRecognition;
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
1080
|
export type PromptInputSpeechButtonProps = ComponentProps<
|
|
1081
|
-
|
|
1081
|
+
typeof PromptInputButton
|
|
1082
1082
|
> & {
|
|
1083
|
-
|
|
1084
|
-
|
|
1083
|
+
textareaRef?: RefObject<HTMLTextAreaElement | null>;
|
|
1084
|
+
onTranscriptionChange?: (text: string) => void;
|
|
1085
1085
|
};
|
|
1086
1086
|
|
|
1087
1087
|
export const PromptInputSpeechButton = ({
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1088
|
+
className,
|
|
1089
|
+
textareaRef,
|
|
1090
|
+
onTranscriptionChange,
|
|
1091
|
+
...props
|
|
1092
1092
|
}: PromptInputSpeechButtonProps) => {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1093
|
+
const [isListening, setIsListening] = useState(false);
|
|
1094
|
+
const [recognition, setRecognition] = useState<SpeechRecognition | null>(
|
|
1095
|
+
null,
|
|
1096
|
+
);
|
|
1097
|
+
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
|
1098
|
+
|
|
1099
|
+
useEffect(() => {
|
|
1100
|
+
if (
|
|
1101
|
+
typeof window !== "undefined" &&
|
|
1102
|
+
("SpeechRecognition" in window || "webkitSpeechRecognition" in window)
|
|
1103
|
+
) {
|
|
1104
|
+
const SpeechRecognition =
|
|
1105
|
+
window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
1106
|
+
const speechRecognition = new SpeechRecognition();
|
|
1107
|
+
|
|
1108
|
+
speechRecognition.continuous = true;
|
|
1109
|
+
speechRecognition.interimResults = true;
|
|
1110
|
+
speechRecognition.lang = "en-US";
|
|
1111
|
+
|
|
1112
|
+
speechRecognition.onstart = () => {
|
|
1113
|
+
setIsListening(true);
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
speechRecognition.onend = () => {
|
|
1117
|
+
setIsListening(false);
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
speechRecognition.onresult = (event) => {
|
|
1121
|
+
let finalTranscript = "";
|
|
1122
|
+
|
|
1123
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
1124
|
+
const result = event.results[i];
|
|
1125
|
+
if (result.isFinal) {
|
|
1126
|
+
finalTranscript += result[0]?.transcript ?? "";
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (finalTranscript && textareaRef?.current) {
|
|
1131
|
+
const textarea = textareaRef.current;
|
|
1132
|
+
const currentValue = textarea.value;
|
|
1133
|
+
const newValue =
|
|
1134
|
+
currentValue + (currentValue ? " " : "") + finalTranscript;
|
|
1135
|
+
|
|
1136
|
+
textarea.value = newValue;
|
|
1137
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1138
|
+
onTranscriptionChange?.(newValue);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
speechRecognition.onerror = (event) => {
|
|
1143
|
+
console.error("Speech recognition error:", event.error);
|
|
1144
|
+
setIsListening(false);
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
recognitionRef.current = speechRecognition;
|
|
1148
|
+
setRecognition(speechRecognition);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return () => {
|
|
1152
|
+
if (recognitionRef.current) {
|
|
1153
|
+
recognitionRef.current.stop();
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
}, [textareaRef, onTranscriptionChange]);
|
|
1157
|
+
|
|
1158
|
+
const toggleListening = useCallback(() => {
|
|
1159
|
+
if (!recognition) {
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (isListening) {
|
|
1164
|
+
recognition.stop();
|
|
1165
|
+
} else {
|
|
1166
|
+
recognition.start();
|
|
1167
|
+
}
|
|
1168
|
+
}, [recognition, isListening]);
|
|
1169
|
+
|
|
1170
|
+
return (
|
|
1171
|
+
<PromptInputButton
|
|
1172
|
+
className={cn(
|
|
1173
|
+
"relative transition-all duration-200",
|
|
1174
|
+
isListening && "animate-pulse bg-accent text-accent-foreground",
|
|
1175
|
+
className,
|
|
1176
|
+
)}
|
|
1177
|
+
disabled={!recognition}
|
|
1178
|
+
onClick={toggleListening}
|
|
1179
|
+
{...props}
|
|
1180
|
+
>
|
|
1181
|
+
<MicIcon className="size-4" />
|
|
1182
|
+
</PromptInputButton>
|
|
1183
|
+
);
|
|
1184
1184
|
};
|
|
1185
1185
|
|
|
1186
1186
|
export type PromptInputSelectProps = ComponentProps<typeof Select>;
|
|
1187
1187
|
|
|
1188
1188
|
export const PromptInputSelect = (props: PromptInputSelectProps) => (
|
|
1189
|
-
|
|
1189
|
+
<Select {...props} />
|
|
1190
1190
|
);
|
|
1191
1191
|
|
|
1192
1192
|
export type PromptInputSelectTriggerProps = ComponentProps<
|
|
1193
|
-
|
|
1193
|
+
typeof SelectTrigger
|
|
1194
1194
|
>;
|
|
1195
1195
|
|
|
1196
1196
|
export const PromptInputSelectTrigger = ({
|
|
1197
|
-
|
|
1198
|
-
|
|
1197
|
+
className,
|
|
1198
|
+
...props
|
|
1199
1199
|
}: PromptInputSelectTriggerProps) => (
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1200
|
+
<SelectTrigger
|
|
1201
|
+
className={cn(
|
|
1202
|
+
"border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors",
|
|
1203
|
+
"hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground",
|
|
1204
|
+
className,
|
|
1205
|
+
)}
|
|
1206
|
+
{...props}
|
|
1207
|
+
/>
|
|
1208
1208
|
);
|
|
1209
1209
|
|
|
1210
1210
|
export type PromptInputSelectContentProps = ComponentProps<
|
|
1211
|
-
|
|
1211
|
+
typeof SelectContent
|
|
1212
1212
|
>;
|
|
1213
1213
|
|
|
1214
1214
|
export const PromptInputSelectContent = ({
|
|
1215
|
-
|
|
1216
|
-
|
|
1215
|
+
className,
|
|
1216
|
+
...props
|
|
1217
1217
|
}: PromptInputSelectContentProps) => (
|
|
1218
|
-
|
|
1218
|
+
<SelectContent className={cn(className)} {...props} />
|
|
1219
1219
|
);
|
|
1220
1220
|
|
|
1221
1221
|
export type PromptInputSelectItemProps = ComponentProps<typeof SelectItem>;
|
|
1222
1222
|
|
|
1223
1223
|
export const PromptInputSelectItem = ({
|
|
1224
|
-
|
|
1225
|
-
|
|
1224
|
+
className,
|
|
1225
|
+
...props
|
|
1226
1226
|
}: PromptInputSelectItemProps) => (
|
|
1227
|
-
|
|
1227
|
+
<SelectItem className={cn(className)} {...props} />
|
|
1228
1228
|
);
|
|
1229
1229
|
|
|
1230
1230
|
export type PromptInputSelectValueProps = ComponentProps<typeof SelectValue>;
|
|
1231
1231
|
|
|
1232
1232
|
export const PromptInputSelectValue = ({
|
|
1233
|
-
|
|
1234
|
-
|
|
1233
|
+
className,
|
|
1234
|
+
...props
|
|
1235
1235
|
}: PromptInputSelectValueProps) => (
|
|
1236
|
-
|
|
1236
|
+
<SelectValue className={cn(className)} {...props} />
|
|
1237
1237
|
);
|
|
1238
1238
|
|
|
1239
1239
|
export type PromptInputHoverCardProps = ComponentProps<typeof HoverCard>;
|
|
1240
1240
|
|
|
1241
1241
|
export const PromptInputHoverCard = ({
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1242
|
+
openDelay = 0,
|
|
1243
|
+
closeDelay = 0,
|
|
1244
|
+
...props
|
|
1245
1245
|
}: PromptInputHoverCardProps) => (
|
|
1246
|
-
|
|
1246
|
+
<HoverCard closeDelay={closeDelay} openDelay={openDelay} {...props} />
|
|
1247
1247
|
);
|
|
1248
1248
|
|
|
1249
1249
|
export type PromptInputHoverCardTriggerProps = ComponentProps<
|
|
1250
|
-
|
|
1250
|
+
typeof HoverCardTrigger
|
|
1251
1251
|
>;
|
|
1252
1252
|
|
|
1253
1253
|
export const PromptInputHoverCardTrigger = (
|
|
1254
|
-
|
|
1254
|
+
props: PromptInputHoverCardTriggerProps,
|
|
1255
1255
|
) => <HoverCardTrigger {...props} />;
|
|
1256
1256
|
|
|
1257
1257
|
export type PromptInputHoverCardContentProps = ComponentProps<
|
|
1258
|
-
|
|
1258
|
+
typeof HoverCardContent
|
|
1259
1259
|
>;
|
|
1260
1260
|
|
|
1261
1261
|
export const PromptInputHoverCardContent = ({
|
|
1262
|
-
|
|
1263
|
-
|
|
1262
|
+
align = "start",
|
|
1263
|
+
...props
|
|
1264
1264
|
}: PromptInputHoverCardContentProps) => (
|
|
1265
|
-
|
|
1265
|
+
<HoverCardContent align={align} {...props} />
|
|
1266
1266
|
);
|
|
1267
1267
|
|
|
1268
1268
|
export type PromptInputTabsListProps = HTMLAttributes<HTMLDivElement>;
|
|
1269
1269
|
|
|
1270
1270
|
export const PromptInputTabsList = ({
|
|
1271
|
-
|
|
1272
|
-
|
|
1271
|
+
className,
|
|
1272
|
+
...props
|
|
1273
1273
|
}: PromptInputTabsListProps) => <div className={cn(className)} {...props} />;
|
|
1274
1274
|
|
|
1275
1275
|
export type PromptInputTabProps = HTMLAttributes<HTMLDivElement>;
|
|
1276
1276
|
|
|
1277
1277
|
export const PromptInputTab = ({
|
|
1278
|
-
|
|
1279
|
-
|
|
1278
|
+
className,
|
|
1279
|
+
...props
|
|
1280
1280
|
}: PromptInputTabProps) => <div className={cn(className)} {...props} />;
|
|
1281
1281
|
|
|
1282
1282
|
export type PromptInputTabLabelProps = HTMLAttributes<HTMLHeadingElement>;
|
|
1283
1283
|
|
|
1284
1284
|
export const PromptInputTabLabel = ({
|
|
1285
|
-
|
|
1286
|
-
|
|
1285
|
+
className,
|
|
1286
|
+
...props
|
|
1287
1287
|
}: PromptInputTabLabelProps) => (
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1288
|
+
<h3
|
|
1289
|
+
className={cn(
|
|
1290
|
+
"mb-2 px-3 font-medium text-muted-foreground text-xs",
|
|
1291
|
+
className,
|
|
1292
|
+
)}
|
|
1293
|
+
{...props}
|
|
1294
|
+
/>
|
|
1295
1295
|
);
|
|
1296
1296
|
|
|
1297
1297
|
export type PromptInputTabBodyProps = HTMLAttributes<HTMLDivElement>;
|
|
1298
1298
|
|
|
1299
1299
|
export const PromptInputTabBody = ({
|
|
1300
|
-
|
|
1301
|
-
|
|
1300
|
+
className,
|
|
1301
|
+
...props
|
|
1302
1302
|
}: PromptInputTabBodyProps) => (
|
|
1303
|
-
|
|
1303
|
+
<div className={cn("space-y-1", className)} {...props} />
|
|
1304
1304
|
);
|
|
1305
1305
|
|
|
1306
1306
|
export type PromptInputTabItemProps = HTMLAttributes<HTMLDivElement>;
|
|
1307
1307
|
|
|
1308
1308
|
export const PromptInputTabItem = ({
|
|
1309
|
-
|
|
1310
|
-
|
|
1309
|
+
className,
|
|
1310
|
+
...props
|
|
1311
1311
|
}: PromptInputTabItemProps) => (
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1312
|
+
<div
|
|
1313
|
+
className={cn(
|
|
1314
|
+
"flex items-center gap-2 px-3 py-2 text-xs hover:bg-accent",
|
|
1315
|
+
className,
|
|
1316
|
+
)}
|
|
1317
|
+
{...props}
|
|
1318
|
+
/>
|
|
1319
1319
|
);
|
|
1320
1320
|
|
|
1321
1321
|
export type PromptInputCommandProps = ComponentProps<typeof Command>;
|
|
1322
1322
|
|
|
1323
1323
|
export const PromptInputCommand = ({
|
|
1324
|
-
|
|
1325
|
-
|
|
1324
|
+
className,
|
|
1325
|
+
...props
|
|
1326
1326
|
}: PromptInputCommandProps) => <Command className={cn(className)} {...props} />;
|
|
1327
1327
|
|
|
1328
1328
|
export type PromptInputCommandInputProps = ComponentProps<typeof CommandInput>;
|
|
1329
1329
|
|
|
1330
1330
|
export const PromptInputCommandInput = ({
|
|
1331
|
-
|
|
1332
|
-
|
|
1331
|
+
className,
|
|
1332
|
+
...props
|
|
1333
1333
|
}: PromptInputCommandInputProps) => (
|
|
1334
|
-
|
|
1334
|
+
<CommandInput className={cn(className)} {...props} />
|
|
1335
1335
|
);
|
|
1336
1336
|
|
|
1337
1337
|
export type PromptInputCommandListProps = ComponentProps<typeof CommandList>;
|
|
1338
1338
|
|
|
1339
1339
|
export const PromptInputCommandList = ({
|
|
1340
|
-
|
|
1341
|
-
|
|
1340
|
+
className,
|
|
1341
|
+
...props
|
|
1342
1342
|
}: PromptInputCommandListProps) => (
|
|
1343
|
-
|
|
1343
|
+
<CommandList className={cn(className)} {...props} />
|
|
1344
1344
|
);
|
|
1345
1345
|
|
|
1346
1346
|
export type PromptInputCommandEmptyProps = ComponentProps<typeof CommandEmpty>;
|
|
1347
1347
|
|
|
1348
1348
|
export const PromptInputCommandEmpty = ({
|
|
1349
|
-
|
|
1350
|
-
|
|
1349
|
+
className,
|
|
1350
|
+
...props
|
|
1351
1351
|
}: PromptInputCommandEmptyProps) => (
|
|
1352
|
-
|
|
1352
|
+
<CommandEmpty className={cn(className)} {...props} />
|
|
1353
1353
|
);
|
|
1354
1354
|
|
|
1355
1355
|
export type PromptInputCommandGroupProps = ComponentProps<typeof CommandGroup>;
|
|
1356
1356
|
|
|
1357
1357
|
export const PromptInputCommandGroup = ({
|
|
1358
|
-
|
|
1359
|
-
|
|
1358
|
+
className,
|
|
1359
|
+
...props
|
|
1360
1360
|
}: PromptInputCommandGroupProps) => (
|
|
1361
|
-
|
|
1361
|
+
<CommandGroup className={cn(className)} {...props} />
|
|
1362
1362
|
);
|
|
1363
1363
|
|
|
1364
1364
|
export type PromptInputCommandItemProps = ComponentProps<typeof CommandItem>;
|
|
1365
1365
|
|
|
1366
1366
|
export const PromptInputCommandItem = ({
|
|
1367
|
-
|
|
1368
|
-
|
|
1367
|
+
className,
|
|
1368
|
+
...props
|
|
1369
1369
|
}: PromptInputCommandItemProps) => (
|
|
1370
|
-
|
|
1370
|
+
<CommandItem className={cn(className)} {...props} />
|
|
1371
1371
|
);
|
|
1372
1372
|
|
|
1373
1373
|
export type PromptInputCommandSeparatorProps = ComponentProps<
|
|
1374
|
-
|
|
1374
|
+
typeof CommandSeparator
|
|
1375
1375
|
>;
|
|
1376
1376
|
|
|
1377
1377
|
export const PromptInputCommandSeparator = ({
|
|
1378
|
-
|
|
1379
|
-
|
|
1378
|
+
className,
|
|
1379
|
+
...props
|
|
1380
1380
|
}: PromptInputCommandSeparatorProps) => (
|
|
1381
|
-
|
|
1381
|
+
<CommandSeparator className={cn(className)} {...props} />
|
|
1382
1382
|
);
|