@usereq/widget 0.2.8 → 0.2.10
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 +110 -3530
- package/package.json +1 -1
- package/src/chat-widget/chat-widget-defaults.tsx +1 -40
- package/src/chat-widget/message-reveal.ts +52 -0
- package/src/custom-element/agent-widget-element.tsx +18 -2
- package/src/styles/widget.css.ts +39 -10
- package/dist/widget.css +0 -2
- package/src/chat-widget/components/chat-widget-frame.tsx +0 -56
- package/src/chat-widget/components/chat-widget-layout.ts +0 -6
- package/src/chat-widget/components/chat-widget-primitives.tsx +0 -720
- package/src/chat-widget/components/confirmation.tsx +0 -155
- package/src/chat-widget/components/conversation.tsx +0 -110
- package/src/chat-widget/components/message.tsx +0 -325
- package/src/chat-widget/index.ts +0 -19
- package/src/chat-widget/styles/chat-widget-box.tsx +0 -258
- package/src/chat-widget/styles/chat-widget-bubble.tsx +0 -238
- package/src/chat-widget/styles/chat-widget-chatbar.tsx +0 -248
- package/src/chat-widget/styles/chat-widget-messenger.tsx +0 -573
package/package.json
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default values for the chat widget when database / agent config is not set.
|
|
3
|
-
* Central place to manage all static default states.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
/** Default
|
|
7
|
-
export const DEFAULT_SPOTLIGHT_MESSAGES = [
|
|
8
|
-
"Ask me anything…",
|
|
9
|
-
"How can I help you today?",
|
|
10
|
-
"Try: What can you do?",
|
|
11
|
-
] as const;
|
|
12
|
-
|
|
13
|
-
/** Default first message from the agent when welcome copy is shown in the open widget. */
|
|
14
|
-
export const DEFAULT_WELCOME_MESSAGE = "Hello! How can I help you today?";
|
|
15
|
-
|
|
16
|
-
/** Default agent name when not set (e.g. ChatWidgetBox closed state). */
|
|
17
|
-
export const DEFAULT_AGENT_NAME_BOX = "Need help?";
|
|
18
|
-
|
|
19
|
-
/** Default agent name for Bubble launcher. */
|
|
20
|
-
export const DEFAULT_AGENT_NAME_BUBBLE = "Assistant";
|
|
21
|
-
|
|
22
|
-
/** Default agent name for ChatBar. */
|
|
23
|
-
export const DEFAULT_AGENT_NAME_CHATBAR = "Mana";
|
|
24
|
-
|
|
25
|
-
/** Default label for "Start chat" button (Box closed card). */
|
|
26
|
-
export const DEFAULT_START_CHAT_LABEL = "Start chat";
|
|
27
|
-
|
|
28
|
-
/** Default label for the open empty-state CTA. */
|
|
5
|
+
/** Default label for the open empty-state "start conversation" CTA. */
|
|
29
6
|
export const DEFAULT_START_CONVERSATION_LABEL = "Start a new conversation";
|
|
30
|
-
|
|
31
|
-
/** Default label on the ChatBar pill when closed. */
|
|
32
|
-
export const DEFAULT_LAUNCHER_LABEL = "Ask";
|
|
33
|
-
|
|
34
|
-
/** Default composer input placeholder. */
|
|
35
|
-
export const DEFAULT_COMPOSER_PLACEHOLDER = "Ask anything…";
|
|
36
|
-
|
|
37
|
-
/** Default "Powered by" text in the widget footer. */
|
|
38
|
-
export const DEFAULT_POWERED_BY = "Powered by UserEQ";
|
|
39
|
-
|
|
40
|
-
/** Default empty state title when there are no messages. */
|
|
41
|
-
export const DEFAULT_EMPTY_TITLE = "No conversation started";
|
|
42
|
-
|
|
43
|
-
/** Default empty state description. */
|
|
44
|
-
export const DEFAULT_EMPTY_DESCRIPTION =
|
|
45
|
-
"Start a new conversation to get started.";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staggered reveal timing for assistant message turns. Pure helper (no React)
|
|
3
|
+
* used by the custom element to drip assistant replies in one-by-one with a
|
|
4
|
+
* brief "typing" hold between them.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type AssistantMessageRevealSource = {
|
|
8
|
+
id: string;
|
|
9
|
+
content: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type AssistantMessageRevealStep<
|
|
13
|
+
T extends AssistantMessageRevealSource = AssistantMessageRevealSource,
|
|
14
|
+
> = {
|
|
15
|
+
message: T;
|
|
16
|
+
delayMs: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function buildAssistantMessageRevealSteps<
|
|
20
|
+
T extends AssistantMessageRevealSource,
|
|
21
|
+
>(
|
|
22
|
+
messages: T[],
|
|
23
|
+
options?: {
|
|
24
|
+
initialDelayMs?: number;
|
|
25
|
+
typingHoldMs?: number;
|
|
26
|
+
interMessageDelayMs?: number;
|
|
27
|
+
interMessageDelayJitterMs?: number;
|
|
28
|
+
},
|
|
29
|
+
): AssistantMessageRevealStep<T>[] {
|
|
30
|
+
if (messages.length === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const initialDelayMs = options?.initialDelayMs ?? 0;
|
|
35
|
+
const typingHoldMs = options?.typingHoldMs ?? 1260;
|
|
36
|
+
const interMessageDelayMs = options?.interMessageDelayMs ?? 1520;
|
|
37
|
+
const jitterMs = Math.max(0, options?.interMessageDelayJitterMs ?? 100);
|
|
38
|
+
|
|
39
|
+
return messages.map((message, index) => {
|
|
40
|
+
const baseDelay =
|
|
41
|
+
index === 0 ? initialDelayMs + typingHoldMs : interMessageDelayMs;
|
|
42
|
+
const jitter =
|
|
43
|
+
index === 0 || jitterMs === 0
|
|
44
|
+
? 0
|
|
45
|
+
: Math.round((Math.random() * 2 - 1) * jitterMs);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
message,
|
|
49
|
+
delayMs: Math.max(0, baseDelay + jitter),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "../types";
|
|
18
18
|
import { widgetTriggerRuleKey } from "../shared/widget-config";
|
|
19
19
|
import { WidgetRuntime, type WidgetRuntimeStatus } from "../renderer/index";
|
|
20
|
-
import { buildAssistantMessageRevealSteps } from "../chat-widget";
|
|
20
|
+
import { buildAssistantMessageRevealSteps } from "../chat-widget/message-reveal";
|
|
21
21
|
import { DEFAULT_STOP_CONVERSATION_PROMPT } from "../shared/stop-confirmation";
|
|
22
22
|
import {
|
|
23
23
|
bootstrapWidget,
|
|
@@ -47,6 +47,15 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
47
47
|
private hostStyleElement: HTMLStyleElement;
|
|
48
48
|
private shadowStyleElement: HTMLStyleElement;
|
|
49
49
|
private mountElement: HTMLDivElement;
|
|
50
|
+
/**
|
|
51
|
+
* Dedicated container the fullscreen (mobile) chat panel portals into. It
|
|
52
|
+
* lives INSIDE the shadow root (so our styles apply) and is `fixed; inset:0`
|
|
53
|
+
* so the panel's `absolute inset-0` fills the viewport. Passing the raw
|
|
54
|
+
* shadow root instead would (a) be rejected as a non-Element portal target
|
|
55
|
+
* and (b) fall back to `document.body`, where our shadow-scoped CSS doesn't
|
|
56
|
+
* reach — which left the panel invisible on real sites.
|
|
57
|
+
*/
|
|
58
|
+
private portalElement: HTMLDivElement;
|
|
50
59
|
private root: Root;
|
|
51
60
|
private agentId = "";
|
|
52
61
|
private mode: "embed" | "preview" = "embed";
|
|
@@ -93,10 +102,17 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
93
102
|
this.mountElement.style.display = "block";
|
|
94
103
|
this.mountElement.style.width = "100%";
|
|
95
104
|
this.mountElement.style.height = "100%";
|
|
105
|
+
// Fullscreen portal target — viewport-sized + in the shadow root. Empty and
|
|
106
|
+
// click-through until the mobile panel mounts into it (the panel/overlay
|
|
107
|
+
// re-enable their own pointer events).
|
|
108
|
+
this.portalElement = document.createElement("div");
|
|
109
|
+
this.portalElement.style.cssText =
|
|
110
|
+
"position:fixed;inset:0;pointer-events:none;z-index:2147483647;";
|
|
96
111
|
this.shadowRootRef.append(
|
|
97
112
|
this.hostStyleElement,
|
|
98
113
|
this.shadowStyleElement,
|
|
99
114
|
this.mountElement,
|
|
115
|
+
this.portalElement,
|
|
100
116
|
);
|
|
101
117
|
this.root = createRoot(this.mountElement);
|
|
102
118
|
}
|
|
@@ -282,7 +298,7 @@ export class AgentWidgetElement extends HTMLElement {
|
|
|
282
298
|
onSendMessage: (content: string) => void this.sendMessage(content),
|
|
283
299
|
onConfirmationDecision: (confirmationId: string, accepted: boolean) =>
|
|
284
300
|
void this.handleConfirmationDecision(confirmationId, accepted),
|
|
285
|
-
portalContainer: this.
|
|
301
|
+
portalContainer: this.portalElement,
|
|
286
302
|
}),
|
|
287
303
|
);
|
|
288
304
|
}
|
package/src/styles/widget.css.ts
CHANGED
|
@@ -5,23 +5,52 @@ export const widgetCss = `
|
|
|
5
5
|
pointer-events: none;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/* Hide the scrollbar in the chat-input textarea (it still scrolls past max height). */
|
|
9
|
+
[data-slot="chat-composer"] [data-slot="textarea"] {
|
|
10
|
+
scrollbar-width: none;
|
|
11
|
+
-ms-overflow-style: none;
|
|
12
|
+
}
|
|
13
|
+
[data-slot="chat-composer"] [data-slot="textarea"]::-webkit-scrollbar {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
@media (max-width: 520px) {
|
|
18
|
+
/*
|
|
19
|
+
* Tighten the launcher margin on small screens. Anchor to the side the
|
|
20
|
+
* placement asks for (via data-horizontal) — setting BOTH left and right
|
|
21
|
+
* would stretch the fixed shell full-width and the inline-flex launcher
|
|
22
|
+
* would collapse to the left edge.
|
|
23
|
+
*/
|
|
9
24
|
.widget-shell[data-mode="embed"] {
|
|
10
25
|
top: auto !important;
|
|
11
|
-
right: 12px !important;
|
|
12
26
|
bottom: 12px !important;
|
|
13
|
-
left: 12px !important;
|
|
14
27
|
transform: none !important;
|
|
15
28
|
}
|
|
16
29
|
|
|
17
|
-
.widget-shell[data-mode="embed"][data-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
.widget-shell[data-mode="embed"][data-horizontal="right"] {
|
|
31
|
+
right: 12px !important;
|
|
32
|
+
left: auto !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.widget-shell[data-mode="embed"][data-horizontal="left"] {
|
|
36
|
+
left: 12px !important;
|
|
37
|
+
right: auto !important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.widget-shell[data-mode="embed"][data-horizontal="center"] {
|
|
41
|
+
left: 50% !important;
|
|
42
|
+
right: auto !important;
|
|
43
|
+
transform: translateX(-50%) !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* The panel is full-screen on mobile, so drop the rounded corners / border /
|
|
48
|
+
* shadow that only make sense for the floating desktop panel.
|
|
49
|
+
*/
|
|
50
|
+
[data-slot="chat-panel"] {
|
|
51
|
+
border-radius: 0 !important;
|
|
52
|
+
border: 0 !important;
|
|
53
|
+
box-shadow: none !important;
|
|
25
54
|
}
|
|
26
55
|
}
|
|
27
56
|
`;
|
package/dist/widget.css
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
@keyframes sd-fadeIn{0%{opacity:0}to{opacity:1}}@keyframes sd-blurIn{0%{opacity:0;filter:blur(4px)}to{opacity:1;filter:blur()}}@keyframes sd-slideUp{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}[data-sd-animate]{animation:var(--sd-animation,sd-fadeIn) var(--sd-duration,.15s) var(--sd-easing,ease) var(--sd-delay,0s) both}
|
|
2
|
-
/*$vite$:1*/
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { cn } from "@usereq/ui/lib/utils";
|
|
5
|
-
|
|
6
|
-
/** Widget position anchor (mirrors server enum values). */
|
|
7
|
-
export type WidgetAnchor =
|
|
8
|
-
| "TOP_LEFT"
|
|
9
|
-
| "TOP_CENTER"
|
|
10
|
-
| "TOP_RIGHT"
|
|
11
|
-
| "BOTTOM_LEFT"
|
|
12
|
-
| "BOTTOM_CENTER"
|
|
13
|
-
| "BOTTOM_RIGHT";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Builds Tailwind classes for the widget anchor from DB WidgetAnchor.
|
|
17
|
-
* Used so the widget renders in the correct position (user-configurable in playground).
|
|
18
|
-
* Side anchors use min-w-[320px] so chatbar doesn't shrink when on left/right.
|
|
19
|
-
*/
|
|
20
|
-
const WIDGET_ANCHOR_CLASSES: Record<WidgetAnchor, string> = {
|
|
21
|
-
TOP_LEFT: "absolute top-8 left-8 min-w-[320px]",
|
|
22
|
-
TOP_CENTER:
|
|
23
|
-
"absolute top-8 left-1/2 w-full -translate-x-1/2 px-6 flex justify-center",
|
|
24
|
-
TOP_RIGHT: "absolute top-8 right-8 min-w-[320px]",
|
|
25
|
-
BOTTOM_LEFT: "absolute bottom-8 left-8 min-w-[320px]",
|
|
26
|
-
BOTTOM_CENTER:
|
|
27
|
-
"absolute bottom-10 left-1/2 w-full -translate-x-1/2 px-6 flex justify-center",
|
|
28
|
-
BOTTOM_RIGHT: "absolute bottom-8 right-8 min-w-[320px]",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export function ChatWidgetPreviewStage({
|
|
32
|
-
children,
|
|
33
|
-
className,
|
|
34
|
-
anchor = "BOTTOM_RIGHT",
|
|
35
|
-
}: {
|
|
36
|
-
children: React.ReactNode;
|
|
37
|
-
className?: string;
|
|
38
|
-
/** Widget position from DB (agent.anchor). User can update in playground. */
|
|
39
|
-
anchor?: WidgetAnchor;
|
|
40
|
-
}) {
|
|
41
|
-
const anchorClasses =
|
|
42
|
-
WIDGET_ANCHOR_CLASSES[anchor] ?? WIDGET_ANCHOR_CLASSES.BOTTOM_RIGHT;
|
|
43
|
-
return (
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
"relative flex min-h-[620px] w-full items-center justify-center overflow-hidden rounded-2xl border bg-linear-to-b from-muted/20 to-muted/5 p-6",
|
|
47
|
-
className
|
|
48
|
-
)}
|
|
49
|
-
>
|
|
50
|
-
<div data-chat-widget-anchor={anchor} className={anchorClasses}>
|
|
51
|
-
{children}
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|