@v-tilt/browser 1.13.0 → 1.13.1
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.map +1 -1
- package/dist/array.chat.js +2 -0
- package/dist/array.chat.js.map +1 -0
- package/dist/array.chat.no-external.js +2 -0
- package/dist/array.chat.no-external.js.map +1 -0
- package/dist/array.full.chat.js +2 -0
- package/dist/array.full.chat.js.map +1 -0
- package/dist/array.full.chat.no-external.js +2 -0
- package/dist/array.full.chat.no-external.js.map +1 -0
- package/dist/array.full.js +1 -1
- package/dist/array.full.js.map +1 -1
- package/dist/array.full.no-external.js +2 -0
- package/dist/array.full.no-external.js.map +1 -0
- 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/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/entrypoints/all-external-dependencies.d.ts +10 -3
- package/dist/entrypoints/array.chat.d.ts +10 -0
- package/dist/entrypoints/array.chat.no-external.d.ts +6 -0
- package/dist/entrypoints/array.full.chat.d.ts +13 -0
- package/dist/entrypoints/array.full.chat.no-external.d.ts +7 -0
- package/dist/entrypoints/array.full.d.ts +5 -9
- package/dist/entrypoints/array.full.no-external.d.ts +12 -0
- package/dist/entrypoints/module.chat.es.d.ts +7 -0
- package/dist/entrypoints/module.full.chat.es.d.ts +12 -0
- package/dist/entrypoints/module.full.es.d.ts +12 -0
- package/dist/entrypoints/module.no-external.es.d.ts +1 -0
- package/dist/extensions/chat/bubble-drag.d.ts +20 -5
- package/dist/extensions/chat/chat-wrapper.d.ts +8 -2
- package/dist/extensions/chat/chat.d.ts +21 -351
- package/dist/extensions/chat/controller/__tests__/fakes/ably-realtime-fake.d.ts +84 -0
- package/dist/extensions/chat/controller/ably-client.d.ts +160 -0
- package/dist/extensions/chat/controller/ably-handlers.d.ts +95 -0
- package/dist/extensions/chat/controller/ably-token.d.ts +67 -0
- package/dist/extensions/chat/controller/chat-controller.d.ts +194 -0
- package/dist/extensions/chat/controller/message-delivery-status.d.ts +6 -0
- package/dist/extensions/chat/controller/message-order.d.ts +12 -0
- package/dist/extensions/chat/controller/message-stream.d.ts +49 -0
- package/dist/extensions/chat/lib/bubble-offset.d.ts +18 -0
- package/dist/extensions/chat/lib/merge-chat-config.d.ts +3 -0
- package/dist/extensions/chat/normalize-send-content.d.ts +2 -0
- package/dist/extensions/chat/outbox/message-delivery.d.ts +17 -0
- package/dist/extensions/chat/outbox/message-outbox.d.ts +57 -0
- package/dist/extensions/chat/store/chat-store.d.ts +122 -0
- package/dist/extensions/chat/types.d.ts +1 -19
- package/dist/extensions/chat/ui/ChannelItem.d.ts +12 -0
- package/dist/extensions/chat/ui/ChannelListView.d.ts +14 -0
- package/dist/extensions/chat/ui/ChatBubble.d.ts +14 -0
- package/dist/extensions/chat/ui/ChatHeader.d.ts +13 -0
- package/dist/extensions/chat/ui/ChatPanel.d.ts +14 -0
- package/dist/extensions/chat/ui/ChatRoot.d.ts +31 -0
- package/dist/extensions/chat/ui/ClosedBanner.d.ts +7 -0
- package/dist/extensions/chat/ui/ConnectionBanner.d.ts +32 -0
- package/dist/extensions/chat/ui/ConversationView.d.ts +14 -0
- package/dist/extensions/chat/ui/MessageBubble.d.ts +25 -0
- package/dist/extensions/chat/ui/MessageInput.d.ts +14 -0
- package/dist/extensions/chat/ui/MessageList.d.ts +19 -0
- package/dist/extensions/chat/ui/NewMessagesPill.d.ts +13 -0
- package/dist/extensions/chat/ui/Skeletons.d.ts +14 -0
- package/dist/extensions/chat/ui/TypingBubble.d.ts +23 -0
- package/dist/extensions/chat/ui/TypingIndicator.d.ts +12 -0
- package/dist/extensions/chat/ui/WidgetSlot.d.ts +20 -0
- package/dist/extensions/chat/ui/hooks/use-auto-mark-read.d.ts +18 -0
- package/dist/extensions/chat/ui/hooks/use-scroll-preservation.d.ts +33 -0
- package/dist/extensions/chat/ui/icons.d.ts +18 -0
- package/dist/extensions/chat/ui/message-render.d.ts +18 -0
- package/dist/extensions/chat/ui/shadow-styles.d.ts +13 -0
- package/dist/extensions/chat/ui/utils/mobile-body-scroll-lock.d.ts +14 -0
- package/dist/extensions/chat/widget-registry.d.ts +21 -5
- package/dist/extensions/chat/widgets/collect-email.d.ts +4 -2
- package/dist/extensions/chat/widgets/escalate-to-human.d.ts +2 -1
- package/dist/external-scripts-loader.js +1 -1
- package/dist/external-scripts-loader.js.map +1 -1
- package/dist/feature.d.ts +1 -0
- package/dist/lib/merge-vtilt-config.d.ts +9 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.chat.d.ts +1788 -0
- package/dist/module.chat.js +2 -0
- package/dist/module.chat.js.map +1 -0
- package/dist/module.d.ts +48 -3
- package/dist/module.full.chat.d.ts +1792 -0
- package/dist/module.full.chat.js +2 -0
- package/dist/module.full.chat.js.map +1 -0
- package/dist/module.full.d.ts +1793 -0
- package/dist/module.full.js +2 -0
- package/dist/module.full.js.map +1 -0
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +48 -3
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/recorder.js.map +1 -1
- package/dist/snippet-stub-methods.d.ts +14 -0
- package/dist/utils/globals.d.ts +53 -27
- package/dist/utils/index.d.ts +8 -0
- package/dist/vtilt.d.ts +6 -1
- package/dist/web-vitals.js.map +1 -1
- package/package.json +74 -71
- package/dist/extensions/chat/chat-styles.d.ts +0 -27
- package/dist/extensions/chat/message-content-styles.d.ts +0 -1
- package/dist/extensions/chat/message-html.d.ts +0 -6
- package/dist/extensions/chat/message-markdown.d.ts +0 -8
- package/dist/extensions/ga4-proxy.d.ts +0 -59
- package/dist/utils/type-utils.d.ts +0 -4
- package/dist/web-vitals.d.ts +0 -81
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Lazy extension entrypoints bundled into `array.full*` / `module.full*` builds.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Registers factories on `window.__VTiltExtensions__` so `loadExternalDependency`
|
|
5
|
+
* is skipped when these bundles are used with `disable_external_dependency_loading`.
|
|
6
|
+
*
|
|
7
|
+
* **Chat is intentionally excluded** — same as PostHog's `all-external-dependencies.ts`
|
|
8
|
+
* (recorder, surveys, web-vitals, … but not `conversations`). Chat is ~60 KB gzip
|
|
9
|
+
* and not every site uses it. Opt in via:
|
|
10
|
+
* - `array.chat.js` / `module.chat.es.js` (core + chat only)
|
|
11
|
+
* - `array.full.chat.js` / `module.full.chat.es.js` (recorder + vitals + chat)
|
|
12
|
+
* - side-effect `import "./chat"` before `vt.init()` (bundler)
|
|
6
13
|
*/
|
|
7
14
|
import "./recorder";
|
|
8
15
|
import "./web-vitals";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core snippet plus chat — equivalent to loading `array.js` and `chat.js`
|
|
3
|
+
* together. Chat registers on `__VTiltExtensions__` at parse time so there is
|
|
4
|
+
* no second network request for `chat.js`.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* <script src="https://cdn.example.com/dist/array.chat.js"></script>
|
|
8
|
+
*/
|
|
9
|
+
import "./chat";
|
|
10
|
+
import "./array";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full IIFE bundle with chat pre-registered on `__VTiltExtensions__`.
|
|
3
|
+
*
|
|
4
|
+
* Use when the chat widget must be ready immediately (hero CTAs, programmatic
|
|
5
|
+
* `vt.sendChatMessage()` on first paint) without a second script request.
|
|
6
|
+
*
|
|
7
|
+
* PostHog equivalent: load `array.js` and side-effect-import `conversations.js`,
|
|
8
|
+
* or use their bundler pattern:
|
|
9
|
+
* import "posthog-js/dist/conversations"
|
|
10
|
+
*/
|
|
11
|
+
import "./all-external-dependencies";
|
|
12
|
+
import "./chat";
|
|
13
|
+
import "./array";
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Full
|
|
3
|
-
*
|
|
4
|
-
* Same as array.ts but includes all external dependencies bundled in.
|
|
5
|
-
* This is useful for users who want a single script tag without lazy loading.
|
|
2
|
+
* Full IIFE bundle — core SDK plus session recording and web vitals
|
|
3
|
+
* pre-registered on `__VTiltExtensions__`.
|
|
6
4
|
*
|
|
7
5
|
* Usage:
|
|
8
6
|
* <script src="https://cdn.example.com/dist/array.full.js"></script>
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Session recording (rrweb)
|
|
14
|
-
* - Web Vitals tracking
|
|
8
|
+
* Does **not** include chat (PostHog keeps conversations out of `array.full` too).
|
|
9
|
+
* For chat without a second request, use `array.chat.js` (core + chat) or
|
|
10
|
+
* `array.full.chat.js` (also includes recorder and web vitals).
|
|
15
11
|
*/
|
|
16
12
|
import "./all-external-dependencies";
|
|
17
13
|
import "./array";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full IIFE bundle with external script loading disabled at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Same as `array.full.ts` but intended for use with
|
|
5
|
+
* `disable_external_dependency_loading: true` — extensions are already on
|
|
6
|
+
* `__VTiltExtensions__`. PostHog equivalent: `array.full.no-external.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Does not include chat; use `array.full.chat.no-external.ts` when chat must
|
|
9
|
+
* be pre-registered without a separate network request.
|
|
10
|
+
*/
|
|
11
|
+
import "./all-external-dependencies";
|
|
12
|
+
import "./array.no-external";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full ESM bundle with chat pre-registered — no extra `chat.js` fetch.
|
|
3
|
+
*
|
|
4
|
+
* PostHog equivalent:
|
|
5
|
+
* import "posthog-js/dist/conversations"
|
|
6
|
+
* import posthog from "posthog-js/dist/module.no-external"
|
|
7
|
+
*/
|
|
8
|
+
import "./all-external-dependencies";
|
|
9
|
+
import "./chat";
|
|
10
|
+
import vt from "./module.es";
|
|
11
|
+
export * from "./module.es";
|
|
12
|
+
export default vt;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full ESM bundle — core SDK plus recorder and web-vitals pre-registered on
|
|
3
|
+
* `__VTiltExtensions__`. Same role as PostHog's `module.full.es.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Chat is not included (see `module.full.chat.es.ts` or side-effect-import
|
|
6
|
+
* `./chat` before init). Matches PostHog keeping `conversations` out of the
|
|
7
|
+
* default full bundle because of bundle size.
|
|
8
|
+
*/
|
|
9
|
+
import "./all-external-dependencies";
|
|
10
|
+
import vt from "./module.es";
|
|
11
|
+
export * from "./module.es";
|
|
12
|
+
export default vt;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { VTilt } from "../vtilt";
|
|
2
2
|
export * from "../types";
|
|
3
|
+
export { VTILT_SNIPPET_STUB_METHODS, VTILT_SNIPPET_STUB_METHOD_NAMES, type VtiltSnippetStubMethodName, } from "../snippet-stub-methods";
|
|
3
4
|
export declare const vt: import("../vtilt").VTilt;
|
|
4
5
|
export default vt;
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
interface DraggableOptions {
|
|
9
9
|
/** Element that initiates the drag (defaults to the target element itself) */
|
|
10
10
|
handle?: HTMLElement;
|
|
11
|
+
/**
|
|
12
|
+
* Fired after the element settles at a new position: initial restore
|
|
13
|
+
* from localStorage, drag end, and viewport resize re-clamp. The chat
|
|
14
|
+
* widget uses this to recompute the panel's opening direction so the
|
|
15
|
+
* panel always extends into whichever side of the viewport has more
|
|
16
|
+
* room (see `usePanelAnchor` in `ui/ChatRoot.tsx`).
|
|
17
|
+
*/
|
|
18
|
+
onPositionChange?: () => void;
|
|
11
19
|
}
|
|
12
20
|
interface DragHandle {
|
|
13
21
|
destroy: () => void;
|
|
@@ -15,13 +23,20 @@ interface DragHandle {
|
|
|
15
23
|
/**
|
|
16
24
|
* Make a fixed-position element draggable.
|
|
17
25
|
*
|
|
18
|
-
* @param el
|
|
19
|
-
* @param opts.handle
|
|
20
|
-
*
|
|
21
|
-
*
|
|
26
|
+
* @param el The element to reposition via translate3d.
|
|
27
|
+
* @param opts.handle Optional drag handle element (e.g. bubble
|
|
28
|
+
* inside a container). Pointer events listen
|
|
29
|
+
* on the handle; transform applies to `el`.
|
|
30
|
+
* Defaults to `el` itself.
|
|
31
|
+
* @param opts.onPositionChange Called after the element settles —
|
|
32
|
+
* initial restore, drag end, and viewport
|
|
33
|
+
* resize re-clamp. Used by the chat widget
|
|
34
|
+
* to recompute the panel's opening
|
|
35
|
+
* direction.
|
|
22
36
|
*
|
|
23
37
|
* - Restores persisted position from localStorage on attach.
|
|
24
|
-
* - Persists position on drag end
|
|
38
|
+
* - Persists position on drag end (and on resize when the clamp had to
|
|
39
|
+
* correct an out-of-bounds value, so the next page load is consistent).
|
|
25
40
|
* - Distinguishes click from drag via a 5px movement threshold.
|
|
26
41
|
* When dragged, the subsequent `click` event on the handle is suppressed once.
|
|
27
42
|
* - Re-clamps on viewport resize.
|
|
@@ -50,6 +50,7 @@ export declare class ChatWrapper extends LazyFeature<ChatConfig, LazyLoadedChatI
|
|
|
50
50
|
private _connectionCallbacks;
|
|
51
51
|
/** True after vt.showChat() so preloaded widget stays visible after replacing the lightweight bubble. */
|
|
52
52
|
private _bubbleExplicitShow;
|
|
53
|
+
private _unreadPollIntervalId;
|
|
53
54
|
constructor(instance: VTilt, config?: ChatConfig);
|
|
54
55
|
static extractConfig(config: VTiltConfig): ChatConfig;
|
|
55
56
|
get isEnabled(): boolean;
|
|
@@ -79,19 +80,24 @@ export declare class ChatWrapper extends LazyFeature<ChatConfig, LazyLoadedChatI
|
|
|
79
80
|
selectChannel(channelId: string): void;
|
|
80
81
|
createChannel(): void;
|
|
81
82
|
goToChannelList(): void;
|
|
82
|
-
sendMessage(content: SendChatMessageContent, options?: SendChatMessageOptions): void
|
|
83
|
+
sendMessage(content: SendChatMessageContent, options?: SendChatMessageOptions): Promise<void>;
|
|
83
84
|
markAsRead(): void;
|
|
84
85
|
registerWidget(definition: Parameters<NonNullable<LazyLoadedChatInterface["registerWidget"]>>[0]): void;
|
|
85
86
|
onMessage(callback: MessageCallback): Unsubscribe;
|
|
86
87
|
onTyping(callback: TypingCallback): Unsubscribe;
|
|
87
88
|
onConnectionChange(callback: ConnectionCallback): Unsubscribe;
|
|
88
|
-
|
|
89
|
+
private _isBubbleDraggable;
|
|
89
90
|
getMergedConfig(): ChatConfig;
|
|
91
|
+
/** Apply corner + inset to the lightweight bubble and/or loaded shadow host. */
|
|
92
|
+
private _syncBubbleLayout;
|
|
90
93
|
destroy(): void;
|
|
91
94
|
private _fetchServerSettings;
|
|
92
95
|
private _fetchUnreadForBubble;
|
|
93
96
|
private _showBubble;
|
|
94
97
|
private _onVisibilityChange;
|
|
98
|
+
private _shouldPollUnread;
|
|
99
|
+
private _startUnreadPoll;
|
|
100
|
+
private _stopUnreadPoll;
|
|
95
101
|
private _schedulePreload;
|
|
96
102
|
private _lazyLoadAndThen;
|
|
97
103
|
}
|
|
@@ -1,353 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Chat Widget
|
|
2
|
+
* Chat Widget — thin re-export shim.
|
|
3
|
+
*
|
|
4
|
+
* The entire chat implementation moved to a layered architecture as part of
|
|
5
|
+
* the May 2026 Preact rewrite. The previous monolithic ~3800-line class
|
|
6
|
+
* (`LazyLoadedChat`) is now decomposed into:
|
|
7
|
+
*
|
|
8
|
+
* - controller/ (chat-controller, ably-client, ably-handlers, ably-token,
|
|
9
|
+
* message-stream) — orchestration and Ably transport
|
|
10
|
+
* - store/ (chat-store) — signal-based reactive state
|
|
11
|
+
* - ui/ (ChatRoot.tsx + components + hooks)
|
|
12
|
+
* — Preact components rendered in a Shadow DOM
|
|
13
|
+
* - widgets/ (collect-email.tsx, escalate-to-human.tsx)
|
|
14
|
+
* — built-in tool widgets (Preact)
|
|
15
|
+
*
|
|
16
|
+
* See `docs/modules/chat/widget-architecture.md` for the full architecture.
|
|
17
|
+
*
|
|
18
|
+
* This file remains so external callers (`entrypoints/chat.ts`,
|
|
19
|
+
* `ChatWrapper`, tests) can keep importing `LazyLoadedChat` from
|
|
20
|
+
* `extensions/chat/chat` unchanged. New code should import the controller
|
|
21
|
+
* and store directly from their own modules.
|
|
3
22
|
*/
|
|
4
|
-
|
|
5
|
-
import { type ChatConfig, type ChatChannel, type ChatChannelSummary, type ChatWidgetView, type LazyLoadedChatInterface, type SendChatMessageContent, type SendChatMessageOptions } from "../../utils/globals";
|
|
6
|
-
import { type MessageCallback, type TypingCallback, type ConnectionCallback, type Unsubscribe } from "./types";
|
|
7
|
-
import { type WidgetContext } from "./widget-registry";
|
|
8
|
-
export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
9
|
-
private _instance;
|
|
10
|
-
private _config;
|
|
11
|
-
private _state;
|
|
12
|
-
private _container;
|
|
13
|
-
private _widget;
|
|
14
|
-
private _bubble;
|
|
15
|
-
private _bubbleDragHandle;
|
|
16
|
-
private _chatTheme;
|
|
17
|
-
private _widgetResizeCleanup;
|
|
18
|
-
private _ably;
|
|
19
|
-
private _notificationsChannel;
|
|
20
|
-
private _ablyChannel;
|
|
21
|
-
private _typingChannel;
|
|
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;
|
|
28
|
-
private _messageCallbacks;
|
|
29
|
-
private _typingCallbacks;
|
|
30
|
-
private _connectionCallbacks;
|
|
31
|
-
private _typingDebounce;
|
|
32
|
-
private _isUserTyping;
|
|
33
|
-
private _initialUserReadAt;
|
|
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;
|
|
47
|
-
constructor(instance: VTilt, config?: ChatConfig);
|
|
48
|
-
get isOpen(): boolean;
|
|
49
|
-
get isConnected(): boolean;
|
|
50
|
-
get isLoading(): boolean;
|
|
51
|
-
get unreadCount(): number;
|
|
52
|
-
get channel(): ChatChannel | null;
|
|
53
|
-
get channels(): ChatChannelSummary[];
|
|
54
|
-
get currentView(): ChatWidgetView;
|
|
55
|
-
private get _theme();
|
|
56
|
-
private get _distinctId();
|
|
57
|
-
/**
|
|
58
|
-
* Track a chat message event via the SDK capture pipeline.
|
|
59
|
-
* Gated by remote config chatTracking settings.
|
|
60
|
-
*
|
|
61
|
-
* - User messages: only if chatTracking.trackUserMessages is enabled
|
|
62
|
-
* - AI/Agent messages: only if chatTracking.trackAgentMessages is enabled
|
|
63
|
-
*/
|
|
64
|
-
private _trackChatMessage;
|
|
65
|
-
open(): void;
|
|
66
|
-
close(): void;
|
|
67
|
-
toggle(): void;
|
|
68
|
-
show(): void;
|
|
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;
|
|
90
|
-
getChannels(): Promise<void>;
|
|
91
|
-
selectChannel(channelId: string): Promise<void>;
|
|
92
|
-
createChannel(): Promise<void>;
|
|
93
|
-
goToChannelList(): Promise<void>;
|
|
94
|
-
sendMessage(content: SendChatMessageContent, options?: SendChatMessageOptions): Promise<void>;
|
|
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;
|
|
102
|
-
private _autoMarkAsRead;
|
|
103
|
-
private _isMessageReadByUser;
|
|
104
|
-
/**
|
|
105
|
-
* Calculate unread count based on messages and user_last_read_at
|
|
106
|
-
* (unread agent/AI messages for the user)
|
|
107
|
-
*/
|
|
108
|
-
private _calculateUnreadCount;
|
|
109
|
-
onMessage(callback: MessageCallback): Unsubscribe;
|
|
110
|
-
onTyping(callback: TypingCallback): Unsubscribe;
|
|
111
|
-
onConnectionChange(callback: ConnectionCallback): Unsubscribe;
|
|
112
|
-
destroy(): void;
|
|
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;
|
|
152
|
-
private _handleNewMessage;
|
|
153
|
-
private _handleTypingEvent;
|
|
154
|
-
private _handleReadCursorEvent;
|
|
155
|
-
private _notifyConnectionChange;
|
|
156
|
-
private _createUI;
|
|
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;
|
|
164
|
-
private _handleUserTyping;
|
|
165
|
-
private _sendTypingIndicator;
|
|
166
|
-
private _handleSend;
|
|
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;
|
|
193
|
-
private _updateHeader;
|
|
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;
|
|
253
|
-
private _getChannelItemHTML;
|
|
254
|
-
private _getConversationHTML;
|
|
255
|
-
private _attachConversationListeners;
|
|
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
|
-
*/
|
|
270
|
-
private _renderMessages;
|
|
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;
|
|
295
|
-
private _getMessageHTML;
|
|
296
|
-
private _isMessageReadByAgent;
|
|
297
|
-
/**
|
|
298
|
-
* Handle streaming AI response
|
|
299
|
-
* Industry standard: Real-time streaming with progressive UI updates
|
|
300
|
-
*/
|
|
301
|
-
private _handleStreamingResponse;
|
|
302
|
-
/**
|
|
303
|
-
* Remove temporary message by ID
|
|
304
|
-
*/
|
|
305
|
-
private _removeTempMessage;
|
|
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;
|
|
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;
|
|
352
|
-
private _formatTime;
|
|
353
|
-
}
|
|
23
|
+
export { ChatController as LazyLoadedChat } from "./controller/chat-controller";
|