@sonordev/site-kit 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/EngageWidget-LRE4VD7O.js +14 -0
  2. package/dist/{EngageWidget-JIXUBG47.js.map → EngageWidget-LRE4VD7O.js.map} +1 -1
  3. package/dist/EngageWidget-OAOLB6ED.mjs +5 -0
  4. package/dist/{EngageWidget-4W5EM4UO.mjs.map → EngageWidget-OAOLB6ED.mjs.map} +1 -1
  5. package/dist/chunk-2FEQ6TYD.mjs +37 -0
  6. package/dist/chunk-2FEQ6TYD.mjs.map +1 -0
  7. package/dist/{chunk-5QLM6LZ2.mjs → chunk-APRAGA5G.mjs} +3 -3
  8. package/dist/{chunk-5QLM6LZ2.mjs.map → chunk-APRAGA5G.mjs.map} +1 -1
  9. package/dist/{chunk-HANRPNKG.js → chunk-FMI65E5Y.js} +3 -3
  10. package/dist/{chunk-HANRPNKG.js.map → chunk-FMI65E5Y.js.map} +1 -1
  11. package/dist/{chunk-SLJJJWMV.js → chunk-FPU2QPO4.js} +338 -33
  12. package/dist/chunk-FPU2QPO4.js.map +1 -0
  13. package/dist/chunk-MLJXELPA.js +693 -0
  14. package/dist/chunk-MLJXELPA.js.map +1 -0
  15. package/dist/{chunk-IAV32YHL.mjs → chunk-RV45YMBJ.mjs} +338 -33
  16. package/dist/chunk-RV45YMBJ.mjs.map +1 -0
  17. package/dist/chunk-TCVHI3JP.mjs +686 -0
  18. package/dist/chunk-TCVHI3JP.mjs.map +1 -0
  19. package/dist/chunk-YBKALLBH.js +46 -0
  20. package/dist/chunk-YBKALLBH.js.map +1 -0
  21. package/dist/engage/index.d.mts +2 -2
  22. package/dist/engage/index.d.ts +2 -2
  23. package/dist/engage/index.js +5 -4
  24. package/dist/engage/index.js.map +1 -1
  25. package/dist/engage/index.mjs +2 -1
  26. package/dist/engage/index.mjs.map +1 -1
  27. package/dist/forms/index.js +19 -683
  28. package/dist/forms/index.js.map +1 -1
  29. package/dist/forms/index.mjs +4 -676
  30. package/dist/forms/index.mjs.map +1 -1
  31. package/dist/index.d.mts +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/layout/client.js +2 -2
  34. package/dist/layout/client.mjs +1 -1
  35. package/dist/layout/index.js +2 -2
  36. package/dist/layout/index.mjs +1 -1
  37. package/dist/middleware/index.d.mts +62 -1
  38. package/dist/middleware/index.d.ts +62 -1
  39. package/dist/middleware/index.js +105 -0
  40. package/dist/middleware/index.js.map +1 -1
  41. package/dist/middleware/index.mjs +103 -1
  42. package/dist/middleware/index.mjs.map +1 -1
  43. package/dist/slots/contract.d.mts +83 -0
  44. package/dist/slots/contract.d.ts +83 -0
  45. package/dist/slots/contract.js +41 -0
  46. package/dist/slots/contract.js.map +1 -0
  47. package/dist/slots/contract.mjs +4 -0
  48. package/dist/slots/contract.mjs.map +1 -0
  49. package/dist/slots/index.d.mts +104 -0
  50. package/dist/slots/index.d.ts +104 -0
  51. package/dist/slots/index.js +186 -0
  52. package/dist/slots/index.js.map +1 -0
  53. package/dist/slots/index.mjs +146 -0
  54. package/dist/slots/index.mjs.map +1 -0
  55. package/dist/{types-BnCwwUX3.d.ts → types-D_1iUxOQ.d.mts} +18 -1
  56. package/dist/{types-BnCwwUX3.d.mts → types-D_1iUxOQ.d.ts} +18 -1
  57. package/package.json +11 -1
  58. package/dist/EngageWidget-4W5EM4UO.mjs +0 -4
  59. package/dist/EngageWidget-JIXUBG47.js +0 -13
  60. package/dist/chunk-IAV32YHL.mjs.map +0 -1
  61. package/dist/chunk-SLJJJWMV.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
+ var chunkMLJXELPA_js = require('./chunk-MLJXELPA.js');
4
5
  var React2 = require('react');
5
6
  var ReactDOM = require('react-dom');
6
7
  var navigation = require('next/navigation');
@@ -13,7 +14,260 @@ var React2__default = /*#__PURE__*/_interopDefault(React2);
13
14
  var ReactDOM__default = /*#__PURE__*/_interopDefault(ReactDOM);
14
15
  var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
15
16
 
16
- // src/engage/EngageWidget.tsx
17
+ function alpha(rgb, a) {
18
+ return `rgba(var(--sk-primary-rgb, ${rgb}), ${a})`;
19
+ }
20
+ function asString(v) {
21
+ return typeof v === "string" && v.trim() ? v : void 0;
22
+ }
23
+ function asRecord(v) {
24
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
25
+ }
26
+ function isNavigable(action) {
27
+ return /^(https?:\/\/|\/|#|tel:|mailto:)/i.test(action.trim());
28
+ }
29
+ var DISPLAY_ONLY = ["heading", "section_header", "paragraph"];
30
+ function InlineManagedForm({ slug, prefill, primaryColor, primaryRgb }) {
31
+ const {
32
+ form,
33
+ isLoading,
34
+ fetchError,
35
+ submitError,
36
+ visibleFields,
37
+ values,
38
+ errors,
39
+ setFieldValue,
40
+ submit,
41
+ isSubmitting,
42
+ isComplete
43
+ } = chunkMLJXELPA_js.useForm(slug, { initialValues: prefill || {} });
44
+ const cardStyle = {
45
+ marginTop: 8,
46
+ padding: 12,
47
+ borderRadius: 12,
48
+ border: `1px solid ${alpha(primaryRgb, 0.25)}`,
49
+ backgroundColor: alpha(primaryRgb, 0.04)
50
+ };
51
+ if (isLoading) {
52
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, fontSize: 13, color: "var(--sk-text-tertiary, #9ca3af)" }, children: "Loading form\u2026" }) });
53
+ }
54
+ if (fetchError || !form) {
55
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, fontSize: 13, color: "var(--sk-text-tertiary, #9ca3af)" }, children: "This form is unavailable right now." }) });
56
+ }
57
+ if (isComplete) {
58
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, fontSize: 13.5, color: "var(--sk-text-primary, #111827)", lineHeight: 1.5 }, children: form.success_message }) });
59
+ }
60
+ const inputBase = {
61
+ width: "100%",
62
+ padding: "8px 10px",
63
+ borderRadius: 8,
64
+ // Longhand (not the `border` shorthand) so the error-state `borderColor`
65
+ // override doesn't mix shorthand+longhand and trigger React's style warning.
66
+ borderWidth: 1,
67
+ borderStyle: "solid",
68
+ borderColor: "var(--sk-border, #e5e7eb)",
69
+ backgroundColor: "var(--sk-bg, #ffffff)",
70
+ color: "var(--sk-text-primary, #111827)",
71
+ fontSize: 13.5,
72
+ boxSizing: "border-box",
73
+ outline: "none"
74
+ };
75
+ const renderField = (field) => {
76
+ const value = values[field.slug];
77
+ const strValue = value == null ? "" : String(value);
78
+ const err = errors[field.slug];
79
+ const labelEl = !field.hide_label && field.label ? /* @__PURE__ */ jsxRuntime.jsxs(
80
+ "label",
81
+ {
82
+ htmlFor: `echo-form-${form.id}-${field.slug}`,
83
+ style: { display: "block", fontSize: 12.5, fontWeight: 600, marginBottom: 4, color: "var(--sk-text-secondary, #4b5563)" },
84
+ children: [
85
+ field.label,
86
+ field.is_required ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--sk-error, #ef4444)" }, children: " *" }) : null
87
+ ]
88
+ }
89
+ ) : null;
90
+ const id = `echo-form-${form.id}-${field.slug}`;
91
+ const common = {
92
+ id,
93
+ placeholder: field.placeholder || (field.hide_label ? field.label : void 0),
94
+ "aria-invalid": !!err,
95
+ style: { ...inputBase, ...err ? { borderColor: "var(--sk-error, #ef4444)" } : {} }
96
+ };
97
+ let control;
98
+ switch (field.field_type) {
99
+ case "textarea":
100
+ control = /* @__PURE__ */ jsxRuntime.jsx(
101
+ "textarea",
102
+ {
103
+ ...common,
104
+ rows: 3,
105
+ value: strValue,
106
+ onChange: (e) => setFieldValue(field.slug, e.target.value),
107
+ style: { ...common.style, resize: "vertical" }
108
+ }
109
+ );
110
+ break;
111
+ case "select":
112
+ control = /* @__PURE__ */ jsxRuntime.jsxs("select", { ...common, value: strValue, onChange: (e) => setFieldValue(field.slug, e.target.value), children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: field.placeholder || "Select\u2026" }),
114
+ (field.options || []).map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
115
+ ] });
116
+ break;
117
+ case "radio":
118
+ control = /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", style: { display: "flex", flexDirection: "column", gap: 4 }, children: (field.options || []).map((opt) => /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 13, cursor: "pointer" }, children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(
120
+ "input",
121
+ {
122
+ type: "radio",
123
+ name: id,
124
+ checked: strValue === opt.value,
125
+ onChange: () => setFieldValue(field.slug, opt.value)
126
+ }
127
+ ),
128
+ opt.label
129
+ ] }, opt.value)) });
130
+ break;
131
+ case "checkbox":
132
+ control = /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 13, cursor: "pointer" }, children: [
133
+ /* @__PURE__ */ jsxRuntime.jsx(
134
+ "input",
135
+ {
136
+ type: "checkbox",
137
+ checked: value === true || value === "true",
138
+ onChange: (e) => setFieldValue(field.slug, e.target.checked)
139
+ }
140
+ ),
141
+ field.placeholder || field.label
142
+ ] });
143
+ break;
144
+ default: {
145
+ const typeMap = {
146
+ email: "email",
147
+ phone: "tel",
148
+ tel: "tel",
149
+ number: "number",
150
+ date: "date",
151
+ time: "time",
152
+ datetime: "datetime-local"
153
+ };
154
+ control = /* @__PURE__ */ jsxRuntime.jsx(
155
+ "input",
156
+ {
157
+ ...common,
158
+ type: typeMap[field.field_type] || "text",
159
+ value: strValue,
160
+ onChange: (e) => setFieldValue(field.slug, e.target.value)
161
+ }
162
+ );
163
+ }
164
+ }
165
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
166
+ field.field_type === "checkbox" ? null : labelEl,
167
+ control,
168
+ err ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "4px 0 0", fontSize: 11.5, color: "var(--sk-error, #ef4444)" }, children: err }) : null
169
+ ] }, field.id || field.slug);
170
+ };
171
+ return /* @__PURE__ */ jsxRuntime.jsxs(
172
+ "form",
173
+ {
174
+ style: cardStyle,
175
+ onSubmit: (e) => {
176
+ e.preventDefault();
177
+ void submit();
178
+ },
179
+ children: [
180
+ form.name ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13.5, fontWeight: 600, marginBottom: 8, color: "var(--sk-text-primary, #111827)" }, children: form.name }) : null,
181
+ visibleFields.map((field) => {
182
+ if (field.field_type === "hidden") return null;
183
+ if (DISPLAY_ONLY.includes(field.field_type)) {
184
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 8px", fontSize: 12.5, color: "var(--sk-text-secondary, #4b5563)" }, children: field.label }, field.id || field.slug);
185
+ }
186
+ return renderField(field);
187
+ }),
188
+ submitError ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { margin: "0 0 8px", fontSize: 12, color: "var(--sk-error, #ef4444)" }, children: submitError }) : null,
189
+ /* @__PURE__ */ jsxRuntime.jsx(
190
+ "button",
191
+ {
192
+ type: "submit",
193
+ disabled: isSubmitting,
194
+ style: {
195
+ width: "100%",
196
+ padding: "9px 14px",
197
+ borderRadius: 8,
198
+ border: "none",
199
+ backgroundColor: primaryColor,
200
+ color: "#fff",
201
+ fontSize: 13.5,
202
+ fontWeight: 600,
203
+ cursor: isSubmitting ? "default" : "pointer",
204
+ opacity: isSubmitting ? 0.7 : 1
205
+ },
206
+ children: isSubmitting ? "Sending\u2026" : form.submit_button_text || "Submit"
207
+ }
208
+ )
209
+ ]
210
+ }
211
+ );
212
+ }
213
+ function SuggestActionChip({ action, message, primaryColor, primaryRgb, onSuggest }) {
214
+ return /* @__PURE__ */ jsxRuntime.jsx(
215
+ "button",
216
+ {
217
+ type: "button",
218
+ onClick: () => onSuggest(action, message),
219
+ style: {
220
+ padding: "6px 12px",
221
+ borderRadius: 16,
222
+ border: `1px solid ${primaryColor}`,
223
+ backgroundColor: alpha(primaryRgb, 0.063),
224
+ color: primaryColor,
225
+ fontSize: 13,
226
+ cursor: "pointer"
227
+ },
228
+ children: message
229
+ }
230
+ );
231
+ }
232
+ function EchoUiActions({ actions, primaryColor, primaryRgb, onSuggest }) {
233
+ if (!actions?.length) return null;
234
+ const chips = actions.filter((a) => a?.type === "suggest_action");
235
+ const forms = actions.filter((a) => a?.type === "show_form");
236
+ if (!chips.length && !forms.length) return null;
237
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 8 }, children: [
238
+ forms.map((a, i) => {
239
+ const slug = asString(a.data?.formSlug);
240
+ if (!slug) return null;
241
+ return /* @__PURE__ */ jsxRuntime.jsx(
242
+ InlineManagedForm,
243
+ {
244
+ slug,
245
+ prefill: asRecord(a.data?.prefill),
246
+ primaryColor,
247
+ primaryRgb
248
+ },
249
+ `form-${slug}-${i}`
250
+ );
251
+ }),
252
+ chips.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: chips.map((a, i) => {
253
+ const message = asString(a.data?.message);
254
+ const action = asString(a.data?.action);
255
+ const label = message || action;
256
+ if (!label) return null;
257
+ return /* @__PURE__ */ jsxRuntime.jsx(
258
+ SuggestActionChip,
259
+ {
260
+ action,
261
+ message: label,
262
+ primaryColor,
263
+ primaryRgb,
264
+ onSuggest
265
+ },
266
+ `chip-${i}`
267
+ );
268
+ }) }) : null
269
+ ] });
270
+ }
17
271
  function getApiConfig() {
18
272
  const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
19
273
  const defaultApiUrl = isDev && process.env?.SONOR_API_URL ? process.env.SONOR_API_URL : "https://api.sonor.io";
@@ -71,8 +325,8 @@ function hexToRgbString(hex, fallback = "59, 130, 246") {
71
325
  if (!match) return fallback;
72
326
  return `${parseInt(match[1], 16)}, ${parseInt(match[2], 16)}, ${parseInt(match[3], 16)}`;
73
327
  }
74
- function hexAlpha(rgbVar, alpha, fallbackRgb = "59, 130, 246") {
75
- return `rgba(var(${rgbVar}, ${fallbackRgb}), ${alpha})`;
328
+ function hexAlpha(rgbVar, alpha2, fallbackRgb = "59, 130, 246") {
329
+ return `rgba(var(${rgbVar}, ${fallbackRgb}), ${alpha2})`;
76
330
  }
77
331
  function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, signalUrl: propSignalUrl, suggested_questions: propSuggestedQuestions, assistant_name: propAssistantName, welcome_message: propWelcomeMessage, assistant_avatar: propAssistantAvatar }) {
78
332
  const [resolvedProjectId, setResolvedProjectId] = React2.useState(propProjectId || null);
@@ -268,11 +522,29 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
268
522
  const aiContent = data?.content ?? data?.response ?? data?.message ?? "I'm sorry, I couldn't process that.";
269
523
  const suggestions = data?.suggestions;
270
524
  const offerHandoff = data?.offerHandoff === true || data?.offer_handoff === true;
525
+ const uiActions = Array.isArray(data?.uiActions) ? data.uiActions.filter((a) => a && typeof a === "object" && typeof a.type === "string" && a.data && typeof a.data === "object") : void 0;
271
526
  const newConversationId = data?.conversationId ?? conversationId;
272
- return { content: aiContent, suggestions, offerHandoff, conversationId: newConversationId };
527
+ return { content: aiContent, suggestions, offerHandoff, uiActions, conversationId: newConversationId };
273
528
  },
274
529
  [signalUrl, visitorId, fetchSiteContext]
275
530
  );
531
+ const pushEchoReply = React2.useCallback(
532
+ (reply, probeContent) => {
533
+ setMessages((prev) => [
534
+ ...prev,
535
+ {
536
+ id: `ai-${Date.now()}`,
537
+ role: "assistant",
538
+ content: reply.content,
539
+ timestamp: /* @__PURE__ */ new Date(),
540
+ ...reply.suggestions?.length ? { suggestions: reply.suggestions } : {},
541
+ ...reply.uiActions?.length ? { uiActions: reply.uiActions } : {},
542
+ ...reply.offerHandoff || userRequestedHandoff(probeContent) ? { offerHandoff: true } : {}
543
+ }
544
+ ]);
545
+ },
546
+ []
547
+ );
276
548
  const handleSocketMessage = React2.useCallback((data) => {
277
549
  switch (data.type || data.event) {
278
550
  case "message": {
@@ -475,19 +747,8 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
475
747
  { id: `u-${Date.now()}`, role: "user", content: pending, timestamp: /* @__PURE__ */ new Date() }
476
748
  ]);
477
749
  try {
478
- const { content: aiContent, suggestions, offerHandoff } = await sendToSignalApi(pending, id);
479
- const userWantsHandoff = userRequestedHandoff(pending);
480
- setMessages((prev) => [
481
- ...prev,
482
- {
483
- id: `ai-${Date.now()}`,
484
- role: "assistant",
485
- content: aiContent,
486
- timestamp: /* @__PURE__ */ new Date(),
487
- ...suggestions?.length ? { suggestions } : {},
488
- ...offerHandoff || userWantsHandoff ? { offerHandoff: true } : {}
489
- }
490
- ]);
750
+ const reply = await sendToSignalApi(pending, id);
751
+ pushEchoReply(reply, pending);
491
752
  } catch (err) {
492
753
  console.error("[ChatWidget] Signal API error:", err);
493
754
  setMessages((prev) => [
@@ -530,7 +791,7 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
530
791
  return () => {
531
792
  if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current);
532
793
  };
533
- }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket, sendToSignalApi]);
794
+ }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket, sendToSignalApi, pushEchoReply]);
534
795
  const handleToggle = React2.useCallback(() => {
535
796
  setIsOpen((prev) => !prev);
536
797
  }, []);
@@ -622,19 +883,9 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
622
883
  setIsLoading(true);
623
884
  if (widgetConfig?.signal_enabled && !isHandedOff && hasText) {
624
885
  try {
625
- const { content: aiContent, suggestions, offerHandoff } = await sendToSignalApi(content, sessionId);
886
+ const reply = await sendToSignalApi(content, sessionId);
626
887
  const lastUserContent = messages.filter((m) => m.role === "user").slice(-1)[0]?.content ?? "";
627
- setMessages((prev) => [
628
- ...prev,
629
- {
630
- id: `ai-${Date.now()}`,
631
- role: "assistant",
632
- content: aiContent,
633
- timestamp: /* @__PURE__ */ new Date(),
634
- ...suggestions?.length ? { suggestions } : {},
635
- ...offerHandoff || userRequestedHandoff(lastUserContent) ? { offerHandoff: true } : {}
636
- }
637
- ]);
888
+ pushEchoReply(reply, lastUserContent);
638
889
  } catch (err) {
639
890
  console.error("[ChatWidget] Signal API error:", err);
640
891
  setMessages((prev) => [
@@ -670,7 +921,52 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
670
921
  }
671
922
  }, 3e3);
672
923
  },
673
- [inputValue, isLoading, pendingFiles, uploadWidgetFile, widgetConfig?.signal_enabled, isHandedOff, sendToSignalApi, sessionId]
924
+ [inputValue, isLoading, pendingFiles, uploadWidgetFile, widgetConfig?.signal_enabled, isHandedOff, sendToSignalApi, sessionId, pushEchoReply]
925
+ );
926
+ const sendEchoText = React2.useCallback(
927
+ async (text) => {
928
+ const content = text.trim();
929
+ if (!content || isLoading) return;
930
+ setMessages((prev) => [
931
+ ...prev,
932
+ { id: `user-${Date.now()}`, role: "user", content, timestamp: /* @__PURE__ */ new Date() }
933
+ ]);
934
+ setIsLoading(true);
935
+ try {
936
+ const reply = await sendToSignalApi(content, sessionId);
937
+ pushEchoReply(reply, content);
938
+ } catch (err) {
939
+ console.error("[ChatWidget] Signal API error:", err);
940
+ setMessages((prev) => [
941
+ ...prev,
942
+ {
943
+ id: `err-${Date.now()}`,
944
+ role: "assistant",
945
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
946
+ timestamp: /* @__PURE__ */ new Date(),
947
+ suggestions: ["Talk to a person", "Try again"],
948
+ offerHandoff: true
949
+ }
950
+ ]);
951
+ } finally {
952
+ setIsLoading(false);
953
+ }
954
+ },
955
+ [isLoading, sessionId, sendToSignalApi, pushEchoReply]
956
+ );
957
+ const handleSuggestAction = React2.useCallback(
958
+ (action, message) => {
959
+ const a = (action || "").trim();
960
+ if (a && isNavigable(a)) {
961
+ if (typeof window !== "undefined") {
962
+ if (/^https?:\/\//i.test(a)) window.open(a, "_blank", "noopener,noreferrer");
963
+ else window.location.href = a;
964
+ }
965
+ return;
966
+ }
967
+ if (message?.trim()) void sendEchoText(message);
968
+ },
969
+ [sendEchoText]
674
970
  );
675
971
  const retryFailedSend = React2.useCallback(() => {
676
972
  if (!lastFailedSend || !sessionId) return;
@@ -1148,6 +1444,15 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
1148
1444
  },
1149
1445
  i
1150
1446
  )) }) : null,
1447
+ message.uiActions?.length ? /* @__PURE__ */ jsxRuntime.jsx(
1448
+ EchoUiActions,
1449
+ {
1450
+ actions: message.uiActions,
1451
+ primaryColor,
1452
+ primaryRgb,
1453
+ onSuggest: handleSuggestAction
1454
+ }
1455
+ ) : null,
1151
1456
  message.sendFailed && lastFailedSend && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1152
1457
  "button",
1153
1458
  {
@@ -2022,5 +2327,5 @@ function getDeviceType() {
2022
2327
  exports.ChatWidget = ChatWidget;
2023
2328
  exports.DesignRenderer = DesignRenderer;
2024
2329
  exports.EngageWidget = EngageWidget;
2025
- //# sourceMappingURL=chunk-SLJJJWMV.js.map
2026
- //# sourceMappingURL=chunk-SLJJJWMV.js.map
2330
+ //# sourceMappingURL=chunk-FPU2QPO4.js.map
2331
+ //# sourceMappingURL=chunk-FPU2QPO4.js.map