@uptrademedia/site-kit 1.1.6 → 1.1.7

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.
@@ -10,9 +10,13 @@ var React2__default = /*#__PURE__*/_interopDefault(React2);
10
10
 
11
11
  // src/engage/ChatWidget.tsx
12
12
  function getApiConfig() {
13
- const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
13
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
14
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
15
+ const defaultSignalUrl = isDev && process.env?.NEXT_PUBLIC_SIGNAL_API_URL ? process.env.NEXT_PUBLIC_SIGNAL_API_URL : "https://signal.uptrademedia.com";
16
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || defaultApiUrl : defaultApiUrl;
14
17
  const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
15
- return { apiUrl, apiKey };
18
+ const signalUrl = typeof window !== "undefined" ? window.__SITE_KIT_SIGNAL_URL__ || defaultSignalUrl : defaultSignalUrl;
19
+ return { apiUrl, apiKey, signalUrl };
16
20
  }
17
21
  function generateVisitorId() {
18
22
  const stored = typeof localStorage !== "undefined" ? localStorage.getItem("engage_visitor_id") : null;
@@ -35,7 +39,7 @@ function isLightColor(hex) {
35
39
  const b = num & 255;
36
40
  return (r * 299 + g * 587 + b * 114) / 1e3 > 160;
37
41
  }
38
- function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
42
+ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, signalUrl: propSignalUrl }) {
39
43
  const [resolvedProjectId, setResolvedProjectId] = React2.useState(propProjectId || null);
40
44
  const projectId = propProjectId || resolvedProjectId || "";
41
45
  const [isOpen, setIsOpen] = React2.useState(false);
@@ -56,6 +60,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
56
60
  const [lastFailedSend, setLastFailedSend] = React2.useState(null);
57
61
  const [showWelcome, setShowWelcome] = React2.useState(true);
58
62
  const [checkingAvailability, setCheckingAvailability] = React2.useState(false);
63
+ const [checkingHandoff, setCheckingHandoff] = React2.useState(false);
59
64
  React2.useRef(null);
60
65
  const pendingInitialMessageRef = React2.useRef(null);
61
66
  const apiKeyMissingWarnedRef = React2.useRef(false);
@@ -83,6 +88,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
83
88
  const offlineHeading = widgetConfig?.offline_heading ?? "No agents available right now";
84
89
  const offlineSubheading = handoffOfflinePrompt ?? widgetConfig?.offline_subheading ?? widgetConfig?.form_description ?? widgetConfig?.offline_message ?? config?.offlineMessage ?? "Leave us a message and we'll get back to you!";
85
90
  const baseUrl = propApiUrl || getApiConfig().apiUrl;
91
+ const signalUrl = propSignalUrl || getApiConfig().signalUrl;
86
92
  React2.useEffect(() => {
87
93
  if (propProjectId || resolvedProjectId) return;
88
94
  const { apiKey } = getApiConfig();
@@ -155,6 +161,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
155
161
  const { data } = await response.json();
156
162
  const session = data.session ?? data;
157
163
  const sid = session?.id ?? session?.session_id ?? data.id ?? data.session_id;
164
+ const signalEnabled = session?.chat_mode === "ai" || session?.chat_mode === "hybrid";
158
165
  setSessionId(sid);
159
166
  const messages2 = session?.messages ?? data.messages ?? [];
160
167
  if (messages2.length > 0) {
@@ -168,13 +175,40 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
168
175
  }))
169
176
  );
170
177
  }
171
- return sid;
178
+ return { id: sid, signalEnabled };
172
179
  }
173
180
  } catch (error) {
174
181
  console.error("[ChatWidget] Session init failed:", error);
175
182
  }
176
183
  return null;
177
184
  }, [projectId, visitorId, baseUrl]);
185
+ const sendToSignalApi = React2.useCallback(
186
+ async (content, conversationId) => {
187
+ const url = `${signalUrl.replace(/\/$/, "")}/echo/public/chat`;
188
+ const body = {
189
+ message: content,
190
+ projectId,
191
+ visitorId,
192
+ ...conversationId ? { conversationId } : {},
193
+ pageUrl: typeof window !== "undefined" ? window.location.href : void 0
194
+ };
195
+ const res = await fetch(url, {
196
+ method: "POST",
197
+ headers: { "Content-Type": "application/json" },
198
+ body: JSON.stringify(body)
199
+ });
200
+ const json = await res.json();
201
+ if (!res.ok) {
202
+ throw new Error(json?.error?.message || json?.message || `Signal API error: ${res.status}`);
203
+ }
204
+ const data = json?.data ?? json;
205
+ const aiContent = data?.content ?? data?.response ?? data?.message ?? "I'm sorry, I couldn't process that.";
206
+ const suggestions = data?.suggestions;
207
+ const newConversationId = data?.conversationId ?? conversationId;
208
+ return { content: aiContent, suggestions, conversationId: newConversationId };
209
+ },
210
+ [signalUrl, projectId, visitorId]
211
+ );
178
212
  const handleSocketMessage = React2.useCallback((data) => {
179
213
  switch (data.type || data.event) {
180
214
  case "message": {
@@ -363,13 +397,50 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
363
397
  }, [fetchWidgetConfig, checkAvailability]);
364
398
  React2.useEffect(() => {
365
399
  if (isOpen && !showWelcome && !checkingAvailability && !showOfflineForm && !sessionId) {
366
- initSession().then(async (id) => {
367
- if (!id) return;
368
- setConnectionStatus("connecting");
369
- await connectSocket(id);
400
+ initSession().then(async (result) => {
401
+ if (!result?.id) return;
402
+ const { id, signalEnabled } = result;
370
403
  const pending = pendingInitialMessageRef.current;
371
404
  if (pending) {
372
405
  pendingInitialMessageRef.current = null;
406
+ }
407
+ if (signalEnabled && pending) {
408
+ setIsLoading(true);
409
+ setMessages((prev) => [
410
+ ...prev,
411
+ { id: `u-${Date.now()}`, role: "user", content: pending, timestamp: /* @__PURE__ */ new Date() }
412
+ ]);
413
+ try {
414
+ const { content: aiContent, suggestions } = await sendToSignalApi(pending, id);
415
+ setMessages((prev) => [
416
+ ...prev,
417
+ {
418
+ id: `ai-${Date.now()}`,
419
+ role: "assistant",
420
+ content: aiContent,
421
+ timestamp: /* @__PURE__ */ new Date(),
422
+ ...suggestions?.length ? { suggestions } : {}
423
+ }
424
+ ]);
425
+ } catch (err) {
426
+ console.error("[ChatWidget] Signal API error:", err);
427
+ setMessages((prev) => [
428
+ ...prev,
429
+ {
430
+ id: `err-${Date.now()}`,
431
+ role: "assistant",
432
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
433
+ timestamp: /* @__PURE__ */ new Date(),
434
+ suggestions: ["Talk to a person", "Try again"]
435
+ }
436
+ ]);
437
+ } finally {
438
+ setIsLoading(false);
439
+ }
440
+ }
441
+ setConnectionStatus("connecting");
442
+ await connectSocket(id);
443
+ if (!signalEnabled && pending) {
373
444
  const waitForSocket = () => new Promise((resolve) => {
374
445
  const check = (attempts = 0) => {
375
446
  if (socketRef.current?.connected || attempts > 20) {
@@ -390,7 +461,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
390
461
  return () => {
391
462
  if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current);
392
463
  };
393
- }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket]);
464
+ }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket, sendToSignalApi]);
394
465
  const handleToggle = React2.useCallback(() => {
395
466
  setIsOpen((prev) => !prev);
396
467
  }, []);
@@ -480,6 +551,36 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
480
551
  setMessages((prev) => [...prev, userMessage]);
481
552
  setInputValue("");
482
553
  setIsLoading(true);
554
+ if (widgetConfig?.signal_enabled && hasText) {
555
+ try {
556
+ const { content: aiContent, suggestions } = await sendToSignalApi(content, sessionId);
557
+ setMessages((prev) => [
558
+ ...prev,
559
+ {
560
+ id: `ai-${Date.now()}`,
561
+ role: "assistant",
562
+ content: aiContent,
563
+ timestamp: /* @__PURE__ */ new Date(),
564
+ ...suggestions?.length ? { suggestions } : {}
565
+ }
566
+ ]);
567
+ } catch (err) {
568
+ console.error("[ChatWidget] Signal API error:", err);
569
+ setMessages((prev) => [
570
+ ...prev,
571
+ {
572
+ id: `err-${Date.now()}`,
573
+ role: "assistant",
574
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
575
+ timestamp: /* @__PURE__ */ new Date(),
576
+ suggestions: ["Talk to a person", "Try again"]
577
+ }
578
+ ]);
579
+ } finally {
580
+ setIsLoading(false);
581
+ }
582
+ return;
583
+ }
483
584
  const socket = socketRef.current;
484
585
  if (socket?.connected) {
485
586
  socket.emit("visitor:message", { content: userMessage.content, attachments: attachments.length ? attachments : void 0 });
@@ -497,7 +598,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
497
598
  }
498
599
  }, 3e3);
499
600
  },
500
- [inputValue, isLoading, pendingFiles, uploadWidgetFile]
601
+ [inputValue, isLoading, pendingFiles, uploadWidgetFile, widgetConfig?.signal_enabled, sendToSignalApi, sessionId]
501
602
  );
502
603
  const retryFailedSend = React2.useCallback(() => {
503
604
  if (!lastFailedSend || !sessionId) return;
@@ -515,33 +616,48 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
515
616
  if (!sessionId) return;
516
617
  const apiKey = ensureApiKey();
517
618
  if (!apiKey) return;
619
+ setCheckingHandoff(true);
518
620
  try {
519
- const availRes = await fetch(`${baseUrl}/engage/widget/availability?projectId=${projectId}`, {
520
- headers: { "x-api-key": apiKey }
521
- });
522
- const avail = availRes.ok ? (await availRes.json()).data : null;
523
- if (avail?.agentsOnline === 0) {
621
+ const firstAvail = await checkAvailability();
622
+ if (firstAvail?.agentsOnline && firstAvail.agentsOnline > 0) {
623
+ setCheckingHandoff(false);
624
+ await fetch(`${baseUrl}/engage/widget/handoff`, {
625
+ method: "POST",
626
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
627
+ body: JSON.stringify({ sessionId })
628
+ });
629
+ setMessages((prev) => [
630
+ ...prev,
631
+ { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
632
+ ]);
633
+ return;
634
+ }
635
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
636
+ const secondAvail = await checkAvailability();
637
+ setCheckingHandoff(false);
638
+ if (secondAvail?.agentsOnline && secondAvail.agentsOnline > 0) {
639
+ await fetch(`${baseUrl}/engage/widget/handoff`, {
640
+ method: "POST",
641
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
642
+ body: JSON.stringify({ sessionId })
643
+ });
644
+ setMessages((prev) => [
645
+ ...prev,
646
+ { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
647
+ ]);
648
+ } else {
524
649
  setHandoffOfflinePrompt(widgetConfig?.offline_subheading ?? "Nobody is online right now. Leave your details and we'll get back to you.");
525
650
  setShowOfflineForm(true);
526
651
  setMessages((prev) => [
527
652
  ...prev,
528
653
  { id: `handoff-offline-${Date.now()}`, role: "system", content: offlineHeading, timestamp: /* @__PURE__ */ new Date() }
529
654
  ]);
530
- return;
531
655
  }
532
- await fetch(`${baseUrl}/engage/widget/handoff`, {
533
- method: "POST",
534
- headers: { "Content-Type": "application/json", "x-api-key": apiKey },
535
- body: JSON.stringify({ sessionId })
536
- });
537
- setMessages((prev) => [
538
- ...prev,
539
- { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
540
- ]);
541
656
  } catch (error) {
657
+ setCheckingHandoff(false);
542
658
  console.error("[ChatWidget] Handoff request failed:", error);
543
659
  }
544
- }, [sessionId, baseUrl, projectId, widgetConfig, offlineHeading]);
660
+ }, [sessionId, baseUrl, projectId, widgetConfig, offlineHeading, checkAvailability]);
545
661
  const [offlineError, setOfflineError] = React2.useState(null);
546
662
  const handleOfflineSubmit = React2.useCallback(
547
663
  async (e) => {
@@ -842,7 +958,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
842
958
  )
843
959
  ] }),
844
960
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
845
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 600, color: "#111827", marginBottom: 6 }, children: "Checking for a team member" }),
961
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 600, color: "#111827", marginBottom: 6 }, children: checkingHandoff ? "Looking for online support" : "Checking for a team member" }),
846
962
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 14, color: "#6b7280", lineHeight: 1.5 }, children: [
847
963
  "One moment please",
848
964
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "inline-flex", width: 20 }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { animation: "checkDots 1.5s infinite steps(4, end)" }, children: "..." }) })
@@ -1045,11 +1161,11 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
1045
1161
  },
1046
1162
  children: [
1047
1163
  Header,
1048
- checkingAvailability ? CheckingScreen : showOfflineForm ? OfflineFormView : showWelcome && welcomeEnabled && messages.length === 0 ? WelcomeScreen : MessagesView,
1164
+ checkingAvailability || checkingHandoff ? CheckingScreen : showOfflineForm ? OfflineFormView : showWelcome && welcomeEnabled && messages.length === 0 ? WelcomeScreen : MessagesView,
1049
1165
  showPoweredBy && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "6px 0", textAlign: "center", fontSize: 11, color: "#9ca3af", backgroundColor: "#ffffff", borderTop: "1px solid #f3f4f6" }, children: [
1050
1166
  "Powered by",
1051
1167
  " ",
1052
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://uptrademedia.com", target: "_blank", rel: "noopener noreferrer", style: { color: "#6b7280", textDecoration: "none", fontWeight: 500 }, children: "Uptrade" })
1168
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://uptrademedia.com", target: "_blank", rel: "noopener noreferrer", style: { color: "#6b7280", textDecoration: "none", fontWeight: 500 }, children: "Sonor" })
1053
1169
  ] }),
1054
1170
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1055
1171
  @keyframes chatSlideUp {
@@ -1344,7 +1460,9 @@ function DesignRenderer({
1344
1460
  )) });
1345
1461
  }
1346
1462
  function getApiConfig2() {
1347
- const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
1463
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
1464
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
1465
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || defaultApiUrl : defaultApiUrl;
1348
1466
  const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
1349
1467
  return { apiUrl, apiKey };
1350
1468
  }
@@ -1705,5 +1823,5 @@ function getDeviceType() {
1705
1823
  exports.ChatWidget = ChatWidget;
1706
1824
  exports.DesignRenderer = DesignRenderer;
1707
1825
  exports.EngageWidget = EngageWidget;
1708
- //# sourceMappingURL=chunk-6ODMCZHH.js.map
1709
- //# sourceMappingURL=chunk-6ODMCZHH.js.map
1826
+ //# sourceMappingURL=chunk-CTKXFLIF.js.map
1827
+ //# sourceMappingURL=chunk-CTKXFLIF.js.map