@usereq/widget 0.1.4 → 0.1.5
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/widget.js +348 -348
- package/package.json +1 -1
- package/src/chat-widget/styles/chat-widget-messenger.tsx +225 -49
package/package.json
CHANGED
|
@@ -10,25 +10,46 @@ import {
|
|
|
10
10
|
} from "@usereq/ui/components/avatar";
|
|
11
11
|
import { Button } from "@usereq/ui/components/button";
|
|
12
12
|
import { Textarea } from "@usereq/ui/components/textarea";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ChevronDown,
|
|
15
|
+
SendHorizonal,
|
|
16
|
+
Phone,
|
|
17
|
+
Video,
|
|
18
|
+
Info,
|
|
19
|
+
} from "lucide-react";
|
|
14
20
|
import {
|
|
15
21
|
DEFAULT_AGENT_NAME_BUBBLE,
|
|
16
|
-
DEFAULT_COMPOSER_PLACEHOLDER,
|
|
17
22
|
DEFAULT_POWERED_BY,
|
|
18
23
|
DEFAULT_SPOTLIGHT_MESSAGES,
|
|
19
24
|
} from "../chat-widget-defaults";
|
|
20
25
|
import {
|
|
21
|
-
ChatWidgetMessages,
|
|
22
26
|
ChatWidgetPanel,
|
|
23
27
|
ChatWidgetPoweredBy,
|
|
28
|
+
ChatWidgetTyping,
|
|
24
29
|
useIsChatWidgetFullscreenViewport,
|
|
25
30
|
useSpotlightFade,
|
|
26
31
|
type ChatWidgetDataProps,
|
|
32
|
+
type ChatWidgetPreviewMessage,
|
|
27
33
|
} from "../components/chat-widget-primitives";
|
|
34
|
+
import {
|
|
35
|
+
Conversation,
|
|
36
|
+
ConversationContent,
|
|
37
|
+
ConversationEmptyState,
|
|
38
|
+
ConversationScrollButton,
|
|
39
|
+
} from "../components/conversation";
|
|
40
|
+
import { foldStopConfirmationMessages } from "../stop-confirmation";
|
|
41
|
+
import { getChatWidgetMessagesLayoutClasses } from "../components/chat-widget-layout";
|
|
42
|
+
import {
|
|
43
|
+
Message,
|
|
44
|
+
MessageContent,
|
|
45
|
+
MessageResponse,
|
|
46
|
+
} from "../components/message";
|
|
28
47
|
|
|
29
48
|
const MESSENGER_GRADIENT =
|
|
30
49
|
"linear-gradient(135deg, #00B2FF 0%, #006AFF 50%, #8E5BFF 100%)";
|
|
31
50
|
|
|
51
|
+
const MESSENGER_BRAND_COLOR = "#0084FF";
|
|
52
|
+
|
|
32
53
|
const PANEL_WIDTH = 372;
|
|
33
54
|
const PANEL_HEIGHT = 580;
|
|
34
55
|
const LAUNCHER_SIZE = 60;
|
|
@@ -40,7 +61,6 @@ const fadeTransition = { duration: 0.35 };
|
|
|
40
61
|
export function ChatWidgetMessenger({
|
|
41
62
|
agentName = DEFAULT_AGENT_NAME_BUBBLE,
|
|
42
63
|
agentAvatarUrl,
|
|
43
|
-
appearance,
|
|
44
64
|
spotlightMessages = [],
|
|
45
65
|
messages = [],
|
|
46
66
|
poweredBy = DEFAULT_POWERED_BY,
|
|
@@ -50,7 +70,6 @@ export function ChatWidgetMessenger({
|
|
|
50
70
|
sendDisabled = false,
|
|
51
71
|
onSendMessage,
|
|
52
72
|
isTyping,
|
|
53
|
-
onSuggestion,
|
|
54
73
|
onLauncherClick,
|
|
55
74
|
className,
|
|
56
75
|
open,
|
|
@@ -120,7 +139,7 @@ export function ChatWidgetMessenger({
|
|
|
120
139
|
transition={fadeTransition}
|
|
121
140
|
className="absolute right-0 bottom-[calc(60px+10px)] flex w-full max-w-70 flex-col items-end"
|
|
122
141
|
>
|
|
123
|
-
<div className="rounded-3xl bg-
|
|
142
|
+
<div className="rounded-3xl bg-background px-4 py-2.5 text-right shadow-[0_4px_16px_rgba(0,0,0,0.12)]">
|
|
124
143
|
<p className="text-[14px] leading-snug text-foreground">
|
|
125
144
|
{spotlightText}
|
|
126
145
|
</p>
|
|
@@ -134,7 +153,7 @@ export function ChatWidgetMessenger({
|
|
|
134
153
|
open={effectiveOpen}
|
|
135
154
|
onOpenChange={handleOpenChange}
|
|
136
155
|
portalContainer={portalContainer}
|
|
137
|
-
desktopClassName="absolute right-0 overflow-hidden rounded-3xl bg-background shadow-[0_8px_32px_rgba(0,0,0,0.16)] border border-
|
|
156
|
+
desktopClassName="absolute right-0 overflow-hidden rounded-3xl bg-background shadow-[0_8px_32px_rgba(0,0,0,0.16)] border border-border"
|
|
138
157
|
desktopStyle={{
|
|
139
158
|
width: PANEL_WIDTH,
|
|
140
159
|
height: PANEL_HEIGHT,
|
|
@@ -149,20 +168,16 @@ export function ChatWidgetMessenger({
|
|
|
149
168
|
onStopConversation={onStopConversation ?? undefined}
|
|
150
169
|
stopDisabled={stopDisabled}
|
|
151
170
|
/>
|
|
152
|
-
<div className="flex min-h-0 flex-1 flex-col
|
|
153
|
-
<
|
|
171
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background">
|
|
172
|
+
<MessengerMessages
|
|
154
173
|
messages={messages}
|
|
155
174
|
isTyping={isTyping ?? status === "sending"}
|
|
156
175
|
status={status}
|
|
157
|
-
suggestions={spotlightForFade}
|
|
158
176
|
errorMessage={errorMessage}
|
|
159
|
-
|
|
177
|
+
agentName={agentName}
|
|
178
|
+
agentAvatarUrl={agentAvatarUrl}
|
|
160
179
|
onConfirmationDecision={onConfirmationDecision}
|
|
161
180
|
onLinkClick={onLinkClick}
|
|
162
|
-
onSuggestion={(value: string) => {
|
|
163
|
-
setDraftValue(value);
|
|
164
|
-
onSuggestion?.(value);
|
|
165
|
-
}}
|
|
166
181
|
emptyAction={emptyAction}
|
|
167
182
|
/>
|
|
168
183
|
</div>
|
|
@@ -199,7 +214,7 @@ export function ChatWidgetMessenger({
|
|
|
199
214
|
style={{
|
|
200
215
|
width: LAUNCHER_SIZE,
|
|
201
216
|
height: LAUNCHER_SIZE,
|
|
202
|
-
|
|
217
|
+
backgroundColor: MESSENGER_BRAND_COLOR,
|
|
203
218
|
boxShadow: "0 6px 20px rgba(0, 132, 255, 0.45)",
|
|
204
219
|
}}
|
|
205
220
|
aria-label={effectiveOpen ? "Close chat" : "Open chat"}
|
|
@@ -209,7 +224,7 @@ export function ChatWidgetMessenger({
|
|
|
209
224
|
whileTap={previewOnly ? undefined : { scale: 0.96 }}
|
|
210
225
|
>
|
|
211
226
|
{effectiveOpen ? (
|
|
212
|
-
<
|
|
227
|
+
<ChevronDown className="size-7" strokeWidth={2.4} />
|
|
213
228
|
) : (
|
|
214
229
|
<MessengerLogo className="size-8" />
|
|
215
230
|
)}
|
|
@@ -219,6 +234,169 @@ export function ChatWidgetMessenger({
|
|
|
219
234
|
);
|
|
220
235
|
}
|
|
221
236
|
|
|
237
|
+
function MessengerMessages({
|
|
238
|
+
messages,
|
|
239
|
+
isTyping,
|
|
240
|
+
status,
|
|
241
|
+
errorMessage,
|
|
242
|
+
agentName,
|
|
243
|
+
agentAvatarUrl,
|
|
244
|
+
onConfirmationDecision,
|
|
245
|
+
onLinkClick,
|
|
246
|
+
emptyAction,
|
|
247
|
+
}: {
|
|
248
|
+
messages: ChatWidgetPreviewMessage[];
|
|
249
|
+
isTyping: boolean;
|
|
250
|
+
status: ChatWidgetDataProps["status"];
|
|
251
|
+
errorMessage?: string | null;
|
|
252
|
+
agentName: string;
|
|
253
|
+
agentAvatarUrl?: string | null;
|
|
254
|
+
onConfirmationDecision?: ChatWidgetDataProps["onConfirmationDecision"];
|
|
255
|
+
onLinkClick?: (linkUrl: string) => void;
|
|
256
|
+
emptyAction?: { label: string; onClick: () => void; disabled?: boolean };
|
|
257
|
+
}) {
|
|
258
|
+
const { scrollClassName, contentClassName } =
|
|
259
|
+
getChatWidgetMessagesLayoutClasses();
|
|
260
|
+
const renderedMessages = React.useMemo(
|
|
261
|
+
() => foldStopConfirmationMessages(messages),
|
|
262
|
+
[messages],
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
if (messages.length === 0 && !isTyping) {
|
|
266
|
+
if (status === "error") {
|
|
267
|
+
return (
|
|
268
|
+
<div className="flex h-full items-center justify-center px-6 py-8 text-center text-sm text-destructive">
|
|
269
|
+
{errorMessage ?? "Something went wrong."}
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<div className="flex min-h-full flex-1 items-center justify-center px-6 py-8">
|
|
276
|
+
<ConversationEmptyState className="w-full max-w-[280px] gap-4">
|
|
277
|
+
<Avatar
|
|
278
|
+
className="size-16 ring-4 ring-background"
|
|
279
|
+
style={{
|
|
280
|
+
boxShadow: "0 6px 18px rgba(0, 132, 255, 0.25)",
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
{agentAvatarUrl ? (
|
|
284
|
+
<AvatarImage src={agentAvatarUrl} alt={agentName} />
|
|
285
|
+
) : null}
|
|
286
|
+
<AvatarFallback
|
|
287
|
+
className="text-white text-base font-semibold"
|
|
288
|
+
style={{ backgroundColor: MESSENGER_BRAND_COLOR }}
|
|
289
|
+
>
|
|
290
|
+
{agentName.slice(0, 2).toUpperCase()}
|
|
291
|
+
</AvatarFallback>
|
|
292
|
+
</Avatar>
|
|
293
|
+
<div>
|
|
294
|
+
<p className="text-[15px] font-semibold leading-tight text-foreground">
|
|
295
|
+
{agentName}
|
|
296
|
+
</p>
|
|
297
|
+
<p className="mt-1.5 text-[13px] leading-snug text-muted-foreground">
|
|
298
|
+
Typically replies in a few minutes.
|
|
299
|
+
</p>
|
|
300
|
+
</div>
|
|
301
|
+
{emptyAction && (
|
|
302
|
+
<Button
|
|
303
|
+
size="lg"
|
|
304
|
+
disabled={emptyAction.disabled}
|
|
305
|
+
onClick={emptyAction.onClick}
|
|
306
|
+
className="rounded-full px-6 text-white shadow-[0_4px_12px_rgba(0,132,255,0.35)] hover:opacity-95 disabled:opacity-60"
|
|
307
|
+
style={{ backgroundColor: MESSENGER_BRAND_COLOR }}
|
|
308
|
+
>
|
|
309
|
+
{emptyAction.label}
|
|
310
|
+
</Button>
|
|
311
|
+
)}
|
|
312
|
+
</ConversationEmptyState>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<Conversation className="relative size-full">
|
|
319
|
+
<ConversationContent
|
|
320
|
+
scrollClassName={scrollClassName}
|
|
321
|
+
className={cn(contentClassName, "gap-1.5 px-3 py-3")}
|
|
322
|
+
>
|
|
323
|
+
{renderedMessages.map((item) => {
|
|
324
|
+
if (item.type === "confirmation") {
|
|
325
|
+
return (
|
|
326
|
+
<Message key={item.message.id} from="assistant">
|
|
327
|
+
<div className="flex w-fit max-w-[80%] shrink-0 flex-col gap-2">
|
|
328
|
+
<MessageResponse
|
|
329
|
+
confirmation={item.latestEnvelope}
|
|
330
|
+
confirmationPrompt={item.envelope.prompt}
|
|
331
|
+
confirmationActionsDisabled={status === "sending"}
|
|
332
|
+
onConfirmationDecision={onConfirmationDecision}
|
|
333
|
+
/>
|
|
334
|
+
</div>
|
|
335
|
+
</Message>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const m = item.message;
|
|
340
|
+
const isUser = m.role === "user";
|
|
341
|
+
return (
|
|
342
|
+
<Message key={m.id} from={m.role}>
|
|
343
|
+
<div className="flex w-fit max-w-[80%] shrink-0 flex-col gap-1.5">
|
|
344
|
+
<MessageContent
|
|
345
|
+
className={cn(
|
|
346
|
+
"whitespace-pre-wrap rounded-[18px] px-3.5 py-2 text-[14px] leading-snug",
|
|
347
|
+
isUser
|
|
348
|
+
? "text-white"
|
|
349
|
+
: "bg-secondary text-secondary-foreground",
|
|
350
|
+
)}
|
|
351
|
+
style={
|
|
352
|
+
isUser ? { background: MESSENGER_GRADIENT } : undefined
|
|
353
|
+
}
|
|
354
|
+
>
|
|
355
|
+
{isUser ? (
|
|
356
|
+
m.content
|
|
357
|
+
) : (
|
|
358
|
+
<MessageResponse onLinkClick={onLinkClick}>
|
|
359
|
+
{m.content}
|
|
360
|
+
</MessageResponse>
|
|
361
|
+
)}
|
|
362
|
+
</MessageContent>
|
|
363
|
+
{m.actions && m.actions.length > 0 && (
|
|
364
|
+
<div className="flex flex-wrap gap-2">
|
|
365
|
+
{m.actions.map((action, i) => (
|
|
366
|
+
<Button
|
|
367
|
+
key={i}
|
|
368
|
+
size="sm"
|
|
369
|
+
variant={action.variant ?? "outline"}
|
|
370
|
+
onClick={action.onClick}
|
|
371
|
+
className="rounded-full"
|
|
372
|
+
>
|
|
373
|
+
{action.label}
|
|
374
|
+
</Button>
|
|
375
|
+
))}
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
</div>
|
|
379
|
+
</Message>
|
|
380
|
+
);
|
|
381
|
+
})}
|
|
382
|
+
{isTyping && (
|
|
383
|
+
<motion.div
|
|
384
|
+
initial={{ opacity: 0, y: 6 }}
|
|
385
|
+
animate={{ opacity: 1, y: 0 }}
|
|
386
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
387
|
+
className="flex w-full justify-start"
|
|
388
|
+
>
|
|
389
|
+
<div className="flex max-w-[80%] items-center rounded-[18px] bg-secondary px-3.5 py-2 text-secondary-foreground">
|
|
390
|
+
<ChatWidgetTyping className="bg-transparent! p-0!" />
|
|
391
|
+
</div>
|
|
392
|
+
</motion.div>
|
|
393
|
+
)}
|
|
394
|
+
</ConversationContent>
|
|
395
|
+
<ConversationScrollButton />
|
|
396
|
+
</Conversation>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
222
400
|
function MessengerHeader({
|
|
223
401
|
agentName,
|
|
224
402
|
agentAvatarUrl,
|
|
@@ -237,7 +415,7 @@ function MessengerHeader({
|
|
|
237
415
|
return (
|
|
238
416
|
<div
|
|
239
417
|
className="flex shrink-0 items-center gap-3 px-4 py-3 text-white"
|
|
240
|
-
style={{
|
|
418
|
+
style={{ backgroundColor: MESSENGER_BRAND_COLOR }}
|
|
241
419
|
>
|
|
242
420
|
<div className="relative">
|
|
243
421
|
<Avatar size="lg" className="ring-2 ring-white/30">
|
|
@@ -287,11 +465,11 @@ function MessengerHeader({
|
|
|
287
465
|
)}
|
|
288
466
|
<button
|
|
289
467
|
type="button"
|
|
290
|
-
aria-label="
|
|
468
|
+
aria-label="Minimize"
|
|
291
469
|
onClick={onClose}
|
|
292
470
|
className="ml-0.5 flex size-8 items-center justify-center rounded-full text-white/90 transition-colors hover:bg-white/15 hover:text-white"
|
|
293
471
|
>
|
|
294
|
-
<
|
|
472
|
+
<ChevronDown className="size-5" strokeWidth={2.4} />
|
|
295
473
|
</button>
|
|
296
474
|
</div>
|
|
297
475
|
</div>
|
|
@@ -326,49 +504,47 @@ function MessengerComposer({
|
|
|
326
504
|
submit();
|
|
327
505
|
};
|
|
328
506
|
|
|
329
|
-
const
|
|
330
|
-
const
|
|
507
|
+
const hasText = value.trim().length > 0;
|
|
508
|
+
const sendDisabled = disabled || !onSubmit || !hasText;
|
|
331
509
|
|
|
332
510
|
return (
|
|
333
|
-
<div className="shrink-0 border-t border-
|
|
334
|
-
<div className="flex items-end gap-
|
|
335
|
-
|
|
511
|
+
<div className="shrink-0 border-t border-border bg-background px-2 py-2">
|
|
512
|
+
<div className="flex items-end gap-0.5">
|
|
513
|
+
{/* Tool icons (Plus / Camera / Image / Mic) temporarily disabled. */}
|
|
514
|
+
<div className="flex flex-1 items-end rounded-3xl bg-secondary px-3.5">
|
|
336
515
|
<Textarea
|
|
337
516
|
ref={textareaRef}
|
|
338
|
-
placeholder=
|
|
517
|
+
placeholder="Aa"
|
|
339
518
|
value={value}
|
|
340
519
|
onChange={(e) => onChange(e.target.value)}
|
|
341
520
|
onKeyDown={handleKeyDown}
|
|
342
521
|
rows={1}
|
|
343
522
|
disabled={disabled}
|
|
344
523
|
className={cn(
|
|
345
|
-
"min-h-9 max-h-32 flex-1 resize-none border-0 bg-transparent py-2 text-[14px] shadow-none",
|
|
524
|
+
"min-h-9 max-h-32 flex-1 resize-none border-0 bg-transparent py-2 text-[14px] leading-snug shadow-none",
|
|
346
525
|
"focus-visible:ring-0 focus-visible:border-0",
|
|
347
526
|
)}
|
|
348
527
|
/>
|
|
349
528
|
</div>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
<ThumbsUp className="size-5" strokeWidth={2.2} />
|
|
370
|
-
</button>
|
|
371
|
-
)}
|
|
529
|
+
<button
|
|
530
|
+
type="button"
|
|
531
|
+
aria-label="Send"
|
|
532
|
+
onClick={submit}
|
|
533
|
+
disabled={sendDisabled}
|
|
534
|
+
className={cn(
|
|
535
|
+
"ml-1 flex size-9 shrink-0 items-center justify-center rounded-full transition-colors",
|
|
536
|
+
"hover:bg-secondary disabled:hover:bg-transparent",
|
|
537
|
+
)}
|
|
538
|
+
style={{ color: MESSENGER_BRAND_COLOR }}
|
|
539
|
+
>
|
|
540
|
+
<SendHorizonal
|
|
541
|
+
className={cn(
|
|
542
|
+
"size-5 transition-opacity",
|
|
543
|
+
sendDisabled && "opacity-40",
|
|
544
|
+
)}
|
|
545
|
+
strokeWidth={2.2}
|
|
546
|
+
/>
|
|
547
|
+
</button>
|
|
372
548
|
</div>
|
|
373
549
|
</div>
|
|
374
550
|
);
|