@mordn/chat-widget 0.1.0

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.mjs ADDED
@@ -0,0 +1,2647 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/ChatWidget.tsx
5
+ import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
6
+
7
+ // src/ui/button.tsx
8
+ import { Slot } from "@radix-ui/react-slot";
9
+ import { cva } from "class-variance-authority";
10
+
11
+ // src/utils/cn.ts
12
+ import { clsx } from "clsx";
13
+ import { twMerge } from "tailwind-merge";
14
+ function cn(...inputs) {
15
+ return twMerge(clsx(inputs));
16
+ }
17
+
18
+ // src/ui/button.tsx
19
+ import { jsx } from "react/jsx-runtime";
20
+ var buttonVariants = cva(
21
+ "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",
22
+ {
23
+ variants: {
24
+ variant: {
25
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
26
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
27
+ 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",
28
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
29
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
30
+ link: "text-primary underline-offset-4 hover:underline"
31
+ },
32
+ size: {
33
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
34
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
35
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
36
+ icon: "size-9"
37
+ }
38
+ },
39
+ defaultVariants: {
40
+ variant: "default",
41
+ size: "default"
42
+ }
43
+ }
44
+ );
45
+ function Button({
46
+ className,
47
+ variant,
48
+ size,
49
+ asChild = false,
50
+ ...props
51
+ }) {
52
+ const Comp = asChild ? Slot : "button";
53
+ return /* @__PURE__ */ jsx(
54
+ Comp,
55
+ {
56
+ "data-slot": "button",
57
+ className: cn(buttonVariants({ variant, size, className })),
58
+ ...props
59
+ }
60
+ );
61
+ }
62
+
63
+ // src/components/conversation.tsx
64
+ import { ArrowDownIcon } from "lucide-react";
65
+ import { useCallback } from "react";
66
+ import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
67
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
68
+ var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx2(
69
+ StickToBottom,
70
+ {
71
+ className: cn("relative flex-1 overflow-y-auto", className),
72
+ initial: "smooth",
73
+ resize: "smooth",
74
+ role: "log",
75
+ ...props
76
+ }
77
+ );
78
+ var ConversationContent = ({
79
+ className,
80
+ ...props
81
+ }) => /* @__PURE__ */ jsx2(StickToBottom.Content, { className: cn("p-4", className), ...props });
82
+ var ConversationScrollButton = ({
83
+ className,
84
+ ...props
85
+ }) => {
86
+ const { isAtBottom, scrollToBottom } = useStickToBottomContext();
87
+ const handleScrollToBottom = useCallback(() => {
88
+ scrollToBottom();
89
+ }, [scrollToBottom]);
90
+ return !isAtBottom && /* @__PURE__ */ jsx2(
91
+ Button,
92
+ {
93
+ className: cn(
94
+ "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
95
+ className
96
+ ),
97
+ onClick: handleScrollToBottom,
98
+ size: "icon",
99
+ type: "button",
100
+ variant: "outline",
101
+ ...props,
102
+ children: /* @__PURE__ */ jsx2(ArrowDownIcon, { className: "size-4" })
103
+ }
104
+ );
105
+ };
106
+
107
+ // src/ui/avatar.tsx
108
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
109
+ import { jsx as jsx3 } from "react/jsx-runtime";
110
+
111
+ // src/components/message.tsx
112
+ import { cva as cva2 } from "class-variance-authority";
113
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
114
+ var Message = ({ className, from, ...props }) => /* @__PURE__ */ jsx4(
115
+ "div",
116
+ {
117
+ className: cn(
118
+ "group flex w-full items-end justify-end gap-2",
119
+ from === "user" ? "is-user" : "is-assistant flex-row-reverse justify-end",
120
+ className
121
+ ),
122
+ ...props
123
+ }
124
+ );
125
+ var messageContentVariants = cva2(
126
+ "flex flex-col gap-2 overflow-hidden leading-relaxed chat-message-content",
127
+ {
128
+ variants: {
129
+ variant: {
130
+ contained: [
131
+ // User messages: compact bubbles on the right (max 85% width)
132
+ "group-[.is-user]:max-w-[85%] group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-lg group-[.is-user]:shadow-sm group-[.is-user]:px-4 group-[.is-user]:py-3",
133
+ // Assistant messages: no bubble, just text on background (max 100% width)
134
+ "group-[.is-assistant]:max-w-full"
135
+ ],
136
+ flat: [
137
+ // User messages: compact on the right
138
+ "group-[.is-user]:max-w-[85%] group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-lg",
139
+ // Assistant messages: full width
140
+ "group-[.is-assistant]:max-w-full"
141
+ ]
142
+ }
143
+ },
144
+ defaultVariants: {
145
+ variant: "contained"
146
+ }
147
+ }
148
+ );
149
+ var MessageContent = ({
150
+ children,
151
+ className,
152
+ variant,
153
+ ...props
154
+ }) => /* @__PURE__ */ jsx4(
155
+ "div",
156
+ {
157
+ className: cn(messageContentVariants({ variant, className })),
158
+ ...props,
159
+ children
160
+ }
161
+ );
162
+
163
+ // src/ui/dropdown-menu.tsx
164
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
165
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
166
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
167
+
168
+ // src/ui/select.tsx
169
+ import * as SelectPrimitive from "@radix-ui/react-select";
170
+ import { CheckIcon as CheckIcon2, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
171
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
172
+
173
+ // src/ui/textarea.tsx
174
+ import { jsx as jsx7 } from "react/jsx-runtime";
175
+ function Textarea({ className, ...props }) {
176
+ return /* @__PURE__ */ jsx7(
177
+ "textarea",
178
+ {
179
+ "data-slot": "textarea",
180
+ className: cn(
181
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring 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 transition-colors outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
182
+ className
183
+ ),
184
+ ...props
185
+ }
186
+ );
187
+ }
188
+
189
+ // src/components/prompt-input.tsx
190
+ import {
191
+ ImageIcon,
192
+ Loader2Icon,
193
+ PaperclipIcon,
194
+ PlusIcon,
195
+ SendIcon,
196
+ SquareIcon,
197
+ XIcon
198
+ } from "lucide-react";
199
+ import { nanoid } from "nanoid";
200
+ import {
201
+ Children,
202
+ createContext,
203
+ Fragment as Fragment2,
204
+ useCallback as useCallback2,
205
+ useContext,
206
+ useEffect,
207
+ useLayoutEffect,
208
+ useMemo,
209
+ useRef,
210
+ useState
211
+ } from "react";
212
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
213
+ var AttachmentsContext = createContext(null);
214
+ var usePromptInputAttachments = () => {
215
+ const context = useContext(AttachmentsContext);
216
+ if (!context) {
217
+ throw new Error(
218
+ "usePromptInputAttachments must be used within a PromptInput"
219
+ );
220
+ }
221
+ return context;
222
+ };
223
+ function PromptInputAttachment({
224
+ data,
225
+ className,
226
+ ...props
227
+ }) {
228
+ const attachments = usePromptInputAttachments();
229
+ return /* @__PURE__ */ jsxs5(
230
+ "div",
231
+ {
232
+ className: cn("group relative h-14 w-14 rounded-lg border", className),
233
+ ...props,
234
+ children: [
235
+ data.mediaType?.startsWith("image/") && data.url ? /* @__PURE__ */ jsx8(
236
+ "img",
237
+ {
238
+ alt: data.filename || "attachment",
239
+ className: "size-full rounded-lg object-cover",
240
+ height: 56,
241
+ src: data.url,
242
+ width: 56
243
+ }
244
+ ) : /* @__PURE__ */ jsx8("div", { className: "flex size-full items-center justify-center text-muted-foreground", children: /* @__PURE__ */ jsx8(PaperclipIcon, { className: "size-4" }) }),
245
+ /* @__PURE__ */ jsx8(
246
+ Button,
247
+ {
248
+ "aria-label": "Remove attachment",
249
+ className: "-right-1 -top-1 absolute h-4 w-4 rounded-full opacity-0 group-hover:opacity-100 p-0 [&_svg]:h-2 [&_svg]:w-2",
250
+ onClick: () => attachments.remove(data.id),
251
+ size: "icon",
252
+ type: "button",
253
+ variant: "outline",
254
+ children: /* @__PURE__ */ jsx8(XIcon, { className: "h-2 w-2 shrink-0" })
255
+ }
256
+ )
257
+ ]
258
+ },
259
+ data.id
260
+ );
261
+ }
262
+ function PromptInputAttachments({
263
+ className,
264
+ children,
265
+ ...props
266
+ }) {
267
+ const attachments = usePromptInputAttachments();
268
+ const [height, setHeight] = useState(0);
269
+ const contentRef = useRef(null);
270
+ useLayoutEffect(() => {
271
+ const el = contentRef.current;
272
+ if (!el) {
273
+ return;
274
+ }
275
+ const ro = new ResizeObserver(() => {
276
+ setHeight(el.getBoundingClientRect().height);
277
+ });
278
+ ro.observe(el);
279
+ setHeight(el.getBoundingClientRect().height);
280
+ return () => ro.disconnect();
281
+ }, []);
282
+ return /* @__PURE__ */ jsx8(
283
+ "div",
284
+ {
285
+ "aria-live": "polite",
286
+ className: cn(
287
+ "overflow-hidden transition-[height] duration-200 ease-out",
288
+ className
289
+ ),
290
+ style: { height: attachments.files.length ? height : 0 },
291
+ ...props,
292
+ children: /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap gap-2 p-3 pt-3", ref: contentRef, children: attachments.files.map((file) => /* @__PURE__ */ jsx8(Fragment2, { children: children(file) }, file.id)) })
293
+ }
294
+ );
295
+ }
296
+ var PromptInput = ({
297
+ className,
298
+ accept,
299
+ multiple,
300
+ globalDrop,
301
+ syncHiddenInput,
302
+ maxFiles,
303
+ maxFileSize,
304
+ onError,
305
+ onSubmit,
306
+ ...props
307
+ }) => {
308
+ const [items, setItems] = useState([]);
309
+ const inputRef = useRef(null);
310
+ const anchorRef = useRef(null);
311
+ const formRef = useRef(null);
312
+ useEffect(() => {
313
+ const root = anchorRef.current?.closest("form");
314
+ if (root instanceof HTMLFormElement) {
315
+ formRef.current = root;
316
+ }
317
+ }, []);
318
+ const openFileDialog = useCallback2(() => {
319
+ inputRef.current?.click();
320
+ }, []);
321
+ const matchesAccept = useCallback2(
322
+ (f) => {
323
+ if (!accept || accept.trim() === "") {
324
+ return true;
325
+ }
326
+ if (accept.includes("image/*")) {
327
+ return f.type.startsWith("image/");
328
+ }
329
+ return true;
330
+ },
331
+ [accept]
332
+ );
333
+ const add = useCallback2(
334
+ (files) => {
335
+ const incoming = Array.from(files);
336
+ const accepted = incoming.filter((f) => matchesAccept(f));
337
+ if (accepted.length === 0) {
338
+ onError?.({
339
+ code: "accept",
340
+ message: "No files match the accepted types."
341
+ });
342
+ return;
343
+ }
344
+ const withinSize = (f) => maxFileSize ? f.size <= maxFileSize : true;
345
+ const sized = accepted.filter(withinSize);
346
+ if (sized.length === 0 && accepted.length > 0) {
347
+ onError?.({
348
+ code: "max_file_size",
349
+ message: "All files exceed the maximum size."
350
+ });
351
+ return;
352
+ }
353
+ setItems((prev) => {
354
+ const capacity = typeof maxFiles === "number" ? Math.max(0, maxFiles - prev.length) : void 0;
355
+ const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;
356
+ if (typeof capacity === "number" && sized.length > capacity) {
357
+ onError?.({
358
+ code: "max_files",
359
+ message: "Too many files. Some were not added."
360
+ });
361
+ }
362
+ const next = [];
363
+ for (const file of capped) {
364
+ next.push({
365
+ id: nanoid(),
366
+ type: "file",
367
+ url: URL.createObjectURL(file),
368
+ mediaType: file.type,
369
+ filename: file.name
370
+ });
371
+ }
372
+ return prev.concat(next);
373
+ });
374
+ },
375
+ [matchesAccept, maxFiles, maxFileSize, onError]
376
+ );
377
+ const remove = useCallback2((id) => {
378
+ setItems((prev) => {
379
+ const found = prev.find((file) => file.id === id);
380
+ if (found?.url) {
381
+ URL.revokeObjectURL(found.url);
382
+ }
383
+ return prev.filter((file) => file.id !== id);
384
+ });
385
+ }, []);
386
+ const clear = useCallback2(() => {
387
+ setItems((prev) => {
388
+ for (const file of prev) {
389
+ if (file.url) {
390
+ URL.revokeObjectURL(file.url);
391
+ }
392
+ }
393
+ return [];
394
+ });
395
+ }, []);
396
+ useEffect(() => {
397
+ if (syncHiddenInput && inputRef.current) {
398
+ if (items.length === 0) {
399
+ inputRef.current.value = "";
400
+ }
401
+ }
402
+ }, [items, syncHiddenInput]);
403
+ useEffect(() => {
404
+ const form = formRef.current;
405
+ if (!form) {
406
+ return;
407
+ }
408
+ const onDragOver = (e) => {
409
+ if (e.dataTransfer?.types?.includes("Files")) {
410
+ e.preventDefault();
411
+ }
412
+ };
413
+ const onDrop = (e) => {
414
+ if (e.dataTransfer?.types?.includes("Files")) {
415
+ e.preventDefault();
416
+ }
417
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
418
+ add(e.dataTransfer.files);
419
+ }
420
+ };
421
+ form.addEventListener("dragover", onDragOver);
422
+ form.addEventListener("drop", onDrop);
423
+ return () => {
424
+ form.removeEventListener("dragover", onDragOver);
425
+ form.removeEventListener("drop", onDrop);
426
+ };
427
+ }, [add]);
428
+ useEffect(() => {
429
+ if (!globalDrop) {
430
+ return;
431
+ }
432
+ const onDragOver = (e) => {
433
+ if (e.dataTransfer?.types?.includes("Files")) {
434
+ e.preventDefault();
435
+ }
436
+ };
437
+ const onDrop = (e) => {
438
+ if (e.dataTransfer?.types?.includes("Files")) {
439
+ e.preventDefault();
440
+ }
441
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
442
+ add(e.dataTransfer.files);
443
+ }
444
+ };
445
+ document.addEventListener("dragover", onDragOver);
446
+ document.addEventListener("drop", onDrop);
447
+ return () => {
448
+ document.removeEventListener("dragover", onDragOver);
449
+ document.removeEventListener("drop", onDrop);
450
+ };
451
+ }, [add, globalDrop]);
452
+ const handleChange = (event) => {
453
+ if (event.currentTarget.files) {
454
+ add(event.currentTarget.files);
455
+ }
456
+ };
457
+ const handleSubmit = (event) => {
458
+ event.preventDefault();
459
+ const files = items.map(({ ...item }) => ({
460
+ ...item
461
+ }));
462
+ onSubmit({ text: event.currentTarget.message.value, files }, event);
463
+ clear();
464
+ };
465
+ const ctx = useMemo(
466
+ () => ({
467
+ files: items.map((item) => ({ ...item, id: item.id })),
468
+ add,
469
+ remove,
470
+ clear,
471
+ openFileDialog,
472
+ fileInputRef: inputRef
473
+ }),
474
+ [items, add, remove, clear, openFileDialog]
475
+ );
476
+ return /* @__PURE__ */ jsxs5(AttachmentsContext.Provider, { value: ctx, children: [
477
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", className: "hidden", ref: anchorRef }),
478
+ /* @__PURE__ */ jsx8(
479
+ "input",
480
+ {
481
+ accept,
482
+ className: "hidden",
483
+ multiple,
484
+ onChange: handleChange,
485
+ ref: inputRef,
486
+ type: "file"
487
+ }
488
+ ),
489
+ /* @__PURE__ */ jsx8(
490
+ "form",
491
+ {
492
+ className: cn(
493
+ "w-full divide-y overflow-hidden rounded-xl border bg-background focus-within:border-ring transition-colors",
494
+ "[&:focus-within]:shadow-none [&:focus]:shadow-none shadow-none",
495
+ className
496
+ ),
497
+ onSubmit: handleSubmit,
498
+ style: { boxShadow: "none" },
499
+ ...props
500
+ }
501
+ )
502
+ ] });
503
+ };
504
+ var PromptInputBody = ({
505
+ className,
506
+ ...props
507
+ }) => /* @__PURE__ */ jsx8("div", { className: cn(className, "flex flex-col"), ...props });
508
+ var PromptInputTextarea = ({
509
+ onChange,
510
+ className,
511
+ placeholder = "What would you like to know?",
512
+ ...props
513
+ }) => {
514
+ const attachments = usePromptInputAttachments();
515
+ const handleKeyDown = (e) => {
516
+ if (e.key === "Enter") {
517
+ if (e.nativeEvent.isComposing) {
518
+ return;
519
+ }
520
+ if (e.shiftKey) {
521
+ return;
522
+ }
523
+ e.preventDefault();
524
+ const form = e.currentTarget.form;
525
+ if (form) {
526
+ form.requestSubmit();
527
+ }
528
+ }
529
+ };
530
+ const handlePaste = (event) => {
531
+ const items = event.clipboardData?.items;
532
+ if (!items) {
533
+ return;
534
+ }
535
+ const files = [];
536
+ for (const item of items) {
537
+ if (item.kind === "file") {
538
+ const file = item.getAsFile();
539
+ if (file) {
540
+ files.push(file);
541
+ }
542
+ }
543
+ }
544
+ if (files.length > 0) {
545
+ event.preventDefault();
546
+ attachments.add(files);
547
+ }
548
+ };
549
+ return /* @__PURE__ */ jsx8(
550
+ Textarea,
551
+ {
552
+ className: cn(
553
+ "w-full resize-none rounded-none border-none p-3 shadow-none outline-none ring-0",
554
+ "field-sizing-content",
555
+ "max-h-48 min-h-16",
556
+ "focus-visible:ring-0 focus-visible:border-red-500 focus-visible:shadow-none focus:ring-0 focus:shadow-none",
557
+ className
558
+ ),
559
+ name: "message",
560
+ onChange: (e) => {
561
+ onChange?.(e);
562
+ },
563
+ onKeyDown: handleKeyDown,
564
+ onPaste: handlePaste,
565
+ placeholder,
566
+ ...props
567
+ }
568
+ );
569
+ };
570
+ var PromptInputToolbar = ({
571
+ className,
572
+ ...props
573
+ }) => /* @__PURE__ */ jsx8(
574
+ "div",
575
+ {
576
+ className: cn("flex items-center justify-between p-1", className),
577
+ ...props
578
+ }
579
+ );
580
+ var PromptInputTools = ({
581
+ className,
582
+ ...props
583
+ }) => /* @__PURE__ */ jsx8(
584
+ "div",
585
+ {
586
+ className: cn(
587
+ "flex items-center gap-1",
588
+ "[&_button:first-child]:rounded-bl-xl",
589
+ className
590
+ ),
591
+ ...props
592
+ }
593
+ );
594
+ var PromptInputButton = ({
595
+ variant = "ghost",
596
+ className,
597
+ size,
598
+ ...props
599
+ }) => {
600
+ const newSize = size ?? Children.count(props.children) > 1 ? "default" : "icon";
601
+ return /* @__PURE__ */ jsx8(
602
+ Button,
603
+ {
604
+ className: cn(
605
+ "shrink-0 gap-1.5 rounded-lg",
606
+ variant === "ghost" && "text-muted-foreground",
607
+ newSize === "default" && "px-3",
608
+ className
609
+ ),
610
+ size: newSize,
611
+ type: "button",
612
+ variant,
613
+ ...props
614
+ }
615
+ );
616
+ };
617
+ var PromptInputSubmit = ({
618
+ className,
619
+ variant = "default",
620
+ size = "icon",
621
+ status,
622
+ children,
623
+ ...props
624
+ }) => {
625
+ let Icon2 = /* @__PURE__ */ jsx8(SendIcon, { className: "size-4" });
626
+ if (status === "submitted") {
627
+ Icon2 = /* @__PURE__ */ jsx8(Loader2Icon, { className: "size-4 animate-spin" });
628
+ } else if (status === "streaming") {
629
+ Icon2 = /* @__PURE__ */ jsx8(SquareIcon, { className: "size-4" });
630
+ } else if (status === "error") {
631
+ Icon2 = /* @__PURE__ */ jsx8(XIcon, { className: "size-4" });
632
+ }
633
+ return /* @__PURE__ */ jsx8(
634
+ Button,
635
+ {
636
+ className: cn("gap-1.5 rounded-lg", className),
637
+ size,
638
+ type: "submit",
639
+ variant,
640
+ ...props,
641
+ children: children ?? Icon2
642
+ }
643
+ );
644
+ };
645
+
646
+ // src/components/message-attachments.tsx
647
+ import { PaperclipIcon as PaperclipIcon2 } from "lucide-react";
648
+ import { jsx as jsx9 } from "react/jsx-runtime";
649
+ function MessageAttachments({ attachments, className }) {
650
+ if (!attachments || attachments.length === 0) {
651
+ return null;
652
+ }
653
+ const handleAttachmentClick = (attachment) => {
654
+ if (attachment.url.startsWith("data:")) {
655
+ const newWindow = window.open("", "_blank");
656
+ if (newWindow) {
657
+ newWindow.document.write(`
658
+ <html>
659
+ <head><title>${attachment.filename}</title></head>
660
+ <body style="margin:0; padding:20px; background:#f5f5f5; display:flex; justify-content:center; align-items:center; min-height:100vh;">
661
+ <img src="${attachment.url}" alt="${attachment.filename}" style="max-width:100%; max-height:100%; object-fit:contain; border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,0.15);" />
662
+ </body>
663
+ </html>
664
+ `);
665
+ newWindow.document.close();
666
+ }
667
+ } else if (attachment.url.startsWith("blob:")) {
668
+ window.open(attachment.url, "_blank");
669
+ } else if (attachment.url.startsWith("http")) {
670
+ window.open(attachment.url, "_blank");
671
+ } else {
672
+ window.open(attachment.url, "_blank");
673
+ }
674
+ };
675
+ return /* @__PURE__ */ jsx9("div", { className: cn("flex flex-wrap gap-2", className), children: attachments.map((attachment, index) => /* @__PURE__ */ jsx9(
676
+ "div",
677
+ {
678
+ className: "group relative h-14 w-14 rounded-lg",
679
+ children: attachment.mediaType.startsWith("image/") ? /* @__PURE__ */ jsx9(
680
+ "img",
681
+ {
682
+ src: attachment.url,
683
+ alt: attachment.filename,
684
+ className: "size-full rounded-lg object-cover cursor-pointer hover:opacity-80 transition-opacity",
685
+ onClick: () => handleAttachmentClick(attachment)
686
+ }
687
+ ) : /* @__PURE__ */ jsx9(
688
+ "div",
689
+ {
690
+ className: "flex size-full items-center justify-center text-muted-foreground cursor-pointer hover:bg-secondary/50 rounded-lg transition-colors",
691
+ onClick: () => handleAttachmentClick(attachment),
692
+ children: /* @__PURE__ */ jsx9(PaperclipIcon2, { className: "size-4" })
693
+ }
694
+ )
695
+ },
696
+ index
697
+ )) });
698
+ }
699
+
700
+ // src/components/interface.tsx
701
+ import { useState as useState4, useEffect as useEffect4, useRef as useRef3, useMemo as useMemo2, useCallback as useCallback3 } from "react";
702
+ import { HistoryIcon, MessageSquareIcon, SearchIcon, ChevronRightIcon as ChevronRightIcon2, PlusIcon as PlusIcon2, XIcon as XIcon2 } from "lucide-react";
703
+ import { Fragment as Fragment5 } from "react";
704
+ import { useChat } from "@ai-sdk/react";
705
+ import { DefaultChatTransport } from "ai";
706
+
707
+ // src/components/response.tsx
708
+ import { memo } from "react";
709
+ import { Streamdown } from "streamdown";
710
+
711
+ // src/hooks/use-code-scroll.ts
712
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
713
+ function useCodeBlockAutoScroll(isStreaming) {
714
+ const observerRef = useRef2(null);
715
+ const containerRef = useRef2(null);
716
+ useEffect2(() => {
717
+ if (!isStreaming || !containerRef.current) {
718
+ if (observerRef.current) {
719
+ observerRef.current.disconnect();
720
+ observerRef.current = null;
721
+ }
722
+ return;
723
+ }
724
+ observerRef.current = new MutationObserver(() => {
725
+ if (!containerRef.current) return;
726
+ const codeBlocks = containerRef.current.querySelectorAll("pre");
727
+ codeBlocks.forEach((pre) => {
728
+ pre.scrollTop = pre.scrollHeight;
729
+ });
730
+ });
731
+ observerRef.current.observe(containerRef.current, {
732
+ childList: true,
733
+ subtree: true,
734
+ characterData: true
735
+ });
736
+ return () => {
737
+ if (observerRef.current) {
738
+ observerRef.current.disconnect();
739
+ }
740
+ };
741
+ }, [isStreaming]);
742
+ return containerRef;
743
+ }
744
+
745
+ // src/components/response.tsx
746
+ import { jsx as jsx10 } from "react/jsx-runtime";
747
+ var Response = memo(
748
+ ({ className, isStreaming = false, ...props }) => {
749
+ const containerRef = useCodeBlockAutoScroll(isStreaming);
750
+ return /* @__PURE__ */ jsx10("div", { ref: containerRef, children: /* @__PURE__ */ jsx10(
751
+ Streamdown,
752
+ {
753
+ className: cn(
754
+ "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
755
+ className
756
+ ),
757
+ ...props
758
+ }
759
+ ) });
760
+ },
761
+ (prevProps, nextProps) => prevProps.children === nextProps.children
762
+ );
763
+ Response.displayName = "Response";
764
+
765
+ // src/ui/collapsible.tsx
766
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
767
+ import { jsx as jsx11 } from "react/jsx-runtime";
768
+ function Collapsible({
769
+ ...props
770
+ }) {
771
+ return /* @__PURE__ */ jsx11(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
772
+ }
773
+ function CollapsibleTrigger2({
774
+ ...props
775
+ }) {
776
+ return /* @__PURE__ */ jsx11(
777
+ CollapsiblePrimitive.CollapsibleTrigger,
778
+ {
779
+ "data-slot": "collapsible-trigger",
780
+ ...props
781
+ }
782
+ );
783
+ }
784
+ function CollapsibleContent2({
785
+ ...props
786
+ }) {
787
+ return /* @__PURE__ */ jsx11(
788
+ CollapsiblePrimitive.CollapsibleContent,
789
+ {
790
+ "data-slot": "collapsible-content",
791
+ ...props
792
+ }
793
+ );
794
+ }
795
+
796
+ // src/components/sources.tsx
797
+ import { BookIcon, ChevronDownIcon as ChevronDownIcon2 } from "lucide-react";
798
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
799
+ var Sources = ({ className, ...props }) => /* @__PURE__ */ jsx12(
800
+ Collapsible,
801
+ {
802
+ className: cn("not-prose mb-4 text-primary text-xs", className),
803
+ ...props
804
+ }
805
+ );
806
+ var SourcesTrigger = ({
807
+ className,
808
+ count,
809
+ children,
810
+ ...props
811
+ }) => /* @__PURE__ */ jsx12(
812
+ CollapsibleTrigger2,
813
+ {
814
+ className: cn("flex items-center gap-2", className),
815
+ ...props,
816
+ children: children ?? /* @__PURE__ */ jsxs6(Fragment3, { children: [
817
+ /* @__PURE__ */ jsxs6("p", { className: "font-medium", children: [
818
+ "Used ",
819
+ count,
820
+ " sources"
821
+ ] }),
822
+ /* @__PURE__ */ jsx12(ChevronDownIcon2, { className: "h-4 w-4" })
823
+ ] })
824
+ }
825
+ );
826
+ var SourcesContent = ({
827
+ className,
828
+ ...props
829
+ }) => /* @__PURE__ */ jsx12(
830
+ CollapsibleContent2,
831
+ {
832
+ className: cn(
833
+ "mt-3 flex w-fit flex-col gap-2",
834
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
835
+ className
836
+ ),
837
+ ...props
838
+ }
839
+ );
840
+ var Source = ({ href, title, children, ...props }) => /* @__PURE__ */ jsx12(
841
+ "a",
842
+ {
843
+ className: "flex items-center gap-2",
844
+ href,
845
+ rel: "noreferrer",
846
+ target: "_blank",
847
+ ...props,
848
+ children: children ?? /* @__PURE__ */ jsxs6(Fragment3, { children: [
849
+ /* @__PURE__ */ jsx12(BookIcon, { className: "h-4 w-4" }),
850
+ /* @__PURE__ */ jsx12("span", { className: "block font-medium", children: title })
851
+ ] })
852
+ }
853
+ );
854
+
855
+ // src/components/reasoning.tsx
856
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
857
+ import { BrainIcon, ChevronDownIcon as ChevronDownIcon3 } from "lucide-react";
858
+ import { createContext as createContext2, memo as memo2, useContext as useContext2, useEffect as useEffect3, useState as useState2 } from "react";
859
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
860
+ var ReasoningContext = createContext2(null);
861
+ var useReasoning = () => {
862
+ const context = useContext2(ReasoningContext);
863
+ if (!context) {
864
+ throw new Error("Reasoning components must be used within Reasoning");
865
+ }
866
+ return context;
867
+ };
868
+ var AUTO_CLOSE_DELAY = 1e3;
869
+ var MS_IN_S = 1e3;
870
+ var Reasoning = memo2(
871
+ ({
872
+ className,
873
+ isStreaming = false,
874
+ open,
875
+ defaultOpen = true,
876
+ onOpenChange,
877
+ duration: durationProp,
878
+ children,
879
+ ...props
880
+ }) => {
881
+ const [isOpen, setIsOpen] = useControllableState({
882
+ prop: open,
883
+ defaultProp: defaultOpen,
884
+ onChange: onOpenChange
885
+ });
886
+ const [duration, setDuration] = useControllableState({
887
+ prop: durationProp,
888
+ defaultProp: void 0
889
+ });
890
+ const [hasAutoClosed, setHasAutoClosed] = useState2(false);
891
+ const [startTime, setStartTime] = useState2(null);
892
+ useEffect3(() => {
893
+ if (isStreaming) {
894
+ if (startTime === null) {
895
+ setStartTime(Date.now());
896
+ }
897
+ } else if (startTime !== null) {
898
+ setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
899
+ setStartTime(null);
900
+ }
901
+ }, [isStreaming, startTime, setDuration]);
902
+ useEffect3(() => {
903
+ if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed && durationProp !== void 0) {
904
+ const timer = setTimeout(() => {
905
+ setIsOpen(false);
906
+ setHasAutoClosed(true);
907
+ }, AUTO_CLOSE_DELAY);
908
+ return () => clearTimeout(timer);
909
+ }
910
+ }, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed, durationProp]);
911
+ const handleOpenChange = (newOpen) => {
912
+ setIsOpen(newOpen);
913
+ };
914
+ return /* @__PURE__ */ jsx13(
915
+ ReasoningContext.Provider,
916
+ {
917
+ value: { isStreaming, isOpen, setIsOpen, duration },
918
+ children: /* @__PURE__ */ jsx13(
919
+ Collapsible,
920
+ {
921
+ className: cn("not-prose", className),
922
+ onOpenChange: handleOpenChange,
923
+ open: isOpen,
924
+ ...props,
925
+ children
926
+ }
927
+ )
928
+ }
929
+ );
930
+ }
931
+ );
932
+ var getThinkingMessage = (isStreaming, duration) => {
933
+ if (isStreaming) {
934
+ return /* @__PURE__ */ jsx13(Fragment4, { children: "Thinking..." });
935
+ }
936
+ if (duration === void 0) {
937
+ return /* @__PURE__ */ jsx13(Fragment4, { children: "Thought process" });
938
+ }
939
+ if (duration === 0) {
940
+ return /* @__PURE__ */ jsx13(Fragment4, { children: "Thought process" });
941
+ }
942
+ return /* @__PURE__ */ jsxs7(Fragment4, { children: [
943
+ "Thought for ",
944
+ duration,
945
+ " seconds"
946
+ ] });
947
+ };
948
+ var ReasoningTrigger = memo2(
949
+ ({ className, children, ...props }) => {
950
+ const { isStreaming, isOpen, duration } = useReasoning();
951
+ return /* @__PURE__ */ jsx13(
952
+ CollapsibleTrigger2,
953
+ {
954
+ className: cn(
955
+ "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
956
+ className
957
+ ),
958
+ ...props,
959
+ children: children ?? /* @__PURE__ */ jsxs7(Fragment4, { children: [
960
+ /* @__PURE__ */ jsx13(BrainIcon, { className: "size-4 flex-shrink-0" }),
961
+ getThinkingMessage(isStreaming, duration),
962
+ /* @__PURE__ */ jsx13(
963
+ ChevronDownIcon3,
964
+ {
965
+ className: cn(
966
+ "size-4 flex-shrink-0 transition-transform ml-1",
967
+ isOpen ? "rotate-180" : "rotate-0"
968
+ )
969
+ }
970
+ )
971
+ ] })
972
+ }
973
+ );
974
+ }
975
+ );
976
+ var ReasoningContent = memo2(
977
+ ({ className, children, ...props }) => /* @__PURE__ */ jsx13(
978
+ CollapsibleContent2,
979
+ {
980
+ className: cn(
981
+ "mt-4 text-sm",
982
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
983
+ className
984
+ ),
985
+ ...props,
986
+ children: /* @__PURE__ */ jsx13(Response, { className: "grid gap-2", children })
987
+ }
988
+ )
989
+ );
990
+ Reasoning.displayName = "Reasoning";
991
+ ReasoningTrigger.displayName = "ReasoningTrigger";
992
+ ReasoningContent.displayName = "ReasoningContent";
993
+
994
+ // src/components/loader.tsx
995
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
996
+ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs8(
997
+ "svg",
998
+ {
999
+ height: size,
1000
+ strokeLinejoin: "round",
1001
+ style: { color: "currentcolor" },
1002
+ viewBox: "0 0 16 16",
1003
+ width: size,
1004
+ children: [
1005
+ /* @__PURE__ */ jsx14("title", { children: "Loader" }),
1006
+ /* @__PURE__ */ jsxs8("g", { clipPath: "url(#clip0_2393_1490)", children: [
1007
+ /* @__PURE__ */ jsx14("path", { d: "M8 0V4", stroke: "currentColor", strokeWidth: "1.5" }),
1008
+ /* @__PURE__ */ jsx14(
1009
+ "path",
1010
+ {
1011
+ d: "M8 16V12",
1012
+ opacity: "0.5",
1013
+ stroke: "currentColor",
1014
+ strokeWidth: "1.5"
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ jsx14(
1018
+ "path",
1019
+ {
1020
+ d: "M3.29773 1.52783L5.64887 4.7639",
1021
+ opacity: "0.9",
1022
+ stroke: "currentColor",
1023
+ strokeWidth: "1.5"
1024
+ }
1025
+ ),
1026
+ /* @__PURE__ */ jsx14(
1027
+ "path",
1028
+ {
1029
+ d: "M12.7023 1.52783L10.3511 4.7639",
1030
+ opacity: "0.1",
1031
+ stroke: "currentColor",
1032
+ strokeWidth: "1.5"
1033
+ }
1034
+ ),
1035
+ /* @__PURE__ */ jsx14(
1036
+ "path",
1037
+ {
1038
+ d: "M12.7023 14.472L10.3511 11.236",
1039
+ opacity: "0.4",
1040
+ stroke: "currentColor",
1041
+ strokeWidth: "1.5"
1042
+ }
1043
+ ),
1044
+ /* @__PURE__ */ jsx14(
1045
+ "path",
1046
+ {
1047
+ d: "M3.29773 14.472L5.64887 11.236",
1048
+ opacity: "0.6",
1049
+ stroke: "currentColor",
1050
+ strokeWidth: "1.5"
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsx14(
1054
+ "path",
1055
+ {
1056
+ d: "M15.6085 5.52783L11.8043 6.7639",
1057
+ opacity: "0.2",
1058
+ stroke: "currentColor",
1059
+ strokeWidth: "1.5"
1060
+ }
1061
+ ),
1062
+ /* @__PURE__ */ jsx14(
1063
+ "path",
1064
+ {
1065
+ d: "M0.391602 10.472L4.19583 9.23598",
1066
+ opacity: "0.7",
1067
+ stroke: "currentColor",
1068
+ strokeWidth: "1.5"
1069
+ }
1070
+ ),
1071
+ /* @__PURE__ */ jsx14(
1072
+ "path",
1073
+ {
1074
+ d: "M15.6085 10.4722L11.8043 9.2361",
1075
+ opacity: "0.3",
1076
+ stroke: "currentColor",
1077
+ strokeWidth: "1.5"
1078
+ }
1079
+ ),
1080
+ /* @__PURE__ */ jsx14(
1081
+ "path",
1082
+ {
1083
+ d: "M0.391602 5.52783L4.19583 6.7639",
1084
+ opacity: "0.8",
1085
+ stroke: "currentColor",
1086
+ strokeWidth: "1.5"
1087
+ }
1088
+ )
1089
+ ] }),
1090
+ /* @__PURE__ */ jsx14("defs", { children: /* @__PURE__ */ jsx14("clipPath", { id: "clip0_2393_1490", children: /* @__PURE__ */ jsx14("rect", { fill: "white", height: "16", width: "16" }) }) })
1091
+ ]
1092
+ }
1093
+ );
1094
+ var Loader = ({ className, size = 16, ...props }) => /* @__PURE__ */ jsx14(
1095
+ "div",
1096
+ {
1097
+ className: cn(
1098
+ "inline-flex animate-spin items-center justify-center",
1099
+ className
1100
+ ),
1101
+ ...props,
1102
+ children: /* @__PURE__ */ jsx14(LoaderIcon, { size })
1103
+ }
1104
+ );
1105
+
1106
+ // src/ui/badge.tsx
1107
+ import { Slot as Slot2 } from "@radix-ui/react-slot";
1108
+ import { cva as cva3 } from "class-variance-authority";
1109
+ import { jsx as jsx15 } from "react/jsx-runtime";
1110
+ var badgeVariants = cva3(
1111
+ "inline-flex items-center justify-center rounded-md border 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",
1112
+ {
1113
+ variants: {
1114
+ variant: {
1115
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
1116
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
1117
+ destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1118
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
1119
+ }
1120
+ },
1121
+ defaultVariants: {
1122
+ variant: "default"
1123
+ }
1124
+ }
1125
+ );
1126
+ function Badge({
1127
+ className,
1128
+ variant,
1129
+ asChild = false,
1130
+ ...props
1131
+ }) {
1132
+ const Comp = asChild ? Slot2 : "span";
1133
+ return /* @__PURE__ */ jsx15(
1134
+ Comp,
1135
+ {
1136
+ "data-slot": "badge",
1137
+ className: cn(badgeVariants({ variant }), className),
1138
+ ...props
1139
+ }
1140
+ );
1141
+ }
1142
+
1143
+ // src/components/tool.tsx
1144
+ import {
1145
+ CheckCircleIcon,
1146
+ ChevronDownIcon as ChevronDownIcon4,
1147
+ CircleIcon as CircleIcon2,
1148
+ ClockIcon,
1149
+ WrenchIcon,
1150
+ XCircleIcon
1151
+ } from "lucide-react";
1152
+ import { isValidElement } from "react";
1153
+
1154
+ // src/components/code-block.tsx
1155
+ import { CheckIcon as CheckIcon3, CopyIcon } from "lucide-react";
1156
+ import { createContext as createContext3, useContext as useContext3, useState as useState3 } from "react";
1157
+ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
1158
+ import {
1159
+ oneDark,
1160
+ oneLight
1161
+ } from "react-syntax-highlighter/dist/esm/styles/prism";
1162
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1163
+ var CodeBlockContext = createContext3({
1164
+ code: ""
1165
+ });
1166
+ var CodeBlock = ({
1167
+ code,
1168
+ language,
1169
+ showLineNumbers = false,
1170
+ className,
1171
+ children,
1172
+ ...props
1173
+ }) => /* @__PURE__ */ jsx16(CodeBlockContext.Provider, { value: { code }, children: /* @__PURE__ */ jsx16(
1174
+ "div",
1175
+ {
1176
+ className: cn(
1177
+ "relative w-full overflow-hidden rounded-md border bg-background text-foreground",
1178
+ className
1179
+ ),
1180
+ ...props,
1181
+ children: /* @__PURE__ */ jsxs9("div", { className: "relative max-h-96 overflow-y-auto", children: [
1182
+ /* @__PURE__ */ jsx16(
1183
+ SyntaxHighlighter,
1184
+ {
1185
+ className: "overflow-hidden dark:hidden",
1186
+ codeTagProps: {
1187
+ className: "font-mono text-sm"
1188
+ },
1189
+ customStyle: {
1190
+ margin: 0,
1191
+ padding: "1rem",
1192
+ fontSize: "0.875rem",
1193
+ background: "hsl(var(--background))",
1194
+ color: "hsl(var(--foreground))"
1195
+ },
1196
+ language,
1197
+ lineNumberStyle: {
1198
+ color: "hsl(var(--muted-foreground))",
1199
+ paddingRight: "1rem",
1200
+ minWidth: "2.5rem"
1201
+ },
1202
+ showLineNumbers,
1203
+ style: oneLight,
1204
+ children: code
1205
+ }
1206
+ ),
1207
+ /* @__PURE__ */ jsx16(
1208
+ SyntaxHighlighter,
1209
+ {
1210
+ className: "hidden overflow-hidden dark:block",
1211
+ codeTagProps: {
1212
+ className: "font-mono text-sm"
1213
+ },
1214
+ customStyle: {
1215
+ margin: 0,
1216
+ padding: "1rem",
1217
+ fontSize: "0.875rem",
1218
+ background: "hsl(var(--background))",
1219
+ color: "hsl(var(--foreground))"
1220
+ },
1221
+ language,
1222
+ lineNumberStyle: {
1223
+ color: "hsl(var(--muted-foreground))",
1224
+ paddingRight: "1rem",
1225
+ minWidth: "2.5rem"
1226
+ },
1227
+ showLineNumbers,
1228
+ style: oneDark,
1229
+ children: code
1230
+ }
1231
+ ),
1232
+ children && /* @__PURE__ */ jsx16("div", { className: "absolute top-2 right-2 flex items-center gap-2", children })
1233
+ ] })
1234
+ }
1235
+ ) });
1236
+
1237
+ // src/components/tool.tsx
1238
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1239
+ var Tool = ({ className, ...props }) => /* @__PURE__ */ jsx17(
1240
+ Collapsible,
1241
+ {
1242
+ className: cn("not-prose w-full rounded-md border", className),
1243
+ ...props
1244
+ }
1245
+ );
1246
+ var getStatusBadge = (status) => {
1247
+ const labels = {
1248
+ "input-streaming": "Pending",
1249
+ "input-available": "Running",
1250
+ "output-available": "Completed",
1251
+ "output-error": "Error"
1252
+ };
1253
+ const icons = {
1254
+ "input-streaming": /* @__PURE__ */ jsx17(CircleIcon2, { className: "size-4" }),
1255
+ "input-available": /* @__PURE__ */ jsx17(ClockIcon, { className: "size-4 animate-pulse" }),
1256
+ "output-available": /* @__PURE__ */ jsx17(CheckCircleIcon, { className: "size-4 text-green-600" }),
1257
+ "output-error": /* @__PURE__ */ jsx17(XCircleIcon, { className: "size-4 text-red-600" })
1258
+ };
1259
+ return /* @__PURE__ */ jsxs10(Badge, { className: "gap-1.5 rounded-full text-xs", variant: "secondary", children: [
1260
+ icons[status],
1261
+ labels[status]
1262
+ ] });
1263
+ };
1264
+ var ToolHeader = ({
1265
+ className,
1266
+ title,
1267
+ type,
1268
+ state,
1269
+ ...props
1270
+ }) => /* @__PURE__ */ jsxs10(
1271
+ CollapsibleTrigger2,
1272
+ {
1273
+ className: cn(
1274
+ "flex w-full items-center justify-between gap-4 p-2",
1275
+ className
1276
+ ),
1277
+ ...props,
1278
+ children: [
1279
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1280
+ /* @__PURE__ */ jsx17(WrenchIcon, { className: "size-4 text-muted-foreground" }),
1281
+ /* @__PURE__ */ jsx17("span", { className: "font-medium text-sm", children: title ?? type.split("-").slice(1).join("-") }),
1282
+ getStatusBadge(state)
1283
+ ] }),
1284
+ /* @__PURE__ */ jsx17(ChevronDownIcon4, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
1285
+ ]
1286
+ }
1287
+ );
1288
+ var ToolContent = ({ className, ...props }) => /* @__PURE__ */ jsx17(
1289
+ CollapsibleContent2,
1290
+ {
1291
+ className: cn(
1292
+ "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
1293
+ className
1294
+ ),
1295
+ ...props
1296
+ }
1297
+ );
1298
+ var ToolInput = ({ className, input, ...props }) => /* @__PURE__ */ jsxs10("div", { className: cn("space-y-2 overflow-hidden p-2", className), ...props, children: [
1299
+ /* @__PURE__ */ jsx17("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Parameters" }),
1300
+ /* @__PURE__ */ jsx17("div", { className: "rounded-md bg-muted/50", children: /* @__PURE__ */ jsx17(CodeBlock, { code: JSON.stringify(input, null, 2), language: "json" }) })
1301
+ ] });
1302
+ var ToolOutput = ({
1303
+ className,
1304
+ output,
1305
+ errorText,
1306
+ ...props
1307
+ }) => {
1308
+ if (!(output || errorText)) {
1309
+ return null;
1310
+ }
1311
+ let Output = /* @__PURE__ */ jsx17("div", { children: output });
1312
+ if (typeof output === "object" && !isValidElement(output)) {
1313
+ Output = /* @__PURE__ */ jsx17(CodeBlock, { code: JSON.stringify(output, null, 2), language: "json" });
1314
+ } else if (typeof output === "string") {
1315
+ Output = /* @__PURE__ */ jsx17(CodeBlock, { code: output, language: "json" });
1316
+ }
1317
+ return /* @__PURE__ */ jsxs10("div", { className: cn("space-y-2 p-2", className), ...props, children: [
1318
+ /* @__PURE__ */ jsx17("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: errorText ? "Error" : "Result" }),
1319
+ /* @__PURE__ */ jsxs10(
1320
+ "div",
1321
+ {
1322
+ className: cn(
1323
+ "overflow-x-auto rounded-md text-xs [&_table]:w-full",
1324
+ errorText ? "bg-destructive/10 text-destructive" : "bg-muted/50 text-foreground"
1325
+ ),
1326
+ children: [
1327
+ errorText && /* @__PURE__ */ jsx17("div", { children: errorText }),
1328
+ Output
1329
+ ]
1330
+ }
1331
+ )
1332
+ ] });
1333
+ };
1334
+
1335
+ // src/components/interface.tsx
1336
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1337
+ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
1338
+ const themeMode = config?.theme?.mode || "light";
1339
+ const [input, setInput] = useState4("");
1340
+ const [showHistory, setShowHistory] = useState4(false);
1341
+ const [conversations, setConversations] = useState4([]);
1342
+ const [loadingHistory, setLoadingHistory] = useState4(false);
1343
+ const [historyLoaded, setHistoryLoaded] = useState4(false);
1344
+ const [searchQuery, setSearchQuery] = useState4("");
1345
+ const [uploadError, setUploadError] = useState4(null);
1346
+ useEffect4(() => {
1347
+ if (uploadError) {
1348
+ const timeoutId = setTimeout(() => setUploadError(null), 5e3);
1349
+ return () => clearTimeout(timeoutId);
1350
+ }
1351
+ }, [uploadError]);
1352
+ const [tabs, setTabs] = useState4([]);
1353
+ const [activeTabId, setActiveTabId] = useState4("");
1354
+ const [initialTabCreated, setInitialTabCreated] = useState4(false);
1355
+ const [isInitializing, setIsInitializing] = useState4(true);
1356
+ const [componentWidth, setComponentWidth] = useState4(768);
1357
+ const [isResizing, setIsResizing] = useState4(false);
1358
+ const lastSyncedTabId = useRef3("");
1359
+ const hasInitialized = useRef3(false);
1360
+ const { messages, sendMessage, status, setMessages } = useChat({
1361
+ id: activeTabId || "temp-id",
1362
+ transport: new DefaultChatTransport({
1363
+ api: "/api/chat",
1364
+ headers: {
1365
+ "X-User-Id": config?.userId || ""
1366
+ }
1367
+ }),
1368
+ // Throttle UI updates to 200ms to prevent hanging during streaming
1369
+ experimental_throttle: 200
1370
+ });
1371
+ const handleSubmit = async (message) => {
1372
+ const hasText = Boolean(message.text);
1373
+ const hasAttachments = Boolean(message.files?.length);
1374
+ if (!(hasText || hasAttachments)) {
1375
+ return;
1376
+ }
1377
+ let uploadedFiles = [];
1378
+ if (message.files && message.files.length > 0) {
1379
+ try {
1380
+ const uploadPromises = message.files.map(async (file) => {
1381
+ try {
1382
+ const response = await fetch(file.url);
1383
+ const blob = await response.blob();
1384
+ const fileObj = new File([blob], file.filename || "unknown", { type: file.mediaType });
1385
+ const formData = new FormData();
1386
+ formData.append("file", fileObj);
1387
+ formData.append("conversationId", activeTabId || "default");
1388
+ formData.append("userId", config?.userId || "demo-user");
1389
+ const uploadResponse = await fetch("/api/chat/upload", {
1390
+ method: "POST",
1391
+ body: formData
1392
+ });
1393
+ if (!uploadResponse.ok) {
1394
+ const errorText = await uploadResponse.text();
1395
+ console.error(`Upload failed for ${file.filename}:`, errorText);
1396
+ return null;
1397
+ }
1398
+ const uploadResult = await uploadResponse.json();
1399
+ return {
1400
+ id: file.id || "unknown",
1401
+ type: "file",
1402
+ url: uploadResult.url,
1403
+ filename: uploadResult.filename,
1404
+ mediaType: uploadResult.mediaType,
1405
+ size: uploadResult.size
1406
+ };
1407
+ } catch (error) {
1408
+ console.error(`Error uploading ${file.filename}:`, error);
1409
+ return null;
1410
+ }
1411
+ });
1412
+ const results = await Promise.all(uploadPromises);
1413
+ uploadedFiles = results.filter((result) => result !== null);
1414
+ if (uploadedFiles.length === 0) {
1415
+ const errorMsg = "All file uploads failed. Please try again.";
1416
+ setUploadError(errorMsg);
1417
+ console.error(errorMsg);
1418
+ return;
1419
+ }
1420
+ if (uploadedFiles.length < message.files.length) {
1421
+ const warnMsg = `Warning: Only ${uploadedFiles.length} of ${message.files.length} files uploaded successfully.`;
1422
+ setUploadError(warnMsg);
1423
+ console.warn(warnMsg);
1424
+ }
1425
+ } catch (error) {
1426
+ const errorMsg = "Error uploading files. Please try again.";
1427
+ setUploadError(errorMsg);
1428
+ console.error("Error in file upload process:", error);
1429
+ return;
1430
+ }
1431
+ }
1432
+ sendMessage({
1433
+ text: message.text || "Sent with attachments",
1434
+ files: uploadedFiles
1435
+ });
1436
+ const activeTab = tabs.find((tab) => tab.id === activeTabId);
1437
+ if (activeTab && activeTab.title === "New Chat" && message.text) {
1438
+ const newTitle = message.text.slice(0, 100);
1439
+ setTabs(
1440
+ (prevTabs) => prevTabs.map(
1441
+ (tab) => tab.id === activeTabId ? { ...tab, title: newTitle } : tab
1442
+ )
1443
+ );
1444
+ }
1445
+ setInput("");
1446
+ };
1447
+ const AttachButton = () => {
1448
+ const attachments = usePromptInputAttachments();
1449
+ return /* @__PURE__ */ jsx18(
1450
+ PromptInputButton,
1451
+ {
1452
+ variant: "ghost",
1453
+ onClick: () => attachments.openFileDialog(),
1454
+ children: /* @__PURE__ */ jsx18(PlusIcon2, { className: "size-4" })
1455
+ }
1456
+ );
1457
+ };
1458
+ const loadConversation = async (conversationId) => {
1459
+ if (!config?.userId) {
1460
+ console.log("Cannot load conversation - no userId");
1461
+ return;
1462
+ }
1463
+ try {
1464
+ const response = await fetch(`/api/chat/history/${conversationId}?userId=${config.userId}`);
1465
+ if (response.ok) {
1466
+ const data = await response.json();
1467
+ const loadedMessages = data.messages || [];
1468
+ setTimeout(() => {
1469
+ setMessages(loadedMessages);
1470
+ }, 0);
1471
+ } else if (response.status === 404) {
1472
+ console.log("Conversation not found in database yet (new chat)");
1473
+ setMessages([]);
1474
+ } else {
1475
+ console.error("Error loading messages:", response.status, response.statusText);
1476
+ }
1477
+ } catch (error) {
1478
+ console.error("Error loading conversation:", error);
1479
+ }
1480
+ };
1481
+ const generateUniqueTabId = () => {
1482
+ let newTabId;
1483
+ let attempts = 0;
1484
+ do {
1485
+ newTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1486
+ attempts++;
1487
+ } while (tabs.find((tab) => tab.id === newTabId) && attempts < 10);
1488
+ if (attempts >= 10) {
1489
+ throw new Error("Unable to generate unique tab ID");
1490
+ }
1491
+ return newTabId;
1492
+ };
1493
+ const createNewTab = useCallback3(() => {
1494
+ if (!initialTabCreated) {
1495
+ console.warn("Cannot create new tab while initializing");
1496
+ return;
1497
+ }
1498
+ const newTabId = generateUniqueTabId();
1499
+ setTabs((prevTabs) => {
1500
+ const existingTab = prevTabs.find((tab) => tab.id === newTabId);
1501
+ if (existingTab) {
1502
+ console.warn("Tab with ID already exists:", newTabId);
1503
+ return prevTabs;
1504
+ }
1505
+ const newTab = {
1506
+ id: newTabId,
1507
+ title: "New Chat",
1508
+ isActive: true
1509
+ };
1510
+ const updatedTabs = prevTabs.map((tab) => ({
1511
+ ...tab,
1512
+ isActive: false
1513
+ }));
1514
+ return [...updatedTabs, newTab];
1515
+ });
1516
+ setActiveTabId(newTabId);
1517
+ setMessages([]);
1518
+ setInput("");
1519
+ }, [initialTabCreated]);
1520
+ const startNewConversation = useCallback3(() => {
1521
+ createNewTab();
1522
+ }, [createNewTab]);
1523
+ useEffect4(() => {
1524
+ return () => {
1525
+ };
1526
+ }, []);
1527
+ const switchToTab = async (tabId) => {
1528
+ const targetTab = tabs.find((tab) => tab.id === tabId);
1529
+ if (!targetTab) return;
1530
+ setTabs(
1531
+ (prevTabs) => prevTabs.map((tab) => ({
1532
+ ...tab,
1533
+ isActive: tab.id === tabId
1534
+ }))
1535
+ );
1536
+ setActiveTabId(tabId);
1537
+ await loadConversation(tabId);
1538
+ };
1539
+ const closeTab = (tabId) => {
1540
+ if (tabs.length <= 1) return;
1541
+ const filteredTabs = tabs.filter((tab) => tab.id !== tabId);
1542
+ if (tabId === activeTabId && filteredTabs.length > 0) {
1543
+ const newActiveTab = filteredTabs[0];
1544
+ setTabs(filteredTabs.map((tab) => ({
1545
+ ...tab,
1546
+ isActive: tab.id === newActiveTab.id
1547
+ })));
1548
+ switchToTab(newActiveTab.id);
1549
+ } else {
1550
+ setTabs(filteredTabs);
1551
+ }
1552
+ if (filteredTabs.length > 0) {
1553
+ localStorage.setItem("chat-tabs", JSON.stringify(filteredTabs));
1554
+ if (tabId === activeTabId) {
1555
+ const newActiveTab = filteredTabs[0];
1556
+ localStorage.setItem("active-tab-id", newActiveTab.id);
1557
+ }
1558
+ }
1559
+ };
1560
+ const fetchConversations = async () => {
1561
+ if (historyLoaded) return;
1562
+ if (!config?.userId) {
1563
+ return;
1564
+ }
1565
+ setLoadingHistory(true);
1566
+ try {
1567
+ const response = await fetch(`/api/chat/history?userId=${config.userId}`);
1568
+ if (response.ok) {
1569
+ const data = await response.json();
1570
+ setConversations(data.conversations || []);
1571
+ setHistoryLoaded(true);
1572
+ } else {
1573
+ console.error("[ChatInterface] Failed to fetch chat history, status:", response.status);
1574
+ const errorText = await response.text();
1575
+ console.error("[ChatInterface] Error response:", errorText);
1576
+ }
1577
+ } catch (error) {
1578
+ console.error("[ChatInterface] Error fetching chat history:", error);
1579
+ } finally {
1580
+ setLoadingHistory(false);
1581
+ }
1582
+ };
1583
+ useEffect4(() => {
1584
+ if (showHistory && !historyLoaded && config?.userId) {
1585
+ fetchConversations();
1586
+ }
1587
+ }, [showHistory, historyLoaded, config?.userId]);
1588
+ useEffect4(() => {
1589
+ if (!historyLoaded && config?.userId) {
1590
+ fetchConversations();
1591
+ }
1592
+ }, [historyLoaded, config?.userId]);
1593
+ useEffect4(() => {
1594
+ if (tabs.length > 0) {
1595
+ const timeoutId = setTimeout(() => {
1596
+ localStorage.setItem("chat-tabs", JSON.stringify(tabs));
1597
+ localStorage.setItem("active-tab-id", activeTabId);
1598
+ }, 500);
1599
+ return () => clearTimeout(timeoutId);
1600
+ }
1601
+ }, [tabs, activeTabId]);
1602
+ useEffect4(() => {
1603
+ if (hasInitialized.current) return;
1604
+ const loadInitialTabs = () => {
1605
+ try {
1606
+ const savedTabs = localStorage.getItem("chat-tabs");
1607
+ const savedActiveTabId = localStorage.getItem("active-tab-id");
1608
+ if (savedTabs && savedTabs !== "[]") {
1609
+ const parsedTabs = JSON.parse(savedTabs);
1610
+ setTabs(parsedTabs);
1611
+ const activeId = savedActiveTabId || parsedTabs[0]?.id;
1612
+ setActiveTabId(activeId);
1613
+ setInitialTabCreated(true);
1614
+ } else if (!initialTabCreated && tabs.length === 0) {
1615
+ const initialTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1616
+ const currentTab = {
1617
+ id: initialTabId,
1618
+ title: "New Chat",
1619
+ isActive: true
1620
+ };
1621
+ setTabs([currentTab]);
1622
+ setActiveTabId(initialTabId);
1623
+ setInitialTabCreated(true);
1624
+ }
1625
+ } finally {
1626
+ setIsInitializing(false);
1627
+ }
1628
+ };
1629
+ loadInitialTabs();
1630
+ hasInitialized.current = true;
1631
+ }, []);
1632
+ const hasLoadedInitialMessages = useRef3(false);
1633
+ useEffect4(() => {
1634
+ if (hasLoadedInitialMessages.current) return;
1635
+ if (!config?.userId) return;
1636
+ if (!activeTabId) return;
1637
+ if (isInitializing) return;
1638
+ loadConversation(activeTabId);
1639
+ hasLoadedInitialMessages.current = true;
1640
+ }, [config?.userId, activeTabId, isInitializing]);
1641
+ useEffect4(() => {
1642
+ if (isInitializing) return;
1643
+ if (activeTabId && tabs.length > 0 && activeTabId !== lastSyncedTabId.current) {
1644
+ lastSyncedTabId.current = activeTabId;
1645
+ setInput("");
1646
+ }
1647
+ }, [activeTabId, isInitializing, tabs.length]);
1648
+ const groupedConversations = useMemo2(() => {
1649
+ const filtered = conversations.filter(
1650
+ (conv) => searchQuery === "" || conv.title.toLowerCase().includes(searchQuery.toLowerCase())
1651
+ );
1652
+ const groups = {};
1653
+ const now = /* @__PURE__ */ new Date();
1654
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1655
+ const yesterday = new Date(today);
1656
+ yesterday.setDate(yesterday.getDate() - 1);
1657
+ filtered.forEach((conv) => {
1658
+ const convDate = new Date(conv.updated_at);
1659
+ const diffTime = now.getTime() - convDate.getTime();
1660
+ const diffDays = Math.floor(diffTime / (1e3 * 60 * 60 * 24));
1661
+ const diffWeeks = Math.floor(diffDays / 7);
1662
+ const diffMonths = Math.floor(diffDays / 30);
1663
+ let groupKey;
1664
+ if (convDate >= today) {
1665
+ groupKey = "Today";
1666
+ } else if (convDate >= yesterday) {
1667
+ groupKey = "Yesterday";
1668
+ } else if (diffDays <= 7) {
1669
+ groupKey = `${diffDays}d ago`;
1670
+ } else if (diffWeeks <= 4) {
1671
+ groupKey = `${diffWeeks}w ago`;
1672
+ } else if (diffMonths <= 12) {
1673
+ groupKey = `${diffMonths}mo ago`;
1674
+ } else {
1675
+ const diffYears = Math.floor(diffMonths / 12);
1676
+ groupKey = `${diffYears}y ago`;
1677
+ }
1678
+ if (!groups[groupKey]) {
1679
+ groups[groupKey] = [];
1680
+ }
1681
+ groups[groupKey].push(conv);
1682
+ });
1683
+ const sortedGroups = Object.entries(groups).sort((a, b) => {
1684
+ const order = ["Today", "Yesterday"];
1685
+ const aIndex = order.indexOf(a[0]);
1686
+ const bIndex = order.indexOf(b[0]);
1687
+ if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;
1688
+ if (aIndex !== -1) return -1;
1689
+ if (bIndex !== -1) return 1;
1690
+ const aMatch = a[0].match(/(\d+)([dw]|mo|y)/);
1691
+ const bMatch = b[0].match(/(\d+)([dw]|mo|y)/);
1692
+ if (aMatch && bMatch) {
1693
+ const aNum = parseInt(aMatch[1]);
1694
+ const bNum = parseInt(bMatch[1]);
1695
+ const aUnit = aMatch[2];
1696
+ const bUnit = bMatch[2];
1697
+ const unitToDays = { "d": 1, "w": 7, "mo": 30, "y": 365 };
1698
+ const aDays = aNum * unitToDays[aUnit];
1699
+ const bDays = bNum * unitToDays[bUnit];
1700
+ return aDays - bDays;
1701
+ }
1702
+ return 0;
1703
+ });
1704
+ return sortedGroups;
1705
+ }, [conversations, searchQuery]);
1706
+ const renderMessages = () => messages.map((message, index) => {
1707
+ const sourceParts = message.parts?.filter((part) => part.type === "source-url") || [];
1708
+ const fileParts = message.parts?.filter((part) => part.type === "file") || [];
1709
+ return /* @__PURE__ */ jsxs11("div", { className: index > 0 ? "mt-6" : "", children: [
1710
+ message.role === "assistant" && sourceParts.length > 0 && /* @__PURE__ */ jsxs11(Sources, { children: [
1711
+ /* @__PURE__ */ jsx18(SourcesTrigger, { count: sourceParts.length }),
1712
+ sourceParts.map((part, i) => /* @__PURE__ */ jsx18(SourcesContent, { children: /* @__PURE__ */ jsx18(
1713
+ Source,
1714
+ {
1715
+ href: part.url,
1716
+ title: part.url
1717
+ },
1718
+ `${message.id}-${i}`
1719
+ ) }, `${message.id}-${i}`))
1720
+ ] }),
1721
+ fileParts.length > 0 && /* @__PURE__ */ jsx18("div", { className: cn(
1722
+ "flex mb-1",
1723
+ message.role === "user" ? "justify-end" : "justify-start"
1724
+ ), children: /* @__PURE__ */ jsx18(
1725
+ MessageAttachments,
1726
+ {
1727
+ attachments: fileParts.map((part) => ({
1728
+ filename: part.filename || "unknown",
1729
+ mediaType: part.mediaType,
1730
+ url: part.url,
1731
+ size: part.size || 0
1732
+ }))
1733
+ }
1734
+ ) }),
1735
+ message.parts ? /* @__PURE__ */ jsx18("div", { className: "space-y-2", children: message.parts.map((part, i) => {
1736
+ switch (part.type) {
1737
+ case "text":
1738
+ const isTextStreaming = status === "streaming" && i === message.parts.length - 1 && message.id === messages.at(-1)?.id;
1739
+ return /* @__PURE__ */ jsx18(Fragment5, { children: /* @__PURE__ */ jsx18(Message, { from: message.role, children: /* @__PURE__ */ jsx18(MessageContent, { children: /* @__PURE__ */ jsx18(Response, { isStreaming: isTextStreaming, children: part.text }) }) }) }, `${message.id}-${i}`);
1740
+ case "reasoning":
1741
+ const isCurrentlyStreaming = status === "streaming" && i === message.parts.length - 1 && message.id === messages.at(-1)?.id;
1742
+ return /* @__PURE__ */ jsxs11(
1743
+ Reasoning,
1744
+ {
1745
+ className: "w-full",
1746
+ isStreaming: isCurrentlyStreaming,
1747
+ defaultOpen: false,
1748
+ open: isCurrentlyStreaming ? true : void 0,
1749
+ children: [
1750
+ /* @__PURE__ */ jsx18(ReasoningTrigger, {}),
1751
+ /* @__PURE__ */ jsx18(ReasoningContent, { children: part.text })
1752
+ ]
1753
+ },
1754
+ `${message.id}-${i}`
1755
+ );
1756
+ default:
1757
+ if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
1758
+ const toolPart = part;
1759
+ return /* @__PURE__ */ jsxs11(Tool, { children: [
1760
+ /* @__PURE__ */ jsx18(ToolHeader, { type: part.type, state: toolPart.state }),
1761
+ /* @__PURE__ */ jsxs11(ToolContent, { children: [
1762
+ /* @__PURE__ */ jsx18(ToolInput, { input: toolPart.input }),
1763
+ /* @__PURE__ */ jsx18(ToolOutput, { output: toolPart.output, errorText: toolPart.errorText })
1764
+ ] })
1765
+ ] }, `${message.id}-${i}`);
1766
+ }
1767
+ return null;
1768
+ }
1769
+ }) }) : (
1770
+ /* Handle standard AI SDK messages with content or text property */
1771
+ /* @__PURE__ */ jsx18(Fragment5, { children: /* @__PURE__ */ jsx18(Message, { from: message.role, children: /* @__PURE__ */ jsx18(MessageContent, { children: /* @__PURE__ */ jsx18(Response, { children: message.content || message.text }) }) }) }, `${message.id}-content`)
1772
+ )
1773
+ ] }, message.id);
1774
+ });
1775
+ const handleSelectConversation = async (selectedConversationId, conversationTitle) => {
1776
+ if (!config?.userId) return;
1777
+ try {
1778
+ const existingTab = tabs.find((tab) => tab.id === selectedConversationId);
1779
+ if (existingTab) {
1780
+ switchToTab(selectedConversationId);
1781
+ setShowHistory(false);
1782
+ return;
1783
+ }
1784
+ const newTab = {
1785
+ id: selectedConversationId,
1786
+ title: conversationTitle,
1787
+ isActive: true
1788
+ };
1789
+ setTabs((prevTabs) => {
1790
+ const updatedTabs = prevTabs.map((tab) => ({
1791
+ ...tab,
1792
+ isActive: false
1793
+ }));
1794
+ return [...updatedTabs, newTab];
1795
+ });
1796
+ setActiveTabId(selectedConversationId);
1797
+ await loadConversation(selectedConversationId);
1798
+ setShowHistory(false);
1799
+ } catch (error) {
1800
+ console.error("Error loading conversation:", error);
1801
+ }
1802
+ };
1803
+ const dropdownRef = useRef3(null);
1804
+ useEffect4(() => {
1805
+ const handleClickOutside = (event) => {
1806
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
1807
+ setShowHistory(false);
1808
+ }
1809
+ };
1810
+ if (showHistory) {
1811
+ document.addEventListener("mousedown", handleClickOutside);
1812
+ }
1813
+ return () => {
1814
+ document.removeEventListener("mousedown", handleClickOutside);
1815
+ };
1816
+ }, [showHistory]);
1817
+ useEffect4(() => {
1818
+ if (!isResizing) return;
1819
+ const handleMouseMove = (e) => {
1820
+ const newWidth = window.innerWidth - e.clientX;
1821
+ const minWidth = 300;
1822
+ const maxWidth = window.innerWidth * 0.8;
1823
+ setComponentWidth(Math.max(minWidth, Math.min(maxWidth, newWidth)));
1824
+ };
1825
+ const handleMouseUp = () => {
1826
+ setIsResizing(false);
1827
+ };
1828
+ document.addEventListener("mousemove", handleMouseMove);
1829
+ document.addEventListener("mouseup", handleMouseUp);
1830
+ return () => {
1831
+ document.removeEventListener("mousemove", handleMouseMove);
1832
+ document.removeEventListener("mouseup", handleMouseUp);
1833
+ };
1834
+ }, [isResizing]);
1835
+ return /* @__PURE__ */ jsx18("div", { className: cn("w-full h-full flex flex-col bg-white dark:bg-gray-900 overflow-hidden ring-1 ring-black/[0.02] dark:ring-white/[0.03]", themeMode === "dark" && "dark"), children: /* @__PURE__ */ jsxs11(
1836
+ "div",
1837
+ {
1838
+ className: cn(
1839
+ "flex flex-col h-full w-full overflow-hidden relative chat-widget-container",
1840
+ themeMode === "dark" && "dark"
1841
+ ),
1842
+ children: [
1843
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
1844
+ borderColor: themeMode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)",
1845
+ backgroundColor: themeMode === "dark" ? "rgba(37,37,37,0.8)" : "rgba(255,255,255,0.8)"
1846
+ }, children: [
1847
+ /* @__PURE__ */ jsx18("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-hide py-0.5 scroll-smooth", children: tabs.map((tab, index) => /* @__PURE__ */ jsxs11(
1848
+ "div",
1849
+ {
1850
+ className: "relative flex items-center gap-1.5 px-3 py-1.5 rounded-lg cursor-pointer transition-all duration-150 group flex-shrink-0 min-w-0",
1851
+ style: {
1852
+ backgroundColor: tab.isActive ? themeMode === "dark" ? "#444444" : "#f5f5f5" : "transparent",
1853
+ color: tab.isActive ? themeMode === "dark" ? "#e5e5e5" : "#171717" : themeMode === "dark" ? "#999999" : "#737373"
1854
+ },
1855
+ onMouseEnter: (e) => {
1856
+ if (!tab.isActive) {
1857
+ e.currentTarget.style.backgroundColor = themeMode === "dark" ? "rgba(68,68,68,0.5)" : "rgba(245,245,245,0.5)";
1858
+ }
1859
+ },
1860
+ onMouseLeave: (e) => {
1861
+ if (!tab.isActive) {
1862
+ e.currentTarget.style.backgroundColor = "transparent";
1863
+ }
1864
+ },
1865
+ onClick: (e) => {
1866
+ e.stopPropagation();
1867
+ switchToTab(tab.id);
1868
+ },
1869
+ children: [
1870
+ /* @__PURE__ */ jsx18("span", { className: "truncate max-w-28 text-[13px] font-medium transition-colors", children: tab.title }),
1871
+ tabs.length > 1 && /* @__PURE__ */ jsx18(
1872
+ "button",
1873
+ {
1874
+ onClick: (e) => {
1875
+ e.stopPropagation();
1876
+ closeTab(tab.id);
1877
+ },
1878
+ className: "rounded-lg p-1 transition-all duration-150 flex-shrink-0 -mr-1",
1879
+ style: {
1880
+ opacity: tab.isActive ? 0.6 : 0
1881
+ },
1882
+ onMouseEnter: (e) => {
1883
+ e.currentTarget.style.opacity = "1";
1884
+ e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#555555" : "#e5e5e5";
1885
+ },
1886
+ onMouseLeave: (e) => {
1887
+ e.currentTarget.style.opacity = tab.isActive ? "0.6" : "0";
1888
+ e.currentTarget.style.backgroundColor = "transparent";
1889
+ },
1890
+ children: /* @__PURE__ */ jsx18(XIcon2, { className: "h-3 w-3", strokeWidth: 2.5 })
1891
+ }
1892
+ )
1893
+ ]
1894
+ },
1895
+ tab.id
1896
+ )) }),
1897
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [
1898
+ /* @__PURE__ */ jsx18(
1899
+ "button",
1900
+ {
1901
+ onClick: createNewTab,
1902
+ className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
1903
+ style: {
1904
+ color: themeMode === "dark" ? "#999999" : "#737373"
1905
+ },
1906
+ onMouseEnter: (e) => {
1907
+ e.currentTarget.style.color = themeMode === "dark" ? "#e5e5e5" : "#171717";
1908
+ e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#444444" : "#f5f5f5";
1909
+ },
1910
+ onMouseLeave: (e) => {
1911
+ e.currentTarget.style.color = themeMode === "dark" ? "#999999" : "#737373";
1912
+ e.currentTarget.style.backgroundColor = "transparent";
1913
+ },
1914
+ title: "New Chat",
1915
+ children: /* @__PURE__ */ jsx18(PlusIcon2, { className: "h-4 w-4", strokeWidth: 2 })
1916
+ }
1917
+ ),
1918
+ /* @__PURE__ */ jsxs11("div", { className: "relative", ref: dropdownRef, children: [
1919
+ /* @__PURE__ */ jsx18(
1920
+ "button",
1921
+ {
1922
+ onClick: () => setShowHistory(!showHistory),
1923
+ className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
1924
+ style: {
1925
+ color: showHistory ? themeMode === "dark" ? "#e5e5e5" : "#171717" : themeMode === "dark" ? "#999999" : "#737373",
1926
+ backgroundColor: showHistory ? themeMode === "dark" ? "#444444" : "#f5f5f5" : "transparent"
1927
+ },
1928
+ onMouseEnter: (e) => {
1929
+ if (!showHistory) {
1930
+ e.currentTarget.style.color = themeMode === "dark" ? "#e5e5e5" : "#171717";
1931
+ e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#444444" : "#f5f5f5";
1932
+ }
1933
+ },
1934
+ onMouseLeave: (e) => {
1935
+ if (!showHistory) {
1936
+ e.currentTarget.style.color = themeMode === "dark" ? "#999999" : "#737373";
1937
+ e.currentTarget.style.backgroundColor = "transparent";
1938
+ }
1939
+ },
1940
+ title: "Chat History",
1941
+ children: /* @__PURE__ */ jsx18(HistoryIcon, { className: "h-4 w-4", strokeWidth: 2 })
1942
+ }
1943
+ ),
1944
+ showHistory && /* @__PURE__ */ jsxs11("div", { className: "absolute right-0 top-full mt-1.5 w-72 rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] z-50 animate-in fade-in slide-in-from-top-1 duration-150 overflow-hidden", style: {
1945
+ backgroundColor: themeMode === "dark" ? "#252525" : "#ffffff",
1946
+ border: `1px solid ${themeMode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)"}`
1947
+ }, children: [
1948
+ /* @__PURE__ */ jsx18("div", { className: "p-2.5 border-b", style: {
1949
+ borderColor: themeMode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)",
1950
+ backgroundColor: themeMode === "dark" ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.02)"
1951
+ }, children: /* @__PURE__ */ jsxs11("div", { className: "relative", children: [
1952
+ /* @__PURE__ */ jsx18(SearchIcon, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5", style: {
1953
+ color: themeMode === "dark" ? "#666666" : "#999999"
1954
+ }, strokeWidth: 2 }),
1955
+ /* @__PURE__ */ jsx18(
1956
+ "input",
1957
+ {
1958
+ type: "text",
1959
+ placeholder: "Search",
1960
+ value: searchQuery,
1961
+ onChange: (e) => setSearchQuery(e.target.value),
1962
+ className: "w-full h-7 pl-8 pr-2.5 text-[13px] rounded-lg focus:outline-none transition-all",
1963
+ style: {
1964
+ backgroundColor: themeMode === "dark" ? "#1a1a1a" : "#ffffff",
1965
+ border: `1px solid ${themeMode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)"}`,
1966
+ color: themeMode === "dark" ? "#e5e5e5" : "#171717"
1967
+ }
1968
+ }
1969
+ )
1970
+ ] }) }),
1971
+ /* @__PURE__ */ jsx18("div", { className: "max-h-[300px] overflow-y-auto ai-assistant-scrollbar", children: loadingHistory ? /* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx18("div", { className: "text-[13px]", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: "Loading..." }) }) : conversations.length === 0 ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
1972
+ /* @__PURE__ */ jsx18("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
1973
+ backgroundColor: themeMode === "dark" ? "#333333" : "#f5f5f5"
1974
+ }, children: /* @__PURE__ */ jsx18(MessageSquareIcon, { className: "h-5 w-5", style: { color: themeMode === "dark" ? "#666666" : "#a3a3a3" }, strokeWidth: 2 }) }),
1975
+ /* @__PURE__ */ jsx18("p", { className: "text-[13px] font-medium mb-0.5", style: { color: themeMode === "dark" ? "#e5e5e5" : "#171717" }, children: "No Conversations" }),
1976
+ /* @__PURE__ */ jsx18("p", { className: "text-[12px]", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: "Start a new chat to begin" })
1977
+ ] }) : groupedConversations.length === 0 ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
1978
+ /* @__PURE__ */ jsx18("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
1979
+ backgroundColor: themeMode === "dark" ? "#333333" : "#f5f5f5"
1980
+ }, children: /* @__PURE__ */ jsx18(SearchIcon, { className: "h-5 w-5", style: { color: themeMode === "dark" ? "#666666" : "#a3a3a3" }, strokeWidth: 2 }) }),
1981
+ /* @__PURE__ */ jsx18("p", { className: "text-[13px] font-medium mb-0.5", style: { color: themeMode === "dark" ? "#e5e5e5" : "#171717" }, children: "No Results" }),
1982
+ /* @__PURE__ */ jsx18("p", { className: "text-[12px]", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: "Try a different search" })
1983
+ ] }) : /* @__PURE__ */ jsx18("div", { className: "py-0.5", children: groupedConversations.map(([groupName, groupConversations]) => /* @__PURE__ */ jsxs11("div", { className: "mb-0.5", children: [
1984
+ /* @__PURE__ */ jsx18("div", { className: "px-2.5 py-1 sticky top-0 backdrop-blur-sm z-10", style: {
1985
+ backgroundColor: themeMode === "dark" ? "rgba(37,37,37,0.95)" : "rgba(255,255,255,0.95)"
1986
+ }, children: /* @__PURE__ */ jsx18("h3", { className: "text-[10px] font-semibold uppercase tracking-wide", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: groupName }) }),
1987
+ /* @__PURE__ */ jsx18("div", { className: "px-1 space-y-0.5", children: groupConversations.map((conversation) => {
1988
+ const isActiveConversation = activeTabId === conversation.id;
1989
+ return /* @__PURE__ */ jsx18(
1990
+ "button",
1991
+ {
1992
+ className: "w-full px-2 py-1 rounded-md transition-all duration-150 text-left group relative",
1993
+ style: {
1994
+ backgroundColor: isActiveConversation ? themeMode === "dark" ? "#333333" : "#f5f5f5" : "transparent"
1995
+ },
1996
+ onMouseEnter: (e) => {
1997
+ if (!isActiveConversation) {
1998
+ e.currentTarget.style.backgroundColor = themeMode === "dark" ? "rgba(51,51,51,0.5)" : "rgba(245,245,245,0.5)";
1999
+ }
2000
+ },
2001
+ onMouseLeave: (e) => {
2002
+ if (!isActiveConversation) {
2003
+ e.currentTarget.style.backgroundColor = "transparent";
2004
+ }
2005
+ },
2006
+ onClick: () => handleSelectConversation(conversation.id, conversation.title),
2007
+ children: /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5", children: [
2008
+ /* @__PURE__ */ jsx18("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx18("p", { className: "text-[12px] line-clamp-1 transition-colors leading-tight", style: {
2009
+ fontWeight: isActiveConversation ? 500 : 400,
2010
+ color: isActiveConversation ? themeMode === "dark" ? "#e5e5e5" : "#171717" : themeMode === "dark" ? "#cccccc" : "#404040"
2011
+ }, children: conversation.title }) }),
2012
+ /* @__PURE__ */ jsx18(
2013
+ ChevronRightIcon2,
2014
+ {
2015
+ className: "h-3 w-3 transition-all duration-150 flex-shrink-0",
2016
+ style: {
2017
+ color: themeMode === "dark" ? "#666666" : "#a3a3a3",
2018
+ opacity: isActiveConversation ? 1 : 0
2019
+ },
2020
+ strokeWidth: 2
2021
+ }
2022
+ )
2023
+ ] })
2024
+ },
2025
+ conversation.id
2026
+ );
2027
+ }) })
2028
+ ] }, groupName)) }) })
2029
+ ] })
2030
+ ] }),
2031
+ onClose && /* @__PURE__ */ jsx18(
2032
+ "button",
2033
+ {
2034
+ onClick: onClose,
2035
+ className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
2036
+ style: {
2037
+ color: themeMode === "dark" ? "#999999" : "#737373"
2038
+ },
2039
+ onMouseEnter: (e) => {
2040
+ e.currentTarget.style.color = themeMode === "dark" ? "#e5e5e5" : "#171717";
2041
+ e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#444444" : "#f5f5f5";
2042
+ },
2043
+ onMouseLeave: (e) => {
2044
+ e.currentTarget.style.color = themeMode === "dark" ? "#999999" : "#737373";
2045
+ e.currentTarget.style.backgroundColor = "transparent";
2046
+ },
2047
+ title: "Close Chat",
2048
+ children: /* @__PURE__ */ jsx18(XIcon2, { className: "h-4 w-4", strokeWidth: 2 })
2049
+ }
2050
+ )
2051
+ ] })
2052
+ ] }),
2053
+ /* @__PURE__ */ jsxs11(Conversation, { className: "flex-1 max-w-full ai-assistant-scrollbar", children: [
2054
+ /* @__PURE__ */ jsxs11(ConversationContent, { className: "max-w-[96%] mx-auto py-6", children: [
2055
+ renderMessages(),
2056
+ status === "submitted" && /* @__PURE__ */ jsx18("div", { className: "mt-6", children: /* @__PURE__ */ jsx18(Message, { from: "assistant", children: /* @__PURE__ */ jsx18(MessageContent, { children: /* @__PURE__ */ jsx18(Loader, { size: 16 }) }) }) })
2057
+ ] }),
2058
+ /* @__PURE__ */ jsx18(ConversationScrollButton, {})
2059
+ ] }),
2060
+ /* @__PURE__ */ jsxs11("div", { className: "px-5 pb-5", children: [
2061
+ uploadError && /* @__PURE__ */ jsx18("div", { className: "mb-3 px-4 py-3 bg-red-50 dark:bg-red-900/20 border border-red-200/60 dark:border-red-800/60 rounded-2xl text-sm text-red-700 dark:text-red-400 shadow-sm", children: uploadError }),
2062
+ /* @__PURE__ */ jsxs11(PromptInput, { onSubmit: handleSubmit, globalDrop: true, multiple: true, accept: "image/*", children: [
2063
+ /* @__PURE__ */ jsxs11(PromptInputBody, { children: [
2064
+ /* @__PURE__ */ jsx18(PromptInputAttachments, { children: (attachment) => /* @__PURE__ */ jsx18(PromptInputAttachment, { data: attachment }) }),
2065
+ /* @__PURE__ */ jsx18(
2066
+ PromptInputTextarea,
2067
+ {
2068
+ onChange: (e) => setInput(e.target.value),
2069
+ value: input
2070
+ }
2071
+ )
2072
+ ] }),
2073
+ /* @__PURE__ */ jsxs11(PromptInputToolbar, { children: [
2074
+ /* @__PURE__ */ jsx18(PromptInputTools, { children: config?.features?.fileUpload !== false && /* @__PURE__ */ jsx18(AttachButton, {}) }),
2075
+ /* @__PURE__ */ jsx18(PromptInputSubmit, { disabled: !input, status })
2076
+ ] })
2077
+ ] })
2078
+ ] })
2079
+ ]
2080
+ }
2081
+ ) });
2082
+ }
2083
+
2084
+ // src/ChatWidget.tsx
2085
+ import { MessageCircle } from "lucide-react";
2086
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2087
+ function ChatWidget({
2088
+ userId,
2089
+ conversationId,
2090
+ initialMessages,
2091
+ className,
2092
+ model,
2093
+ systemPrompt,
2094
+ temperature,
2095
+ theme,
2096
+ features,
2097
+ display
2098
+ }) {
2099
+ const initialWidth = display?.width || "30vw";
2100
+ const showToggleButton = display?.showToggleButton !== false;
2101
+ const [isOpen, setIsOpen] = useState5(display?.defaultOpen || false);
2102
+ const [width, setWidth] = useState5(() => {
2103
+ const value = parseInt(initialWidth);
2104
+ if (initialWidth.includes("vw")) {
2105
+ return value / 100 * window.innerWidth;
2106
+ }
2107
+ return value || window.innerWidth * 0.3;
2108
+ });
2109
+ const isResizing = useRef4(false);
2110
+ const handleMouseDown = useCallback4((e) => {
2111
+ e.preventDefault();
2112
+ isResizing.current = true;
2113
+ document.body.style.cursor = "ew-resize";
2114
+ document.body.style.userSelect = "none";
2115
+ }, []);
2116
+ useEffect5(() => {
2117
+ const getConstraints = () => ({
2118
+ minWidth: window.innerWidth * 0.2,
2119
+ maxWidth: window.innerWidth * 0.6
2120
+ });
2121
+ const handleMouseMove = (e) => {
2122
+ if (!isResizing.current) return;
2123
+ const { minWidth, maxWidth } = getConstraints();
2124
+ const newWidth = window.innerWidth - e.clientX;
2125
+ setWidth(Math.min(maxWidth, Math.max(minWidth, newWidth)));
2126
+ };
2127
+ const handleMouseUp = () => {
2128
+ if (isResizing.current) {
2129
+ isResizing.current = false;
2130
+ document.body.style.cursor = "";
2131
+ document.body.style.userSelect = "";
2132
+ }
2133
+ };
2134
+ const handleWindowResize = () => {
2135
+ const { minWidth, maxWidth } = getConstraints();
2136
+ setWidth((prev) => Math.min(maxWidth, Math.max(minWidth, prev)));
2137
+ };
2138
+ document.addEventListener("mousemove", handleMouseMove);
2139
+ document.addEventListener("mouseup", handleMouseUp);
2140
+ window.addEventListener("resize", handleWindowResize);
2141
+ return () => {
2142
+ document.removeEventListener("mousemove", handleMouseMove);
2143
+ document.removeEventListener("mouseup", handleMouseUp);
2144
+ window.removeEventListener("resize", handleWindowResize);
2145
+ };
2146
+ }, []);
2147
+ const config = useMemo3(() => ({
2148
+ userId,
2149
+ model,
2150
+ systemPrompt,
2151
+ temperature,
2152
+ theme,
2153
+ features
2154
+ }), [userId, model, systemPrompt, temperature, theme, features]);
2155
+ const togglePosition = display?.toggleButtonPosition || { bottom: "24px", right: "24px" };
2156
+ return /* @__PURE__ */ jsxs12(Fragment6, { children: [
2157
+ showToggleButton && !isOpen && /* @__PURE__ */ jsx19(
2158
+ "button",
2159
+ {
2160
+ onClick: () => setIsOpen(true),
2161
+ className: "fixed z-50 rounded-full bg-primary text-primary-foreground shadow-lg hover:opacity-90 transition-all p-4",
2162
+ style: togglePosition,
2163
+ "aria-label": "Open chat",
2164
+ children: /* @__PURE__ */ jsx19(MessageCircle, { className: "h-6 w-6" })
2165
+ }
2166
+ ),
2167
+ isOpen && /* @__PURE__ */ jsxs12(
2168
+ "div",
2169
+ {
2170
+ className: `fixed top-0 right-0 h-screen z-50 border-l border-gray-200 dark:border-gray-800 ${className || ""}`,
2171
+ style: {
2172
+ animation: "chat-slide-in-right 0.3s ease-out forwards",
2173
+ width: `${width}px`
2174
+ },
2175
+ children: [
2176
+ /* @__PURE__ */ jsx19(
2177
+ "div",
2178
+ {
2179
+ onMouseDown: handleMouseDown,
2180
+ className: "absolute left-0 top-0 h-full w-1 cursor-ew-resize hover:bg-primary/20 active:bg-primary/30 transition-colors z-10"
2181
+ }
2182
+ ),
2183
+ /* @__PURE__ */ jsx19("div", { className: "w-full h-full overflow-hidden", children: /* @__PURE__ */ jsx19(
2184
+ ChatInterface,
2185
+ {
2186
+ id: conversationId,
2187
+ initialMessages,
2188
+ config,
2189
+ onClose: () => setIsOpen(false)
2190
+ }
2191
+ ) })
2192
+ ]
2193
+ }
2194
+ )
2195
+ ] });
2196
+ }
2197
+ var ChatWidget_default = ChatWidget;
2198
+
2199
+ // src/hooks/use-chat-theme.ts
2200
+ import { useState as useState6, useEffect as useEffect6 } from "react";
2201
+
2202
+ // src/utils/models.ts
2203
+ var MODELS = [
2204
+ {
2205
+ name: "GPT-5 Nano",
2206
+ value: "openai/gpt-5-nano"
2207
+ },
2208
+ // Anthropic models
2209
+ {
2210
+ name: "Claude Sonnet 4.5",
2211
+ value: "anthropic/claude-sonnet-4-5"
2212
+ },
2213
+ {
2214
+ name: "Claude Sonnet 4",
2215
+ value: "anthropic/claude-sonnet-4-0"
2216
+ },
2217
+ {
2218
+ name: "Claude Haiku 3.5",
2219
+ value: "anthropic/claude-3-5-haiku-latest"
2220
+ },
2221
+ // OpenAI models
2222
+ {
2223
+ name: "GPT-5",
2224
+ value: "openai/gpt-5"
2225
+ },
2226
+ {
2227
+ name: "GPT-OSS-120B",
2228
+ value: "openai/gpt-oss-120b"
2229
+ },
2230
+ {
2231
+ name: "GPT 4o",
2232
+ value: "openai/gpt-4o"
2233
+ },
2234
+ // Google models
2235
+ {
2236
+ name: "Gemini 2.5 Flash Lite",
2237
+ value: "google/gemini-2.5-flash-lite"
2238
+ },
2239
+ {
2240
+ name: "Gemini 2.5 Flash",
2241
+ value: "google/gemini-2.5-flash"
2242
+ },
2243
+ {
2244
+ name: "Gemini 2.5 Pro",
2245
+ value: "google/gemini-2.5-pro"
2246
+ }
2247
+ ];
2248
+ var DEFAULT_MODEL = MODELS[0].value;
2249
+
2250
+ // src/hooks/use-chat-theme.ts
2251
+ function hexToHSL(hex) {
2252
+ hex = hex.replace("#", "");
2253
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
2254
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
2255
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
2256
+ const max = Math.max(r, g, b);
2257
+ const min = Math.min(r, g, b);
2258
+ let h = 0;
2259
+ let s = 0;
2260
+ const l = (max + min) / 2;
2261
+ if (max !== min) {
2262
+ const d = max - min;
2263
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2264
+ switch (max) {
2265
+ case r:
2266
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
2267
+ break;
2268
+ case g:
2269
+ h = ((b - r) / d + 2) / 6;
2270
+ break;
2271
+ case b:
2272
+ h = ((r - g) / d + 4) / 6;
2273
+ break;
2274
+ }
2275
+ }
2276
+ return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
2277
+ }
2278
+ var defaultTheme = {
2279
+ // Light mode
2280
+ lightPrimary: "#3b82f6",
2281
+ // Blue
2282
+ lightSecondary: "#f5f5f5",
2283
+ // Light gray
2284
+ lightText: "#0a0a0a",
2285
+ // Near black
2286
+ // Dark mode
2287
+ darkPrimary: "#3b82f6",
2288
+ // Blue (same as light)
2289
+ darkSecondary: "#262626",
2290
+ // Dark gray
2291
+ darkText: "#ffffff",
2292
+ // White
2293
+ // Typography
2294
+ fontFamily: "system-ui",
2295
+ fontSize: 14
2296
+ };
2297
+ var fontOptions = [
2298
+ { value: "system-ui", label: "System Default" },
2299
+ { value: "Inter, sans-serif", label: "Inter" },
2300
+ { value: "Roboto, sans-serif", label: "Roboto" },
2301
+ { value: "Open Sans, sans-serif", label: "Open Sans" },
2302
+ { value: "Lato, sans-serif", label: "Lato" },
2303
+ { value: "Poppins, sans-serif", label: "Poppins" },
2304
+ { value: "Montserrat, sans-serif", label: "Montserrat" },
2305
+ { value: "Georgia, serif", label: "Georgia" },
2306
+ { value: "ui-monospace, monospace", label: "Monospace" }
2307
+ ];
2308
+ var defaultConversationStarters = [
2309
+ { text: "How can I help you today?", enabled: true },
2310
+ { text: "What features does this product offer?", enabled: true },
2311
+ { text: "Tell me about your capabilities", enabled: true }
2312
+ ];
2313
+ var defaultModel = DEFAULT_MODEL;
2314
+ var defaultSystemPrompt = "You are a helpful AI assistant.";
2315
+ var defaultTemperature = 0.7;
2316
+ var defaultThemeMode = "light";
2317
+ function useChatTheme() {
2318
+ const [theme, setTheme] = useState6(defaultTheme);
2319
+ const [conversationStarters, setConversationStarters] = useState6(defaultConversationStarters);
2320
+ const [model, setModel] = useState6(defaultModel);
2321
+ const [systemPrompt, setSystemPrompt] = useState6(defaultSystemPrompt);
2322
+ const [temperature, setTemperature] = useState6(defaultTemperature);
2323
+ const [themeMode, setThemeMode] = useState6(defaultThemeMode);
2324
+ useEffect6(() => {
2325
+ const savedTheme = localStorage.getItem("chat-theme");
2326
+ if (savedTheme) {
2327
+ try {
2328
+ setTheme(JSON.parse(savedTheme));
2329
+ } catch (error) {
2330
+ console.error("Error loading theme:", error);
2331
+ }
2332
+ }
2333
+ const savedStarters = localStorage.getItem("chat-conversation-starters");
2334
+ if (savedStarters) {
2335
+ try {
2336
+ setConversationStarters(JSON.parse(savedStarters));
2337
+ } catch (error) {
2338
+ console.error("Error loading conversation starters:", error);
2339
+ }
2340
+ }
2341
+ const savedModel = localStorage.getItem("chat-model");
2342
+ if (savedModel) {
2343
+ try {
2344
+ setModel(savedModel);
2345
+ } catch (error) {
2346
+ console.error("Error loading model:", error);
2347
+ }
2348
+ }
2349
+ const savedSystemPrompt = localStorage.getItem("chat-system-prompt");
2350
+ if (savedSystemPrompt) {
2351
+ try {
2352
+ setSystemPrompt(savedSystemPrompt);
2353
+ } catch (error) {
2354
+ console.error("Error loading system prompt:", error);
2355
+ }
2356
+ }
2357
+ const savedTemperature = localStorage.getItem("chat-temperature");
2358
+ if (savedTemperature) {
2359
+ try {
2360
+ setTemperature(parseFloat(savedTemperature));
2361
+ } catch (error) {
2362
+ console.error("Error loading temperature:", error);
2363
+ }
2364
+ }
2365
+ const savedThemeMode = localStorage.getItem("chat-theme-mode");
2366
+ if (savedThemeMode) {
2367
+ try {
2368
+ setThemeMode(savedThemeMode);
2369
+ } catch (error) {
2370
+ console.error("Error loading theme mode:", error);
2371
+ }
2372
+ }
2373
+ const handleThemeChange = (e) => {
2374
+ const customEvent = e;
2375
+ setTheme(customEvent.detail);
2376
+ };
2377
+ const handleStartersChange = (e) => {
2378
+ const customEvent = e;
2379
+ setConversationStarters(customEvent.detail);
2380
+ };
2381
+ const handleSystemPromptChange = (e) => {
2382
+ const customEvent = e;
2383
+ setSystemPrompt(customEvent.detail);
2384
+ };
2385
+ const handleModelChange = (e) => {
2386
+ const customEvent = e;
2387
+ setModel(customEvent.detail);
2388
+ };
2389
+ const handleTemperatureChange = (e) => {
2390
+ const customEvent = e;
2391
+ setTemperature(customEvent.detail);
2392
+ };
2393
+ const handleThemeModeChange = (e) => {
2394
+ const customEvent = e;
2395
+ setThemeMode(customEvent.detail);
2396
+ };
2397
+ window.addEventListener("chat-theme-change", handleThemeChange);
2398
+ window.addEventListener("chat-starters-change", handleStartersChange);
2399
+ window.addEventListener("chat-model-change", handleModelChange);
2400
+ window.addEventListener("chat-system-prompt-change", handleSystemPromptChange);
2401
+ window.addEventListener("chat-temperature-change", handleTemperatureChange);
2402
+ window.addEventListener("chat-theme-mode-change", handleThemeModeChange);
2403
+ return () => {
2404
+ window.removeEventListener("chat-theme-change", handleThemeChange);
2405
+ window.removeEventListener("chat-starters-change", handleStartersChange);
2406
+ window.removeEventListener("chat-model-change", handleModelChange);
2407
+ window.removeEventListener("chat-system-prompt-change", handleSystemPromptChange);
2408
+ window.removeEventListener("chat-temperature-change", handleTemperatureChange);
2409
+ window.removeEventListener("chat-theme-mode-change", handleThemeModeChange);
2410
+ };
2411
+ }, []);
2412
+ useEffect6(() => {
2413
+ localStorage.setItem("chat-theme", JSON.stringify(theme));
2414
+ const root = document.documentElement;
2415
+ if (themeMode === "light") {
2416
+ root.style.setProperty("--chat-primary", hexToHSL(theme.lightPrimary));
2417
+ root.style.setProperty("--chat-secondary", hexToHSL(theme.lightSecondary));
2418
+ root.style.setProperty("--chat-text", hexToHSL(theme.lightText));
2419
+ } else {
2420
+ root.style.setProperty("--chat-primary", hexToHSL(theme.darkPrimary));
2421
+ root.style.setProperty("--chat-secondary", hexToHSL(theme.darkSecondary));
2422
+ root.style.setProperty("--chat-text", hexToHSL(theme.darkText));
2423
+ }
2424
+ root.style.setProperty("--chat-font-family", theme.fontFamily);
2425
+ root.style.setProperty("--chat-font-size", `${theme.fontSize}px`);
2426
+ window.dispatchEvent(new CustomEvent("chat-theme-change", { detail: theme }));
2427
+ }, [theme, themeMode]);
2428
+ useEffect6(() => {
2429
+ localStorage.setItem("chat-conversation-starters", JSON.stringify(conversationStarters));
2430
+ window.dispatchEvent(new CustomEvent("chat-starters-change", { detail: conversationStarters }));
2431
+ }, [conversationStarters]);
2432
+ useEffect6(() => {
2433
+ localStorage.setItem("chat-model", model);
2434
+ window.dispatchEvent(new CustomEvent("chat-model-change", { detail: model }));
2435
+ }, [model]);
2436
+ useEffect6(() => {
2437
+ localStorage.setItem("chat-system-prompt", systemPrompt);
2438
+ window.dispatchEvent(new CustomEvent("chat-system-prompt-change", { detail: systemPrompt }));
2439
+ }, [systemPrompt]);
2440
+ useEffect6(() => {
2441
+ localStorage.setItem("chat-temperature", temperature.toString());
2442
+ window.dispatchEvent(new CustomEvent("chat-temperature-change", { detail: temperature }));
2443
+ }, [temperature]);
2444
+ useEffect6(() => {
2445
+ localStorage.setItem("chat-theme-mode", themeMode);
2446
+ window.dispatchEvent(new CustomEvent("chat-theme-mode-change", { detail: themeMode }));
2447
+ }, [themeMode]);
2448
+ const updateColor = (key, value) => {
2449
+ setTheme((prev) => ({ ...prev, [key]: value }));
2450
+ };
2451
+ const updateLightColors = (colors) => {
2452
+ setTheme((prev) => ({
2453
+ ...prev,
2454
+ ...colors.primary && { lightPrimary: colors.primary },
2455
+ ...colors.secondary && { lightSecondary: colors.secondary },
2456
+ ...colors.text && { lightText: colors.text }
2457
+ }));
2458
+ };
2459
+ const updateDarkColors = (colors) => {
2460
+ setTheme((prev) => ({
2461
+ ...prev,
2462
+ ...colors.primary && { darkPrimary: colors.primary },
2463
+ ...colors.secondary && { darkSecondary: colors.secondary },
2464
+ ...colors.text && { darkText: colors.text }
2465
+ }));
2466
+ };
2467
+ const resetTheme = () => {
2468
+ setTheme(defaultTheme);
2469
+ };
2470
+ const updateFontSize = (size) => {
2471
+ setTheme((prev) => ({ ...prev, fontSize: size }));
2472
+ };
2473
+ const updateFontFamily = (family) => {
2474
+ setTheme((prev) => ({ ...prev, fontFamily: family }));
2475
+ };
2476
+ const updateConversationStarters = (starters) => {
2477
+ setConversationStarters(starters);
2478
+ };
2479
+ const updateSystemPrompt = (prompt) => {
2480
+ setSystemPrompt(prompt);
2481
+ };
2482
+ const updateModel = (selectedModel) => {
2483
+ setModel(selectedModel);
2484
+ };
2485
+ const updateTemperature = (temp) => {
2486
+ setTemperature(temp);
2487
+ };
2488
+ const updateThemeMode = (mode) => {
2489
+ setThemeMode(mode);
2490
+ };
2491
+ return {
2492
+ theme,
2493
+ updateColor,
2494
+ updateLightColors,
2495
+ updateDarkColors,
2496
+ resetTheme,
2497
+ updateFontSize,
2498
+ updateFontFamily,
2499
+ conversationStarters,
2500
+ updateConversationStarters,
2501
+ model,
2502
+ updateModel,
2503
+ systemPrompt,
2504
+ updateSystemPrompt,
2505
+ temperature,
2506
+ updateTemperature,
2507
+ themeMode,
2508
+ updateThemeMode
2509
+ };
2510
+ }
2511
+
2512
+ // src/ui/input.tsx
2513
+ import * as React from "react";
2514
+ import { jsx as jsx20 } from "react/jsx-runtime";
2515
+ var Input = React.forwardRef(
2516
+ ({ className, type, ...props }, ref) => {
2517
+ return /* @__PURE__ */ jsx20(
2518
+ "input",
2519
+ {
2520
+ type,
2521
+ className: cn(
2522
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
2523
+ className
2524
+ ),
2525
+ ref,
2526
+ ...props
2527
+ }
2528
+ );
2529
+ }
2530
+ );
2531
+ Input.displayName = "Input";
2532
+
2533
+ // src/ui/dialog.tsx
2534
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2535
+ import { XIcon as XIcon3 } from "lucide-react";
2536
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
2537
+ function Dialog({
2538
+ ...props
2539
+ }) {
2540
+ return /* @__PURE__ */ jsx21(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
2541
+ }
2542
+ function DialogPortal({
2543
+ ...props
2544
+ }) {
2545
+ return /* @__PURE__ */ jsx21(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
2546
+ }
2547
+ function DialogOverlay({
2548
+ className,
2549
+ ...props
2550
+ }) {
2551
+ return /* @__PURE__ */ jsx21(
2552
+ DialogPrimitive.Overlay,
2553
+ {
2554
+ "data-slot": "dialog-overlay",
2555
+ className: cn(
2556
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
2557
+ className
2558
+ ),
2559
+ ...props
2560
+ }
2561
+ );
2562
+ }
2563
+ function DialogContent({
2564
+ className,
2565
+ children,
2566
+ showCloseButton = true,
2567
+ ...props
2568
+ }) {
2569
+ return /* @__PURE__ */ jsxs13(DialogPortal, { "data-slot": "dialog-portal", children: [
2570
+ /* @__PURE__ */ jsx21(DialogOverlay, {}),
2571
+ /* @__PURE__ */ jsxs13(
2572
+ DialogPrimitive.Content,
2573
+ {
2574
+ "data-slot": "dialog-content",
2575
+ className: cn(
2576
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
2577
+ className
2578
+ ),
2579
+ ...props,
2580
+ children: [
2581
+ children,
2582
+ showCloseButton && /* @__PURE__ */ jsxs13(
2583
+ DialogPrimitive.Close,
2584
+ {
2585
+ "data-slot": "dialog-close",
2586
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
2587
+ children: [
2588
+ /* @__PURE__ */ jsx21(XIcon3, {}),
2589
+ /* @__PURE__ */ jsx21("span", { className: "sr-only", children: "Close" })
2590
+ ]
2591
+ }
2592
+ )
2593
+ ]
2594
+ }
2595
+ )
2596
+ ] });
2597
+ }
2598
+ function DialogHeader({ className, ...props }) {
2599
+ return /* @__PURE__ */ jsx21(
2600
+ "div",
2601
+ {
2602
+ "data-slot": "dialog-header",
2603
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
2604
+ ...props
2605
+ }
2606
+ );
2607
+ }
2608
+ function DialogTitle({
2609
+ className,
2610
+ ...props
2611
+ }) {
2612
+ return /* @__PURE__ */ jsx21(
2613
+ DialogPrimitive.Title,
2614
+ {
2615
+ "data-slot": "dialog-title",
2616
+ className: cn("text-lg leading-none font-semibold", className),
2617
+ ...props
2618
+ }
2619
+ );
2620
+ }
2621
+ function DialogDescription({
2622
+ className,
2623
+ ...props
2624
+ }) {
2625
+ return /* @__PURE__ */ jsx21(
2626
+ DialogPrimitive.Description,
2627
+ {
2628
+ "data-slot": "dialog-description",
2629
+ className: cn("text-muted-foreground text-sm", className),
2630
+ ...props
2631
+ }
2632
+ );
2633
+ }
2634
+ export {
2635
+ Button,
2636
+ ChatWidget,
2637
+ Dialog,
2638
+ DialogContent,
2639
+ DialogDescription,
2640
+ DialogHeader,
2641
+ DialogTitle,
2642
+ Input,
2643
+ ChatWidget_default as default,
2644
+ fontOptions,
2645
+ useChatTheme
2646
+ };
2647
+ //# sourceMappingURL=index.mjs.map