@usereq/widget 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/README.md +166 -0
- package/dist/widget.css +2 -0
- package/dist/widget.js +3569 -0
- package/package.json +98 -0
- package/src/chat-widget/chat-widget-appearance.ts +55 -0
- package/src/chat-widget/chat-widget-defaults.tsx +45 -0
- package/src/chat-widget/components/chat-widget-frame.tsx +56 -0
- package/src/chat-widget/components/chat-widget-layout.ts +6 -0
- package/src/chat-widget/components/chat-widget-primitives.tsx +720 -0
- package/src/chat-widget/components/confirmation.tsx +155 -0
- package/src/chat-widget/components/conversation.tsx +110 -0
- package/src/chat-widget/components/message.tsx +325 -0
- package/src/chat-widget/index.ts +9 -0
- package/src/chat-widget/stop-confirmation.ts +288 -0
- package/src/chat-widget/styles/chat-widget-box.tsx +258 -0
- package/src/chat-widget/styles/chat-widget-bubble.tsx +238 -0
- package/src/chat-widget/styles/chat-widget-chatbar.tsx +248 -0
- package/src/custom-element/agent-widget-element.tsx +584 -0
- package/src/embed.ts +8 -0
- package/src/index.ts +10 -0
- package/src/register.ts +15 -0
- package/src/renderer/index.ts +6 -0
- package/src/renderer/widget-runtime.tsx +205 -0
- package/src/runtime/analytics.ts +327 -0
- package/src/runtime/api-origin.ts +29 -0
- package/src/runtime/api.ts +151 -0
- package/src/runtime/bootstrap.ts +309 -0
- package/src/runtime/messages.ts +12 -0
- package/src/runtime/session-storage.ts +32 -0
- package/src/runtime/token-renewal.ts +13 -0
- package/src/runtime/trigger-rule.ts +182 -0
- package/src/shared/analytics.ts +54 -0
- package/src/shared/shadow-css.ts +15 -0
- package/src/shared/shadow-theme.ts +85 -0
- package/src/shared/stop-confirmation.ts +20 -0
- package/src/shared/widget-config.ts +104 -0
- package/src/styles/widget.css.ts +27 -0
- package/src/types.ts +85 -0
- package/src/vite-env.d.ts +4 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
import { DEFAULT_START_CONVERSATION_LABEL } from "../chat-widget/chat-widget-defaults";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_WIDGET_APPEARANCE,
|
|
6
|
+
normalizeWidgetPlacement,
|
|
7
|
+
normalizeWidgetVariant,
|
|
8
|
+
type WidgetAppearance,
|
|
9
|
+
type WidgetMessage,
|
|
10
|
+
type WidgetPlacement,
|
|
11
|
+
type WidgetSessionConfig,
|
|
12
|
+
type WidgetSessionState,
|
|
13
|
+
type WidgetVariant,
|
|
14
|
+
} from "../types";
|
|
15
|
+
import { widgetTriggerRuleKey } from "../shared/widget-config";
|
|
16
|
+
import { WidgetRuntime, type WidgetRuntimeStatus } from "../renderer/index";
|
|
17
|
+
import { buildAssistantMessageRevealSteps } from "../chat-widget";
|
|
18
|
+
import { DEFAULT_STOP_CONVERSATION_PROMPT } from "../shared/stop-confirmation";
|
|
19
|
+
import type { WidgetTriggerRuleType } from "../shared/analytics";
|
|
20
|
+
import {
|
|
21
|
+
bootstrapWidget,
|
|
22
|
+
resetWidgetSession,
|
|
23
|
+
submitWidgetStopConfirmation,
|
|
24
|
+
startWidgetConversation,
|
|
25
|
+
submitWidgetMessage,
|
|
26
|
+
} from "../runtime/bootstrap";
|
|
27
|
+
import {
|
|
28
|
+
createWidgetAnalyticsTracker,
|
|
29
|
+
type WidgetAnalyticsTracker,
|
|
30
|
+
} from "../runtime/analytics";
|
|
31
|
+
import { getWidgetShadowCss } from "../shared/shadow-css";
|
|
32
|
+
import { WIDGET_SHADOW_THEME_CSS } from "../shared/shadow-theme";
|
|
33
|
+
|
|
34
|
+
type WidgetAppearanceAttributes = Partial<WidgetAppearance>;
|
|
35
|
+
|
|
36
|
+
export class AgentWidgetElement extends HTMLElement {
|
|
37
|
+
static observedAttributes = [
|
|
38
|
+
"agent-id",
|
|
39
|
+
"mode",
|
|
40
|
+
"variant",
|
|
41
|
+
"placement",
|
|
42
|
+
"avatar-orb-color-1",
|
|
43
|
+
"avatar-orb-color-2",
|
|
44
|
+
"action-text",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
private shadowRootRef: ShadowRoot;
|
|
48
|
+
private hostStyleElement: HTMLStyleElement;
|
|
49
|
+
private shadowStyleElement: HTMLStyleElement;
|
|
50
|
+
private mountElement: HTMLDivElement;
|
|
51
|
+
private root: Root;
|
|
52
|
+
private agentId = "";
|
|
53
|
+
private mode: "embed" | "preview" = "embed";
|
|
54
|
+
private variant: string | null = null;
|
|
55
|
+
private placement: string | null = null;
|
|
56
|
+
private appearanceAttributes: WidgetAppearanceAttributes = {};
|
|
57
|
+
private open = false;
|
|
58
|
+
private status: WidgetRuntimeStatus = "idle";
|
|
59
|
+
private sessionConfig: WidgetSessionConfig | null = null;
|
|
60
|
+
private sessionState: WidgetSessionState | null = null;
|
|
61
|
+
private messages: WidgetMessage[] = [];
|
|
62
|
+
private widgetTitle = "Assistant";
|
|
63
|
+
private welcomeMessage: string | null = null;
|
|
64
|
+
private errorMessage: string | null = null;
|
|
65
|
+
private bootstrapPromise: Promise<void> | null = null;
|
|
66
|
+
private bootstrapVersion = 0;
|
|
67
|
+
private sendVersion = 0;
|
|
68
|
+
private confirmationVersion = 0;
|
|
69
|
+
private bootstrapPhase: "idle" | "booting" | "ready" | "blocked" = "idle";
|
|
70
|
+
private analyticsTrackerKey = "";
|
|
71
|
+
private analyticsTracker: WidgetAnalyticsTracker | null = null;
|
|
72
|
+
|
|
73
|
+
constructor() {
|
|
74
|
+
super();
|
|
75
|
+
this.shadowRootRef = this.attachShadow({ mode: "open" });
|
|
76
|
+
this.hostStyleElement = document.createElement("style");
|
|
77
|
+
this.hostStyleElement.textContent = `
|
|
78
|
+
${WIDGET_SHADOW_THEME_CSS}
|
|
79
|
+
|
|
80
|
+
:host {
|
|
81
|
+
display: block;
|
|
82
|
+
box-sizing: border-box;
|
|
83
|
+
width: 100%;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:host([mode="preview"]) {
|
|
87
|
+
position: relative;
|
|
88
|
+
min-height: 720px;
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
this.shadowStyleElement = document.createElement("style");
|
|
92
|
+
this.shadowStyleElement.textContent = getWidgetShadowCss() ?? "";
|
|
93
|
+
this.mountElement = document.createElement("div");
|
|
94
|
+
this.mountElement.style.display = "block";
|
|
95
|
+
this.mountElement.style.width = "100%";
|
|
96
|
+
this.mountElement.style.height = "100%";
|
|
97
|
+
this.shadowRootRef.append(
|
|
98
|
+
this.hostStyleElement,
|
|
99
|
+
this.shadowStyleElement,
|
|
100
|
+
this.mountElement,
|
|
101
|
+
);
|
|
102
|
+
this.root = createRoot(this.mountElement);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
connectedCallback() {
|
|
106
|
+
this.syncAttributes();
|
|
107
|
+
this.recreateAnalyticsTracker();
|
|
108
|
+
this.renderWidget();
|
|
109
|
+
if (this.agentId) {
|
|
110
|
+
void this.ensureBootstrap();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
disconnectedCallback() {
|
|
115
|
+
this.bootstrapPromise = null;
|
|
116
|
+
this.analyticsTracker?.dispose();
|
|
117
|
+
this.analyticsTracker = null;
|
|
118
|
+
this.analyticsTrackerKey = "";
|
|
119
|
+
this.root.unmount();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
attributeChangedCallback(
|
|
123
|
+
name?: string,
|
|
124
|
+
oldValue?: string | null,
|
|
125
|
+
newValue?: string | null,
|
|
126
|
+
) {
|
|
127
|
+
this.syncAttributes();
|
|
128
|
+
this.recreateAnalyticsTracker();
|
|
129
|
+
if (name === "agent-id" && oldValue !== newValue) {
|
|
130
|
+
this.clearAgentState();
|
|
131
|
+
}
|
|
132
|
+
this.renderWidget();
|
|
133
|
+
if (name === "agent-id" && this.agentId) {
|
|
134
|
+
void this.ensureBootstrap();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private syncAttributes() {
|
|
139
|
+
this.agentId = this.getAttribute("agent-id")?.trim() ?? "";
|
|
140
|
+
this.mode = normalizeWidgetMode(this.getAttribute("mode"));
|
|
141
|
+
this.variant = this.getAttribute("variant");
|
|
142
|
+
this.placement = this.getAttribute("placement");
|
|
143
|
+
this.appearanceAttributes = parseAppearanceAttributes(this);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private getResolvedPlacement(): WidgetPlacement {
|
|
147
|
+
return normalizeWidgetPlacement(
|
|
148
|
+
this.placement ?? this.sessionConfig?.widgetConfig.widgetPlacement ?? "bottom-right",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private getResolvedVariant(): WidgetVariant {
|
|
153
|
+
return normalizeWidgetVariant(
|
|
154
|
+
this.variant ?? this.sessionConfig?.widgetConfig.widgetVariant ?? "chatbar",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private getResolvedAppearance(): WidgetAppearance {
|
|
159
|
+
const base =
|
|
160
|
+
this.sessionConfig?.widgetConfig.widgetAppearance ?? DEFAULT_WIDGET_APPEARANCE;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
avatarOrbColor1:
|
|
164
|
+
this.appearanceAttributes.avatarOrbColor1?.trim() ||
|
|
165
|
+
base.avatarOrbColor1,
|
|
166
|
+
avatarOrbColor2:
|
|
167
|
+
this.appearanceAttributes.avatarOrbColor2?.trim() ||
|
|
168
|
+
base.avatarOrbColor2,
|
|
169
|
+
actionText:
|
|
170
|
+
this.appearanceAttributes.actionText?.trim() || base.actionText,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private getWidgetTitle(): string {
|
|
175
|
+
return this.sessionConfig?.widgetConfig.name ?? this.widgetTitle;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private getAgentAvatarUrl(): string | null {
|
|
179
|
+
return this.sessionConfig?.widgetConfig.agentAvatarUrl ?? null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private getWelcomeMessage(): string | null {
|
|
183
|
+
return this.sessionConfig?.widgetConfig.welcomeMessage ?? this.welcomeMessage;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private getSpotlights(): string[] {
|
|
187
|
+
return this.sessionConfig?.widgetConfig.spotlightMessages ?? [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private clearAgentState() {
|
|
191
|
+
this.sendVersion += 1;
|
|
192
|
+
this.confirmationVersion += 1;
|
|
193
|
+
this.bootstrapVersion += 1;
|
|
194
|
+
this.bootstrapPhase = "idle";
|
|
195
|
+
this.bootstrapPromise = null;
|
|
196
|
+
this.sessionConfig = null;
|
|
197
|
+
this.sessionState = null;
|
|
198
|
+
this.messages = [];
|
|
199
|
+
this.widgetTitle = "Assistant";
|
|
200
|
+
this.welcomeMessage = null;
|
|
201
|
+
this.errorMessage = null;
|
|
202
|
+
this.status = "idle";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private recreateAnalyticsTracker() {
|
|
206
|
+
const nextKey = `${this.mode}:${this.agentId}`;
|
|
207
|
+
if (nextKey === this.analyticsTrackerKey) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.analyticsTracker?.dispose();
|
|
212
|
+
this.analyticsTracker = createWidgetAnalyticsTracker({
|
|
213
|
+
mode: this.mode,
|
|
214
|
+
agentId: this.agentId,
|
|
215
|
+
getSession: () => this.sessionState,
|
|
216
|
+
getConversationId: () => this.sessionState?.conversationId,
|
|
217
|
+
});
|
|
218
|
+
this.analyticsTrackerKey = nextKey;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private renderWidget() {
|
|
222
|
+
if (
|
|
223
|
+
this.bootstrapPhase !== "ready" ||
|
|
224
|
+
this.sessionConfig == null ||
|
|
225
|
+
this.sessionState == null
|
|
226
|
+
) {
|
|
227
|
+
this.root.render(null);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const appearance = this.getResolvedAppearance();
|
|
232
|
+
const placement = this.getResolvedPlacement();
|
|
233
|
+
const variant = this.getResolvedVariant();
|
|
234
|
+
const emptyAction =
|
|
235
|
+
this.status === "booting" ||
|
|
236
|
+
(this.sessionState != null && !this.sessionState.conversationId)
|
|
237
|
+
? {
|
|
238
|
+
label: DEFAULT_START_CONVERSATION_LABEL,
|
|
239
|
+
onClick: () => void this.handleStartConversation(),
|
|
240
|
+
disabled: this.status !== "ready",
|
|
241
|
+
}
|
|
242
|
+
: undefined;
|
|
243
|
+
|
|
244
|
+
this.root.render(
|
|
245
|
+
React.createElement(WidgetRuntime, {
|
|
246
|
+
key: `${this.agentId}:${widgetTriggerRuleKey(
|
|
247
|
+
this.sessionConfig?.widgetConfig.triggerRule,
|
|
248
|
+
)}`,
|
|
249
|
+
mode: this.mode,
|
|
250
|
+
agentId: this.agentId,
|
|
251
|
+
variant,
|
|
252
|
+
placement,
|
|
253
|
+
appearance,
|
|
254
|
+
agentAvatarUrl: this.getAgentAvatarUrl(),
|
|
255
|
+
widgetTitle: this.getWidgetTitle(),
|
|
256
|
+
welcomeMessage: this.getWelcomeMessage(),
|
|
257
|
+
spotlights: this.getSpotlights(),
|
|
258
|
+
open: this.open,
|
|
259
|
+
status: this.status,
|
|
260
|
+
messages: this.messages,
|
|
261
|
+
errorMessage: this.errorMessage,
|
|
262
|
+
triggerRule: this.sessionConfig?.widgetConfig.triggerRule ?? null,
|
|
263
|
+
emptyAction,
|
|
264
|
+
onStopConversation:
|
|
265
|
+
this.sessionState?.conversationId != null
|
|
266
|
+
? () => void this.handleStopConversation()
|
|
267
|
+
: undefined,
|
|
268
|
+
sendDisabled:
|
|
269
|
+
this.status === "sending" || !this.sessionState?.conversationId,
|
|
270
|
+
onOpenChange: (nextOpen: boolean) => void this.handleOpenChange(nextOpen),
|
|
271
|
+
onSendMessage: (content: string) => void this.sendMessage(content),
|
|
272
|
+
onConfirmationDecision: (confirmationId: string, accepted: boolean) =>
|
|
273
|
+
void this.handleConfirmationDecision(confirmationId, accepted),
|
|
274
|
+
onWidgetVisible: (triggerRuleType: WidgetTriggerRuleType | null) =>
|
|
275
|
+
this.analyticsTracker?.trackView(triggerRuleType),
|
|
276
|
+
onLinkClick: (linkUrl: string) =>
|
|
277
|
+
this.analyticsTracker?.trackLinkClicked(linkUrl),
|
|
278
|
+
portalContainer: this.shadowRootRef,
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private async ensureBootstrap() {
|
|
284
|
+
if (this.bootstrapPromise) {
|
|
285
|
+
return this.bootstrapPromise;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const runVersion = ++this.bootstrapVersion;
|
|
289
|
+
this.bootstrapPromise = (async () => {
|
|
290
|
+
if (!this.agentId) {
|
|
291
|
+
this.status = "error";
|
|
292
|
+
this.errorMessage = "Missing agent id";
|
|
293
|
+
this.renderWidget();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.bootstrapPhase = "booting";
|
|
298
|
+
this.status = "booting";
|
|
299
|
+
this.errorMessage = null;
|
|
300
|
+
this.renderWidget();
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const state = await bootstrapWidget({
|
|
304
|
+
agentId: this.agentId,
|
|
305
|
+
pageUrl: window.location.href,
|
|
306
|
+
referrer: document.referrer || undefined,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (runVersion !== this.bootstrapVersion) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this.sessionConfig = {
|
|
314
|
+
sessionToken: state.session.sessionToken,
|
|
315
|
+
expiresAt: state.session.expiresAt,
|
|
316
|
+
widgetConfig: state.session.widgetConfig,
|
|
317
|
+
};
|
|
318
|
+
this.sessionState = state.session;
|
|
319
|
+
this.messages = state.messages;
|
|
320
|
+
this.widgetTitle = state.session.widgetConfig.name;
|
|
321
|
+
this.welcomeMessage = state.session.widgetConfig.welcomeMessage;
|
|
322
|
+
this.status = "ready";
|
|
323
|
+
this.bootstrapPhase = "ready";
|
|
324
|
+
if (this.mode === "embed") {
|
|
325
|
+
this.analyticsTracker?.trackScriptLoaded();
|
|
326
|
+
this.analyticsTracker?.trackUniqueScriptLoaded();
|
|
327
|
+
}
|
|
328
|
+
this.renderWidget();
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (runVersion !== this.bootstrapVersion) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
this.status = "error";
|
|
335
|
+
this.bootstrapPhase = "blocked";
|
|
336
|
+
this.errorMessage =
|
|
337
|
+
error instanceof Error ? error.message : "Failed to start widget";
|
|
338
|
+
this.renderWidget();
|
|
339
|
+
}
|
|
340
|
+
})().finally(() => {
|
|
341
|
+
if (runVersion === this.bootstrapVersion) {
|
|
342
|
+
this.bootstrapPromise = null;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return this.bootstrapPromise;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private async sendMessage(content: string) {
|
|
350
|
+
const trimmedContent = content.trim();
|
|
351
|
+
if (!trimmedContent || !this.sessionState?.conversationId) return;
|
|
352
|
+
|
|
353
|
+
const runVersion = ++this.sendVersion;
|
|
354
|
+
const optimisticMessageId = `optimistic:${Date.now()}`;
|
|
355
|
+
const optimisticMessage: WidgetMessage = {
|
|
356
|
+
id: optimisticMessageId,
|
|
357
|
+
conversationId: this.sessionState.conversationId,
|
|
358
|
+
role: "user",
|
|
359
|
+
content: trimmedContent,
|
|
360
|
+
createdAt: new Date().toISOString(),
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
this.status = "sending";
|
|
364
|
+
this.errorMessage = null;
|
|
365
|
+
this.messages = [...this.messages, optimisticMessage];
|
|
366
|
+
this.renderWidget();
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const result = await submitWidgetMessage({
|
|
370
|
+
agentId: this.agentId,
|
|
371
|
+
content: trimmedContent,
|
|
372
|
+
});
|
|
373
|
+
if (runVersion !== this.sendVersion) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
this.sessionState = result.session;
|
|
377
|
+
|
|
378
|
+
const revealSteps = buildAssistantMessageRevealSteps(
|
|
379
|
+
result.assistantMessages.map((message) => ({
|
|
380
|
+
id: message.id,
|
|
381
|
+
content: message.content,
|
|
382
|
+
createdAt: message.createdAt,
|
|
383
|
+
})),
|
|
384
|
+
{
|
|
385
|
+
initialDelayMs: 0,
|
|
386
|
+
typingHoldMs: 1260,
|
|
387
|
+
interMessageDelayMs: 1520,
|
|
388
|
+
interMessageDelayJitterMs: 100,
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
for (const step of revealSteps) {
|
|
393
|
+
await new Promise((resolve) => window.setTimeout(resolve, step.delayMs));
|
|
394
|
+
if (runVersion !== this.sendVersion) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const dto = step.message;
|
|
398
|
+
this.messages = [
|
|
399
|
+
...this.messages,
|
|
400
|
+
{
|
|
401
|
+
id: dto.id,
|
|
402
|
+
conversationId: result.conversationId,
|
|
403
|
+
role: "assistant",
|
|
404
|
+
content: dto.content,
|
|
405
|
+
createdAt: dto.createdAt,
|
|
406
|
+
},
|
|
407
|
+
];
|
|
408
|
+
this.renderWidget();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (runVersion === this.sendVersion) {
|
|
412
|
+
this.status = "ready";
|
|
413
|
+
this.renderWidget();
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (runVersion !== this.sendVersion) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
this.messages = this.messages.filter(
|
|
420
|
+
(message) => message.id !== optimisticMessageId,
|
|
421
|
+
);
|
|
422
|
+
this.status = "error";
|
|
423
|
+
this.errorMessage =
|
|
424
|
+
error instanceof Error ? error.message : "Failed to send message";
|
|
425
|
+
this.renderWidget();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private async handleOpenChange(nextOpen: boolean) {
|
|
430
|
+
const wasOpen = this.open;
|
|
431
|
+
this.open = nextOpen;
|
|
432
|
+
this.renderWidget();
|
|
433
|
+
if (!wasOpen && nextOpen && this.mode === "embed") {
|
|
434
|
+
this.analyticsTracker?.trackClick();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (this.open && (this.status === "idle" || !this.sessionState)) {
|
|
438
|
+
await this.ensureBootstrap();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private async handleStartConversation() {
|
|
443
|
+
if (!this.sessionState) {
|
|
444
|
+
await this.ensureBootstrap();
|
|
445
|
+
}
|
|
446
|
+
if (!this.sessionState) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
this.status = "booting";
|
|
451
|
+
this.errorMessage = null;
|
|
452
|
+
this.renderWidget();
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const result = await startWidgetConversation({
|
|
456
|
+
agentId: this.agentId,
|
|
457
|
+
});
|
|
458
|
+
this.sessionState = result.session;
|
|
459
|
+
this.messages = result.messages;
|
|
460
|
+
this.status = "ready";
|
|
461
|
+
this.renderWidget();
|
|
462
|
+
} catch (error) {
|
|
463
|
+
this.status = "error";
|
|
464
|
+
this.errorMessage =
|
|
465
|
+
error instanceof Error ? error.message : "Failed to start conversation";
|
|
466
|
+
this.renderWidget();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private async handleStopConversation() {
|
|
471
|
+
if (!this.sessionState?.conversationId) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const confirmationId = crypto.randomUUID();
|
|
476
|
+
const runVersion = ++this.confirmationVersion;
|
|
477
|
+
|
|
478
|
+
this.status = "sending";
|
|
479
|
+
this.errorMessage = null;
|
|
480
|
+
this.renderWidget();
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const result = await submitWidgetStopConfirmation({
|
|
484
|
+
agentId: this.agentId,
|
|
485
|
+
confirmationId,
|
|
486
|
+
prompt: DEFAULT_STOP_CONVERSATION_PROMPT,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
if (runVersion !== this.confirmationVersion) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this.sessionState = result.session;
|
|
494
|
+
this.messages = [...this.messages, result.confirmationMessage];
|
|
495
|
+
this.status = "ready";
|
|
496
|
+
this.renderWidget();
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (runVersion !== this.confirmationVersion) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
this.status = "error";
|
|
502
|
+
this.errorMessage =
|
|
503
|
+
error instanceof Error ? error.message : "Failed to stop conversation";
|
|
504
|
+
this.renderWidget();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private async handleConfirmationDecision(
|
|
509
|
+
confirmationId: string,
|
|
510
|
+
accepted: boolean,
|
|
511
|
+
) {
|
|
512
|
+
if (!this.sessionState?.conversationId) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const runVersion = ++this.confirmationVersion;
|
|
517
|
+
this.status = "sending";
|
|
518
|
+
this.errorMessage = null;
|
|
519
|
+
this.renderWidget();
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
const result = await submitWidgetStopConfirmation({
|
|
523
|
+
agentId: this.agentId,
|
|
524
|
+
confirmationId,
|
|
525
|
+
decision: accepted ? "accepted" : "rejected",
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
if (runVersion !== this.confirmationVersion) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
this.sessionState = result.session;
|
|
533
|
+
this.messages = [...this.messages, result.confirmationMessage];
|
|
534
|
+
this.renderWidget();
|
|
535
|
+
|
|
536
|
+
if (accepted) {
|
|
537
|
+
window.setTimeout(() => {
|
|
538
|
+
if (runVersion !== this.confirmationVersion) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
void this.handleReset();
|
|
542
|
+
}, 0);
|
|
543
|
+
} else {
|
|
544
|
+
this.status = "ready";
|
|
545
|
+
this.renderWidget();
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (runVersion !== this.confirmationVersion) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
this.status = "error";
|
|
552
|
+
this.errorMessage =
|
|
553
|
+
error instanceof Error ? error.message : "Failed to update confirmation";
|
|
554
|
+
this.renderWidget();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private handleReset = async () => {
|
|
559
|
+
this.sendVersion += 1;
|
|
560
|
+
await resetWidgetSession(this.agentId);
|
|
561
|
+
this.clearAgentState();
|
|
562
|
+
this.open = true;
|
|
563
|
+
this.renderWidget();
|
|
564
|
+
await this.ensureBootstrap();
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function parseAppearanceAttributes(
|
|
569
|
+
element: HTMLElement,
|
|
570
|
+
): WidgetAppearanceAttributes {
|
|
571
|
+
const avatarOrbColor1 = element.getAttribute("avatar-orb-color-1");
|
|
572
|
+
const avatarOrbColor2 = element.getAttribute("avatar-orb-color-2");
|
|
573
|
+
const actionText = element.getAttribute("action-text");
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
avatarOrbColor1: avatarOrbColor1?.trim() || undefined,
|
|
577
|
+
avatarOrbColor2: avatarOrbColor2?.trim() || undefined,
|
|
578
|
+
actionText: actionText?.trim() || undefined,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function normalizeWidgetMode(value: string | null): "embed" | "preview" {
|
|
583
|
+
return value === "preview" ? "preview" : "embed";
|
|
584
|
+
}
|
package/src/embed.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./shared/widget-config";
|
|
2
|
+
export * from "./shared/analytics";
|
|
3
|
+
export * from "./renderer/index";
|
|
4
|
+
export * from "./types";
|
|
5
|
+
export * from "./shared/shadow-css";
|
|
6
|
+
export * from "./register";
|
|
7
|
+
|
|
8
|
+
import { registerWidgetElement } from "./register";
|
|
9
|
+
|
|
10
|
+
registerWidgetElement();
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AgentWidgetElement } from "./custom-element/agent-widget-element";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
"usereq-agent-widget": AgentWidgetElement;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function registerWidgetElement() {
|
|
10
|
+
if (!customElements.get("usereq-agent-widget")) {
|
|
11
|
+
customElements.define("usereq-agent-widget", AgentWidgetElement);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { AgentWidgetElement };
|