@spacinbox/sdk 2.0.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/index.cjs ADDED
@@ -0,0 +1,1494 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/widget/state.ts
24
+ var import_signals, appId, visitorId, sessionToken, tokenExpiresAt, isOpen, currentUser, metadata, isOnline, isSetup, refetchSessionCounter;
25
+ var init_state = __esm({
26
+ "src/widget/state.ts"() {
27
+ "use strict";
28
+ import_signals = require("@preact/signals");
29
+ appId = (0, import_signals.signal)(null);
30
+ visitorId = (0, import_signals.signal)(null);
31
+ sessionToken = (0, import_signals.signal)(null);
32
+ tokenExpiresAt = (0, import_signals.signal)(null);
33
+ isOpen = (0, import_signals.signal)(false);
34
+ currentUser = (0, import_signals.signal)(null);
35
+ metadata = (0, import_signals.signal)({});
36
+ isOnline = (0, import_signals.signal)(true);
37
+ isSetup = (0, import_signals.signal)(false);
38
+ refetchSessionCounter = (0, import_signals.signal)(0);
39
+ }
40
+ });
41
+
42
+ // src/widget/modules/api/mock.ts
43
+ function scheduleAutoClose(conversationId) {
44
+ const existing = autoCloseTimers.get(conversationId);
45
+ if (existing) clearTimeout(existing);
46
+ const timer = setTimeout(() => {
47
+ const conv = CONVERSATIONS.find((c) => c.id === conversationId);
48
+ if (conv) conv.status = "CLOSED";
49
+ autoCloseTimers.delete(conversationId);
50
+ }, 8e3);
51
+ autoCloseTimers.set(conversationId, timer);
52
+ }
53
+ var AGENTS, CONVERSATIONS, MESSAGES, autoCloseTimers, delay, mockService;
54
+ var init_mock = __esm({
55
+ "src/widget/modules/api/mock.ts"() {
56
+ "use strict";
57
+ AGENTS = [
58
+ { id: "agent-1", name: "Alice Martin", picture: void 0 },
59
+ { id: "agent-2", name: "Bob Chen", picture: void 0 }
60
+ ];
61
+ CONVERSATIONS = [
62
+ {
63
+ id: "conv-1",
64
+ agentId: AGENTS[1].id,
65
+ agent: AGENTS[1],
66
+ status: "OPEN",
67
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
68
+ unread_contact_count: 0
69
+ },
70
+ {
71
+ id: "conv-2",
72
+ agentId: AGENTS[0].id,
73
+ agent: AGENTS[0],
74
+ status: "OPEN",
75
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
76
+ unread_contact_count: 0
77
+ },
78
+ {
79
+ id: "conv-3",
80
+ agentId: AGENTS[1].id,
81
+ agent: AGENTS[1],
82
+ status: "OPEN",
83
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
84
+ unread_contact_count: 0
85
+ },
86
+ {
87
+ id: "conv-4",
88
+ agentId: AGENTS[0].id,
89
+ agent: AGENTS[0],
90
+ status: "OPEN",
91
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
92
+ unread_contact_count: 0
93
+ }
94
+ ];
95
+ MESSAGES = [];
96
+ autoCloseTimers = /* @__PURE__ */ new Map();
97
+ delay = (ms = 300) => new Promise((res) => setTimeout(res, ms));
98
+ mockService = {
99
+ async createSession(_dto) {
100
+ await delay();
101
+ return {
102
+ access_token: `mock-token-${Date.now()}`,
103
+ token_type: "Bearer",
104
+ expires_in: 3600,
105
+ visitor_id: _dto.visitorId
106
+ };
107
+ },
108
+ async getSession() {
109
+ return { count_notifications: 1 };
110
+ },
111
+ async identifyVisitor(_dto) {
112
+ await delay();
113
+ return { visitor_id: null, access_token: null };
114
+ },
115
+ async getAgents() {
116
+ await delay();
117
+ return AGENTS;
118
+ },
119
+ async getConversation(conversationId) {
120
+ await delay();
121
+ const conv = CONVERSATIONS.find((c) => c.id === conversationId);
122
+ if (!conv) throw new Error(`Conversation ${conversationId} not found`);
123
+ return conv;
124
+ },
125
+ async reopenConversation(conversationId) {
126
+ await delay();
127
+ const conv = CONVERSATIONS.find((c) => c.id === conversationId);
128
+ if (conv) conv.status = "OPEN";
129
+ },
130
+ async getConversationSocketToken(conversationId) {
131
+ await delay();
132
+ return { access_token: `mock-conv-socket-token-${conversationId}-${Date.now()}` };
133
+ },
134
+ async createConversation(dto) {
135
+ await delay();
136
+ const conversation = {
137
+ id: `conv-${Date.now()}`,
138
+ agentId: dto.agentId,
139
+ status: "OPEN",
140
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
141
+ messages: [],
142
+ unread_contact_count: 0
143
+ };
144
+ if (dto.text) {
145
+ const message = {
146
+ id: `msg-${Date.now()}`,
147
+ sender: "user",
148
+ text: dto.text,
149
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
150
+ conversationId: conversation.id
151
+ };
152
+ conversation.messages?.push(message);
153
+ scheduleAutoClose(conversation.id);
154
+ }
155
+ CONVERSATIONS.push(conversation);
156
+ return conversation;
157
+ },
158
+ async getConversations() {
159
+ await delay();
160
+ return CONVERSATIONS.map((conv) => {
161
+ const all = [
162
+ ...conv.messages ?? [],
163
+ ...MESSAGES.filter((m) => m.conversationId === conv.id)
164
+ ];
165
+ const lastMessage = all.sort(
166
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
167
+ )[0];
168
+ return { ...conv, lastMessage };
169
+ });
170
+ },
171
+ async getMessages(conversationId, _params) {
172
+ await delay();
173
+ const messages = MESSAGES.filter((m) => m.conversationId === conversationId);
174
+ return { messages, hasMore: false, nextCursor: null };
175
+ },
176
+ async sendMessage(conversationId, dto) {
177
+ await delay();
178
+ const message = {
179
+ id: `msg-${Date.now()}`,
180
+ conversationId,
181
+ text: dto.text,
182
+ sender: "user",
183
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
184
+ };
185
+ MESSAGES.push(message);
186
+ scheduleAutoClose(conversationId);
187
+ return message;
188
+ }
189
+ };
190
+ }
191
+ });
192
+
193
+ // src/widget/modules/visitor/service.ts
194
+ var service_exports = {};
195
+ __export(service_exports, {
196
+ VisitorService: () => VisitorService
197
+ });
198
+ var STORAGE_KEY, REFRESH_THRESHOLD_MS, VisitorService;
199
+ var init_service = __esm({
200
+ "src/widget/modules/visitor/service.ts"() {
201
+ "use strict";
202
+ init_state();
203
+ init_service2();
204
+ STORAGE_KEY = "spacinbox_vid";
205
+ REFRESH_THRESHOLD_MS = 6e4;
206
+ VisitorService = {
207
+ async init() {
208
+ let id = localStorage.getItem(STORAGE_KEY);
209
+ if (!id) {
210
+ id = crypto.randomUUID();
211
+ localStorage.setItem(STORAGE_KEY, id);
212
+ }
213
+ visitorId.value = id;
214
+ await this.refreshSession(id);
215
+ isSetup.value = true;
216
+ },
217
+ async refreshSession(visitor_id = visitorId.value) {
218
+ if (!visitor_id) {
219
+ return;
220
+ }
221
+ try {
222
+ const response = await ApiService.createSession({ visitorId: visitor_id });
223
+ sessionToken.value = response.access_token;
224
+ visitorId.value = response.visitor_id;
225
+ localStorage.setItem(STORAGE_KEY, response.visitor_id);
226
+ tokenExpiresAt.value = Date.now() + response.expires_in * 1e3;
227
+ } catch (err) {
228
+ isOnline.value = false;
229
+ throw err;
230
+ }
231
+ },
232
+ isTokenExpiringSoon() {
233
+ if (!tokenExpiresAt.value) return true;
234
+ return tokenExpiresAt.value - Date.now() < REFRESH_THRESHOLD_MS;
235
+ },
236
+ identify(user) {
237
+ ApiService.identifyVisitor({
238
+ externalId: user.userId,
239
+ name: user.name,
240
+ email: user.email
241
+ }).then(({ visitor_id, access_token }) => {
242
+ if (access_token) {
243
+ sessionToken.value = access_token;
244
+ }
245
+ if (visitor_id && visitor_id !== visitorId.value) {
246
+ visitorId.value = visitor_id;
247
+ localStorage.setItem(STORAGE_KEY, visitor_id);
248
+ }
249
+ });
250
+ },
251
+ clear() {
252
+ localStorage.removeItem(STORAGE_KEY);
253
+ visitorId.value = null;
254
+ sessionToken.value = null;
255
+ tokenExpiresAt.value = null;
256
+ isSetup.value = false;
257
+ }
258
+ };
259
+ }
260
+ });
261
+
262
+ // src/widget/modules/api/service.ts
263
+ async function doFetch(method, path, body) {
264
+ if (!appId.value) throw new Error("Spacinbox: boot() must be called before any API request");
265
+ const headers = {
266
+ "Content-Type": "application/json",
267
+ "x-app-id": appId.value,
268
+ "x-visitor-id": visitorId.value ?? ""
269
+ };
270
+ if (sessionToken.value) {
271
+ headers["Authorization"] = `Bearer ${sessionToken.value}`;
272
+ }
273
+ return fetch(`${BASE_URL}/widget${path}`, {
274
+ method,
275
+ headers,
276
+ body: body ? JSON.stringify(body) : void 0
277
+ });
278
+ }
279
+ async function request(method, path, body) {
280
+ if (path !== "/sessions") {
281
+ const { VisitorService: VisitorService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
282
+ if (VisitorService2.isTokenExpiringSoon()) {
283
+ await VisitorService2.refreshSession();
284
+ }
285
+ }
286
+ let res = await doFetch(method, path, body);
287
+ if (res.status === 401 && path !== "/sessions") {
288
+ const { VisitorService: VisitorService2 } = await Promise.resolve().then(() => (init_service(), service_exports));
289
+ await VisitorService2.refreshSession();
290
+ res = await doFetch(method, path, body);
291
+ }
292
+ if (!res.ok) throw new Error(`[Spacinbox] ${method} ${path} failed with status ${res.status}`);
293
+ return res.json();
294
+ }
295
+ var BASE_URL, IS_MOCKED, ApiService;
296
+ var init_service2 = __esm({
297
+ "src/widget/modules/api/service.ts"() {
298
+ "use strict";
299
+ init_state();
300
+ init_mock();
301
+ BASE_URL = "https://api.spacinbox.com";
302
+ IS_MOCKED = false;
303
+ ApiService = IS_MOCKED ? mockService : {
304
+ createSession(dto) {
305
+ return request("POST", "/sessions", dto);
306
+ },
307
+ getSession() {
308
+ return request("GET", "/sessions");
309
+ },
310
+ identifyVisitor(dto) {
311
+ return request("POST", "/visitors/identify", dto);
312
+ },
313
+ getAgents() {
314
+ return request("GET", "/agents");
315
+ },
316
+ getConversations() {
317
+ return request("GET", "/conversations");
318
+ },
319
+ createConversation(dto) {
320
+ return request("POST", "/conversations", dto);
321
+ },
322
+ getConversation(conversationId) {
323
+ return request("GET", `/conversations/${conversationId}`);
324
+ },
325
+ reopenConversation(conversationId) {
326
+ return request("POST", `/conversations/${conversationId}/reopen`);
327
+ },
328
+ getConversationSocketToken(conversationId) {
329
+ return request("GET", `/conversations/${conversationId}/token`);
330
+ },
331
+ getMessages(conversationId, params) {
332
+ const qs = new URLSearchParams();
333
+ if (params?.before) qs.set("before", params.before);
334
+ if (params?.limit) qs.set("limit", String(params.limit));
335
+ const query = qs.toString();
336
+ return request(
337
+ "GET",
338
+ `/conversations/${conversationId}/messages${query ? `?${query}` : ""}`
339
+ );
340
+ },
341
+ sendMessage(conversationId, dto) {
342
+ return request("POST", `/conversations/${conversationId}/messages`, dto);
343
+ }
344
+ };
345
+ }
346
+ });
347
+
348
+ // src/index.ts
349
+ var index_exports = {};
350
+ __export(index_exports, {
351
+ default: () => index_default
352
+ });
353
+ module.exports = __toCommonJS(index_exports);
354
+
355
+ // src/widget/mount.tsx
356
+ var import_preact2 = require("preact");
357
+
358
+ // src/widget/Widget.tsx
359
+ var import_lucide_preact6 = require("lucide-preact");
360
+ var import_hooks12 = require("preact/hooks");
361
+
362
+ // src/widget/components/Layout.tsx
363
+ init_state();
364
+
365
+ // src/widget/ui/Loader.tsx
366
+ var import_lucide_preact = require("lucide-preact");
367
+ var import_jsx_runtime = require("preact/jsx-runtime");
368
+ var Loader = ({ size = 20, class: cls }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_preact.Loader2, { size, class: `animate-spin text-inherit ${cls ?? ""}` });
369
+
370
+ // src/widget/components/Layout.tsx
371
+ var import_jsx_runtime2 = require("preact/jsx-runtime");
372
+ var Layout = ({ isOpen: isOpen2, children }) => {
373
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
374
+ "div",
375
+ {
376
+ class: `fixed bottom-20 right-4 w-80 h-112 bg-white rounded-xl shadow-2xl border border-gray-200 flex flex-col overflow-hidden ${!isOpen2 && "hidden"}`,
377
+ children: [
378
+ !isOnline.value && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { class: "bg-amber-50 border-b border-amber-200 px-3 py-2 text-xs text-amber-700 text-center", children: [
379
+ "Connecting... ",
380
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Loader, {})
381
+ ] }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { class: "flex-1 overflow-y-auto", children })
383
+ ]
384
+ }
385
+ );
386
+ };
387
+
388
+ // src/widget/hooks/useNewMessage.tsx
389
+ var import_hooks = require("preact/hooks");
390
+
391
+ // src/widget/modules/socket/service.ts
392
+ var import_signals2 = require("@preact/signals");
393
+ var import_socket = require("socket.io-client");
394
+ init_state();
395
+ init_service2();
396
+ init_service();
397
+ var socket = (0, import_signals2.signal)(null);
398
+ var SocketService = {
399
+ async connect() {
400
+ const token = sessionToken.value;
401
+ socket.value = (0, import_socket.io)("https://ws.spacinbox.com", {
402
+ auth: { token },
403
+ reconnection: true
404
+ });
405
+ socket.value.on("connect", () => {
406
+ isOnline.value = true;
407
+ });
408
+ socket.value.on("disconnect", (reason) => {
409
+ if (reason !== "io client disconnect") {
410
+ isOnline.value = false;
411
+ }
412
+ });
413
+ socket.value.on("connect_error", async (err) => {
414
+ if (err.message === "Missing token" || err.message === "Invalid token") {
415
+ await VisitorService.refreshSession();
416
+ if (socket.value) {
417
+ socket.value.auth = { token: sessionToken.value };
418
+ socket.value.connect();
419
+ }
420
+ } else {
421
+ isOnline.value = false;
422
+ }
423
+ });
424
+ },
425
+ async joinRoom(conversationId) {
426
+ if (!socket.value) {
427
+ return;
428
+ }
429
+ const { access_token: token } = await ApiService.getConversationSocketToken(conversationId);
430
+ return new Promise((resolve, reject) => {
431
+ socket.value.emit("join-room", { token }, (response) => {
432
+ if (!response?.ok) {
433
+ reject(new Error());
434
+ } else {
435
+ resolve();
436
+ }
437
+ });
438
+ });
439
+ },
440
+ leaveRoom(conversationId) {
441
+ socket.value?.emit("leave-room", { room_id: `conversation:${conversationId}` });
442
+ },
443
+ typingStart(conversationId) {
444
+ socket.value?.emit("typing-start", { room_id: `conversation:${conversationId}` });
445
+ },
446
+ typingStop(conversationId) {
447
+ socket.value?.emit("typing-stop", { room_id: `conversation:${conversationId}` });
448
+ },
449
+ on(event, handler) {
450
+ socket.value?.on(event, handler);
451
+ },
452
+ off(event, handler) {
453
+ socket.value?.off(event, handler);
454
+ },
455
+ disconnect() {
456
+ socket.value?.disconnect();
457
+ socket.value = null;
458
+ }
459
+ };
460
+
461
+ // src/widget/hooks/useNewMessage.tsx
462
+ var useOnNewMessage = (handler) => {
463
+ (0, import_hooks.useEffect)(() => {
464
+ if (!socket.value) {
465
+ return;
466
+ }
467
+ SocketService.on("new-message", () => handler());
468
+ return () => {
469
+ SocketService.off("new-message", () => handler());
470
+ };
471
+ }, [socket.value]);
472
+ };
473
+
474
+ // src/widget/hooks/useQuery.ts
475
+ var import_hooks2 = require("preact/hooks");
476
+ var cache = /* @__PURE__ */ new Map();
477
+ var invalidateQuery = (queryKey) => cache.delete(serializeKey(queryKey));
478
+ function serializeKey(key) {
479
+ return typeof key === "string" ? key : JSON.stringify(key);
480
+ }
481
+ function useQuery({ queryKey, queryFn, enabled = true, cache: useCache = true, onSuccess, onError, onSettled }) {
482
+ const key = serializeKey(queryKey);
483
+ const cached = useCache ? cache.get(key) : void 0;
484
+ const [refetchCounter, setRefetchCounter] = (0, import_hooks2.useState)(0);
485
+ const [state, setState] = (0, import_hooks2.useState)(
486
+ cached !== void 0 ? { data: cached, loading: false, error: null, isSuccess: true, isFetched: true } : { data: null, loading: enabled, error: null, isSuccess: false, isFetched: false }
487
+ );
488
+ (0, import_hooks2.useEffect)(() => {
489
+ if (!enabled) return;
490
+ let cancelled = false;
491
+ const isRefetch = refetchCounter > 0;
492
+ if (cached === void 0 || isRefetch) {
493
+ setState((s) => ({ ...s, loading: true, error: null, isSuccess: false }));
494
+ }
495
+ queryFn().then((data) => {
496
+ if (cancelled) return;
497
+ if (useCache) cache.set(key, data);
498
+ setState({ data, loading: false, error: null, isSuccess: true, isFetched: true });
499
+ onSuccess?.(data);
500
+ onSettled?.(data, null);
501
+ }).catch((error) => {
502
+ if (cancelled) return;
503
+ setState((s) => ({ ...s, loading: false, error, isSuccess: false, isFetched: true }));
504
+ onError?.(error);
505
+ onSettled?.(null, error);
506
+ });
507
+ return () => {
508
+ cancelled = true;
509
+ };
510
+ }, [key, enabled, refetchCounter]);
511
+ const refetch = (0, import_hooks2.useCallback)(() => setRefetchCounter((c) => c + 1), []);
512
+ return { ...state, refetch };
513
+ }
514
+
515
+ // src/widget/Widget.tsx
516
+ init_service2();
517
+
518
+ // src/widget/pages/conversation.tsx
519
+ var import_signals4 = require("@preact/signals");
520
+ var import_hooks10 = require("preact/hooks");
521
+
522
+ // src/widget/hooks/useMessages.ts
523
+ var import_hooks3 = require("preact/hooks");
524
+ init_service2();
525
+ function useMessages(conversationId, limit = 30) {
526
+ const [state, setState] = (0, import_hooks3.useState)({
527
+ messages: [],
528
+ hasMore: false,
529
+ nextCursor: null,
530
+ loading: !!conversationId,
531
+ loadingMore: false
532
+ });
533
+ const stateRef = (0, import_hooks3.useRef)(state);
534
+ stateRef.current = state;
535
+ (0, import_hooks3.useEffect)(() => {
536
+ if (!conversationId) return;
537
+ let cancelled = false;
538
+ setState({ messages: [], hasMore: false, nextCursor: null, loading: true, loadingMore: false });
539
+ ApiService.getMessages(conversationId, { limit }).then(({ messages, hasMore, nextCursor }) => {
540
+ if (cancelled) {
541
+ return;
542
+ }
543
+ setState({ messages, hasMore, nextCursor, loading: false, loadingMore: false });
544
+ }).catch(() => {
545
+ if (cancelled) return;
546
+ setState((s) => ({ ...s, loading: false }));
547
+ });
548
+ return () => {
549
+ cancelled = true;
550
+ };
551
+ }, [conversationId]);
552
+ const loadMore = (0, import_hooks3.useCallback)(async () => {
553
+ const { hasMore, nextCursor, loadingMore } = stateRef.current;
554
+ if (!hasMore || loadingMore || !nextCursor || !conversationId) return;
555
+ setState((s) => ({ ...s, loadingMore: true }));
556
+ try {
557
+ const {
558
+ messages: older,
559
+ hasMore: newHasMore,
560
+ nextCursor: newCursor
561
+ } = await ApiService.getMessages(conversationId, { before: nextCursor, limit });
562
+ setState((s) => ({
563
+ ...s,
564
+ messages: [...s.messages, ...older],
565
+ hasMore: newHasMore,
566
+ nextCursor: newCursor,
567
+ loadingMore: false
568
+ }));
569
+ } catch {
570
+ setState((s) => ({ ...s, loadingMore: false }));
571
+ }
572
+ }, [conversationId]);
573
+ const append = (0, import_hooks3.useCallback)((message) => {
574
+ setState((item) => {
575
+ if (item.messages.some((m) => m.id === message.id)) {
576
+ return item;
577
+ }
578
+ return { ...item, messages: [message, ...item.messages] };
579
+ });
580
+ }, []);
581
+ const replace = (0, import_hooks3.useCallback)((tempId, message) => {
582
+ setState((s) => ({
583
+ ...s,
584
+ messages: s.messages.filter((m) => m.id !== message.id).map((m) => m.id === tempId ? message : m)
585
+ }));
586
+ }, []);
587
+ const remove = (0, import_hooks3.useCallback)((id) => {
588
+ setState((s) => ({ ...s, messages: s.messages.filter((m) => m.id !== id) }));
589
+ }, []);
590
+ return { ...state, loadMore, append, replace, remove };
591
+ }
592
+
593
+ // src/widget/hooks/useMutation.ts
594
+ var import_hooks4 = require("preact/hooks");
595
+ function useMutation({ mutationFn, onSuccess, onError, onSettled }) {
596
+ const [state, setState] = (0, import_hooks4.useState)({
597
+ data: null,
598
+ loading: false,
599
+ error: null,
600
+ isSuccess: false,
601
+ isFetched: false
602
+ });
603
+ const mutate = async (variables) => {
604
+ setState({ data: null, loading: true, error: null, isSuccess: false, isFetched: false });
605
+ try {
606
+ const data = await mutationFn(variables);
607
+ setState({ data, loading: false, error: null, isSuccess: true, isFetched: true });
608
+ onSuccess?.(data, variables);
609
+ onSettled?.(data, null, variables);
610
+ return data;
611
+ } catch (error) {
612
+ const err = error;
613
+ setState({ data: null, loading: false, error: err, isSuccess: false, isFetched: true });
614
+ onError?.(err, variables);
615
+ onSettled?.(null, err, variables);
616
+ }
617
+ };
618
+ return { ...state, mutate };
619
+ }
620
+
621
+ // src/widget/pages/conversation.tsx
622
+ init_service2();
623
+
624
+ // src/widget/modules/conversation/components/Header.tsx
625
+ var import_lucide_preact2 = require("lucide-preact");
626
+
627
+ // src/widget/router.ts
628
+ var import_signals3 = require("@preact/signals");
629
+ var pageStack = (0, import_signals3.signal)([{ page: "home" }]);
630
+ var currentRoute = (0, import_signals3.computed)(() => pageStack.value.at(-1));
631
+ var currentPage = (0, import_signals3.computed)(() => currentRoute.value.page);
632
+ var canGoBack = (0, import_signals3.computed)(() => pageStack.value.length > 1);
633
+ var navigate = (page, params) => {
634
+ pageStack.value = [...pageStack.value, { page, params }];
635
+ };
636
+ var goBack = () => {
637
+ if (pageStack.value.length > 1)
638
+ pageStack.value = pageStack.value.slice(0, -1);
639
+ };
640
+ var resetRouter = () => {
641
+ pageStack.value = [{ page: "home" }];
642
+ };
643
+
644
+ // src/widget/ui/Avatar.tsx
645
+ var import_jsx_runtime3 = require("preact/jsx-runtime");
646
+ var sizes = {
647
+ sm: "w-7 h-7 text-xs",
648
+ md: "w-9 h-9 text-sm",
649
+ lg: "w-12 h-12 text-base"
650
+ };
651
+ var Avatar = ({ src, children, size = "md", class: cls }) => {
652
+ const sizeClass = typeof size === "number" ? "" : sizes[size];
653
+ const sizeStyle = typeof size === "number" ? { width: `${size}px`, height: `${size}px`, fontSize: `${Math.round(size * 0.4)}px` } : void 0;
654
+ const base = `inline-flex items-center justify-center rounded-full overflow-hidden shrink-0 bg-blue-100 text-blue-700 font-medium border-2 border-blue-400 ${sizeClass} ${cls ?? ""}`;
655
+ if (src) {
656
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src, class: `${base} object-cover`, style: sizeStyle, alt: children ?? "" });
657
+ }
658
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { class: base, style: sizeStyle, children: children ? children.charAt(0).toUpperCase() : "?" });
659
+ };
660
+
661
+ // src/widget/ui/Button.tsx
662
+ var import_jsx_runtime4 = require("preact/jsx-runtime");
663
+ var variants = {
664
+ filled: "bg-blue-600 hover:bg-blue-700 text-white",
665
+ subtle: "bg-neutral-100 hover:bg-neutral-200 text-neutral-800",
666
+ light: "bg-transparent hover:text-neutral-800 text-neutral-500",
667
+ outline: "border border-neutral-200 hover:bg-neutral-50 text-neutral-800"
668
+ };
669
+ var Button = ({ children, onClick, variant = "filled", fullWidth, class: cls }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
670
+ "button",
671
+ {
672
+ onClick,
673
+ class: `
674
+ inline-flex items-center justify-center gap-2
675
+ px-4 py-2 rounded-lg text-sm font-medium
676
+ transition-colors cursor-pointer
677
+ ${variants[variant]}
678
+ ${fullWidth ? "w-full" : ""}
679
+ ${cls ?? ""}
680
+ `,
681
+ children
682
+ }
683
+ );
684
+
685
+ // src/widget/ui/Group.tsx
686
+ var import_jsx_runtime5 = require("preact/jsx-runtime");
687
+ var gaps = {
688
+ xs: "gap-1",
689
+ sm: "gap-2",
690
+ md: "gap-4",
691
+ lg: "gap-6"
692
+ };
693
+ var justifies = {
694
+ start: "justify-start",
695
+ center: "justify-center",
696
+ end: "justify-end",
697
+ between: "justify-between"
698
+ };
699
+ var aligns = {
700
+ start: "items-start",
701
+ center: "items-center",
702
+ end: "items-end"
703
+ };
704
+ var Group = ({ children, gap = "sm", justify = "start", align = "center", class: cls }) => {
705
+ const gapClass = typeof gap === "number" ? "" : gaps[gap];
706
+ const gapStyle = typeof gap === "number" ? { gap: `${gap * 4}px` } : {};
707
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { class: `flex flex-row ${gapClass} ${justifies[justify]} ${aligns[align]} ${cls ?? ""}`, style: gapStyle, children });
708
+ };
709
+
710
+ // src/widget/ui/Indicator.tsx
711
+ var import_jsx_runtime6 = require("preact/jsx-runtime");
712
+ var colors = {
713
+ default: "bg-blue-500",
714
+ green: "bg-green-500",
715
+ red: "bg-red-500"
716
+ };
717
+ var positions = {
718
+ "top-right": "top-0 right-0 translate-x-1/2 -translate-y-1/2",
719
+ "bottom-right": "bottom-0 right-0"
720
+ };
721
+ var sizes2 = {
722
+ sm: { dot: "w-2.5 h-2.5", badge: "h-4 min-w-4 text-[10px]" },
723
+ md: { dot: "w-3.5 h-3.5", badge: "h-5 min-w-5 text-xs" },
724
+ lg: { dot: "w-4 h-4", badge: "h-6 min-w-6 text-sm" }
725
+ };
726
+ var Indicator = ({
727
+ children,
728
+ active = true,
729
+ visible = true,
730
+ color = "default",
731
+ label,
732
+ size = "sm",
733
+ position = "bottom-right",
734
+ offset
735
+ }) => {
736
+ const show = active && visible;
737
+ const offsetStyle = offset ? { transform: `translate(calc(50% + ${offset.x ?? 0}px), calc(-50% + ${offset.y ?? 0}px))` } : void 0;
738
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { class: "relative inline-flex", children: [
739
+ children,
740
+ show && (label !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
741
+ "span",
742
+ {
743
+ style: offsetStyle,
744
+ class: `absolute ${positions[position]} ${sizes2[size].badge} px-1 ${colors[color]} text-white font-semibold rounded-full flex items-center justify-center`,
745
+ children: label
746
+ }
747
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
748
+ "span",
749
+ {
750
+ style: offsetStyle,
751
+ class: `absolute ${positions[position]} ${sizes2[size].dot} ${colors[color]} border-2 border-white rounded-full`
752
+ }
753
+ ))
754
+ ] });
755
+ };
756
+
757
+ // src/widget/ui/Text.tsx
758
+ var import_jsx_runtime7 = require("preact/jsx-runtime");
759
+ var sizes3 = {
760
+ xs: "text-xs",
761
+ sm: "text-sm",
762
+ md: "text-base",
763
+ lg: "text-lg",
764
+ xl: "text-xl"
765
+ };
766
+ var weights = {
767
+ normal: "font-normal",
768
+ medium: "font-medium",
769
+ semibold: "font-semibold"
770
+ };
771
+ var colors2 = {
772
+ default: "text-neutral-900",
773
+ dimmed: "text-neutral-400"
774
+ };
775
+ var Text = ({
776
+ children,
777
+ size = "sm",
778
+ weight = "normal",
779
+ color = "default",
780
+ lineClamp,
781
+ class: cls
782
+ }) => {
783
+ const clampStyle = lineClamp ? {
784
+ overflow: "hidden",
785
+ display: "-webkit-box",
786
+ WebkitLineClamp: lineClamp,
787
+ WebkitBoxOrient: "vertical"
788
+ } : void 0;
789
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { class: `${sizes3[size]} ${weights[weight]} ${colors2[color]} ${cls ?? ""}`, style: clampStyle, children });
790
+ };
791
+
792
+ // src/widget/modules/conversation/components/Header.tsx
793
+ var import_jsx_runtime8 = require("preact/jsx-runtime");
794
+ var Header = ({ agent, loading }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Group, { class: "border-b border-neutral-100 p-2", children: [
795
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { variant: "light", onClick: goBack, "aria-label": "Back", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_preact2.ArrowLeft, {}) }),
796
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { class: "w-8 h-8 rounded-full bg-neutral-200 animate-pulse shrink-0" }) : agent ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
797
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Indicator, { color: "green", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Avatar, { size: "sm", src: agent.picture, children: agent.name.charAt(0) }) }),
798
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { weight: "medium", children: agent.name })
799
+ ] }) : null
800
+ ] });
801
+
802
+ // src/widget/modules/conversation/components/InputMessage.tsx
803
+ var import_hooks5 = require("preact/hooks");
804
+ var import_lucide_preact3 = require("lucide-preact");
805
+ var import_jsx_runtime9 = require("preact/jsx-runtime");
806
+ var InputMessage = ({ onSend, onType, disabled }) => {
807
+ const ref = (0, import_hooks5.useRef)(null);
808
+ (0, import_hooks5.useEffect)(() => {
809
+ if (!disabled) ref.current?.focus();
810
+ }, [disabled]);
811
+ const resize = () => {
812
+ const el = ref.current;
813
+ if (!el) return;
814
+ el.style.height = "auto";
815
+ el.style.height = `${el.scrollHeight}px`;
816
+ };
817
+ const submit = () => {
818
+ const el = ref.current;
819
+ if (!el || disabled) return;
820
+ const text = el.value.trim();
821
+ if (!text) return;
822
+ onSend(text);
823
+ el.value = "";
824
+ el.style.height = "auto";
825
+ };
826
+ const onKeyDown = (e) => {
827
+ if (e.key !== "Enter") return;
828
+ if (e.metaKey || e.ctrlKey) {
829
+ e.preventDefault();
830
+ const el = ref.current;
831
+ const start = el.selectionStart ?? el.value.length;
832
+ const end = el.selectionEnd ?? el.value.length;
833
+ el.value = el.value.slice(0, start) + "\n" + el.value.slice(end);
834
+ el.selectionStart = el.selectionEnd = start + 1;
835
+ resize();
836
+ } else if (!e.shiftKey) {
837
+ e.preventDefault();
838
+ submit();
839
+ }
840
+ };
841
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { class: "flex items-end gap-2 px-3 py-3 border-t border-neutral-100", children: [
842
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
843
+ "textarea",
844
+ {
845
+ ref,
846
+ rows: 1,
847
+ placeholder: "Write a message...",
848
+ onInput: () => {
849
+ resize();
850
+ onType?.();
851
+ },
852
+ onKeyDown,
853
+ disabled,
854
+ class: "flex-1 text-sm bg-neutral-100 rounded-2xl px-4 py-2 outline-none placeholder:text-neutral-400 disabled:opacity-50 resize-none overflow-y-auto leading-5",
855
+ style: { maxHeight: "120px" }
856
+ }
857
+ ),
858
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
859
+ "button",
860
+ {
861
+ onClick: submit,
862
+ disabled,
863
+ class: "w-8 h-8 flex items-center justify-center bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-colors cursor-pointer shrink-0 disabled:opacity-50 disabled:cursor-not-allowed mb-0.5",
864
+ "aria-label": "Send",
865
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_preact3.Send, { size: 14 })
866
+ }
867
+ )
868
+ ] });
869
+ };
870
+
871
+ // src/widget/modules/conversation/components/MessageList.tsx
872
+ var import_preact = require("preact");
873
+ var import_hooks6 = require("preact/hooks");
874
+
875
+ // src/widget/modules/conversation/components/DateSeparator.tsx
876
+ var import_jsx_runtime10 = require("preact/jsx-runtime");
877
+ function formatLabel(dateStr) {
878
+ const date = new Date(dateStr);
879
+ const today = /* @__PURE__ */ new Date();
880
+ const yesterday = new Date(today);
881
+ yesterday.setDate(today.getDate() - 1);
882
+ const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
883
+ if (sameDay(date, today)) return "Today";
884
+ if (sameDay(date, yesterday)) return "Yesterday";
885
+ return date.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" }).replace(",", "");
886
+ }
887
+ var DateSeparator = ({ date }) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { class: "flex items-center gap-2 my-3", children: [
888
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { class: "flex-1 h-px bg-neutral-200" }),
889
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { class: "text-xs text-neutral-400 font-medium px-1", children: formatLabel(date) }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { class: "flex-1 h-px bg-neutral-200" })
891
+ ] });
892
+
893
+ // src/widget/modules/conversation/components/MessageBubble.tsx
894
+ var import_jsx_runtime11 = require("preact/jsx-runtime");
895
+ function formatTime(dateStr) {
896
+ return new Date(dateStr).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
897
+ }
898
+ var MessageBubble = ({ message, agent, isFirstInGroup }) => {
899
+ const isUser = message.sender === "user";
900
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { class: `flex gap-2 items-end ${isUser ? "flex-row-reverse" : "flex-row"}`, children: [
901
+ !isUser && (isFirstInGroup ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { class: "w-7 h-7 rounded-full bg-neutral-200 shrink-0 overflow-hidden flex items-center justify-center text-xs font-semibold text-neutral-600 self-end mb-0.5", children: agent?.picture ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: agent.picture, alt: agent.name, class: "w-full h-full object-cover" }) : agent?.name?.charAt(0) ?? "?" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { class: "w-7 shrink-0" })),
902
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { class: `flex flex-col gap-0.5 max-w-[72%] ${isUser ? "items-end" : "items-start"}`, children: [
903
+ !isUser && isFirstInGroup && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { class: "text-xs text-neutral-500 font-semibold px-1", children: agent?.name }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { class: `relative px-3 pt-2 pb-5 min-w-18 rounded-2xl text-sm leading-snug whitespace-pre-wrap ${isUser ? "bg-blue-600 text-white rounded-br-sm" : "bg-neutral-100 text-neutral-900 rounded-bl-sm"}`, children: [
905
+ message.text,
906
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { class: `absolute bottom-1.5 right-2.5 text-[10px] leading-none ${isUser ? "text-blue-200" : "text-neutral-400"}`, children: formatTime(message.createdAt) })
907
+ ] })
908
+ ] })
909
+ ] });
910
+ };
911
+
912
+ // src/widget/modules/conversation/components/MessageList.tsx
913
+ var import_jsx_runtime12 = require("preact/jsx-runtime");
914
+ function isSameDay(a, b) {
915
+ return new Date(a).toDateString() === new Date(b).toDateString();
916
+ }
917
+ var MessageList = ({
918
+ messages,
919
+ agent,
920
+ scrollKey,
921
+ hasMore,
922
+ loadingMore,
923
+ onLoadMore,
924
+ isTyping
925
+ }) => {
926
+ const containerRef = (0, import_hooks6.useRef)(null);
927
+ (0, import_hooks6.useEffect)(() => {
928
+ const container = containerRef.current;
929
+ if (!container) {
930
+ return;
931
+ }
932
+ const onScroll = () => {
933
+ const canTrigger = Math.abs(container.scrollTop) > container.scrollHeight - container.clientHeight - 100;
934
+ if (hasMore && !loadingMore && canTrigger) {
935
+ onLoadMore?.();
936
+ }
937
+ };
938
+ container.addEventListener("scroll", onScroll, { passive: true });
939
+ return () => container.removeEventListener("scroll", onScroll);
940
+ }, [hasMore, loadingMore, onLoadMore]);
941
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { class: "flex-1 flex flex-col overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { ref: containerRef, class: "flex-1 overflow-y-auto flex flex-col-reverse gap-1 px-3 py-4", children: [
942
+ isTyping && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { class: "flex gap-2 items-end", children: [
943
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { class: "w-7 h-7 rounded-full bg-neutral-200 shrink-0 overflow-hidden flex items-center justify-center text-xs font-semibold text-neutral-600 self-end mb-0.5", children: agent?.picture ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: agent.picture, alt: agent.name, class: "w-full h-full object-cover" }) : agent?.name?.charAt(0) ?? "?" }),
944
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { class: "bg-neutral-100 rounded-2xl rounded-bl-sm px-3 py-2.5 flex gap-1 items-center", children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { class: "w-1.5 h-1.5 rounded-full bg-neutral-400 animate-bounce", style: { animationDelay: "0ms" } }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { class: "w-1.5 h-1.5 rounded-full bg-neutral-400 animate-bounce", style: { animationDelay: "150ms" } }),
947
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { class: "w-1.5 h-1.5 rounded-full bg-neutral-400 animate-bounce", style: { animationDelay: "300ms" } })
948
+ ] })
949
+ ] }),
950
+ messages.map((msg, i) => {
951
+ const next = messages[i + 1];
952
+ const showDate = !next || !isSameDay(next.createdAt, msg.createdAt);
953
+ const isFirstInGroup = showDate || next?.sender !== msg.sender;
954
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_preact.Fragment, { children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageBubble, { message: msg, agent, isFirstInGroup }),
956
+ showDate && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DateSeparator, { date: msg.createdAt })
957
+ ] }, msg.id);
958
+ }),
959
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
960
+ "div",
961
+ {
962
+ class: `flex justify-center py-2 border-b border-neutral-100 ${!loadingMore && "opacity-0"}`,
963
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Loader, { size: 16 })
964
+ }
965
+ )
966
+ ] }) });
967
+ };
968
+
969
+ // src/widget/modules/conversation/components/ResolvedBanner.tsx
970
+ var import_lucide_preact4 = require("lucide-preact");
971
+ var import_hooks7 = require("preact/hooks");
972
+
973
+ // src/widget/ui/Stack.tsx
974
+ var import_jsx_runtime13 = require("preact/jsx-runtime");
975
+ var gaps2 = {
976
+ xs: "gap-1",
977
+ sm: "gap-2",
978
+ md: "gap-4",
979
+ lg: "gap-6"
980
+ };
981
+ var aligns2 = {
982
+ start: "items-start",
983
+ center: "items-center",
984
+ end: "items-end",
985
+ stretch: "items-stretch"
986
+ };
987
+ var justifies2 = {
988
+ start: "justify-start",
989
+ center: "justify-center",
990
+ end: "justify-end",
991
+ between: "justify-between"
992
+ };
993
+ var Stack = ({ children, gap = "md", align, justify, class: cls }) => {
994
+ const gapClass = gaps2[gap] ?? `gap-${gap}`;
995
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { class: `flex flex-col ${gapClass} ${align ? aligns2[align] : ""} ${justify ? justifies2[justify] : ""} ${cls ?? ""}`, children });
996
+ };
997
+
998
+ // src/widget/modules/conversation/components/ResolvedBanner.tsx
999
+ var import_jsx_runtime14 = require("preact/jsx-runtime");
1000
+ var ResolvedBanner = ({ onYes, onNo }) => {
1001
+ const [thanked, setThanked] = (0, import_hooks7.useState)(false);
1002
+ (0, import_hooks7.useEffect)(() => {
1003
+ if (!thanked) return;
1004
+ const timer = setTimeout(onYes, 3e3);
1005
+ return () => clearTimeout(timer);
1006
+ }, [thanked]);
1007
+ if (thanked) {
1008
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Stack, { class: "border-t border-neutral-100 p-4 min-h-[175px]", align: "center", children: [
1009
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_preact4.CheckCircle, { size: 20, class: "text-green-500" }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { size: "sm", weight: "semibold", children: "Glad we could help" }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { size: "sm", color: "dimmed", children: "See you next time" })
1012
+ ] });
1013
+ }
1014
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Stack, { class: "border-t border-neutral-100 p-4 min-h-[175px]", align: "center", children: [
1015
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_preact4.CheckCircle, { size: 20, class: "text-green-500" }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Stack, { gap: 0, align: "center", children: [
1017
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { size: "sm", children: "This conversation has been resolved" }),
1018
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { size: "sm", color: "dimmed", children: "Did we answer your question?" })
1019
+ ] }),
1020
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Group, { class: "w-full", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Button, { class: "flex-1", variant: "outline", fullWidth: true, onClick: onNo, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Stack, { gap: 0, children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { children: "No" }),
1023
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { size: "xs", color: "dimmed", children: "I need more help" })
1024
+ ] }) }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Button, { class: "flex-1", onClick: () => setThanked(true), children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Stack, { gap: 0, children: [
1026
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { class: "text-white", weight: "semibold", children: "Yes" }),
1027
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { size: "xs", class: "text-white", children: "Thanks" })
1028
+ ] }) })
1029
+ ] })
1030
+ ] });
1031
+ };
1032
+
1033
+ // src/widget/modules/socket/useOnTyping.ts
1034
+ var import_hooks8 = require("preact/hooks");
1035
+ var TYPING_TIMEOUT_MS = 1e4;
1036
+ var useOnTyping = ({ room_id }) => {
1037
+ const [isTyping, setIsTyping] = (0, import_hooks8.useState)(false);
1038
+ const stopTimerRef = (0, import_hooks8.useRef)(null);
1039
+ const clearStopTimer = () => {
1040
+ if (stopTimerRef.current) {
1041
+ clearTimeout(stopTimerRef.current);
1042
+ stopTimerRef.current = null;
1043
+ }
1044
+ };
1045
+ const stopTyping = () => {
1046
+ clearStopTimer();
1047
+ setIsTyping(false);
1048
+ };
1049
+ (0, import_hooks8.useEffect)(() => {
1050
+ if (!socket.value) {
1051
+ return;
1052
+ }
1053
+ const handlerStart = (payload) => {
1054
+ const canSkip = room_id && !payload.room_id.includes(room_id);
1055
+ if (canSkip) {
1056
+ return;
1057
+ }
1058
+ setIsTyping(true);
1059
+ clearStopTimer();
1060
+ stopTimerRef.current = setTimeout(stopTyping, TYPING_TIMEOUT_MS);
1061
+ };
1062
+ const handlerStop = (payload) => {
1063
+ const canSkip = room_id && !payload.room_id.includes(room_id);
1064
+ if (canSkip) {
1065
+ return;
1066
+ }
1067
+ stopTyping();
1068
+ };
1069
+ socket.value.on("typing-start", handlerStart);
1070
+ socket.value.on("typing-stop", handlerStop);
1071
+ return () => {
1072
+ socket.value?.off("typing-start", handlerStart);
1073
+ socket.value?.off("typing-stop", handlerStop);
1074
+ clearStopTimer();
1075
+ };
1076
+ }, [room_id, socket]);
1077
+ return { isTyping };
1078
+ };
1079
+
1080
+ // src/widget/modules/socket/useTyping.ts
1081
+ var import_hooks9 = require("preact/hooks");
1082
+ var TYPING_START_DELAY_MS = 3e3;
1083
+ var TYPING_HEARTBEAT_MS = 5e3;
1084
+ var TYPING_STOP_DELAY_MS = 5e3;
1085
+ var useTyping = ({ room_id }) => {
1086
+ const startTimerRef = (0, import_hooks9.useRef)(null);
1087
+ const stopTimerRef = (0, import_hooks9.useRef)(null);
1088
+ const lastEmitRef = (0, import_hooks9.useRef)(0);
1089
+ const onType = () => {
1090
+ if (!socket.value) {
1091
+ return;
1092
+ }
1093
+ const now = Date.now();
1094
+ if (lastEmitRef.current > 0 && now - lastEmitRef.current >= TYPING_HEARTBEAT_MS) {
1095
+ SocketService.typingStart(room_id);
1096
+ lastEmitRef.current = now;
1097
+ }
1098
+ if (lastEmitRef.current === 0 && !startTimerRef.current) {
1099
+ startTimerRef.current = setTimeout(() => {
1100
+ SocketService.typingStart(room_id);
1101
+ lastEmitRef.current = Date.now();
1102
+ startTimerRef.current = null;
1103
+ }, TYPING_START_DELAY_MS);
1104
+ }
1105
+ if (stopTimerRef.current) {
1106
+ clearTimeout(stopTimerRef.current);
1107
+ }
1108
+ stopTimerRef.current = setTimeout(() => {
1109
+ if (startTimerRef.current) {
1110
+ clearTimeout(startTimerRef.current);
1111
+ startTimerRef.current = null;
1112
+ }
1113
+ if (lastEmitRef.current > 0) {
1114
+ SocketService.typingStop(room_id);
1115
+ lastEmitRef.current = 0;
1116
+ }
1117
+ stopTimerRef.current = null;
1118
+ }, TYPING_STOP_DELAY_MS);
1119
+ };
1120
+ const stopTyping = () => {
1121
+ if (startTimerRef.current) {
1122
+ clearTimeout(startTimerRef.current);
1123
+ startTimerRef.current = null;
1124
+ }
1125
+ if (stopTimerRef.current) {
1126
+ clearTimeout(stopTimerRef.current);
1127
+ stopTimerRef.current = null;
1128
+ }
1129
+ if (lastEmitRef.current > 0) {
1130
+ SocketService.typingStop(room_id);
1131
+ lastEmitRef.current = 0;
1132
+ }
1133
+ };
1134
+ return { onType, stopTyping };
1135
+ };
1136
+
1137
+ // src/widget/pages/conversation.tsx
1138
+ init_state();
1139
+ var import_jsx_runtime15 = require("preact/jsx-runtime");
1140
+ var Conversation = (props) => {
1141
+ const conversationId = (0, import_signals4.useSignal)(props.conversationId);
1142
+ const isCreating = (0, import_signals4.useSignal)(false);
1143
+ const agentsQuery = useQuery({
1144
+ queryKey: ["agents"],
1145
+ queryFn: () => ApiService.getAgents()
1146
+ });
1147
+ const conversationQuery = useQuery({
1148
+ queryKey: [`conversation-${conversationId}`],
1149
+ queryFn: () => ApiService.getConversation(conversationId.value),
1150
+ enabled: !!conversationId.value
1151
+ });
1152
+ const { messages, hasMore, loadingMore, loadMore, append, replace, remove } = useMessages(
1153
+ conversationId.value
1154
+ );
1155
+ const reopenMutation = useMutation({
1156
+ mutationFn: () => ApiService.reopenConversation(conversationId.value),
1157
+ onSuccess: () => conversationQuery.refetch()
1158
+ });
1159
+ const agent = conversationQuery.data?.agent ?? agentsQuery.data?.[0];
1160
+ const isResolved = conversationQuery.data?.status === "CLOSED";
1161
+ const isOpen2 = conversationQuery.data?.status === "OPEN";
1162
+ const canConnectSocket = conversationId.value && isOpen2;
1163
+ const onNewMessage = (message) => {
1164
+ append(message);
1165
+ };
1166
+ (0, import_hooks10.useEffect)(() => {
1167
+ if (!conversationId.value) {
1168
+ return;
1169
+ }
1170
+ SocketService.on("conversation-updated", () => conversationQuery.refetch());
1171
+ return () => {
1172
+ SocketService.off("conversation-updated", () => conversationQuery.refetch());
1173
+ };
1174
+ }, [conversationId.value]);
1175
+ (0, import_hooks10.useEffect)(() => {
1176
+ if (!canConnectSocket) {
1177
+ return;
1178
+ }
1179
+ const id = conversationId.value;
1180
+ SocketService.joinRoom(id).then(() => {
1181
+ refetchSessionCounter.value += 1;
1182
+ }).catch((error) => {
1183
+ });
1184
+ SocketService.on("new-message", onNewMessage);
1185
+ return () => {
1186
+ SocketService.leaveRoom(id);
1187
+ SocketService.off("new-message", onNewMessage);
1188
+ };
1189
+ }, [canConnectSocket]);
1190
+ const send = async (text) => {
1191
+ if (!agent || isCreating.value) return;
1192
+ stopTyping();
1193
+ const tempId = `temp-${Date.now()}`;
1194
+ const optimistic = {
1195
+ id: tempId,
1196
+ conversationId: conversationId.value ?? "pending",
1197
+ text,
1198
+ sender: "user",
1199
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1200
+ };
1201
+ append(optimistic);
1202
+ try {
1203
+ if (!conversationId.value) {
1204
+ isCreating.value = true;
1205
+ const conversation = await ApiService.createConversation({ agentId: agent.id, text });
1206
+ conversationId.value = conversation.id;
1207
+ invalidateQuery(["conversations"]);
1208
+ } else {
1209
+ const message = await ApiService.sendMessage(conversationId.value, { text });
1210
+ replace(tempId, message);
1211
+ }
1212
+ } catch {
1213
+ remove(tempId);
1214
+ } finally {
1215
+ isCreating.value = false;
1216
+ }
1217
+ };
1218
+ const handleYes = () => {
1219
+ goBack();
1220
+ };
1221
+ const handleNo = () => {
1222
+ reopenMutation.mutate();
1223
+ };
1224
+ const { onType, stopTyping } = useTyping({ room_id: conversationId.value ?? "" });
1225
+ const { isTyping } = useOnTyping({ room_id: conversationId.value });
1226
+ const disabled = !agent || isCreating.value;
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { class: "flex flex-col h-full", children: [
1228
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1229
+ Header,
1230
+ {
1231
+ agent,
1232
+ loading: !agent && (agentsQuery.loading || conversationQuery.loading)
1233
+ }
1234
+ ),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1236
+ MessageList,
1237
+ {
1238
+ messages,
1239
+ agent,
1240
+ scrollKey: isResolved,
1241
+ hasMore,
1242
+ loadingMore,
1243
+ onLoadMore: loadMore,
1244
+ isTyping
1245
+ }
1246
+ ),
1247
+ isResolved ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ResolvedBanner, { onYes: handleYes, onNo: handleNo }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(InputMessage, { onSend: send, onType, disabled })
1248
+ ] });
1249
+ };
1250
+
1251
+ // src/widget/pages/home.tsx
1252
+ var import_lucide_preact5 = require("lucide-preact");
1253
+ var import_hooks11 = require("preact/hooks");
1254
+ init_service2();
1255
+ init_state();
1256
+
1257
+ // src/widget/ui/Card.tsx
1258
+ var import_jsx_runtime16 = require("preact/jsx-runtime");
1259
+ var Card = ({ children, onClick, class: cls }) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1260
+ "div",
1261
+ {
1262
+ onClick,
1263
+ class: `
1264
+ p-4 rounded-lg border border-neutral-200 bg-white
1265
+ ${onClick ? "cursor-pointer hover:bg-neutral-50 transition-colors" : ""}
1266
+ ${cls ?? ""}
1267
+ `,
1268
+ children
1269
+ }
1270
+ );
1271
+
1272
+ // src/widget/ui/Title.tsx
1273
+ var import_jsx_runtime17 = require("preact/jsx-runtime");
1274
+ var styles = {
1275
+ 1: { tag: "h1", cls: "text-2xl font-semibold" },
1276
+ 2: { tag: "h2", cls: "text-lg font-semibold" },
1277
+ 3: { tag: "h3", cls: "text-base font-medium" }
1278
+ };
1279
+ var colors3 = {
1280
+ default: "text-neutral-800",
1281
+ dimmed: "text-neutral-400"
1282
+ };
1283
+ var Title = ({ children, order = 1, color = "default", lineClamp, class: cls }) => {
1284
+ const { tag: Tag, cls: base } = styles[order];
1285
+ const colorClass = colors3[color] ?? color;
1286
+ const clampStyle = lineClamp ? { overflow: "hidden", display: "-webkit-box", WebkitLineClamp: lineClamp, WebkitBoxOrient: "vertical" } : void 0;
1287
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Tag, { class: `${base} ${cls ?? ""} ${colorClass}`, style: clampStyle, children });
1288
+ };
1289
+
1290
+ // src/widget/pages/home.tsx
1291
+ var import_jsx_runtime18 = require("preact/jsx-runtime");
1292
+ var Home = () => {
1293
+ const agentsQuery = useQuery({ queryKey: ["agents"], queryFn: () => ApiService.getAgents() });
1294
+ const conversationsQuery = useQuery({
1295
+ queryKey: ["conversations"],
1296
+ queryFn: () => ApiService.getConversations()
1297
+ });
1298
+ const agents = agentsQuery.data ?? [];
1299
+ const conversations = conversationsQuery.data ?? [];
1300
+ const userName = currentUser.value?.name;
1301
+ useOnNewMessage(() => {
1302
+ conversationsQuery.refetch();
1303
+ });
1304
+ (0, import_hooks11.useEffect)(() => {
1305
+ conversationsQuery.refetch();
1306
+ }, [visitorId.value]);
1307
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Stack, { class: "flex flex-col p-4 gradient-header", children: [
1308
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Group, { justify: "end", children: agents?.map((agent) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Indicator, { color: "green", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Avatar, { src: agent.picture, children: agent.name }) }, agent.id)) }),
1309
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Stack, { gap: 0, class: "mt-12", children: [
1310
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Title, { color: "dimmed", lineClamp: 2, children: userName ? `Hey ${userName} \u{1F44B}` : `Hey \u{1F44B}` }),
1311
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Title, { children: "How can we help?" })
1312
+ ] }),
1313
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Card, { class: "mb-8", onClick: () => navigate("conversation"), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Group, { align: "center", justify: "between", children: [
1314
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Stack, { gap: 0, children: [
1315
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { class: "font-semibold", children: "Send us a message" }),
1316
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: "dimmed", children: "Usual reply time is a few minutes" })
1317
+ ] }),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_preact5.SendHorizonal, {})
1319
+ ] }) }),
1320
+ conversations && conversations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Stack, { gap: 2, children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: "dimmed", class: "text-xs font-medium uppercase tracking-wide px-1", children: "Previous conversations" }),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Stack, { gap: 1, children: conversations.map((conv) => {
1323
+ const agent = conv.agent;
1324
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1325
+ Card,
1326
+ {
1327
+ onClick: () => navigate("conversation", { conversationId: conv.id }),
1328
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Group, { align: "center", justify: "between", children: [
1329
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Group, { align: "center", gap: 2, children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Avatar, { size: "sm", src: agent?.picture, children: agent?.name }),
1331
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Stack, { gap: 0, children: [
1332
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { class: "font-medium text-xs", children: agent?.name ?? "Support" }),
1333
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Text, { color: "dimmed", class: "text-xs truncate max-w-[180px]", children: conv.lastMessage?.text ?? "No messages yet" })
1334
+ ] })
1335
+ ] }),
1336
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1337
+ Indicator,
1338
+ {
1339
+ color: "red",
1340
+ label: conv.unread_contact_count,
1341
+ visible: conv.unread_contact_count > 0,
1342
+ position: "top-right",
1343
+ offset: { x: -36, y: 15 },
1344
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_preact5.ChevronRight, { size: 14, class: "text-neutral-400 shrink-0" })
1345
+ }
1346
+ )
1347
+ ] })
1348
+ },
1349
+ conv.id
1350
+ );
1351
+ }) })
1352
+ ] })
1353
+ ] });
1354
+ };
1355
+
1356
+ // src/widget/Widget.tsx
1357
+ init_state();
1358
+ var import_jsx_runtime19 = require("preact/jsx-runtime");
1359
+ var renderPage = () => {
1360
+ const route = currentRoute.value;
1361
+ switch (route.page) {
1362
+ case "home":
1363
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Home, {});
1364
+ case "conversation":
1365
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Conversation, { conversationId: route.params?.conversationId });
1366
+ }
1367
+ };
1368
+ var Internal = ({ hideButton }) => {
1369
+ const sessionQuery = useQuery({
1370
+ queryFn: () => ApiService.getSession(),
1371
+ queryKey: ["session"],
1372
+ enabled: !!sessionToken.value
1373
+ });
1374
+ useOnNewMessage(() => {
1375
+ sessionQuery.refetch();
1376
+ });
1377
+ (0, import_hooks12.useEffect)(() => {
1378
+ if (sessionQuery.loading) {
1379
+ return;
1380
+ }
1381
+ sessionQuery.refetch();
1382
+ }, [refetchSessionCounter.value]);
1383
+ const countNotifications = sessionQuery.data?.count_notifications ?? 0;
1384
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
1385
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Layout, { isOpen: isOpen.value, children: renderPage() }),
1386
+ !hideButton && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1387
+ "button",
1388
+ {
1389
+ onClick: () => isOpen.value = !isOpen.value,
1390
+ class: "fixed bottom-4 right-4 w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-lg flex items-center justify-center text-2xl transition-colors cursor-pointer",
1391
+ "aria-label": isOpen.value ? "Close support chat" : "Open support chat",
1392
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1393
+ Indicator,
1394
+ {
1395
+ label: countNotifications,
1396
+ visible: countNotifications > 0,
1397
+ position: "top-right",
1398
+ size: "md",
1399
+ color: "red",
1400
+ children: isOpen.value ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_preact6.X, {}) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_preact6.MessageCircle, {})
1401
+ }
1402
+ )
1403
+ }
1404
+ )
1405
+ ] });
1406
+ };
1407
+ var Widget = (props) => {
1408
+ if (!isSetup.value) {
1409
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, {});
1410
+ }
1411
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Internal, { ...props });
1412
+ };
1413
+
1414
+ // src/widget/compiled-css.ts
1415
+ var compiled_css_default = '/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",\n "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",\n "Courier New", monospace;\n --color-red-500: oklch(63.7% 0.237 25.331);\n --color-amber-50: oklch(98.7% 0.022 95.277);\n --color-amber-200: oklch(92.4% 0.12 95.746);\n --color-amber-700: oklch(55.5% 0.163 48.998);\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-blue-100: oklch(93.2% 0.032 255.585);\n --color-blue-200: oklch(88.2% 0.059 254.128);\n --color-blue-400: oklch(70.7% 0.165 254.624);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-blue-600: oklch(54.6% 0.245 262.881);\n --color-blue-700: oklch(48.8% 0.243 264.376);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-neutral-50: oklch(98.5% 0 0);\n --color-neutral-100: oklch(97% 0 0);\n --color-neutral-200: oklch(92.2% 0 0);\n --color-neutral-300: oklch(87% 0 0);\n --color-neutral-400: oklch(70.8% 0 0);\n --color-neutral-500: oklch(55.6% 0 0);\n --color-neutral-600: oklch(43.9% 0 0);\n --color-neutral-800: oklch(26.9% 0 0);\n --color-neutral-900: oklch(20.5% 0 0);\n --color-white: #fff;\n --spacing: 0.25rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --font-weight-normal: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --tracking-wide: 0.025em;\n --leading-snug: 1.375;\n --radius-sm: 0.25rem;\n --radius-lg: 0.5rem;\n --radius-xl: 0.75rem;\n --radius-2xl: 1rem;\n --animate-spin: spin 1s linear infinite;\n --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n --animate-bounce: bounce 1s infinite;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden="until-found"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .visible {\n visibility: visible;\n }\n .absolute {\n position: absolute;\n }\n .fixed {\n position: fixed;\n }\n .relative {\n position: relative;\n }\n .start {\n inset-inline-start: var(--spacing);\n }\n .end {\n inset-inline-end: var(--spacing);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .right-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-2\\.5 {\n right: calc(var(--spacing) * 2.5);\n }\n .right-4 {\n right: calc(var(--spacing) * 4);\n }\n .bottom-0 {\n bottom: calc(var(--spacing) * 0);\n }\n .bottom-1\\.5 {\n bottom: calc(var(--spacing) * 1.5);\n }\n .bottom-4 {\n bottom: calc(var(--spacing) * 4);\n }\n .bottom-20 {\n bottom: calc(var(--spacing) * 20);\n }\n .container {\n width: 100%;\n @media (width >= 40rem) {\n max-width: 40rem;\n }\n @media (width >= 48rem) {\n max-width: 48rem;\n }\n @media (width >= 64rem) {\n max-width: 64rem;\n }\n @media (width >= 80rem) {\n max-width: 80rem;\n }\n @media (width >= 96rem) {\n max-width: 96rem;\n }\n }\n .my-3 {\n margin-block: calc(var(--spacing) * 3);\n }\n .mt-12 {\n margin-top: calc(var(--spacing) * 12);\n }\n .mb-0\\.5 {\n margin-bottom: calc(var(--spacing) * 0.5);\n }\n .mb-8 {\n margin-bottom: calc(var(--spacing) * 8);\n }\n .flex {\n display: flex;\n }\n .hidden {\n display: none;\n }\n .inline-flex {\n display: inline-flex;\n }\n .h-1\\.5 {\n height: calc(var(--spacing) * 1.5);\n }\n .h-2\\.5 {\n height: calc(var(--spacing) * 2.5);\n }\n .h-3\\.5 {\n height: calc(var(--spacing) * 3.5);\n }\n .h-4 {\n height: calc(var(--spacing) * 4);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-6 {\n height: calc(var(--spacing) * 6);\n }\n .h-7 {\n height: calc(var(--spacing) * 7);\n }\n .h-8 {\n height: calc(var(--spacing) * 8);\n }\n .h-9 {\n height: calc(var(--spacing) * 9);\n }\n .h-12 {\n height: calc(var(--spacing) * 12);\n }\n .h-14 {\n height: calc(var(--spacing) * 14);\n }\n .h-112 {\n height: calc(var(--spacing) * 112);\n }\n .h-full {\n height: 100%;\n }\n .h-px {\n height: 1px;\n }\n .min-h-\\[175px\\] {\n min-height: 175px;\n }\n .w-1\\.5 {\n width: calc(var(--spacing) * 1.5);\n }\n .w-2\\.5 {\n width: calc(var(--spacing) * 2.5);\n }\n .w-3\\.5 {\n width: calc(var(--spacing) * 3.5);\n }\n .w-4 {\n width: calc(var(--spacing) * 4);\n }\n .w-7 {\n width: calc(var(--spacing) * 7);\n }\n .w-8 {\n width: calc(var(--spacing) * 8);\n }\n .w-9 {\n width: calc(var(--spacing) * 9);\n }\n .w-12 {\n width: calc(var(--spacing) * 12);\n }\n .w-14 {\n width: calc(var(--spacing) * 14);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\n }\n .w-full {\n width: 100%;\n }\n .max-w-\\[72\\%\\] {\n max-width: 72%;\n }\n .max-w-\\[180px\\] {\n max-width: 180px;\n }\n .min-w-4 {\n min-width: calc(var(--spacing) * 4);\n }\n .min-w-5 {\n min-width: calc(var(--spacing) * 5);\n }\n .min-w-6 {\n min-width: calc(var(--spacing) * 6);\n }\n .min-w-18 {\n min-width: calc(var(--spacing) * 18);\n }\n .flex-1 {\n flex: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\n }\n .translate-x-1\\/2 {\n --tw-translate-x: calc(1 / 2 * 100%);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-bounce {\n animation: var(--animate-bounce);\n }\n .animate-pulse {\n animation: var(--animate-pulse);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .resize {\n resize: both;\n }\n .resize-none {\n resize: none;\n }\n .flex-col {\n flex-direction: column;\n }\n .flex-col-reverse {\n flex-direction: column-reverse;\n }\n .flex-row {\n flex-direction: row;\n }\n .flex-row-reverse {\n flex-direction: row-reverse;\n }\n .flex-wrap {\n flex-wrap: wrap;\n }\n .items-center {\n align-items: center;\n }\n .items-end {\n align-items: flex-end;\n }\n .items-start {\n align-items: flex-start;\n }\n .items-stretch {\n align-items: stretch;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .justify-end {\n justify-content: flex-end;\n }\n .justify-start {\n justify-content: flex-start;\n }\n .gap-0\\.5 {\n gap: calc(var(--spacing) * 0.5);\n }\n .gap-1 {\n gap: calc(var(--spacing) * 1);\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-6 {\n gap: calc(var(--spacing) * 6);\n }\n .self-end {\n align-self: flex-end;\n }\n .truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded-2xl {\n border-radius: var(--radius-2xl);\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-xl {\n border-radius: var(--radius-xl);\n }\n .rounded-br-sm {\n border-bottom-right-radius: var(--radius-sm);\n }\n .rounded-bl-sm {\n border-bottom-left-radius: var(--radius-sm);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-2 {\n border-style: var(--tw-border-style);\n border-width: 2px;\n }\n .border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n }\n .border-amber-200 {\n border-color: var(--color-amber-200);\n }\n .border-blue-400 {\n border-color: var(--color-blue-400);\n }\n .border-gray-200 {\n border-color: var(--color-gray-200);\n }\n .border-neutral-100 {\n border-color: var(--color-neutral-100);\n }\n .border-neutral-200 {\n border-color: var(--color-neutral-200);\n }\n .border-white {\n border-color: var(--color-white);\n }\n .bg-amber-50 {\n background-color: var(--color-amber-50);\n }\n .bg-blue-100 {\n background-color: var(--color-blue-100);\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-blue-600 {\n background-color: var(--color-blue-600);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-neutral-100 {\n background-color: var(--color-neutral-100);\n }\n .bg-neutral-200 {\n background-color: var(--color-neutral-200);\n }\n .bg-neutral-400 {\n background-color: var(--color-neutral-400);\n }\n .bg-red-500 {\n background-color: var(--color-red-500);\n }\n .bg-transparent {\n background-color: transparent;\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .object-cover {\n object-fit: cover;\n }\n .p-2 {\n padding: calc(var(--spacing) * 2);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .px-1 {\n padding-inline: calc(var(--spacing) * 1);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-2\\.5 {\n padding-block: calc(var(--spacing) * 2.5);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .py-4 {\n padding-block: calc(var(--spacing) * 4);\n }\n .pt-2 {\n padding-top: calc(var(--spacing) * 2);\n }\n .pb-5 {\n padding-bottom: calc(var(--spacing) * 5);\n }\n .text-center {\n text-align: center;\n }\n .text-2xl {\n font-size: var(--text-2xl);\n line-height: var(--tw-leading, var(--text-2xl--line-height));\n }\n .text-base {\n font-size: var(--text-base);\n line-height: var(--tw-leading, var(--text-base--line-height));\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .text-\\[10px\\] {\n font-size: 10px;\n }\n .leading-5 {\n --tw-leading: calc(var(--spacing) * 5);\n line-height: calc(var(--spacing) * 5);\n }\n .leading-none {\n --tw-leading: 1;\n line-height: 1;\n }\n .leading-snug {\n --tw-leading: var(--leading-snug);\n line-height: var(--leading-snug);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-wide {\n --tw-tracking: var(--tracking-wide);\n letter-spacing: var(--tracking-wide);\n }\n .whitespace-pre-wrap {\n white-space: pre-wrap;\n }\n .text-amber-700 {\n color: var(--color-amber-700);\n }\n .text-blue-200 {\n color: var(--color-blue-200);\n }\n .text-blue-700 {\n color: var(--color-blue-700);\n }\n .text-green-500 {\n color: var(--color-green-500);\n }\n .text-inherit {\n color: inherit;\n }\n .text-neutral-400 {\n color: var(--color-neutral-400);\n }\n .text-neutral-500 {\n color: var(--color-neutral-500);\n }\n .text-neutral-600 {\n color: var(--color-neutral-600);\n }\n .text-neutral-800 {\n color: var(--color-neutral-800);\n }\n .text-neutral-900 {\n color: var(--color-neutral-900);\n }\n .text-white {\n color: var(--color-white);\n }\n .uppercase {\n text-transform: uppercase;\n }\n .opacity-0 {\n opacity: 0%;\n }\n .shadow {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .outline-none {\n --tw-outline-style: none;\n outline-style: none;\n }\n .placeholder\\:text-neutral-400 {\n &::placeholder {\n color: var(--color-neutral-400);\n }\n }\n .hover\\:bg-blue-700 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-blue-700);\n }\n }\n }\n .hover\\:bg-neutral-50 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-neutral-50);\n }\n }\n }\n .hover\\:bg-neutral-200 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-neutral-200);\n }\n }\n }\n .hover\\:text-neutral-800 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-neutral-800);\n }\n }\n }\n .disabled\\:cursor-not-allowed {\n &:disabled {\n cursor: not-allowed;\n }\n }\n .disabled\\:opacity-50 {\n &:disabled {\n opacity: 50%;\n }\n }\n}\n*, ::before, ::after {\n --tw-border-style: solid;\n}\n.gradient-header {\n background: linear-gradient(to bottom, var(--color-neutral-200) 0%, var(--color-white) 60%);\n}\n.lucide {\n height: 1em;\n width: 1em;\n display: inline-block;\n}\n::-webkit-scrollbar {\n width: 4px;\n}\n::-webkit-scrollbar-track {\n background: transparent;\n}\n::-webkit-scrollbar-thumb {\n background: var(--color-neutral-300);\n border-radius: 9999px;\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: "*";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: "*";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: "*";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: "*";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: "*";\n inherits: false;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-leading {\n syntax: "*";\n inherits: false;\n}\n@property --tw-font-weight {\n syntax: "*";\n inherits: false;\n}\n@property --tw-tracking {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: "<length>";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: "*";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-outline-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@keyframes pulse {\n 50% {\n opacity: 0.5;\n }\n}\n@keyframes bounce {\n 0%, 100% {\n transform: translateY(-25%);\n animation-timing-function: cubic-bezier(0.8, 0, 1, 1);\n }\n 50% {\n transform: none;\n animation-timing-function: cubic-bezier(0, 0, 0.2, 1);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-border-style: solid;\n --tw-leading: initial;\n --tw-font-weight: initial;\n --tw-tracking: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-outline-style: solid;\n }\n }\n}\n';
1416
+
1417
+ // src/widget/mount.tsx
1418
+ init_service();
1419
+ init_state();
1420
+ var import_jsx_runtime20 = require("preact/jsx-runtime");
1421
+ function mountWidget(options) {
1422
+ const { appId: id, hideButton = false } = options;
1423
+ appId.value = id;
1424
+ VisitorService.init().then(() => SocketService.connect());
1425
+ const host = document.createElement("div");
1426
+ host.id = "spacinbox-widget";
1427
+ document.body.appendChild(host);
1428
+ const shadow = host.attachShadow({ mode: "open" });
1429
+ const sheet = new CSSStyleSheet();
1430
+ sheet.replaceSync(compiled_css_default);
1431
+ shadow.adoptedStyleSheets = [sheet];
1432
+ const mountPoint = document.createElement("div");
1433
+ shadow.appendChild(mountPoint);
1434
+ (0, import_preact2.render)(/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Widget, { hideButton }), mountPoint);
1435
+ return {
1436
+ open() {
1437
+ isOpen.value = true;
1438
+ },
1439
+ close() {
1440
+ isOpen.value = false;
1441
+ },
1442
+ identify(user) {
1443
+ currentUser.value = user;
1444
+ VisitorService.identify(user);
1445
+ },
1446
+ setMetadata(data) {
1447
+ metadata.value = { ...metadata.value, ...data };
1448
+ },
1449
+ shutdown() {
1450
+ (0, import_preact2.render)(null, mountPoint);
1451
+ host.remove();
1452
+ isOpen.value = false;
1453
+ currentUser.value = null;
1454
+ metadata.value = {};
1455
+ visitorId.value = null;
1456
+ sessionToken.value = null;
1457
+ resetRouter();
1458
+ VisitorService.clear();
1459
+ SocketService.disconnect();
1460
+ }
1461
+ };
1462
+ }
1463
+
1464
+ // src/index.ts
1465
+ var controller = null;
1466
+ var Spacinbox = {
1467
+ boot(options) {
1468
+ if (typeof document === "undefined") {
1469
+ return;
1470
+ }
1471
+ if (controller) {
1472
+ return;
1473
+ }
1474
+ controller = mountWidget(options);
1475
+ },
1476
+ identify(user) {
1477
+ controller?.identify(user);
1478
+ },
1479
+ setMetadata(data) {
1480
+ controller?.setMetadata(data);
1481
+ },
1482
+ open() {
1483
+ controller?.open();
1484
+ },
1485
+ close() {
1486
+ controller?.close();
1487
+ },
1488
+ shutdown() {
1489
+ controller?.shutdown();
1490
+ controller = null;
1491
+ }
1492
+ };
1493
+ var index_default = Spacinbox;
1494
+ //# sourceMappingURL=index.cjs.map