@helpway/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4164 @@
1
+ // src/helpway-widget.tsx
2
+ import { useEffect } from "react";
3
+ function HelpwayWidget({
4
+ apiKey,
5
+ src = "https://api.helpway.ai/widget.js"
6
+ }) {
7
+ useEffect(() => {
8
+ if (typeof document === "undefined") return;
9
+ const existing = document.querySelector(
10
+ `script[data-helpway-loader="${apiKey}"]`
11
+ );
12
+ if (existing) return;
13
+ const s = document.createElement("script");
14
+ s.src = src;
15
+ s.dataset.key = apiKey;
16
+ s.dataset.helpwayLoader = apiKey;
17
+ s.defer = true;
18
+ document.body.appendChild(s);
19
+ return () => {
20
+ s.remove();
21
+ try {
22
+ window.dispatchEvent(new CustomEvent("helpway:cleanup"));
23
+ } catch {
24
+ }
25
+ };
26
+ }, [apiKey, src]);
27
+ return null;
28
+ }
29
+
30
+ // ../widget-core/src/client.ts
31
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
32
+
33
+ // ../widget-core/src/visitor-id.ts
34
+ var PREFIX = "helpway:visitor";
35
+ var LEGACY_KEY = "helpway:visitor-id";
36
+ function keyFor(publicKey) {
37
+ const suffix = publicKey.slice(-16);
38
+ return `${PREFIX}:${suffix}`;
39
+ }
40
+ function generateId() {
41
+ const ts = Date.now().toString(36);
42
+ const rand = Math.random().toString(36).slice(2, 10);
43
+ return `${ts}${rand}`;
44
+ }
45
+ function getOrCreateVisitorId(publicKey) {
46
+ try {
47
+ const storageKey = keyFor(publicKey);
48
+ const existing = localStorage.getItem(storageKey);
49
+ if (existing) return existing;
50
+ const legacy = localStorage.getItem(LEGACY_KEY);
51
+ if (legacy) {
52
+ localStorage.setItem(storageKey, legacy);
53
+ localStorage.removeItem(LEGACY_KEY);
54
+ return legacy;
55
+ }
56
+ const id = generateId();
57
+ localStorage.setItem(storageKey, id);
58
+ return id;
59
+ } catch {
60
+ return generateId();
61
+ }
62
+ }
63
+ function syncVisitorId(publicKey, serverVisitorId) {
64
+ try {
65
+ localStorage.setItem(keyFor(publicKey), serverVisitorId);
66
+ } catch {
67
+ }
68
+ }
69
+ function clearVisitorId(publicKey) {
70
+ try {
71
+ localStorage.removeItem(keyFor(publicKey));
72
+ } catch {
73
+ }
74
+ }
75
+
76
+ // ../widget-core/src/client.ts
77
+ function createWidgetClient(config) {
78
+ return createTRPCClient({
79
+ links: [
80
+ httpBatchLink({
81
+ url: `${config.apiUrl}/trpc`,
82
+ headers() {
83
+ return {
84
+ "X-Public-Key": config.apiKey,
85
+ "X-Visitor-Id": getOrCreateVisitorId(config.apiKey)
86
+ };
87
+ },
88
+ fetch(url, options) {
89
+ return globalThis.fetch(url, { ...options, credentials: "include" });
90
+ }
91
+ })
92
+ ]
93
+ });
94
+ }
95
+
96
+ // ../types/src/realtime-events.ts
97
+ function isRealtimeEvent(data) {
98
+ return typeof data === "object" && data !== null && "type" in data && typeof data.type === "string";
99
+ }
100
+
101
+ // ../widget-core/src/ws.ts
102
+ var WebSocketManager = class {
103
+ ws = null;
104
+ handlers = /* @__PURE__ */ new Set();
105
+ config;
106
+ reconnectAttempts = 0;
107
+ maxReconnectAttempts = 10;
108
+ reconnectTimer = null;
109
+ pingTimer = null;
110
+ isDestroyed = false;
111
+ visibilityHandler = null;
112
+ constructor(config) {
113
+ this.config = config;
114
+ this.setupVisibilityListener();
115
+ }
116
+ setupVisibilityListener() {
117
+ if (typeof document === "undefined") return;
118
+ this.visibilityHandler = () => {
119
+ if (document.visibilityState === "hidden") {
120
+ this.stopPing();
121
+ } else if (this.ws?.readyState === WebSocket.OPEN) {
122
+ this.startPing();
123
+ this.send({ type: "ping" });
124
+ }
125
+ };
126
+ document.addEventListener("visibilitychange", this.visibilityHandler);
127
+ }
128
+ /** Update connection parameters (call before connect) */
129
+ configure(params) {
130
+ Object.assign(this.config, params);
131
+ }
132
+ /** Connect to the ChatRoom Durable Object WebSocket */
133
+ connect() {
134
+ if (this.ws?.readyState === WebSocket.OPEN) return;
135
+ this.isDestroyed = false;
136
+ const protocol = this.config.apiUrl.startsWith("https") ? "wss" : "ws";
137
+ const base = this.config.apiUrl.replace(/^https?/, protocol);
138
+ const params = new URLSearchParams({
139
+ type: "visitor",
140
+ id: this.config.visitorId,
141
+ websiteId: this.config.websiteId
142
+ });
143
+ const ws = new WebSocket(`${base}/ws?${params}`);
144
+ ws.onopen = () => {
145
+ this.reconnectAttempts = 0;
146
+ if (typeof document === "undefined" || document.visibilityState === "visible") {
147
+ this.startPing();
148
+ }
149
+ };
150
+ ws.onmessage = (event) => {
151
+ try {
152
+ const data = JSON.parse(event.data);
153
+ if (data.type === "pong") return;
154
+ if (isRealtimeEvent(data)) {
155
+ for (const handler of this.handlers) {
156
+ handler(data);
157
+ }
158
+ }
159
+ } catch {
160
+ }
161
+ };
162
+ ws.onclose = () => {
163
+ this.stopPing();
164
+ this.ws = null;
165
+ if (!this.isDestroyed) {
166
+ this.scheduleReconnect();
167
+ }
168
+ };
169
+ ws.onerror = () => {
170
+ };
171
+ this.ws = ws;
172
+ }
173
+ /** Disconnect and stop reconnecting */
174
+ disconnect() {
175
+ this.isDestroyed = true;
176
+ this.stopPing();
177
+ if (this.reconnectTimer) {
178
+ clearTimeout(this.reconnectTimer);
179
+ this.reconnectTimer = null;
180
+ }
181
+ if (this.visibilityHandler && typeof document !== "undefined") {
182
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
183
+ this.visibilityHandler = null;
184
+ }
185
+ if (this.ws) {
186
+ this.ws.close(1e3, "Client disconnect");
187
+ this.ws = null;
188
+ }
189
+ }
190
+ /** Send a JSON message through the WebSocket */
191
+ send(data) {
192
+ if (this.ws?.readyState === WebSocket.OPEN) {
193
+ this.ws.send(JSON.stringify(data));
194
+ }
195
+ }
196
+ /** Send typing indicator */
197
+ sendTyping(conversationId, isTyping) {
198
+ this.send({ type: "typing", conversationId, isTyping });
199
+ }
200
+ /** Subscribe to realtime events */
201
+ onEvent(handler) {
202
+ this.handlers.add(handler);
203
+ return () => {
204
+ this.handlers.delete(handler);
205
+ };
206
+ }
207
+ /** Check if connected */
208
+ get isConnected() {
209
+ return this.ws?.readyState === WebSocket.OPEN;
210
+ }
211
+ // ── Private ──────────────────────────────────────────
212
+ scheduleReconnect() {
213
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
214
+ const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
215
+ this.reconnectAttempts++;
216
+ this.reconnectTimer = setTimeout(() => {
217
+ this.connect();
218
+ }, delay);
219
+ }
220
+ startPing() {
221
+ this.stopPing();
222
+ this.pingTimer = setInterval(() => {
223
+ this.send({ type: "ping" });
224
+ }, 25e3);
225
+ }
226
+ stopPing() {
227
+ if (this.pingTimer) {
228
+ clearInterval(this.pingTimer);
229
+ this.pingTimer = null;
230
+ }
231
+ }
232
+ };
233
+
234
+ // ../widget-core/src/store.ts
235
+ import { createStore } from "zustand/vanilla";
236
+ var CONTENT_CACHE_TTL = 5 * 60 * 1e3;
237
+ function createWidgetStore() {
238
+ return createStore((set, get) => ({
239
+ // Initial state
240
+ isReady: false,
241
+ isConnecting: true,
242
+ visitor: null,
243
+ website: null,
244
+ agentConfig: null,
245
+ conversations: [],
246
+ activeConversationId: null,
247
+ messages: /* @__PURE__ */ new Map(),
248
+ typingUsers: /* @__PURE__ */ new Map(),
249
+ aiProcessing: /* @__PURE__ */ new Map(),
250
+ isOpen: false,
251
+ view: "home",
252
+ incomingPreview: null,
253
+ contentCache: /* @__PURE__ */ new Map(),
254
+ // ── Lifecycle ────────────────────────────────────
255
+ setReady(visitor, website, agentConfig) {
256
+ set({
257
+ isReady: true,
258
+ isConnecting: false,
259
+ visitor,
260
+ website,
261
+ agentConfig: agentConfig ?? null
262
+ });
263
+ },
264
+ setConnecting(connecting) {
265
+ set({ isConnecting: connecting });
266
+ },
267
+ // ── Conversations ────────────────────────────────
268
+ setConversations(conversations) {
269
+ set({ conversations });
270
+ },
271
+ addConversation(conversation) {
272
+ set((state) => ({
273
+ conversations: [conversation, ...state.conversations]
274
+ }));
275
+ },
276
+ setActiveConversation(id) {
277
+ set({ activeConversationId: id, view: id ? "conversation" : "home" });
278
+ },
279
+ // ── Messages ─────────────────────────────────────
280
+ setMessages(conversationId, items) {
281
+ set((state) => {
282
+ const next = new Map(state.messages);
283
+ next.set(conversationId, items);
284
+ return { messages: next };
285
+ });
286
+ },
287
+ addMessage(conversationId, item) {
288
+ set((state) => {
289
+ const next = new Map(state.messages);
290
+ const existing = next.get(conversationId) ?? [];
291
+ if (existing.some((m) => m.id === item.id)) return state;
292
+ const isAgentWelcome = !!item.aiAgentId && !item.visitorId;
293
+ const firstIsVisitor = existing[0]?.visitorId != null;
294
+ if (isAgentWelcome && firstIsVisitor) {
295
+ next.set(conversationId, [item, ...existing]);
296
+ return { messages: next };
297
+ }
298
+ const last = existing[existing.length - 1];
299
+ const isOutOfOrder = last && item.createdAt < last.createdAt;
300
+ const merged = [...existing, item];
301
+ if (isOutOfOrder) {
302
+ merged.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
303
+ }
304
+ next.set(conversationId, merged);
305
+ return { messages: next };
306
+ });
307
+ },
308
+ replaceMessage(conversationId, oldId, item) {
309
+ set((state) => {
310
+ const next = new Map(state.messages);
311
+ const existing = next.get(conversationId) ?? [];
312
+ if (existing.some((m) => m.id === item.id && m.id !== oldId)) {
313
+ next.set(
314
+ conversationId,
315
+ existing.filter((m) => m.id !== oldId)
316
+ );
317
+ return { messages: next };
318
+ }
319
+ next.set(
320
+ conversationId,
321
+ existing.map(
322
+ (m) => m.id === oldId ? { ...item, _stableKey: m._stableKey ?? oldId } : m
323
+ )
324
+ );
325
+ return { messages: next };
326
+ });
327
+ },
328
+ removeMessage(conversationId, id) {
329
+ set((state) => {
330
+ const next = new Map(state.messages);
331
+ const existing = next.get(conversationId);
332
+ if (!existing) return state;
333
+ next.set(
334
+ conversationId,
335
+ existing.filter((m) => m.id !== id)
336
+ );
337
+ return { messages: next };
338
+ });
339
+ },
340
+ remapConversationId(oldId, newId) {
341
+ set((state) => {
342
+ const nextMessages = new Map(state.messages);
343
+ const tempItems = nextMessages.get(oldId) ?? [];
344
+ const existingItems = nextMessages.get(newId) ?? [];
345
+ const tempRewritten = tempItems.map((m) => ({ ...m, conversationId: newId }));
346
+ const isAgentMsg = (m) => !!m.aiAgentId && !m.visitorId && !m.userId;
347
+ const seen = /* @__PURE__ */ new Set();
348
+ const merged = [];
349
+ let keptAgentWelcome = false;
350
+ for (const m of tempRewritten) {
351
+ if (seen.has(m.id)) continue;
352
+ seen.add(m.id);
353
+ merged.push(m);
354
+ if (isAgentMsg(m) && merged.length === 1) keptAgentWelcome = true;
355
+ }
356
+ for (const m of existingItems) {
357
+ if (seen.has(m.id)) continue;
358
+ if (isAgentMsg(m) && keptAgentWelcome) continue;
359
+ seen.add(m.id);
360
+ merged.push(m);
361
+ }
362
+ nextMessages.delete(oldId);
363
+ nextMessages.set(newId, merged);
364
+ const hasNewId = state.conversations.some((c) => c.id === newId);
365
+ const conversations = hasNewId ? state.conversations.filter((c) => c.id !== oldId) : state.conversations.map((c) => c.id === oldId ? { ...c, id: newId } : c);
366
+ return {
367
+ messages: nextMessages,
368
+ conversations,
369
+ activeConversationId: state.activeConversationId === oldId ? newId : state.activeConversationId
370
+ };
371
+ });
372
+ },
373
+ // ── Presence ─────────────────────────────────────
374
+ setTyping(conversationId, isTyping) {
375
+ set((state) => {
376
+ const next = new Map(state.typingUsers);
377
+ next.set(conversationId, isTyping);
378
+ return { typingUsers: next };
379
+ });
380
+ },
381
+ setAiProcessing(conversationId, processing) {
382
+ set((state) => {
383
+ const next = new Map(state.aiProcessing);
384
+ next.set(conversationId, processing);
385
+ return { aiProcessing: next };
386
+ });
387
+ },
388
+ // ── Identity ─────────────────────────────────────
389
+ updateVisitorContact(contact) {
390
+ set((state) => ({
391
+ visitor: state.visitor ? { ...state.visitor, contact } : state.visitor
392
+ }));
393
+ },
394
+ // ── Unread ───────────────────────────────────────
395
+ clearUnread(conversationId) {
396
+ set((s) => ({
397
+ conversations: s.conversations.map(
398
+ (c) => c.id === conversationId ? { ...c, unreadCount: 0 } : c
399
+ )
400
+ }));
401
+ },
402
+ // ── UI ───────────────────────────────────────────
403
+ setOpen(open) {
404
+ set((s) => ({
405
+ isOpen: open,
406
+ incomingPreview: open ? null : s.incomingPreview
407
+ // dismiss preview when opening
408
+ }));
409
+ },
410
+ setView(view) {
411
+ set({ view });
412
+ },
413
+ dismissPreview() {
414
+ set({ incomingPreview: null });
415
+ },
416
+ // ── Content Cache ────────────────────────────────
417
+ setContentCache(key, data) {
418
+ get().contentCache.set(key, { data, ts: Date.now() });
419
+ },
420
+ // ── Realtime Event Handler ───────────────────────
421
+ handleRealtimeEvent(event) {
422
+ const state = get();
423
+ switch (event.type) {
424
+ case "timelineItemCreated": {
425
+ if (event.item.visibility === "public") {
426
+ const isAgentAuthored = !!event.item.aiAgentId && !event.item.visitorId && !event.item.userId;
427
+ const existing = state.messages.get(event.conversationId);
428
+ const seedId = existing?.[0]?.id;
429
+ const isSeedWelcomeEcho = isAgentAuthored && !!existing && existing.length === 1 && typeof seedId === "string" && seedId.startsWith("seed-welcome-") && seedId !== event.item.id;
430
+ if (isSeedWelcomeEcho) {
431
+ break;
432
+ }
433
+ state.addMessage(event.conversationId, event.item);
434
+ }
435
+ if (event.item.aiAgentId) {
436
+ state.setAiProcessing(event.conversationId, false);
437
+ state.setTyping(event.conversationId, false);
438
+ }
439
+ if (event.item.type === "message" && event.item.text) {
440
+ const isIncoming = !event.item.visitorId;
441
+ const currentState = get();
442
+ const isViewingThis = currentState.isOpen && currentState.activeConversationId === event.conversationId;
443
+ set((s) => ({
444
+ conversations: s.conversations.map(
445
+ (c) => c.id === event.conversationId ? {
446
+ ...c,
447
+ lastMessageText: event.item.text,
448
+ lastMessageAt: event.item.createdAt,
449
+ // Increment unread if incoming and not actively viewing
450
+ ...isIncoming && !isViewingThis ? { unreadCount: c.unreadCount + 1 } : {}
451
+ } : c
452
+ ),
453
+ // Show preview bubble if widget is closed and message is incoming
454
+ ...isIncoming && !currentState.isOpen ? {
455
+ incomingPreview: {
456
+ text: event.item.text ?? "",
457
+ conversationId: event.conversationId,
458
+ timestamp: Date.now()
459
+ }
460
+ } : {}
461
+ }));
462
+ }
463
+ break;
464
+ }
465
+ case "conversationUpdated": {
466
+ set((s) => ({
467
+ conversations: s.conversations.map(
468
+ (c) => c.id === event.conversationId ? { ...c, ...event.updates } : c
469
+ )
470
+ }));
471
+ break;
472
+ }
473
+ case "conversationTyping": {
474
+ if (!event.visitorId) {
475
+ state.setTyping(event.conversationId, event.isTyping);
476
+ if (event.isTyping) {
477
+ setTimeout(() => {
478
+ state.setTyping(event.conversationId, false);
479
+ }, 5e3);
480
+ }
481
+ }
482
+ break;
483
+ }
484
+ case "aiProcessingProgress": {
485
+ state.setAiProcessing(event.conversationId, true);
486
+ state.setTyping(event.conversationId, true);
487
+ break;
488
+ }
489
+ case "aiProcessingCompleted": {
490
+ state.setAiProcessing(event.conversationId, false);
491
+ state.setTyping(event.conversationId, false);
492
+ break;
493
+ }
494
+ default:
495
+ break;
496
+ }
497
+ }
498
+ }));
499
+ }
500
+
501
+ // ../widget-core/src/i18n.ts
502
+ var en = {
503
+ // Navigation & tabs
504
+ "widget.home": "Home",
505
+ "widget.messages": "Messages",
506
+ "widget.help": "Help",
507
+ // Home screen
508
+ "widget.send_us_message": "Send us a message",
509
+ "widget.reply_time": "We typically reply in a few minutes",
510
+ "widget.recent_conversations": "Recent conversations",
511
+ "widget.see_all": "See all conversations",
512
+ // Messages tab
513
+ "widget.no_messages": "No messages yet",
514
+ "widget.no_messages_desc": "Start a conversation to get help",
515
+ "widget.start_conversation": "Start a conversation",
516
+ "widget.no_preview": "No messages yet",
517
+ // Conversation
518
+ "widget.new_conversation": "New conversation",
519
+ "widget.resolved_text": "This conversation has been resolved",
520
+ "widget.send_followup": "Send a follow-up",
521
+ "widget.start_a_conversation": "Start a conversation",
522
+ // Composer
523
+ "widget.placeholder": "Send a message...",
524
+ "widget.placeholder_new": "How can we help?",
525
+ "widget.drop_files": "Drop files here",
526
+ // CSAT
527
+ "widget.csat_label": "How was your experience?",
528
+ "widget.csat_thanks": "Thanks for your feedback!",
529
+ // Inline email collection (after AI reply)
530
+ "widget.email_title": "Stay in the loop",
531
+ "widget.email_subtitle": "Get notified when we reply",
532
+ "widget.email_placeholder": "you@email.com",
533
+ "widget.email_continue": "Notify me",
534
+ "widget.email_saved": "You'll be notified by email",
535
+ "widget.email_dismiss": "No thanks",
536
+ // Errors
537
+ "widget.error_title": "Something went wrong",
538
+ "widget.error_desc": "Failed to load. Please try again.",
539
+ "widget.retry": "Retry",
540
+ "widget.reconnecting": "Reconnecting...",
541
+ "widget.send_error": "Message not sent",
542
+ // Date separators (today is implicit — never rendered)
543
+ "widget.yesterday": "Yesterday",
544
+ // Sender labels
545
+ "widget.better_ai": "Better AI",
546
+ "widget.support": "Support",
547
+ // Events
548
+ "widget.joined": "joined the conversation",
549
+ "widget.left": "left the conversation",
550
+ // Content/search
551
+ "widget.content": "Content",
552
+ "widget.search": "Search...",
553
+ "widget.no_results": "No results found",
554
+ "widget.try_different": "Try a different search term",
555
+ "widget.nothing_yet": "Nothing here yet",
556
+ "widget.check_back": "Check back later for updates",
557
+ "widget.load_more": "Load more",
558
+ "widget.loading": "Loading...",
559
+ // Copy
560
+ "widget.copy": "Copy message",
561
+ "widget.copied": "Copied",
562
+ // File upload
563
+ "widget.file_too_large": "File too large (max 10 MB)",
564
+ "widget.click_to_open": "Click to open",
565
+ "widget.caption_required": "Add a message to send with your attachment",
566
+ // Accessibility
567
+ "widget.aria_chat": "Support chat",
568
+ "widget.aria_close": "Close",
569
+ "widget.aria_back": "Back",
570
+ "widget.aria_send": "Send message",
571
+ "widget.aria_attach": "Attach file",
572
+ "widget.aria_input": "Message input",
573
+ "widget.aria_remove": "Remove",
574
+ "widget.aria_dismiss": "Dismiss",
575
+ "widget.aria_open": "Open support",
576
+ "widget.aria_close_support": "Close support",
577
+ "widget.aria_good": "Good",
578
+ "widget.aria_bad": "Bad",
579
+ "widget.aria_messages": "Messages",
580
+ // Time (relative)
581
+ "widget.time_now": "now",
582
+ "widget.time_yesterday": "Yesterday",
583
+ // Powered by
584
+ "widget.powered_by": "Powered by",
585
+ // Hero / home screen
586
+ "widget.welcome_message": "Hi there \u{1F44B}\nHow can we help?",
587
+ "widget.subtitle_text": "Usually replies in minutes"
588
+ };
589
+ var currentLocale = "en";
590
+ var translations = {};
591
+ var isLoaded = false;
592
+ var revision = 0;
593
+ var listeners = [];
594
+ function t(key, params) {
595
+ const value = translations[key] ?? en[key] ?? key;
596
+ if (!params) return value;
597
+ return value.replace(/\{\{(\w+)\}\}/g, (_, k) => {
598
+ return params[k]?.toString() ?? `{{${k}}}`;
599
+ });
600
+ }
601
+ function getLocale() {
602
+ return currentLocale;
603
+ }
604
+ function getI18nRevision() {
605
+ return revision;
606
+ }
607
+ function onLocaleChange(fn) {
608
+ listeners.push(fn);
609
+ return () => {
610
+ const idx = listeners.indexOf(fn);
611
+ if (idx >= 0) listeners.splice(idx, 1);
612
+ };
613
+ }
614
+ async function initI18n(options) {
615
+ if (options?.cdnBase) {
616
+ cdnBase = options.cdnBase;
617
+ }
618
+ const detected = options?.locale ?? (typeof navigator !== "undefined" ? navigator.language : "en");
619
+ currentLocale = normalizeLocale(detected);
620
+ if (currentLocale === "en") {
621
+ isLoaded = true;
622
+ notify();
623
+ return;
624
+ }
625
+ try {
626
+ const data = await fetchTranslations(currentLocale);
627
+ if (data) {
628
+ for (const [key, value] of Object.entries(data)) {
629
+ if (typeof value === "string") {
630
+ translations[`widget.${key}`] = value;
631
+ }
632
+ }
633
+ }
634
+ } catch (err) {
635
+ console.warn(`[Helpway] Failed to fetch ${currentLocale} translations, using English`, err);
636
+ }
637
+ isLoaded = true;
638
+ notify();
639
+ }
640
+ async function setLocale(locale) {
641
+ const normalized = normalizeLocale(locale);
642
+ if (normalized === currentLocale) return;
643
+ currentLocale = normalized;
644
+ translations = {};
645
+ if (normalized === "en") {
646
+ notify();
647
+ return;
648
+ }
649
+ try {
650
+ const data = await fetchTranslations(normalized);
651
+ if (data) {
652
+ for (const [key, value] of Object.entries(data)) {
653
+ if (typeof value === "string") {
654
+ translations[`widget.${key}`] = value;
655
+ }
656
+ }
657
+ }
658
+ } catch {
659
+ }
660
+ notify();
661
+ }
662
+ var cdnBase = "https://cdn.better-i18n.com/helpway/widget";
663
+ async function fetchTranslations(locale) {
664
+ const url = `${cdnBase}/${locale}/translations.json`;
665
+ let res = await fetch(url);
666
+ if (!res.ok && locale.includes("-")) {
667
+ const base = locale.split("-")[0];
668
+ res = await fetch(`${cdnBase}/${base}/translations.json`);
669
+ }
670
+ if (!res.ok) return null;
671
+ const data = await res.json();
672
+ const widgetNs = data.widget;
673
+ if (widgetNs && typeof widgetNs === "object" && !Array.isArray(widgetNs)) {
674
+ const result = {};
675
+ for (const [k, v] of Object.entries(widgetNs)) {
676
+ if (typeof v === "string") result[k] = v;
677
+ }
678
+ return result;
679
+ }
680
+ return null;
681
+ }
682
+ function normalizeLocale(input) {
683
+ const regional = ["pt-br", "zh-cn", "zh-tw", "zh-hk"];
684
+ const lower = input.toLowerCase().replace("_", "-");
685
+ for (const r of regional) {
686
+ if (lower.startsWith(r)) return r;
687
+ }
688
+ return lower.split("-")[0] ?? "en";
689
+ }
690
+ function notify() {
691
+ revision++;
692
+ for (const fn of listeners) {
693
+ try {
694
+ fn();
695
+ } catch {
696
+ }
697
+ }
698
+ }
699
+
700
+ // ../widget-core/src/utils.ts
701
+ function formatRelativeTime(date) {
702
+ const now = Date.now();
703
+ const then = typeof date === "string" ? new Date(date).getTime() : date.getTime();
704
+ const diffMs = now - then;
705
+ const diffMin = Math.floor(diffMs / 6e4);
706
+ const diffHour = Math.floor(diffMs / 36e5);
707
+ const diffDay = Math.floor(diffMs / 864e5);
708
+ if (diffMin < 1) return t("widget.time_now");
709
+ if (diffMin < 60) return `${diffMin}m`;
710
+ if (diffHour < 24) return `${diffHour}h`;
711
+ if (diffDay === 1) return t("widget.time_yesterday");
712
+ if (diffDay < 7) return `${diffDay}d`;
713
+ const d = typeof date === "string" ? new Date(date) : date;
714
+ return d.toLocaleDateString(getLocale(), { month: "short", day: "numeric" });
715
+ }
716
+ function formatTime(date) {
717
+ const d = typeof date === "string" ? new Date(date) : date;
718
+ return d.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
719
+ }
720
+ function resolveLabel(label) {
721
+ if (!label.startsWith("widget.")) return label;
722
+ const resolved = t(label);
723
+ if (resolved === label) {
724
+ return label.replace("widget.", "").replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
725
+ }
726
+ return resolved;
727
+ }
728
+ function getInitials(value) {
729
+ if (!value) return "?";
730
+ if (value.includes("@")) return (value[0] ?? "?").toUpperCase();
731
+ const parts = value.trim().split(/\s+/);
732
+ if (parts.length >= 2) {
733
+ return ((parts[0]?.[0] ?? "") + (parts[1]?.[0] ?? "")).toUpperCase();
734
+ }
735
+ return (parts[0]?.[0] ?? "?").toUpperCase();
736
+ }
737
+
738
+ // src/provider.tsx
739
+ import {
740
+ createContext,
741
+ useCallback,
742
+ useContext,
743
+ useEffect as useEffect2,
744
+ useRef,
745
+ useState
746
+ } from "react";
747
+ import { useStore } from "zustand";
748
+ import { jsx } from "react/jsx-runtime";
749
+ var WidgetContext = createContext(null);
750
+ function useWidgetContext() {
751
+ const ctx = useContext(WidgetContext);
752
+ if (!ctx) {
753
+ throw new Error("useWidgetContext must be used within <SupportProvider>");
754
+ }
755
+ return ctx;
756
+ }
757
+ function useWidgetStore(selector) {
758
+ const { store } = useWidgetContext();
759
+ return useStore(store, selector);
760
+ }
761
+ function useIdentify() {
762
+ const { identify } = useWidgetContext();
763
+ return identify;
764
+ }
765
+ function SupportProvider({
766
+ apiKey,
767
+ apiUrl,
768
+ locale,
769
+ initialConversationId,
770
+ onReady,
771
+ children
772
+ }) {
773
+ const initRef = useRef(null);
774
+ const [hasBootstrapError, setHasBootstrapError] = useState(false);
775
+ const [wsDisconnected, setWsDisconnected] = useState(false);
776
+ const [bootstrapAttempt, setBootstrapAttempt] = useState(0);
777
+ if (!initRef.current) {
778
+ const resolvedApiUrl = apiUrl ?? inferApiUrl();
779
+ const client2 = createWidgetClient({
780
+ apiUrl: resolvedApiUrl,
781
+ apiKey
782
+ });
783
+ const store2 = createWidgetStore();
784
+ const ws2 = new WebSocketManager({
785
+ apiUrl: resolvedApiUrl,
786
+ visitorId: "",
787
+ websiteId: ""
788
+ });
789
+ const identify2 = async (data) => {
790
+ try {
791
+ const result = await client2.widget.identify.mutate(data);
792
+ if (result.contact) {
793
+ store2.getState().updateVisitorContact({
794
+ id: result.contact.id,
795
+ name: result.contact.name ?? null,
796
+ email: result.contact.email ?? null
797
+ });
798
+ }
799
+ const metaLocale = data.metadata?.locale;
800
+ if (typeof metaLocale === "string" && metaLocale.length > 0) {
801
+ setLocale(metaLocale);
802
+ }
803
+ } catch (err) {
804
+ console.error("[Helpway] identify failed:", err);
805
+ }
806
+ };
807
+ initRef.current = { client: client2, store: store2, ws: ws2, identify: identify2 };
808
+ }
809
+ const { client, store, ws, identify } = initRef.current;
810
+ const retryBootstrap = useCallback(() => {
811
+ setHasBootstrapError(false);
812
+ setBootstrapAttempt((n) => n + 1);
813
+ }, []);
814
+ useEffect2(() => {
815
+ let cancelled = false;
816
+ async function bootstrap() {
817
+ try {
818
+ const data = await client.widget.bootstrap.query();
819
+ if (cancelled) return;
820
+ const resolvedLocale = locale ?? data.detectedLocale ?? void 0;
821
+ await initI18n({
822
+ locale: resolvedLocale,
823
+ cdnBase: data.agentConfig?.i18nCdnBase ?? void 0
824
+ });
825
+ store.getState().setReady(data.visitor, data.website, data.agentConfig);
826
+ setHasBootstrapError(false);
827
+ if (data.visitor?.id) {
828
+ syncVisitorId(apiKey, data.visitor.id);
829
+ }
830
+ const convData = await client.widget.listConversations.query();
831
+ if (!cancelled) {
832
+ store.getState().setConversations(
833
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- tRPC inferred type
834
+ convData.conversations.map(
835
+ (c) => ({
836
+ id: c.id,
837
+ title: c.title,
838
+ status: c.status,
839
+ lastMessageText: c.lastMessageText,
840
+ lastMessageAt: c.lastMessageAt,
841
+ unreadCount: 0
842
+ })
843
+ )
844
+ );
845
+ }
846
+ if (typeof window !== "undefined") {
847
+ client.widget.updatePage.mutate({
848
+ url: window.location.href,
849
+ title: document.title || window.location.pathname
850
+ }).catch(() => {
851
+ });
852
+ }
853
+ if (data.website?.id && data.visitor?.id) {
854
+ ws.configure({ websiteId: data.website.id, visitorId: data.visitor.id });
855
+ ws.connect();
856
+ }
857
+ if (initialConversationId && !cancelled) {
858
+ store.getState().setActiveConversation(initialConversationId);
859
+ store.getState().setOpen(true);
860
+ }
861
+ const openConversation = (id) => {
862
+ store.getState().setActiveConversation(id);
863
+ store.getState().setOpen(true);
864
+ };
865
+ onReady?.({ identify: initRef.current.identify, openConversation });
866
+ } catch (err) {
867
+ console.error("[Helpway] Bootstrap failed:", err);
868
+ if (!cancelled) {
869
+ store.getState().setConnecting(false);
870
+ setHasBootstrapError(true);
871
+ }
872
+ }
873
+ }
874
+ bootstrap();
875
+ let markSeenTimer = null;
876
+ const scheduleMarkSeen = (conversationId) => {
877
+ if (markSeenTimer) clearTimeout(markSeenTimer);
878
+ markSeenTimer = setTimeout(() => {
879
+ client.widget.markSeen.mutate({ conversationId }).catch(() => {
880
+ });
881
+ store.getState().clearUnread(conversationId);
882
+ }, 400);
883
+ };
884
+ const unsub = ws.onEvent((event) => {
885
+ store.getState().handleRealtimeEvent(event);
886
+ if (event.type === "timelineItemCreated") {
887
+ const item = event.item;
888
+ const isPublicIncoming = item?.visibility === "public" && !item.visitorId;
889
+ if (!isPublicIncoming) return;
890
+ const s = store.getState();
891
+ const isViewing = s.isOpen && s.activeConversationId === event.conversationId;
892
+ if (isViewing) scheduleMarkSeen(event.conversationId);
893
+ }
894
+ });
895
+ const wsCheck = setInterval(() => {
896
+ setWsDisconnected(!ws.isConnected);
897
+ }, 3e3);
898
+ return () => {
899
+ cancelled = true;
900
+ unsub();
901
+ clearInterval(wsCheck);
902
+ if (markSeenTimer) clearTimeout(markSeenTimer);
903
+ ws.disconnect();
904
+ };
905
+ }, [client, store, ws, bootstrapAttempt]);
906
+ useEffect2(() => {
907
+ if (locale != null) {
908
+ setLocale(locale);
909
+ }
910
+ }, [locale]);
911
+ const ctxValue = {
912
+ client,
913
+ store,
914
+ ws,
915
+ identify,
916
+ hasBootstrapError,
917
+ wsDisconnected,
918
+ retryBootstrap
919
+ };
920
+ return /* @__PURE__ */ jsx(WidgetContext.Provider, { value: ctxValue, children });
921
+ }
922
+ function inferApiUrl() {
923
+ if (typeof document !== "undefined") {
924
+ const script = document.querySelector("script[data-key][src]");
925
+ if (script) {
926
+ const src = script.getAttribute("src");
927
+ if (src) {
928
+ try {
929
+ const url = new URL(src);
930
+ return `${url.protocol}//${url.host}`;
931
+ } catch {
932
+ }
933
+ }
934
+ }
935
+ }
936
+ return typeof window !== "undefined" ? window.location.origin : "http://localhost:52500";
937
+ }
938
+
939
+ // src/hooks/use-widget.ts
940
+ function useWidget() {
941
+ const { client } = useWidgetContext();
942
+ const isReady = useWidgetStore((s) => s.isReady);
943
+ const isConnecting = useWidgetStore((s) => s.isConnecting);
944
+ const visitor = useWidgetStore((s) => s.visitor);
945
+ const website = useWidgetStore((s) => s.website);
946
+ const agentConfig = useWidgetStore((s) => s.agentConfig);
947
+ async function identify(data) {
948
+ return client.widget.identify.mutate(data);
949
+ }
950
+ return { isReady, isConnecting, visitor, website, agentConfig, identify };
951
+ }
952
+
953
+ // src/hooks/use-conversations.ts
954
+ import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
955
+ function useConversations() {
956
+ const { client, store } = useWidgetContext();
957
+ const conversations = useWidgetStore((s) => s.conversations);
958
+ const setActiveConversation = useWidgetStore((s) => s.setActiveConversation);
959
+ const [isCreating, setIsCreating] = useState2(false);
960
+ const clientRef = useRef2(client);
961
+ const storeRef = useRef2(store);
962
+ clientRef.current = client;
963
+ storeRef.current = store;
964
+ const create = useCallback2(async (text) => {
965
+ setIsCreating(true);
966
+ const tempConversationId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
967
+ const now = (/* @__PURE__ */ new Date()).toISOString();
968
+ const optimisticMessageId = `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
969
+ const currentVisitorId = storeRef.current.getState().visitor?.id ?? null;
970
+ const optimisticMsg = {
971
+ id: optimisticMessageId,
972
+ conversationId: tempConversationId,
973
+ organizationId: "",
974
+ type: "message",
975
+ visibility: "public",
976
+ text,
977
+ parts: null,
978
+ userId: null,
979
+ visitorId: currentVisitorId,
980
+ aiAgentId: null,
981
+ createdAt: now,
982
+ editedAt: null,
983
+ deletedAt: null
984
+ };
985
+ const store2 = storeRef.current.getState();
986
+ store2.addConversation({
987
+ id: tempConversationId,
988
+ title: null,
989
+ status: "open",
990
+ lastMessageText: text,
991
+ lastMessageAt: now,
992
+ unreadCount: 0
993
+ });
994
+ store2.setMessages(tempConversationId, [optimisticMsg]);
995
+ store2.setActiveConversation(tempConversationId);
996
+ try {
997
+ const result = await clientRef.current.widget.createConversation.mutate({ text });
998
+ const conv = result.conversation;
999
+ storeRef.current.getState().remapConversationId(tempConversationId, conv.id);
1000
+ if (result.item) {
1001
+ storeRef.current.getState().replaceMessage(conv.id, optimisticMessageId, result.item);
1002
+ }
1003
+ if (result.welcomeItem) {
1004
+ const welcome = result.welcomeItem;
1005
+ const existing = storeRef.current.getState().messages.get(conv.id) ?? [];
1006
+ const withoutWelcome = existing.filter((m) => m.id !== welcome.id);
1007
+ storeRef.current.getState().setMessages(conv.id, [welcome, ...withoutWelcome]);
1008
+ }
1009
+ return conv;
1010
+ } catch (err) {
1011
+ storeRef.current.getState().removeMessage(tempConversationId, optimisticMessageId);
1012
+ const convs = storeRef.current.getState().conversations.filter((c) => c.id !== tempConversationId);
1013
+ storeRef.current.setState({ conversations: convs, activeConversationId: null });
1014
+ throw err;
1015
+ } finally {
1016
+ setIsCreating(false);
1017
+ }
1018
+ }, []);
1019
+ return { conversations, create, isCreating, setActiveConversation };
1020
+ }
1021
+
1022
+ // src/hooks/use-timeline.ts
1023
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1024
+ var EMPTY_ITEMS = [];
1025
+ function useTimeline(conversationId) {
1026
+ const { client, store } = useWidgetContext();
1027
+ const messages = useWidgetStore(
1028
+ (s) => conversationId ? s.messages.get(conversationId) ?? EMPTY_ITEMS : EMPTY_ITEMS
1029
+ );
1030
+ const [isLoading, setIsLoading] = useState3(false);
1031
+ const [hasMore, setHasMore] = useState3(false);
1032
+ const cursorRef = useRef3(void 0);
1033
+ const clientRef = useRef3(client);
1034
+ const storeRef = useRef3(store);
1035
+ clientRef.current = client;
1036
+ storeRef.current = store;
1037
+ useEffect3(() => {
1038
+ if (!conversationId) return;
1039
+ if (conversationId.startsWith("temp-")) return;
1040
+ let cancelled = false;
1041
+ const existingMessages = storeRef.current.getState().messages.get(conversationId);
1042
+ const hasSeed = !!existingMessages && existingMessages.length > 0;
1043
+ if (hasSeed) {
1044
+ storeRef.current.getState().clearUnread(conversationId);
1045
+ clientRef.current.widget.markSeen.mutate({ conversationId }).catch(() => {
1046
+ });
1047
+ return () => {
1048
+ cancelled = true;
1049
+ };
1050
+ }
1051
+ setIsLoading(true);
1052
+ async function load() {
1053
+ try {
1054
+ const data = await clientRef.current.widget.getTimeline.query({
1055
+ conversationId,
1056
+ limit: 50
1057
+ });
1058
+ if (cancelled) return;
1059
+ if (data.items.length > 0) {
1060
+ storeRef.current.getState().setMessages(conversationId, data.items);
1061
+ }
1062
+ setHasMore(data.hasMore);
1063
+ cursorRef.current = data.nextCursor ?? void 0;
1064
+ storeRef.current.getState().clearUnread(conversationId);
1065
+ clientRef.current.widget.markSeen.mutate({ conversationId }).catch(() => {
1066
+ });
1067
+ } catch (err) {
1068
+ console.error("[useTimeline] Failed to load:", err);
1069
+ } finally {
1070
+ if (!cancelled) setIsLoading(false);
1071
+ }
1072
+ }
1073
+ load();
1074
+ return () => {
1075
+ cancelled = true;
1076
+ };
1077
+ }, [conversationId]);
1078
+ const loadMore = useCallback3(async () => {
1079
+ if (!conversationId || !hasMore || isLoading) return;
1080
+ setIsLoading(true);
1081
+ try {
1082
+ const data = await clientRef.current.widget.getTimeline.query({
1083
+ conversationId,
1084
+ cursor: cursorRef.current,
1085
+ limit: 50
1086
+ });
1087
+ const existing = storeRef.current.getState().messages.get(conversationId) ?? [];
1088
+ storeRef.current.getState().setMessages(conversationId, [...data.items, ...existing]);
1089
+ setHasMore(data.hasMore);
1090
+ cursorRef.current = data.nextCursor ?? void 0;
1091
+ } finally {
1092
+ setIsLoading(false);
1093
+ }
1094
+ }, [conversationId, hasMore, isLoading]);
1095
+ return { items: messages, isLoading, hasMore, loadMore };
1096
+ }
1097
+
1098
+ // src/hooks/use-composer.ts
1099
+ import { useCallback as useCallback4, useState as useState4 } from "react";
1100
+ var MAX_ATTACHMENTS = 10;
1101
+ var MAX_FILE_SIZE = 10 * 1024 * 1024;
1102
+ function useComposer(conversationId) {
1103
+ const { client, ws, store } = useWidgetContext();
1104
+ const [text, setText] = useState4("");
1105
+ const [isSubmitting, setIsSubmitting] = useState4(false);
1106
+ const [attachments, setAttachments] = useState4([]);
1107
+ const [sendError, setSendError] = useState4(null);
1108
+ const addFiles = useCallback4(
1109
+ async (files) => {
1110
+ if (!conversationId) return;
1111
+ const remaining = MAX_ATTACHMENTS - attachments.length;
1112
+ const toAdd = files.slice(0, remaining);
1113
+ if (toAdd.length === 0) return;
1114
+ const pending = toAdd.map((file) => ({
1115
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1116
+ file,
1117
+ previewUrl: file.type.startsWith("image/") ? URL.createObjectURL(file) : null,
1118
+ isUploading: true,
1119
+ error: null,
1120
+ part: null
1121
+ }));
1122
+ setAttachments((prev) => [...prev, ...pending]);
1123
+ for (const item of pending) {
1124
+ try {
1125
+ if (item.file.size > MAX_FILE_SIZE) {
1126
+ throw new Error(t("widget.file_too_large"));
1127
+ }
1128
+ const base64 = await fileToBase64(item.file);
1129
+ const convIdForUpload = conversationId.startsWith("temp-") ? void 0 : conversationId;
1130
+ const result = await client.widget.uploadAttachment.mutate({
1131
+ ...convIdForUpload ? { conversationId: convIdForUpload } : {},
1132
+ base64,
1133
+ mimeType: item.file.type || "application/octet-stream",
1134
+ fileName: item.file.name
1135
+ });
1136
+ setAttachments(
1137
+ (prev) => prev.map(
1138
+ (a) => a.id === item.id ? { ...a, isUploading: false, part: result.part } : a
1139
+ )
1140
+ );
1141
+ } catch (err) {
1142
+ const message = err instanceof Error ? err.message : "Upload failed";
1143
+ setAttachments(
1144
+ (prev) => prev.map((a) => a.id === item.id ? { ...a, isUploading: false, error: message } : a)
1145
+ );
1146
+ }
1147
+ }
1148
+ },
1149
+ [conversationId, attachments.length, client]
1150
+ );
1151
+ const removeAttachment = useCallback4((id) => {
1152
+ setAttachments((prev) => {
1153
+ const item = prev.find((a) => a.id === id);
1154
+ if (item?.previewUrl) URL.revokeObjectURL(item.previewUrl);
1155
+ return prev.filter((a) => a.id !== id);
1156
+ });
1157
+ }, []);
1158
+ const submit = useCallback4(async () => {
1159
+ const trimmed = text.trim();
1160
+ const readyParts = attachments.filter((a) => a.part !== null).map((a) => a.part);
1161
+ const hasText = trimmed.length > 0;
1162
+ const hasAttachments = readyParts.length > 0;
1163
+ if (!conversationId || isSubmitting) return;
1164
+ if (!hasText) {
1165
+ if (hasAttachments) {
1166
+ setSendError(t("widget.caption_required"));
1167
+ setTimeout(() => setSendError(null), 4e3);
1168
+ }
1169
+ return;
1170
+ }
1171
+ const stillUploading = attachments.some((a) => a.isUploading);
1172
+ if (stillUploading) return;
1173
+ const optimisticId = `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
1174
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1175
+ const currentVisitorId = store.getState().visitor?.id ?? null;
1176
+ const optimisticItem = {
1177
+ id: optimisticId,
1178
+ conversationId,
1179
+ organizationId: "",
1180
+ type: "message",
1181
+ visibility: "public",
1182
+ text: hasText ? trimmed : null,
1183
+ parts: hasText || hasAttachments ? [
1184
+ ...hasText ? [{ type: "text", text: trimmed }] : [],
1185
+ ...readyParts
1186
+ ] : null,
1187
+ userId: null,
1188
+ visitorId: currentVisitorId,
1189
+ aiAgentId: null,
1190
+ createdAt: now,
1191
+ editedAt: null,
1192
+ deletedAt: null
1193
+ };
1194
+ const attachmentsSnapshot = attachments;
1195
+ const textSnapshot = text;
1196
+ store.getState().addMessage(conversationId, optimisticItem);
1197
+ setText("");
1198
+ setAttachments([]);
1199
+ ws.sendTyping(conversationId, false);
1200
+ setIsSubmitting(true);
1201
+ try {
1202
+ const isTempConversation = conversationId.startsWith("temp-");
1203
+ if (isTempConversation) {
1204
+ const result = await client.widget.createConversation.mutate({
1205
+ ...hasText ? { text: trimmed } : {},
1206
+ ...hasAttachments ? { parts: readyParts } : {}
1207
+ });
1208
+ const conv = result.conversation;
1209
+ store.getState().remapConversationId(conversationId, conv.id);
1210
+ if (result.item) {
1211
+ store.getState().replaceMessage(conv.id, optimisticId, result.item);
1212
+ }
1213
+ } else {
1214
+ const result = await client.widget.sendMessage.mutate({
1215
+ conversationId,
1216
+ ...hasText ? { text: trimmed } : {},
1217
+ ...hasAttachments ? { parts: readyParts } : {}
1218
+ });
1219
+ store.getState().replaceMessage(conversationId, optimisticId, result.item);
1220
+ }
1221
+ for (const a of attachmentsSnapshot) {
1222
+ if (a.previewUrl) URL.revokeObjectURL(a.previewUrl);
1223
+ }
1224
+ } catch (err) {
1225
+ console.error("[useComposer] Send failed:", err);
1226
+ store.getState().removeMessage(conversationId, optimisticId);
1227
+ setText(textSnapshot);
1228
+ setAttachments(attachmentsSnapshot);
1229
+ setSendError(err instanceof Error ? err.message : t("widget.send_error"));
1230
+ setTimeout(() => setSendError(null), 5e3);
1231
+ } finally {
1232
+ setIsSubmitting(false);
1233
+ }
1234
+ }, [text, attachments, conversationId, isSubmitting, client, ws, store]);
1235
+ const clearSendError = useCallback4(() => setSendError(null), []);
1236
+ return {
1237
+ text,
1238
+ setText,
1239
+ submit,
1240
+ isSubmitting,
1241
+ attachments,
1242
+ addFiles,
1243
+ removeAttachment,
1244
+ sendError,
1245
+ clearSendError
1246
+ };
1247
+ }
1248
+ function fileToBase64(file) {
1249
+ return new Promise((resolve, reject) => {
1250
+ const reader = new FileReader();
1251
+ reader.onload = () => {
1252
+ const result = reader.result;
1253
+ const base64 = result.split(",")[1];
1254
+ if (!base64) return reject(new Error("Failed to encode file"));
1255
+ resolve(base64);
1256
+ };
1257
+ reader.onerror = () => reject(new Error("Failed to read file"));
1258
+ reader.readAsDataURL(file);
1259
+ });
1260
+ }
1261
+
1262
+ // src/hooks/use-presence.ts
1263
+ import { useCallback as useCallback5, useRef as useRef4 } from "react";
1264
+ function usePresence(conversationId) {
1265
+ const { ws } = useWidgetContext();
1266
+ const isTyping = useWidgetStore(
1267
+ (s) => conversationId ? s.typingUsers.get(conversationId) ?? false : false
1268
+ );
1269
+ const isAiProcessing = useWidgetStore(
1270
+ (s) => conversationId ? s.aiProcessing.get(conversationId) ?? false : false
1271
+ );
1272
+ const typingTimer = useRef4(null);
1273
+ const isSendingTyping = useRef4(false);
1274
+ const setTyping = useCallback5(
1275
+ (typing) => {
1276
+ if (!conversationId) return;
1277
+ if (typingTimer.current) clearTimeout(typingTimer.current);
1278
+ if (typing) {
1279
+ if (!isSendingTyping.current) {
1280
+ isSendingTyping.current = true;
1281
+ ws.sendTyping(conversationId, true);
1282
+ }
1283
+ typingTimer.current = setTimeout(() => {
1284
+ isSendingTyping.current = false;
1285
+ ws.sendTyping(conversationId, false);
1286
+ }, 3e3);
1287
+ } else {
1288
+ if (isSendingTyping.current) {
1289
+ isSendingTyping.current = false;
1290
+ ws.sendTyping(conversationId, false);
1291
+ }
1292
+ }
1293
+ },
1294
+ [conversationId, ws]
1295
+ );
1296
+ return { isTyping, isAiProcessing, setTyping };
1297
+ }
1298
+
1299
+ // src/hooks/use-unread.ts
1300
+ function useUnread() {
1301
+ const totalUnread = useWidgetStore(
1302
+ (s) => s.conversations.reduce((sum, c) => sum + c.unreadCount, 0)
1303
+ );
1304
+ return { totalUnread };
1305
+ }
1306
+
1307
+ // src/hooks/use-realtime.ts
1308
+ import { useEffect as useEffect4 } from "react";
1309
+ function useRealtime(handler) {
1310
+ const { ws } = useWidgetContext();
1311
+ useEffect4(() => {
1312
+ return ws.onEvent(handler);
1313
+ }, [ws, handler]);
1314
+ }
1315
+
1316
+ // src/themed/default-widget.tsx
1317
+ import { useCallback as useCallback12, useEffect as useEffect12, useRef as useRef13, useState as useState12 } from "react";
1318
+
1319
+ // src/hooks/use-i18n.ts
1320
+ import { useEffect as useEffect5, useState as useState5 } from "react";
1321
+ function useI18n() {
1322
+ const [rev, setRev] = useState5(() => getI18nRevision());
1323
+ useEffect5(() => {
1324
+ return onLocaleChange(() => {
1325
+ setRev(getI18nRevision());
1326
+ });
1327
+ }, []);
1328
+ return rev;
1329
+ }
1330
+
1331
+ // src/themed/composer.tsx
1332
+ import { useCallback as useCallback7, useRef as useRef7, useState as useState7 } from "react";
1333
+
1334
+ // src/themed/icons.tsx
1335
+ import {
1336
+ IconBubble2,
1337
+ IconCrossMedium,
1338
+ IconArrowLeft,
1339
+ IconChevronRight,
1340
+ IconSparklesSoft,
1341
+ IconPaperPlaneTopRight,
1342
+ IconPaperclip1,
1343
+ IconArrowUp,
1344
+ IconHome,
1345
+ IconBubbleDots,
1346
+ IconMagnifyingGlass,
1347
+ IconBubblePlus,
1348
+ IconAppleIntelligence,
1349
+ IconBook,
1350
+ IconMegaphone,
1351
+ IconLightBulb,
1352
+ IconCircleQuestionmark,
1353
+ IconNewspaper,
1354
+ IconStar
1355
+ } from "@central-icons-react/round-outlined-radius-2-stroke-2";
1356
+
1357
+ // src/themed/message-bubble.tsx
1358
+ import { useCallback as useCallback6, useLayoutEffect, useRef as useRef6, useState as useState6 } from "react";
1359
+ import { createPortal } from "react-dom";
1360
+
1361
+ // src/themed/content/simple-markdown.tsx
1362
+ import { useEffect as useEffect6, useRef as useRef5 } from "react";
1363
+ import { jsx as jsx2 } from "react/jsx-runtime";
1364
+ function renderMarkdown(md) {
1365
+ const codeBlocks = [];
1366
+ const codeBlockPlaceholder = (i) => `\0CODEBLOCK${i}\0`;
1367
+ let body = md.replace(
1368
+ // Eat ALL newlines around the fence (zero-or-more both sides). The
1369
+ // replacement re-emits its own `\n\n` pair so the paragraph splitter
1370
+ // cleanly bookends the fence. Without this, `before\n\n` + `\n\n` from
1371
+ // us = 3 newlines, the extra one becomes <br /> and lands inside a <p>.
1372
+ /\n*```(\w+)?\n?([\s\S]*?)```\n*/g,
1373
+ (_match, lang, code) => {
1374
+ const escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1375
+ const className = lang ? ` class="language-${lang}"` : "";
1376
+ const langLabel = lang ?? "code";
1377
+ const trimmed = escaped.replace(/\n$/, "");
1378
+ codeBlocks.push(
1379
+ `<div class="hw-code" data-hw-code="1"><div class="hw-code-header"><span class="hw-code-lang">${langLabel}</span><button type="button" class="hw-code-copy" aria-label="Copy code"><span class="hw-code-copy-label">Copy</span></button></div><pre><code${className}>${trimmed}</code></pre></div>`
1380
+ );
1381
+ return `
1382
+
1383
+ ${codeBlockPlaceholder(codeBlocks.length - 1)}
1384
+
1385
+ `;
1386
+ }
1387
+ );
1388
+ let html = body.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/^---$/gm, "<hr />").replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />').replace(
1389
+ /\[([^\]]+)\]\(([^)]+)\)/g,
1390
+ '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
1391
+ ).replace(
1392
+ /(?<!")(?<!=)(https?:\/\/[^\s<)]+)/g,
1393
+ '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>'
1394
+ ).replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>").replace(/^[*-] (.+)$/gm, "<li>$1</li>").replace(/\n\n/g, "</p><p>").replace(/\n/g, "<br />");
1395
+ html = html.replace(/((?:<li>.*?<\/li>\s*)+)/g, "<ul>$1</ul>");
1396
+ if (!html.startsWith("<h") && !html.startsWith("<ul") && !html.startsWith("<blockquote") && !html.startsWith("<hr")) {
1397
+ html = `<p>${html}</p>`;
1398
+ }
1399
+ if (codeBlocks.length > 0) {
1400
+ html = html.replace(/\u0000CODEBLOCK(\d+)\u0000/g, (_m, idx) => {
1401
+ const i = Number.parseInt(idx, 10);
1402
+ return codeBlocks[i] ?? "";
1403
+ });
1404
+ html = html.replace(
1405
+ /<p>(\s*<div class="hw-code"[\s\S]*?<\/div>\s*)<\/p>/g,
1406
+ "$1"
1407
+ );
1408
+ }
1409
+ return html;
1410
+ }
1411
+ function linkifyText(text) {
1412
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(
1413
+ /(https?:\/\/[^\s<]+)/g,
1414
+ '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>'
1415
+ ).replace(/\n/g, "<br />");
1416
+ }
1417
+ function MarkdownContent({
1418
+ bodyHtml,
1419
+ bodyMarkdown,
1420
+ className
1421
+ }) {
1422
+ const rootRef = useRef5(null);
1423
+ const html = bodyHtml ?? (bodyMarkdown ? renderMarkdown(bodyMarkdown) : "");
1424
+ useEffect6(() => {
1425
+ const root = rootRef.current;
1426
+ if (!root) return;
1427
+ const handler = (e) => {
1428
+ const target = e.target;
1429
+ const btn = target?.closest(".hw-code-copy");
1430
+ if (!btn || !root.contains(btn)) return;
1431
+ const block = btn.closest(".hw-code");
1432
+ const code = block?.querySelector("pre code");
1433
+ const text = code?.textContent ?? "";
1434
+ if (!text) return;
1435
+ void navigator.clipboard?.writeText(text).then(
1436
+ () => flashCopied(btn),
1437
+ () => flashCopied(btn, true)
1438
+ );
1439
+ };
1440
+ root.addEventListener("click", handler);
1441
+ return () => {
1442
+ root.removeEventListener("click", handler);
1443
+ };
1444
+ }, [html]);
1445
+ if (!html) return null;
1446
+ return /* @__PURE__ */ jsx2(
1447
+ "div",
1448
+ {
1449
+ ref: rootRef,
1450
+ className: `hw-markdown ${className ?? ""}`,
1451
+ dangerouslySetInnerHTML: { __html: html }
1452
+ }
1453
+ );
1454
+ }
1455
+ function flashCopied(btn, failed = false) {
1456
+ const label = btn.querySelector(".hw-code-copy-label");
1457
+ if (!label) return;
1458
+ const original = label.textContent ?? "Copy";
1459
+ label.textContent = failed ? "Failed" : "Copied";
1460
+ btn.classList.add(failed ? "hw-code-copy--failed" : "hw-code-copy--copied");
1461
+ setTimeout(() => {
1462
+ label.textContent = original;
1463
+ btn.classList.remove("hw-code-copy--copied", "hw-code-copy--failed");
1464
+ }, 1500);
1465
+ }
1466
+
1467
+ // src/themed/message-bubble.tsx
1468
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
1469
+ function MessageBubble({ text, sender, senderName, time, parts }) {
1470
+ const senderClass = `hw-msg--${sender}`;
1471
+ const agentName = useWidgetStore((s) => s.agentConfig?.name) ?? t("widget.better_ai");
1472
+ const relativeTime = formatRelativeTime(time);
1473
+ const meta = sender === "ai" ? `${agentName} \xB7 ${relativeTime}` : relativeTime;
1474
+ const imageParts = parts?.filter((p) => p.type === "image") ?? [];
1475
+ const fileParts = parts?.filter((p) => p.type === "file") ?? [];
1476
+ const hasText = text.length > 0;
1477
+ return /* @__PURE__ */ jsx3("div", { className: `hw-msg ${senderClass}`, children: /* @__PURE__ */ jsxs("div", { className: "hw-msg-body", children: [
1478
+ sender === "team" && /* @__PURE__ */ jsx3("div", { className: "hw-msg-sender", children: senderName ?? t("widget.support") }),
1479
+ imageParts.length > 0 && /* @__PURE__ */ jsx3("div", { className: "hw-msg-images", children: imageParts.map((img, i) => /* @__PURE__ */ jsx3(PreviewableThumbnail, { src: img.url, type: "image" }, i)) }),
1480
+ hasText && /* @__PURE__ */ jsx3("div", { className: "hw-msg-bubble", children: sender === "ai" || sender === "team" ? /* @__PURE__ */ jsx3("span", { dangerouslySetInnerHTML: { __html: renderMarkdown(text) } }) : /* @__PURE__ */ jsx3("span", { dangerouslySetInnerHTML: { __html: linkifyText(text) } }) }),
1481
+ fileParts.length > 0 && /* @__PURE__ */ jsx3("div", { className: "hw-msg-files", children: fileParts.map((file, i) => /* @__PURE__ */ jsx3(FileCard, { file }, i)) }),
1482
+ /* @__PURE__ */ jsxs("div", { className: "hw-msg-meta", children: [
1483
+ meta,
1484
+ hasText && /* @__PURE__ */ jsx3(CopyButton, { text })
1485
+ ] })
1486
+ ] }) });
1487
+ }
1488
+ function PreviewableThumbnail({ src, type }) {
1489
+ const [hover, setHover] = useState6(false);
1490
+ const ref = useRef6(null);
1491
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1492
+ /* @__PURE__ */ jsx3(
1493
+ "a",
1494
+ {
1495
+ ref,
1496
+ href: src,
1497
+ target: "_blank",
1498
+ rel: "noopener noreferrer",
1499
+ className: "hw-msg-image-link",
1500
+ onMouseEnter: () => setHover(true),
1501
+ onMouseLeave: () => setHover(false),
1502
+ children: /* @__PURE__ */ jsx3("img", { src, alt: "", className: "hw-msg-image", loading: "lazy" })
1503
+ }
1504
+ ),
1505
+ hover && /* @__PURE__ */ jsx3(AnchoredPreview, { src, anchorRef: ref, href: src })
1506
+ ] });
1507
+ }
1508
+ var PREVIEWABLE_EXTS = /* @__PURE__ */ new Set(["pdf", "csv", "png", "jpg", "jpeg", "gif", "webp", "svg"]);
1509
+ function FileCard({ file }) {
1510
+ const [hover, setHover] = useState6(false);
1511
+ const ref = useRef6(null);
1512
+ const ext = file.fileName.split(".").pop()?.toLowerCase() ?? "";
1513
+ const isPreviewable = PREVIEWABLE_EXTS.has(ext);
1514
+ const isImage = file.mimeType.startsWith("image/");
1515
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1516
+ /* @__PURE__ */ jsxs(
1517
+ "a",
1518
+ {
1519
+ ref,
1520
+ href: file.url,
1521
+ target: "_blank",
1522
+ rel: "noopener noreferrer",
1523
+ className: "hw-msg-file",
1524
+ onMouseEnter: () => isPreviewable && setHover(true),
1525
+ onMouseLeave: () => setHover(false),
1526
+ children: [
1527
+ /* @__PURE__ */ jsx3("span", { className: "hw-msg-file-icon", children: /* @__PURE__ */ jsx3(FileTypeIcon, { ext }) }),
1528
+ /* @__PURE__ */ jsxs("span", { className: "hw-msg-file-info", children: [
1529
+ /* @__PURE__ */ jsx3("span", { className: "hw-msg-file-name", children: file.fileName }),
1530
+ /* @__PURE__ */ jsx3("span", { className: "hw-msg-file-size", children: formatFileSize(file.fileSize) })
1531
+ ] })
1532
+ ]
1533
+ }
1534
+ ),
1535
+ hover && isPreviewable && (isImage ? /* @__PURE__ */ jsx3(AnchoredPreview, { src: file.url, anchorRef: ref, href: file.url }) : /* @__PURE__ */ jsx3(AnchoredDocPreview, { fileName: file.fileName, ext, anchorRef: ref, href: file.url }))
1536
+ ] });
1537
+ }
1538
+ function AnchoredPreview({
1539
+ src,
1540
+ anchorRef,
1541
+ href,
1542
+ onOpen
1543
+ }) {
1544
+ const [style, setStyle] = useState6({ opacity: 0 });
1545
+ const [portalTarget, setPortalTarget] = useState6(null);
1546
+ useLayoutEffect(() => {
1547
+ const anchor = anchorRef.current;
1548
+ if (!anchor) return;
1549
+ const rect = anchor.getBoundingClientRect();
1550
+ const pw = 260;
1551
+ const gap = 8;
1552
+ const vw = window.innerWidth;
1553
+ let left = rect.left + rect.width / 2 - pw / 2;
1554
+ if (left < 8) left = 8;
1555
+ if (left + pw > vw - 8) left = vw - pw - 8;
1556
+ const estimatedH = 200;
1557
+ let top = rect.top - estimatedH - gap;
1558
+ if (top < 8) top = rect.bottom + gap;
1559
+ setStyle({ left, top, width: pw, opacity: 1 });
1560
+ const root = anchor.getRootNode();
1561
+ setPortalTarget(root instanceof ShadowRoot ? root : document.body);
1562
+ }, [anchorRef]);
1563
+ const handleClick = () => {
1564
+ if (onOpen) onOpen();
1565
+ else if (href) window.open(href, "_blank");
1566
+ };
1567
+ if (!portalTarget) return null;
1568
+ return createPortal(
1569
+ /* @__PURE__ */ jsx3("div", { className: "hw-hover-preview", style, onClick: handleClick, children: /* @__PURE__ */ jsx3("img", { src, alt: "", className: "hw-hover-preview-img" }) }),
1570
+ portalTarget
1571
+ );
1572
+ }
1573
+ function AnchoredDocPreview({
1574
+ fileName,
1575
+ ext,
1576
+ anchorRef,
1577
+ href
1578
+ }) {
1579
+ const [style, setStyle] = useState6({ opacity: 0 });
1580
+ const [portalTarget, setPortalTarget] = useState6(null);
1581
+ useLayoutEffect(() => {
1582
+ const anchor = anchorRef.current;
1583
+ if (!anchor) return;
1584
+ const rect = anchor.getBoundingClientRect();
1585
+ const pw = 220;
1586
+ const ph = 80;
1587
+ const gap = 8;
1588
+ const vw = window.innerWidth;
1589
+ let left = rect.left + rect.width / 2 - pw / 2;
1590
+ if (left < 8) left = 8;
1591
+ if (left + pw > vw - 8) left = vw - pw - 8;
1592
+ let top = rect.top - ph - gap;
1593
+ if (top < 8) top = rect.bottom + gap;
1594
+ setStyle({ left, top, width: pw, opacity: 1 });
1595
+ const root = anchor.getRootNode();
1596
+ setPortalTarget(root instanceof ShadowRoot ? root : document.body);
1597
+ }, [anchorRef]);
1598
+ if (!portalTarget) return null;
1599
+ return createPortal(
1600
+ /* @__PURE__ */ jsxs(
1601
+ "div",
1602
+ {
1603
+ className: "hw-hover-preview hw-hover-preview--doc",
1604
+ style,
1605
+ onClick: () => window.open(href, "_blank"),
1606
+ children: [
1607
+ /* @__PURE__ */ jsx3("div", { className: "hw-hover-preview-doc-icon", children: /* @__PURE__ */ jsx3(FileTypeIcon, { ext }) }),
1608
+ /* @__PURE__ */ jsxs("div", { className: "hw-hover-preview-doc-info", children: [
1609
+ /* @__PURE__ */ jsx3("span", { className: "hw-hover-preview-doc-name", children: fileName }),
1610
+ /* @__PURE__ */ jsx3("span", { className: "hw-hover-preview-doc-hint", children: t("widget.click_to_open") })
1611
+ ] })
1612
+ ]
1613
+ }
1614
+ ),
1615
+ portalTarget
1616
+ );
1617
+ }
1618
+ function formatFileSize(bytes) {
1619
+ if (bytes < 1024) return `${bytes} B`;
1620
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
1621
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1622
+ }
1623
+ function CopyButton({ text }) {
1624
+ const [copied, setCopied] = useState6(false);
1625
+ const handleCopy = useCallback6(
1626
+ async (e) => {
1627
+ e.stopPropagation();
1628
+ try {
1629
+ await navigator.clipboard.writeText(text);
1630
+ setCopied(true);
1631
+ setTimeout(() => setCopied(false), 2e3);
1632
+ } catch {
1633
+ }
1634
+ },
1635
+ [text]
1636
+ );
1637
+ return /* @__PURE__ */ jsx3(
1638
+ "button",
1639
+ {
1640
+ type: "button",
1641
+ className: `hw-msg-copy ${copied ? "hw-msg-copy--done" : ""}`,
1642
+ onClick: handleCopy,
1643
+ "aria-label": copied ? t("widget.copied") : t("widget.copy"),
1644
+ children: copied ? /* @__PURE__ */ jsx3(
1645
+ "svg",
1646
+ {
1647
+ viewBox: "0 0 24 24",
1648
+ fill: "none",
1649
+ stroke: "currentColor",
1650
+ strokeWidth: 2,
1651
+ strokeLinecap: "round",
1652
+ strokeLinejoin: "round",
1653
+ children: /* @__PURE__ */ jsx3("polyline", { points: "20 6 9 17 4 12" })
1654
+ }
1655
+ ) : /* @__PURE__ */ jsxs(
1656
+ "svg",
1657
+ {
1658
+ viewBox: "0 0 24 24",
1659
+ fill: "none",
1660
+ stroke: "currentColor",
1661
+ strokeWidth: 2,
1662
+ strokeLinecap: "round",
1663
+ strokeLinejoin: "round",
1664
+ children: [
1665
+ /* @__PURE__ */ jsx3("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
1666
+ /* @__PURE__ */ jsx3("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
1667
+ ]
1668
+ }
1669
+ )
1670
+ }
1671
+ );
1672
+ }
1673
+ function FileTypeIcon({ ext }) {
1674
+ const colors = {
1675
+ pdf: "#ef4444",
1676
+ csv: "#22c55e",
1677
+ doc: "#3b82f6",
1678
+ docx: "#3b82f6",
1679
+ xls: "#22c55e",
1680
+ xlsx: "#22c55e",
1681
+ txt: "#6b7280",
1682
+ zip: "#f59e0b"
1683
+ };
1684
+ const color = colors[ext] ?? "currentColor";
1685
+ return /* @__PURE__ */ jsxs(
1686
+ "svg",
1687
+ {
1688
+ viewBox: "0 0 24 24",
1689
+ fill: "none",
1690
+ stroke: color,
1691
+ strokeWidth: 1.5,
1692
+ strokeLinecap: "round",
1693
+ strokeLinejoin: "round",
1694
+ children: [
1695
+ /* @__PURE__ */ jsx3("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1696
+ /* @__PURE__ */ jsx3("polyline", { points: "14 2 14 8 20 8" }),
1697
+ ext === "pdf" && /* @__PURE__ */ jsx3("text", { x: "8", y: "18", fontSize: "6", fill: color, stroke: "none", fontWeight: "700", children: "PDF" }),
1698
+ ext === "csv" && /* @__PURE__ */ jsx3("text", { x: "7", y: "18", fontSize: "6", fill: color, stroke: "none", fontWeight: "700", children: "CSV" })
1699
+ ]
1700
+ }
1701
+ );
1702
+ }
1703
+
1704
+ // src/themed/composer.tsx
1705
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1706
+ var MAX_CHARS = 5e3;
1707
+ var CHAR_WARN_THRESHOLD = 4800;
1708
+ var ACCEPTED_TYPES = [
1709
+ "image/png",
1710
+ "image/jpeg",
1711
+ "image/gif",
1712
+ "image/webp",
1713
+ "image/svg+xml",
1714
+ "application/pdf",
1715
+ "text/plain",
1716
+ "text/csv",
1717
+ "application/zip",
1718
+ "application/msword",
1719
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1720
+ "application/vnd.ms-excel",
1721
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1722
+ ].join(",");
1723
+ function Composer({
1724
+ text,
1725
+ onTextChange,
1726
+ onSubmit,
1727
+ onTyping,
1728
+ isSubmitting,
1729
+ placeholder,
1730
+ compact,
1731
+ attachments,
1732
+ onAddFiles,
1733
+ onRemoveAttachment,
1734
+ disabled
1735
+ }) {
1736
+ const ref = useRef7(null);
1737
+ const fileInputRef = useRef7(null);
1738
+ const handleKeyDown = useCallback7(
1739
+ (e) => {
1740
+ if (e.key === "Enter" && !e.shiftKey) {
1741
+ e.preventDefault();
1742
+ onSubmit();
1743
+ }
1744
+ },
1745
+ [onSubmit]
1746
+ );
1747
+ const handleChange = useCallback7(
1748
+ (e) => {
1749
+ const value = e.target.value;
1750
+ if (value.length > MAX_CHARS) return;
1751
+ onTextChange(value);
1752
+ onTyping?.(value.length > 0);
1753
+ const el = ref.current;
1754
+ if (el) {
1755
+ el.style.height = "auto";
1756
+ el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
1757
+ }
1758
+ },
1759
+ [onTextChange, onTyping]
1760
+ );
1761
+ const handlePaste = useCallback7(
1762
+ (e) => {
1763
+ if (!onAddFiles) return;
1764
+ const files = extractFiles(e.clipboardData);
1765
+ if (files.length > 0) {
1766
+ e.preventDefault();
1767
+ onAddFiles(files);
1768
+ }
1769
+ },
1770
+ [onAddFiles]
1771
+ );
1772
+ const [isDragging, setIsDragging] = useState7(false);
1773
+ const dragCountRef = useRef7(0);
1774
+ const handleDragEnter = useCallback7((e) => {
1775
+ e.preventDefault();
1776
+ dragCountRef.current++;
1777
+ if (dragCountRef.current === 1) setIsDragging(true);
1778
+ }, []);
1779
+ const handleDragLeave = useCallback7((e) => {
1780
+ e.preventDefault();
1781
+ dragCountRef.current--;
1782
+ if (dragCountRef.current === 0) setIsDragging(false);
1783
+ }, []);
1784
+ const handleDragOver = useCallback7((e) => {
1785
+ e.preventDefault();
1786
+ e.dataTransfer.dropEffect = "copy";
1787
+ }, []);
1788
+ const handleDrop = useCallback7(
1789
+ (e) => {
1790
+ e.preventDefault();
1791
+ dragCountRef.current = 0;
1792
+ setIsDragging(false);
1793
+ if (!onAddFiles) return;
1794
+ const files = extractFiles(e.dataTransfer);
1795
+ if (files.length > 0) onAddFiles(files);
1796
+ },
1797
+ [onAddFiles]
1798
+ );
1799
+ const handleFileClick = useCallback7(() => {
1800
+ fileInputRef.current?.click();
1801
+ }, []);
1802
+ const handleFileChange = useCallback7(
1803
+ (e) => {
1804
+ if (!onAddFiles || !e.target.files) return;
1805
+ onAddFiles(Array.from(e.target.files));
1806
+ e.target.value = "";
1807
+ },
1808
+ [onAddFiles]
1809
+ );
1810
+ const hasText = text.trim().length > 0;
1811
+ const hasAttachments = attachments && attachments.some((a) => a.part !== null);
1812
+ const stillUploading = attachments?.some((a) => a.isUploading) ?? false;
1813
+ const canSend = (hasText || hasAttachments) && !stillUploading;
1814
+ const showCharCount = text.length >= CHAR_WARN_THRESHOLD;
1815
+ const isOverLimit = text.length > MAX_CHARS;
1816
+ return /* @__PURE__ */ jsx4(
1817
+ "div",
1818
+ {
1819
+ className: `hw-composer ${compact ? "hw-composer--compact" : ""} ${isDragging ? "hw-composer--dragging" : ""}`,
1820
+ onDragEnter: handleDragEnter,
1821
+ onDragLeave: handleDragLeave,
1822
+ onDragOver: handleDragOver,
1823
+ onDrop: handleDrop,
1824
+ children: /* @__PURE__ */ jsxs2("div", { className: "hw-composer-box", style: { position: "relative" }, children: [
1825
+ isDragging && /* @__PURE__ */ jsx4("div", { className: "hw-composer-drop-overlay", children: /* @__PURE__ */ jsx4("span", { className: "hw-composer-drop-text", children: t("widget.drop_files") }) }),
1826
+ attachments && attachments.length > 0 && /* @__PURE__ */ jsx4("div", { className: "hw-composer-previews", children: attachments.map((a) => /* @__PURE__ */ jsx4(
1827
+ PreviewThumb,
1828
+ {
1829
+ attachment: a,
1830
+ onRemove: onRemoveAttachment ? () => onRemoveAttachment(a.id) : void 0
1831
+ },
1832
+ a.id
1833
+ )) }),
1834
+ /* @__PURE__ */ jsx4(
1835
+ "textarea",
1836
+ {
1837
+ ref,
1838
+ className: "hw-composer-input",
1839
+ value: text,
1840
+ onChange: handleChange,
1841
+ onKeyDown: handleKeyDown,
1842
+ onPaste: handlePaste,
1843
+ placeholder: placeholder ?? t("widget.placeholder"),
1844
+ rows: 1,
1845
+ disabled: disabled || isSubmitting,
1846
+ "aria-label": t("widget.aria_input")
1847
+ }
1848
+ ),
1849
+ /* @__PURE__ */ jsxs2("div", { className: "hw-composer-toolbar", children: [
1850
+ /* @__PURE__ */ jsxs2("div", { className: "hw-composer-toolbar-left", children: [
1851
+ /* @__PURE__ */ jsx4(
1852
+ "button",
1853
+ {
1854
+ type: "button",
1855
+ className: "hw-composer-attach",
1856
+ onClick: handleFileClick,
1857
+ disabled: disabled || isSubmitting,
1858
+ "aria-label": t("widget.aria_attach"),
1859
+ children: /* @__PURE__ */ jsx4(IconPaperclip1, {})
1860
+ }
1861
+ ),
1862
+ /* @__PURE__ */ jsx4(
1863
+ "input",
1864
+ {
1865
+ ref: fileInputRef,
1866
+ type: "file",
1867
+ accept: ACCEPTED_TYPES,
1868
+ multiple: true,
1869
+ hidden: true,
1870
+ onChange: handleFileChange
1871
+ }
1872
+ ),
1873
+ showCharCount && /* @__PURE__ */ jsx4("span", { className: `hw-composer-charcount ${isOverLimit ? "hw-composer-charcount--over" : ""}`, children: MAX_CHARS - text.length })
1874
+ ] }),
1875
+ /* @__PURE__ */ jsx4(
1876
+ "button",
1877
+ {
1878
+ type: "button",
1879
+ className: `hw-composer-send ${canSend && !isOverLimit && !disabled ? "hw-composer-send--active" : "hw-composer-send--inactive"}`,
1880
+ onClick: onSubmit,
1881
+ disabled: disabled || !canSend || isSubmitting || isOverLimit,
1882
+ "aria-label": t("widget.aria_send"),
1883
+ children: /* @__PURE__ */ jsx4(IconPaperPlaneTopRight, {})
1884
+ }
1885
+ )
1886
+ ] })
1887
+ ] })
1888
+ }
1889
+ );
1890
+ }
1891
+ function PreviewThumb({ attachment: a, onRemove }) {
1892
+ const [hover, setHover] = useState7(false);
1893
+ const ref = useRef7(null);
1894
+ return /* @__PURE__ */ jsxs2(
1895
+ "div",
1896
+ {
1897
+ ref,
1898
+ className: `hw-composer-preview ${a.error ? "hw-composer-preview--error" : ""}`,
1899
+ onMouseEnter: () => setHover(true),
1900
+ onMouseLeave: () => setHover(false),
1901
+ children: [
1902
+ a.previewUrl ? /* @__PURE__ */ jsx4("img", { src: a.previewUrl, alt: a.file.name, className: "hw-composer-preview-img" }) : /* @__PURE__ */ jsx4("div", { className: "hw-composer-preview-file", children: /* @__PURE__ */ jsx4("span", { className: "hw-composer-preview-ext", children: a.file.name.split(".").pop()?.toUpperCase() ?? "FILE" }) }),
1903
+ a.isUploading && /* @__PURE__ */ jsx4("div", { className: "hw-composer-preview-loader" }),
1904
+ a.error && /* @__PURE__ */ jsx4("div", { className: "hw-composer-preview-badge", title: a.error, children: "!" }),
1905
+ onRemove && /* @__PURE__ */ jsx4(
1906
+ "button",
1907
+ {
1908
+ type: "button",
1909
+ className: "hw-composer-preview-remove",
1910
+ onClick: onRemove,
1911
+ "aria-label": t("widget.aria_remove"),
1912
+ children: /* @__PURE__ */ jsx4(IconCrossMedium, {})
1913
+ }
1914
+ ),
1915
+ hover && a.previewUrl && !a.isUploading && /* @__PURE__ */ jsx4(AnchoredPreview, { src: a.previewUrl, anchorRef: ref })
1916
+ ]
1917
+ }
1918
+ );
1919
+ }
1920
+ function extractFiles(data) {
1921
+ const files = [];
1922
+ if (data.files) {
1923
+ for (let i = 0; i < data.files.length; i++) {
1924
+ const file = data.files[i];
1925
+ if (file) files.push(file);
1926
+ }
1927
+ }
1928
+ return files;
1929
+ }
1930
+
1931
+ // src/themed/debug-overlay.tsx
1932
+ import { useEffect as useEffect7, useMemo, useRef as useRef8, useState as useState8 } from "react";
1933
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1934
+ var MAX_EVENTS = 60;
1935
+ function isDebugEnabled() {
1936
+ if (typeof window === "undefined") return false;
1937
+ if (typeof window.localStorage !== "undefined" && window.localStorage.getItem("hw:debug") === "1") {
1938
+ return true;
1939
+ }
1940
+ const search = window.location?.search ?? "";
1941
+ return search.includes("hwdebug=1") || search.includes("debug=hw");
1942
+ }
1943
+ function senderOf(item) {
1944
+ if (item.visitorId) return "visitor";
1945
+ if (item.aiAgentId) return "ai";
1946
+ if (item.userId) return "team";
1947
+ return item.type;
1948
+ }
1949
+ function WidgetDebugOverlay() {
1950
+ const [enabled, setEnabled] = useState8(false);
1951
+ const [open, setOpen] = useState8(true);
1952
+ const [events, setEvents] = useState8([]);
1953
+ const eventsRef = useRef8([]);
1954
+ const activeId = useWidgetStore((s) => s.activeConversationId);
1955
+ const messagesMap = useWidgetStore((s) => s.messages);
1956
+ const typingUsers = useWidgetStore((s) => s.typingUsers);
1957
+ const aiProcessingMap = useWidgetStore((s) => s.aiProcessing);
1958
+ const { store } = useWidgetContext();
1959
+ useEffect7(() => {
1960
+ setEnabled(isDebugEnabled());
1961
+ }, []);
1962
+ const pushEvent = useMemo(
1963
+ () => (ev) => {
1964
+ const e = {
1965
+ ...ev,
1966
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
1967
+ t: Date.now()
1968
+ };
1969
+ eventsRef.current = [e, ...eventsRef.current].slice(0, MAX_EVENTS);
1970
+ setEvents(eventsRef.current);
1971
+ },
1972
+ []
1973
+ );
1974
+ useEffect7(() => {
1975
+ if (!enabled) return;
1976
+ if (typeof window === "undefined" || typeof window.fetch !== "function") return;
1977
+ const originalFetch = window.fetch.bind(window);
1978
+ const patched = (async (input, init) => {
1979
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1980
+ const isTrpc = url.includes("/trpc/");
1981
+ if (isTrpc) {
1982
+ const method = init?.method ?? (typeof input !== "string" && !(input instanceof URL) ? input.method : "GET");
1983
+ const label = url.split("/trpc/")[1]?.split("?")[0] ?? url;
1984
+ pushEvent({ kind: "trpc", label: `\u2192 ${method} ${label}`, detail: shortJson(init?.body) });
1985
+ }
1986
+ try {
1987
+ const res = await originalFetch(input, init);
1988
+ if (isTrpc) {
1989
+ const label = url.split("/trpc/")[1]?.split("?")[0] ?? url;
1990
+ pushEvent({ kind: "trpc", label: `\u2190 ${res.status} ${label}` });
1991
+ }
1992
+ return res;
1993
+ } catch (err) {
1994
+ if (isTrpc) {
1995
+ const label = url.split("/trpc/")[1]?.split("?")[0] ?? url;
1996
+ pushEvent({ kind: "error", label: `\u2718 ${label}`, detail: err instanceof Error ? err.message : String(err) });
1997
+ }
1998
+ throw err;
1999
+ }
2000
+ });
2001
+ window.fetch = patched;
2002
+ return () => {
2003
+ window.fetch = originalFetch;
2004
+ };
2005
+ }, [enabled, pushEvent]);
2006
+ useEffect7(() => {
2007
+ if (!enabled) return;
2008
+ const original = store.getState().handleRealtimeEvent;
2009
+ store.setState({
2010
+ handleRealtimeEvent: (ev) => {
2011
+ pushEvent({ kind: "ws", label: ev.type, detail: shortJson(ev) });
2012
+ original(ev);
2013
+ }
2014
+ });
2015
+ return () => {
2016
+ store.setState({ handleRealtimeEvent: original });
2017
+ };
2018
+ }, [enabled, store, pushEvent]);
2019
+ const activeMessages = activeId ? messagesMap.get(activeId) ?? [] : [];
2020
+ const lastMsgSignature = useMemo(
2021
+ () => activeMessages.map((m) => `${m.id}:${senderOf(m)}:${m.createdAt}`).join("|"),
2022
+ [activeMessages]
2023
+ );
2024
+ const prevSigRef = useRef8(lastMsgSignature);
2025
+ useEffect7(() => {
2026
+ if (!enabled) return;
2027
+ if (prevSigRef.current !== lastMsgSignature) {
2028
+ prevSigRef.current = lastMsgSignature;
2029
+ pushEvent({
2030
+ kind: "store",
2031
+ label: `messages[${activeMessages.length}]`,
2032
+ detail: activeMessages.map((m, i) => `${i}:${senderOf(m)}=${(m.text ?? "").slice(0, 24)}`).join(" | ")
2033
+ });
2034
+ }
2035
+ }, [lastMsgSignature, activeMessages, enabled, pushEvent]);
2036
+ if (!enabled) return null;
2037
+ const isTyping = activeId ? typingUsers.get(activeId) : false;
2038
+ const isAi = activeId ? aiProcessingMap.get(activeId) : false;
2039
+ return /* @__PURE__ */ jsxs3("div", { style: styles.wrap, children: [
2040
+ /* @__PURE__ */ jsxs3("div", { style: styles.header, children: [
2041
+ /* @__PURE__ */ jsx5("strong", { children: "hw debug" }),
2042
+ /* @__PURE__ */ jsxs3("span", { style: styles.meta, children: [
2043
+ " ",
2044
+ "\xB7 conv ",
2045
+ activeId ? activeId.slice(0, 10) : "\u2014",
2046
+ " \xB7 items ",
2047
+ activeMessages.length,
2048
+ " \xB7 typing ",
2049
+ isTyping ? "Y" : "n",
2050
+ " \xB7 ai ",
2051
+ isAi ? "Y" : "n"
2052
+ ] }),
2053
+ /* @__PURE__ */ jsx5("button", { type: "button", style: styles.btn, onClick: () => setOpen((v) => !v), children: open ? "hide" : "show" }),
2054
+ /* @__PURE__ */ jsx5("button", { type: "button", style: styles.btn, onClick: () => {
2055
+ eventsRef.current = [];
2056
+ setEvents([]);
2057
+ }, children: "clear" })
2058
+ ] }),
2059
+ open && /* @__PURE__ */ jsxs3("div", { style: styles.body, children: [
2060
+ /* @__PURE__ */ jsxs3("div", { style: styles.section, children: [
2061
+ /* @__PURE__ */ jsx5("div", { style: styles.sectionTitle, children: "messages (active)" }),
2062
+ activeMessages.length === 0 ? /* @__PURE__ */ jsx5("div", { style: styles.empty, children: "empty" }) : activeMessages.map((m, i) => /* @__PURE__ */ jsxs3("div", { style: styles.row, children: [
2063
+ /* @__PURE__ */ jsx5("span", { style: { ...styles.badge, background: colorFor(senderOf(m)) }, children: i }),
2064
+ /* @__PURE__ */ jsx5("span", { style: styles.sender, children: senderOf(m) }),
2065
+ /* @__PURE__ */ jsx5("span", { style: styles.ts, children: m.createdAt.slice(11, 23) }),
2066
+ /* @__PURE__ */ jsx5("span", { style: styles.text, children: (m.text ?? "").slice(0, 60) })
2067
+ ] }, m.id))
2068
+ ] }),
2069
+ /* @__PURE__ */ jsxs3("div", { style: styles.section, children: [
2070
+ /* @__PURE__ */ jsx5("div", { style: styles.sectionTitle, children: "events" }),
2071
+ events.length === 0 ? /* @__PURE__ */ jsx5("div", { style: styles.empty, children: "no events yet \u2014 send a message" }) : events.map((e) => /* @__PURE__ */ jsxs3("div", { style: styles.row, children: [
2072
+ /* @__PURE__ */ jsx5("span", { style: { ...styles.badge, background: kindColor(e.kind) }, children: e.kind }),
2073
+ /* @__PURE__ */ jsx5("span", { style: styles.ts, children: new Date(e.t).toISOString().slice(11, 23) }),
2074
+ /* @__PURE__ */ jsx5("span", { style: styles.evLabel, children: e.label }),
2075
+ e.detail && /* @__PURE__ */ jsx5("span", { style: styles.detail, children: e.detail })
2076
+ ] }, e.id))
2077
+ ] })
2078
+ ] })
2079
+ ] });
2080
+ }
2081
+ function shortJson(v) {
2082
+ if (v === void 0) return "";
2083
+ try {
2084
+ const s = JSON.stringify(v);
2085
+ if (!s) return "";
2086
+ return s.length > 180 ? `${s.slice(0, 177)}\u2026` : s;
2087
+ } catch {
2088
+ return String(v);
2089
+ }
2090
+ }
2091
+ function colorFor(sender) {
2092
+ switch (sender) {
2093
+ case "visitor":
2094
+ return "#1d4ed8";
2095
+ case "ai":
2096
+ return "#7c3aed";
2097
+ case "team":
2098
+ return "#16a34a";
2099
+ default:
2100
+ return "#6b7280";
2101
+ }
2102
+ }
2103
+ function kindColor(kind) {
2104
+ switch (kind) {
2105
+ case "trpc":
2106
+ return "#0ea5e9";
2107
+ case "ws":
2108
+ return "#f97316";
2109
+ case "store":
2110
+ return "#64748b";
2111
+ case "error":
2112
+ return "#dc2626";
2113
+ }
2114
+ }
2115
+ var styles = {
2116
+ wrap: {
2117
+ position: "fixed",
2118
+ right: 12,
2119
+ bottom: 12,
2120
+ zIndex: 2147483e3,
2121
+ width: 420,
2122
+ maxHeight: "70vh",
2123
+ background: "rgba(9,9,11,0.94)",
2124
+ color: "#e5e7eb",
2125
+ borderRadius: 10,
2126
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2127
+ fontSize: 11,
2128
+ boxShadow: "0 12px 30px rgba(0,0,0,0.35)",
2129
+ overflow: "hidden",
2130
+ pointerEvents: "auto"
2131
+ },
2132
+ header: {
2133
+ display: "flex",
2134
+ alignItems: "center",
2135
+ gap: 6,
2136
+ padding: "6px 10px",
2137
+ background: "#18181b",
2138
+ borderBottom: "1px solid #27272a"
2139
+ },
2140
+ meta: { flex: 1, color: "#a1a1aa", fontSize: 10 },
2141
+ btn: {
2142
+ background: "#27272a",
2143
+ color: "#e4e4e7",
2144
+ border: "1px solid #3f3f46",
2145
+ borderRadius: 4,
2146
+ padding: "2px 8px",
2147
+ cursor: "pointer",
2148
+ fontSize: 10
2149
+ },
2150
+ body: { display: "flex", flexDirection: "column", maxHeight: "calc(70vh - 32px)", overflow: "auto" },
2151
+ section: { borderBottom: "1px solid #27272a", padding: "6px 10px" },
2152
+ sectionTitle: { color: "#a1a1aa", fontSize: 10, textTransform: "uppercase", letterSpacing: 0.6, marginBottom: 4 },
2153
+ row: {
2154
+ display: "grid",
2155
+ gridTemplateColumns: "32px 56px 72px 1fr",
2156
+ gap: 6,
2157
+ alignItems: "baseline",
2158
+ padding: "2px 0",
2159
+ borderTop: "1px dashed #27272a"
2160
+ },
2161
+ badge: {
2162
+ display: "inline-block",
2163
+ borderRadius: 3,
2164
+ padding: "1px 4px",
2165
+ color: "white",
2166
+ fontSize: 9,
2167
+ textAlign: "center"
2168
+ },
2169
+ sender: { color: "#d4d4d8" },
2170
+ ts: { color: "#71717a", fontSize: 10 },
2171
+ text: { color: "#e4e4e7", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" },
2172
+ evLabel: { color: "#e4e4e7" },
2173
+ detail: { gridColumn: "1 / -1", color: "#a1a1aa", fontSize: 10, whiteSpace: "pre-wrap", wordBreak: "break-all" },
2174
+ empty: { color: "#52525b", fontStyle: "italic", fontSize: 10 }
2175
+ };
2176
+
2177
+ // src/themed/content/content-tab-view.tsx
2178
+ import { useCallback as useCallback11, useEffect as useEffect10 } from "react";
2179
+
2180
+ // src/hooks/use-content-tab.ts
2181
+ import { useCallback as useCallback8, useEffect as useEffect8, useState as useState9 } from "react";
2182
+ var EMPTY_TABS = [];
2183
+ function listKey(tabId, lang) {
2184
+ return `flat:${tabId}:${lang}:list`;
2185
+ }
2186
+ function detailKey(tabId, lang, slug) {
2187
+ return `flat:${tabId}:${lang}:detail:${slug}`;
2188
+ }
2189
+ function readCache(cache, key) {
2190
+ const entry = cache.get(key);
2191
+ if (!entry) return null;
2192
+ if (Date.now() - entry.ts > CONTENT_CACHE_TTL) return null;
2193
+ return entry.data;
2194
+ }
2195
+ function useContentTab(tabId) {
2196
+ const { client, store } = useWidgetContext();
2197
+ const contentTabs = useWidgetStore((s) => s.agentConfig?.contentTabs) ?? EMPTY_TABS;
2198
+ const tab = contentTabs.find((t2) => t2.id === tabId) ?? null;
2199
+ const lang = getLocale();
2200
+ const cache = store.getState().contentCache;
2201
+ const [entries, setEntries] = useState9(
2202
+ () => readCache(cache, listKey(tabId, lang))?.items ?? []
2203
+ );
2204
+ const [detail, setDetail] = useState9(null);
2205
+ const [isLoading, setIsLoading] = useState9(false);
2206
+ const [isLoadingDetail, setIsLoadingDetail] = useState9(false);
2207
+ const [hasMore, setHasMore] = useState9(
2208
+ () => readCache(cache, listKey(tabId, lang))?.hasMore ?? false
2209
+ );
2210
+ const [total, setTotal] = useState9(
2211
+ () => readCache(cache, listKey(tabId, lang))?.total ?? 0
2212
+ );
2213
+ const [page, setPage] = useState9(
2214
+ () => readCache(cache, listKey(tabId, lang))?.page ?? 1
2215
+ );
2216
+ const fetchEntries = useCallback8(
2217
+ async (opts) => {
2218
+ if (!tab) return;
2219
+ const p = opts?.page ?? 1;
2220
+ const reset = opts?.reset ?? p === 1;
2221
+ setIsLoading(true);
2222
+ try {
2223
+ const result = await client.widget.getContent.query({
2224
+ tabId,
2225
+ page: p,
2226
+ limit: 10,
2227
+ search: opts?.search,
2228
+ language: getLocale()
2229
+ });
2230
+ if (result.items) {
2231
+ setEntries((prev) => {
2232
+ const nextItems = reset ? result.items : [...prev, ...result.items];
2233
+ if (!opts?.search) {
2234
+ store.getState().setContentCache(listKey(tabId, getLocale()), {
2235
+ items: nextItems,
2236
+ total: result.total,
2237
+ hasMore: result.hasMore,
2238
+ page: p
2239
+ });
2240
+ }
2241
+ return nextItems;
2242
+ });
2243
+ setHasMore(result.hasMore);
2244
+ setTotal(result.total);
2245
+ setPage(p);
2246
+ }
2247
+ } catch (err) {
2248
+ console.error("[useContentTab] fetch error:", err);
2249
+ } finally {
2250
+ setIsLoading(false);
2251
+ }
2252
+ },
2253
+ [client, tabId, tab, store]
2254
+ );
2255
+ const loadMore = useCallback8(() => {
2256
+ if (!hasMore || isLoading) return;
2257
+ fetchEntries({ page: page + 1 });
2258
+ }, [fetchEntries, hasMore, isLoading, page]);
2259
+ const fetchDetail = useCallback8(
2260
+ async (slug) => {
2261
+ if (!tab) return;
2262
+ const cached = readCache(
2263
+ store.getState().contentCache,
2264
+ detailKey(tabId, getLocale(), slug)
2265
+ );
2266
+ if (cached) {
2267
+ setDetail(cached);
2268
+ return;
2269
+ }
2270
+ setIsLoadingDetail(true);
2271
+ try {
2272
+ const result = await client.widget.getContent.query({
2273
+ tabId,
2274
+ slug,
2275
+ language: getLocale()
2276
+ });
2277
+ if (result.entry) {
2278
+ const entry = result.entry;
2279
+ setDetail(entry);
2280
+ store.getState().setContentCache(
2281
+ detailKey(tabId, getLocale(), slug),
2282
+ entry
2283
+ );
2284
+ }
2285
+ } catch (err) {
2286
+ console.error("[useContentTab] detail error:", err);
2287
+ } finally {
2288
+ setIsLoadingDetail(false);
2289
+ }
2290
+ },
2291
+ [client, tabId, tab, store]
2292
+ );
2293
+ const clearDetail = useCallback8(() => {
2294
+ setDetail(null);
2295
+ }, []);
2296
+ useEffect8(() => {
2297
+ if (tab && entries.length === 0) {
2298
+ fetchEntries({ page: 1, reset: true });
2299
+ }
2300
+ }, [tab]);
2301
+ return {
2302
+ tab,
2303
+ entries,
2304
+ detail,
2305
+ isLoading,
2306
+ isLoadingDetail,
2307
+ hasMore,
2308
+ total,
2309
+ fetchEntries,
2310
+ loadMore,
2311
+ fetchDetail,
2312
+ clearDetail
2313
+ };
2314
+ }
2315
+
2316
+ // src/themed/content/content-detail-view.tsx
2317
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2318
+ function ContentDetailView({
2319
+ entry,
2320
+ isLoading
2321
+ }) {
2322
+ if (isLoading || !entry) {
2323
+ return /* @__PURE__ */ jsxs4("div", { className: "hw-skeleton-content", style: { paddingTop: 20 }, children: [
2324
+ /* @__PURE__ */ jsx6("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "60%", height: 18 } }),
2325
+ /* @__PURE__ */ jsx6("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "35%", height: 12 } }),
2326
+ /* @__PURE__ */ jsxs4("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 8 }, children: [
2327
+ /* @__PURE__ */ jsx6("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "100%", height: 12 } }),
2328
+ /* @__PURE__ */ jsx6("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "95%", height: 12 } }),
2329
+ /* @__PURE__ */ jsx6("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "80%", height: 12 } }),
2330
+ /* @__PURE__ */ jsx6("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "60%", height: 12 } })
2331
+ ] })
2332
+ ] });
2333
+ }
2334
+ const badge = entry.customFields.version ?? entry.customFields.priority ?? entry.customFields.type ?? null;
2335
+ return /* @__PURE__ */ jsxs4("div", { className: "hw-content-detail", children: [
2336
+ /* @__PURE__ */ jsxs4("div", { className: "hw-content-detail-header", children: [
2337
+ /* @__PURE__ */ jsx6("h1", { className: "hw-content-detail-title", children: entry.title }),
2338
+ (badge || entry.publishedAt) && /* @__PURE__ */ jsxs4("div", { className: "hw-content-detail-meta", children: [
2339
+ badge && /* @__PURE__ */ jsx6("span", { className: "hw-content-entry-badge", children: badge }),
2340
+ entry.publishedAt && /* @__PURE__ */ jsx6("span", { className: "hw-content-detail-date", children: formatRelativeTime(entry.publishedAt) })
2341
+ ] })
2342
+ ] }),
2343
+ (entry.body || entry.bodyHtml) && /* @__PURE__ */ jsx6("div", { className: "hw-content-detail-body", children: /* @__PURE__ */ jsx6(MarkdownContent, { bodyHtml: entry.bodyHtml, bodyMarkdown: entry.body }) })
2344
+ ] });
2345
+ }
2346
+
2347
+ // src/themed/content/content-list-view.tsx
2348
+ import { useCallback as useCallback9, useRef as useRef9, useState as useState10 } from "react";
2349
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2350
+ function ContentListView({
2351
+ entries,
2352
+ isLoading,
2353
+ hasMore,
2354
+ onSelect,
2355
+ onLoadMore,
2356
+ onSearch,
2357
+ searchPlaceholder,
2358
+ display
2359
+ }) {
2360
+ const [searchText, setSearchText] = useState10("");
2361
+ const debounceRef = useRef9(void 0);
2362
+ const handleSearch = useCallback9(
2363
+ (value) => {
2364
+ setSearchText(value);
2365
+ if (!onSearch) return;
2366
+ clearTimeout(debounceRef.current);
2367
+ debounceRef.current = setTimeout(() => {
2368
+ onSearch(value.trim());
2369
+ }, 300);
2370
+ },
2371
+ [onSearch]
2372
+ );
2373
+ return /* @__PURE__ */ jsxs5("div", { className: "hw-content-list", children: [
2374
+ onSearch && /* @__PURE__ */ jsx7("div", { className: "hw-content-search", children: /* @__PURE__ */ jsxs5("div", { className: "hw-content-search-box", children: [
2375
+ /* @__PURE__ */ jsxs5("svg", { className: "hw-content-search-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2376
+ /* @__PURE__ */ jsx7("circle", { cx: "11", cy: "11", r: "8" }),
2377
+ /* @__PURE__ */ jsx7("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
2378
+ ] }),
2379
+ /* @__PURE__ */ jsx7(
2380
+ "input",
2381
+ {
2382
+ type: "text",
2383
+ className: "hw-content-search-input",
2384
+ placeholder: searchPlaceholder ?? t("widget.search"),
2385
+ value: searchText,
2386
+ onChange: (e) => handleSearch(e.target.value)
2387
+ }
2388
+ )
2389
+ ] }) }),
2390
+ isLoading && entries.length === 0 && /* @__PURE__ */ jsx7("div", { className: "hw-skeleton-content", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxs5("div", { className: "hw-skeleton-content-item", children: [
2391
+ /* @__PURE__ */ jsx7("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "70%", height: 14 } }),
2392
+ /* @__PURE__ */ jsx7("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "90%", height: 12 } }),
2393
+ /* @__PURE__ */ jsx7("div", { className: "hw-skeleton hw-skeleton-line", style: { width: "40%", height: 10 } })
2394
+ ] }, i)) }),
2395
+ !isLoading && entries.length === 0 && /* @__PURE__ */ jsxs5("div", { className: "hw-content-empty", children: [
2396
+ /* @__PURE__ */ jsx7("div", { className: "hw-content-empty-title", children: searchText ? t("widget.no_results") : t("widget.nothing_yet") }),
2397
+ /* @__PURE__ */ jsx7("div", { className: "hw-content-empty-desc", children: searchText ? t("widget.try_different") : t("widget.check_back") })
2398
+ ] }),
2399
+ entries.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "hw-content-entries", children: [
2400
+ entries.map((entry) => /* @__PURE__ */ jsx7(ContentEntryCard, { entry, onSelect, display }, entry.slug)),
2401
+ hasMore && /* @__PURE__ */ jsx7(
2402
+ "button",
2403
+ {
2404
+ type: "button",
2405
+ className: "hw-content-load-more",
2406
+ onClick: onLoadMore,
2407
+ disabled: isLoading,
2408
+ children: isLoading ? t("widget.loading") : t("widget.load_more")
2409
+ }
2410
+ )
2411
+ ] })
2412
+ ] });
2413
+ }
2414
+ function truncateText(text, maxLen = 80) {
2415
+ if (!text) return "";
2416
+ const clean = text.replace(/[#*_`>\[\]!()-]/g, "").trim();
2417
+ return clean.length > maxLen ? `${clean.slice(0, maxLen)}...` : clean;
2418
+ }
2419
+ function ContentEntryCard({
2420
+ entry,
2421
+ onSelect,
2422
+ display
2423
+ }) {
2424
+ const { customFields } = entry;
2425
+ const badge = display?.badgeField ? customFields[display.badgeField] ?? null : customFields.version ?? customFields.priority ?? customFields.type ?? null;
2426
+ const subtitleValue = display?.subtitleField ? (display.subtitleField === "body" ? entry.body : customFields[display.subtitleField]) ?? null : customFields.excerpt ?? customFields.summary ?? customFields.description ?? null;
2427
+ const preview = subtitleValue ? truncateText(subtitleValue) : truncateText(entry.body);
2428
+ return /* @__PURE__ */ jsx7("button", { type: "button", className: "hw-content-entry", onClick: () => onSelect(entry.slug), children: /* @__PURE__ */ jsxs5("div", { className: "hw-content-entry-main", children: [
2429
+ /* @__PURE__ */ jsxs5("div", { className: "hw-content-entry-text", children: [
2430
+ /* @__PURE__ */ jsx7("span", { className: "hw-content-entry-title", children: entry.title }),
2431
+ preview && /* @__PURE__ */ jsx7("span", { className: "hw-content-entry-desc", children: preview }),
2432
+ (badge || entry.publishedAt) && /* @__PURE__ */ jsxs5("div", { className: "hw-content-entry-meta", children: [
2433
+ badge && /* @__PURE__ */ jsx7("span", { className: "hw-content-entry-badge", children: badge }),
2434
+ entry.publishedAt && /* @__PURE__ */ jsx7("span", { className: "hw-content-entry-date", children: formatRelativeTime(entry.publishedAt) })
2435
+ ] })
2436
+ ] }),
2437
+ /* @__PURE__ */ jsx7("svg", { className: "hw-content-entry-chevron", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx7("polyline", { points: "9 18 15 12 9 6" }) })
2438
+ ] }) });
2439
+ }
2440
+
2441
+ // src/themed/content/grouped-content-tab-view.tsx
2442
+ import { useEffect as useEffect9, useRef as useRef11 } from "react";
2443
+
2444
+ // src/hooks/use-grouped-content-tab.ts
2445
+ import { useCallback as useCallback10, useRef as useRef10, useState as useState11 } from "react";
2446
+ var EMPTY_TABS2 = [];
2447
+ function categoriesKey(tabId, lang) {
2448
+ return `grouped:${tabId}:${lang}:categories`;
2449
+ }
2450
+ function itemsKey(tabId, lang, groupSlug) {
2451
+ return `grouped:${tabId}:${lang}:items:${groupSlug}`;
2452
+ }
2453
+ function detailKey2(tabId, lang, slug) {
2454
+ return `grouped:${tabId}:${lang}:detail:${slug}`;
2455
+ }
2456
+ function readCache2(cache, key) {
2457
+ const entry = cache.get(key);
2458
+ if (!entry) return null;
2459
+ if (Date.now() - entry.ts > CONTENT_CACHE_TTL) return null;
2460
+ return entry.data;
2461
+ }
2462
+ function useGroupedContentTab(tabId) {
2463
+ const { client, store } = useWidgetContext();
2464
+ const contentTabs = useWidgetStore((s) => s.agentConfig?.contentTabs) ?? EMPTY_TABS2;
2465
+ const tab = contentTabs.find((t2) => t2.id === tabId) ?? null;
2466
+ const lang = getLocale();
2467
+ const cache = store.getState().contentCache;
2468
+ const cachedCategories = readCache2(cache, categoriesKey(tabId, lang));
2469
+ const [categories, setCategories] = useState11(cachedCategories ?? []);
2470
+ const [isCategoriesLoading, setIsCategoriesLoading] = useState11(false);
2471
+ const [items, setItems] = useState11([]);
2472
+ const [isItemsLoading, setIsItemsLoading] = useState11(false);
2473
+ const [itemsHasMore, setItemsHasMore] = useState11(false);
2474
+ const itemsPageRef = useRef10(1);
2475
+ const [detail, setDetail] = useState11(null);
2476
+ const [isLoadingDetail, setIsLoadingDetail] = useState11(false);
2477
+ const [activeCategory, setActiveCategory] = useState11(null);
2478
+ const allCategoriesRef = useRef10(cachedCategories ?? []);
2479
+ const fetchCategories = useCallback10(async () => {
2480
+ if (!tab) return;
2481
+ setIsCategoriesLoading(true);
2482
+ try {
2483
+ const result = await client.widget.getContent.query({
2484
+ tabId,
2485
+ page: 1,
2486
+ limit: 50,
2487
+ language: getLocale()
2488
+ });
2489
+ if (result.items) {
2490
+ allCategoriesRef.current = result.items;
2491
+ setCategories(result.items);
2492
+ store.getState().setContentCache(categoriesKey(tabId, getLocale()), result.items);
2493
+ }
2494
+ } catch (err) {
2495
+ console.error("[useGroupedContentTab] categories error:", err);
2496
+ } finally {
2497
+ setIsCategoriesLoading(false);
2498
+ }
2499
+ }, [client, tabId, tab, store]);
2500
+ const initFetch = useCallback10(() => {
2501
+ if (tab && categories.length === 0) {
2502
+ fetchCategories();
2503
+ }
2504
+ }, [fetchCategories, tab, categories.length]);
2505
+ const fetchItems = useCallback10(
2506
+ async (opts) => {
2507
+ if (!tab?.itemsModelSlug) return;
2508
+ const p = opts?.page ?? 1;
2509
+ const reset = opts?.reset ?? p === 1;
2510
+ setIsItemsLoading(true);
2511
+ try {
2512
+ const result = await client.widget.getContent.query({
2513
+ tabId,
2514
+ page: p,
2515
+ limit: 10,
2516
+ search: opts?.search,
2517
+ groupSlug: opts?.groupSlug,
2518
+ itemsModel: tab.itemsModelSlug,
2519
+ language: getLocale()
2520
+ });
2521
+ if (result.items) {
2522
+ setItems((prev) => {
2523
+ const nextItems = reset ? result.items : [...prev, ...result.items];
2524
+ if (!opts?.search && opts?.groupSlug) {
2525
+ store.getState().setContentCache(
2526
+ itemsKey(tabId, getLocale(), opts.groupSlug),
2527
+ { items: nextItems, hasMore: result.hasMore, page: p }
2528
+ );
2529
+ }
2530
+ return nextItems;
2531
+ });
2532
+ setItemsHasMore(result.hasMore);
2533
+ itemsPageRef.current = p;
2534
+ }
2535
+ } catch (err) {
2536
+ console.error("[useGroupedContentTab] items error:", err);
2537
+ } finally {
2538
+ setIsItemsLoading(false);
2539
+ }
2540
+ },
2541
+ [client, tabId, tab, store]
2542
+ );
2543
+ const selectCategory = useCallback10(
2544
+ (category) => {
2545
+ setActiveCategory(category);
2546
+ setDetail(null);
2547
+ const cached = readCache2(
2548
+ store.getState().contentCache,
2549
+ itemsKey(tabId, getLocale(), category.slug)
2550
+ );
2551
+ if (cached) {
2552
+ setItems(cached.items);
2553
+ setItemsHasMore(cached.hasMore);
2554
+ itemsPageRef.current = cached.page;
2555
+ return;
2556
+ }
2557
+ setItems([]);
2558
+ fetchItems({ page: 1, reset: true, groupSlug: category.slug });
2559
+ },
2560
+ [fetchItems, store, tabId]
2561
+ );
2562
+ const loadMoreItems = useCallback10(() => {
2563
+ if (!itemsHasMore || isItemsLoading || !activeCategory) return;
2564
+ fetchItems({
2565
+ page: itemsPageRef.current + 1,
2566
+ groupSlug: activeCategory.slug
2567
+ });
2568
+ }, [fetchItems, itemsHasMore, isItemsLoading, activeCategory]);
2569
+ const searchItems = useCallback10(
2570
+ (query) => {
2571
+ setItems([]);
2572
+ fetchItems({
2573
+ page: 1,
2574
+ reset: true,
2575
+ search: query || void 0,
2576
+ groupSlug: query ? void 0 : activeCategory?.slug
2577
+ });
2578
+ },
2579
+ [fetchItems, activeCategory]
2580
+ );
2581
+ const fetchDetail = useCallback10(
2582
+ async (slug) => {
2583
+ if (!tab?.itemsModelSlug) return;
2584
+ const cached = readCache2(
2585
+ store.getState().contentCache,
2586
+ detailKey2(tabId, getLocale(), slug)
2587
+ );
2588
+ if (cached) {
2589
+ setDetail(cached);
2590
+ return;
2591
+ }
2592
+ setIsLoadingDetail(true);
2593
+ try {
2594
+ const result = await client.widget.getContent.query({
2595
+ tabId,
2596
+ slug,
2597
+ itemsModel: tab.itemsModelSlug,
2598
+ language: getLocale()
2599
+ });
2600
+ if (result.entry) {
2601
+ const entry = result.entry;
2602
+ setDetail(entry);
2603
+ store.getState().setContentCache(
2604
+ detailKey2(tabId, getLocale(), slug),
2605
+ entry
2606
+ );
2607
+ }
2608
+ } catch (err) {
2609
+ console.error("[useGroupedContentTab] detail error:", err);
2610
+ } finally {
2611
+ setIsLoadingDetail(false);
2612
+ }
2613
+ },
2614
+ [client, tabId, tab, store]
2615
+ );
2616
+ const searchCategories = useCallback10(
2617
+ (query) => {
2618
+ if (!query.trim()) {
2619
+ setCategories(allCategoriesRef.current);
2620
+ return;
2621
+ }
2622
+ const q = query.toLowerCase();
2623
+ setCategories(
2624
+ allCategoriesRef.current.filter(
2625
+ (cat) => cat.title.toLowerCase().includes(q) || (cat.customFields.description ?? "").toLowerCase().includes(q)
2626
+ )
2627
+ );
2628
+ },
2629
+ []
2630
+ );
2631
+ const clearDetail = useCallback10(() => {
2632
+ setDetail(null);
2633
+ }, []);
2634
+ const clearCategory = useCallback10(() => {
2635
+ setActiveCategory(null);
2636
+ setItems([]);
2637
+ setDetail(null);
2638
+ }, []);
2639
+ return {
2640
+ tab,
2641
+ categories,
2642
+ isCategoriesLoading,
2643
+ items,
2644
+ isItemsLoading,
2645
+ itemsHasMore,
2646
+ activeCategory,
2647
+ detail,
2648
+ isLoadingDetail,
2649
+ initFetch,
2650
+ selectCategory,
2651
+ searchCategories,
2652
+ loadMoreItems,
2653
+ searchItems,
2654
+ fetchDetail,
2655
+ clearDetail,
2656
+ clearCategory
2657
+ };
2658
+ }
2659
+
2660
+ // src/themed/content/grouped-content-tab-view.tsx
2661
+ import { jsx as jsx8 } from "react/jsx-runtime";
2662
+ function GroupedContentTabView({
2663
+ tab,
2664
+ subView,
2665
+ detailSlug,
2666
+ onSelectCategory,
2667
+ onSelectEntry
2668
+ }) {
2669
+ const {
2670
+ categories,
2671
+ isCategoriesLoading,
2672
+ items,
2673
+ isItemsLoading,
2674
+ itemsHasMore,
2675
+ activeCategory,
2676
+ detail,
2677
+ isLoadingDetail,
2678
+ initFetch,
2679
+ selectCategory,
2680
+ searchCategories,
2681
+ loadMoreItems,
2682
+ searchItems,
2683
+ fetchDetail,
2684
+ clearCategory
2685
+ } = useGroupedContentTab(tab.id);
2686
+ useEffect9(() => {
2687
+ initFetch();
2688
+ }, [initFetch]);
2689
+ useEffect9(() => {
2690
+ if (detailSlug && subView === "content-detail") {
2691
+ fetchDetail(detailSlug);
2692
+ }
2693
+ }, [detailSlug, subView, fetchDetail]);
2694
+ const prevSubView = useRef11(subView);
2695
+ useEffect9(() => {
2696
+ if (prevSubView.current === "content-category" && subView === null) {
2697
+ clearCategory();
2698
+ }
2699
+ prevSubView.current = subView;
2700
+ }, [subView, clearCategory]);
2701
+ if (subView === "content-detail" && detailSlug) {
2702
+ return /* @__PURE__ */ jsx8(ContentDetailView, { entry: detail, isLoading: isLoadingDetail });
2703
+ }
2704
+ if (subView === "content-category" && activeCategory) {
2705
+ return /* @__PURE__ */ jsx8(
2706
+ ContentListView,
2707
+ {
2708
+ entries: items,
2709
+ isLoading: isItemsLoading,
2710
+ hasMore: itemsHasMore,
2711
+ onSelect: onSelectEntry,
2712
+ onLoadMore: loadMoreItems,
2713
+ onSearch: searchItems,
2714
+ searchPlaceholder: t("widget.search"),
2715
+ display: tab.display
2716
+ }
2717
+ );
2718
+ }
2719
+ return /* @__PURE__ */ jsx8(
2720
+ ContentListView,
2721
+ {
2722
+ entries: categories,
2723
+ isLoading: isCategoriesLoading,
2724
+ hasMore: false,
2725
+ onSelect: (slug) => {
2726
+ const cat = categories.find((c) => c.slug === slug);
2727
+ if (cat) {
2728
+ selectCategory(cat);
2729
+ onSelectCategory(cat);
2730
+ }
2731
+ },
2732
+ onLoadMore: () => {
2733
+ },
2734
+ onSearch: searchCategories,
2735
+ searchPlaceholder: t("widget.search")
2736
+ }
2737
+ );
2738
+ }
2739
+
2740
+ // src/themed/content/content-tab-view.tsx
2741
+ import { jsx as jsx9 } from "react/jsx-runtime";
2742
+ function ContentTabView({
2743
+ tab,
2744
+ showDetail,
2745
+ detailSlug,
2746
+ subView,
2747
+ onSelectEntry,
2748
+ onSelectCategory
2749
+ }) {
2750
+ if (tab.tabType === "grouped" && tab.itemsModelSlug) {
2751
+ return /* @__PURE__ */ jsx9(
2752
+ GroupedContentTabView,
2753
+ {
2754
+ tab,
2755
+ subView: subView ?? null,
2756
+ detailSlug,
2757
+ onSelectCategory: onSelectCategory ?? (() => {
2758
+ }),
2759
+ onSelectEntry
2760
+ }
2761
+ );
2762
+ }
2763
+ return /* @__PURE__ */ jsx9(
2764
+ FlatContentTabView,
2765
+ {
2766
+ tab,
2767
+ showDetail,
2768
+ detailSlug,
2769
+ onSelectEntry
2770
+ }
2771
+ );
2772
+ }
2773
+ function FlatContentTabView({
2774
+ tab,
2775
+ showDetail,
2776
+ detailSlug,
2777
+ onSelectEntry
2778
+ }) {
2779
+ const {
2780
+ entries,
2781
+ detail,
2782
+ isLoading,
2783
+ isLoadingDetail,
2784
+ hasMore,
2785
+ fetchEntries,
2786
+ loadMore,
2787
+ fetchDetail
2788
+ } = useContentTab(tab.id);
2789
+ useEffect10(() => {
2790
+ if (detailSlug) fetchDetail(detailSlug);
2791
+ }, [detailSlug, fetchDetail]);
2792
+ const handleSearch = useCallback11(
2793
+ (query) => {
2794
+ fetchEntries({ page: 1, search: query || void 0, reset: true });
2795
+ },
2796
+ [fetchEntries]
2797
+ );
2798
+ if (showDetail && detailSlug) {
2799
+ return /* @__PURE__ */ jsx9(ContentDetailView, { entry: detail, isLoading: isLoadingDetail });
2800
+ }
2801
+ return /* @__PURE__ */ jsx9(
2802
+ ContentListView,
2803
+ {
2804
+ entries,
2805
+ isLoading,
2806
+ hasMore,
2807
+ onSelect: onSelectEntry,
2808
+ onLoadMore: loadMore,
2809
+ onSearch: handleSearch,
2810
+ searchPlaceholder: `${t("widget.search")} ${resolveLabel(tab.label).toLowerCase()}...`,
2811
+ display: tab.display
2812
+ }
2813
+ );
2814
+ }
2815
+
2816
+ // src/themed/content/tab-icon.tsx
2817
+ import { jsx as jsx10 } from "react/jsx-runtime";
2818
+ var ICON_MAP = {
2819
+ book: IconBook,
2820
+ megaphone: IconMegaphone,
2821
+ lightbulb: IconLightBulb,
2822
+ "help-circle": IconCircleQuestionmark,
2823
+ newspaper: IconNewspaper,
2824
+ star: IconStar
2825
+ };
2826
+ function ContentTabIconComponent({ icon }) {
2827
+ const Icon = ICON_MAP[icon] ?? IconBook;
2828
+ return /* @__PURE__ */ jsx10(Icon, {});
2829
+ }
2830
+
2831
+ // src/themed/message-list.tsx
2832
+ import React, { useEffect as useEffect11, useRef as useRef12 } from "react";
2833
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2834
+ function parseParts(raw) {
2835
+ if (!raw) return null;
2836
+ if (Array.isArray(raw)) return raw;
2837
+ try {
2838
+ const parsed = JSON.parse(raw);
2839
+ return Array.isArray(parsed) ? parsed : null;
2840
+ } catch {
2841
+ return null;
2842
+ }
2843
+ }
2844
+ function MessageList({
2845
+ items,
2846
+ visitorName,
2847
+ isTyping,
2848
+ isAiProcessing,
2849
+ hasMore,
2850
+ isLoadingMore,
2851
+ onLoadMore,
2852
+ renderAfterMessages
2853
+ }) {
2854
+ const bottomRef = useRef12(null);
2855
+ const topRef = useRef12(null);
2856
+ const containerRef = useRef12(null);
2857
+ const prevItemCountRef = useRef12(items.length);
2858
+ const isInitialLoadRef = useRef12(true);
2859
+ const agentName = useWidgetStore((s) => s.agentConfig?.name) ?? t("widget.better_ai");
2860
+ useEffect11(() => {
2861
+ const prevCount = prevItemCountRef.current;
2862
+ const newCount = items.length;
2863
+ prevItemCountRef.current = newCount;
2864
+ if (isInitialLoadRef.current || newCount > prevCount) {
2865
+ const lastItem = items[items.length - 1];
2866
+ const prevLastItem = prevCount > 0 ? items[prevCount - 1] : null;
2867
+ if (isInitialLoadRef.current || !prevLastItem || lastItem?.id !== prevLastItem?.id) {
2868
+ bottomRef.current?.scrollIntoView({
2869
+ behavior: isInitialLoadRef.current ? "instant" : "smooth"
2870
+ });
2871
+ }
2872
+ isInitialLoadRef.current = false;
2873
+ }
2874
+ }, [items]);
2875
+ useEffect11(() => {
2876
+ if (isTyping || isAiProcessing) {
2877
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
2878
+ }
2879
+ }, [isTyping, isAiProcessing]);
2880
+ useEffect11(() => {
2881
+ if (!hasMore || !onLoadMore || !topRef.current) return;
2882
+ const observer = new IntersectionObserver(
2883
+ (entries) => {
2884
+ if (entries[0]?.isIntersecting && !isLoadingMore) {
2885
+ const container = containerRef.current;
2886
+ const prevScrollHeight = container?.scrollHeight ?? 0;
2887
+ const prevScrollTop = container?.scrollTop ?? 0;
2888
+ onLoadMore();
2889
+ requestAnimationFrame(() => {
2890
+ if (container) {
2891
+ const newScrollHeight = container.scrollHeight;
2892
+ container.scrollTop = prevScrollTop + (newScrollHeight - prevScrollHeight);
2893
+ }
2894
+ });
2895
+ }
2896
+ },
2897
+ { root: containerRef.current, threshold: 0.1 }
2898
+ );
2899
+ observer.observe(topRef.current);
2900
+ return () => observer.disconnect();
2901
+ }, [hasMore, onLoadMore, isLoadingMore]);
2902
+ if (items.length === 0) {
2903
+ return /* @__PURE__ */ jsx11(
2904
+ "div",
2905
+ {
2906
+ className: "hw-messages",
2907
+ role: "log",
2908
+ "aria-label": t("widget.aria_messages"),
2909
+ "aria-live": "polite",
2910
+ children: /* @__PURE__ */ jsxs6("div", { className: "hw-empty", children: [
2911
+ /* @__PURE__ */ jsx11("div", { className: "hw-empty-icon", children: /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx11("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) }) }),
2912
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, color: "#aaa" }, children: t("widget.start_a_conversation") })
2913
+ ] })
2914
+ }
2915
+ );
2916
+ }
2917
+ let lastDateKey = "";
2918
+ const todayKey = (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA");
2919
+ const yesterday = /* @__PURE__ */ new Date();
2920
+ yesterday.setDate(yesterday.getDate() - 1);
2921
+ const yesterdayKey = yesterday.toLocaleDateString("en-CA");
2922
+ return /* @__PURE__ */ jsxs6(
2923
+ "div",
2924
+ {
2925
+ ref: containerRef,
2926
+ className: "hw-messages",
2927
+ role: "log",
2928
+ "aria-label": t("widget.aria_messages"),
2929
+ "aria-live": "polite",
2930
+ children: [
2931
+ /* @__PURE__ */ jsx11("div", { ref: topRef, className: "hw-load-more-trigger", children: isLoadingMore && /* @__PURE__ */ jsxs6("div", { className: "hw-load-more-spinner", children: [
2932
+ /* @__PURE__ */ jsx11("div", {}),
2933
+ /* @__PURE__ */ jsx11("div", {}),
2934
+ /* @__PURE__ */ jsx11("div", {})
2935
+ ] }) }),
2936
+ items.map((item) => {
2937
+ if (item.type === "event") {
2938
+ return /* @__PURE__ */ jsx11(EventCard, { text: item.text ?? "" }, item._stableKey ?? item.id);
2939
+ }
2940
+ const itemDate = new Date(item.createdAt);
2941
+ const dateKey = itemDate.toLocaleDateString("en-CA");
2942
+ const isNewBucket = dateKey !== lastDateKey;
2943
+ if (isNewBucket) lastDateKey = dateKey;
2944
+ const showDateSep = isNewBucket && dateKey !== todayKey;
2945
+ const dateLabel = dateKey === yesterdayKey ? t("widget.yesterday") : itemDate.toLocaleDateString(getLocale(), {
2946
+ weekday: "long",
2947
+ month: "short",
2948
+ day: "numeric"
2949
+ });
2950
+ const sender = item.visitorId ? "visitor" : item.aiAgentId ? "ai" : "team";
2951
+ return /* @__PURE__ */ jsxs6(React.Fragment, { children: [
2952
+ showDateSep && /* @__PURE__ */ jsxs6("div", { className: "hw-date-sep", children: [
2953
+ /* @__PURE__ */ jsx11("div", { className: "hw-date-sep-line" }),
2954
+ /* @__PURE__ */ jsx11("div", { className: "hw-date-sep-text", children: dateLabel }),
2955
+ /* @__PURE__ */ jsx11("div", { className: "hw-date-sep-line" })
2956
+ ] }),
2957
+ /* @__PURE__ */ jsx11(
2958
+ MessageBubble,
2959
+ {
2960
+ text: item.text ?? "",
2961
+ sender,
2962
+ senderName: sender === "visitor" ? visitorName : item.userName ?? void 0,
2963
+ time: item.createdAt,
2964
+ parts: parseParts(item.parts)
2965
+ }
2966
+ )
2967
+ ] }, item._stableKey ?? item.id);
2968
+ }),
2969
+ renderAfterMessages,
2970
+ isAiProcessing && /* @__PURE__ */ jsxs6("div", { className: "hw-typing", children: [
2971
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-label", children: agentName }),
2972
+ /* @__PURE__ */ jsxs6("div", { className: "hw-typing-dots", children: [
2973
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-dot" }),
2974
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-dot" }),
2975
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-dot" })
2976
+ ] })
2977
+ ] }),
2978
+ isTyping && !isAiProcessing && /* @__PURE__ */ jsx11("div", { className: "hw-typing", children: /* @__PURE__ */ jsxs6("div", { className: "hw-typing-dots", children: [
2979
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-dot" }),
2980
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-dot" }),
2981
+ /* @__PURE__ */ jsx11("div", { className: "hw-typing-dot" })
2982
+ ] }) }),
2983
+ /* @__PURE__ */ jsx11("div", { ref: bottomRef })
2984
+ ]
2985
+ }
2986
+ );
2987
+ }
2988
+ function parseEventData(text) {
2989
+ if (!text.startsWith("{")) return null;
2990
+ try {
2991
+ const data = JSON.parse(text);
2992
+ if (data.action && data.name) return data;
2993
+ } catch {
2994
+ }
2995
+ return null;
2996
+ }
2997
+ function EventCard({ text }) {
2998
+ const data = parseEventData(text);
2999
+ if (data) {
3000
+ const message = data.action === "joined" ? t("widget.joined") : t("widget.left");
3001
+ return /* @__PURE__ */ jsxs6("div", { className: "hw-event hw-event--card", children: [
3002
+ /* @__PURE__ */ jsx11("div", { className: "hw-event-avatar", children: data.image ? /* @__PURE__ */ jsx11("img", { src: data.image, alt: "", className: "hw-event-avatar-img" }) : /* @__PURE__ */ jsx11("span", { children: getInitials(data.name) }) }),
3003
+ /* @__PURE__ */ jsxs6("div", { className: "hw-event-body", children: [
3004
+ /* @__PURE__ */ jsx11("span", { className: "hw-event-name", children: data.name }),
3005
+ " ",
3006
+ /* @__PURE__ */ jsx11("span", { className: "hw-event-action", children: message })
3007
+ ] })
3008
+ ] });
3009
+ }
3010
+ return /* @__PURE__ */ jsx11("div", { className: "hw-event", children: /* @__PURE__ */ jsx11("div", { className: "hw-event-text", children: text }) });
3011
+ }
3012
+
3013
+ // src/themed/default-widget.tsx
3014
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
3015
+ function resolveLabel2(label) {
3016
+ if (!label.startsWith("widget.")) return label;
3017
+ const resolved = t(label);
3018
+ if (resolved === label)
3019
+ return label.replace("widget.", "").replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
3020
+ return resolved;
3021
+ }
3022
+ var EMPTY_CONTENT_TABS = [];
3023
+ var DEFAULT_EN_WELCOME = "Hi there \u{1F44B}\nHow can we help?";
3024
+ var DEFAULT_EN_SUBTITLE = "Usually replies in minutes";
3025
+ function HelpwayMark({ className }) {
3026
+ return /* @__PURE__ */ jsx12(
3027
+ "svg",
3028
+ {
3029
+ className,
3030
+ width: "100%",
3031
+ height: "100%",
3032
+ viewBox: "0 0 42 42",
3033
+ fill: "none",
3034
+ xmlns: "http://www.w3.org/2000/svg",
3035
+ "aria-hidden": "true",
3036
+ children: /* @__PURE__ */ jsx12(
3037
+ "path",
3038
+ {
3039
+ d: "M28.7 0H12.8L0 12.8V28.7L12.8 41.5H28.7L41.5 28.7V12.8L28.7 0ZM15 28.9L6.80002 20.7L15 12.5C18.1 9.4 23.2 9.4 26.3 12.5L34.5 20.7L26.3 28.9C23.2 32 18.2 32 15 28.9Z",
3040
+ fill: "currentColor"
3041
+ }
3042
+ )
3043
+ }
3044
+ );
3045
+ }
3046
+ function WidgetLogo({ className }) {
3047
+ const agentConfig = useWidgetStore((s) => s.agentConfig);
3048
+ const logoUrl = useWidgetStore((s) => s.website?.logoUrl);
3049
+ if (agentConfig?.avatarUrl) {
3050
+ return /* @__PURE__ */ jsx12("img", { src: agentConfig.avatarUrl, alt: "", className: `hw-agent-img ${className ?? ""}` });
3051
+ }
3052
+ if (agentConfig?.avatarPreset) {
3053
+ const color = agentConfig.brandColor ?? "#0a0a0a";
3054
+ return /* @__PURE__ */ jsx12(
3055
+ "div",
3056
+ {
3057
+ className: `hw-agent-icon ${className ?? ""}`,
3058
+ style: {
3059
+ backgroundColor: color,
3060
+ display: "flex",
3061
+ alignItems: "center",
3062
+ justifyContent: "center",
3063
+ borderRadius: 8
3064
+ },
3065
+ children: /* @__PURE__ */ jsx12(AgentPresetIcon, { presetId: agentConfig.avatarPreset })
3066
+ }
3067
+ );
3068
+ }
3069
+ if (logoUrl) {
3070
+ return /* @__PURE__ */ jsx12("img", { src: logoUrl, alt: "", className: `hw-agent-img ${className ?? ""}` });
3071
+ }
3072
+ return /* @__PURE__ */ jsx12(HelpwayMark, { className });
3073
+ }
3074
+ var PRESET_SVG = {
3075
+ // Helpway brand mark — octagonal chamfer with stylized "W" inside.
3076
+ // First entry so it's the default preset for new orgs.
3077
+ helpway: '<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4 0H7.31L0 7.31V16.4L7.31 23.71H16.4L23.71 16.4V7.31L16.4 0ZM8.57 16.51L3.89 11.83L8.57 7.14C10.34 5.37 13.26 5.37 15.03 7.14L19.71 11.83L15.03 16.51C13.26 18.29 10.4 18.29 8.57 16.51Z" fill="white"/>',
3078
+ atom: '<path fill-rule="evenodd" clip-rule="evenodd" d="M9.67904 3.29155C10.7467 1.56948 13.2537 1.56948 14.3214 3.29155L15.1308 4.59707C15.2462 4.7833 15.3634 4.9684 15.4823 5.15233C15.7005 5.1305 15.9185 5.10662 16.1363 5.08068L17.6626 4.89893C19.6747 4.65934 21.2396 6.61723 20.5571 8.52637L20.0401 9.97255C19.9664 10.1786 19.8947 10.3854 19.825 10.5927C19.9779 10.7493 20.1323 10.9045 20.2882 11.0583L21.3821 12.1371C22.8256 13.5609 22.2668 16.0039 20.3491 16.6605L18.895 17.1584C18.6876 17.2294 18.4809 17.3023 18.275 17.3772C18.2478 17.5943 18.2226 17.8117 18.1995 18.0294L18.0372 19.5565C17.823 21.572 15.5635 22.6577 13.8546 21.5695L12.5584 20.7442C12.3734 20.6264 12.1873 20.5103 12.0002 20.3961C11.8131 20.5103 11.627 20.6264 11.442 20.7442L10.1458 21.5695C8.4369 22.6577 6.17739 21.572 5.96321 19.5565L5.80094 18.0294C5.7778 17.8117 5.75263 17.5943 5.72541 17.3772C5.51949 17.3023 5.31282 17.2294 5.10542 17.1584L3.65133 16.6605C1.7336 16.0039 1.17475 13.5609 2.61829 12.1371L3.71216 11.0583C3.86807 10.9045 4.0225 10.7493 4.17541 10.5927C4.10567 10.3854 4.03395 10.1786 3.96028 9.97255L3.44329 8.52637C2.7608 6.61723 4.32567 4.65934 6.33777 4.89893L7.86408 5.08068C8.08191 5.10662 8.29993 5.1305 8.5181 5.15233C8.63698 4.9684 8.75417 4.7833 8.86963 4.59707L9.67904 3.29155ZM7.61407 6.4827L6.16966 6.31071C5.20357 6.19567 4.45578 7.13505 4.78207 8.04778L5.2716 9.41715C6.10452 8.48167 6.88648 7.5021 7.61407 6.4827ZM5.78854 10.961C7.04552 9.61105 8.19784 8.16742 9.23549 6.64268C11.076 6.78287 12.9244 6.78287 14.7649 6.64268C15.8026 8.16743 16.9549 9.61105 18.2119 10.961C17.6657 12.722 17.2545 14.522 16.9818 16.3454C15.2628 17.0169 13.5974 17.8181 12.0002 18.7422C10.403 17.8181 8.73761 17.0169 7.01863 16.3454C6.74592 14.522 6.3347 12.722 5.78854 10.961ZM4.65269 12.1275L3.61666 13.1494C2.92581 13.8308 3.19234 15.0006 4.11188 15.3154L5.48873 15.7868C5.27593 14.553 4.99684 13.3315 4.65269 12.1275ZM7.22343 17.9611L7.377 19.4062C7.47946 20.3704 8.56185 20.8926 9.38218 20.3703L10.6083 19.5895C9.51004 18.9865 8.38012 18.4429 7.22343 17.9611ZM13.3921 19.5895L14.6182 20.3703C15.4385 20.8926 16.5209 20.3704 16.6234 19.4062L16.777 17.9611C15.6203 18.4429 14.4903 18.9865 13.3921 19.5895ZM18.5117 15.7868L19.8885 15.3154C20.8081 15.0006 21.0746 13.8308 20.3837 13.1494L19.3477 12.1275C19.0035 13.3315 18.7245 14.553 18.5117 15.7868ZM18.7288 9.41715L19.2183 8.04778C19.5446 7.13505 18.7968 6.19567 17.8307 6.31071L16.3863 6.4827C17.1139 7.5021 17.8959 8.48167 18.7288 9.41715ZM13.8786 5.2756C12.6272 5.3429 11.3731 5.3429 10.1218 5.2756L10.8874 4.04072C11.3991 3.21542 12.6013 3.21542 13.113 4.04072L13.8786 5.2756Z" fill="white"/>',
3079
+ brain: '<path d="M15 10C13.3431 10 12 8.65685 12 7" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 14C10.6569 14 12 15.3431 12 17" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 3.53516L12 20.2422" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 14.5C21 15.9633 20.102 17.2168 18.8271 17.7396C18.2888 19.6221 16.5553 21 14.5 21C13.5749 21 12.7151 20.7209 12 20.2422C11.285 20.7209 10.4251 21 9.5 21C7.44468 21 5.71119 19.6221 5.17291 17.7396C3.89797 17.2168 3 15.9633 3 14.5C3 13.5207 3.40223 12.6353 4.05051 12C3.40223 11.3647 3 10.4793 3 9.5C3 7.69511 4.36618 6.20931 6.12085 6.0203C6.55779 4.28478 8.12886 3 10 3C10.7286 3 11.4117 3.19479 12 3.53513C12.5883 3.19479 13.2714 3 14 3C15.8711 3 17.4422 4.28478 17.8792 6.0203C19.6338 6.20931 21 7.69511 21 9.5C21 10.4793 20.5978 11.3647 19.9495 12C20.5978 12.6353 21 13.5207 21 14.5Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3080
+ robot: '<path d="M12 4H7C5.89543 4 5 4.89543 5 6V11C5 12.1046 5.89543 13 7 13H17C18.1046 13 19 12.1046 19 11V6C19 4.89543 18.1046 4 17 4H12ZM12 4V2M6 15L4 17M6 15C6 18.3137 8.68629 21 12 21C15.3137 21 18 18.3137 18 15M6 15V13M18 15L20 17M18 15V13M9 8V9M15 8V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3081
+ sparkle: '<path fill-rule="evenodd" clip-rule="evenodd" d="M21 12.5C14.75 12.5 12 15.4028 12 22C12 15.4028 9.25 12.5 3 12.5C9.25 12.5 12 9.59722 12 3C12 9.59722 14.75 12.5 21 12.5Z" stroke="white" stroke-width="2" stroke-linejoin="round"/>',
3082
+ bolt: '<path fill-rule="evenodd" clip-rule="evenodd" d="M13.1981 20.0571C11.7326 20.0571 10.2936 19.5252 9.46756 18.3817L9.1762 19.7304L3.79688 22.5824L4.3776 19.7304L8.2951 2H13.0916L11.7059 8.24953C12.8251 7.02621 13.8643 6.57412 15.1967 6.57412C18.0746 6.57412 19.9931 8.46227 19.9931 11.9194C19.9931 15.483 17.7815 20.0571 13.1981 20.0571ZM15.0368 12.9301C15.0368 14.5788 13.8643 15.8287 12.3455 15.8287C11.4927 15.8287 10.72 15.5096 10.2137 14.9511L10.9598 11.6802C11.5194 11.1216 12.1589 10.8025 12.905 10.8025C14.0509 10.8025 15.0368 11.6536 15.0368 12.9301Z" fill="white"/>',
3083
+ agent: '<path d="M12 8.5V12M12 12H8C6.89543 12 6 12.8954 6 14V15.5M12 12H16C17.1046 12 18 12.8954 18 14V15.5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="6" r="2.5" stroke="white" stroke-width="2"/><circle cx="6" cy="18" r="2.5" stroke="white" stroke-width="2"/><circle cx="18" cy="18" r="2.5" stroke="white" stroke-width="2"/>',
3084
+ bubble: '<path d="M4.00195 6.00003C4.00195 4.89546 4.89738 4.00003 6.00195 4.00002L18.0019 4C19.1065 4 20.002 4.89543 20.002 6V16.0358C20.002 17.1403 19.1065 18.0358 18.002 18.0358L15.3757 18.0358C15.1427 18.0358 14.9171 18.1171 14.7377 18.2657L11.9977 20.5358L9.29398 18.2694C9.11393 18.1185 8.8865 18.0358 8.65157 18.0358H6.00195C4.89738 18.0358 4.00195 17.1403 4.00195 16.0358V6.00003Z" fill="white"/><path d="M13.2395 9.43518L12.5426 7.62334C12.4561 7.39842 12.24 7.25 11.999 7.25C11.758 7.25 11.5419 7.39842 11.4554 7.62334L10.7586 9.43518C10.657 9.69927 10.4483 9.90797 10.1842 10.0095L8.37236 10.7064C8.14744 10.7929 7.99902 11.009 7.99902 11.25C7.99902 11.491 8.14744 11.7071 8.37236 11.7936L10.1842 12.4905C10.4483 12.592 10.657 12.8007 10.7586 13.0648L11.4554 14.8767C11.5419 15.1016 11.758 15.25 11.999 15.25C12.24 15.25 12.4561 15.1016 12.5426 14.8767L13.2395 13.0648C13.3411 12.8007 13.5498 12.592 13.8138 12.4905L15.6257 11.7936C15.8506 11.7071 15.999 11.491 15.999 11.25C15.999 11.009 15.8506 10.7929 15.6257 10.7064L13.8138 10.0095C13.5498 9.90797 13.3411 9.69927 13.2395 9.43518Z" fill="white"/>',
3085
+ lightbulb: '<path d="M10 21H14M15.6076 15C17.6404 13.7751 19 11.5463 19 9C19 5.13401 15.866 2 12 2C8.13401 2 5 5.13401 5 9C5 11.5463 6.35958 13.7751 8.39241 15M15.6076 15C15.4111 15.1184 15.2084 15.2274 15 15.3264V17C15 17.5523 14.5523 18 14 18H10C9.44772 18 9 17.5523 9 17V15.3264C8.7916 15.2274 8.58886 15.1184 8.39241 15M15.6076 15H8.39241" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3086
+ wand: '<path d="M11.3908 12.6089L4 19.9996" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.3943 5.2792C18.5999 5.1944 18.8056 5.40008 18.7208 5.60565L17.1697 9.36581C17.1339 9.45248 17.1497 9.55186 17.2105 9.62323L19.8476 12.72C19.9918 12.8894 19.8597 13.1485 19.638 13.1314L15.5826 12.8182C15.4891 12.811 15.3994 12.8566 15.3503 12.9365L13.22 16.4015C13.1035 16.591 12.8163 16.5455 12.764 16.3293L11.8087 12.3756C11.7867 12.2844 11.7156 12.2133 11.6244 12.1913L7.67069 11.236C7.45453 11.1837 7.40903 10.8964 7.59847 10.78L11.0635 8.64965C11.1434 8.60055 11.189 8.51089 11.1818 8.41743L10.8686 4.36198C10.8515 4.14027 11.1106 4.00822 11.2799 4.15239L14.3768 6.78952C14.4481 6.8503 14.5475 6.86604 14.6342 6.83029L18.3943 5.2792Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3087
+ chip: '<path d="M8 5.5V4M12 5.5V4M16 5.5V4M8 20V18.5M12 20V18.5M16 20V18.5M8.75 15H15.25C15.6642 15 16 14.6642 16 14.25V9.75C16 9.33579 15.6642 9 15.25 9H8.75C8.33579 9 8 9.33579 8 9.75V14.25C8 14.6642 8.33579 15 8.75 15ZM6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3088
+ code: '<path d="M10 9.5L8.20711 11.2929C7.81658 11.6834 7.81658 12.3166 8.20711 12.7071L10 14.5M14 9.5L15.7929 11.2929C16.1834 11.6834 16.1834 12.3166 15.7929 12.7071L14 14.5M6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3089
+ lightning: '<path d="M4.11593 12.2764L8.61593 3.27639C8.70063 3.107 8.87376 3 9.06315 3H17.121C17.5097 3 17.7497 3.42399 17.5498 3.75725L15.4585 7.24275C15.2585 7.57601 15.4986 8 15.8872 8H20.6909C21.1505 8 21.3666 8.56776 21.0235 8.87338L7.38117 21.0236C7.00034 21.3628 6.41568 20.981 6.57301 20.4959L8.79194 13.6543C8.8967 13.3312 8.6559 13 8.31633 13H4.56314C4.19145 13 3.94971 12.6088 4.11593 12.2764Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3090
+ vibe: '<path d="M18.9847 2.4329C18.9568 2.18651 18.7485 2.00025 18.5005 2C18.2525 1.99975 18.0438 2.18557 18.0154 2.43191C17.838 3.96993 16.9699 4.83801 15.4319 5.01541C15.1856 5.04382 14.9997 5.25253 15 5.5005C15.0003 5.74847 15.1865 5.9568 15.4329 5.9847C16.9491 6.15645 17.8774 7.01617 18.0141 8.55536C18.0365 8.80725 18.2477 9.00028 18.5005 9C18.7534 8.99971 18.9642 8.80621 18.986 8.55426C19.1173 7.03683 20.0368 6.11734 21.5543 5.98599C21.8062 5.96418 21.9997 5.75344 22 5.50055C22.0003 5.24766 21.8073 5.03648 21.5554 5.01411C20.0162 4.87738 19.1564 3.94911 18.9847 2.4329Z" fill="white"/><path d="M2 20C2 16.6863 4.68629 14 8 14H8.5" fill="white"/><path d="M11 20L12.6892 14.0879C12.8732 13.444 13.4617 13 14.1315 13H20.5417C21.2725 13 21.8001 13.6995 21.5994 14.4022L20.2072 19.2747C20.0846 19.704 19.6922 20 19.2457 20H11ZM11 20H7" fill="white"/><circle cx="8.75" cy="7.5" r="3.5" stroke="white" stroke-width="2"/>',
3091
+ network: '<path d="M12 6.8501L16 7.2501M17.75 10.0001L16.55 14.0001M13.5 14.3572L12 12.0001L10.4091 9.5001M6.25 14.0001L7.45 10.0001M8 16.7501L12 17.1501" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="18.5" cy="7.5" r="2.5" stroke="white" stroke-width="2"/><circle cx="5.5" cy="16.5" r="2.5" stroke="white" stroke-width="2"/><circle cx="8.5" cy="6.5" r="3.5" stroke="white" stroke-width="2"/><circle cx="15.5" cy="17.5" r="3.5" stroke="white" stroke-width="2"/>',
3092
+ console: '<path d="M7.5 8L9.25 9.75L7.5 11.5M12 11.5H14M6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3093
+ prompt: '<path d="M6 5H5C3.89543 5 3 5.89543 3 7V8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 5H19C20.1046 5 21 5.89543 21 7V8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 16V17C21 18.1046 20.1046 19 19 19H18" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 19H5C3.89543 19 3 18.1046 3 17V16" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 10H16" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 14H14" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3094
+ globe: '<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 12H3" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 21C10.067 21 8.5 16.9706 8.5 12C8.5 7.02944 10.067 3 12 3C13.933 3 15.5 7.02944 15.5 12C15.5 16.9706 13.933 21 12 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3095
+ target: '<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 12C13 12.5523 12.5523 13 12 13C11.4477 13 11 12.5523 11 12C11 11.4477 11.4477 11 12 11C12.5523 11 13 11.4477 13 12Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3096
+ diamond: '<path d="M8.28256 4C7.77978 4 7.29545 4.18937 6.926 4.53039L2.53016 8.58809C1.69548 9.35857 1.66929 10.6687 2.47251 11.4719L10.5864 19.5858C11.3674 20.3668 12.6338 20.3668 13.4148 19.5858L21.5287 11.4719C22.3319 10.6687 22.3057 9.35857 21.471 8.58809L17.0752 4.53039C16.7057 4.18937 16.2214 4 15.7186 4H8.28256Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.5 8L7.5 10L9.5 12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3097
+ star: '<path d="M11.5288 2.29524C11.7179 1.90159 12.2821 1.90159 12.4712 2.29524L14.9458 7.44648C15.0219 7.60491 15.1735 7.71434 15.3487 7.73728L21.0456 8.4832C21.4809 8.5402 21.6552 9.07312 21.3368 9.37342L17.1693 13.303C17.0412 13.4238 16.9832 13.6009 17.0154 13.7735L18.0616 19.3857C18.1416 19.8146 17.6852 20.144 17.2993 19.9359L12.249 17.2133C12.0937 17.1296 11.9063 17.1296 11.751 17.2133L6.70073 19.9359C6.3148 20.144 5.85841 19.8146 5.93837 19.3857L6.98459 13.7735C7.01677 13.6009 6.95885 13.4238 6.83068 13.303L2.66324 9.37342C2.34476 9.07312 2.51909 8.5402 2.95444 8.4832L8.65127 7.73728C8.82648 7.71434 8.97811 7.60491 9.05422 7.44648L11.5288 2.29524Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3098
+ heart: '<path d="M21 10C21 15.75 12.75 20 12 20C11.25 20 3 15.75 3 10C3 6 5.5 4 8 4C10.5 4 12 5.5 12 5.5C12 5.5 13.5 4 16 4C18.5 4 21 6 21 10Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3099
+ shield: '<path d="M20 7.17737C20 6.32338 19.4578 5.56361 18.6502 5.286L12.6502 3.2235C12.2288 3.07866 11.7712 3.07866 11.3498 3.22349L5.34984 5.286C4.54224 5.56361 4 6.32338 4 7.17737V11.9123C4 16.8848 8 19 12 21.1579C16 19 20 16.8848 20 11.9123V7.17737Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3100
+ rocket: '<path d="M3 21H5C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19V21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M7.00098 13H4.76717C3.98987 13 3.50976 12.152 3.90968 11.4855L5.41837 8.97101C5.77982 8.3686 6.43083 8 7.13336 8H11.251C13.7671 5.25513 16.4925 3.3474 20.0017 3.04278C20.5519 2.99502 21.006 3.44906 20.9582 3.99927C20.6536 7.50845 18.7459 10.2339 16.001 12.75V16.8676C16.001 17.5701 15.6324 18.2212 15.03 18.5826L12.5155 20.0913C11.849 20.4912 11.001 20.0111 11.001 19.2338V17L7.00098 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 13L11.25 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 12.75L11 17" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3101
+ crown: '<path d="M4.16831 17.4079L2 7L8 10L12 4L16 10L22 7L19.8317 17.4079C19.6385 18.3353 18.8211 19 17.8737 19H6.12628C5.17892 19 4.36153 18.3353 4.16831 17.4079Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3102
+ trophy: '<path d="M12.0039 17V14" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 5H19C20.1046 5 21 5.89543 21 7C21 8.65685 19.6569 10 18 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 5C6 3.89543 6.89543 3 8 3H16C17.1046 3 18 3.89543 18 5V8C18 11.3137 15.3137 14 12 14C8.68629 14 6 11.3137 6 8V5Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 19C7 17.8954 7.89543 17 9 17H15C16.1046 17 17 17.8954 17 19C17 20.1046 16.1046 21 15 21H9C7.89543 21 7 20.1046 7 19Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 5H5C3.89543 5 3 5.89543 3 7C3 8.65685 4.34315 10 6 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3103
+ compass: '<path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M14.6558 8.73023L10.5513 9.84965C10.2097 9.94281 9.94281 10.2097 9.84965 10.5513L8.73023 14.6558C8.62843 15.0291 8.97092 15.3716 9.34417 15.2698L13.4487 14.1504C13.7903 14.0572 14.0572 13.7903 14.1504 13.4487L15.2698 9.34417C15.3716 8.97092 15.0291 8.62843 14.6558 8.73023Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3104
+ fire: '<path d="M19.5 13.8122C19.5 23.3907 4.5 23.4012 4.5 13.8122C4.5 6.61352 12 2.5 12 2.5C12 2.5 19.5 6.61352 19.5 13.8122Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.5 17.4061C15.5 22.1954 8.5 22.2006 8.5 17.4061C8.5 13.8068 12 11.75 12 11.75C12 11.75 15.5 13.8068 15.5 17.4061Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3105
+ key: '<path d="M20.8002 9.8753L20.0194 10.5L20.0194 10.5L20.8002 9.8753ZM22.0002 11.3753L21.2194 12V12L22.0002 11.3753ZM22.0002 12.6247L21.2194 12V12L22.0002 12.6247ZM20.8002 14.1247L20.0194 13.5L20.0194 13.5L20.8002 14.1247ZM18 14.5L17.5528 15.3944C17.6916 15.4639 17.8448 15.5 18 15.5V14.5ZM16 13.5L16.4472 12.6056C16.1657 12.4648 15.8343 12.4648 15.5528 12.6056L16 13.5ZM14 14.5V15.5C14.1552 15.5 14.3084 15.4639 14.4472 15.3944L14 14.5ZM11.0141 9.01843L11.8165 8.42152L11.8165 8.42152L11.0141 9.01843ZM11.0141 14.9816L10.2118 14.3847V14.3847L11.0141 14.9816ZM6.5 12C6.5 11.7239 6.72386 11.5 7 11.5V13.5C7.82843 13.5 8.5 12.8284 8.5 12H6.5ZM7 11.5C7.27614 11.5 7.5 11.7239 7.5 12H5.5C5.5 12.8284 6.17157 13.5 7 13.5V11.5ZM7.5 12C7.5 12.2761 7.27614 12.5 7 12.5V10.5C6.17157 10.5 5.5 11.1716 5.5 12H7.5ZM7 12.5C6.72386 12.5 6.5 12.2761 6.5 12H8.5C8.5 11.1716 7.82843 10.5 7 10.5V12.5ZM6.5 13H7.5V11H6.5V13ZM7 16C4.79086 16 3 14.2091 3 12H1C1 15.3137 3.68629 18 7 18V16ZM3 12C3 9.79086 4.79086 8 7 8V6C3.68629 6 1 8.68629 1 12H3ZM20.0194 10.5L21.2194 12L22.7811 10.7506L21.5811 9.25061L20.0194 10.5ZM21.2194 12L20.0194 13.5L21.5811 14.7494L22.7811 13.2494L21.2194 12ZM20.0194 13.5H18V15.5H20.0194V13.5ZM18.4472 13.6056L16.4472 12.6056L15.5528 14.3944L17.5528 15.3944L18.4472 13.6056ZM15.5528 12.6056L13.5528 13.6056L14.4472 15.3944L16.4472 14.3944L15.5528 12.6056ZM11.9079 10.5H20.0194V8.5H11.9079V10.5ZM7 8C8.31459 8 9.48107 8.63311 10.2118 9.61533L11.8165 8.42152C10.7246 6.9539 8.97356 6 7 6V8ZM14 13.5H11.9079V15.5H14V13.5ZM10.2118 14.3847C9.48107 15.3669 8.31459 16 7 16V18C8.97356 18 10.7246 17.0461 11.8165 15.5785L10.2118 14.3847ZM11.9079 13.5C11.1751 13.5 10.5749 13.8967 10.2118 14.3847L11.8165 15.5785C11.8445 15.5408 11.8734 15.5183 11.8929 15.5074C11.9106 15.4976 11.9148 15.5 11.9079 15.5V13.5ZM20.0194 13.5L20.0194 13.5V15.5C20.6269 15.5 21.2016 15.2238 21.5811 14.7494L20.0194 13.5ZM11.9079 8.5C11.9148 8.5 11.9106 8.50239 11.8929 8.49258C11.8734 8.48172 11.8445 8.45923 11.8165 8.42152L10.2118 9.61533C10.5749 10.1033 11.1751 10.5 11.9079 10.5V8.5ZM21.2194 12V12L22.7811 13.2494C23.3655 12.519 23.3655 11.481 22.7811 10.7506L21.2194 12ZM21.5811 9.25061C21.2016 8.77618 20.6269 8.5 20.0194 8.5V10.5L20.0194 10.5L21.5811 9.25061Z" fill="white"/>',
3106
+ lock: '<path d="M5 12C5 10.8954 5.89543 10 7 10H17C18.1046 10 19 10.8954 19 12V19C19 20.1046 18.1046 21 17 21H7C5.89543 21 5 20.1046 5 19V12Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 9.5V7C16 4.79086 14.2091 3 12 3C9.79086 3 8 4.79086 8 7V9.5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 14V17" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3107
+ bookmark: '<path d="M19 19.9948V5C19 3.89543 18.1046 3 17 3H7C5.89543 3 5 3.89543 5 5V19.9948C5 20.8047 5.91243 21.2787 6.57503 20.813L10.8499 17.8083C11.54 17.3233 12.46 17.3233 13.1501 17.8083L17.425 20.813C18.0876 21.2787 19 20.8047 19 19.9948Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3108
+ store: '<path d="M4 10V18C4 19.1046 4.89543 20 6 20H18C19.1046 20 20 19.1046 20 18V10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M14.4996 4H9.49956L9.2133 8.00762C9.0978 9.62461 10.3785 11 11.9996 11C13.6207 11 14.9013 9.62461 14.7858 8.00763L14.4996 4Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.50047 4H5.73506C4.73976 4 3.89592 4.73186 3.75516 5.71716L3.4699 7.71396C3.22214 9.44831 4.56792 11 6.31987 11C7.83025 11 9.08385 9.83276 9.19146 8.32621L9.50047 4Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.2654 4H14.5L14.809 8.32621C14.9166 9.83276 16.1702 11 17.6806 11C19.4326 11 20.7783 9.44831 20.5306 7.71396L20.2453 5.71716C20.1046 4.73186 19.2607 4 18.2654 4Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3109
+ bag: '<path d="M15.0001 8C15.0001 9.65685 13.6569 11 12.0001 11C10.3432 11 9.00008 9.65685 9.00008 8M4.88288 5.87524L4.13288 17.8752C4.06092 19.0266 4.97534 20 6.12899 20H17.8712C19.0248 20 19.9392 19.0266 19.8673 17.8752L19.1173 5.87524C19.0514 4.82117 18.1773 4 17.1212 4H6.87899C5.82286 4 4.94876 4.82117 4.88288 5.87524Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3110
+ "credit-card": '<path d="M3 10V17C3 18.1046 3.89543 19 5 19H19C20.1046 19 21 18.1046 21 17V10M3 10V7C3 5.89543 3.89543 5 5 5H19C20.1046 5 21 5.89518 21 6.99975V10M3 10H21" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3111
+ receipt: '<path d="M9 8H15M9 12H11M19 21V5C19 3.89543 18.1046 3 17 3H7C5.89543 3 5 3.89543 5 5V21L7.33333 19L9.66667 21L12 19L14.3333 21L16.6667 19L19 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3112
+ bank: '<path d="M11.1056 2.94723L3.68328 6.65838C3.26452 6.86776 3 7.29576 3 7.76395C3 8.44661 3.55341 9.00002 4.23607 9.00002H19.7639C20.4466 9.00002 21 8.44661 21 7.76395C21 7.29576 20.7355 6.86776 20.3167 6.65838L12.8944 2.94723C12.3314 2.6657 11.6686 2.6657 11.1056 2.94723Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M19.6126 20H4.38749C3.70493 20 3.22296 19.3313 3.4388 18.6838L3.54421 18.3675C3.81644 17.5509 4.58072 17 5.44158 17H18.5585C19.4194 17 20.1837 17.5509 20.4559 18.3675L20.5613 18.6838C20.7772 19.3313 20.2952 20 19.6126 20Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 9V17" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 17V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M5 9V17" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 17V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3113
+ wallet: '<path d="M3 8V7C3 5.89543 3.89543 5 5 5H19C20.1046 5 21 5.89543 21 7V8M3 8H21M3 8V11M21 8V11M21 11V17C21 18.1046 20.1046 19 19 19H5C3.89543 19 3 18.1046 3 17V11M21 11H15C15 12.1046 14.1046 13 13 13H11C9.89543 13 9 12.1046 9 11H3" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3114
+ package: '<path d="M9 4V7.5C9 8.32843 9.67157 9 10.5 9H13.5C14.3284 9 15 8.32843 15 7.5V4M14 16H16M4 6V18C4 19.1046 4.89543 20 6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3115
+ truck: '<path d="M10 16.5C10 17.8807 8.88071 19 7.5 19C6.11929 19 5 17.8807 5 16.5C5 15.1193 6.11929 14 7.5 14C8.88071 14 10 15.1193 10 16.5Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 16.5C19 17.8807 17.8807 19 16.5 19C15.1193 19 14 17.8807 14 16.5C14 15.1193 15.1193 14 16.5 14C17.8807 14 19 15.1193 19 16.5Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 16H14" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 14V7H6.07037C5.40166 7 4.7772 7.3342 4.40627 7.8906L3.3359 9.49615C3.11688 9.82469 3 10.2107 3 10.6056V14.2857C3 15.2325 3.76751 16 4.71429 16" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 9V7C9 5.89543 9.89543 5 11 5H19C20.1046 5 21 5.89543 21 7V14C21 15.1046 20.1046 16 19 16" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3116
+ megaphone: '<path d="M17.9961 14.0001C19.6529 14.0001 20.9961 12.657 20.9961 11.0001C20.9961 9.34325 19.6529 8.0001 17.9961 8.0001M12.8254 18.0001C12.4136 19.1653 11.3023 20.0001 9.99609 20.0001C8.33924 20.0001 6.99609 18.657 6.99609 17.0001V15.5001M6.99829 6.5001V15.5001M17.9961 5.73752V16.2627C17.9961 17.617 16.6784 18.5797 15.3882 18.1681L4.38819 14.6585C3.55899 14.394 2.99609 13.6235 2.99609 12.7532V9.24704C2.99609 8.37667 3.55899 7.60622 4.38819 7.34167L15.3882 3.83215C16.6784 3.42051 17.9961 4.38325 17.9961 5.73752Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3117
+ email: '<path d="M20.782 6.09202L19.891 6.54601L19.891 6.54601L20.782 6.09202ZM19.908 5.21799L20.362 4.32698V4.32698L19.908 5.21799ZM4.09202 5.21799L3.63803 4.32698L3.63803 4.32698L4.09202 5.21799ZM3.21799 6.09202L2.32698 5.63803L2.32698 5.63803L3.21799 6.09202ZM3.21799 17.908L4.10899 17.454L4.10899 17.454L3.21799 17.908ZM4.09202 18.782L4.54601 17.891H4.54601L4.09202 18.782ZM19.908 18.782L20.362 19.673V19.673L19.908 18.782ZM20.782 17.908L19.891 17.454L19.891 17.454L20.782 17.908ZM21 8.2H20V15.8H21H22V8.2H21ZM21 8.2H22C22 7.65645 22.0008 7.18864 21.9694 6.80497C21.9371 6.40963 21.8658 6.01641 21.673 5.63803L20.782 6.09202L19.891 6.54601C19.9162 6.59545 19.9539 6.69617 19.9761 6.96784C19.9992 7.25117 20 7.62345 20 8.2H21ZM20.782 6.09202L21.673 5.63803C21.3854 5.07354 20.9265 4.6146 20.362 4.32698L19.908 5.21799L19.454 6.10899C19.6422 6.20487 19.7951 6.35785 19.891 6.54601L20.782 6.09202ZM17.8 5V6C18.3766 6 18.7488 6.00078 19.0322 6.02393C19.3038 6.04612 19.4045 6.0838 19.454 6.10899L19.908 5.21799L20.362 4.32698C19.9836 4.13419 19.5904 4.06287 19.195 4.03057C18.8114 3.99922 18.3436 4 17.8 4V5ZM6.2 5V6H17.8V5V4H6.2V5ZM6.2 5V4C5.65645 4 5.18864 3.99922 4.80497 4.03057C4.40963 4.06287 4.01641 4.13419 3.63803 4.32698L4.09202 5.21799L4.54601 6.10899C4.59545 6.0838 4.69617 6.04612 4.96784 6.02393C5.25117 6.00078 5.62345 6 6.2 6V5ZM3.21799 6.09202L4.10899 6.54601C4.20487 6.35785 4.35785 6.20487 4.54601 6.10899L4.09202 5.21799L3.63803 4.32698C3.07354 4.6146 2.6146 5.07354 2.32698 5.63803L3.21799 6.09202ZM3 8.2H4C4 7.62345 4.00078 7.25117 4.02393 6.96784C4.04612 6.69617 4.0838 6.59545 4.10899 6.54601L3.21799 6.09202L2.32698 5.63803C2.13419 6.01641 2.06287 6.40963 2.03057 6.80497C1.99922 7.18864 2 7.65645 2 8.2H3ZM3 15.8H4V8.2H3H2V15.8H3ZM3 15.8H2C2 16.3435 1.99922 16.8114 2.03057 17.195C2.06287 17.5904 2.13419 17.9836 2.32698 18.362L3.21799 17.908L4.10899 17.454C4.0838 17.4045 4.04612 17.3038 4.02393 17.0322C4.00078 16.7488 4 16.3765 4 15.8H3ZM4.09202 18.782L4.54601 17.891C4.35785 17.7951 4.20487 17.6421 4.10899 17.454L3.21799 17.908L2.32698 18.362C2.6146 18.9265 3.07354 19.3854 3.63803 19.673L4.09202 18.782ZM6.2 19V18C5.62345 18 5.25117 17.9992 4.96784 17.9761C4.69617 17.9539 4.59545 17.9162 4.54601 17.891L4.09202 18.782L3.63803 19.673C4.01641 19.8658 4.40963 19.9371 4.80497 19.9694C5.18864 20.0008 5.65645 20 6.2 20V19ZM17.8 19V18H6.2V19V20H17.8V19ZM17.8 19V20C18.3436 20 18.8114 20.0008 19.195 19.9694C19.5904 19.9371 19.9836 19.8658 20.362 19.673L19.908 18.782L19.454 17.891C19.4045 17.9162 19.3038 17.9539 19.0322 17.9761C18.7488 17.9992 18.3766 18 17.8 18V19ZM20.782 17.908L19.891 17.454C19.7951 17.6421 19.6422 17.7951 19.454 17.891L19.908 18.782L20.362 19.673C20.9265 19.3854 21.3854 18.9265 21.673 18.362L20.782 17.908ZM21 15.8H20C20 16.3765 19.9992 16.7488 19.9761 17.0322C19.9539 17.3038 19.9162 17.4045 19.891 17.454L20.782 17.908L21.673 18.362C21.8658 17.9836 21.9371 17.5904 21.9694 17.195C22.0008 16.8114 22 16.3435 22 15.8H21Z" fill="white"/><path d="M21 5.63635L13.2665 11.9638C12.5297 12.5666 11.4703 12.5666 10.7335 11.9638L3 5.63635" fill="white"/>',
3118
+ bell: '<path d="M4.69376 9.44981C5.15435 5.76511 8.28661 3 12 3C15.7134 3 18.8456 5.76511 19.3062 9.44981L19.969 14.7519C20.1182 15.9456 19.1874 17 17.9844 17H6.01555C4.81256 17 3.88178 15.9456 4.031 14.7519L4.69376 9.44981Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 17C16 19.2091 14.2091 21 12 21C9.79086 21 8 19.2091 8 17" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3119
+ people: '<path d="M15.5 6.5C15.5 8.433 13.933 10 12 10C10.067 10 8.5 8.433 8.5 6.5C8.5 4.567 10.067 3 12 3C13.933 3 15.5 4.567 15.5 6.5Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.0011 13C8.60997 13 6.03711 15.0143 4.9836 17.8629C4.5748 18.9682 5.51944 20 6.69796 20H17.3043C18.4828 20 19.4274 18.9682 19.0186 17.8629C17.9651 15.0143 15.3923 13 12.0011 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3120
+ support: '<path d="M6 10H4.5C3.67157 10 3 10.6716 3 11.5V15C3 15.8284 3.67157 16.5 4.5 16.5H6V10Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 10H18.5V16.5H20C20.8284 16.5 21.5 15.8284 21.5 15V11.5C21.5 10.6716 20.8284 10 20 10Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M19.5 10V9.75C19.5 6.02208 16.2541 3 12.25 3C8.24594 3 5 6.02208 5 9.75V10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 19V19.5C12 20.3284 12.6716 21 13.5 21H15.25C17.7353 21 19.75 18.9853 19.75 16.5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3121
+ handshake: '<path d="M11.8305 5.68856C9.57907 3.43715 5.92881 3.43715 3.6774 5.68856C1.57785 7.78811 1.40103 11.1544 3.42019 13.3313C5.71921 15.8101 8.14783 18.1408 10.7345 20.3519C11.4619 20.9737 12.5313 20.9737 13.2587 20.3519C15.8453 18.1408 18.2739 15.8101 20.573 13.3313C22.5921 11.1544 22.4153 7.78811 20.3158 5.68856C18.0644 3.43715 14.4141 3.43715 12.1627 5.68856L8.8146 9.03664C8.22881 9.62243 8.22881 10.5722 8.8146 11.158C9.98617 12.3295 11.8857 12.3295 13.0572 11.158L13.7643 10.4509C14.1549 10.0603 14.788 10.0603 15.1786 10.4509L18.7141 13.9864" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3122
+ pill: '<path d="M17.4541 10.0962C18.6109 8.31497 18.4083 5.90826 16.8462 4.34619C15.0513 2.55127 12.1411 2.55127 10.3462 4.34619L4.34619 10.3462C2.55127 12.1411 2.55127 15.0513 4.34619 16.8462C5.90826 18.4083 8.31497 18.6109 10.0962 17.4541" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M7.5 7.5L11 11" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M16.5 20.5L14.5 10.5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="15.5" cy="15.5" r="5.5" stroke="white" stroke-width="2"/>',
3123
+ "heart-beat": '<path d="M16 12H14.5L13 14L10.5 10L9.25 12H8M12 5.76835C18.1619 -0.481837 28.7252 11.1257 12 20.5C-4.72523 11.1257 5.83803 -0.481838 12 5.76835Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
3124
+ book: '<path d="M9 6C8.44772 6 8 6.44772 8 7C8 7.55228 8.44772 8 9 8V6ZM15 8C15.5523 8 16 7.55228 16 7C16 6.44772 15.5523 6 15 6V8ZM9 10C8.44772 10 8 10.4477 8 11C8 11.5523 8.44772 12 9 12V10ZM12 12C12.5523 12 13 11.5523 13 11C13 10.4477 12.5523 10 12 10V12ZM7 4H17V2H7V4ZM18 5V19H20V5H18ZM17 20H7V22H17V20ZM6 19V5H4V19H6ZM7 20C6.44771 20 6 19.5523 6 19H4C4 20.6569 5.34315 22 7 22V20ZM18 19C18 19.5523 17.5523 20 17 20V22C18.6569 22 20 20.6569 20 19H18ZM17 4C17.5523 4 18 4.44772 18 5H20C20 3.34315 18.6569 2 17 2V4ZM7 2C5.34315 2 4 3.34315 4 5H6C6 4.44772 6.44772 4 7 4V2ZM18 12V15H20V12H18ZM17 16H7V18H17V16ZM7 22H10V20H7V22ZM7 16C5.34315 16 4 17.3431 4 19H6C6 18.4477 6.44772 18 7 18V16ZM18 15C18 15.5523 17.5523 16 17 16V18C18.6569 18 20 16.6569 20 15H18ZM9 8H15V6H9V8ZM9 12H12V10H9V12Z" fill="white"/>',
3125
+ graduate: '<path d="M23 9L12 14L1 9L12 4L23 9ZM23 9V16M5 11V15.3025C5 16.0398 5.40565 16.7173 6.05556 17.0655L11.0556 19.744C11.6455 20.0601 12.3545 20.0601 12.9444 19.744L17.9444 17.0655C18.5944 16.7173 19 16.0398 19 15.3025V11" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
3126
+ };
3127
+ function AgentPresetIcon({ presetId }) {
3128
+ const svgContent = PRESET_SVG[presetId] ?? PRESET_SVG.sparkle;
3129
+ return (
3130
+ // biome-ignore lint: trusted static SVG content from our icon library
3131
+ /* @__PURE__ */ jsx12(
3132
+ "svg",
3133
+ {
3134
+ viewBox: "0 0 24 24",
3135
+ fill: "none",
3136
+ xmlns: "http://www.w3.org/2000/svg",
3137
+ style: { width: "55%", height: "55%" },
3138
+ dangerouslySetInnerHTML: { __html: svgContent ?? "" }
3139
+ }
3140
+ )
3141
+ );
3142
+ }
3143
+ function DefaultWidget({ initialOpen = false } = {}) {
3144
+ const { isReady, website, agentConfig } = useWidget();
3145
+ useI18n();
3146
+ const { client, store, hasBootstrapError, wsDisconnected, retryBootstrap } = useWidgetContext();
3147
+ const { conversations, create, isCreating, setActiveConversation } = useConversations();
3148
+ const activeId = useWidgetStore((s) => s.activeConversationId);
3149
+ const { totalUnread } = useUnread();
3150
+ const contentTabs = useWidgetStore((s) => s.agentConfig?.contentTabs) ?? EMPTY_CONTENT_TABS;
3151
+ const visitorContact = useWidgetStore((s) => s.visitor?.contact);
3152
+ if (process.env.NODE_ENV !== "production") {
3153
+ console.debug("[Widget]", {
3154
+ reqEmail: agentConfig?.requireVisitorEmail,
3155
+ contact: visitorContact?.email ?? null,
3156
+ activeId
3157
+ });
3158
+ }
3159
+ const [isOpen, setIsOpen] = useState12(initialOpen);
3160
+ const [isClosing, setIsClosing] = useState12(false);
3161
+ const [activeTab, setActiveTab] = useState12("home");
3162
+ const [subView, setSubView] = useState12(null);
3163
+ const [newText, setNewText] = useState12("");
3164
+ const [contentDetailSlug, setContentDetailSlug] = useState12(null);
3165
+ const [contentCategoryName, setContentCategoryName] = useState12(null);
3166
+ const closeTimerRef = useRef13(void 0);
3167
+ const welcomeMsgRaw = agentConfig?.welcomeMessage;
3168
+ const welcomeIsFactoryDefault = !welcomeMsgRaw || welcomeMsgRaw === DEFAULT_EN_WELCOME;
3169
+ const welcomeMessage = welcomeMsgRaw?.startsWith("widget.") ? t(welcomeMsgRaw) : welcomeIsFactoryDefault ? t("widget.welcome_message") : welcomeMsgRaw;
3170
+ const openConversation = useCallback12(
3171
+ (id) => {
3172
+ setActiveConversation(id);
3173
+ setSubView("conversation");
3174
+ },
3175
+ [setActiveConversation]
3176
+ );
3177
+ const openNewConversation = useCallback12(() => {
3178
+ const agentId = agentConfig?.id;
3179
+ if (!agentId) {
3180
+ setSubView("new");
3181
+ return;
3182
+ }
3183
+ const tempId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3184
+ const seedWelcome = {
3185
+ id: `seed-welcome-${tempId}`,
3186
+ conversationId: tempId,
3187
+ organizationId: "",
3188
+ type: "message",
3189
+ visibility: "public",
3190
+ text: welcomeMessage,
3191
+ parts: null,
3192
+ userId: null,
3193
+ visitorId: null,
3194
+ aiAgentId: agentId,
3195
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3196
+ editedAt: null,
3197
+ deletedAt: null
3198
+ };
3199
+ store.getState().setMessages(tempId, [seedWelcome]);
3200
+ setActiveConversation(tempId);
3201
+ setSubView("conversation");
3202
+ }, [agentConfig?.id, store, setActiveConversation, welcomeMessage]);
3203
+ const goBack = useCallback12(() => {
3204
+ if (subView === "content-detail") {
3205
+ const tabId = activeTab.startsWith("content:") ? activeTab.slice(8) : null;
3206
+ const activeContentTab = tabId ? contentTabs.find((t2) => t2.id === tabId) : null;
3207
+ if (activeContentTab?.tabType === "grouped" && contentCategoryName) {
3208
+ setSubView("content-category");
3209
+ setContentDetailSlug(null);
3210
+ return;
3211
+ }
3212
+ setSubView(null);
3213
+ setContentDetailSlug(null);
3214
+ return;
3215
+ }
3216
+ if (subView === "content-category") {
3217
+ setSubView(null);
3218
+ setContentCategoryName(null);
3219
+ return;
3220
+ }
3221
+ const leavingId = store.getState().activeConversationId;
3222
+ if (leavingId && !leavingId.startsWith("temp-")) {
3223
+ store.getState().clearUnread(leavingId);
3224
+ client.widget.markSeen.mutate({ conversationId: leavingId }).catch(() => {
3225
+ });
3226
+ }
3227
+ setActiveConversation(null);
3228
+ setSubView(null);
3229
+ }, [setActiveConversation, subView, activeTab, contentTabs, contentCategoryName, client, store]);
3230
+ const switchTab = useCallback12(
3231
+ (tab) => {
3232
+ tabStateRef.current[activeTab] = { subView, activeId };
3233
+ const saved = tabStateRef.current[tab];
3234
+ setActiveTab(tab);
3235
+ setSubView(saved?.subView ?? null);
3236
+ if (saved?.activeId != null) {
3237
+ setActiveConversation(saved.activeId);
3238
+ } else if (!saved) {
3239
+ setActiveConversation(null);
3240
+ }
3241
+ },
3242
+ [activeTab, subView, activeId, setActiveConversation]
3243
+ );
3244
+ const storeSetOpen = useWidgetStore((s) => s.setOpen);
3245
+ const handleBubbleClick = useCallback12(() => {
3246
+ if (isOpen) {
3247
+ const leavingId = store.getState().activeConversationId;
3248
+ if (leavingId && !leavingId.startsWith("temp-")) {
3249
+ store.getState().clearUnread(leavingId);
3250
+ client.widget.markSeen.mutate({ conversationId: leavingId }).catch(() => {
3251
+ });
3252
+ }
3253
+ setIsClosing(true);
3254
+ closeTimerRef.current = setTimeout(() => {
3255
+ setIsOpen(false);
3256
+ setIsClosing(false);
3257
+ storeSetOpen(false);
3258
+ }, 200);
3259
+ } else {
3260
+ setIsOpen(true);
3261
+ setIsClosing(false);
3262
+ setSubView(null);
3263
+ setActiveTab("home");
3264
+ storeSetOpen(true);
3265
+ }
3266
+ }, [isOpen, storeSetOpen, client, store]);
3267
+ const handleClose = useCallback12(() => {
3268
+ const leavingId = store.getState().activeConversationId;
3269
+ if (leavingId && !leavingId.startsWith("temp-")) {
3270
+ store.getState().clearUnread(leavingId);
3271
+ client.widget.markSeen.mutate({ conversationId: leavingId }).catch(() => {
3272
+ });
3273
+ }
3274
+ setIsClosing(true);
3275
+ closeTimerRef.current = setTimeout(() => {
3276
+ setIsOpen(false);
3277
+ setIsClosing(false);
3278
+ storeSetOpen(false);
3279
+ }, 200);
3280
+ }, [storeSetOpen, client, store]);
3281
+ const handleNewConversation = useCallback12(async () => {
3282
+ const trimmed = newText.trim();
3283
+ if (!trimmed || isCreating) return;
3284
+ const conv = await create(trimmed);
3285
+ if (conv) {
3286
+ setNewText("");
3287
+ setSubView("conversation");
3288
+ }
3289
+ }, [newText, isCreating, create]);
3290
+ const windowRef = useRef13(null);
3291
+ const bubbleRef = useRef13(null);
3292
+ const tabStateRef = useRef13({});
3293
+ const touchStartY = useRef13(0);
3294
+ const touchDeltaY = useRef13(0);
3295
+ const handleTouchStart = useCallback12((e) => {
3296
+ touchStartY.current = e.touches[0]?.clientY ?? 0;
3297
+ touchDeltaY.current = 0;
3298
+ }, []);
3299
+ const handleTouchMove = useCallback12((e) => {
3300
+ const delta = (e.touches[0]?.clientY ?? 0) - touchStartY.current;
3301
+ touchDeltaY.current = delta;
3302
+ if (delta > 0 && windowRef.current) {
3303
+ windowRef.current.style.transform = `translateY(${Math.min(delta, 200)}px)`;
3304
+ windowRef.current.style.opacity = `${Math.max(1 - delta / 400, 0.5)}`;
3305
+ }
3306
+ }, []);
3307
+ const handleTouchEnd = useCallback12(() => {
3308
+ if (windowRef.current) {
3309
+ windowRef.current.style.transform = "";
3310
+ windowRef.current.style.opacity = "";
3311
+ }
3312
+ if (touchDeltaY.current > 80) {
3313
+ handleClose();
3314
+ }
3315
+ }, [handleClose]);
3316
+ useEffect12(() => {
3317
+ if (!isOpen || !windowRef.current) return;
3318
+ const vv = window.visualViewport;
3319
+ if (!vv) return;
3320
+ const win = windowRef.current;
3321
+ const applyHeight = () => {
3322
+ win.style.setProperty("--hw-visible-height", `${vv.height}px`);
3323
+ };
3324
+ applyHeight();
3325
+ vv.addEventListener("resize", applyHeight);
3326
+ vv.addEventListener("scroll", applyHeight);
3327
+ return () => {
3328
+ vv.removeEventListener("resize", applyHeight);
3329
+ vv.removeEventListener("scroll", applyHeight);
3330
+ win.style.removeProperty("--hw-visible-height");
3331
+ };
3332
+ }, [isOpen]);
3333
+ useEffect12(() => {
3334
+ if (!isOpen || !windowRef.current) return;
3335
+ const win = windowRef.current;
3336
+ const handleKeyDown = (e) => {
3337
+ if (e.key === "Escape") {
3338
+ if (subView) {
3339
+ goBack();
3340
+ } else {
3341
+ handleClose();
3342
+ setTimeout(() => bubbleRef.current?.focus(), 220);
3343
+ }
3344
+ }
3345
+ };
3346
+ win.addEventListener("keydown", handleKeyDown);
3347
+ return () => win.removeEventListener("keydown", handleKeyDown);
3348
+ }, [isOpen, subView, handleClose, goBack]);
3349
+ useEffect12(() => {
3350
+ if (!isOpen || !windowRef.current) return;
3351
+ const win = windowRef.current;
3352
+ const getFocusable = () => win.querySelectorAll(
3353
+ 'button:not([disabled]), [href], input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
3354
+ );
3355
+ const getActive = () => {
3356
+ const root = win.getRootNode();
3357
+ if (root instanceof ShadowRoot) return root.activeElement;
3358
+ return document.activeElement;
3359
+ };
3360
+ const handleTab = (e) => {
3361
+ if (e.key !== "Tab") return;
3362
+ const focusable2 = getFocusable();
3363
+ if (focusable2.length === 0) return;
3364
+ const first = focusable2[0];
3365
+ const last = focusable2[focusable2.length - 1];
3366
+ const active = getActive();
3367
+ if (e.shiftKey && active === first) {
3368
+ e.preventDefault();
3369
+ last?.focus();
3370
+ } else if (!e.shiftKey && active === last) {
3371
+ e.preventDefault();
3372
+ first?.focus();
3373
+ }
3374
+ };
3375
+ win.addEventListener("keydown", handleTab);
3376
+ const focusable = getFocusable();
3377
+ if (focusable.length > 0) focusable[0]?.focus();
3378
+ return () => win.removeEventListener("keydown", handleTab);
3379
+ }, [isOpen]);
3380
+ const showTabBar = subView === null;
3381
+ const companyName = agentConfig?.name ?? website?.name ?? "Support";
3382
+ const subtitleMsg = agentConfig?.subtitleText;
3383
+ const subtitleIsFactoryDefault = !subtitleMsg || subtitleMsg === DEFAULT_EN_SUBTITLE;
3384
+ const subtitleText = subtitleMsg?.startsWith("widget.") ? t(subtitleMsg) : subtitleIsFactoryDefault ? t("widget.subtitle_text") : subtitleMsg;
3385
+ const brandColor = agentConfig?.brandColor ?? "#0a0a0a";
3386
+ const widgetPosition = agentConfig?.widgetPosition ?? "bottom-right";
3387
+ const launcherStyle = agentConfig?.launcherStyle ?? "icon";
3388
+ const bubbleStyle = agentConfig?.bubbleStyle ?? "rounded";
3389
+ return /* @__PURE__ */ jsxs7(
3390
+ "div",
3391
+ {
3392
+ className: "hw-widget",
3393
+ style: {
3394
+ "--hw-primary": brandColor,
3395
+ "--hw-position-right": widgetPosition === "bottom-right" ? "20px" : "auto",
3396
+ "--hw-position-left": widgetPosition === "bottom-left" ? "20px" : "auto",
3397
+ "--hw-window-right": widgetPosition === "bottom-right" ? "0" : "auto",
3398
+ "--hw-window-left": widgetPosition === "bottom-left" ? "0" : "auto"
3399
+ },
3400
+ children: [
3401
+ isOpen && !isReady && !hasBootstrapError && /* @__PURE__ */ jsx12("div", { className: `hw-window ${isClosing ? "hw-window--closing" : ""}`, children: /* @__PURE__ */ jsxs7("div", { className: "hw-skeleton-loading", children: [
3402
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 120 } }),
3403
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 200 } }),
3404
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 160 } })
3405
+ ] }) }),
3406
+ isOpen && hasBootstrapError && /* @__PURE__ */ jsx12("div", { className: `hw-window ${isClosing ? "hw-window--closing" : ""}`, children: /* @__PURE__ */ jsxs7("div", { className: "hw-bootstrap-error", children: [
3407
+ /* @__PURE__ */ jsx12("div", { className: "hw-bootstrap-error-text", children: t("widget.error_desc") }),
3408
+ /* @__PURE__ */ jsx12("button", { type: "button", className: "hw-bootstrap-error-retry", onClick: retryBootstrap, children: t("widget.retry") })
3409
+ ] }) }),
3410
+ isOpen && isReady && /* @__PURE__ */ jsxs7(
3411
+ "div",
3412
+ {
3413
+ ref: windowRef,
3414
+ className: `hw-window ${isClosing ? "hw-window--closing" : ""}`,
3415
+ role: "dialog",
3416
+ "aria-label": t("widget.aria_chat"),
3417
+ "aria-modal": "true",
3418
+ onTouchStart: handleTouchStart,
3419
+ onTouchMove: handleTouchMove,
3420
+ onTouchEnd: handleTouchEnd,
3421
+ children: [
3422
+ /* @__PURE__ */ jsx12("div", { className: "hw-drag-handle", children: /* @__PURE__ */ jsx12("div", { className: "hw-drag-handle-pill" }) }),
3423
+ wsDisconnected && /* @__PURE__ */ jsxs7("div", { className: "hw-disconnect", children: [
3424
+ /* @__PURE__ */ jsx12("div", { className: "hw-disconnect-dot" }),
3425
+ t("widget.reconnecting")
3426
+ ] }),
3427
+ (subView === "content-detail" || subView === "content-category") && (() => {
3428
+ const tabId = activeTab.replace("content:", "");
3429
+ const contentTab = contentTabs.find((t2) => t2.id === tabId);
3430
+ const headerTitle = subView === "content-category" && contentCategoryName ? contentCategoryName : contentTab?.label ? resolveLabel2(contentTab.label) : t("widget.content");
3431
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-header hw-header--centered", children: [
3432
+ /* @__PURE__ */ jsx12(
3433
+ "button",
3434
+ {
3435
+ type: "button",
3436
+ className: "hw-header-back",
3437
+ onClick: goBack,
3438
+ "aria-label": t("widget.aria_back"),
3439
+ children: /* @__PURE__ */ jsx12(
3440
+ "svg",
3441
+ {
3442
+ viewBox: "0 0 24 24",
3443
+ fill: "none",
3444
+ stroke: "currentColor",
3445
+ strokeWidth: 2,
3446
+ strokeLinecap: "round",
3447
+ strokeLinejoin: "round",
3448
+ children: /* @__PURE__ */ jsx12("path", { d: "M15 18l-6-6 6-6" })
3449
+ }
3450
+ )
3451
+ }
3452
+ ),
3453
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-title", children: headerTitle }),
3454
+ /* @__PURE__ */ jsx12(
3455
+ "button",
3456
+ {
3457
+ type: "button",
3458
+ className: "hw-header-close",
3459
+ onClick: handleClose,
3460
+ "aria-label": t("widget.aria_close"),
3461
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 20 })
3462
+ }
3463
+ )
3464
+ ] });
3465
+ })(),
3466
+ subView !== null && subView !== "content-detail" && subView !== "content-category" && /* @__PURE__ */ jsxs7("div", { className: "hw-header hw-header--sub", children: [
3467
+ /* @__PURE__ */ jsx12(
3468
+ "button",
3469
+ {
3470
+ type: "button",
3471
+ className: "hw-header-back",
3472
+ onClick: goBack,
3473
+ "aria-label": t("widget.aria_back"),
3474
+ children: /* @__PURE__ */ jsx12(
3475
+ "svg",
3476
+ {
3477
+ viewBox: "0 0 24 24",
3478
+ fill: "none",
3479
+ stroke: "currentColor",
3480
+ strokeWidth: 2,
3481
+ strokeLinecap: "round",
3482
+ strokeLinejoin: "round",
3483
+ children: /* @__PURE__ */ jsx12("path", { d: "M15 18l-6-6 6-6" })
3484
+ }
3485
+ )
3486
+ }
3487
+ ),
3488
+ /* @__PURE__ */ jsxs7("div", { className: "hw-header-identity", children: [
3489
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-bot-icon", children: /* @__PURE__ */ jsx12(WidgetLogo, {}) }),
3490
+ /* @__PURE__ */ jsxs7("div", { className: "hw-header-info", children: [
3491
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-name", children: companyName }),
3492
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-status", children: subtitleText })
3493
+ ] })
3494
+ ] }),
3495
+ /* @__PURE__ */ jsx12(
3496
+ "button",
3497
+ {
3498
+ type: "button",
3499
+ className: "hw-header-close",
3500
+ onClick: handleClose,
3501
+ "aria-label": t("widget.aria_close"),
3502
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 20 })
3503
+ }
3504
+ )
3505
+ ] }),
3506
+ subView === null && activeTab === "home" && /* @__PURE__ */ jsxs7(Fragment2, { children: [
3507
+ /* @__PURE__ */ jsxs7("div", { className: "hw-hero", children: [
3508
+ /* @__PURE__ */ jsxs7("div", { className: "hw-hero-top", children: [
3509
+ /* @__PURE__ */ jsx12("div", { className: "hw-hero-bot-icon", children: /* @__PURE__ */ jsx12(WidgetLogo, {}) }),
3510
+ /* @__PURE__ */ jsx12(
3511
+ "button",
3512
+ {
3513
+ type: "button",
3514
+ className: "hw-hero-close",
3515
+ onClick: handleClose,
3516
+ "aria-label": t("widget.aria_close"),
3517
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 20 })
3518
+ }
3519
+ )
3520
+ ] }),
3521
+ /* @__PURE__ */ jsx12("div", { className: "hw-hero-greeting", children: welcomeMessage.split("\n").map((line, i) => /* @__PURE__ */ jsxs7(
3522
+ "span",
3523
+ {
3524
+ className: i === 0 ? "hw-hero-greeting-name" : "hw-hero-greeting-question",
3525
+ children: [
3526
+ i > 0 && /* @__PURE__ */ jsx12("br", {}),
3527
+ line
3528
+ ]
3529
+ },
3530
+ i
3531
+ )) }),
3532
+ /* @__PURE__ */ jsx12("div", { className: "hw-hero-subtitle", children: subtitleText })
3533
+ ] }),
3534
+ /* @__PURE__ */ jsx12("div", { className: "hw-sheet", children: /* @__PURE__ */ jsxs7("div", { className: "hw-sheet-inner", children: [
3535
+ /* @__PURE__ */ jsxs7("button", { type: "button", className: "hw-card", onClick: openNewConversation, children: [
3536
+ /* @__PURE__ */ jsxs7("div", { className: "hw-card-body", children: [
3537
+ /* @__PURE__ */ jsx12("div", { className: "hw-card-title", children: t("widget.send_us_message") }),
3538
+ /* @__PURE__ */ jsx12("div", { className: "hw-card-meta", children: t("widget.reply_time") })
3539
+ ] }),
3540
+ /* @__PURE__ */ jsx12("div", { className: "hw-card-arrow", children: /* @__PURE__ */ jsx12(IconPaperPlaneTopRight, {}) })
3541
+ ] }),
3542
+ /* @__PURE__ */ jsx12(
3543
+ HomeView,
3544
+ {
3545
+ conversations,
3546
+ onSelect: openConversation,
3547
+ onMessagesClick: () => switchTab("messages")
3548
+ }
3549
+ )
3550
+ ] }) })
3551
+ ] }),
3552
+ subView === null && activeTab === "messages" && /* @__PURE__ */ jsxs7("div", { className: "hw-header hw-header--centered", children: [
3553
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-spacer" }),
3554
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-title", children: t("widget.messages") }),
3555
+ /* @__PURE__ */ jsx12(
3556
+ "button",
3557
+ {
3558
+ type: "button",
3559
+ className: "hw-header-close",
3560
+ onClick: handleClose,
3561
+ "aria-label": t("widget.aria_close"),
3562
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 20 })
3563
+ }
3564
+ )
3565
+ ] }),
3566
+ subView === null && activeTab.startsWith("content:") && (() => {
3567
+ const tabId = activeTab.replace("content:", "");
3568
+ const contentTab = contentTabs.find((t2) => t2.id === tabId);
3569
+ if (!contentTab) return null;
3570
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-header hw-header--centered", children: [
3571
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-spacer" }),
3572
+ /* @__PURE__ */ jsx12("div", { className: "hw-header-title", children: resolveLabel2(contentTab.label) }),
3573
+ /* @__PURE__ */ jsx12(
3574
+ "button",
3575
+ {
3576
+ type: "button",
3577
+ className: "hw-header-close",
3578
+ onClick: handleClose,
3579
+ "aria-label": t("widget.aria_close"),
3580
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 20 })
3581
+ }
3582
+ )
3583
+ ] });
3584
+ })(),
3585
+ (subView !== null || activeTab !== "home") && /* @__PURE__ */ jsxs7("div", { className: "hw-content", children: [
3586
+ subView === null && activeTab === "messages" && /* @__PURE__ */ jsx12(
3587
+ MessagesView,
3588
+ {
3589
+ conversations,
3590
+ onSelect: openConversation,
3591
+ onNewClick: openNewConversation
3592
+ }
3593
+ ),
3594
+ subView === "new" && /* @__PURE__ */ jsx12(
3595
+ NewConversationView,
3596
+ {
3597
+ text: newText,
3598
+ onTextChange: setNewText,
3599
+ onSubmit: handleNewConversation,
3600
+ isSubmitting: isCreating,
3601
+ welcomeMessage
3602
+ }
3603
+ ),
3604
+ subView === "conversation" && activeId && /* @__PURE__ */ jsx12(ConversationView, { conversationId: activeId }),
3605
+ activeTab.startsWith("content:") && (() => {
3606
+ const tabId = activeTab.replace("content:", "");
3607
+ const contentTab = contentTabs.find((t2) => t2.id === tabId);
3608
+ if (!contentTab) return null;
3609
+ return /* @__PURE__ */ jsx12(
3610
+ ContentTabView,
3611
+ {
3612
+ tab: contentTab,
3613
+ showDetail: subView === "content-detail",
3614
+ detailSlug: contentDetailSlug,
3615
+ subView,
3616
+ onSelectEntry: (slug) => {
3617
+ setContentDetailSlug(slug);
3618
+ setSubView("content-detail");
3619
+ },
3620
+ onSelectCategory: (cat) => {
3621
+ setContentCategoryName(cat.title);
3622
+ setSubView("content-category");
3623
+ }
3624
+ }
3625
+ );
3626
+ })()
3627
+ ] }),
3628
+ showTabBar && /* @__PURE__ */ jsx12(
3629
+ TabBar,
3630
+ {
3631
+ activeTab,
3632
+ onTabChange: switchTab,
3633
+ unreadCount: totalUnread,
3634
+ contentTabs,
3635
+ showMessagesTab: agentConfig?.showMessagesTab ?? true
3636
+ }
3637
+ ),
3638
+ /* @__PURE__ */ jsxs7("div", { className: "hw-powered", children: [
3639
+ t("widget.powered_by"),
3640
+ " ",
3641
+ /* @__PURE__ */ jsx12("a", { href: "https://helpway.ai", target: "_blank", rel: "noopener noreferrer", children: "Helpway" })
3642
+ ] })
3643
+ ]
3644
+ }
3645
+ ),
3646
+ !isOpen && /* @__PURE__ */ jsx12(MessagePreview, { onOpen: handleBubbleClick }),
3647
+ /* @__PURE__ */ jsx12(WidgetDebugOverlay, {}),
3648
+ /* @__PURE__ */ jsxs7(
3649
+ "button",
3650
+ {
3651
+ ref: bubbleRef,
3652
+ type: "button",
3653
+ className: [
3654
+ "hw-bubble",
3655
+ `hw-bubble--${bubbleStyle}`,
3656
+ launcherStyle !== "icon" ? `hw-bubble--launcher-${launcherStyle}` : "",
3657
+ isOpen ? "hw-bubble--open" : "",
3658
+ website?.logoUrl ? "hw-bubble--avatar" : ""
3659
+ ].filter(Boolean).join(" "),
3660
+ onClick: handleBubbleClick,
3661
+ "aria-label": isOpen ? t("widget.aria_close_support") : t("widget.aria_open"),
3662
+ "aria-expanded": isOpen,
3663
+ "aria-haspopup": "dialog",
3664
+ children: [
3665
+ /* @__PURE__ */ jsx12(
3666
+ "span",
3667
+ {
3668
+ className: `hw-bubble-icon hw-bubble-icon--bot ${isOpen ? "hw-bubble-icon--hidden" : ""}`,
3669
+ children: /* @__PURE__ */ jsx12(WidgetLogo, {})
3670
+ }
3671
+ ),
3672
+ /* @__PURE__ */ jsx12(
3673
+ "span",
3674
+ {
3675
+ className: `hw-bubble-icon hw-bubble-icon--close ${!isOpen ? "hw-bubble-icon--hidden" : ""}`,
3676
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 24 })
3677
+ }
3678
+ ),
3679
+ launcherStyle !== "icon" && !isOpen && /* @__PURE__ */ jsx12("span", { className: "hw-bubble-label", children: launcherStyle === "help" ? t("widget.help") : companyName }),
3680
+ !isOpen && totalUnread > 0 && /* @__PURE__ */ jsx12("div", { className: "hw-bubble-badge", children: totalUnread })
3681
+ ]
3682
+ }
3683
+ )
3684
+ ]
3685
+ }
3686
+ );
3687
+ }
3688
+ function TabBar({
3689
+ activeTab,
3690
+ onTabChange,
3691
+ unreadCount,
3692
+ contentTabs,
3693
+ showMessagesTab = true
3694
+ }) {
3695
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-tabs", role: "tablist", children: [
3696
+ /* @__PURE__ */ jsxs7(
3697
+ "button",
3698
+ {
3699
+ type: "button",
3700
+ className: `hw-tabs-item ${activeTab === "home" ? "hw-tabs-item--active" : ""}`,
3701
+ onClick: () => onTabChange("home"),
3702
+ role: "tab",
3703
+ "aria-selected": activeTab === "home",
3704
+ children: [
3705
+ /* @__PURE__ */ jsx12(IconHome, {}),
3706
+ /* @__PURE__ */ jsx12("span", { children: t("widget.home") })
3707
+ ]
3708
+ }
3709
+ ),
3710
+ showMessagesTab && /* @__PURE__ */ jsxs7(
3711
+ "button",
3712
+ {
3713
+ type: "button",
3714
+ className: `hw-tabs-item ${activeTab === "messages" ? "hw-tabs-item--active" : ""}`,
3715
+ onClick: () => onTabChange("messages"),
3716
+ role: "tab",
3717
+ "aria-selected": activeTab === "messages",
3718
+ children: [
3719
+ /* @__PURE__ */ jsxs7("span", { className: "hw-tabs-icon-wrap", children: [
3720
+ /* @__PURE__ */ jsx12(IconBubbleDots, {}),
3721
+ unreadCount > 0 && /* @__PURE__ */ jsx12("span", { className: "hw-tabs-badge", children: unreadCount > 99 ? "99+" : unreadCount })
3722
+ ] }),
3723
+ /* @__PURE__ */ jsx12("span", { children: t("widget.messages") })
3724
+ ]
3725
+ }
3726
+ ),
3727
+ contentTabs.map((tab) => /* @__PURE__ */ jsxs7(
3728
+ "button",
3729
+ {
3730
+ type: "button",
3731
+ className: `hw-tabs-item ${activeTab === `content:${tab.id}` ? "hw-tabs-item--active" : ""}`,
3732
+ onClick: () => onTabChange(`content:${tab.id}`),
3733
+ role: "tab",
3734
+ "aria-selected": activeTab === `content:${tab.id}`,
3735
+ children: [
3736
+ /* @__PURE__ */ jsx12(ContentTabIconComponent, { icon: tab.icon }),
3737
+ /* @__PURE__ */ jsx12("span", { children: resolveLabel2(tab.label) })
3738
+ ]
3739
+ },
3740
+ tab.id
3741
+ ))
3742
+ ] });
3743
+ }
3744
+ function HomeView({
3745
+ conversations,
3746
+ onSelect,
3747
+ onMessagesClick
3748
+ }) {
3749
+ const recent = conversations.slice(0, 3);
3750
+ if (recent.length === 0) return null;
3751
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-recent-card", children: [
3752
+ /* @__PURE__ */ jsx12("div", { className: "hw-recent-header", children: t("widget.recent_conversations") }),
3753
+ recent.map((conv, i) => /* @__PURE__ */ jsx12(
3754
+ ConversationRow,
3755
+ {
3756
+ conversation: conv,
3757
+ onSelect,
3758
+ isLast: i === recent.length - 1
3759
+ },
3760
+ conv.id
3761
+ )),
3762
+ conversations.length > 3 && /* @__PURE__ */ jsx12("button", { type: "button", className: "hw-recent-see-all", onClick: onMessagesClick, children: t("widget.see_all") })
3763
+ ] });
3764
+ }
3765
+ function MessagesView({
3766
+ conversations,
3767
+ onSelect,
3768
+ onNewClick
3769
+ }) {
3770
+ if (conversations.length === 0) {
3771
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-empty-state", children: [
3772
+ /* @__PURE__ */ jsx12("div", { className: "hw-empty-icon-wrap", children: /* @__PURE__ */ jsx12(IconBubbleDots, {}) }),
3773
+ /* @__PURE__ */ jsx12("div", { className: "hw-empty-title", children: t("widget.no_messages") }),
3774
+ /* @__PURE__ */ jsx12("div", { className: "hw-empty-desc", children: t("widget.no_messages_desc") }),
3775
+ /* @__PURE__ */ jsx12("button", { type: "button", className: "hw-empty-btn", onClick: onNewClick, children: t("widget.start_conversation") })
3776
+ ] });
3777
+ }
3778
+ return /* @__PURE__ */ jsx12("div", { className: "hw-msgs-tab", children: conversations.map((conv) => /* @__PURE__ */ jsx12(ConversationCard, { conversation: conv, onSelect }, conv.id)) });
3779
+ }
3780
+ function ConvCardBody({ conv }) {
3781
+ const agentName = useWidgetStore((s) => s.agentConfig?.name) ?? "Support";
3782
+ const preview = conv.lastMessageText ?? t("widget.no_preview");
3783
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
3784
+ /* @__PURE__ */ jsx12("div", { className: "hw-conv-card-avatar", children: /* @__PURE__ */ jsx12(WidgetLogo, {}) }),
3785
+ /* @__PURE__ */ jsxs7("div", { className: "hw-conv-card-content", children: [
3786
+ /* @__PURE__ */ jsxs7("div", { className: "hw-conv-card-top", children: [
3787
+ /* @__PURE__ */ jsx12("span", { className: "hw-conv-card-name", children: agentName }),
3788
+ conv.lastMessageAt && /* @__PURE__ */ jsx12("span", { className: "hw-conv-card-time", children: formatRelativeTime(conv.lastMessageAt) })
3789
+ ] }),
3790
+ /* @__PURE__ */ jsx12("div", { className: "hw-conv-card-preview", children: preview })
3791
+ ] })
3792
+ ] });
3793
+ }
3794
+ function ConversationCard({
3795
+ conversation: conv,
3796
+ onSelect
3797
+ }) {
3798
+ const hasUnread = conv.unreadCount > 0;
3799
+ return /* @__PURE__ */ jsxs7(
3800
+ "button",
3801
+ {
3802
+ type: "button",
3803
+ className: `hw-conv-card ${hasUnread ? "hw-conv-card--unread" : ""}`,
3804
+ onClick: () => onSelect(conv.id),
3805
+ children: [
3806
+ /* @__PURE__ */ jsx12("div", { className: "hw-conv-card-row", children: /* @__PURE__ */ jsx12(ConvCardBody, { conv }) }),
3807
+ hasUnread && /* @__PURE__ */ jsx12("div", { className: "hw-conv-card-badge", children: conv.unreadCount })
3808
+ ]
3809
+ }
3810
+ );
3811
+ }
3812
+ function ConversationRow({
3813
+ conversation: conv,
3814
+ onSelect,
3815
+ isLast
3816
+ }) {
3817
+ const hasUnread = conv.unreadCount > 0;
3818
+ return /* @__PURE__ */ jsxs7(
3819
+ "button",
3820
+ {
3821
+ type: "button",
3822
+ className: `hw-recent-row ${hasUnread ? "hw-recent-row--unread" : ""} ${isLast ? "hw-recent-row--last" : ""}`,
3823
+ onClick: () => onSelect(conv.id),
3824
+ children: [
3825
+ /* @__PURE__ */ jsx12(ConvCardBody, { conv }),
3826
+ hasUnread && /* @__PURE__ */ jsx12("div", { className: "hw-recent-row-badge", children: conv.unreadCount })
3827
+ ]
3828
+ }
3829
+ );
3830
+ }
3831
+ function InlineEmailPrompt() {
3832
+ const { client, store } = useWidgetContext();
3833
+ const updateContact = useWidgetStore((s) => s.updateVisitorContact);
3834
+ const [email, setEmail] = useState12("");
3835
+ const [isSubmitting, setIsSubmitting] = useState12(false);
3836
+ const [isSuccess, setIsSuccess] = useState12(false);
3837
+ const [hasError, setHasError] = useState12(false);
3838
+ const handleSubmit = async () => {
3839
+ const trimmed = email.trim();
3840
+ if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
3841
+ setHasError(true);
3842
+ return;
3843
+ }
3844
+ setHasError(false);
3845
+ const previousContact = store.getState().visitor?.contact ?? null;
3846
+ const optimisticContactId = previousContact?.id ?? `optimistic-contact-${Date.now()}`;
3847
+ updateContact({
3848
+ id: optimisticContactId,
3849
+ name: previousContact?.name ?? null,
3850
+ email: trimmed
3851
+ });
3852
+ setIsSuccess(true);
3853
+ setIsSubmitting(true);
3854
+ try {
3855
+ const result = await client.widget.collectEmail.mutate({ email: trimmed });
3856
+ if (result.contact) {
3857
+ updateContact({
3858
+ id: result.contact.id,
3859
+ name: result.contact.name,
3860
+ email: result.contact.email
3861
+ });
3862
+ }
3863
+ } catch {
3864
+ if (previousContact) {
3865
+ updateContact(previousContact);
3866
+ } else {
3867
+ updateContact({ id: "", name: null, email: null });
3868
+ }
3869
+ setIsSuccess(false);
3870
+ } finally {
3871
+ setIsSubmitting(false);
3872
+ }
3873
+ };
3874
+ if (isSuccess) {
3875
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-email-card hw-email-card--success", children: [
3876
+ /* @__PURE__ */ jsx12("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "#22c55e", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx12("polyline", { points: "20 6 9 17 4 12" }) }),
3877
+ t("widget.email_saved")
3878
+ ] });
3879
+ }
3880
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-email-card", children: [
3881
+ /* @__PURE__ */ jsxs7("div", { className: "hw-email-card-header", children: [
3882
+ /* @__PURE__ */ jsx12("div", { className: "hw-email-card-icon", children: /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
3883
+ /* @__PURE__ */ jsx12("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
3884
+ /* @__PURE__ */ jsx12("path", { d: "M22 7l-10 7L2 7" })
3885
+ ] }) }),
3886
+ /* @__PURE__ */ jsxs7("div", { children: [
3887
+ /* @__PURE__ */ jsx12("p", { className: "hw-email-card-title", children: t("widget.email_title") }),
3888
+ /* @__PURE__ */ jsx12("p", { className: "hw-email-card-subtitle", children: t("widget.email_subtitle") })
3889
+ ] })
3890
+ ] }),
3891
+ /* @__PURE__ */ jsx12(
3892
+ "input",
3893
+ {
3894
+ type: "email",
3895
+ className: `hw-email-card-input${hasError ? " hw-email-card-input--error" : ""}`,
3896
+ value: email,
3897
+ onChange: (e) => {
3898
+ setEmail(e.target.value);
3899
+ setHasError(false);
3900
+ },
3901
+ onKeyDown: (e) => {
3902
+ if (e.key === "Enter") handleSubmit();
3903
+ },
3904
+ placeholder: t("widget.email_placeholder")
3905
+ }
3906
+ ),
3907
+ /* @__PURE__ */ jsx12(
3908
+ "button",
3909
+ {
3910
+ type: "button",
3911
+ className: "hw-email-card-submit",
3912
+ onClick: handleSubmit,
3913
+ disabled: isSubmitting || !email.trim(),
3914
+ children: isSubmitting ? /* @__PURE__ */ jsx12("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "32", strokeDashoffset: "32", children: /* @__PURE__ */ jsx12("animate", { attributeName: "stroke-dashoffset", values: "32;0", dur: "1s", repeatCount: "indefinite" }) }) }) : t("widget.email_continue")
3915
+ }
3916
+ )
3917
+ ] });
3918
+ }
3919
+ function NewConversationView({
3920
+ text,
3921
+ onTextChange,
3922
+ onSubmit,
3923
+ isSubmitting,
3924
+ welcomeMessage
3925
+ }) {
3926
+ const welcomeTime = useRef13((/* @__PURE__ */ new Date()).toISOString()).current;
3927
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-new", children: [
3928
+ /* @__PURE__ */ jsx12("div", { className: "hw-new-welcome", children: /* @__PURE__ */ jsx12(
3929
+ MessageBubble,
3930
+ {
3931
+ text: welcomeMessage,
3932
+ sender: "ai",
3933
+ time: welcomeTime,
3934
+ parts: null
3935
+ }
3936
+ ) }),
3937
+ /* @__PURE__ */ jsx12(
3938
+ Composer,
3939
+ {
3940
+ text,
3941
+ onTextChange,
3942
+ onSubmit,
3943
+ isSubmitting,
3944
+ placeholder: t("widget.placeholder_new")
3945
+ }
3946
+ )
3947
+ ] });
3948
+ }
3949
+ function ConversationView({ conversationId }) {
3950
+ const { items, isLoading, hasMore, loadMore } = useTimeline(conversationId);
3951
+ const {
3952
+ text,
3953
+ setText,
3954
+ submit,
3955
+ isSubmitting,
3956
+ attachments,
3957
+ addFiles,
3958
+ removeAttachment,
3959
+ sendError,
3960
+ clearSendError
3961
+ } = useComposer(conversationId);
3962
+ const { isTyping, isAiProcessing, setTyping } = usePresence(conversationId);
3963
+ const agentConfig = useWidgetStore((s) => s.agentConfig);
3964
+ const visitorContact = useWidgetStore((s) => s.visitor?.contact);
3965
+ const isResolved = useWidgetStore((s) => {
3966
+ const conv = s.conversations.find((c) => c.id === conversationId);
3967
+ return conv?.status === "resolved";
3968
+ });
3969
+ const hasVisitorMessage = items.some(
3970
+ (it) => it.type === "message" && Boolean(it.visitorId)
3971
+ );
3972
+ const hasAiReply = items.some(
3973
+ (it) => it.type === "message" && Boolean(it.aiAgentId)
3974
+ );
3975
+ const needsEmail = !!agentConfig?.requireVisitorEmail && !visitorContact?.email && hasVisitorMessage && hasAiReply;
3976
+ if (isLoading && items.length === 0) {
3977
+ return /* @__PURE__ */ jsx12("div", { className: "hw-messages", children: /* @__PURE__ */ jsxs7("div", { className: "hw-skeleton-messages", children: [
3978
+ /* @__PURE__ */ jsxs7("div", { className: "hw-skeleton-msg", children: [
3979
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-avatar" }),
3980
+ /* @__PURE__ */ jsxs7("div", { className: "hw-skeleton-bubble", children: [
3981
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 140 } }),
3982
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 200 } })
3983
+ ] })
3984
+ ] }),
3985
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton-msg hw-skeleton-msg--right", children: /* @__PURE__ */ jsx12("div", { className: "hw-skeleton-bubble", style: { alignItems: "flex-end" }, children: /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 160 } }) }) }),
3986
+ /* @__PURE__ */ jsxs7("div", { className: "hw-skeleton-msg", children: [
3987
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-avatar" }),
3988
+ /* @__PURE__ */ jsxs7("div", { className: "hw-skeleton-bubble", children: [
3989
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 180 } }),
3990
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 120 } }),
3991
+ /* @__PURE__ */ jsx12("div", { className: "hw-skeleton hw-skeleton-line", style: { width: 80 } })
3992
+ ] })
3993
+ ] })
3994
+ ] }) });
3995
+ }
3996
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
3997
+ /* @__PURE__ */ jsx12(
3998
+ MessageList,
3999
+ {
4000
+ items,
4001
+ isTyping,
4002
+ isAiProcessing,
4003
+ hasMore,
4004
+ isLoadingMore: isLoading,
4005
+ onLoadMore: loadMore
4006
+ }
4007
+ ),
4008
+ isResolved ? /* @__PURE__ */ jsx12(ResolvedBanner, { conversationId }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
4009
+ needsEmail && /* @__PURE__ */ jsx12(InlineEmailPrompt, {}),
4010
+ sendError && /* @__PURE__ */ jsxs7("div", { className: "hw-send-error", children: [
4011
+ sendError,
4012
+ /* @__PURE__ */ jsx12(
4013
+ "button",
4014
+ {
4015
+ type: "button",
4016
+ className: "hw-send-error-retry",
4017
+ onClick: () => {
4018
+ clearSendError();
4019
+ submit();
4020
+ },
4021
+ children: t("widget.retry")
4022
+ }
4023
+ )
4024
+ ] }),
4025
+ /* @__PURE__ */ jsx12(
4026
+ Composer,
4027
+ {
4028
+ text,
4029
+ onTextChange: setText,
4030
+ onSubmit: submit,
4031
+ onTyping: setTyping,
4032
+ isSubmitting,
4033
+ attachments,
4034
+ onAddFiles: addFiles,
4035
+ onRemoveAttachment: removeAttachment,
4036
+ disabled: needsEmail
4037
+ }
4038
+ )
4039
+ ] })
4040
+ ] });
4041
+ }
4042
+ function ResolvedBanner({ conversationId }) {
4043
+ const { client } = useWidgetContext();
4044
+ const [csatRating, setCsatRating] = useState12(null);
4045
+ const [csatSubmitted, setCsatSubmitted] = useState12(false);
4046
+ const handleReopen = async () => {
4047
+ try {
4048
+ await client.widget.sendMessage.mutate({ conversationId, text: t("widget.send_followup") });
4049
+ } catch (err) {
4050
+ console.error("Failed to reopen:", err);
4051
+ }
4052
+ };
4053
+ const handleCsat = (rating) => {
4054
+ setCsatRating(rating);
4055
+ setCsatSubmitted(true);
4056
+ client.widget.sendCsat.mutate({ conversationId, rating }).catch(() => {
4057
+ });
4058
+ };
4059
+ return /* @__PURE__ */ jsxs7("div", { className: "hw-resolved", children: [
4060
+ /* @__PURE__ */ jsx12("div", { className: "hw-resolved-text", children: t("widget.resolved_text") }),
4061
+ !csatSubmitted ? /* @__PURE__ */ jsxs7("div", { className: "hw-csat", children: [
4062
+ /* @__PURE__ */ jsx12("div", { className: "hw-csat-label", children: t("widget.csat_label") }),
4063
+ /* @__PURE__ */ jsxs7("div", { className: "hw-csat-buttons", children: [
4064
+ /* @__PURE__ */ jsx12(
4065
+ "button",
4066
+ {
4067
+ type: "button",
4068
+ className: `hw-csat-btn ${csatRating === "good" ? "hw-csat-btn--selected" : ""}`,
4069
+ onClick: () => handleCsat("good"),
4070
+ "aria-label": t("widget.aria_good"),
4071
+ children: "\u{1F44D}"
4072
+ }
4073
+ ),
4074
+ /* @__PURE__ */ jsx12(
4075
+ "button",
4076
+ {
4077
+ type: "button",
4078
+ className: `hw-csat-btn ${csatRating === "bad" ? "hw-csat-btn--selected" : ""}`,
4079
+ onClick: () => handleCsat("bad"),
4080
+ "aria-label": t("widget.aria_bad"),
4081
+ children: "\u{1F44E}"
4082
+ }
4083
+ )
4084
+ ] })
4085
+ ] }) : /* @__PURE__ */ jsx12("div", { className: "hw-csat-thanks", children: t("widget.csat_thanks") }),
4086
+ /* @__PURE__ */ jsx12("div", { className: "hw-resolved-actions", children: /* @__PURE__ */ jsx12("button", { type: "button", className: "hw-resolved-reopen", onClick: handleReopen, children: t("widget.send_followup") }) })
4087
+ ] });
4088
+ }
4089
+ function MessagePreview({ onOpen }) {
4090
+ const preview = useWidgetStore((s) => s.incomingPreview);
4091
+ const dismissPreview = useWidgetStore((s) => s.dismissPreview);
4092
+ const agentName = useWidgetStore((s) => s.agentConfig?.name) ?? "Support";
4093
+ const [closing, setClosing] = useState12(false);
4094
+ useEffect12(() => {
4095
+ if (!preview) return;
4096
+ const timer = setTimeout(() => {
4097
+ setClosing(true);
4098
+ setTimeout(() => dismissPreview(), 200);
4099
+ }, 8e3);
4100
+ return () => clearTimeout(timer);
4101
+ }, [preview, dismissPreview]);
4102
+ if (!preview) return null;
4103
+ const handleClose = (e) => {
4104
+ e.stopPropagation();
4105
+ setClosing(true);
4106
+ setTimeout(() => {
4107
+ dismissPreview();
4108
+ setClosing(false);
4109
+ }, 200);
4110
+ };
4111
+ const handleClick = () => {
4112
+ dismissPreview();
4113
+ onOpen();
4114
+ };
4115
+ return /* @__PURE__ */ jsxs7(
4116
+ "div",
4117
+ {
4118
+ className: `hw-preview ${closing ? "hw-preview--closing" : ""}`,
4119
+ onClick: handleClick,
4120
+ role: "button",
4121
+ tabIndex: 0,
4122
+ children: [
4123
+ /* @__PURE__ */ jsxs7("div", { className: "hw-preview-header", children: [
4124
+ /* @__PURE__ */ jsx12("div", { className: "hw-preview-avatar", children: /* @__PURE__ */ jsx12(WidgetLogo, {}) }),
4125
+ /* @__PURE__ */ jsx12("div", { className: "hw-preview-name", children: agentName }),
4126
+ /* @__PURE__ */ jsx12(
4127
+ "button",
4128
+ {
4129
+ type: "button",
4130
+ className: "hw-preview-close",
4131
+ onClick: handleClose,
4132
+ "aria-label": t("widget.aria_dismiss"),
4133
+ children: /* @__PURE__ */ jsx12(IconCrossMedium, { size: 20 })
4134
+ }
4135
+ )
4136
+ ] }),
4137
+ /* @__PURE__ */ jsx12("div", { className: "hw-preview-body", children: preview.text })
4138
+ ]
4139
+ }
4140
+ );
4141
+ }
4142
+ export {
4143
+ Composer,
4144
+ DefaultWidget,
4145
+ HelpwayWidget,
4146
+ MessageBubble,
4147
+ MessageList,
4148
+ SupportProvider,
4149
+ clearVisitorId,
4150
+ formatRelativeTime,
4151
+ formatTime,
4152
+ getInitials,
4153
+ getOrCreateVisitorId,
4154
+ useComposer,
4155
+ useConversations,
4156
+ useIdentify,
4157
+ usePresence,
4158
+ useRealtime,
4159
+ useTimeline,
4160
+ useUnread,
4161
+ useWidget,
4162
+ useWidgetStore
4163
+ };
4164
+ //# sourceMappingURL=index.js.map