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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +123 -0
  2. package/components.json +21 -0
  3. package/dist/index.css +4241 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.d.mts +59 -0
  6. package/dist/index.d.ts +59 -0
  7. package/dist/index.js +2169 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +2182 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/example/.env.sample +2 -0
  12. package/example/App.tsx +368 -0
  13. package/example/app.css +1 -0
  14. package/example/index.html +12 -0
  15. package/example/main.tsx +9 -0
  16. package/example/vite.config.ts +34 -0
  17. package/package.json +75 -0
  18. package/postcss.config.cjs +3 -0
  19. package/src/components/ai-elements/agent.tsx +140 -0
  20. package/src/components/ai-elements/artifact.tsx +147 -0
  21. package/src/components/ai-elements/attachments.tsx +421 -0
  22. package/src/components/ai-elements/audio-player.tsx +228 -0
  23. package/src/components/ai-elements/canvas.tsx +22 -0
  24. package/src/components/ai-elements/chain-of-thought.tsx +228 -0
  25. package/src/components/ai-elements/checkpoint.tsx +71 -0
  26. package/src/components/ai-elements/code-block.tsx +532 -0
  27. package/src/components/ai-elements/commit.tsx +448 -0
  28. package/src/components/ai-elements/confirmation.tsx +176 -0
  29. package/src/components/ai-elements/connection.tsx +28 -0
  30. package/src/components/ai-elements/context.tsx +408 -0
  31. package/src/components/ai-elements/controls.tsx +18 -0
  32. package/src/components/ai-elements/conversation.tsx +100 -0
  33. package/src/components/ai-elements/edge.tsx +140 -0
  34. package/src/components/ai-elements/environment-variables.tsx +295 -0
  35. package/src/components/ai-elements/file-tree.tsx +258 -0
  36. package/src/components/ai-elements/image.tsx +24 -0
  37. package/src/components/ai-elements/inline-citation.tsx +287 -0
  38. package/src/components/ai-elements/message.tsx +336 -0
  39. package/src/components/ai-elements/mic-selector.tsx +370 -0
  40. package/src/components/ai-elements/model-selector.tsx +211 -0
  41. package/src/components/ai-elements/node.tsx +71 -0
  42. package/src/components/ai-elements/open-in-chat.tsx +365 -0
  43. package/src/components/ai-elements/package-info.tsx +233 -0
  44. package/src/components/ai-elements/panel.tsx +15 -0
  45. package/src/components/ai-elements/persona.tsx +270 -0
  46. package/src/components/ai-elements/plan.tsx +142 -0
  47. package/src/components/ai-elements/prompt-input.tsx +1263 -0
  48. package/src/components/ai-elements/queue.tsx +274 -0
  49. package/src/components/ai-elements/reasoning.tsx +193 -0
  50. package/src/components/ai-elements/sandbox.tsx +126 -0
  51. package/src/components/ai-elements/schema-display.tsx +458 -0
  52. package/src/components/ai-elements/shimmer.tsx +64 -0
  53. package/src/components/ai-elements/snippet.tsx +139 -0
  54. package/src/components/ai-elements/sources.tsx +77 -0
  55. package/src/components/ai-elements/speech-input.tsx +301 -0
  56. package/src/components/ai-elements/stack-trace.tsx +482 -0
  57. package/src/components/ai-elements/suggestion.tsx +53 -0
  58. package/src/components/ai-elements/task.tsx +87 -0
  59. package/src/components/ai-elements/terminal.tsx +261 -0
  60. package/src/components/ai-elements/test-results.tsx +485 -0
  61. package/src/components/ai-elements/tool.tsx +174 -0
  62. package/src/components/ai-elements/toolbar.tsx +16 -0
  63. package/src/components/ai-elements/transcription.tsx +124 -0
  64. package/src/components/ai-elements/voice-selector.tsx +479 -0
  65. package/src/components/ai-elements/web-preview.tsx +263 -0
  66. package/src/components/chat/Chat.tsx +178 -0
  67. package/src/components/chat/Input.tsx +98 -0
  68. package/src/components/chat/Message.tsx +276 -0
  69. package/src/components/chat/index.ts +2 -0
  70. package/src/components/index.ts +1 -0
  71. package/src/components/ui/accordion.tsx +64 -0
  72. package/src/components/ui/alert.tsx +66 -0
  73. package/src/components/ui/avatar.tsx +107 -0
  74. package/src/components/ui/badge.tsx +48 -0
  75. package/src/components/ui/button-group.tsx +83 -0
  76. package/src/components/ui/button.tsx +64 -0
  77. package/src/components/ui/card.tsx +92 -0
  78. package/src/components/ui/carousel.tsx +239 -0
  79. package/src/components/ui/collapsible.tsx +31 -0
  80. package/src/components/ui/command.tsx +184 -0
  81. package/src/components/ui/dialog.tsx +158 -0
  82. package/src/components/ui/dropdown-menu.tsx +257 -0
  83. package/src/components/ui/hover-card.tsx +42 -0
  84. package/src/components/ui/input-group.tsx +168 -0
  85. package/src/components/ui/input.tsx +21 -0
  86. package/src/components/ui/popover.tsx +87 -0
  87. package/src/components/ui/progress.tsx +31 -0
  88. package/src/components/ui/scroll-area.tsx +56 -0
  89. package/src/components/ui/select.tsx +190 -0
  90. package/src/components/ui/separator.tsx +28 -0
  91. package/src/components/ui/spinner.tsx +16 -0
  92. package/src/components/ui/switch.tsx +33 -0
  93. package/src/components/ui/tabs.tsx +91 -0
  94. package/src/components/ui/textarea.tsx +18 -0
  95. package/src/components/ui/tooltip.tsx +61 -0
  96. package/src/css/global.css +123 -0
  97. package/src/css/index.css +1 -0
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/use-copy-to-clipboard.ts +31 -0
  100. package/src/index.ts +4 -0
  101. package/src/lib/utils.ts +6 -0
  102. package/src/locales/context.ts +8 -0
  103. package/src/locales/hooks.ts +20 -0
  104. package/src/locales/index.ts +3 -0
  105. package/src/locales/langs/en.ts +17 -0
  106. package/src/locales/langs/index.ts +12 -0
  107. package/src/locales/langs/zh-cn.ts +18 -0
  108. package/tsconfig.json +21 -0
  109. package/tsup.config.ts +21 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,2182 @@
1
+ // src/components/ui/button.tsx
2
+ import { cva } from "class-variance-authority";
3
+ import { Slot } from "radix-ui";
4
+
5
+ // src/lib/utils.ts
6
+ import { clsx } from "clsx";
7
+ import { twMerge } from "tailwind-merge";
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+
12
+ // src/components/ui/button.tsx
13
+ import { jsx } from "react/jsx-runtime";
14
+ var buttonVariants = cva(
15
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
16
+ {
17
+ variants: {
18
+ variant: {
19
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
20
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
21
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
22
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
23
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
24
+ link: "text-primary underline-offset-4 hover:underline"
25
+ },
26
+ size: {
27
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
28
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
29
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
30
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
31
+ icon: "size-9",
32
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
33
+ "icon-sm": "size-8",
34
+ "icon-lg": "size-10"
35
+ }
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default"
40
+ }
41
+ }
42
+ );
43
+ function Button({
44
+ className,
45
+ variant = "default",
46
+ size = "default",
47
+ asChild = false,
48
+ ...props
49
+ }) {
50
+ const Comp = asChild ? Slot.Root : "button";
51
+ return /* @__PURE__ */ jsx(
52
+ Comp,
53
+ {
54
+ "data-slot": "button",
55
+ "data-variant": variant,
56
+ "data-size": size,
57
+ className: cn(buttonVariants({ variant, size, className })),
58
+ ...props
59
+ }
60
+ );
61
+ }
62
+
63
+ // src/components/ui/hover-card.tsx
64
+ import { HoverCard as HoverCardPrimitive } from "radix-ui";
65
+ import { jsx as jsx2 } from "react/jsx-runtime";
66
+
67
+ // src/components/ai-elements/attachments.tsx
68
+ import {
69
+ FileTextIcon,
70
+ GlobeIcon,
71
+ ImageIcon,
72
+ Music2Icon,
73
+ PaperclipIcon,
74
+ VideoIcon,
75
+ XIcon
76
+ } from "lucide-react";
77
+ import { createContext, useContext, useMemo } from "react";
78
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
79
+ var getMediaCategory = (data) => {
80
+ if (data.type === "source-document") {
81
+ return "source";
82
+ }
83
+ const mediaType = data.mediaType ?? "";
84
+ if (mediaType.startsWith("image/")) {
85
+ return "image";
86
+ }
87
+ if (mediaType.startsWith("video/")) {
88
+ return "video";
89
+ }
90
+ if (mediaType.startsWith("audio/")) {
91
+ return "audio";
92
+ }
93
+ if (mediaType.startsWith("application/") || mediaType.startsWith("text/")) {
94
+ return "document";
95
+ }
96
+ return "unknown";
97
+ };
98
+ var AttachmentsContext = createContext(null);
99
+ var AttachmentContext = createContext(null);
100
+ var useAttachmentsContext = () => useContext(AttachmentsContext) ?? { variant: "grid" };
101
+ var useAttachmentContext = () => {
102
+ const ctx = useContext(AttachmentContext);
103
+ if (!ctx) {
104
+ throw new Error("Attachment components must be used within <Attachment>");
105
+ }
106
+ return ctx;
107
+ };
108
+ var Attachments = ({
109
+ variant = "grid",
110
+ className,
111
+ children,
112
+ ...props
113
+ }) => {
114
+ const contextValue = useMemo(() => ({ variant }), [variant]);
115
+ return /* @__PURE__ */ jsx3(AttachmentsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx3(
116
+ "div",
117
+ {
118
+ className: cn(
119
+ "flex items-start",
120
+ variant === "list" ? "flex-col gap-2" : "flex-wrap gap-2",
121
+ variant === "grid" && "ml-auto w-fit",
122
+ className
123
+ ),
124
+ ...props,
125
+ children
126
+ }
127
+ ) });
128
+ };
129
+ var Attachment = ({
130
+ data,
131
+ onRemove,
132
+ className,
133
+ children,
134
+ ...props
135
+ }) => {
136
+ const { variant } = useAttachmentsContext();
137
+ const mediaCategory = getMediaCategory(data);
138
+ const contextValue = useMemo(
139
+ () => ({ data, mediaCategory, onRemove, variant }),
140
+ [data, mediaCategory, onRemove, variant]
141
+ );
142
+ return /* @__PURE__ */ jsx3(AttachmentContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx3(
143
+ "div",
144
+ {
145
+ className: cn(
146
+ "group relative",
147
+ variant === "grid" && "size-24 overflow-hidden rounded-lg",
148
+ variant === "inline" && [
149
+ "flex h-8 cursor-pointer select-none items-center gap-1.5",
150
+ "rounded-md border border-border px-1.5",
151
+ "font-medium text-sm transition-all",
152
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50"
153
+ ],
154
+ variant === "list" && [
155
+ "flex w-full items-center gap-3 rounded-lg border p-3",
156
+ "hover:bg-accent/50"
157
+ ],
158
+ className
159
+ ),
160
+ ...props,
161
+ children
162
+ }
163
+ ) });
164
+ };
165
+ var AttachmentPreview = ({
166
+ fallbackIcon,
167
+ className,
168
+ ...props
169
+ }) => {
170
+ const { data, mediaCategory, variant } = useAttachmentContext();
171
+ const iconSize = variant === "inline" ? "size-3" : "size-4";
172
+ const renderImage = (url, filename, isGrid) => isGrid ? /* @__PURE__ */ jsx3(
173
+ "img",
174
+ {
175
+ alt: filename || "Image",
176
+ className: "size-full object-cover",
177
+ height: 96,
178
+ src: url,
179
+ width: 96
180
+ }
181
+ ) : /* @__PURE__ */ jsx3(
182
+ "img",
183
+ {
184
+ alt: filename || "Image",
185
+ className: "size-full rounded object-cover",
186
+ height: 20,
187
+ src: url,
188
+ width: 20
189
+ }
190
+ );
191
+ const renderIcon = (Icon) => /* @__PURE__ */ jsx3(Icon, { className: cn(iconSize, "text-muted-foreground") });
192
+ const renderContent = () => {
193
+ if (mediaCategory === "image" && data.type === "file" && data.url) {
194
+ return renderImage(data.url, data.filename, variant === "grid");
195
+ }
196
+ if (mediaCategory === "video" && data.type === "file" && data.url) {
197
+ return /* @__PURE__ */ jsx3("video", { className: "size-full object-cover", muted: true, src: data.url });
198
+ }
199
+ const iconMap = {
200
+ image: ImageIcon,
201
+ video: VideoIcon,
202
+ audio: Music2Icon,
203
+ source: GlobeIcon,
204
+ document: FileTextIcon,
205
+ unknown: PaperclipIcon
206
+ };
207
+ const Icon = iconMap[mediaCategory];
208
+ return fallbackIcon ?? renderIcon(Icon);
209
+ };
210
+ return /* @__PURE__ */ jsx3(
211
+ "div",
212
+ {
213
+ className: cn(
214
+ "flex shrink-0 items-center justify-center overflow-hidden",
215
+ variant === "grid" && "size-full bg-muted",
216
+ variant === "inline" && "size-5 rounded bg-background",
217
+ variant === "list" && "size-12 rounded bg-muted",
218
+ className
219
+ ),
220
+ ...props,
221
+ children: renderContent()
222
+ }
223
+ );
224
+ };
225
+ var AttachmentRemove = ({
226
+ label = "Remove",
227
+ className,
228
+ children,
229
+ ...props
230
+ }) => {
231
+ const { onRemove, variant } = useAttachmentContext();
232
+ if (!onRemove) {
233
+ return null;
234
+ }
235
+ return /* @__PURE__ */ jsxs(
236
+ Button,
237
+ {
238
+ "aria-label": label,
239
+ className: cn(
240
+ variant === "grid" && [
241
+ "absolute top-2 right-2 size-6 rounded-full p-0",
242
+ "bg-background/80 backdrop-blur-sm",
243
+ "opacity-0 transition-opacity group-hover:opacity-100",
244
+ "hover:bg-background",
245
+ "[&>svg]:size-3"
246
+ ],
247
+ variant === "inline" && [
248
+ "size-5 rounded p-0",
249
+ "opacity-0 transition-opacity group-hover:opacity-100",
250
+ "[&>svg]:size-2.5"
251
+ ],
252
+ variant === "list" && ["size-8 shrink-0 rounded p-0", "[&>svg]:size-4"],
253
+ className
254
+ ),
255
+ onClick: (e) => {
256
+ e.stopPropagation();
257
+ onRemove();
258
+ },
259
+ type: "button",
260
+ variant: "ghost",
261
+ ...props,
262
+ children: [
263
+ children ?? /* @__PURE__ */ jsx3(XIcon, {}),
264
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: label })
265
+ ]
266
+ }
267
+ );
268
+ };
269
+
270
+ // src/components/ui/command.tsx
271
+ import { Command as CommandPrimitive } from "cmdk";
272
+ import { SearchIcon } from "lucide-react";
273
+
274
+ // src/components/ui/dialog.tsx
275
+ import { XIcon as XIcon2 } from "lucide-react";
276
+ import { Dialog as DialogPrimitive } from "radix-ui";
277
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
278
+
279
+ // src/components/ui/command.tsx
280
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
281
+
282
+ // src/components/ui/dropdown-menu.tsx
283
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
284
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
285
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
286
+
287
+ // src/components/ui/input-group.tsx
288
+ import { cva as cva2 } from "class-variance-authority";
289
+
290
+ // src/components/ui/input.tsx
291
+ import { jsx as jsx7 } from "react/jsx-runtime";
292
+
293
+ // src/components/ui/textarea.tsx
294
+ import { jsx as jsx8 } from "react/jsx-runtime";
295
+ function Textarea({ className, ...props }) {
296
+ return /* @__PURE__ */ jsx8(
297
+ "textarea",
298
+ {
299
+ "data-slot": "textarea",
300
+ className: cn(
301
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
302
+ className
303
+ ),
304
+ ...props
305
+ }
306
+ );
307
+ }
308
+
309
+ // src/components/ui/input-group.tsx
310
+ import { jsx as jsx9 } from "react/jsx-runtime";
311
+ function InputGroup({ className, ...props }) {
312
+ return /* @__PURE__ */ jsx9(
313
+ "div",
314
+ {
315
+ "data-slot": "input-group",
316
+ role: "group",
317
+ className: cn(
318
+ "group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
319
+ "h-9 min-w-0 has-[>textarea]:h-auto",
320
+ // Variants based on alignment.
321
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
322
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
323
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
324
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
325
+ // Focus state.
326
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
327
+ // Error state.
328
+ "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
329
+ className
330
+ ),
331
+ ...props
332
+ }
333
+ );
334
+ }
335
+ var inputGroupAddonVariants = cva2(
336
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
337
+ {
338
+ variants: {
339
+ align: {
340
+ "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
341
+ "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
342
+ "block-start": "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
343
+ "block-end": "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5"
344
+ }
345
+ },
346
+ defaultVariants: {
347
+ align: "inline-start"
348
+ }
349
+ }
350
+ );
351
+ function InputGroupAddon({
352
+ className,
353
+ align = "inline-start",
354
+ ...props
355
+ }) {
356
+ return /* @__PURE__ */ jsx9(
357
+ "div",
358
+ {
359
+ role: "group",
360
+ "data-slot": "input-group-addon",
361
+ "data-align": align,
362
+ className: cn(inputGroupAddonVariants({ align }), className),
363
+ onClick: (e) => {
364
+ if (e.target.closest("button")) {
365
+ return;
366
+ }
367
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
368
+ },
369
+ ...props
370
+ }
371
+ );
372
+ }
373
+ var inputGroupButtonVariants = cva2(
374
+ "text-sm shadow-none flex gap-2 items-center",
375
+ {
376
+ variants: {
377
+ size: {
378
+ xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
379
+ sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
380
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
381
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0"
382
+ }
383
+ },
384
+ defaultVariants: {
385
+ size: "xs"
386
+ }
387
+ }
388
+ );
389
+ function InputGroupButton({
390
+ className,
391
+ type = "button",
392
+ variant = "ghost",
393
+ size = "xs",
394
+ ...props
395
+ }) {
396
+ return /* @__PURE__ */ jsx9(
397
+ Button,
398
+ {
399
+ type,
400
+ "data-size": size,
401
+ variant,
402
+ className: cn(inputGroupButtonVariants({ size }), className),
403
+ ...props
404
+ }
405
+ );
406
+ }
407
+ function InputGroupTextarea({
408
+ className,
409
+ ...props
410
+ }) {
411
+ return /* @__PURE__ */ jsx9(
412
+ Textarea,
413
+ {
414
+ "data-slot": "input-group-control",
415
+ className: cn(
416
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
417
+ className
418
+ ),
419
+ ...props
420
+ }
421
+ );
422
+ }
423
+
424
+ // src/components/ui/select.tsx
425
+ import { CheckIcon as CheckIcon2, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
426
+ import { Select as SelectPrimitive } from "radix-ui";
427
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
428
+
429
+ // src/components/ui/spinner.tsx
430
+ import { Loader2Icon } from "lucide-react";
431
+ import { jsx as jsx11 } from "react/jsx-runtime";
432
+ function Spinner({ className, ...props }) {
433
+ return /* @__PURE__ */ jsx11(
434
+ Loader2Icon,
435
+ {
436
+ role: "status",
437
+ "aria-label": "Loading",
438
+ className: cn("size-4 animate-spin", className),
439
+ ...props
440
+ }
441
+ );
442
+ }
443
+
444
+ // src/components/ai-elements/prompt-input.tsx
445
+ import {
446
+ CornerDownLeftIcon,
447
+ ImageIcon as ImageIcon2,
448
+ PlusIcon,
449
+ SquareIcon,
450
+ XIcon as XIcon3
451
+ } from "lucide-react";
452
+ import { nanoid } from "nanoid";
453
+ import {
454
+ Children,
455
+ createContext as createContext2,
456
+ useCallback,
457
+ useContext as useContext2,
458
+ useEffect,
459
+ useMemo as useMemo2,
460
+ useRef,
461
+ useState
462
+ } from "react";
463
+ import { Fragment, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
464
+ var PromptInputController = createContext2(
465
+ null
466
+ );
467
+ var ProviderAttachmentsContext = createContext2(
468
+ null
469
+ );
470
+ var useOptionalPromptInputController = () => useContext2(PromptInputController);
471
+ var useOptionalProviderAttachments = () => useContext2(ProviderAttachmentsContext);
472
+ function PromptInputProvider({
473
+ initialInput: initialTextInput = "",
474
+ children
475
+ }) {
476
+ const [textInput, setTextInput] = useState(initialTextInput);
477
+ const clearInput = useCallback(() => setTextInput(""), []);
478
+ const [attachmentFiles, setAttachmentFiles] = useState([]);
479
+ const fileInputRef = useRef(null);
480
+ const openRef = useRef(() => void 0);
481
+ const add = useCallback((files) => {
482
+ const incoming = Array.from(files);
483
+ if (incoming.length === 0) {
484
+ return;
485
+ }
486
+ setAttachmentFiles(
487
+ (prev) => prev.concat(
488
+ incoming.map((file) => ({
489
+ id: nanoid(),
490
+ type: "file",
491
+ url: URL.createObjectURL(file),
492
+ mediaType: file.type,
493
+ filename: file.name
494
+ }))
495
+ )
496
+ );
497
+ }, []);
498
+ const remove = useCallback((id) => {
499
+ setAttachmentFiles((prev) => {
500
+ const found = prev.find((f) => f.id === id);
501
+ if (found?.url) {
502
+ URL.revokeObjectURL(found.url);
503
+ }
504
+ return prev.filter((f) => f.id !== id);
505
+ });
506
+ }, []);
507
+ const clear = useCallback(() => {
508
+ setAttachmentFiles((prev) => {
509
+ for (const f of prev) {
510
+ if (f.url) {
511
+ URL.revokeObjectURL(f.url);
512
+ }
513
+ }
514
+ return [];
515
+ });
516
+ }, []);
517
+ const attachmentsRef = useRef(attachmentFiles);
518
+ attachmentsRef.current = attachmentFiles;
519
+ useEffect(
520
+ () => () => {
521
+ for (const f of attachmentsRef.current) {
522
+ if (f.url) {
523
+ URL.revokeObjectURL(f.url);
524
+ }
525
+ }
526
+ },
527
+ []
528
+ );
529
+ const openFileDialog = useCallback(() => {
530
+ openRef.current?.();
531
+ }, []);
532
+ const attachments = useMemo2(
533
+ () => ({
534
+ files: attachmentFiles,
535
+ add,
536
+ remove,
537
+ clear,
538
+ openFileDialog,
539
+ fileInputRef
540
+ }),
541
+ [attachmentFiles, add, remove, clear, openFileDialog]
542
+ );
543
+ const __registerFileInput = useCallback(
544
+ (ref, open) => {
545
+ fileInputRef.current = ref.current;
546
+ openRef.current = open;
547
+ },
548
+ []
549
+ );
550
+ const controller = useMemo2(
551
+ () => ({
552
+ textInput: {
553
+ value: textInput,
554
+ setInput: setTextInput,
555
+ clear: clearInput
556
+ },
557
+ attachments,
558
+ __registerFileInput
559
+ }),
560
+ [textInput, clearInput, attachments, __registerFileInput]
561
+ );
562
+ return /* @__PURE__ */ jsx12(PromptInputController.Provider, { value: controller, children: /* @__PURE__ */ jsx12(ProviderAttachmentsContext.Provider, { value: attachments, children }) });
563
+ }
564
+ var LocalAttachmentsContext = createContext2(null);
565
+ var usePromptInputAttachments = () => {
566
+ const provider = useOptionalProviderAttachments();
567
+ const local = useContext2(LocalAttachmentsContext);
568
+ const context = local ?? provider;
569
+ if (!context) {
570
+ throw new Error(
571
+ "usePromptInputAttachments must be used within a PromptInput or PromptInputProvider"
572
+ );
573
+ }
574
+ return context;
575
+ };
576
+ var LocalReferencedSourcesContext = createContext2(null);
577
+ var PromptInput = ({
578
+ className,
579
+ accept,
580
+ multiple,
581
+ globalDrop,
582
+ syncHiddenInput,
583
+ maxFiles,
584
+ maxFileSize,
585
+ onError,
586
+ onSubmit,
587
+ children,
588
+ ...props
589
+ }) => {
590
+ const controller = useOptionalPromptInputController();
591
+ const usingProvider = !!controller;
592
+ const inputRef = useRef(null);
593
+ const formRef = useRef(null);
594
+ const [items, setItems] = useState([]);
595
+ const files = usingProvider ? controller.attachments.files : items;
596
+ const [referencedSources, setReferencedSources] = useState([]);
597
+ const filesRef = useRef(files);
598
+ filesRef.current = files;
599
+ const openFileDialogLocal = useCallback(() => {
600
+ inputRef.current?.click();
601
+ }, []);
602
+ const matchesAccept = useCallback(
603
+ (f) => {
604
+ if (!accept || accept.trim() === "") {
605
+ return true;
606
+ }
607
+ const patterns = accept.split(",").map((s) => s.trim()).filter(Boolean);
608
+ return patterns.some((pattern) => {
609
+ if (pattern.endsWith("/*")) {
610
+ const prefix = pattern.slice(0, -1);
611
+ return f.type.startsWith(prefix);
612
+ }
613
+ return f.type === pattern;
614
+ });
615
+ },
616
+ [accept]
617
+ );
618
+ const addLocal = useCallback(
619
+ (fileList) => {
620
+ const incoming = Array.from(fileList);
621
+ const accepted = incoming.filter((f) => matchesAccept(f));
622
+ if (incoming.length && accepted.length === 0) {
623
+ onError?.({
624
+ code: "accept",
625
+ message: "No files match the accepted types."
626
+ });
627
+ return;
628
+ }
629
+ const withinSize = (f) => maxFileSize ? f.size <= maxFileSize : true;
630
+ const sized = accepted.filter(withinSize);
631
+ if (accepted.length > 0 && sized.length === 0) {
632
+ onError?.({
633
+ code: "max_file_size",
634
+ message: "All files exceed the maximum size."
635
+ });
636
+ return;
637
+ }
638
+ setItems((prev) => {
639
+ const capacity = typeof maxFiles === "number" ? Math.max(0, maxFiles - prev.length) : void 0;
640
+ const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;
641
+ if (typeof capacity === "number" && sized.length > capacity) {
642
+ onError?.({
643
+ code: "max_files",
644
+ message: "Too many files. Some were not added."
645
+ });
646
+ }
647
+ const next = [];
648
+ for (const file of capped) {
649
+ next.push({
650
+ id: nanoid(),
651
+ type: "file",
652
+ url: URL.createObjectURL(file),
653
+ mediaType: file.type,
654
+ filename: file.name
655
+ });
656
+ }
657
+ return prev.concat(next);
658
+ });
659
+ },
660
+ [matchesAccept, maxFiles, maxFileSize, onError]
661
+ );
662
+ const removeLocal = useCallback(
663
+ (id) => setItems((prev) => {
664
+ const found = prev.find((file) => file.id === id);
665
+ if (found?.url) {
666
+ URL.revokeObjectURL(found.url);
667
+ }
668
+ return prev.filter((file) => file.id !== id);
669
+ }),
670
+ []
671
+ );
672
+ const addWithProviderValidation = useCallback(
673
+ (fileList) => {
674
+ const incoming = Array.from(fileList);
675
+ const accepted = incoming.filter((f) => matchesAccept(f));
676
+ if (incoming.length && accepted.length === 0) {
677
+ onError?.({
678
+ code: "accept",
679
+ message: "No files match the accepted types."
680
+ });
681
+ return;
682
+ }
683
+ const withinSize = (f) => maxFileSize ? f.size <= maxFileSize : true;
684
+ const sized = accepted.filter(withinSize);
685
+ if (accepted.length > 0 && sized.length === 0) {
686
+ onError?.({
687
+ code: "max_file_size",
688
+ message: "All files exceed the maximum size."
689
+ });
690
+ return;
691
+ }
692
+ const currentCount = files.length;
693
+ const capacity = typeof maxFiles === "number" ? Math.max(0, maxFiles - currentCount) : void 0;
694
+ const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;
695
+ if (typeof capacity === "number" && sized.length > capacity) {
696
+ onError?.({
697
+ code: "max_files",
698
+ message: "Too many files. Some were not added."
699
+ });
700
+ }
701
+ if (capped.length > 0) {
702
+ controller?.attachments.add(capped);
703
+ }
704
+ },
705
+ [matchesAccept, maxFileSize, maxFiles, onError, files.length, controller]
706
+ );
707
+ const clearAttachments = useCallback(
708
+ () => usingProvider ? controller?.attachments.clear() : setItems((prev) => {
709
+ for (const file of prev) {
710
+ if (file.url) {
711
+ URL.revokeObjectURL(file.url);
712
+ }
713
+ }
714
+ return [];
715
+ }),
716
+ [usingProvider, controller]
717
+ );
718
+ const clearReferencedSources = useCallback(
719
+ () => setReferencedSources([]),
720
+ []
721
+ );
722
+ const add = usingProvider ? addWithProviderValidation : addLocal;
723
+ const remove = usingProvider ? controller.attachments.remove : removeLocal;
724
+ const openFileDialog = usingProvider ? controller.attachments.openFileDialog : openFileDialogLocal;
725
+ const clear = useCallback(() => {
726
+ clearAttachments();
727
+ clearReferencedSources();
728
+ }, [clearAttachments, clearReferencedSources]);
729
+ useEffect(() => {
730
+ if (!usingProvider) {
731
+ return;
732
+ }
733
+ controller.__registerFileInput(inputRef, () => inputRef.current?.click());
734
+ }, [usingProvider, controller]);
735
+ useEffect(() => {
736
+ if (syncHiddenInput && inputRef.current && files.length === 0) {
737
+ inputRef.current.value = "";
738
+ }
739
+ }, [files, syncHiddenInput]);
740
+ useEffect(() => {
741
+ const form = formRef.current;
742
+ if (!form) {
743
+ return;
744
+ }
745
+ if (globalDrop) {
746
+ return;
747
+ }
748
+ const onDragOver = (e) => {
749
+ if (e.dataTransfer?.types?.includes("Files")) {
750
+ e.preventDefault();
751
+ }
752
+ };
753
+ const onDrop = (e) => {
754
+ if (e.dataTransfer?.types?.includes("Files")) {
755
+ e.preventDefault();
756
+ }
757
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
758
+ add(e.dataTransfer.files);
759
+ }
760
+ };
761
+ form.addEventListener("dragover", onDragOver);
762
+ form.addEventListener("drop", onDrop);
763
+ return () => {
764
+ form.removeEventListener("dragover", onDragOver);
765
+ form.removeEventListener("drop", onDrop);
766
+ };
767
+ }, [add, globalDrop]);
768
+ useEffect(() => {
769
+ if (!globalDrop) {
770
+ return;
771
+ }
772
+ const onDragOver = (e) => {
773
+ if (e.dataTransfer?.types?.includes("Files")) {
774
+ e.preventDefault();
775
+ }
776
+ };
777
+ const onDrop = (e) => {
778
+ if (e.dataTransfer?.types?.includes("Files")) {
779
+ e.preventDefault();
780
+ }
781
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
782
+ add(e.dataTransfer.files);
783
+ }
784
+ };
785
+ document.addEventListener("dragover", onDragOver);
786
+ document.addEventListener("drop", onDrop);
787
+ return () => {
788
+ document.removeEventListener("dragover", onDragOver);
789
+ document.removeEventListener("drop", onDrop);
790
+ };
791
+ }, [add, globalDrop]);
792
+ useEffect(
793
+ () => () => {
794
+ if (!usingProvider) {
795
+ for (const f of filesRef.current) {
796
+ if (f.url) {
797
+ URL.revokeObjectURL(f.url);
798
+ }
799
+ }
800
+ }
801
+ },
802
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- cleanup only on unmount; filesRef always current
803
+ [usingProvider]
804
+ );
805
+ const handleChange = (event) => {
806
+ if (event.currentTarget.files) {
807
+ add(event.currentTarget.files);
808
+ }
809
+ event.currentTarget.value = "";
810
+ };
811
+ const convertBlobUrlToDataUrl = async (url) => {
812
+ try {
813
+ const response = await fetch(url);
814
+ const blob = await response.blob();
815
+ return new Promise((resolve) => {
816
+ const reader = new FileReader();
817
+ reader.onloadend = () => resolve(reader.result);
818
+ reader.onerror = () => resolve(null);
819
+ reader.readAsDataURL(blob);
820
+ });
821
+ } catch {
822
+ return null;
823
+ }
824
+ };
825
+ const attachmentsCtx = useMemo2(
826
+ () => ({
827
+ files: files.map((item) => ({ ...item, id: item.id })),
828
+ add,
829
+ remove,
830
+ clear: clearAttachments,
831
+ openFileDialog,
832
+ fileInputRef: inputRef
833
+ }),
834
+ [files, add, remove, clearAttachments, openFileDialog]
835
+ );
836
+ const refsCtx = useMemo2(
837
+ () => ({
838
+ sources: referencedSources,
839
+ add: (incoming) => {
840
+ const array = Array.isArray(incoming) ? incoming : [incoming];
841
+ setReferencedSources(
842
+ (prev) => prev.concat(array.map((s) => ({ ...s, id: nanoid() })))
843
+ );
844
+ },
845
+ remove: (id) => {
846
+ setReferencedSources((prev) => prev.filter((s) => s.id !== id));
847
+ },
848
+ clear: clearReferencedSources
849
+ }),
850
+ [referencedSources, clearReferencedSources]
851
+ );
852
+ const handleSubmit = (event) => {
853
+ event.preventDefault();
854
+ const form = event.currentTarget;
855
+ const text = usingProvider ? controller.textInput.value : (() => {
856
+ const formData = new FormData(form);
857
+ return formData.get("message") || "";
858
+ })();
859
+ if (!usingProvider) {
860
+ form.reset();
861
+ }
862
+ Promise.all(
863
+ files.map(async ({ id, ...item }) => {
864
+ if (item.url?.startsWith("blob:")) {
865
+ const dataUrl = await convertBlobUrlToDataUrl(item.url);
866
+ return {
867
+ ...item,
868
+ url: dataUrl ?? item.url
869
+ };
870
+ }
871
+ return item;
872
+ })
873
+ ).then((convertedFiles) => {
874
+ try {
875
+ const result = onSubmit({ text, files: convertedFiles }, event);
876
+ if (result instanceof Promise) {
877
+ result.then(() => {
878
+ clear();
879
+ if (usingProvider) {
880
+ controller.textInput.clear();
881
+ }
882
+ }).catch(() => {
883
+ });
884
+ } else {
885
+ clear();
886
+ if (usingProvider) {
887
+ controller.textInput.clear();
888
+ }
889
+ }
890
+ } catch {
891
+ }
892
+ }).catch(() => {
893
+ });
894
+ };
895
+ const inner = /* @__PURE__ */ jsxs6(Fragment, { children: [
896
+ /* @__PURE__ */ jsx12(
897
+ "input",
898
+ {
899
+ accept,
900
+ "aria-label": "Upload files",
901
+ className: "hidden",
902
+ multiple,
903
+ onChange: handleChange,
904
+ ref: inputRef,
905
+ title: "Upload files",
906
+ type: "file"
907
+ }
908
+ ),
909
+ /* @__PURE__ */ jsx12(
910
+ "form",
911
+ {
912
+ className: cn("w-full", className),
913
+ onSubmit: handleSubmit,
914
+ ref: formRef,
915
+ ...props,
916
+ children: /* @__PURE__ */ jsx12(InputGroup, { className: "overflow-hidden", children })
917
+ }
918
+ )
919
+ ] });
920
+ const withReferencedSources = /* @__PURE__ */ jsx12(LocalReferencedSourcesContext.Provider, { value: refsCtx, children: inner });
921
+ return /* @__PURE__ */ jsx12(LocalAttachmentsContext.Provider, { value: attachmentsCtx, children: withReferencedSources });
922
+ };
923
+ var PromptInputBody = ({
924
+ className,
925
+ ...props
926
+ }) => /* @__PURE__ */ jsx12("div", { className: cn("contents", className), ...props });
927
+ var PromptInputTextarea = ({
928
+ onChange,
929
+ onKeyDown,
930
+ className,
931
+ placeholder = "What would you like to know?",
932
+ ...props
933
+ }) => {
934
+ const controller = useOptionalPromptInputController();
935
+ const attachments = usePromptInputAttachments();
936
+ const [isComposing, setIsComposing] = useState(false);
937
+ const handleKeyDown = (e) => {
938
+ onKeyDown?.(e);
939
+ if (e.defaultPrevented) {
940
+ return;
941
+ }
942
+ if (e.key === "Enter") {
943
+ if (isComposing || e.nativeEvent.isComposing) {
944
+ return;
945
+ }
946
+ if (e.shiftKey) {
947
+ return;
948
+ }
949
+ e.preventDefault();
950
+ const form = e.currentTarget.form;
951
+ const submitButton = form?.querySelector(
952
+ 'button[type="submit"]'
953
+ );
954
+ if (submitButton?.disabled) {
955
+ return;
956
+ }
957
+ form?.requestSubmit();
958
+ }
959
+ if (e.key === "Backspace" && e.currentTarget.value === "" && attachments.files.length > 0) {
960
+ e.preventDefault();
961
+ const lastAttachment = attachments.files[attachments.files.length - 1];
962
+ if (lastAttachment) {
963
+ attachments.remove(lastAttachment.id);
964
+ }
965
+ }
966
+ };
967
+ const handlePaste = (event) => {
968
+ const items = event.clipboardData?.items;
969
+ if (!items) {
970
+ return;
971
+ }
972
+ const files = [];
973
+ for (const item of items) {
974
+ if (item.kind === "file") {
975
+ const file = item.getAsFile();
976
+ if (file) {
977
+ files.push(file);
978
+ }
979
+ }
980
+ }
981
+ if (files.length > 0) {
982
+ event.preventDefault();
983
+ attachments.add(files);
984
+ }
985
+ };
986
+ const controlledProps = controller ? {
987
+ value: controller.textInput.value,
988
+ onChange: (e) => {
989
+ controller.textInput.setInput(e.currentTarget.value);
990
+ onChange?.(e);
991
+ }
992
+ } : {
993
+ onChange
994
+ };
995
+ return /* @__PURE__ */ jsx12(
996
+ InputGroupTextarea,
997
+ {
998
+ className: cn("field-sizing-content max-h-48 min-h-16", className),
999
+ name: "message",
1000
+ onCompositionEnd: () => setIsComposing(false),
1001
+ onCompositionStart: () => setIsComposing(true),
1002
+ onKeyDown: handleKeyDown,
1003
+ onPaste: handlePaste,
1004
+ placeholder,
1005
+ ...props,
1006
+ ...controlledProps
1007
+ }
1008
+ );
1009
+ };
1010
+ var PromptInputFooter = ({
1011
+ className,
1012
+ ...props
1013
+ }) => /* @__PURE__ */ jsx12(
1014
+ InputGroupAddon,
1015
+ {
1016
+ align: "block-end",
1017
+ className: cn("justify-between gap-1", className),
1018
+ ...props
1019
+ }
1020
+ );
1021
+ var PromptInputTools = ({
1022
+ className,
1023
+ ...props
1024
+ }) => /* @__PURE__ */ jsx12("div", { className: cn("flex items-center gap-1", className), ...props });
1025
+ var PromptInputSubmit = ({
1026
+ className,
1027
+ variant = "default",
1028
+ size = "icon-sm",
1029
+ status,
1030
+ onStop,
1031
+ onClick,
1032
+ children,
1033
+ ...props
1034
+ }) => {
1035
+ const isGenerating = status === "submitted" || status === "streaming";
1036
+ let Icon = /* @__PURE__ */ jsx12(CornerDownLeftIcon, { className: "size-4" });
1037
+ if (status === "submitted") {
1038
+ Icon = /* @__PURE__ */ jsx12(Spinner, {});
1039
+ } else if (status === "streaming") {
1040
+ Icon = /* @__PURE__ */ jsx12(SquareIcon, { className: "size-4" });
1041
+ } else if (status === "error") {
1042
+ Icon = /* @__PURE__ */ jsx12(XIcon3, { className: "size-4" });
1043
+ }
1044
+ const handleClick = (e) => {
1045
+ if (isGenerating && onStop) {
1046
+ e.preventDefault();
1047
+ onStop();
1048
+ return;
1049
+ }
1050
+ onClick?.(e);
1051
+ };
1052
+ return /* @__PURE__ */ jsx12(
1053
+ InputGroupButton,
1054
+ {
1055
+ "aria-label": isGenerating ? "Stop" : "Submit",
1056
+ className: cn(className),
1057
+ onClick: handleClick,
1058
+ size,
1059
+ type: isGenerating && onStop ? "button" : "submit",
1060
+ variant,
1061
+ ...props,
1062
+ children: children ?? Icon
1063
+ }
1064
+ );
1065
+ };
1066
+
1067
+ // src/locales/context.ts
1068
+ import { createContext as createContext3 } from "react";
1069
+ var LocaleContext = createContext3({
1070
+ locale: "zh-CN"
1071
+ });
1072
+
1073
+ // src/locales/hooks.ts
1074
+ import { useContext as useContext3, useMemo as useMemo3 } from "react";
1075
+
1076
+ // src/locales/langs/zh-cn.ts
1077
+ var zhCN = {
1078
+ chat: {
1079
+ emptyTitle: "\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u5230\u4F60\uFF1F"
1080
+ },
1081
+ input: {
1082
+ placeholder: "\u8BF7\u8F93\u5165\u6D88\u606F...",
1083
+ disclaimer: "\u5185\u5BB9\u7531 AI \u751F\u6210\uFF0C\u4EC5\u4F9B\u53C2\u8003"
1084
+ },
1085
+ message: {
1086
+ copy: "\u590D\u5236",
1087
+ copied: "\u5DF2\u590D\u5236",
1088
+ copyToClipboard: "\u590D\u5236",
1089
+ paused: "\u5DF2\u6682\u505C\u751F\u6210"
1090
+ }
1091
+ };
1092
+
1093
+ // src/locales/langs/en.ts
1094
+ var en = {
1095
+ chat: {
1096
+ emptyTitle: "How can I help you?"
1097
+ },
1098
+ input: {
1099
+ placeholder: "Type your message...",
1100
+ disclaimer: "AI-generated content, for reference only"
1101
+ },
1102
+ message: {
1103
+ copy: "Copy",
1104
+ copied: "Copied!",
1105
+ copyToClipboard: "Copy to clipboard",
1106
+ paused: "Generation paused"
1107
+ }
1108
+ };
1109
+
1110
+ // src/locales/langs/index.ts
1111
+ var locales = {
1112
+ "zh-CN": zhCN,
1113
+ "en-US": en
1114
+ };
1115
+
1116
+ // src/locales/hooks.ts
1117
+ function useLocale(name) {
1118
+ const { locale: localeCode } = useContext3(LocaleContext);
1119
+ const locale = useMemo3(() => {
1120
+ const fullLocale = locales[localeCode] ?? locales["zh-CN"];
1121
+ return name ? fullLocale[name] : fullLocale;
1122
+ }, [localeCode, name]);
1123
+ return { locale, localeCode };
1124
+ }
1125
+
1126
+ // src/components/chat/Input.tsx
1127
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1128
+ var PromptInputAttachmentsDisplay = () => {
1129
+ const attachments = usePromptInputAttachments();
1130
+ if (attachments.files.length === 0) {
1131
+ return null;
1132
+ }
1133
+ return /* @__PURE__ */ jsx13(Attachments, { variant: "inline", children: attachments.files.map((attachment) => /* @__PURE__ */ jsxs7(
1134
+ Attachment,
1135
+ {
1136
+ data: attachment,
1137
+ onRemove: () => attachments.remove(attachment.id),
1138
+ children: [
1139
+ /* @__PURE__ */ jsx13(AttachmentPreview, {}),
1140
+ /* @__PURE__ */ jsx13(AttachmentRemove, {})
1141
+ ]
1142
+ },
1143
+ attachment.id
1144
+ )) });
1145
+ };
1146
+ function Input2({
1147
+ onSend,
1148
+ onStop,
1149
+ placeholder,
1150
+ streaming = false
1151
+ }) {
1152
+ const { locale } = useLocale("input");
1153
+ const status = streaming ? "streaming" : "ready";
1154
+ const handleSubmit = (message) => {
1155
+ const text = message.text.trim();
1156
+ const hasText = Boolean(text);
1157
+ const hasAttachments = Boolean(message.files?.length);
1158
+ if (!(hasText || hasAttachments)) {
1159
+ return;
1160
+ }
1161
+ onSend?.({
1162
+ ...message,
1163
+ text
1164
+ });
1165
+ };
1166
+ return /* @__PURE__ */ jsx13("div", { className: "pb-4", children: /* @__PURE__ */ jsxs7(PromptInputProvider, { children: [
1167
+ /* @__PURE__ */ jsxs7(PromptInput, { globalDrop: true, multiple: true, onSubmit: handleSubmit, children: [
1168
+ /* @__PURE__ */ jsx13(PromptInputAttachmentsDisplay, {}),
1169
+ /* @__PURE__ */ jsx13(PromptInputBody, { children: /* @__PURE__ */ jsx13(
1170
+ PromptInputTextarea,
1171
+ {
1172
+ placeholder: placeholder ?? locale.placeholder
1173
+ }
1174
+ ) }),
1175
+ /* @__PURE__ */ jsxs7(PromptInputFooter, { children: [
1176
+ /* @__PURE__ */ jsx13(PromptInputTools, {}),
1177
+ /* @__PURE__ */ jsx13(PromptInputSubmit, { status, onStop })
1178
+ ] })
1179
+ ] }),
1180
+ /* @__PURE__ */ jsx13("div", { className: "text-center text-xs opacity-50 mt-3", children: locale.disclaimer })
1181
+ ] }) });
1182
+ }
1183
+
1184
+ // src/components/chat/Chat.tsx
1185
+ import { useChat } from "@cloudbase/agent-react-core";
1186
+
1187
+ // src/components/ui/button-group.tsx
1188
+ import { cva as cva3 } from "class-variance-authority";
1189
+ import { Slot as Slot2 } from "radix-ui";
1190
+
1191
+ // src/components/ui/separator.tsx
1192
+ import { Separator as SeparatorPrimitive } from "radix-ui";
1193
+ import { jsx as jsx14 } from "react/jsx-runtime";
1194
+
1195
+ // src/components/ui/button-group.tsx
1196
+ import { jsx as jsx15 } from "react/jsx-runtime";
1197
+ var buttonGroupVariants = cva3(
1198
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
1199
+ {
1200
+ variants: {
1201
+ orientation: {
1202
+ horizontal: "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
1203
+ vertical: "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none"
1204
+ }
1205
+ },
1206
+ defaultVariants: {
1207
+ orientation: "horizontal"
1208
+ }
1209
+ }
1210
+ );
1211
+
1212
+ // src/components/ui/tooltip.tsx
1213
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
1214
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
1215
+ function TooltipProvider({
1216
+ delayDuration = 0,
1217
+ ...props
1218
+ }) {
1219
+ return /* @__PURE__ */ jsx16(
1220
+ TooltipPrimitive.Provider,
1221
+ {
1222
+ "data-slot": "tooltip-provider",
1223
+ delayDuration,
1224
+ ...props
1225
+ }
1226
+ );
1227
+ }
1228
+ function Tooltip({
1229
+ ...props
1230
+ }) {
1231
+ return /* @__PURE__ */ jsx16(TooltipProvider, { children: /* @__PURE__ */ jsx16(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props }) });
1232
+ }
1233
+ function TooltipTrigger({
1234
+ ...props
1235
+ }) {
1236
+ return /* @__PURE__ */ jsx16(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
1237
+ }
1238
+ function TooltipContent({
1239
+ className,
1240
+ sideOffset = 0,
1241
+ children,
1242
+ ...props
1243
+ }) {
1244
+ return /* @__PURE__ */ jsx16(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs8(
1245
+ TooltipPrimitive.Content,
1246
+ {
1247
+ "data-slot": "tooltip-content",
1248
+ sideOffset,
1249
+ className: cn(
1250
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
1251
+ className
1252
+ ),
1253
+ ...props,
1254
+ children: [
1255
+ children,
1256
+ /* @__PURE__ */ jsx16(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
1257
+ ]
1258
+ }
1259
+ ) });
1260
+ }
1261
+
1262
+ // src/components/ai-elements/message.tsx
1263
+ import { cjk } from "@streamdown/cjk";
1264
+ import { code } from "@streamdown/code";
1265
+ import { math } from "@streamdown/math";
1266
+ import { mermaid } from "@streamdown/mermaid";
1267
+ import { ChevronLeftIcon, ChevronRightIcon as ChevronRightIcon2 } from "lucide-react";
1268
+ import { createContext as createContext4, memo, useContext as useContext4, useEffect as useEffect2, useState as useState2 } from "react";
1269
+ import { Streamdown } from "streamdown";
1270
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
1271
+ var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx17(
1272
+ "div",
1273
+ {
1274
+ className: cn(
1275
+ "group flex w-full max-w-[95%] flex-col gap-2",
1276
+ from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
1277
+ className
1278
+ ),
1279
+ ...props
1280
+ }
1281
+ );
1282
+ var MessageContent = ({
1283
+ children,
1284
+ className,
1285
+ ...props
1286
+ }) => /* @__PURE__ */ jsx17(
1287
+ "div",
1288
+ {
1289
+ className: cn(
1290
+ "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
1291
+ "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
1292
+ "group-[.is-assistant]:text-foreground",
1293
+ className
1294
+ ),
1295
+ ...props,
1296
+ children
1297
+ }
1298
+ );
1299
+ var MessageActions = ({
1300
+ className,
1301
+ children,
1302
+ ...props
1303
+ }) => /* @__PURE__ */ jsx17("div", { className: cn("flex items-center gap-1", className), ...props, children });
1304
+ var MessageAction = ({
1305
+ tooltip,
1306
+ children,
1307
+ label,
1308
+ variant = "ghost",
1309
+ size = "icon-sm",
1310
+ ...props
1311
+ }) => {
1312
+ const button = /* @__PURE__ */ jsxs9(Button, { size, type: "button", variant, ...props, children: [
1313
+ children,
1314
+ /* @__PURE__ */ jsx17("span", { className: "sr-only", children: label || tooltip })
1315
+ ] });
1316
+ if (tooltip) {
1317
+ return /* @__PURE__ */ jsx17(TooltipProvider, { children: /* @__PURE__ */ jsxs9(Tooltip, { children: [
1318
+ /* @__PURE__ */ jsx17(TooltipTrigger, { asChild: true, children: button }),
1319
+ /* @__PURE__ */ jsx17(TooltipContent, { children: /* @__PURE__ */ jsx17("p", { children: tooltip }) })
1320
+ ] }) });
1321
+ }
1322
+ return button;
1323
+ };
1324
+ var MessageBranchContext = createContext4(
1325
+ null
1326
+ );
1327
+ var MessageResponse = memo(
1328
+ ({ className, ...props }) => /* @__PURE__ */ jsx17(
1329
+ Streamdown,
1330
+ {
1331
+ className: cn(
1332
+ "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
1333
+ className
1334
+ ),
1335
+ plugins: { code, mermaid, math, cjk },
1336
+ ...props
1337
+ }
1338
+ ),
1339
+ (prevProps, nextProps) => prevProps.children === nextProps.children
1340
+ );
1341
+ MessageResponse.displayName = "MessageResponse";
1342
+
1343
+ // src/components/ui/badge.tsx
1344
+ import { cva as cva4 } from "class-variance-authority";
1345
+ import { Slot as Slot3 } from "radix-ui";
1346
+ import { jsx as jsx18 } from "react/jsx-runtime";
1347
+ var badgeVariants = cva4(
1348
+ "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
1349
+ {
1350
+ variants: {
1351
+ variant: {
1352
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
1353
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
1354
+ destructive: "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1355
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
1356
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
1357
+ link: "text-primary underline-offset-4 [a&]:hover:underline"
1358
+ }
1359
+ },
1360
+ defaultVariants: {
1361
+ variant: "default"
1362
+ }
1363
+ }
1364
+ );
1365
+ function Badge({
1366
+ className,
1367
+ variant = "default",
1368
+ asChild = false,
1369
+ ...props
1370
+ }) {
1371
+ const Comp = asChild ? Slot3.Root : "span";
1372
+ return /* @__PURE__ */ jsx18(
1373
+ Comp,
1374
+ {
1375
+ "data-slot": "badge",
1376
+ "data-variant": variant,
1377
+ className: cn(badgeVariants({ variant }), className),
1378
+ ...props
1379
+ }
1380
+ );
1381
+ }
1382
+
1383
+ // src/components/ui/collapsible.tsx
1384
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
1385
+ import { jsx as jsx19 } from "react/jsx-runtime";
1386
+ function Collapsible({
1387
+ ...props
1388
+ }) {
1389
+ return /* @__PURE__ */ jsx19(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
1390
+ }
1391
+ function CollapsibleTrigger({
1392
+ ...props
1393
+ }) {
1394
+ return /* @__PURE__ */ jsx19(
1395
+ CollapsiblePrimitive.CollapsibleTrigger,
1396
+ {
1397
+ "data-slot": "collapsible-trigger",
1398
+ ...props
1399
+ }
1400
+ );
1401
+ }
1402
+ function CollapsibleContent({
1403
+ ...props
1404
+ }) {
1405
+ return /* @__PURE__ */ jsx19(
1406
+ CollapsiblePrimitive.CollapsibleContent,
1407
+ {
1408
+ "data-slot": "collapsible-content",
1409
+ ...props
1410
+ }
1411
+ );
1412
+ }
1413
+
1414
+ // src/components/ai-elements/tool.tsx
1415
+ import {
1416
+ CheckCircleIcon,
1417
+ ChevronDownIcon as ChevronDownIcon2,
1418
+ CircleIcon as CircleIcon2,
1419
+ ClockIcon,
1420
+ WrenchIcon,
1421
+ XCircleIcon
1422
+ } from "lucide-react";
1423
+ import { isValidElement } from "react";
1424
+
1425
+ // src/components/ai-elements/code-block.tsx
1426
+ import { CheckIcon as CheckIcon3, CopyIcon } from "lucide-react";
1427
+ import {
1428
+ createContext as createContext5,
1429
+ memo as memo2,
1430
+ useContext as useContext5,
1431
+ useEffect as useEffect3,
1432
+ useMemo as useMemo4,
1433
+ useRef as useRef2,
1434
+ useState as useState3
1435
+ } from "react";
1436
+ import {
1437
+ createHighlighter
1438
+ } from "shiki";
1439
+ import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1440
+ var isItalic = (fontStyle) => fontStyle && fontStyle & 1;
1441
+ var isBold = (fontStyle) => fontStyle && fontStyle & 2;
1442
+ var isUnderline = (fontStyle) => (
1443
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
1444
+ fontStyle && fontStyle & 4
1445
+ );
1446
+ var addKeysToTokens = (lines) => lines.map((line, lineIdx) => ({
1447
+ key: `line-${lineIdx}`,
1448
+ tokens: line.map((token, tokenIdx) => ({
1449
+ token,
1450
+ key: `line-${lineIdx}-${tokenIdx}`
1451
+ }))
1452
+ }));
1453
+ var TokenSpan = ({ token }) => /* @__PURE__ */ jsx20(
1454
+ "span",
1455
+ {
1456
+ className: "dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]",
1457
+ style: {
1458
+ color: token.color,
1459
+ backgroundColor: token.bgColor,
1460
+ ...token.htmlStyle,
1461
+ fontStyle: isItalic(token.fontStyle) ? "italic" : void 0,
1462
+ fontWeight: isBold(token.fontStyle) ? "bold" : void 0,
1463
+ textDecoration: isUnderline(token.fontStyle) ? "underline" : void 0
1464
+ },
1465
+ children: token.content
1466
+ }
1467
+ );
1468
+ var LineSpan = ({
1469
+ keyedLine,
1470
+ showLineNumbers
1471
+ }) => /* @__PURE__ */ jsx20("span", { className: showLineNumbers ? LINE_NUMBER_CLASSES : "block", children: keyedLine.tokens.length === 0 ? "\n" : keyedLine.tokens.map(({ token, key }) => /* @__PURE__ */ jsx20(TokenSpan, { token }, key)) });
1472
+ var CodeBlockContext = createContext5({
1473
+ code: ""
1474
+ });
1475
+ var highlighterCache = /* @__PURE__ */ new Map();
1476
+ var tokensCache = /* @__PURE__ */ new Map();
1477
+ var subscribers = /* @__PURE__ */ new Map();
1478
+ var getTokensCacheKey = (code2, language) => {
1479
+ const start = code2.slice(0, 100);
1480
+ const end = code2.length > 100 ? code2.slice(-100) : "";
1481
+ return `${language}:${code2.length}:${start}:${end}`;
1482
+ };
1483
+ var getHighlighter = (language) => {
1484
+ const cached = highlighterCache.get(language);
1485
+ if (cached) {
1486
+ return cached;
1487
+ }
1488
+ const highlighterPromise = createHighlighter({
1489
+ themes: ["github-light", "github-dark"],
1490
+ langs: [language]
1491
+ });
1492
+ highlighterCache.set(language, highlighterPromise);
1493
+ return highlighterPromise;
1494
+ };
1495
+ var createRawTokens = (code2) => ({
1496
+ tokens: code2.split("\n").map(
1497
+ (line) => line === "" ? [] : [
1498
+ {
1499
+ content: line,
1500
+ color: "inherit"
1501
+ }
1502
+ ]
1503
+ ),
1504
+ fg: "inherit",
1505
+ bg: "transparent"
1506
+ });
1507
+ function highlightCode(code2, language, callback) {
1508
+ const tokensCacheKey = getTokensCacheKey(code2, language);
1509
+ const cached = tokensCache.get(tokensCacheKey);
1510
+ if (cached) {
1511
+ return cached;
1512
+ }
1513
+ if (callback) {
1514
+ if (!subscribers.has(tokensCacheKey)) {
1515
+ subscribers.set(tokensCacheKey, /* @__PURE__ */ new Set());
1516
+ }
1517
+ subscribers.get(tokensCacheKey)?.add(callback);
1518
+ }
1519
+ getHighlighter(language).then((highlighter) => {
1520
+ const availableLangs = highlighter.getLoadedLanguages();
1521
+ const langToUse = availableLangs.includes(language) ? language : "text";
1522
+ const result = highlighter.codeToTokens(code2, {
1523
+ lang: langToUse,
1524
+ themes: {
1525
+ light: "github-light",
1526
+ dark: "github-dark"
1527
+ }
1528
+ });
1529
+ const tokenized = {
1530
+ tokens: result.tokens,
1531
+ fg: result.fg ?? "inherit",
1532
+ bg: result.bg ?? "transparent"
1533
+ };
1534
+ tokensCache.set(tokensCacheKey, tokenized);
1535
+ const subs = subscribers.get(tokensCacheKey);
1536
+ if (subs) {
1537
+ for (const sub of subs) {
1538
+ sub(tokenized);
1539
+ }
1540
+ subscribers.delete(tokensCacheKey);
1541
+ }
1542
+ }).catch((error) => {
1543
+ console.error("Failed to highlight code:", error);
1544
+ subscribers.delete(tokensCacheKey);
1545
+ });
1546
+ return null;
1547
+ }
1548
+ var LINE_NUMBER_CLASSES = cn(
1549
+ "block",
1550
+ "before:content-[counter(line)]",
1551
+ "before:inline-block",
1552
+ "before:[counter-increment:line]",
1553
+ "before:w-8",
1554
+ "before:mr-4",
1555
+ "before:text-right",
1556
+ "before:text-muted-foreground/50",
1557
+ "before:font-mono",
1558
+ "before:select-none"
1559
+ );
1560
+ var CodeBlockBody = memo2(
1561
+ ({
1562
+ tokenized,
1563
+ showLineNumbers,
1564
+ className
1565
+ }) => {
1566
+ const preStyle = useMemo4(
1567
+ () => ({
1568
+ backgroundColor: tokenized.bg,
1569
+ color: tokenized.fg
1570
+ }),
1571
+ [tokenized.bg, tokenized.fg]
1572
+ );
1573
+ const keyedLines = useMemo4(
1574
+ () => addKeysToTokens(tokenized.tokens),
1575
+ [tokenized.tokens]
1576
+ );
1577
+ return /* @__PURE__ */ jsx20(
1578
+ "pre",
1579
+ {
1580
+ className: cn(
1581
+ "dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-4 text-sm",
1582
+ className
1583
+ ),
1584
+ style: preStyle,
1585
+ children: /* @__PURE__ */ jsx20(
1586
+ "code",
1587
+ {
1588
+ className: cn(
1589
+ "font-mono text-sm",
1590
+ showLineNumbers && "[counter-increment:line_0] [counter-reset:line]"
1591
+ ),
1592
+ children: keyedLines.map((keyedLine) => /* @__PURE__ */ jsx20(
1593
+ LineSpan,
1594
+ {
1595
+ keyedLine,
1596
+ showLineNumbers
1597
+ },
1598
+ keyedLine.key
1599
+ ))
1600
+ }
1601
+ )
1602
+ }
1603
+ );
1604
+ },
1605
+ (prevProps, nextProps) => prevProps.tokenized === nextProps.tokenized && prevProps.showLineNumbers === nextProps.showLineNumbers && prevProps.className === nextProps.className
1606
+ );
1607
+ var CodeBlockContainer = ({
1608
+ className,
1609
+ language,
1610
+ style,
1611
+ ...props
1612
+ }) => /* @__PURE__ */ jsx20(
1613
+ "div",
1614
+ {
1615
+ className: cn(
1616
+ "group relative w-full overflow-hidden rounded-md border bg-background text-foreground",
1617
+ className
1618
+ ),
1619
+ "data-language": language,
1620
+ style: {
1621
+ contentVisibility: "auto",
1622
+ containIntrinsicSize: "auto 200px",
1623
+ ...style
1624
+ },
1625
+ ...props
1626
+ }
1627
+ );
1628
+ var CodeBlockContent = ({
1629
+ code: code2,
1630
+ language,
1631
+ showLineNumbers = false
1632
+ }) => {
1633
+ const rawTokens = useMemo4(() => createRawTokens(code2), [code2]);
1634
+ const [tokenized, setTokenized] = useState3(
1635
+ () => highlightCode(code2, language) ?? rawTokens
1636
+ );
1637
+ useEffect3(() => {
1638
+ setTokenized(highlightCode(code2, language) ?? rawTokens);
1639
+ highlightCode(code2, language, setTokenized);
1640
+ }, [code2, language, rawTokens]);
1641
+ return /* @__PURE__ */ jsx20("div", { className: "relative overflow-auto", children: /* @__PURE__ */ jsx20(CodeBlockBody, { showLineNumbers, tokenized }) });
1642
+ };
1643
+ var CodeBlock = ({
1644
+ code: code2,
1645
+ language,
1646
+ showLineNumbers = false,
1647
+ className,
1648
+ children,
1649
+ ...props
1650
+ }) => /* @__PURE__ */ jsx20(CodeBlockContext.Provider, { value: { code: code2 }, children: /* @__PURE__ */ jsxs10(CodeBlockContainer, { className, language, ...props, children: [
1651
+ children,
1652
+ /* @__PURE__ */ jsx20(
1653
+ CodeBlockContent,
1654
+ {
1655
+ code: code2,
1656
+ language,
1657
+ showLineNumbers
1658
+ }
1659
+ )
1660
+ ] }) });
1661
+
1662
+ // src/components/ai-elements/tool.tsx
1663
+ import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
1664
+ var Tool = ({ className, ...props }) => /* @__PURE__ */ jsx21(
1665
+ Collapsible,
1666
+ {
1667
+ className: cn("group not-prose mb-4 w-full rounded-md border", className),
1668
+ ...props
1669
+ }
1670
+ );
1671
+ var getStatusBadge = (status) => {
1672
+ const labels = {
1673
+ "input-streaming": "Pending",
1674
+ "input-available": "Running",
1675
+ "approval-requested": "Awaiting Approval",
1676
+ "approval-responded": "Responded",
1677
+ "output-available": "Completed",
1678
+ "output-error": "Error",
1679
+ "output-denied": "Denied"
1680
+ };
1681
+ const icons = {
1682
+ "input-streaming": /* @__PURE__ */ jsx21(CircleIcon2, { className: "size-4" }),
1683
+ "input-available": /* @__PURE__ */ jsx21(ClockIcon, { className: "size-4 animate-pulse" }),
1684
+ "approval-requested": /* @__PURE__ */ jsx21(ClockIcon, { className: "size-4 text-yellow-600" }),
1685
+ "approval-responded": /* @__PURE__ */ jsx21(CheckCircleIcon, { className: "size-4 text-blue-600" }),
1686
+ "output-available": /* @__PURE__ */ jsx21(CheckCircleIcon, { className: "size-4 text-green-600" }),
1687
+ "output-error": /* @__PURE__ */ jsx21(XCircleIcon, { className: "size-4 text-red-600" }),
1688
+ "output-denied": /* @__PURE__ */ jsx21(XCircleIcon, { className: "size-4 text-orange-600" })
1689
+ };
1690
+ return /* @__PURE__ */ jsxs11(Badge, { className: "gap-1.5 rounded-full text-xs", variant: "secondary", children: [
1691
+ icons[status],
1692
+ labels[status]
1693
+ ] });
1694
+ };
1695
+ var ToolHeader = ({
1696
+ className,
1697
+ title,
1698
+ type,
1699
+ state,
1700
+ toolName,
1701
+ ...props
1702
+ }) => {
1703
+ const derivedName = type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-");
1704
+ return /* @__PURE__ */ jsxs11(
1705
+ CollapsibleTrigger,
1706
+ {
1707
+ className: cn(
1708
+ "flex w-full items-center justify-between gap-4 p-3",
1709
+ className
1710
+ ),
1711
+ ...props,
1712
+ children: [
1713
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1714
+ /* @__PURE__ */ jsx21(WrenchIcon, { className: "size-4 text-muted-foreground" }),
1715
+ /* @__PURE__ */ jsx21("span", { className: "font-medium text-sm", children: title ?? derivedName }),
1716
+ getStatusBadge(state)
1717
+ ] }),
1718
+ /* @__PURE__ */ jsx21(ChevronDownIcon2, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
1719
+ ]
1720
+ }
1721
+ );
1722
+ };
1723
+ var ToolContent = ({ className, ...props }) => /* @__PURE__ */ jsx21(
1724
+ CollapsibleContent,
1725
+ {
1726
+ className: cn(
1727
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 space-y-4 p-4 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
1728
+ className
1729
+ ),
1730
+ ...props
1731
+ }
1732
+ );
1733
+ var ToolInput = ({ className, input, ...props }) => /* @__PURE__ */ jsxs11("div", { className: cn("space-y-2 overflow-hidden", className), ...props, children: [
1734
+ /* @__PURE__ */ jsx21("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Parameters" }),
1735
+ /* @__PURE__ */ jsx21("div", { className: "rounded-md bg-muted/50", children: /* @__PURE__ */ jsx21(CodeBlock, { code: JSON.stringify(input, null, 2), language: "json" }) })
1736
+ ] });
1737
+ var ToolOutput = ({
1738
+ className,
1739
+ output,
1740
+ errorText,
1741
+ ...props
1742
+ }) => {
1743
+ if (!(output || errorText)) {
1744
+ return null;
1745
+ }
1746
+ let Output = /* @__PURE__ */ jsx21("div", { children: output });
1747
+ if (typeof output === "object" && !isValidElement(output)) {
1748
+ Output = /* @__PURE__ */ jsx21(CodeBlock, { code: JSON.stringify(output, null, 2), language: "json" });
1749
+ } else if (typeof output === "string") {
1750
+ Output = /* @__PURE__ */ jsx21(CodeBlock, { code: output, language: "json" });
1751
+ }
1752
+ return /* @__PURE__ */ jsxs11("div", { className: cn("space-y-2", className), ...props, children: [
1753
+ /* @__PURE__ */ jsx21("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: errorText ? "Error" : "Result" }),
1754
+ /* @__PURE__ */ jsxs11(
1755
+ "div",
1756
+ {
1757
+ className: cn(
1758
+ "overflow-x-auto rounded-md text-xs [&_table]:w-full",
1759
+ errorText ? "bg-destructive/10 text-destructive" : "bg-muted/50 text-foreground"
1760
+ ),
1761
+ children: [
1762
+ errorText && /* @__PURE__ */ jsx21("div", { children: errorText }),
1763
+ Output
1764
+ ]
1765
+ }
1766
+ )
1767
+ ] });
1768
+ };
1769
+
1770
+ // src/hooks/use-copy-to-clipboard.ts
1771
+ import { useState as useState4 } from "react";
1772
+ function useCopyToClipboard({
1773
+ timeout = 2e3
1774
+ } = {}) {
1775
+ const [isCopied, setIsCopied] = useState4(false);
1776
+ const copyToClipboard = (value) => {
1777
+ if (typeof window === "undefined" || !navigator.clipboard?.writeText) {
1778
+ return;
1779
+ }
1780
+ if (!value) {
1781
+ return;
1782
+ }
1783
+ navigator.clipboard.writeText(value).then(() => {
1784
+ setIsCopied(true);
1785
+ setTimeout(() => {
1786
+ setIsCopied(false);
1787
+ }, timeout);
1788
+ });
1789
+ };
1790
+ return { isCopied, copyToClipboard };
1791
+ }
1792
+
1793
+ // src/components/chat/Message.tsx
1794
+ import { CheckIcon as CheckIcon4, CopyIcon as CopyIcon2 } from "lucide-react";
1795
+ import { memo as memo3 } from "react";
1796
+ import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
1797
+ var CopyAction = memo3(({ content }) => {
1798
+ const { locale } = useLocale("message");
1799
+ const { isCopied, copyToClipboard } = useCopyToClipboard();
1800
+ return /* @__PURE__ */ jsx22(
1801
+ MessageAction,
1802
+ {
1803
+ label: locale.copy,
1804
+ onClick: () => copyToClipboard(content),
1805
+ tooltip: isCopied ? locale.copied : locale.copyToClipboard,
1806
+ children: isCopied ? /* @__PURE__ */ jsx22(CheckIcon4, { className: "size-4" }) : /* @__PURE__ */ jsx22(CopyIcon2, { className: "size-4" })
1807
+ }
1808
+ );
1809
+ });
1810
+ CopyAction.displayName = "CopyAction";
1811
+ var mapToolStatus = (status) => {
1812
+ const statusMap = {
1813
+ pending: "input-streaming",
1814
+ ready: "input-available",
1815
+ executing: "input-available",
1816
+ completed: "output-available",
1817
+ failed: "output-error"
1818
+ };
1819
+ return statusMap[status];
1820
+ };
1821
+ var ToolCallRenderer = memo3(
1822
+ ({ toolPart, actions, onRespond }) => {
1823
+ const action = actions?.[toolPart.name];
1824
+ if (action && (action.type === "client-tool" && action.render || action.type === "tool-call")) {
1825
+ const respond = (result) => {
1826
+ onRespond?.(toolPart.toolCallId, result);
1827
+ };
1828
+ const render = action.render;
1829
+ const rendered = render?.({
1830
+ toolCall: toolPart,
1831
+ respond
1832
+ });
1833
+ if (typeof rendered === "string") {
1834
+ return /* @__PURE__ */ jsx22("div", { children: rendered });
1835
+ }
1836
+ return rendered;
1837
+ }
1838
+ const state = mapToolStatus(toolPart.status);
1839
+ return /* @__PURE__ */ jsxs12(Tool, { children: [
1840
+ /* @__PURE__ */ jsx22(
1841
+ ToolHeader,
1842
+ {
1843
+ type: "dynamic-tool",
1844
+ state,
1845
+ toolName: toolPart.name
1846
+ }
1847
+ ),
1848
+ /* @__PURE__ */ jsxs12(ToolContent, { children: [
1849
+ /* @__PURE__ */ jsx22(ToolInput, { input: toolPart.args }),
1850
+ (toolPart.status === "completed" || toolPart.status === "failed") && /* @__PURE__ */ jsx22(
1851
+ ToolOutput,
1852
+ {
1853
+ output: toolPart.result,
1854
+ errorText: toolPart.error?.message
1855
+ }
1856
+ )
1857
+ ] })
1858
+ ] });
1859
+ }
1860
+ );
1861
+ ToolCallRenderer.displayName = "ToolCallRenderer";
1862
+ function ChatMessage({
1863
+ messages,
1864
+ isLoading = false,
1865
+ actions,
1866
+ onRespond
1867
+ }) {
1868
+ const { locale } = useLocale("message");
1869
+ const visibleMessages = messages.filter(
1870
+ (message) => message.role === "user" || message.role === "assistant"
1871
+ );
1872
+ const lastVisibleMessage = visibleMessages[visibleMessages.length - 1];
1873
+ const shouldRenderAssistantPlaceholder = isLoading && (!lastVisibleMessage || lastVisibleMessage.role === "user");
1874
+ return /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-4", children: [
1875
+ visibleMessages.map((message) => {
1876
+ const role = message.role;
1877
+ const isLoadingAssistantMessage = isLoading && role === "assistant" && message.id === lastVisibleMessage?.id;
1878
+ const allTextContent = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
1879
+ const hasAssistantText = role === "assistant" && allTextContent.trim().length > 0;
1880
+ const isPausedAssistantNoText = role === "assistant" && Boolean(message.paused) && !hasAssistantText;
1881
+ const copyContent = allTextContent || (isPausedAssistantNoText ? locale.paused : "");
1882
+ return /* @__PURE__ */ jsxs12(Message, { from: role, children: [
1883
+ message.parts.map((part) => {
1884
+ if (part.type === "text") {
1885
+ const textPart = part;
1886
+ return /* @__PURE__ */ jsx22(MessageContent, { children: role === "assistant" ? /* @__PURE__ */ jsx22(MessageResponse, { children: textPart.text }) : textPart.text }, textPart.id);
1887
+ }
1888
+ if (part.type === "tool") {
1889
+ const toolPart = part;
1890
+ return /* @__PURE__ */ jsx22(
1891
+ ToolCallRenderer,
1892
+ {
1893
+ toolPart,
1894
+ actions,
1895
+ onRespond
1896
+ },
1897
+ toolPart.id
1898
+ );
1899
+ }
1900
+ return null;
1901
+ }),
1902
+ isLoadingAssistantMessage && /* @__PURE__ */ jsx22(MessageContent, { children: /* @__PURE__ */ jsx22("div", { className: "inline-flex items-center text-muted-foreground", children: /* @__PURE__ */ jsx22(SvgSpinners3DotsFade, { className: "text-xl" }) }) }),
1903
+ isPausedAssistantNoText && !isLoadingAssistantMessage && /* @__PURE__ */ jsx22(MessageContent, { children: /* @__PURE__ */ jsx22("div", { className: "text-muted-foreground", children: locale.paused }) }),
1904
+ role === "assistant" && copyContent && !isLoadingAssistantMessage && /* @__PURE__ */ jsx22(MessageActions, { children: /* @__PURE__ */ jsx22(CopyAction, { content: copyContent }) })
1905
+ ] }, message.id);
1906
+ }),
1907
+ shouldRenderAssistantPlaceholder && /* @__PURE__ */ jsx22(Message, { from: "assistant", children: /* @__PURE__ */ jsx22(MessageContent, { children: /* @__PURE__ */ jsx22("div", { className: "inline-flex items-center text-muted-foreground", children: /* @__PURE__ */ jsx22(SvgSpinners3DotsFade, { className: "text-xl" }) }) }) })
1908
+ ] });
1909
+ }
1910
+ function SvgSpinners3DotsFade(props) {
1911
+ return /* @__PURE__ */ jsxs12(
1912
+ "svg",
1913
+ {
1914
+ xmlns: "http://www.w3.org/2000/svg",
1915
+ width: "1em",
1916
+ height: "1em",
1917
+ viewBox: "0 0 24 24",
1918
+ ...props,
1919
+ children: [
1920
+ /* @__PURE__ */ jsx22("circle", { cx: "4", cy: "12", r: "3", fill: "currentColor", children: /* @__PURE__ */ jsx22(
1921
+ "animate",
1922
+ {
1923
+ id: "dotFadeFirst",
1924
+ fill: "freeze",
1925
+ attributeName: "opacity",
1926
+ begin: "0;dotFadeThird.end-0.25s",
1927
+ dur: "0.75s",
1928
+ values: "1;.2"
1929
+ }
1930
+ ) }),
1931
+ /* @__PURE__ */ jsx22("circle", { cx: "12", cy: "12", r: "3", fill: "currentColor", opacity: ".4", children: /* @__PURE__ */ jsx22(
1932
+ "animate",
1933
+ {
1934
+ fill: "freeze",
1935
+ attributeName: "opacity",
1936
+ begin: "dotFadeFirst.begin+0.15s",
1937
+ dur: "0.75s",
1938
+ values: "1;.2"
1939
+ }
1940
+ ) }),
1941
+ /* @__PURE__ */ jsx22("circle", { cx: "20", cy: "12", r: "3", fill: "currentColor", opacity: ".3", children: /* @__PURE__ */ jsx22(
1942
+ "animate",
1943
+ {
1944
+ id: "dotFadeThird",
1945
+ fill: "freeze",
1946
+ attributeName: "opacity",
1947
+ begin: "dotFadeFirst.begin+0.3s",
1948
+ dur: "0.75s",
1949
+ values: "1;.2"
1950
+ }
1951
+ ) })
1952
+ ]
1953
+ }
1954
+ );
1955
+ }
1956
+
1957
+ // src/components/chat/Chat.tsx
1958
+ import { v4 as uuidv4 } from "uuid";
1959
+ import { useMemo as useMemo5 } from "react";
1960
+
1961
+ // src/components/ui/scroll-area.tsx
1962
+ import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
1963
+ import { jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1964
+ function ScrollArea({
1965
+ className,
1966
+ children,
1967
+ ...props
1968
+ }) {
1969
+ return /* @__PURE__ */ jsxs13(
1970
+ ScrollAreaPrimitive.Root,
1971
+ {
1972
+ "data-slot": "scroll-area",
1973
+ className: cn("relative", className),
1974
+ ...props,
1975
+ children: [
1976
+ /* @__PURE__ */ jsx23(
1977
+ ScrollAreaPrimitive.Viewport,
1978
+ {
1979
+ "data-slot": "scroll-area-viewport",
1980
+ className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
1981
+ children
1982
+ }
1983
+ ),
1984
+ /* @__PURE__ */ jsx23(ScrollBar, {}),
1985
+ /* @__PURE__ */ jsx23(ScrollAreaPrimitive.Corner, {})
1986
+ ]
1987
+ }
1988
+ );
1989
+ }
1990
+ function ScrollBar({
1991
+ className,
1992
+ orientation = "vertical",
1993
+ ...props
1994
+ }) {
1995
+ return /* @__PURE__ */ jsx23(
1996
+ ScrollAreaPrimitive.ScrollAreaScrollbar,
1997
+ {
1998
+ "data-slot": "scroll-area-scrollbar",
1999
+ orientation,
2000
+ className: cn(
2001
+ "flex touch-none p-px transition-colors select-none",
2002
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
2003
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
2004
+ className
2005
+ ),
2006
+ ...props,
2007
+ children: /* @__PURE__ */ jsx23(
2008
+ ScrollAreaPrimitive.ScrollAreaThumb,
2009
+ {
2010
+ "data-slot": "scroll-area-thumb",
2011
+ className: "bg-border relative flex-1 rounded-full"
2012
+ }
2013
+ )
2014
+ }
2015
+ );
2016
+ }
2017
+
2018
+ // src/components/ai-elements/suggestion.tsx
2019
+ import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
2020
+ var Suggestions = ({
2021
+ className,
2022
+ children,
2023
+ ...props
2024
+ }) => /* @__PURE__ */ jsxs14(ScrollArea, { className: "w-full overflow-x-auto whitespace-nowrap", ...props, children: [
2025
+ /* @__PURE__ */ jsx24("div", { className: cn("flex w-max flex-nowrap items-center gap-2", className), children }),
2026
+ /* @__PURE__ */ jsx24(ScrollBar, { className: "hidden", orientation: "horizontal" })
2027
+ ] });
2028
+ var Suggestion = ({
2029
+ suggestion,
2030
+ onClick,
2031
+ className,
2032
+ variant = "outline",
2033
+ size = "sm",
2034
+ children,
2035
+ ...props
2036
+ }) => {
2037
+ const handleClick = () => {
2038
+ onClick?.(suggestion);
2039
+ };
2040
+ return /* @__PURE__ */ jsx24(
2041
+ Button,
2042
+ {
2043
+ className: cn("cursor-pointer rounded-full px-4", className),
2044
+ onClick: handleClick,
2045
+ size,
2046
+ type: "button",
2047
+ variant,
2048
+ ...props,
2049
+ children: children || suggestion
2050
+ }
2051
+ );
2052
+ };
2053
+
2054
+ // src/components/chat/Chat.tsx
2055
+ import { Fragment as Fragment2, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
2056
+ function AgKitUI({
2057
+ className = "h-full min-h-0 mx-auto max-w-225 flex flex-col overflow-hidden px-4",
2058
+ containerClassName,
2059
+ messagesClassName,
2060
+ inputClassName,
2061
+ emptyTitleClassName,
2062
+ inputPlaceholder,
2063
+ suggestions,
2064
+ onSuggestionClick,
2065
+ locale = "zh-CN"
2066
+ }) {
2067
+ const contextValue = useMemo5(() => ({ locale }), [locale]);
2068
+ return /* @__PURE__ */ jsx25(LocaleContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx25(
2069
+ AgKitUIContent,
2070
+ {
2071
+ className,
2072
+ containerClassName,
2073
+ messagesClassName,
2074
+ inputClassName,
2075
+ emptyTitleClassName,
2076
+ inputPlaceholder,
2077
+ suggestions,
2078
+ onSuggestionClick
2079
+ }
2080
+ ) });
2081
+ }
2082
+ function AgKitUIContent({
2083
+ className,
2084
+ containerClassName,
2085
+ messagesClassName,
2086
+ inputClassName,
2087
+ emptyTitleClassName,
2088
+ inputPlaceholder,
2089
+ suggestions,
2090
+ onSuggestionClick
2091
+ }) {
2092
+ const { locale } = useLocale("chat");
2093
+ const { uiMessages, streaming, sendMessage, actions, abort } = useChat();
2094
+ const handleRespond = (toolCallId, result) => {
2095
+ sendMessage({
2096
+ id: uuidv4(),
2097
+ role: "tool",
2098
+ content: result,
2099
+ toolCallId
2100
+ });
2101
+ };
2102
+ const visibleMessages = uiMessages.filter(
2103
+ (message) => message.role === "user" || message.role === "assistant"
2104
+ );
2105
+ const isEmpty = visibleMessages.length === 0;
2106
+ const inputNode = /* @__PURE__ */ jsxs15("div", { className: inputClassName, children: [
2107
+ isEmpty && suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx25(Suggestions, { className: "mb-3", children: suggestions.map((suggestion) => suggestion.trim()).filter(Boolean).map((suggestion, index) => /* @__PURE__ */ jsx25(
2108
+ Suggestion,
2109
+ {
2110
+ suggestion,
2111
+ variant: "ghost",
2112
+ className: "max-w-full border border-border/80 text-muted-foreground",
2113
+ disabled: streaming,
2114
+ onClick: (value) => {
2115
+ onSuggestionClick?.(value);
2116
+ sendMessage(value);
2117
+ }
2118
+ },
2119
+ `${suggestion}-${index}`
2120
+ )) }),
2121
+ /* @__PURE__ */ jsx25(
2122
+ Input2,
2123
+ {
2124
+ placeholder: inputPlaceholder,
2125
+ streaming,
2126
+ onStop: () => {
2127
+ if (!streaming) return;
2128
+ abort();
2129
+ },
2130
+ onSend: (message) => {
2131
+ sendMessage(message.text);
2132
+ }
2133
+ }
2134
+ )
2135
+ ] });
2136
+ return /* @__PURE__ */ jsx25(
2137
+ "div",
2138
+ {
2139
+ className: cn(
2140
+ "bg-background text-foreground h-full min-h-0 w-full",
2141
+ containerClassName
2142
+ ),
2143
+ children: /* @__PURE__ */ jsx25("div", { className: cn(className), children: isEmpty ? /* @__PURE__ */ jsx25("div", { className: "flex min-h-0 flex-1 items-center justify-center", children: /* @__PURE__ */ jsxs15("div", { className: "w-full", children: [
2144
+ /* @__PURE__ */ jsx25(
2145
+ "div",
2146
+ {
2147
+ className: cn(
2148
+ "mb-5 text-center text-xl font-medium text-foreground",
2149
+ emptyTitleClassName
2150
+ ),
2151
+ children: locale.emptyTitle
2152
+ }
2153
+ ),
2154
+ inputNode
2155
+ ] }) }) : /* @__PURE__ */ jsxs15(Fragment2, { children: [
2156
+ /* @__PURE__ */ jsx25(
2157
+ "div",
2158
+ {
2159
+ className: cn(
2160
+ "min-h-0 flex-1 overflow-y-auto py-4",
2161
+ messagesClassName
2162
+ ),
2163
+ children: /* @__PURE__ */ jsx25(
2164
+ ChatMessage,
2165
+ {
2166
+ messages: uiMessages,
2167
+ isLoading: streaming,
2168
+ actions,
2169
+ onRespond: handleRespond
2170
+ }
2171
+ )
2172
+ }
2173
+ ),
2174
+ inputNode
2175
+ ] }) })
2176
+ }
2177
+ );
2178
+ }
2179
+ export {
2180
+ AgKitUI
2181
+ };
2182
+ //# sourceMappingURL=index.mjs.map