@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.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1073 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1042 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
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
|