@dmanikanta17/chat-ui-react 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,1042 @@
1
+ // src/ChatUI.tsx
2
+ import { useState as useState3, useEffect as useEffect2 } from "react";
3
+ import { Send, Sparkles, Loader2, User, RefreshCcw, X as X2 } from "lucide-react";
4
+
5
+ // src/components/ui/button.tsx
6
+ import { Slot } from "@radix-ui/react-slot";
7
+ import { cva } from "class-variance-authority";
8
+
9
+ // src/lib/utils.ts
10
+ import { clsx } from "clsx";
11
+ import { twMerge } from "tailwind-merge";
12
+ function cn(...inputs) {
13
+ return twMerge(clsx(inputs));
14
+ }
15
+
16
+ // src/components/ui/button.tsx
17
+ import { jsx } from "react/jsx-runtime";
18
+ var buttonVariants = cva(
19
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer 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",
20
+ {
21
+ variants: {
22
+ variant: {
23
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
24
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
25
+ 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",
26
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
27
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
28
+ link: "text-primary underline-offset-4 hover:underline"
29
+ },
30
+ size: {
31
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
32
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
33
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
34
+ icon: "size-9",
35
+ "icon-sm": "size-8",
36
+ "icon-lg": "size-10"
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/ui/avatar.tsx
64
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
65
+ import { jsx as jsx2 } from "react/jsx-runtime";
66
+ function Avatar({
67
+ className,
68
+ ...props
69
+ }) {
70
+ return /* @__PURE__ */ jsx2(
71
+ AvatarPrimitive.Root,
72
+ {
73
+ "data-slot": "avatar",
74
+ className: cn(
75
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
76
+ className
77
+ ),
78
+ ...props
79
+ }
80
+ );
81
+ }
82
+ function AvatarImage({
83
+ className,
84
+ ...props
85
+ }) {
86
+ return /* @__PURE__ */ jsx2(
87
+ AvatarPrimitive.Image,
88
+ {
89
+ "data-slot": "avatar-image",
90
+ className: cn("aspect-square size-full", className),
91
+ ...props
92
+ }
93
+ );
94
+ }
95
+ function AvatarFallback({
96
+ className,
97
+ ...props
98
+ }) {
99
+ return /* @__PURE__ */ jsx2(
100
+ AvatarPrimitive.Fallback,
101
+ {
102
+ "data-slot": "avatar-fallback",
103
+ className: cn(
104
+ "bg-muted flex size-full items-center justify-center rounded-full",
105
+ className
106
+ ),
107
+ ...props
108
+ }
109
+ );
110
+ }
111
+
112
+ // src/components/ui/message-loading.tsx
113
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
114
+ function MessageLoading() {
115
+ return /* @__PURE__ */ jsxs(
116
+ "svg",
117
+ {
118
+ width: "24",
119
+ height: "24",
120
+ viewBox: "0 0 24 24",
121
+ xmlns: "http://www.w3.org/2000/svg",
122
+ className: "text-foreground",
123
+ children: [
124
+ /* @__PURE__ */ jsx3("circle", { cx: "4", cy: "12", r: "2", fill: "currentColor", children: /* @__PURE__ */ jsx3(
125
+ "animate",
126
+ {
127
+ id: "spinner_qFRN",
128
+ begin: "0;spinner_OcgL.end+0.25s",
129
+ attributeName: "cy",
130
+ calcMode: "spline",
131
+ dur: "0.6s",
132
+ values: "12;6;12",
133
+ keySplines: ".33,.66,.66,1;.33,0,.66,.33"
134
+ }
135
+ ) }),
136
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor", children: /* @__PURE__ */ jsx3(
137
+ "animate",
138
+ {
139
+ begin: "spinner_qFRN.begin+0.1s",
140
+ attributeName: "cy",
141
+ calcMode: "spline",
142
+ dur: "0.6s",
143
+ values: "12;6;12",
144
+ keySplines: ".33,.66,.66,1;.33,0,.66,.33"
145
+ }
146
+ ) }),
147
+ /* @__PURE__ */ jsx3("circle", { cx: "20", cy: "12", r: "2", fill: "currentColor", children: /* @__PURE__ */ jsx3(
148
+ "animate",
149
+ {
150
+ id: "spinner_OcgL",
151
+ begin: "spinner_qFRN.begin+0.2s",
152
+ attributeName: "cy",
153
+ calcMode: "spline",
154
+ dur: "0.6s",
155
+ values: "12;6;12",
156
+ keySplines: ".33,.66,.66,1;.33,0,.66,.33"
157
+ }
158
+ ) })
159
+ ]
160
+ }
161
+ );
162
+ }
163
+
164
+ // src/components/ui/chat-bubble.tsx
165
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
166
+ function ChatBubble({
167
+ variant = "received",
168
+ layout = "default",
169
+ className,
170
+ children
171
+ }) {
172
+ return /* @__PURE__ */ jsx4(
173
+ "div",
174
+ {
175
+ className: cn(
176
+ "flex items-start gap-2 mb-4",
177
+ variant === "sent" && "flex-row-reverse",
178
+ className
179
+ ),
180
+ children
181
+ }
182
+ );
183
+ }
184
+ function ChatBubbleMessage({
185
+ variant = "received",
186
+ isLoading,
187
+ className,
188
+ children
189
+ }) {
190
+ return /* @__PURE__ */ jsx4(
191
+ "div",
192
+ {
193
+ className: cn(
194
+ "rounded-[1.25rem] px-4 py-2 text-sm max-w-[85%]",
195
+ variant === "sent" ? "bg-primary text-primary-foreground" : "bg-muted",
196
+ className
197
+ ),
198
+ children: isLoading ? /* @__PURE__ */ jsx4("div", { className: "flex items-center space-x-2", children: /* @__PURE__ */ jsx4(MessageLoading, {}) }) : children
199
+ }
200
+ );
201
+ }
202
+ function ChatBubbleAvatar({
203
+ src,
204
+ fallback = "AI",
205
+ className
206
+ }) {
207
+ return /* @__PURE__ */ jsxs2(Avatar, { className: cn("h-8 w-8", className), children: [
208
+ src && /* @__PURE__ */ jsx4(AvatarImage, { src }),
209
+ /* @__PURE__ */ jsx4(AvatarFallback, { children: fallback })
210
+ ] });
211
+ }
212
+
213
+ // src/components/ui/chat-input.tsx
214
+ import * as React from "react";
215
+
216
+ // src/components/ui/textarea.tsx
217
+ import { jsx as jsx5 } from "react/jsx-runtime";
218
+ function Textarea({ className, ...props }) {
219
+ return /* @__PURE__ */ jsx5(
220
+ "textarea",
221
+ {
222
+ "data-slot": "textarea",
223
+ className: cn(
224
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
225
+ className
226
+ ),
227
+ ...props
228
+ }
229
+ );
230
+ }
231
+
232
+ // src/components/ui/chat-input.tsx
233
+ import { jsx as jsx6 } from "react/jsx-runtime";
234
+ var ChatInput = React.forwardRef(
235
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx6(
236
+ Textarea,
237
+ {
238
+ autoComplete: "off",
239
+ ref,
240
+ name: "message",
241
+ className: cn(
242
+ "max-h-12 px-4 py-3 bg-background text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 w-full rounded-md flex items-center h-16 resize-none",
243
+ className
244
+ ),
245
+ ...props
246
+ }
247
+ )
248
+ );
249
+ ChatInput.displayName = "ChatInput";
250
+
251
+ // src/components/ui/expandable-chat.tsx
252
+ import { useRef, useState } from "react";
253
+ import { X, MessageCircle } from "lucide-react";
254
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
255
+ var chatConfig = {
256
+ dimensions: {
257
+ sm: "sm:max-w-sm sm:max-h-[500px]",
258
+ md: "sm:max-w-md sm:max-h-[600px]",
259
+ lg: "sm:max-w-lg sm:max-h-[700px]",
260
+ xl: "sm:max-w-xl sm:max-h-[800px]",
261
+ full: "sm:w-full sm:h-full"
262
+ },
263
+ positions: {
264
+ "bottom-right": "bottom-5 right-5",
265
+ "bottom-left": "bottom-5 left-5"
266
+ },
267
+ chatPositions: {
268
+ "bottom-right": "sm:bottom-[calc(100%+10px)] sm:right-0",
269
+ "bottom-left": "sm:bottom-[calc(100%+10px)] sm:left-0"
270
+ },
271
+ states: {
272
+ open: "pointer-events-auto opacity-100 visible scale-100 translate-y-0",
273
+ closed: "pointer-events-none opacity-0 invisible scale-100 sm:translate-y-5"
274
+ }
275
+ };
276
+ var ExpandableChat = ({
277
+ className,
278
+ position = "bottom-right",
279
+ size = "md",
280
+ icon,
281
+ children,
282
+ isOpen: controlledIsOpen,
283
+ onOpenChange,
284
+ ...props
285
+ }) => {
286
+ const [internalIsOpen, setInternalIsOpen] = useState(false);
287
+ const isControlled = controlledIsOpen !== void 0;
288
+ const isOpen = isControlled ? controlledIsOpen : internalIsOpen;
289
+ const chatRef = useRef(null);
290
+ const toggleChat = () => {
291
+ const newState = !isOpen;
292
+ if (isControlled && onOpenChange) {
293
+ onOpenChange(newState);
294
+ } else {
295
+ setInternalIsOpen(newState);
296
+ }
297
+ };
298
+ return /* @__PURE__ */ jsxs3(
299
+ "div",
300
+ {
301
+ className: cn(`fixed ${chatConfig.positions[position]} z-50`, className),
302
+ ...props,
303
+ children: [
304
+ /* @__PURE__ */ jsxs3(
305
+ "div",
306
+ {
307
+ ref: chatRef,
308
+ className: cn(
309
+ "flex flex-col bg-background dark:bg-zinc-900 sm:rounded-lg shadow-md overflow-hidden transition-all duration-250 ease-out sm:absolute sm:w-[90vw] sm:h-[80vh] fixed inset-0 w-full h-full sm:inset-auto",
310
+ chatConfig.chatPositions[position],
311
+ chatConfig.dimensions[size],
312
+ isOpen ? chatConfig.states.open : chatConfig.states.closed,
313
+ className
314
+ ),
315
+ children: [
316
+ children,
317
+ /* @__PURE__ */ jsx7(
318
+ Button,
319
+ {
320
+ variant: "ghost",
321
+ size: "icon",
322
+ className: "absolute top-2 right-2 sm:hidden",
323
+ onClick: toggleChat,
324
+ children: /* @__PURE__ */ jsx7(X, { className: "h-4 w-4" })
325
+ }
326
+ )
327
+ ]
328
+ }
329
+ ),
330
+ /* @__PURE__ */ jsx7(
331
+ ExpandableChatToggle,
332
+ {
333
+ icon,
334
+ isOpen,
335
+ toggleChat
336
+ }
337
+ )
338
+ ]
339
+ }
340
+ );
341
+ };
342
+ ExpandableChat.displayName = "ExpandableChat";
343
+ var ExpandableChatHeader = ({
344
+ className,
345
+ ...props
346
+ }) => /* @__PURE__ */ jsx7(
347
+ "div",
348
+ {
349
+ className: cn("flex items-center justify-between p-4 border-b", className),
350
+ ...props
351
+ }
352
+ );
353
+ ExpandableChatHeader.displayName = "ExpandableChatHeader";
354
+ var ExpandableChatBody = ({
355
+ className,
356
+ ...props
357
+ }) => /* @__PURE__ */ jsx7("div", { className: cn("flex-grow overflow-y-auto", className), ...props });
358
+ ExpandableChatBody.displayName = "ExpandableChatBody";
359
+ var ExpandableChatFooter = ({
360
+ className,
361
+ ...props
362
+ }) => /* @__PURE__ */ jsx7("div", { className: cn("border-t p-4", className), ...props });
363
+ ExpandableChatFooter.displayName = "ExpandableChatFooter";
364
+ var ExpandableChatToggle = ({
365
+ className,
366
+ icon,
367
+ isOpen,
368
+ toggleChat,
369
+ ...props
370
+ }) => /* @__PURE__ */ jsx7(
371
+ Button,
372
+ {
373
+ variant: "default",
374
+ onClick: toggleChat,
375
+ className: cn(
376
+ "w-16 h-16 rounded-full shadow-md flex items-center justify-center hover:shadow-lg hover:shadow-black/30 transition-all duration-300 p-0 ring-0",
377
+ !isOpen ? "!bg-transparent !border-0" : "",
378
+ className
379
+ ),
380
+ ...props,
381
+ children: isOpen ? /* @__PURE__ */ jsx7(X, { className: "h-6 w-6" }) : icon || /* @__PURE__ */ jsx7(MessageCircle, { className: "h-6 w-6" })
382
+ }
383
+ );
384
+ ExpandableChatToggle.displayName = "ExpandableChatToggle";
385
+
386
+ // src/components/ui/chat-message-list.tsx
387
+ import * as React3 from "react";
388
+ import { ArrowDown } from "lucide-react";
389
+
390
+ // src/components/hooks/use-auto-scroll.ts
391
+ import { useCallback, useEffect, useRef as useRef2, useState as useState2 } from "react";
392
+ function useAutoScroll(options = {}) {
393
+ const { offset = 20, smooth = false, content } = options;
394
+ const scrollRef = useRef2(null);
395
+ const lastContentHeight = useRef2(0);
396
+ const userHasScrolled = useRef2(false);
397
+ const [scrollState, setScrollState] = useState2({
398
+ isAtBottom: true,
399
+ autoScrollEnabled: true
400
+ });
401
+ const checkIsAtBottom = useCallback(
402
+ (element) => {
403
+ const { scrollTop, scrollHeight, clientHeight } = element;
404
+ const distanceToBottom = Math.abs(
405
+ scrollHeight - scrollTop - clientHeight
406
+ );
407
+ return distanceToBottom <= offset;
408
+ },
409
+ [offset]
410
+ );
411
+ const scrollToBottom = useCallback(
412
+ (instant) => {
413
+ if (!scrollRef.current) return;
414
+ const targetScrollTop = scrollRef.current.scrollHeight - scrollRef.current.clientHeight;
415
+ if (instant) {
416
+ scrollRef.current.scrollTop = targetScrollTop;
417
+ } else {
418
+ scrollRef.current.scrollTo({
419
+ top: targetScrollTop,
420
+ behavior: smooth ? "smooth" : "auto"
421
+ });
422
+ }
423
+ setScrollState({
424
+ isAtBottom: true,
425
+ autoScrollEnabled: true
426
+ });
427
+ userHasScrolled.current = false;
428
+ },
429
+ [smooth]
430
+ );
431
+ const handleScroll = useCallback(() => {
432
+ if (!scrollRef.current) return;
433
+ const atBottom = checkIsAtBottom(scrollRef.current);
434
+ setScrollState((prev) => ({
435
+ isAtBottom: atBottom,
436
+ // Re-enable auto-scroll if at the bottom
437
+ autoScrollEnabled: atBottom ? true : prev.autoScrollEnabled
438
+ }));
439
+ }, [checkIsAtBottom]);
440
+ useEffect(() => {
441
+ const element = scrollRef.current;
442
+ if (!element) return;
443
+ element.addEventListener("scroll", handleScroll, { passive: true });
444
+ return () => element.removeEventListener("scroll", handleScroll);
445
+ }, [handleScroll]);
446
+ useEffect(() => {
447
+ const scrollElement = scrollRef.current;
448
+ if (!scrollElement) return;
449
+ const currentHeight = scrollElement.scrollHeight;
450
+ const hasNewContent = currentHeight !== lastContentHeight.current;
451
+ if (hasNewContent) {
452
+ if (scrollState.autoScrollEnabled) {
453
+ requestAnimationFrame(() => {
454
+ scrollToBottom(lastContentHeight.current === 0);
455
+ });
456
+ }
457
+ lastContentHeight.current = currentHeight;
458
+ }
459
+ }, [content, scrollState.autoScrollEnabled, scrollToBottom]);
460
+ useEffect(() => {
461
+ const element = scrollRef.current;
462
+ if (!element) return;
463
+ const resizeObserver = new ResizeObserver(() => {
464
+ if (scrollState.autoScrollEnabled) {
465
+ scrollToBottom(true);
466
+ }
467
+ });
468
+ resizeObserver.observe(element);
469
+ return () => resizeObserver.disconnect();
470
+ }, [scrollState.autoScrollEnabled, scrollToBottom]);
471
+ const disableAutoScroll = useCallback(() => {
472
+ const atBottom = scrollRef.current ? checkIsAtBottom(scrollRef.current) : false;
473
+ if (!atBottom) {
474
+ userHasScrolled.current = true;
475
+ setScrollState((prev) => ({
476
+ ...prev,
477
+ autoScrollEnabled: false
478
+ }));
479
+ }
480
+ }, [checkIsAtBottom]);
481
+ return {
482
+ scrollRef,
483
+ isAtBottom: scrollState.isAtBottom,
484
+ autoScrollEnabled: scrollState.autoScrollEnabled,
485
+ scrollToBottom: () => scrollToBottom(false),
486
+ disableAutoScroll
487
+ };
488
+ }
489
+
490
+ // src/components/ui/chat-message-list.tsx
491
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
492
+ var ChatMessageList = React3.forwardRef(
493
+ ({ className, children, smooth = false, ...props }, _ref) => {
494
+ const {
495
+ scrollRef,
496
+ isAtBottom,
497
+ autoScrollEnabled,
498
+ scrollToBottom,
499
+ disableAutoScroll
500
+ } = useAutoScroll({
501
+ smooth,
502
+ content: children
503
+ });
504
+ return /* @__PURE__ */ jsxs4("div", { className: "relative w-full h-full", children: [
505
+ /* @__PURE__ */ jsx8(
506
+ "div",
507
+ {
508
+ className: `flex flex-col w-full h-full p-4 overflow-y-auto ${className}`,
509
+ ref: scrollRef,
510
+ onWheel: disableAutoScroll,
511
+ onTouchMove: disableAutoScroll,
512
+ ...props,
513
+ children: /* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-6", children })
514
+ }
515
+ ),
516
+ !isAtBottom && /* @__PURE__ */ jsx8(
517
+ Button,
518
+ {
519
+ onClick: () => {
520
+ scrollToBottom();
521
+ },
522
+ size: "icon",
523
+ variant: "outline",
524
+ className: "absolute bottom-2 left-1/2 transform -translate-x-1/2 inline-flex rounded-full shadow-md",
525
+ "aria-label": "Scroll to bottom",
526
+ children: /* @__PURE__ */ jsx8(ArrowDown, { className: "h-4 w-4" })
527
+ }
528
+ )
529
+ ] });
530
+ }
531
+ );
532
+ ChatMessageList.displayName = "ChatMessageList";
533
+
534
+ // src/components/ui/shining-text.tsx
535
+ import { motion } from "framer-motion";
536
+ import { jsx as jsx9 } from "react/jsx-runtime";
537
+ function ShiningText({ text }) {
538
+ return /* @__PURE__ */ jsx9(
539
+ motion.div,
540
+ {
541
+ className: "bg-[linear-gradient(110deg,#9ca3af,35%,#fff,50%,#9ca3af,75%,#9ca3af)] bg-[length:200%_100%] bg-clip-text text-sm text-transparent m-0 inline-block",
542
+ initial: { backgroundPosition: "200% 0" },
543
+ animate: { backgroundPosition: "-200% 0" },
544
+ transition: {
545
+ repeat: Infinity,
546
+ duration: 2,
547
+ ease: "linear"
548
+ },
549
+ children: text
550
+ }
551
+ );
552
+ }
553
+
554
+ // src/components/ui/moving-border.tsx
555
+ import {
556
+ motion as motion2,
557
+ useAnimationFrame,
558
+ useMotionTemplate,
559
+ useMotionValue,
560
+ useTransform
561
+ } from "framer-motion";
562
+ import { useRef as useRef3 } from "react";
563
+ import { Fragment, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
564
+ function Button2({
565
+ borderRadius = "1.75rem",
566
+ children,
567
+ as: Component = "button",
568
+ containerClassName,
569
+ borderClassName,
570
+ duration,
571
+ className,
572
+ ...otherProps
573
+ }) {
574
+ return /* @__PURE__ */ jsxs5(
575
+ Component,
576
+ {
577
+ className: cn(
578
+ "bg-transparent relative text-xl h-16 w-40 p-[1px] overflow-hidden ",
579
+ containerClassName
580
+ ),
581
+ style: {
582
+ borderRadius
583
+ },
584
+ ...otherProps,
585
+ children: [
586
+ /* @__PURE__ */ jsx10(
587
+ "div",
588
+ {
589
+ className: "absolute inset-0",
590
+ style: { borderRadius: `calc(${borderRadius} * 0.96)` },
591
+ children: /* @__PURE__ */ jsx10(MovingBorder, { duration, rx: "30%", ry: "30%", children: /* @__PURE__ */ jsx10(
592
+ "div",
593
+ {
594
+ className: cn(
595
+ "h-20 w-20 opacity-[0.8] bg-[radial-gradient(#0ea5e9_40%,transparent_60%)]",
596
+ borderClassName
597
+ )
598
+ }
599
+ ) })
600
+ }
601
+ ),
602
+ /* @__PURE__ */ jsx10(
603
+ "div",
604
+ {
605
+ className: cn(
606
+ "relative bg-background border border-border backdrop-blur-xl text-foreground flex items-center justify-center w-full h-full text-sm antialiased",
607
+ className
608
+ ),
609
+ style: {
610
+ borderRadius: `calc(${borderRadius} * 0.96)`
611
+ },
612
+ children
613
+ }
614
+ )
615
+ ]
616
+ }
617
+ );
618
+ }
619
+ var MovingBorder = ({
620
+ children,
621
+ duration = 2e3,
622
+ rx,
623
+ ry,
624
+ ...otherProps
625
+ }) => {
626
+ const pathRef = useRef3(null);
627
+ const progress = useMotionValue(0);
628
+ useAnimationFrame((time) => {
629
+ const length = pathRef.current?.getTotalLength();
630
+ if (length) {
631
+ const pxPerMillisecond = length / duration;
632
+ progress.set(time * pxPerMillisecond % length);
633
+ }
634
+ });
635
+ const x = useTransform(
636
+ progress,
637
+ (val) => pathRef.current?.getPointAtLength(val).x
638
+ );
639
+ const y = useTransform(
640
+ progress,
641
+ (val) => pathRef.current?.getPointAtLength(val).y
642
+ );
643
+ const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;
644
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
645
+ /* @__PURE__ */ jsx10(
646
+ "svg",
647
+ {
648
+ xmlns: "http://www.w3.org/2000/svg",
649
+ preserveAspectRatio: "none",
650
+ className: "absolute h-full w-full",
651
+ width: "100%",
652
+ height: "100%",
653
+ ...otherProps,
654
+ children: /* @__PURE__ */ jsx10(
655
+ "rect",
656
+ {
657
+ fill: "none",
658
+ width: "100%",
659
+ height: "100%",
660
+ rx,
661
+ ry,
662
+ ref: pathRef
663
+ }
664
+ )
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsx10(
668
+ motion2.div,
669
+ {
670
+ style: {
671
+ position: "absolute",
672
+ top: 0,
673
+ left: 0,
674
+ display: "inline-block",
675
+ transform
676
+ },
677
+ children
678
+ }
679
+ )
680
+ ] });
681
+ };
682
+
683
+ // src/ChatUI.tsx
684
+ import ReactMarkdown from "react-markdown";
685
+ import remarkGfm from "remark-gfm";
686
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
687
+ function ChatUI({
688
+ endpoint,
689
+ logoSrc = "/bot_logo.jpg",
690
+ soundSrc = "/sound.mp3",
691
+ title = "AI Assistant",
692
+ welcomeMessage = "Welcome to Services! \u{1F44B}",
693
+ description = "I'm here to help you navigate our services. Feel free to ask me anything!"
694
+ }) {
695
+ const [messages, setMessages] = useState3([]);
696
+ const [input, setInput] = useState3("");
697
+ const [isLoading, setIsLoading] = useState3(false);
698
+ const [isInitialized, setIsInitialized] = useState3(false);
699
+ useEffect2(() => {
700
+ const savedMessages = localStorage.getItem("chat-history");
701
+ if (savedMessages) {
702
+ try {
703
+ setMessages(JSON.parse(savedMessages));
704
+ } catch (e) {
705
+ console.error("Failed to parse chat history", e);
706
+ }
707
+ }
708
+ setIsInitialized(true);
709
+ }, []);
710
+ const [isChatOpen, setIsChatOpen] = useState3(false);
711
+ const [showNotification, setShowNotification] = useState3(false);
712
+ const [isDismissed, setIsDismissed] = useState3(false);
713
+ useEffect2(() => {
714
+ const timer = setTimeout(() => {
715
+ if (!isDismissed && !isChatOpen) {
716
+ setShowNotification(true);
717
+ const audio = new Audio(soundSrc);
718
+ audio.play().catch((error) => {
719
+ if (error.name !== "NotAllowedError") {
720
+ console.error("Error playing notification sound:", error);
721
+ }
722
+ });
723
+ }
724
+ }, 2e3);
725
+ return () => clearTimeout(timer);
726
+ }, [isChatOpen, isDismissed, soundSrc]);
727
+ useEffect2(() => {
728
+ if (isChatOpen) {
729
+ setShowNotification(false);
730
+ setIsDismissed(true);
731
+ }
732
+ }, [isChatOpen]);
733
+ const handleDismissNotification = () => {
734
+ setShowNotification(false);
735
+ setIsDismissed(true);
736
+ };
737
+ useEffect2(() => {
738
+ if (isInitialized) {
739
+ localStorage.setItem("chat-history", JSON.stringify(messages));
740
+ }
741
+ }, [messages, isInitialized]);
742
+ const handleReset = () => {
743
+ setMessages([]);
744
+ localStorage.removeItem("chat-history");
745
+ };
746
+ const handleSubmit = async (e) => {
747
+ e.preventDefault();
748
+ if (!input.trim() || isLoading) return;
749
+ const userQuestion = input.trim();
750
+ const userMessage = {
751
+ id: Date.now().toString(),
752
+ role: "user",
753
+ content: userQuestion
754
+ };
755
+ const history = messages.map((msg) => ({
756
+ role: msg.role,
757
+ content: msg.content
758
+ }));
759
+ setMessages((prev) => [...prev, userMessage]);
760
+ setInput("");
761
+ setIsLoading(true);
762
+ try {
763
+ const response = await fetch(endpoint, {
764
+ method: "POST",
765
+ headers: {
766
+ "Content-Type": "application/json"
767
+ },
768
+ body: JSON.stringify({
769
+ query: userQuestion,
770
+ history,
771
+ stream: true
772
+ })
773
+ });
774
+ if (!response.body) {
775
+ throw new Error("ReadableStream not supported.");
776
+ }
777
+ const reader = response.body.getReader();
778
+ const decoder = new TextDecoder();
779
+ let aiResponse = "";
780
+ let isFirstChunk = true;
781
+ while (true) {
782
+ const { value, done } = await reader.read();
783
+ if (done) break;
784
+ const chunk = decoder.decode(value, { stream: true });
785
+ aiResponse += chunk;
786
+ if (isFirstChunk) {
787
+ isFirstChunk = false;
788
+ setIsLoading(false);
789
+ const botMessageId = (Date.now() + 1).toString();
790
+ const botMessage = {
791
+ id: botMessageId,
792
+ role: "assistant",
793
+ content: aiResponse
794
+ };
795
+ setMessages((prev) => [...prev, botMessage]);
796
+ } else {
797
+ setMessages((prev) => {
798
+ const updated = [...prev];
799
+ const lastMsgIndex = updated.length - 1;
800
+ if (lastMsgIndex >= 0 && updated[lastMsgIndex].role === "assistant") {
801
+ updated[lastMsgIndex] = {
802
+ ...updated[lastMsgIndex],
803
+ content: aiResponse
804
+ };
805
+ }
806
+ return updated;
807
+ });
808
+ }
809
+ }
810
+ const audio = new Audio(soundSrc);
811
+ audio.play().catch((e2) => console.error("Error playing notification sound:", e2));
812
+ } catch (error) {
813
+ console.error("Chat error:", error);
814
+ setMessages((prev) => [
815
+ ...prev,
816
+ {
817
+ id: (Date.now() + 2).toString(),
818
+ role: "assistant",
819
+ content: "I apologize, but I encountered an error. Please try again later."
820
+ }
821
+ ]);
822
+ } finally {
823
+ setIsLoading(false);
824
+ }
825
+ };
826
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
827
+ showNotification && !isChatOpen && /* @__PURE__ */ jsx11("div", { className: "fixed bottom-24 right-4 sm:right-5 z-50 animate-in fade-in slide-in-from-bottom-5 duration-300", children: /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
828
+ /* @__PURE__ */ jsxs6(
829
+ Button2,
830
+ {
831
+ as: "div",
832
+ borderRadius: "0.75rem",
833
+ duration: 3e3,
834
+ containerClassName: "w-72 sm:w-80 h-auto overflow-hidden rounded-xl bg-transparent",
835
+ borderClassName: "bg-[radial-gradient(#0ea5e9_40%,transparent_60%)]",
836
+ className: "bg-background dark:bg-zinc-900 border dark:border-zinc-800 p-5 items-start justify-start flex-col w-full h-full text-foreground shadow-lg",
837
+ children: [
838
+ /* @__PURE__ */ jsxs6(
839
+ Button,
840
+ {
841
+ variant: "ghost",
842
+ size: "icon",
843
+ className: "absolute top-3 right-3 h-6 w-6 text-muted-foreground hover:text-foreground z-10",
844
+ onClick: handleDismissNotification,
845
+ children: [
846
+ /* @__PURE__ */ jsx11(X2, { className: "h-4 w-4" }),
847
+ /* @__PURE__ */ jsx11("span", { className: "sr-only", children: "Close" })
848
+ ]
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-start gap-3 mb-4 w-full", children: [
852
+ /* @__PURE__ */ jsx11("div", { className: "relative h-10 w-10 shrink-0 overflow-hidden rounded-full ring-2 ring-background dark:ring-zinc-800", children: /* @__PURE__ */ jsx11("img", { src: logoSrc, alt: "AI", className: "object-cover h-full w-full" }) }),
853
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-col pt-0.5", children: [
854
+ /* @__PURE__ */ jsx11("h3", { className: "font-bold text-sm leading-tight text-foreground", children: title }),
855
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 mt-0.5", children: [
856
+ /* @__PURE__ */ jsxs6("span", { className: "relative flex h-2 w-2", children: [
857
+ /* @__PURE__ */ jsx11("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
858
+ /* @__PURE__ */ jsx11("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-green-500" })
859
+ ] }),
860
+ /* @__PURE__ */ jsx11("span", { className: "text-xs text-muted-foreground font-medium", children: "Online" })
861
+ ] })
862
+ ] })
863
+ ] }),
864
+ /* @__PURE__ */ jsxs6("div", { className: "w-full text-left", children: [
865
+ /* @__PURE__ */ jsx11("p", { className: "text-sm font-medium text-foreground leading-snug", children: welcomeMessage }),
866
+ /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground leading-relaxed mt-2", children: description }),
867
+ /* @__PURE__ */ jsx11(
868
+ Button,
869
+ {
870
+ className: "w-full mt-4 h-10 rounded-lg bg-[#535bf2] hover:bg-[#464ec9] text-white shadow-sm transition-all duration-200 font-medium",
871
+ onClick: () => setIsChatOpen(true),
872
+ children: "Start chatting"
873
+ }
874
+ )
875
+ ] })
876
+ ]
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsx11("div", { className: "absolute -bottom-2 right-6 w-4 h-4 bg-background dark:bg-zinc-900 border-b border-r dark:border-zinc-800 transform rotate-45 z-0" })
880
+ ] }) }),
881
+ /* @__PURE__ */ jsxs6(
882
+ ExpandableChat,
883
+ {
884
+ size: "lg",
885
+ position: "bottom-right",
886
+ isOpen: isChatOpen,
887
+ onOpenChange: setIsChatOpen,
888
+ icon: /* @__PURE__ */ jsx11("div", { className: "relative h-full w-full overflow-hidden rounded-full", children: /* @__PURE__ */ jsx11("img", { src: logoSrc, alt: "AI", className: "object-cover h-full w-full" }) }),
889
+ className: "z-50",
890
+ children: [
891
+ /* @__PURE__ */ jsxs6(ExpandableChatHeader, { className: "bg-muted/40 flex-col text-center justify-center border-b p-4 relative", children: [
892
+ /* @__PURE__ */ jsxs6("h1", { className: "text-xl font-semibold flex items-center justify-center gap-2", children: [
893
+ title,
894
+ /* @__PURE__ */ jsx11(Sparkles, { className: "h-4 w-4 text-yellow-500 fill-yellow-500" })
895
+ ] }),
896
+ /* @__PURE__ */ jsxs6("p", { className: "text-sm text-muted-foreground flex items-center justify-center gap-1.5 pt-1", children: [
897
+ /* @__PURE__ */ jsx11("span", { className: "flex h-2 w-2 rounded-full bg-green-500 animate-pulse" }),
898
+ "Online and ready to help"
899
+ ] }),
900
+ /* @__PURE__ */ jsx11(
901
+ Button,
902
+ {
903
+ variant: "ghost",
904
+ size: "icon",
905
+ className: "absolute top-3 left-3 h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/50 sm:top-5 sm:right-5 sm:left-auto",
906
+ onClick: handleReset,
907
+ title: "Reset Chat",
908
+ children: /* @__PURE__ */ jsx11(RefreshCcw, { className: "h-4 w-4" })
909
+ }
910
+ )
911
+ ] }),
912
+ /* @__PURE__ */ jsx11(ExpandableChatBody, { className: "bg-background/50", children: messages.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center justify-center h-full p-6 text-center animate-in fade-in duration-500", children: [
913
+ /* @__PURE__ */ jsx11("div", { className: "bg-background rounded-full p-4 mb-6 shadow-md ring-1 ring-border/50", children: /* @__PURE__ */ jsx11("div", { className: "relative w-16 h-16 overflow-hidden rounded-full", children: /* @__PURE__ */ jsx11("img", { src: logoSrc, alt: "AI Logo", className: "object-cover w-full h-full" }) }) }),
914
+ /* @__PURE__ */ jsx11("h2", { className: "text-2xl font-bold mb-2 tracking-tight", children: title }),
915
+ /* @__PURE__ */ jsxs6("p", { className: "text-muted-foreground text-sm", children: [
916
+ "Welcome to ",
917
+ title,
918
+ " \u{1F49B}"
919
+ ] })
920
+ ] }) : /* @__PURE__ */ jsxs6(ChatMessageList, { children: [
921
+ messages.map((message) => /* @__PURE__ */ jsxs6(
922
+ ChatBubble,
923
+ {
924
+ variant: message.role === "user" ? "sent" : "received",
925
+ children: [
926
+ /* @__PURE__ */ jsx11(
927
+ ChatBubbleAvatar,
928
+ {
929
+ className: "h-8 w-8 shrink-0",
930
+ src: message.role === "user" ? void 0 : logoSrc,
931
+ fallback: message.role === "user" ? /* @__PURE__ */ jsx11(User, { className: "h-4 w-4" }) : "AI"
932
+ }
933
+ ),
934
+ /* @__PURE__ */ jsx11(
935
+ ChatBubbleMessage,
936
+ {
937
+ variant: message.role === "user" ? "sent" : "received",
938
+ children: message.role === "user" ? message.content : /* @__PURE__ */ jsx11("div", { className: cn(
939
+ "prose dark:prose-invert text-sm break-words leading-normal max-w-none",
940
+ "prose-p:m-0 prose-ul:m-0 prose-ol:m-0 prose-li:m-0"
941
+ ), children: /* @__PURE__ */ jsx11(
942
+ ReactMarkdown,
943
+ {
944
+ remarkPlugins: [remarkGfm],
945
+ components: {
946
+ ul: ({ node, ...props }) => /* @__PURE__ */ jsx11("ul", { className: "list-disc pl-4 my-1", ...props }),
947
+ ol: ({ node, ...props }) => /* @__PURE__ */ jsx11("ol", { className: "list-decimal pl-4 my-1", ...props }),
948
+ li: ({ node, ...props }) => /* @__PURE__ */ jsx11("li", { className: "my-0.5 pl-1", ...props }),
949
+ p: ({ node, ...props }) => /* @__PURE__ */ jsx11("p", { className: "mb-2 last:mb-0", ...props }),
950
+ strong: ({ node, ...props }) => /* @__PURE__ */ jsx11("span", { className: "font-bold text-foreground", ...props }),
951
+ a: ({ node, href, children, ...props }) => /* @__PURE__ */ jsx11(
952
+ "a",
953
+ {
954
+ href,
955
+ target: "_blank",
956
+ rel: "noopener noreferrer",
957
+ className: "font-semibold text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 hover:underline transition-colors break-all",
958
+ ...props,
959
+ children
960
+ }
961
+ )
962
+ },
963
+ children: message.content
964
+ }
965
+ ) })
966
+ }
967
+ )
968
+ ]
969
+ },
970
+ message.id
971
+ )),
972
+ isLoading && /* @__PURE__ */ jsxs6(ChatBubble, { variant: "received", children: [
973
+ /* @__PURE__ */ jsx11(
974
+ ChatBubbleAvatar,
975
+ {
976
+ className: "h-8 w-8 shrink-0",
977
+ src: logoSrc,
978
+ fallback: "AI"
979
+ }
980
+ ),
981
+ /* @__PURE__ */ jsxs6(ChatBubbleMessage, { className: "bg-transparent p-0 flex items-center gap-2", children: [
982
+ /* @__PURE__ */ jsx11(Sparkles, { className: "h-4 w-4 text-foreground/50" }),
983
+ /* @__PURE__ */ jsx11(ShiningText, { text: "AI Assistant thinking..." })
984
+ ] })
985
+ ] })
986
+ ] }) }),
987
+ /* @__PURE__ */ jsxs6(ExpandableChatFooter, { className: "bg-muted/40 p-3", children: [
988
+ /* @__PURE__ */ jsxs6(
989
+ "form",
990
+ {
991
+ onSubmit: handleSubmit,
992
+ className: "relative rounded-3xl border bg-background focus-within:ring-1 focus-within:ring-ring p-1 shadow-sm",
993
+ children: [
994
+ /* @__PURE__ */ jsx11(
995
+ ChatInput,
996
+ {
997
+ value: input,
998
+ onChange: (e) => setInput(e.target.value),
999
+ placeholder: "Message...",
1000
+ rows: 1,
1001
+ className: "min-h-0 h-auto max-h-32 resize-none rounded-2xl bg-background border-0 px-3 py-2.5 shadow-none focus-visible:ring-0 text-base sm:text-sm",
1002
+ onKeyDown: (e) => {
1003
+ if (e.key === "Enter" && !e.shiftKey) {
1004
+ e.preventDefault();
1005
+ handleSubmit(e);
1006
+ }
1007
+ }
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-2 pb-1", children: [
1011
+ /* @__PURE__ */ jsx11("div", { className: "flex items-center gap-2" }),
1012
+ /* @__PURE__ */ jsxs6(
1013
+ Button,
1014
+ {
1015
+ type: "submit",
1016
+ size: "icon",
1017
+ className: "h-8 w-8 rounded-full transition-all duration-200",
1018
+ disabled: !input.trim() || isLoading,
1019
+ children: [
1020
+ isLoading ? /* @__PURE__ */ jsx11(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx11(Send, { className: "h-4 w-4" }),
1021
+ /* @__PURE__ */ jsx11("span", { className: "sr-only", children: "Send" })
1022
+ ]
1023
+ }
1024
+ )
1025
+ ] })
1026
+ ]
1027
+ }
1028
+ ),
1029
+ /* @__PURE__ */ jsx11("div", { className: "mt-2 text-center flex flex-col items-center justify-center gap-0.5", children: /* @__PURE__ */ jsxs6("span", { className: "text-[10px] text-muted-foreground/60", children: [
1030
+ "Powered by ",
1031
+ /* @__PURE__ */ jsx11("a", { href: "#", className: "hover:underline hover:text-primary transition-colors", children: "ChatUI" })
1032
+ ] }) })
1033
+ ] })
1034
+ ]
1035
+ }
1036
+ )
1037
+ ] });
1038
+ }
1039
+ export {
1040
+ ChatUI
1041
+ };
1042
+ //# sourceMappingURL=index.mjs.map