@v-tilt/browser 1.11.0 → 1.13.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/all-external-dependencies.js +1 -1
- package/dist/all-external-dependencies.js.map +1 -1
- package/dist/array.full.js +1 -1
- package/dist/array.full.js.map +1 -1
- package/dist/array.js +1 -1
- package/dist/array.js.map +1 -1
- package/dist/array.no-external.js +1 -1
- package/dist/array.no-external.js.map +1 -1
- package/dist/autocapture-types.d.ts +17 -0
- package/dist/autocapture-utils.d.ts +24 -1
- package/dist/autocapture.d.ts +94 -5
- package/dist/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/constants.d.ts +19 -13
- package/dist/core/capture.d.ts +15 -5
- package/dist/core/config-utils.d.ts +15 -0
- package/dist/core/consent.d.ts +62 -0
- package/dist/core/event-buffer.d.ts +60 -0
- package/dist/core/fb-cookies.d.ts +32 -0
- package/dist/core/feature-manager.d.ts +61 -69
- package/dist/core/fifo-queue.d.ts +23 -0
- package/dist/core/identity.d.ts +23 -33
- package/dist/core/index.d.ts +7 -1
- package/dist/core/page-lifecycle.d.ts +41 -0
- package/dist/core/remote-config.d.ts +14 -17
- package/dist/extensions/chat/bubble-drag.d.ts +30 -0
- package/dist/extensions/chat/chat-api.d.ts +15 -0
- package/dist/extensions/chat/chat-styles.d.ts +27 -0
- package/dist/extensions/chat/chat-wrapper.d.ts +20 -145
- package/dist/extensions/chat/chat.d.ts +261 -14
- package/dist/extensions/chat/message-content-styles.d.ts +1 -0
- package/dist/extensions/chat/message-html.d.ts +6 -0
- package/dist/extensions/chat/message-markdown.d.ts +8 -0
- package/dist/extensions/chat/normalize-send-content.d.ts +24 -0
- package/dist/extensions/chat/types.d.ts +19 -57
- package/dist/extensions/chat/widget-registry.d.ts +53 -0
- package/dist/extensions/chat/widgets/collect-email.d.ts +6 -0
- package/dist/extensions/chat/widgets/escalate-to-human.d.ts +6 -0
- package/dist/extensions/ga4-proxy.d.ts +59 -0
- package/dist/extensions/google-tag-gateway/consent-bridge.d.ts +27 -0
- package/dist/extensions/google-tag-gateway/enhanced-conversions.d.ts +35 -0
- package/dist/extensions/google-tag-gateway/event-bridge.d.ts +74 -0
- package/dist/extensions/google-tag-gateway/google-tag-gateway.d.ts +95 -0
- package/dist/extensions/google-tag-gateway/gtag-loader.d.ts +85 -0
- package/dist/extensions/google-tag-gateway/index.d.ts +7 -0
- package/dist/extensions/google-tag-gateway/normalize.d.ts +28 -0
- package/dist/extensions/google-tag-gateway/public-api.d.ts +23 -0
- package/dist/extensions/history-autocapture.d.ts +2 -2
- package/dist/extensions/replay/index.d.ts +1 -1
- package/dist/extensions/replay/session-recording-utils.d.ts +13 -43
- package/dist/extensions/replay/session-recording-wrapper.d.ts +10 -66
- package/dist/extensions/replay/session-recording.d.ts +53 -1
- package/dist/extensions/replay/types.d.ts +6 -1
- package/dist/extensions/web-vitals/web-vitals-manager.d.ts +14 -43
- package/dist/external-scripts-loader.js +1 -1
- package/dist/external-scripts-loader.js.map +1 -1
- package/dist/feature.d.ts +54 -172
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +728 -753
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +728 -753
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/rate-limiter.d.ts +0 -1
- package/dist/recorder.js +1 -1
- package/dist/recorder.js.map +1 -1
- package/dist/request.d.ts +34 -20
- package/dist/scroll-depth-tracker.d.ts +42 -0
- package/dist/server.d.ts +114 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +12 -0
- package/dist/types.d.ts +204 -9
- package/dist/user-manager.d.ts +26 -52
- package/dist/utils/base64.d.ts +30 -0
- package/dist/utils/bot-detection.d.ts +28 -0
- package/dist/utils/endpoint-url.d.ts +36 -0
- package/dist/utils/event-emitter.d.ts +1 -0
- package/dist/utils/globals.d.ts +71 -2
- package/dist/utils/index.d.ts +20 -5
- package/dist/utils/logger.d.ts +66 -0
- package/dist/utils/request-utils.d.ts +5 -0
- package/dist/utils/safewrap.d.ts +6 -1
- package/dist/utils/transport-health.d.ts +55 -0
- package/dist/vtilt.d.ts +85 -25
- package/dist/web-vitals.js.map +1 -1
- package/package.json +71 -66
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Chat Widget - Lazy loaded chat implementation using Ably for real-time messaging.
|
|
3
3
|
*/
|
|
4
4
|
import type { VTilt } from "../../vtilt";
|
|
5
|
-
import { type ChatConfig, type ChatChannel, type ChatChannelSummary, type ChatWidgetView, type LazyLoadedChatInterface } from "../../utils/globals";
|
|
5
|
+
import { type ChatConfig, type ChatChannel, type ChatChannelSummary, type ChatWidgetView, type LazyLoadedChatInterface, type SendChatMessageContent, type SendChatMessageOptions } from "../../utils/globals";
|
|
6
6
|
import { type MessageCallback, type TypingCallback, type ConnectionCallback, type Unsubscribe } from "./types";
|
|
7
|
+
import { type WidgetContext } from "./widget-registry";
|
|
7
8
|
export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
8
9
|
private _instance;
|
|
9
10
|
private _config;
|
|
@@ -11,10 +12,19 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
|
11
12
|
private _container;
|
|
12
13
|
private _widget;
|
|
13
14
|
private _bubble;
|
|
15
|
+
private _bubbleDragHandle;
|
|
16
|
+
private _chatTheme;
|
|
17
|
+
private _widgetResizeCleanup;
|
|
14
18
|
private _ably;
|
|
19
|
+
private _notificationsChannel;
|
|
15
20
|
private _ablyChannel;
|
|
16
21
|
private _typingChannel;
|
|
17
22
|
private _connectionState;
|
|
23
|
+
private _ablyProjectId;
|
|
24
|
+
/** Channel id included in the last per-channel Ably token authorize. */
|
|
25
|
+
private _realtimeChannelId;
|
|
26
|
+
/** Channel id we are connecting to (select/create in flight). */
|
|
27
|
+
private _pendingRealtimeChannelId;
|
|
18
28
|
private _messageCallbacks;
|
|
19
29
|
private _typingCallbacks;
|
|
20
30
|
private _connectionCallbacks;
|
|
@@ -22,6 +32,18 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
|
22
32
|
private _isUserTyping;
|
|
23
33
|
private _initialUserReadAt;
|
|
24
34
|
private _isMarkingRead;
|
|
35
|
+
private _widgetRegistry;
|
|
36
|
+
/** Tracks whether show() was explicitly called (vs open() temporarily revealing the container). */
|
|
37
|
+
private _bubbleExplicitShow;
|
|
38
|
+
private _lastConnectedDistinctId;
|
|
39
|
+
private _unsubscribeIdentity;
|
|
40
|
+
private _unsubscribeReset;
|
|
41
|
+
private _identityChangeInFlight;
|
|
42
|
+
private _userScrolledUp;
|
|
43
|
+
private _unreadNewMessagesCount;
|
|
44
|
+
private _messagesScrollListenerBound;
|
|
45
|
+
private _conversationListenersBound;
|
|
46
|
+
private _channelListListenersBound;
|
|
25
47
|
constructor(instance: VTilt, config?: ChatConfig);
|
|
26
48
|
get isOpen(): boolean;
|
|
27
49
|
get isConnected(): boolean;
|
|
@@ -45,12 +67,38 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
|
45
67
|
toggle(): void;
|
|
46
68
|
show(): void;
|
|
47
69
|
hide(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Build a stub ChatChannel from a ChatChannelSummary so that
|
|
72
|
+
* `selectChannel` can populate state synchronously while the real
|
|
73
|
+
* `GET /channels/:id` response is in flight. The header reads `ai_mode`
|
|
74
|
+
* and `status` immediately; remaining fields are filled by the response.
|
|
75
|
+
*/
|
|
76
|
+
private _stubChannelFromSummary;
|
|
77
|
+
/**
|
|
78
|
+
* Handle a vTilt identity change (anon → identified, or user → user).
|
|
79
|
+
*
|
|
80
|
+
* Ably enforces that the connection's `clientId` is immutable for the
|
|
81
|
+
* lifetime of a Realtime connection. Authorizing a token with a different
|
|
82
|
+
* `clientId` puts the connection into the terminal `failed` state and
|
|
83
|
+
* produces a 40102 error. The correct response is to fully close the
|
|
84
|
+
* existing client and create a new one with a token bound to the new id.
|
|
85
|
+
*
|
|
86
|
+
* This also clears per-identity state (channels, messages, unread count)
|
|
87
|
+
* since those belong to the previous user.
|
|
88
|
+
*/
|
|
89
|
+
private _handleIdentityChange;
|
|
48
90
|
getChannels(): Promise<void>;
|
|
49
91
|
selectChannel(channelId: string): Promise<void>;
|
|
50
92
|
createChannel(): Promise<void>;
|
|
51
|
-
goToChannelList(): void
|
|
52
|
-
sendMessage(content:
|
|
93
|
+
goToChannelList(): Promise<void>;
|
|
94
|
+
sendMessage(content: SendChatMessageContent, options?: SendChatMessageOptions): Promise<void>;
|
|
53
95
|
markAsRead(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Send a silent trigger to the messages API after a widget action completes.
|
|
98
|
+
* This makes the AI respond to the action (e.g., acknowledge email collection)
|
|
99
|
+
* without creating a visible user message in the chat.
|
|
100
|
+
*/
|
|
101
|
+
private _triggerAIAfterWidgetAction;
|
|
54
102
|
private _autoMarkAsRead;
|
|
55
103
|
private _isMessageReadByUser;
|
|
56
104
|
/**
|
|
@@ -62,32 +110,188 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
|
62
110
|
onTyping(callback: TypingCallback): Unsubscribe;
|
|
63
111
|
onConnectionChange(callback: ConnectionCallback): Unsubscribe;
|
|
64
112
|
destroy(): void;
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Ensure Ably client is connected and subscribed to the project notifications channel.
|
|
115
|
+
* Called when the widget opens. Safe to call multiple times.
|
|
116
|
+
*/
|
|
117
|
+
private _ensureAblyConnected;
|
|
118
|
+
/**
|
|
119
|
+
* Request an Ably token for the current connection scope.
|
|
120
|
+
* Pass channelId when subscribing to a conversation; omit on channel list.
|
|
121
|
+
*/
|
|
122
|
+
private _refreshAblyToken;
|
|
123
|
+
/**
|
|
124
|
+
* Subscribe to per-channel messages and typing for a specific conversation.
|
|
125
|
+
*
|
|
126
|
+
* Attach is async; the user may close the widget or switch to a different
|
|
127
|
+
* channel while we are still awaiting `attach()`. In that case
|
|
128
|
+
* `_disconnectChannel()` runs concurrently — it nulls `this._ablyChannel`
|
|
129
|
+
* and calls `detach()`, which makes Ably reject the pending `attach()` with
|
|
130
|
+
* "Attach request superseded by a subsequent detach request". We treat
|
|
131
|
+
* that race as expected: bail out silently instead of logging an error.
|
|
132
|
+
*/
|
|
133
|
+
private _connectChannel;
|
|
134
|
+
/**
|
|
135
|
+
* Unsubscribe, detach, and release per-channel Ably channels.
|
|
136
|
+
* Ably only allows release() when the channel is initialized, detached, or
|
|
137
|
+
* failed — not when attached. So we detach first, then release.
|
|
138
|
+
* Releasing removes the channel from the Ably internal registry so a
|
|
139
|
+
* subsequent auth.authorize() with narrower capabilities won't try to
|
|
140
|
+
* reattach it and fail with "Channel denied access."
|
|
141
|
+
*/
|
|
142
|
+
private _disconnectChannel;
|
|
143
|
+
/**
|
|
144
|
+
* Fully disconnect from Ably — unsubscribe everything and close the connection.
|
|
145
|
+
*/
|
|
146
|
+
private _disconnectAll;
|
|
147
|
+
/**
|
|
148
|
+
* Handle project-level notifications (new channels, updates, closes).
|
|
149
|
+
* Updates channel list and badge count in real time.
|
|
150
|
+
*/
|
|
151
|
+
private _handleNotification;
|
|
67
152
|
private _handleNewMessage;
|
|
68
153
|
private _handleTypingEvent;
|
|
69
154
|
private _handleReadCursorEvent;
|
|
70
155
|
private _notifyConnectionChange;
|
|
71
156
|
private _createUI;
|
|
72
157
|
private _attachEventListeners;
|
|
158
|
+
/**
|
|
159
|
+
* Mark a widget as submitted in local message metadata.
|
|
160
|
+
* Called after a successful widget action — _renderWidgets() uses this
|
|
161
|
+
* to show the confirmation UI instead of the input form.
|
|
162
|
+
*/
|
|
163
|
+
private _markWidgetSubmitted;
|
|
73
164
|
private _handleUserTyping;
|
|
74
165
|
private _sendTypingIndicator;
|
|
75
166
|
private _handleSend;
|
|
76
167
|
private _updateUI;
|
|
168
|
+
private _isMobile;
|
|
169
|
+
private _containerBaseStyle;
|
|
170
|
+
private _savedTransform;
|
|
171
|
+
private _isMobileFullscreen;
|
|
172
|
+
/**
|
|
173
|
+
* Mobile fullscreen: make the *container* fullscreen instead of the widget.
|
|
174
|
+
* Using position:fixed on the widget is unreliable because the container
|
|
175
|
+
* may have a CSS transform (from drag) which creates a new containing block.
|
|
176
|
+
* The container is a direct child of <body> so position:fixed is always safe.
|
|
177
|
+
*
|
|
178
|
+
* Both elements get their full style replaced atomically via setAttribute.
|
|
179
|
+
* Display is set by _updateUI after this call.
|
|
180
|
+
*/
|
|
181
|
+
private _applyMobileFullscreen;
|
|
182
|
+
private _removeMobileFullscreen;
|
|
183
|
+
private _scrollY;
|
|
184
|
+
private _lockBodyScroll;
|
|
185
|
+
/**
|
|
186
|
+
* Ensure the opened widget:
|
|
187
|
+
* 1. Covers the bubble (anchored at container bottom-right) for UI consistency.
|
|
188
|
+
* 2. Stays fully inside the viewport by shifting its position when near edges (no size change).
|
|
189
|
+
*
|
|
190
|
+
* On mobile the CSS media query handles fullscreen positioning, so this is a no-op.
|
|
191
|
+
*/
|
|
192
|
+
private _constrainWidgetToViewport;
|
|
77
193
|
private _updateHeader;
|
|
78
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Render the channel-list view with mode-aware diffing.
|
|
196
|
+
*
|
|
197
|
+
* Three modes share a single content container so the click delegation
|
|
198
|
+
* stays bound across them:
|
|
199
|
+
* - empty: no channels and not loading → welcome / CTA screen
|
|
200
|
+
* - skeleton: no channels and loading → shimmer placeholders
|
|
201
|
+
* - populated: 1+ channels → scaffold + diff items by id
|
|
202
|
+
*
|
|
203
|
+
* Only the populated mode preserves item DOM identity; the other two are
|
|
204
|
+
* rare transient states where a full innerHTML write is cheap.
|
|
205
|
+
*/
|
|
206
|
+
private _renderChannelList;
|
|
207
|
+
/**
|
|
208
|
+
* Wire a single delegated click handler on the list container so we don't
|
|
209
|
+
* re-attach per-item listeners on every render.
|
|
210
|
+
*/
|
|
211
|
+
private _setupChannelListDelegation;
|
|
212
|
+
private _createChannelItemNode;
|
|
213
|
+
private _updateChannelItemNode;
|
|
214
|
+
/**
|
|
215
|
+
* Compact fingerprint used to decide whether a channel-item DOM node needs
|
|
216
|
+
* an update. Only includes fields that the rendered HTML depends on.
|
|
217
|
+
*/
|
|
218
|
+
private _channelItemFingerprint;
|
|
219
|
+
private _getEmptyListHTML;
|
|
220
|
+
private _getListScaffoldHTML;
|
|
221
|
+
private _getListSkeletonHTML;
|
|
222
|
+
/**
|
|
223
|
+
* Build a skeleton bubble that mirrors the shape of a real chat message
|
|
224
|
+
* from `_getMessageHTML`: same avatar size (32×32), same bubble padding
|
|
225
|
+
* (12px 16px), same asymmetric border-radius (20/20/20/4 incoming,
|
|
226
|
+
* 20/20/4/20 outgoing), and a meta line underneath sized like the
|
|
227
|
+
* "Sender · 12:34" footer. The shimmer fills the bubble interior — not
|
|
228
|
+
* floating lines — so the placeholder reads as a "loading message"
|
|
229
|
+
* rather than a generic content blob. Width varies per call so the
|
|
230
|
+
* stack of bubbles has visual rhythm instead of looking like a parade
|
|
231
|
+
* of identical rectangles.
|
|
232
|
+
*/
|
|
233
|
+
private _getSkeletonBubbleHTML;
|
|
234
|
+
/**
|
|
235
|
+
* Skeleton placeholder shown while `selectChannel` is loading the
|
|
236
|
+
* messages of an existing conversation. Three alternating bubbles
|
|
237
|
+
* (incoming → outgoing → incoming) imply history is on its way.
|
|
238
|
+
* Bubble widths vary to avoid the "fake repeated rectangles" look.
|
|
239
|
+
*/
|
|
240
|
+
private _getMessageSkeletonHTML;
|
|
241
|
+
/**
|
|
242
|
+
* New-conversation loader shown while `createChannel` (or
|
|
243
|
+
* `sendMessage({channel: 'new'})`) is in flight. A single incoming
|
|
244
|
+
* bubble using *exactly* the same shape, padding, border-radius, and
|
|
245
|
+
* avatar treatment as a real incoming message from `_getMessageHTML`
|
|
246
|
+
* — so when the real greeting arrives it morphs in place rather than
|
|
247
|
+
* snapping to a new layout. The avatar uses the live AI/Support color
|
|
248
|
+
* + icon (identity is known from `_config.aiMode`), and the bubble
|
|
249
|
+
* carries three animated typing dots instead of text, signalling
|
|
250
|
+
* "the assistant is preparing your greeting."
|
|
251
|
+
*/
|
|
252
|
+
private _getNewChannelLoaderHTML;
|
|
79
253
|
private _getChannelItemHTML;
|
|
80
254
|
private _getConversationHTML;
|
|
81
|
-
private _attachChannelListListeners;
|
|
82
255
|
private _attachConversationListeners;
|
|
83
|
-
private _escapeHtml;
|
|
84
256
|
private _formatRelativeTime;
|
|
257
|
+
/**
|
|
258
|
+
* Render messages with a key-based diff.
|
|
259
|
+
*
|
|
260
|
+
* - Each message node carries `data-msg-key="msg:<id>"` and a fingerprint;
|
|
261
|
+
* if both match what we already have, the node is left untouched.
|
|
262
|
+
* - The "New" divider lives as a sibling node with its own key so it
|
|
263
|
+
* participates in the same diff (no flicker when it moves).
|
|
264
|
+
* - Scroll position is preserved: we only snap to the bottom when the user
|
|
265
|
+
* was already near the bottom or just sent their own message. Otherwise
|
|
266
|
+
* we surface a "↓ N new messages" pill instead of yanking them down.
|
|
267
|
+
* - Newly-inserted message nodes get the `vtilt-msg-enter` class for a
|
|
268
|
+
* subtle fade-in animation. The initial bulk render skips animation.
|
|
269
|
+
*/
|
|
85
270
|
private _renderMessages;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
private
|
|
271
|
+
/**
|
|
272
|
+
* Build the "New" divider node (rendered above the first unread agent/AI
|
|
273
|
+
* message when scrolled into view).
|
|
274
|
+
*/
|
|
275
|
+
private _createMessageDividerNode;
|
|
276
|
+
/**
|
|
277
|
+
* Build a single message DOM node from the message model.
|
|
278
|
+
*/
|
|
279
|
+
private _createMessageNode;
|
|
280
|
+
/**
|
|
281
|
+
* Compact fingerprint used to decide whether a message node needs an update.
|
|
282
|
+
* Streaming AI responses update the content field many times — re-rendering
|
|
283
|
+
* the same DOM lets the cursor and layout stay stable for the user.
|
|
284
|
+
*/
|
|
285
|
+
private _messageFingerprint;
|
|
286
|
+
/**
|
|
287
|
+
* Wire (once per container) a scroll listener that tracks whether the user
|
|
288
|
+
* is reading history above the latest message. Used to suppress auto-scroll
|
|
289
|
+
* and to dismiss the "new messages" pill when the user scrolls back down.
|
|
290
|
+
*/
|
|
291
|
+
private _setupMessagesScrollListener;
|
|
292
|
+
private _isNearBottom;
|
|
293
|
+
private _showNewMessagesPill;
|
|
294
|
+
private _hideNewMessagesPill;
|
|
91
295
|
private _getMessageHTML;
|
|
92
296
|
private _isMessageReadByAgent;
|
|
93
297
|
/**
|
|
@@ -99,8 +303,51 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
|
99
303
|
* Remove temporary message by ID
|
|
100
304
|
*/
|
|
101
305
|
private _removeTempMessage;
|
|
102
|
-
private _apiRequest;
|
|
103
306
|
private _getTimeOpen;
|
|
307
|
+
/**
|
|
308
|
+
* Bubble body for any message — plain escaped text or sanitized rich HTML,
|
|
309
|
+
* using the same rules for user (html/markdown) and agent/AI messages.
|
|
310
|
+
*/
|
|
311
|
+
private _renderMessageContent;
|
|
312
|
+
private _renderMessageHtml;
|
|
313
|
+
/**
|
|
314
|
+
* Render widgets for a message. Widget data sources (checked in order):
|
|
315
|
+
* 1. metadata._widgets -- set during streaming (temp messages)
|
|
316
|
+
* 2. metadata.widgets -- persisted in DB (loaded on refresh, received via Ably)
|
|
317
|
+
* 3. Content markers -- fallback for legacy messages with <!--vtilt:widget:...--> in content
|
|
318
|
+
*
|
|
319
|
+
* If a widget has submitted=true in metadata, render the confirmation instead of the input.
|
|
320
|
+
*/
|
|
321
|
+
private _renderWidgets;
|
|
322
|
+
/**
|
|
323
|
+
* Build WidgetContext for widget renderers.
|
|
324
|
+
*/
|
|
325
|
+
private _getWidgetContext;
|
|
326
|
+
/**
|
|
327
|
+
* Extract widget blocks from streamed text, strip them, and return parsed widgets.
|
|
328
|
+
*/
|
|
329
|
+
private _parseWidgetBlocks;
|
|
330
|
+
/**
|
|
331
|
+
* Public method for SDK consumers to register custom widgets.
|
|
332
|
+
*/
|
|
333
|
+
registerWidget(definition: {
|
|
334
|
+
type: string;
|
|
335
|
+
toolDescription: string;
|
|
336
|
+
parameters: Record<string, {
|
|
337
|
+
type: string;
|
|
338
|
+
description: string;
|
|
339
|
+
}>;
|
|
340
|
+
render: (params: Record<string, unknown>, context: WidgetContext) => string;
|
|
341
|
+
onAction: (action: string, data: Record<string, unknown>, context: WidgetContext) => Promise<{
|
|
342
|
+
replaceHTML?: string;
|
|
343
|
+
sendMessage?: string;
|
|
344
|
+
}>;
|
|
345
|
+
}): void;
|
|
104
346
|
private _escapeHTML;
|
|
347
|
+
/**
|
|
348
|
+
* Get a human-readable preview for a message (for channel list).
|
|
349
|
+
* Falls back to widget labels if content is empty (tool-only messages).
|
|
350
|
+
*/
|
|
351
|
+
private _getMessagePreview;
|
|
105
352
|
private _formatTime;
|
|
106
353
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CHAT_MESSAGE_CONTENT_CSS = "\n #vtilt-chat-widget .vtilt-md {\n max-width: 100%;\n overflow-wrap: anywhere;\n word-break: break-word;\n font-size: inherit;\n line-height: 1.45;\n }\n #vtilt-chat-widget .vtilt-md * {\n box-sizing: border-box;\n }\n #vtilt-chat-widget .vtilt-md :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, pre) {\n margin: 0;\n }\n #vtilt-chat-widget .vtilt-md p { margin: 0 0 0.5em 0; }\n #vtilt-chat-widget .vtilt-md p:last-child { margin-bottom: 0; }\n #vtilt-chat-widget .vtilt-md :is(b, strong) { font-weight: 600; }\n #vtilt-chat-widget .vtilt-md :is(i, em) { font-style: italic; }\n #vtilt-chat-widget .vtilt-md u { text-decoration: underline; }\n #vtilt-chat-widget .vtilt-md :is(h1, h2, h3, h4, h5, h6) {\n font-size: 1em;\n font-weight: 600;\n margin: 0.65em 0 0.35em 0;\n line-height: 1.3;\n }\n #vtilt-chat-widget .vtilt-md :is(h1, h2, h3):first-child { margin-top: 0.15em; }\n\n /* Lists \u2014 flex rows + ::before bullets (beats host list-item / ::marker resets) */\n #vtilt-chat-widget .vtilt-md :is(ul, ol) {\n list-style: none !important;\n margin: 0.5em 0 !important;\n padding: 0 0 0 0.35em !important;\n }\n #vtilt-chat-widget .vtilt-md li {\n display: flex !important;\n flex-direction: row !important;\n flex-wrap: wrap !important;\n align-items: flex-start !important;\n gap: 0.35em !important;\n margin: 0.2em 0 !important;\n padding: 0 !important;\n line-height: 1.45;\n list-style: none !important;\n list-style-type: none !important;\n }\n #vtilt-chat-widget .vtilt-md li::marker {\n content: \"\" !important;\n font-size: 0 !important;\n color: transparent !important;\n }\n #vtilt-chat-widget .vtilt-md li::-webkit-list-marker {\n display: none !important;\n }\n #vtilt-chat-widget .vtilt-md ul > li::before {\n content: \"\u2022\" !important;\n display: inline-block !important;\n position: static !important;\n flex: 0 0 auto;\n line-height: 1.45;\n }\n #vtilt-chat-widget .vtilt-md ul ul > li::before {\n content: \"\u25E6\" !important;\n }\n #vtilt-chat-widget .vtilt-md ol { counter-reset: vtilt-ol; }\n #vtilt-chat-widget .vtilt-md ol > li { counter-increment: vtilt-ol; }\n #vtilt-chat-widget .vtilt-md ol > li::before {\n content: counter(vtilt-ol) \".\" !important;\n display: inline-block !important;\n position: static !important;\n flex: 0 0 auto;\n min-width: 1.1em;\n line-height: 1.45;\n }\n #vtilt-chat-widget .vtilt-md ol ol { counter-reset: vtilt-ol; }\n #vtilt-chat-widget .vtilt-md li > :is(ul, ol) {\n flex: 1 0 100% !important;\n width: 100% !important;\n margin: 0.25em 0 0 0 !important;\n padding-left: 0.75em !important;\n }\n\n #vtilt-chat-widget .vtilt-md code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.9em;\n background: rgba(0, 0, 0, 0.06);\n padding: 0.1em 0.35em;\n border-radius: 4px;\n }\n #vtilt-chat-widget .vtilt-md pre {\n margin: 0.5em 0;\n padding: 0.5em 0.65em;\n border-radius: 6px;\n background: rgba(0, 0, 0, 0.06);\n overflow-x: auto;\n white-space: pre-wrap;\n }\n #vtilt-chat-widget .vtilt-md pre code { background: none; padding: 0; }\n #vtilt-chat-widget .vtilt-md blockquote {\n margin: 0.5em 0;\n padding-left: 0.75em;\n border-left: 3px solid rgba(0, 0, 0, 0.15);\n }\n #vtilt-chat-widget .vtilt-md a { color: inherit; text-decoration: underline; }\n #vtilt-chat-widget .vtilt-md br { line-height: inherit; }\n\n /* Widget visitor bubble (brand color) */\n #vtilt-chat-widget .vtilt-user-md code { background: rgba(255, 255, 255, 0.2); }\n #vtilt-chat-widget .vtilt-user-md pre { background: rgba(255, 255, 255, 0.15); }\n #vtilt-chat-widget .vtilt-user-md pre code { background: none; }\n #vtilt-chat-widget .vtilt-user-md blockquote { border-left-color: rgba(255, 255, 255, 0.35); }\n";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert stored message content to safe HTML for bubble rendering.
|
|
3
|
+
*/
|
|
4
|
+
export declare function formatMessageBodyHtml(text: string, contentType: "text" | "html" | "attachment", doc: Document | null): string;
|
|
5
|
+
/** Plain-text preview for channel list (strips widgets and HTML). */
|
|
6
|
+
export declare function plainTextMessagePreview(content: string, maxLength?: number): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Detect content that already contains HTML block-level tags. */
|
|
2
|
+
export declare const HTML_BLOCK_TAG_RE: RegExp;
|
|
3
|
+
/**
|
|
4
|
+
* Best-effort markdown → HTML. Returns input unchanged when it already looks like HTML.
|
|
5
|
+
*/
|
|
6
|
+
export declare function markdownToHtml(src: string): string;
|
|
7
|
+
/** Whether markdown conversion should run before sanitization. */
|
|
8
|
+
export declare function shouldUseMarkdownPipeline(contentType: "text" | "html" | "attachment", cleanText: string): boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SendChatMessageContent, SendChatMessageOptions } from "../../utils/globals";
|
|
2
|
+
import type { ContentType } from "./types";
|
|
3
|
+
/** Metadata key for markdown stored as `content_type: text`. Must match dashboard `message-rendering.ts`. */
|
|
4
|
+
export declare const RICH_FORMAT_METADATA_KEY = "rich_format";
|
|
5
|
+
export declare const RICH_FORMAT_MARKDOWN = "markdown";
|
|
6
|
+
export interface NormalizedOutgoingMessage {
|
|
7
|
+
content: string;
|
|
8
|
+
content_type: ContentType;
|
|
9
|
+
metadata: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function isRichUserMessage(message: {
|
|
12
|
+
content_type: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}): boolean;
|
|
15
|
+
/** Whether a stored message should use the rich HTML renderer in the widget. */
|
|
16
|
+
export declare function messageUsesRichRendering(message: {
|
|
17
|
+
sender_type: string;
|
|
18
|
+
content_type: string;
|
|
19
|
+
metadata?: Record<string, unknown>;
|
|
20
|
+
}): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Normalize integrator input into stored message fields.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeSendChatMessageContent(input: SendChatMessageContent, options?: SendChatMessageOptions): NormalizedOutgoingMessage | null;
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Type definitions for the chat widget extension.
|
|
5
5
|
* Core types are re-exported from globals.ts for consistency.
|
|
6
6
|
*/
|
|
7
|
-
export type { ChatMessage, ChatChannel, ChatChannelSummary, ChatConfig, ChatTheme, ChatWidgetView, LazyLoadedChatInterface, } from "../../utils/globals";
|
|
7
|
+
export type { ChatMessage, ChatChannel, ChatChannelSummary, ChatConfig, ChatTheme, ChatWidgetView, LazyLoadedChatInterface, SendChatMessageContent, SendChatMessageOptions, } from "../../utils/globals";
|
|
8
|
+
export { messageUsesRichRendering, normalizeSendChatMessageContent, } from "./normalize-send-content";
|
|
9
|
+
export type { NormalizedOutgoingMessage } from "./normalize-send-content";
|
|
8
10
|
/**
|
|
9
11
|
* Sender types for chat messages
|
|
10
12
|
*/
|
|
@@ -45,75 +47,35 @@ export interface PresenceStatus {
|
|
|
45
47
|
current_page_url?: string;
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
48
|
-
*
|
|
50
|
+
* Normalized channel event names for analytics tracking
|
|
49
51
|
*
|
|
50
52
|
* Message events are tracked client-side via capture() for full enrichment
|
|
51
|
-
* (
|
|
53
|
+
* (session, page URL, device, person properties, etc.).
|
|
52
54
|
* Widget UI events (open/close) are exposed via callbacks in ChatConfig.
|
|
53
55
|
*
|
|
54
|
-
* All
|
|
55
|
-
* - $
|
|
56
|
-
* - $
|
|
57
|
-
* - $message_id:
|
|
56
|
+
* All channel message events use $channel_message with normalized properties:
|
|
57
|
+
* - $channel_type: channel discriminator ('chat', 'email', 'sms')
|
|
58
|
+
* - $conversation_id: conversation identifier
|
|
59
|
+
* - $message_id: message identifier
|
|
60
|
+
* - $direction: 'inbound' | 'outbound'
|
|
61
|
+
* - $sender_type: 'user' | 'ai' | 'agent' | 'system'
|
|
62
|
+
* - $content_preview: first 100 chars of content
|
|
58
63
|
*
|
|
59
64
|
* Tracking is gated by remote config:
|
|
60
65
|
* - chatTracking.trackUserMessages → captures when $sender_type=user
|
|
61
66
|
* - chatTracking.trackAgentMessages → captures when $sender_type=ai|agent
|
|
62
67
|
*/
|
|
63
|
-
export declare const
|
|
64
|
-
|
|
65
|
-
readonly MESSAGE: "$chat_message";
|
|
68
|
+
export declare const CHANNEL_EVENTS: {
|
|
69
|
+
readonly MESSAGE: "$channel_message";
|
|
66
70
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
export interface ChatMessageEventProperties {
|
|
71
|
-
$channel_id: string;
|
|
72
|
-
$message_id: string;
|
|
73
|
-
$sender_type: SenderType;
|
|
74
|
-
$content_preview: string;
|
|
75
|
-
$ai_mode: boolean;
|
|
76
|
-
$word_count: number;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* @deprecated Use ChatMessageEventProperties instead
|
|
80
|
-
*/
|
|
81
|
-
export interface ChatMessageSentEventProperties {
|
|
82
|
-
$channel_id: string;
|
|
71
|
+
export interface ChannelMessageEventProperties {
|
|
72
|
+
$channel_type: string;
|
|
73
|
+
$conversation_id: string;
|
|
83
74
|
$message_id: string;
|
|
84
|
-
$
|
|
75
|
+
$direction: "inbound" | "outbound";
|
|
85
76
|
$sender_type: SenderType;
|
|
86
|
-
$ai_mode: boolean;
|
|
87
|
-
$word_count: number;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* @deprecated Use ChatMessageEventProperties instead
|
|
91
|
-
*/
|
|
92
|
-
export interface ChatMessageReceivedEventProperties {
|
|
93
|
-
$channel_id: string;
|
|
94
|
-
$message_id: string;
|
|
95
77
|
$content_preview: string;
|
|
96
|
-
$
|
|
97
|
-
$response_time_ms?: number;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Chat event properties for $chat_started
|
|
101
|
-
*/
|
|
102
|
-
export interface ChatStartedEventProperties {
|
|
103
|
-
$channel_id: string;
|
|
104
|
-
$initiated_by: "user" | "agent";
|
|
105
|
-
$ai_mode: boolean;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Chat event properties for $chat_closed
|
|
109
|
-
*/
|
|
110
|
-
export interface ChatClosedEventProperties {
|
|
111
|
-
$channel_id: string;
|
|
112
|
-
$resolved_by: "agent" | "user" | "system" | "auto";
|
|
113
|
-
$resolution: "resolved" | "unresolved" | "abandoned";
|
|
114
|
-
$message_count: number;
|
|
115
|
-
$duration_seconds: number;
|
|
116
|
-
$ai_only: boolean;
|
|
78
|
+
$ai_mode?: boolean;
|
|
117
79
|
}
|
|
118
80
|
/**
|
|
119
81
|
* Internal widget state
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Widget Registry - Extensible widget system for AI-triggered UI.
|
|
3
|
+
* Tool calls from the AI are mapped to registered widgets; each widget provides render() and onAction().
|
|
4
|
+
*/
|
|
5
|
+
export interface WidgetContext {
|
|
6
|
+
channelId: string;
|
|
7
|
+
distinctId: string;
|
|
8
|
+
primaryColor: string;
|
|
9
|
+
/** @deprecated Use buildEndpointUrl() instead */
|
|
10
|
+
apiBase: string;
|
|
11
|
+
token: string;
|
|
12
|
+
/** The ID of the message containing this widget */
|
|
13
|
+
messageId: string;
|
|
14
|
+
/** Build a full URL for a given endpoint path, respecting api_host */
|
|
15
|
+
buildEndpointUrl(path: string): string;
|
|
16
|
+
}
|
|
17
|
+
export interface WidgetActionResult {
|
|
18
|
+
/** Whether the action succeeded — drives metadata-based re-render */
|
|
19
|
+
success?: boolean;
|
|
20
|
+
/** @deprecated Use metadata-driven rendering instead. Kept for custom widget compat. */
|
|
21
|
+
replaceHTML?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ChatWidgetDefinition {
|
|
24
|
+
/** Unique type identifier, e.g. "collect_email", "schedule_meeting" */
|
|
25
|
+
type: string;
|
|
26
|
+
/** AI tool description (sent to LLM so it knows when to invoke) */
|
|
27
|
+
toolDescription: string;
|
|
28
|
+
/** JSON schema shape for the tool parameters */
|
|
29
|
+
parameters: Record<string, {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
}>;
|
|
33
|
+
/** Returns an HTML string to render inline in the chat bubble */
|
|
34
|
+
render(params: Record<string, unknown>, context: WidgetContext): string;
|
|
35
|
+
/** Called when the widget form is submitted or button clicked */
|
|
36
|
+
onAction(action: string, data: Record<string, unknown>, context: WidgetContext): Promise<WidgetActionResult>;
|
|
37
|
+
}
|
|
38
|
+
export declare class ChatWidgetRegistry {
|
|
39
|
+
private _widgets;
|
|
40
|
+
register(definition: ChatWidgetDefinition): void;
|
|
41
|
+
get(type: string): ChatWidgetDefinition | undefined;
|
|
42
|
+
getAll(): ChatWidgetDefinition[];
|
|
43
|
+
/**
|
|
44
|
+
* Build tool definitions for the AI from all registered widgets.
|
|
45
|
+
*/
|
|
46
|
+
getToolDefinitions(): Record<string, {
|
|
47
|
+
description: string;
|
|
48
|
+
parameters: Record<string, {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
}>;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in collect_email widget: renders name + email inputs, POSTs to widget actions API.
|
|
3
|
+
* UI state is metadata-driven — submitted state is rendered by _renderWidgets().
|
|
4
|
+
*/
|
|
5
|
+
import type { ChatWidgetDefinition } from "../widget-registry";
|
|
6
|
+
export declare const collectEmailWidget: ChatWidgetDefinition;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in escalate_to_human widget: shows a "Connect with a human" button.
|
|
3
|
+
* UI state is metadata-driven — submitted state is rendered by _renderWidgets().
|
|
4
|
+
*/
|
|
5
|
+
import type { ChatWidgetDefinition } from "../widget-registry";
|
|
6
|
+
export declare const escalateToHumanWidget: ChatWidgetDefinition;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GA4 Proxy Feature
|
|
3
|
+
*
|
|
4
|
+
* Automatically configures gtag.js to send through vTilt's domain.
|
|
5
|
+
* When enabled, either reconfigures an existing gtag.js or loads a new one
|
|
6
|
+
* with server_container_url pointing to vTilt's proxy routes.
|
|
7
|
+
*
|
|
8
|
+
* Config sources (init config takes priority over remote config):
|
|
9
|
+
* vt.init({ ga4: { measurementId: 'G-XXX' } })
|
|
10
|
+
* /api/decide → { ga4: { measurementId: 'G-XXX' } }
|
|
11
|
+
*/
|
|
12
|
+
import type { VTiltConfig } from "../types";
|
|
13
|
+
import type { Feature, FeatureConfig } from "../feature";
|
|
14
|
+
export interface GA4ProxyConfig extends FeatureConfig {
|
|
15
|
+
measurementId?: string;
|
|
16
|
+
debugMode?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare class GA4Proxy implements Feature {
|
|
19
|
+
readonly name = "GA4Proxy";
|
|
20
|
+
private _instance;
|
|
21
|
+
private _config;
|
|
22
|
+
private _isStarted;
|
|
23
|
+
private _pendingGtagCallbacks;
|
|
24
|
+
/** Fires once both client_id and session_id gtag callbacks complete. */
|
|
25
|
+
onIdentityResolved?: () => void;
|
|
26
|
+
constructor(instance: {
|
|
27
|
+
getConfig(): VTiltConfig;
|
|
28
|
+
}, config?: GA4ProxyConfig);
|
|
29
|
+
static extractConfig(config: VTiltConfig): GA4ProxyConfig;
|
|
30
|
+
get isEnabled(): boolean;
|
|
31
|
+
get isStarted(): boolean;
|
|
32
|
+
startIfEnabled(): void;
|
|
33
|
+
stop(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Sync user_id to gtag.js so both proxy and MP event streams share
|
|
36
|
+
* the same identity after identify(). GA4 Realtime uses user_id as
|
|
37
|
+
* the highest-priority identifier; without this, proxy events (no
|
|
38
|
+
* user_id) and MP events (with user_id) count as separate users.
|
|
39
|
+
*/
|
|
40
|
+
setUserId(userId: string | null): void;
|
|
41
|
+
onConfigUpdate(config: VTiltConfig): void;
|
|
42
|
+
private _start;
|
|
43
|
+
private _getApiBase;
|
|
44
|
+
private _hasGtag;
|
|
45
|
+
private _reconfigureExistingGtag;
|
|
46
|
+
private _loadAndConfigureGtag;
|
|
47
|
+
/**
|
|
48
|
+
* Use gtag('get', ...) to obtain the authoritative client_id and
|
|
49
|
+
* session_id from gtag.js. Fires onIdentityResolved once BOTH
|
|
50
|
+
* callbacks have completed (counter-based).
|
|
51
|
+
*/
|
|
52
|
+
private _resolveGtagIdentity;
|
|
53
|
+
/**
|
|
54
|
+
* Fires after each gtag('get', ...) callback. When all pending callbacks
|
|
55
|
+
* have settled, notifies the host (EventBuffer) so buffered MP events
|
|
56
|
+
* can flush with stable GA4 IDs (same client_id as gtag.js).
|
|
57
|
+
*/
|
|
58
|
+
private _onGtagCallbackSettled;
|
|
59
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent Mode v2 bridge.
|
|
3
|
+
*
|
|
4
|
+
* Maps vTilt's 3-boolean consent (analytics / marketing / advertising) to
|
|
5
|
+
* Google's 4-key Consent Mode v2 payload (ad_storage, ad_user_data,
|
|
6
|
+
* ad_personalization, analytics_storage) and pushes it through gtag.
|
|
7
|
+
*
|
|
8
|
+
* Mapping (default):
|
|
9
|
+
* advertising -> ad_storage + ad_user_data + ad_personalization
|
|
10
|
+
* analytics -> analytics_storage
|
|
11
|
+
* marketing is not sent to Google by default (it covers email/SMS, not ads).
|
|
12
|
+
*
|
|
13
|
+
* Admin UI may override individual keys (`consentOverride`) to hard-wire a
|
|
14
|
+
* specific value regardless of user consent — useful for jurisdictions or
|
|
15
|
+
* projects with fixed policies.
|
|
16
|
+
*/
|
|
17
|
+
import type { ConsentState } from "../../core/consent";
|
|
18
|
+
import type { GoogleConsentOverride } from "../../types";
|
|
19
|
+
export type GtagFn = (...args: unknown[]) => void;
|
|
20
|
+
export type GoogleConsentPayload = Record<"ad_storage" | "ad_user_data" | "ad_personalization" | "analytics_storage", "granted" | "denied">;
|
|
21
|
+
export declare function buildConsentPayload(consent: ConsentState, override?: GoogleConsentOverride): GoogleConsentPayload;
|
|
22
|
+
/**
|
|
23
|
+
* Send the default consent state BEFORE gtag.js loads so that every
|
|
24
|
+
* subsequent request carries the right flags. Call once at feature start.
|
|
25
|
+
*/
|
|
26
|
+
export declare function pushConsentDefault(gtag: GtagFn, consent: ConsentState, override?: GoogleConsentOverride): void;
|
|
27
|
+
export declare function pushConsentUpdate(gtag: GtagFn, consent: ConsentState, override?: GoogleConsentOverride): void;
|