@usereq/widget 0.1.1 → 0.1.3
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/README.md +7 -7
- package/dist/widget.js +379 -379
- package/package.json +1 -1
- package/src/chat-widget/index.ts +1 -0
- package/src/chat-widget/styles/chat-widget-messenger.tsx +396 -0
- package/src/custom-element/agent-widget-element.tsx +16 -0
- package/src/renderer/widget-runtime.tsx +15 -1
- package/src/shared/widget-config.ts +18 -0
- package/src/types.ts +5 -0
package/package.json
CHANGED
package/src/chat-widget/index.ts
CHANGED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
5
|
+
import { cn } from "@usereq/ui/lib/utils";
|
|
6
|
+
import {
|
|
7
|
+
Avatar,
|
|
8
|
+
AvatarFallback,
|
|
9
|
+
AvatarImage,
|
|
10
|
+
} from "@usereq/ui/components/avatar";
|
|
11
|
+
import { Button } from "@usereq/ui/components/button";
|
|
12
|
+
import { Textarea } from "@usereq/ui/components/textarea";
|
|
13
|
+
import { X, SendHorizonal, Phone, Video, Info, ThumbsUp } from "lucide-react";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_AGENT_NAME_BUBBLE,
|
|
16
|
+
DEFAULT_COMPOSER_PLACEHOLDER,
|
|
17
|
+
DEFAULT_POWERED_BY,
|
|
18
|
+
DEFAULT_SPOTLIGHT_MESSAGES,
|
|
19
|
+
} from "../chat-widget-defaults";
|
|
20
|
+
import {
|
|
21
|
+
ChatWidgetMessages,
|
|
22
|
+
ChatWidgetPanel,
|
|
23
|
+
ChatWidgetPoweredBy,
|
|
24
|
+
useIsChatWidgetFullscreenViewport,
|
|
25
|
+
useSpotlightFade,
|
|
26
|
+
type ChatWidgetDataProps,
|
|
27
|
+
} from "../components/chat-widget-primitives";
|
|
28
|
+
|
|
29
|
+
const MESSENGER_GRADIENT =
|
|
30
|
+
"linear-gradient(135deg, #00B2FF 0%, #006AFF 50%, #8E5BFF 100%)";
|
|
31
|
+
|
|
32
|
+
const PANEL_WIDTH = 372;
|
|
33
|
+
const PANEL_HEIGHT = 580;
|
|
34
|
+
const LAUNCHER_SIZE = 60;
|
|
35
|
+
const SPOTLIGHT_GAP = 10;
|
|
36
|
+
const PANEL_GAP = 14;
|
|
37
|
+
|
|
38
|
+
const fadeTransition = { duration: 0.35 };
|
|
39
|
+
|
|
40
|
+
export function ChatWidgetMessenger({
|
|
41
|
+
agentName = DEFAULT_AGENT_NAME_BUBBLE,
|
|
42
|
+
agentAvatarUrl,
|
|
43
|
+
appearance,
|
|
44
|
+
spotlightMessages = [],
|
|
45
|
+
messages = [],
|
|
46
|
+
poweredBy = DEFAULT_POWERED_BY,
|
|
47
|
+
headerStatus = "Active now",
|
|
48
|
+
errorMessage,
|
|
49
|
+
status = "idle",
|
|
50
|
+
sendDisabled = false,
|
|
51
|
+
onSendMessage,
|
|
52
|
+
isTyping,
|
|
53
|
+
onSuggestion,
|
|
54
|
+
onLauncherClick,
|
|
55
|
+
className,
|
|
56
|
+
open,
|
|
57
|
+
defaultOpen = false,
|
|
58
|
+
onOpenChange,
|
|
59
|
+
previewOnly = false,
|
|
60
|
+
onStopConversation,
|
|
61
|
+
stopDisabled,
|
|
62
|
+
onConfirmationDecision,
|
|
63
|
+
onLinkClick,
|
|
64
|
+
emptyAction,
|
|
65
|
+
portalContainer,
|
|
66
|
+
}: ChatWidgetDataProps & {
|
|
67
|
+
open?: boolean;
|
|
68
|
+
className?: string;
|
|
69
|
+
defaultOpen?: boolean;
|
|
70
|
+
onOpenChange?: (open: boolean) => void;
|
|
71
|
+
onLauncherClick?: () => void;
|
|
72
|
+
previewOnly?: boolean;
|
|
73
|
+
portalContainer?: React.ComponentProps<
|
|
74
|
+
typeof ChatWidgetPanel
|
|
75
|
+
>["portalContainer"];
|
|
76
|
+
}) {
|
|
77
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
|
|
78
|
+
previewOnly ? false : defaultOpen,
|
|
79
|
+
);
|
|
80
|
+
const [draftValue, setDraftValue] = React.useState("");
|
|
81
|
+
const spotlightForFade =
|
|
82
|
+
spotlightMessages.length > 0
|
|
83
|
+
? spotlightMessages
|
|
84
|
+
: [...DEFAULT_SPOTLIGHT_MESSAGES];
|
|
85
|
+
const spotlightText = useSpotlightFade(spotlightForFade, 3200);
|
|
86
|
+
const effectiveOpen = previewOnly ? false : (open ?? uncontrolledOpen);
|
|
87
|
+
const handleOpenChange = React.useCallback(
|
|
88
|
+
(nextOpen: boolean) => {
|
|
89
|
+
if (onOpenChange) {
|
|
90
|
+
onOpenChange(nextOpen);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setUncontrolledOpen(nextOpen);
|
|
94
|
+
},
|
|
95
|
+
[onOpenChange],
|
|
96
|
+
);
|
|
97
|
+
const isFullscreenViewport = useIsChatWidgetFullscreenViewport();
|
|
98
|
+
const hideLauncher = isFullscreenViewport && effectiveOpen;
|
|
99
|
+
|
|
100
|
+
const sendIsDisabled = sendDisabled || status === "booting";
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
className={cn("relative", className)}
|
|
105
|
+
style={{
|
|
106
|
+
width: spotlightForFade.length > 0 ? 280 : LAUNCHER_SIZE,
|
|
107
|
+
height:
|
|
108
|
+
spotlightForFade.length > 0
|
|
109
|
+
? LAUNCHER_SIZE + SPOTLIGHT_GAP + 56
|
|
110
|
+
: LAUNCHER_SIZE,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{spotlightForFade.length > 0 && !effectiveOpen && (
|
|
114
|
+
<AnimatePresence mode="wait">
|
|
115
|
+
<motion.div
|
|
116
|
+
key={spotlightText}
|
|
117
|
+
initial={{ opacity: 0, y: 6 }}
|
|
118
|
+
animate={{ opacity: 1, y: 0 }}
|
|
119
|
+
exit={{ opacity: 0, y: 6 }}
|
|
120
|
+
transition={fadeTransition}
|
|
121
|
+
className="absolute right-0 bottom-[calc(60px+10px)] flex w-full max-w-70 flex-col items-end"
|
|
122
|
+
>
|
|
123
|
+
<div className="rounded-3xl bg-white px-4 py-2.5 text-right shadow-[0_4px_16px_rgba(0,0,0,0.12)]">
|
|
124
|
+
<p className="text-[14px] leading-snug text-foreground">
|
|
125
|
+
{spotlightText}
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
</motion.div>
|
|
129
|
+
</AnimatePresence>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
{!previewOnly && (
|
|
133
|
+
<ChatWidgetPanel
|
|
134
|
+
open={effectiveOpen}
|
|
135
|
+
onOpenChange={handleOpenChange}
|
|
136
|
+
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-black/5"
|
|
138
|
+
desktopStyle={{
|
|
139
|
+
width: PANEL_WIDTH,
|
|
140
|
+
height: PANEL_HEIGHT,
|
|
141
|
+
bottom: LAUNCHER_SIZE + PANEL_GAP,
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<MessengerHeader
|
|
145
|
+
agentName={agentName}
|
|
146
|
+
agentAvatarUrl={agentAvatarUrl}
|
|
147
|
+
statusText={headerStatus}
|
|
148
|
+
onClose={() => handleOpenChange(false)}
|
|
149
|
+
onStopConversation={onStopConversation ?? undefined}
|
|
150
|
+
stopDisabled={stopDisabled}
|
|
151
|
+
/>
|
|
152
|
+
<div className="flex min-h-0 flex-1 flex-col bg-[#F5F5F7] dark:bg-background overflow-hidden">
|
|
153
|
+
<ChatWidgetMessages
|
|
154
|
+
messages={messages}
|
|
155
|
+
isTyping={isTyping ?? status === "sending"}
|
|
156
|
+
status={status}
|
|
157
|
+
suggestions={spotlightForFade}
|
|
158
|
+
errorMessage={errorMessage}
|
|
159
|
+
appearance={appearance}
|
|
160
|
+
onConfirmationDecision={onConfirmationDecision}
|
|
161
|
+
onLinkClick={onLinkClick}
|
|
162
|
+
onSuggestion={(value: string) => {
|
|
163
|
+
setDraftValue(value);
|
|
164
|
+
onSuggestion?.(value);
|
|
165
|
+
}}
|
|
166
|
+
emptyAction={emptyAction}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
{!(emptyAction && messages.length === 0 && !isTyping) && (
|
|
170
|
+
<MessengerComposer
|
|
171
|
+
value={draftValue}
|
|
172
|
+
onChange={setDraftValue}
|
|
173
|
+
onSubmit={onSendMessage}
|
|
174
|
+
disabled={sendIsDisabled}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
<ChatWidgetPoweredBy
|
|
178
|
+
text={poweredBy}
|
|
179
|
+
className="bg-background py-1.5"
|
|
180
|
+
/>
|
|
181
|
+
</ChatWidgetPanel>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{!hideLauncher && (
|
|
185
|
+
<motion.button
|
|
186
|
+
type="button"
|
|
187
|
+
onClick={
|
|
188
|
+
previewOnly
|
|
189
|
+
? undefined
|
|
190
|
+
: () => {
|
|
191
|
+
onLauncherClick?.();
|
|
192
|
+
handleOpenChange(!effectiveOpen);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
className={cn(
|
|
196
|
+
"absolute bottom-0 right-0 flex items-center justify-center rounded-full text-white",
|
|
197
|
+
previewOnly && "pointer-events-none",
|
|
198
|
+
)}
|
|
199
|
+
style={{
|
|
200
|
+
width: LAUNCHER_SIZE,
|
|
201
|
+
height: LAUNCHER_SIZE,
|
|
202
|
+
background: MESSENGER_GRADIENT,
|
|
203
|
+
boxShadow: "0 6px 20px rgba(0, 132, 255, 0.45)",
|
|
204
|
+
}}
|
|
205
|
+
aria-label={effectiveOpen ? "Close chat" : "Open chat"}
|
|
206
|
+
aria-expanded={effectiveOpen}
|
|
207
|
+
animate={{ scale: 1 }}
|
|
208
|
+
whileHover={previewOnly ? undefined : { scale: 1.04 }}
|
|
209
|
+
whileTap={previewOnly ? undefined : { scale: 0.96 }}
|
|
210
|
+
>
|
|
211
|
+
{effectiveOpen ? (
|
|
212
|
+
<X className="size-7" strokeWidth={2.4} />
|
|
213
|
+
) : (
|
|
214
|
+
<MessengerLogo className="size-8" />
|
|
215
|
+
)}
|
|
216
|
+
</motion.button>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function MessengerHeader({
|
|
223
|
+
agentName,
|
|
224
|
+
agentAvatarUrl,
|
|
225
|
+
statusText,
|
|
226
|
+
onClose,
|
|
227
|
+
onStopConversation,
|
|
228
|
+
stopDisabled,
|
|
229
|
+
}: {
|
|
230
|
+
agentName: string;
|
|
231
|
+
agentAvatarUrl?: string | null;
|
|
232
|
+
statusText: string;
|
|
233
|
+
onClose: () => void;
|
|
234
|
+
onStopConversation?: () => void;
|
|
235
|
+
stopDisabled?: boolean;
|
|
236
|
+
}) {
|
|
237
|
+
return (
|
|
238
|
+
<div
|
|
239
|
+
className="flex shrink-0 items-center gap-3 px-4 py-3 text-white"
|
|
240
|
+
style={{ background: MESSENGER_GRADIENT }}
|
|
241
|
+
>
|
|
242
|
+
<div className="relative">
|
|
243
|
+
<Avatar size="lg" className="ring-2 ring-white/30">
|
|
244
|
+
{agentAvatarUrl ? (
|
|
245
|
+
<AvatarImage src={agentAvatarUrl} alt={agentName} />
|
|
246
|
+
) : null}
|
|
247
|
+
<AvatarFallback className="bg-white/20 text-white">
|
|
248
|
+
{agentName.slice(0, 2).toUpperCase()}
|
|
249
|
+
</AvatarFallback>
|
|
250
|
+
</Avatar>
|
|
251
|
+
<span className="absolute -bottom-0.5 -right-0.5 size-3 rounded-full bg-[#31D778] ring-2 ring-white" />
|
|
252
|
+
</div>
|
|
253
|
+
<div className="min-w-0 flex-1">
|
|
254
|
+
<div className="truncate text-[15px] font-semibold leading-tight">
|
|
255
|
+
{agentName}
|
|
256
|
+
</div>
|
|
257
|
+
<div className="text-[12px] leading-tight text-white/80">
|
|
258
|
+
{statusText}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
aria-label="Phone"
|
|
265
|
+
className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white"
|
|
266
|
+
>
|
|
267
|
+
<Phone className="size-4.5" strokeWidth={2.2} />
|
|
268
|
+
</button>
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
aria-label="Video"
|
|
272
|
+
className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white"
|
|
273
|
+
>
|
|
274
|
+
<Video className="size-4.5" strokeWidth={2.2} />
|
|
275
|
+
</button>
|
|
276
|
+
{onStopConversation != null && (
|
|
277
|
+
<button
|
|
278
|
+
type="button"
|
|
279
|
+
aria-label="Conversation info"
|
|
280
|
+
title="End conversation"
|
|
281
|
+
disabled={stopDisabled}
|
|
282
|
+
onClick={onStopConversation}
|
|
283
|
+
className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white disabled:opacity-50"
|
|
284
|
+
>
|
|
285
|
+
<Info className="size-4.5" strokeWidth={2.2} />
|
|
286
|
+
</button>
|
|
287
|
+
)}
|
|
288
|
+
<button
|
|
289
|
+
type="button"
|
|
290
|
+
aria-label="Close"
|
|
291
|
+
onClick={onClose}
|
|
292
|
+
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
|
+
>
|
|
294
|
+
<X className="size-4.5" strokeWidth={2.4} />
|
|
295
|
+
</button>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function MessengerComposer({
|
|
302
|
+
value,
|
|
303
|
+
onChange,
|
|
304
|
+
onSubmit,
|
|
305
|
+
disabled,
|
|
306
|
+
}: {
|
|
307
|
+
value: string;
|
|
308
|
+
onChange: (value: string) => void;
|
|
309
|
+
onSubmit?: (value: string) => void;
|
|
310
|
+
disabled?: boolean;
|
|
311
|
+
}) {
|
|
312
|
+
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
|
313
|
+
|
|
314
|
+
const submit = () => {
|
|
315
|
+
const trimmed = value.trim();
|
|
316
|
+
if (!trimmed || !onSubmit) return;
|
|
317
|
+
onSubmit(trimmed);
|
|
318
|
+
onChange("");
|
|
319
|
+
textareaRef.current?.focus();
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
323
|
+
if (e.key !== "Enter") return;
|
|
324
|
+
if (e.shiftKey) return;
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
submit();
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const trimmedValue = value.trim();
|
|
330
|
+
const showSend = trimmedValue.length > 0;
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<div className="shrink-0 border-t border-black/5 bg-background px-3 py-2.5">
|
|
334
|
+
<div className="flex items-end gap-2">
|
|
335
|
+
<div className="flex flex-1 items-end rounded-3xl bg-[#F0F0F2] px-3 dark:bg-muted">
|
|
336
|
+
<Textarea
|
|
337
|
+
ref={textareaRef}
|
|
338
|
+
placeholder={DEFAULT_COMPOSER_PLACEHOLDER}
|
|
339
|
+
value={value}
|
|
340
|
+
onChange={(e) => onChange(e.target.value)}
|
|
341
|
+
onKeyDown={handleKeyDown}
|
|
342
|
+
rows={1}
|
|
343
|
+
disabled={disabled}
|
|
344
|
+
className={cn(
|
|
345
|
+
"min-h-9 max-h-32 flex-1 resize-none border-0 bg-transparent py-2 text-[14px] shadow-none",
|
|
346
|
+
"focus-visible:ring-0 focus-visible:border-0",
|
|
347
|
+
)}
|
|
348
|
+
/>
|
|
349
|
+
</div>
|
|
350
|
+
{showSend ? (
|
|
351
|
+
<Button
|
|
352
|
+
type="button"
|
|
353
|
+
size="icon"
|
|
354
|
+
aria-label="Send"
|
|
355
|
+
onClick={submit}
|
|
356
|
+
disabled={disabled || !onSubmit}
|
|
357
|
+
className="h-9 w-9 shrink-0 rounded-full text-white hover:opacity-90 disabled:opacity-60"
|
|
358
|
+
style={{ background: MESSENGER_GRADIENT }}
|
|
359
|
+
>
|
|
360
|
+
<SendHorizonal className="size-4" />
|
|
361
|
+
</Button>
|
|
362
|
+
) : (
|
|
363
|
+
<button
|
|
364
|
+
type="button"
|
|
365
|
+
aria-label="Like"
|
|
366
|
+
disabled
|
|
367
|
+
className="flex size-9 shrink-0 items-center justify-center rounded-full text-[#0084FF] disabled:opacity-100"
|
|
368
|
+
>
|
|
369
|
+
<ThumbsUp className="size-5" strokeWidth={2.2} />
|
|
370
|
+
</button>
|
|
371
|
+
)}
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function MessengerLogo({ className }: { className?: string }) {
|
|
378
|
+
return (
|
|
379
|
+
<svg
|
|
380
|
+
viewBox="0 0 24 24"
|
|
381
|
+
fill="currentColor"
|
|
382
|
+
aria-hidden="true"
|
|
383
|
+
className={className}
|
|
384
|
+
>
|
|
385
|
+
<path d="M12 2C6.48 2 2 6.18 2 11.32c0 2.93 1.45 5.55 3.7 7.27v3.4l3.39-1.86a10.5 10.5 0 0 0 2.91.41c5.52 0 10-4.18 10-9.22S17.52 2 12 2Zm1.06 12.42-2.55-2.72-4.97 2.72 5.46-5.8 2.61 2.72 4.91-2.72-5.46 5.8Z" />
|
|
386
|
+
</svg>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export const MESSENGER_PRESET_GRADIENT = MESSENGER_GRADIENT;
|
|
391
|
+
|
|
392
|
+
export const MESSENGER_PRESET_APPEARANCE = {
|
|
393
|
+
avatarOrbColor1: "#00B2FF",
|
|
394
|
+
avatarOrbColor2: "#006AFF",
|
|
395
|
+
actionText: "Chat with us",
|
|
396
|
+
} as const;
|
|
@@ -3,11 +3,14 @@ import { createRoot, type Root } from "react-dom/client";
|
|
|
3
3
|
import { DEFAULT_START_CONVERSATION_LABEL } from "../chat-widget/chat-widget-defaults";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_WIDGET_APPEARANCE,
|
|
6
|
+
DEFAULT_WIDGET_PRESET,
|
|
6
7
|
normalizeWidgetPlacement,
|
|
8
|
+
normalizeWidgetPreset,
|
|
7
9
|
normalizeWidgetVariant,
|
|
8
10
|
type WidgetAppearance,
|
|
9
11
|
type WidgetMessage,
|
|
10
12
|
type WidgetPlacement,
|
|
13
|
+
type WidgetPreset,
|
|
11
14
|
type WidgetSessionConfig,
|
|
12
15
|
type WidgetSessionState,
|
|
13
16
|
type WidgetVariant,
|
|
@@ -38,6 +41,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
38
41
|
"agent-id",
|
|
39
42
|
"mode",
|
|
40
43
|
"variant",
|
|
44
|
+
"preset",
|
|
41
45
|
"placement",
|
|
42
46
|
"avatar-orb-color-1",
|
|
43
47
|
"avatar-orb-color-2",
|
|
@@ -52,6 +56,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
52
56
|
private agentId = "";
|
|
53
57
|
private mode: "embed" | "preview" = "embed";
|
|
54
58
|
private variant: string | null = null;
|
|
59
|
+
private preset: string | null = null;
|
|
55
60
|
private placement: string | null = null;
|
|
56
61
|
private appearanceAttributes: WidgetAppearanceAttributes = {};
|
|
57
62
|
private open = false;
|
|
@@ -139,6 +144,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
139
144
|
this.agentId = this.getAttribute("agent-id")?.trim() ?? "";
|
|
140
145
|
this.mode = normalizeWidgetMode(this.getAttribute("mode"));
|
|
141
146
|
this.variant = this.getAttribute("variant");
|
|
147
|
+
this.preset = this.getAttribute("preset");
|
|
142
148
|
this.placement = this.getAttribute("placement");
|
|
143
149
|
this.appearanceAttributes = parseAppearanceAttributes(this);
|
|
144
150
|
}
|
|
@@ -155,6 +161,14 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
155
161
|
);
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
private getResolvedPreset(): WidgetPreset {
|
|
165
|
+
return normalizeWidgetPreset(
|
|
166
|
+
this.preset ??
|
|
167
|
+
this.sessionConfig?.widgetConfig.widgetPreset ??
|
|
168
|
+
DEFAULT_WIDGET_PRESET,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
158
172
|
private getResolvedAppearance(): WidgetAppearance {
|
|
159
173
|
const base =
|
|
160
174
|
this.sessionConfig?.widgetConfig.widgetAppearance ?? DEFAULT_WIDGET_APPEARANCE;
|
|
@@ -231,6 +245,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
231
245
|
const appearance = this.getResolvedAppearance();
|
|
232
246
|
const placement = this.getResolvedPlacement();
|
|
233
247
|
const variant = this.getResolvedVariant();
|
|
248
|
+
const preset = this.getResolvedPreset();
|
|
234
249
|
const emptyAction =
|
|
235
250
|
this.status === "booting" ||
|
|
236
251
|
(this.sessionState != null && !this.sessionState.conversationId)
|
|
@@ -249,6 +264,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
249
264
|
mode: this.mode,
|
|
250
265
|
agentId: this.agentId,
|
|
251
266
|
variant,
|
|
267
|
+
preset,
|
|
252
268
|
placement,
|
|
253
269
|
appearance,
|
|
254
270
|
agentAvatarUrl: this.getAgentAvatarUrl(),
|
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
ChatWidgetBubble,
|
|
4
4
|
ChatWidgetBox,
|
|
5
5
|
ChatWidgetChatBar,
|
|
6
|
+
ChatWidgetMessenger,
|
|
6
7
|
} from "../chat-widget";
|
|
7
8
|
import type { WidgetMessage } from "../types";
|
|
8
9
|
import {
|
|
9
10
|
splitWidgetPlacement,
|
|
10
11
|
type WidgetAppearance,
|
|
11
12
|
type WidgetPlacement,
|
|
13
|
+
type WidgetPreset,
|
|
12
14
|
type WidgetVariant,
|
|
13
15
|
} from "../shared/widget-config";
|
|
14
16
|
import type { WidgetTriggerRule } from "../shared/widget-config";
|
|
@@ -25,6 +27,7 @@ export type WidgetRuntimeProps = {
|
|
|
25
27
|
mode: WidgetRuntimeMode;
|
|
26
28
|
agentId: string;
|
|
27
29
|
variant: WidgetVariant;
|
|
30
|
+
preset?: WidgetPreset;
|
|
28
31
|
placement: WidgetPlacement;
|
|
29
32
|
appearance: WidgetAppearance;
|
|
30
33
|
agentAvatarUrl: string | null;
|
|
@@ -57,10 +60,15 @@ const VARIANT_COMPONENTS = {
|
|
|
57
60
|
chatbar: ChatWidgetChatBar,
|
|
58
61
|
} as const;
|
|
59
62
|
|
|
63
|
+
const PRESET_COMPONENTS = {
|
|
64
|
+
facebook_messenger: ChatWidgetMessenger,
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
60
67
|
export function WidgetRuntime({
|
|
61
68
|
mode,
|
|
62
69
|
agentId,
|
|
63
70
|
variant,
|
|
71
|
+
preset = "default",
|
|
64
72
|
placement,
|
|
65
73
|
appearance,
|
|
66
74
|
agentAvatarUrl,
|
|
@@ -84,7 +92,12 @@ export function WidgetRuntime({
|
|
|
84
92
|
}: WidgetRuntimeProps) {
|
|
85
93
|
const { ready } = useWidgetTriggerGate(triggerRule);
|
|
86
94
|
const { horizontal, vertical } = splitWidgetPlacement(placement);
|
|
87
|
-
const
|
|
95
|
+
const PresetComponent =
|
|
96
|
+
preset !== "default"
|
|
97
|
+
? PRESET_COMPONENTS[preset as Exclude<WidgetPreset, "default">]
|
|
98
|
+
: undefined;
|
|
99
|
+
const Component =
|
|
100
|
+
PresetComponent ?? VARIANT_COMPONENTS[variant] ?? ChatWidgetBubble;
|
|
88
101
|
const shellStyle = getWidgetShellStyle(mode, placement);
|
|
89
102
|
const resolvedAvatarUrl = agentAvatarUrl?.trim() || undefined;
|
|
90
103
|
const pendingConfirmationId = getPendingStopConfirmationId(messages);
|
|
@@ -115,6 +128,7 @@ export function WidgetRuntime({
|
|
|
115
128
|
data-horizontal={horizontal}
|
|
116
129
|
data-vertical={vertical}
|
|
117
130
|
data-variant={variant}
|
|
131
|
+
data-preset={preset}
|
|
118
132
|
data-open={String(open)}
|
|
119
133
|
data-status={status}
|
|
120
134
|
style={shellStyle}
|
|
@@ -14,6 +14,10 @@ export type WidgetPlacement =
|
|
|
14
14
|
|
|
15
15
|
export type WidgetVariant = "default" | "chatbar" | "box";
|
|
16
16
|
|
|
17
|
+
export type WidgetPreset = "default" | "facebook_messenger";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_WIDGET_PRESET: WidgetPreset = "default";
|
|
20
|
+
|
|
17
21
|
export type WidgetAppearance = ResolvedChatWidgetAppearance;
|
|
18
22
|
|
|
19
23
|
export const DEFAULT_WIDGET_APPEARANCE: WidgetAppearance =
|
|
@@ -64,6 +68,20 @@ export function widgetAppearanceToAttributes(
|
|
|
64
68
|
];
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
export function normalizeWidgetPreset(
|
|
72
|
+
value: string | null | undefined,
|
|
73
|
+
): WidgetPreset {
|
|
74
|
+
switch (value) {
|
|
75
|
+
case "facebook_messenger":
|
|
76
|
+
case "facebook-messenger":
|
|
77
|
+
return "facebook_messenger";
|
|
78
|
+
case "default":
|
|
79
|
+
return "default";
|
|
80
|
+
default:
|
|
81
|
+
return DEFAULT_WIDGET_PRESET;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
export function normalizeWidgetVariant(
|
|
68
86
|
value: string | null | undefined,
|
|
69
87
|
): WidgetVariant {
|
package/src/types.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
WidgetAppearance,
|
|
3
3
|
WidgetPlacement,
|
|
4
|
+
WidgetPreset,
|
|
4
5
|
WidgetVariant,
|
|
5
6
|
WidgetTriggerRule,
|
|
6
7
|
} from "./shared/widget-config";
|
|
7
8
|
|
|
8
9
|
export {
|
|
9
10
|
DEFAULT_WIDGET_APPEARANCE,
|
|
11
|
+
DEFAULT_WIDGET_PRESET,
|
|
10
12
|
type WidgetAppearance,
|
|
11
13
|
type WidgetPlacement,
|
|
14
|
+
type WidgetPreset,
|
|
12
15
|
type WidgetVariant,
|
|
13
16
|
normalizeWidgetAppearance,
|
|
14
17
|
normalizeWidgetPlacement,
|
|
18
|
+
normalizeWidgetPreset,
|
|
15
19
|
normalizeWidgetVariant,
|
|
16
20
|
splitWidgetPlacement,
|
|
17
21
|
widgetAppearanceToAttributes,
|
|
@@ -37,6 +41,7 @@ export type WidgetSessionConfig = {
|
|
|
37
41
|
spotlightMessages: string[];
|
|
38
42
|
widgetAppearance: WidgetAppearance;
|
|
39
43
|
widgetVariant: WidgetVariant;
|
|
44
|
+
widgetPreset: WidgetPreset;
|
|
40
45
|
widgetPlacement: WidgetPlacement;
|
|
41
46
|
allowedDomains: string[];
|
|
42
47
|
triggerRule: WidgetTriggerRule | null;
|