@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.
@@ -4,9 +4,13 @@ import { usePathname } from 'next/navigation';
4
4
 
5
5
  // src/engage/ChatWidget.tsx
6
6
  function getApiConfig() {
7
- const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
7
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
8
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
9
+ const defaultSignalUrl = isDev && process.env?.NEXT_PUBLIC_SIGNAL_API_URL ? process.env.NEXT_PUBLIC_SIGNAL_API_URL : "https://signal.uptrademedia.com";
10
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || defaultApiUrl : defaultApiUrl;
8
11
  const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
9
- return { apiUrl, apiKey };
12
+ const signalUrl = typeof window !== "undefined" ? window.__SITE_KIT_SIGNAL_URL__ || defaultSignalUrl : defaultSignalUrl;
13
+ return { apiUrl, apiKey, signalUrl };
10
14
  }
11
15
  function generateVisitorId() {
12
16
  const stored = typeof localStorage !== "undefined" ? localStorage.getItem("engage_visitor_id") : null;
@@ -29,7 +33,7 @@ function isLightColor(hex) {
29
33
  const b = num & 255;
30
34
  return (r * 299 + g * 587 + b * 114) / 1e3 > 160;
31
35
  }
32
- function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
36
+ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, signalUrl: propSignalUrl }) {
33
37
  const [resolvedProjectId, setResolvedProjectId] = useState(propProjectId || null);
34
38
  const projectId = propProjectId || resolvedProjectId || "";
35
39
  const [isOpen, setIsOpen] = useState(false);
@@ -50,6 +54,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
50
54
  const [lastFailedSend, setLastFailedSend] = useState(null);
51
55
  const [showWelcome, setShowWelcome] = useState(true);
52
56
  const [checkingAvailability, setCheckingAvailability] = useState(false);
57
+ const [checkingHandoff, setCheckingHandoff] = useState(false);
53
58
  useRef(null);
54
59
  const pendingInitialMessageRef = useRef(null);
55
60
  const apiKeyMissingWarnedRef = useRef(false);
@@ -77,6 +82,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
77
82
  const offlineHeading = widgetConfig?.offline_heading ?? "No agents available right now";
78
83
  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!";
79
84
  const baseUrl = propApiUrl || getApiConfig().apiUrl;
85
+ const signalUrl = propSignalUrl || getApiConfig().signalUrl;
80
86
  useEffect(() => {
81
87
  if (propProjectId || resolvedProjectId) return;
82
88
  const { apiKey } = getApiConfig();
@@ -149,6 +155,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
149
155
  const { data } = await response.json();
150
156
  const session = data.session ?? data;
151
157
  const sid = session?.id ?? session?.session_id ?? data.id ?? data.session_id;
158
+ const signalEnabled = session?.chat_mode === "ai" || session?.chat_mode === "hybrid";
152
159
  setSessionId(sid);
153
160
  const messages2 = session?.messages ?? data.messages ?? [];
154
161
  if (messages2.length > 0) {
@@ -162,13 +169,40 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
162
169
  }))
163
170
  );
164
171
  }
165
- return sid;
172
+ return { id: sid, signalEnabled };
166
173
  }
167
174
  } catch (error) {
168
175
  console.error("[ChatWidget] Session init failed:", error);
169
176
  }
170
177
  return null;
171
178
  }, [projectId, visitorId, baseUrl]);
179
+ const sendToSignalApi = useCallback(
180
+ async (content, conversationId) => {
181
+ const url = `${signalUrl.replace(/\/$/, "")}/echo/public/chat`;
182
+ const body = {
183
+ message: content,
184
+ projectId,
185
+ visitorId,
186
+ ...conversationId ? { conversationId } : {},
187
+ pageUrl: typeof window !== "undefined" ? window.location.href : void 0
188
+ };
189
+ const res = await fetch(url, {
190
+ method: "POST",
191
+ headers: { "Content-Type": "application/json" },
192
+ body: JSON.stringify(body)
193
+ });
194
+ const json = await res.json();
195
+ if (!res.ok) {
196
+ throw new Error(json?.error?.message || json?.message || `Signal API error: ${res.status}`);
197
+ }
198
+ const data = json?.data ?? json;
199
+ const aiContent = data?.content ?? data?.response ?? data?.message ?? "I'm sorry, I couldn't process that.";
200
+ const suggestions = data?.suggestions;
201
+ const newConversationId = data?.conversationId ?? conversationId;
202
+ return { content: aiContent, suggestions, conversationId: newConversationId };
203
+ },
204
+ [signalUrl, projectId, visitorId]
205
+ );
172
206
  const handleSocketMessage = useCallback((data) => {
173
207
  switch (data.type || data.event) {
174
208
  case "message": {
@@ -357,13 +391,50 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
357
391
  }, [fetchWidgetConfig, checkAvailability]);
358
392
  useEffect(() => {
359
393
  if (isOpen && !showWelcome && !checkingAvailability && !showOfflineForm && !sessionId) {
360
- initSession().then(async (id) => {
361
- if (!id) return;
362
- setConnectionStatus("connecting");
363
- await connectSocket(id);
394
+ initSession().then(async (result) => {
395
+ if (!result?.id) return;
396
+ const { id, signalEnabled } = result;
364
397
  const pending = pendingInitialMessageRef.current;
365
398
  if (pending) {
366
399
  pendingInitialMessageRef.current = null;
400
+ }
401
+ if (signalEnabled && pending) {
402
+ setIsLoading(true);
403
+ setMessages((prev) => [
404
+ ...prev,
405
+ { id: `u-${Date.now()}`, role: "user", content: pending, timestamp: /* @__PURE__ */ new Date() }
406
+ ]);
407
+ try {
408
+ const { content: aiContent, suggestions } = await sendToSignalApi(pending, id);
409
+ setMessages((prev) => [
410
+ ...prev,
411
+ {
412
+ id: `ai-${Date.now()}`,
413
+ role: "assistant",
414
+ content: aiContent,
415
+ timestamp: /* @__PURE__ */ new Date(),
416
+ ...suggestions?.length ? { suggestions } : {}
417
+ }
418
+ ]);
419
+ } catch (err) {
420
+ console.error("[ChatWidget] Signal API error:", err);
421
+ setMessages((prev) => [
422
+ ...prev,
423
+ {
424
+ id: `err-${Date.now()}`,
425
+ role: "assistant",
426
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
427
+ timestamp: /* @__PURE__ */ new Date(),
428
+ suggestions: ["Talk to a person", "Try again"]
429
+ }
430
+ ]);
431
+ } finally {
432
+ setIsLoading(false);
433
+ }
434
+ }
435
+ setConnectionStatus("connecting");
436
+ await connectSocket(id);
437
+ if (!signalEnabled && pending) {
367
438
  const waitForSocket = () => new Promise((resolve) => {
368
439
  const check = (attempts = 0) => {
369
440
  if (socketRef.current?.connected || attempts > 20) {
@@ -384,7 +455,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
384
455
  return () => {
385
456
  if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current);
386
457
  };
387
- }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket]);
458
+ }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket, sendToSignalApi]);
388
459
  const handleToggle = useCallback(() => {
389
460
  setIsOpen((prev) => !prev);
390
461
  }, []);
@@ -474,6 +545,36 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
474
545
  setMessages((prev) => [...prev, userMessage]);
475
546
  setInputValue("");
476
547
  setIsLoading(true);
548
+ if (widgetConfig?.signal_enabled && hasText) {
549
+ try {
550
+ const { content: aiContent, suggestions } = await sendToSignalApi(content, sessionId);
551
+ setMessages((prev) => [
552
+ ...prev,
553
+ {
554
+ id: `ai-${Date.now()}`,
555
+ role: "assistant",
556
+ content: aiContent,
557
+ timestamp: /* @__PURE__ */ new Date(),
558
+ ...suggestions?.length ? { suggestions } : {}
559
+ }
560
+ ]);
561
+ } catch (err) {
562
+ console.error("[ChatWidget] Signal API error:", err);
563
+ setMessages((prev) => [
564
+ ...prev,
565
+ {
566
+ id: `err-${Date.now()}`,
567
+ role: "assistant",
568
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
569
+ timestamp: /* @__PURE__ */ new Date(),
570
+ suggestions: ["Talk to a person", "Try again"]
571
+ }
572
+ ]);
573
+ } finally {
574
+ setIsLoading(false);
575
+ }
576
+ return;
577
+ }
477
578
  const socket = socketRef.current;
478
579
  if (socket?.connected) {
479
580
  socket.emit("visitor:message", { content: userMessage.content, attachments: attachments.length ? attachments : void 0 });
@@ -491,7 +592,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
491
592
  }
492
593
  }, 3e3);
493
594
  },
494
- [inputValue, isLoading, pendingFiles, uploadWidgetFile]
595
+ [inputValue, isLoading, pendingFiles, uploadWidgetFile, widgetConfig?.signal_enabled, sendToSignalApi, sessionId]
495
596
  );
496
597
  const retryFailedSend = useCallback(() => {
497
598
  if (!lastFailedSend || !sessionId) return;
@@ -509,33 +610,48 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
509
610
  if (!sessionId) return;
510
611
  const apiKey = ensureApiKey();
511
612
  if (!apiKey) return;
613
+ setCheckingHandoff(true);
512
614
  try {
513
- const availRes = await fetch(`${baseUrl}/engage/widget/availability?projectId=${projectId}`, {
514
- headers: { "x-api-key": apiKey }
515
- });
516
- const avail = availRes.ok ? (await availRes.json()).data : null;
517
- if (avail?.agentsOnline === 0) {
615
+ const firstAvail = await checkAvailability();
616
+ if (firstAvail?.agentsOnline && firstAvail.agentsOnline > 0) {
617
+ setCheckingHandoff(false);
618
+ await fetch(`${baseUrl}/engage/widget/handoff`, {
619
+ method: "POST",
620
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
621
+ body: JSON.stringify({ sessionId })
622
+ });
623
+ setMessages((prev) => [
624
+ ...prev,
625
+ { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
626
+ ]);
627
+ return;
628
+ }
629
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
630
+ const secondAvail = await checkAvailability();
631
+ setCheckingHandoff(false);
632
+ if (secondAvail?.agentsOnline && secondAvail.agentsOnline > 0) {
633
+ await fetch(`${baseUrl}/engage/widget/handoff`, {
634
+ method: "POST",
635
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
636
+ body: JSON.stringify({ sessionId })
637
+ });
638
+ setMessages((prev) => [
639
+ ...prev,
640
+ { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
641
+ ]);
642
+ } else {
518
643
  setHandoffOfflinePrompt(widgetConfig?.offline_subheading ?? "Nobody is online right now. Leave your details and we'll get back to you.");
519
644
  setShowOfflineForm(true);
520
645
  setMessages((prev) => [
521
646
  ...prev,
522
647
  { id: `handoff-offline-${Date.now()}`, role: "system", content: offlineHeading, timestamp: /* @__PURE__ */ new Date() }
523
648
  ]);
524
- return;
525
649
  }
526
- await fetch(`${baseUrl}/engage/widget/handoff`, {
527
- method: "POST",
528
- headers: { "Content-Type": "application/json", "x-api-key": apiKey },
529
- body: JSON.stringify({ sessionId })
530
- });
531
- setMessages((prev) => [
532
- ...prev,
533
- { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
534
- ]);
535
650
  } catch (error) {
651
+ setCheckingHandoff(false);
536
652
  console.error("[ChatWidget] Handoff request failed:", error);
537
653
  }
538
- }, [sessionId, baseUrl, projectId, widgetConfig, offlineHeading]);
654
+ }, [sessionId, baseUrl, projectId, widgetConfig, offlineHeading, checkAvailability]);
539
655
  const [offlineError, setOfflineError] = useState(null);
540
656
  const handleOfflineSubmit = useCallback(
541
657
  async (e) => {
@@ -836,7 +952,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
836
952
  )
837
953
  ] }),
838
954
  /* @__PURE__ */ jsxs("div", { children: [
839
- /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 600, color: "#111827", marginBottom: 6 }, children: "Checking for a team member" }),
955
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 600, color: "#111827", marginBottom: 6 }, children: checkingHandoff ? "Looking for online support" : "Checking for a team member" }),
840
956
  /* @__PURE__ */ jsxs("div", { style: { fontSize: 14, color: "#6b7280", lineHeight: 1.5 }, children: [
841
957
  "One moment please",
842
958
  /* @__PURE__ */ jsx("span", { style: { display: "inline-flex", width: 20 }, children: /* @__PURE__ */ jsx("span", { style: { animation: "checkDots 1.5s infinite steps(4, end)" }, children: "..." }) })
@@ -1039,11 +1155,11 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl }) {
1039
1155
  },
1040
1156
  children: [
1041
1157
  Header,
1042
- checkingAvailability ? CheckingScreen : showOfflineForm ? OfflineFormView : showWelcome && welcomeEnabled && messages.length === 0 ? WelcomeScreen : MessagesView,
1158
+ checkingAvailability || checkingHandoff ? CheckingScreen : showOfflineForm ? OfflineFormView : showWelcome && welcomeEnabled && messages.length === 0 ? WelcomeScreen : MessagesView,
1043
1159
  showPoweredBy && /* @__PURE__ */ jsxs("div", { style: { padding: "6px 0", textAlign: "center", fontSize: 11, color: "#9ca3af", backgroundColor: "#ffffff", borderTop: "1px solid #f3f4f6" }, children: [
1044
1160
  "Powered by",
1045
1161
  " ",
1046
- /* @__PURE__ */ jsx("a", { href: "https://uptrademedia.com", target: "_blank", rel: "noopener noreferrer", style: { color: "#6b7280", textDecoration: "none", fontWeight: 500 }, children: "Uptrade" })
1162
+ /* @__PURE__ */ jsx("a", { href: "https://uptrademedia.com", target: "_blank", rel: "noopener noreferrer", style: { color: "#6b7280", textDecoration: "none", fontWeight: 500 }, children: "Sonor" })
1047
1163
  ] }),
1048
1164
  /* @__PURE__ */ jsx("style", { children: `
1049
1165
  @keyframes chatSlideUp {
@@ -1338,7 +1454,9 @@ function DesignRenderer({
1338
1454
  )) });
1339
1455
  }
1340
1456
  function getApiConfig2() {
1341
- const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
1457
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
1458
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
1459
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || defaultApiUrl : defaultApiUrl;
1342
1460
  const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
1343
1461
  return { apiUrl, apiKey };
1344
1462
  }
@@ -1697,5 +1815,5 @@ function getDeviceType() {
1697
1815
  }
1698
1816
 
1699
1817
  export { ChatWidget, DesignRenderer, EngageWidget };
1700
- //# sourceMappingURL=chunk-E6L6AY2Q.mjs.map
1701
- //# sourceMappingURL=chunk-E6L6AY2Q.mjs.map
1818
+ //# sourceMappingURL=chunk-TOUIKTXU.mjs.map
1819
+ //# sourceMappingURL=chunk-TOUIKTXU.mjs.map