@ringg/react-native 0.3.0 → 0.4.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/README.md +90 -80
- package/app.plugin.js +73 -0
- package/dist/index.d.mts +267 -2
- package/dist/index.d.ts +267 -2
- package/dist/index.js +825 -412
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +797 -431
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -3
package/dist/index.mjs
CHANGED
|
@@ -14,6 +14,117 @@ var DEFAULT_WIDGET_THEME = {
|
|
|
14
14
|
"borderRadius": "16px",
|
|
15
15
|
"fontFamily": "inherit"
|
|
16
16
|
};
|
|
17
|
+
var WIDGET_PRESET_THEMES = {
|
|
18
|
+
"light": {
|
|
19
|
+
"primaryColor": "#18181b",
|
|
20
|
+
"primaryTextColor": "#ffffff",
|
|
21
|
+
"backgroundColor": "#ffffff",
|
|
22
|
+
"surfaceColor": "#f4f4f5",
|
|
23
|
+
"textColor": "#18181b",
|
|
24
|
+
"mutedTextColor": "#71717a",
|
|
25
|
+
"borderColor": "#e4e4e7"
|
|
26
|
+
},
|
|
27
|
+
"warm": {
|
|
28
|
+
"primaryColor": "#57534e",
|
|
29
|
+
"primaryTextColor": "#ffffff",
|
|
30
|
+
"backgroundColor": "#fefefe",
|
|
31
|
+
"surfaceColor": "#fafaf9",
|
|
32
|
+
"textColor": "#1c1917",
|
|
33
|
+
"mutedTextColor": "#78716c",
|
|
34
|
+
"borderColor": "#e7e5e4"
|
|
35
|
+
},
|
|
36
|
+
"blue": {
|
|
37
|
+
"primaryColor": "#2563eb",
|
|
38
|
+
"primaryTextColor": "#ffffff",
|
|
39
|
+
"backgroundColor": "#fafcff",
|
|
40
|
+
"surfaceColor": "#f0f7ff",
|
|
41
|
+
"textColor": "#1e3a8a",
|
|
42
|
+
"mutedTextColor": "#64748b",
|
|
43
|
+
"borderColor": "#dbeafe"
|
|
44
|
+
},
|
|
45
|
+
"indigo": {
|
|
46
|
+
"primaryColor": "#6366f1",
|
|
47
|
+
"primaryTextColor": "#ffffff",
|
|
48
|
+
"backgroundColor": "#fbfbff",
|
|
49
|
+
"surfaceColor": "#f5f5ff",
|
|
50
|
+
"textColor": "#312e81",
|
|
51
|
+
"mutedTextColor": "#6b7280",
|
|
52
|
+
"borderColor": "#e0e7ff"
|
|
53
|
+
},
|
|
54
|
+
"green": {
|
|
55
|
+
"primaryColor": "#16a34a",
|
|
56
|
+
"primaryTextColor": "#ffffff",
|
|
57
|
+
"backgroundColor": "#fbfefb",
|
|
58
|
+
"surfaceColor": "#f0fdf4",
|
|
59
|
+
"textColor": "#14532d",
|
|
60
|
+
"mutedTextColor": "#6b7280",
|
|
61
|
+
"borderColor": "#dcfce7"
|
|
62
|
+
},
|
|
63
|
+
"orange": {
|
|
64
|
+
"primaryColor": "#ea580c",
|
|
65
|
+
"primaryTextColor": "#ffffff",
|
|
66
|
+
"backgroundColor": "#fffdfb",
|
|
67
|
+
"surfaceColor": "#fff7ed",
|
|
68
|
+
"textColor": "#7c2d12",
|
|
69
|
+
"mutedTextColor": "#78716c",
|
|
70
|
+
"borderColor": "#ffedd5"
|
|
71
|
+
},
|
|
72
|
+
"dark": {
|
|
73
|
+
"primaryColor": "#fafafa",
|
|
74
|
+
"primaryTextColor": "#18181b",
|
|
75
|
+
"backgroundColor": "#18181b",
|
|
76
|
+
"surfaceColor": "#27272a",
|
|
77
|
+
"textColor": "#fafafa",
|
|
78
|
+
"mutedTextColor": "#a1a1aa",
|
|
79
|
+
"borderColor": "#3f3f46"
|
|
80
|
+
},
|
|
81
|
+
"sunset": {
|
|
82
|
+
"primaryColor": "linear-gradient(135deg, #f97316, #ef4444)",
|
|
83
|
+
"primaryTextColor": "#ffffff",
|
|
84
|
+
"backgroundColor": "#fffbf7",
|
|
85
|
+
"surfaceColor": "#fff3e8",
|
|
86
|
+
"textColor": "#7c2d12",
|
|
87
|
+
"mutedTextColor": "#9a3412",
|
|
88
|
+
"borderColor": "#fed7aa"
|
|
89
|
+
},
|
|
90
|
+
"ocean": {
|
|
91
|
+
"primaryColor": "linear-gradient(135deg, #667eea, #764ba2)",
|
|
92
|
+
"primaryTextColor": "#ffffff",
|
|
93
|
+
"backgroundColor": "#faf9ff",
|
|
94
|
+
"surfaceColor": "#f0edff",
|
|
95
|
+
"textColor": "#312e81",
|
|
96
|
+
"mutedTextColor": "#6366f1",
|
|
97
|
+
"borderColor": "#ddd6fe"
|
|
98
|
+
},
|
|
99
|
+
"emerald": {
|
|
100
|
+
"primaryColor": "linear-gradient(135deg, #10b981, #0ea5e9)",
|
|
101
|
+
"primaryTextColor": "#ffffff",
|
|
102
|
+
"backgroundColor": "#f7fdfb",
|
|
103
|
+
"surfaceColor": "#ecfdf5",
|
|
104
|
+
"textColor": "#064e3b",
|
|
105
|
+
"mutedTextColor": "#047857",
|
|
106
|
+
"borderColor": "#a7f3d0"
|
|
107
|
+
},
|
|
108
|
+
"midnight": {
|
|
109
|
+
"primaryColor": "linear-gradient(135deg, #6366f1, #8b5cf6)",
|
|
110
|
+
"primaryTextColor": "#ffffff",
|
|
111
|
+
"backgroundColor": "#18181b",
|
|
112
|
+
"surfaceColor": "#27272a",
|
|
113
|
+
"textColor": "#fafafa",
|
|
114
|
+
"mutedTextColor": "#a1a1aa",
|
|
115
|
+
"borderColor": "#3f3f46"
|
|
116
|
+
},
|
|
117
|
+
"policybazaar": {
|
|
118
|
+
"primaryColor": "linear-gradient(88deg, #9403fd, #00adfe)",
|
|
119
|
+
"primaryTextColor": "#ffffff",
|
|
120
|
+
"backgroundColor": "#ffffff",
|
|
121
|
+
"surfaceColor": "#f4f2ff",
|
|
122
|
+
"agentBubbleColor": "linear-gradient(107deg, #f0fff9, #f7eeff)",
|
|
123
|
+
"textColor": "#18181b",
|
|
124
|
+
"mutedTextColor": "#64748b",
|
|
125
|
+
"borderColor": "#e6e2ff"
|
|
126
|
+
}
|
|
127
|
+
};
|
|
17
128
|
var TIMING = {
|
|
18
129
|
"chatWidgetGraceMs": 800,
|
|
19
130
|
"chatWidgetAfterTextMs": 120,
|
|
@@ -34,6 +145,47 @@ var WIDGET_DEFAULTS = {
|
|
|
34
145
|
};
|
|
35
146
|
|
|
36
147
|
// ../core/dist/ports/event-bus.js
|
|
148
|
+
function createDomEventBus() {
|
|
149
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
150
|
+
return {
|
|
151
|
+
emit(event, payload) {
|
|
152
|
+
if (typeof window !== "undefined") {
|
|
153
|
+
window.dispatchEvent(new CustomEvent(event, { detail: payload }));
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
on(event, handler) {
|
|
157
|
+
if (typeof window === "undefined")
|
|
158
|
+
return () => {
|
|
159
|
+
};
|
|
160
|
+
const wrapped = ((e) => handler(e.detail));
|
|
161
|
+
let set = listeners.get(event);
|
|
162
|
+
if (!set) {
|
|
163
|
+
set = /* @__PURE__ */ new Set();
|
|
164
|
+
listeners.set(event, set);
|
|
165
|
+
}
|
|
166
|
+
set.add(wrapped);
|
|
167
|
+
window.addEventListener(event, wrapped);
|
|
168
|
+
return () => {
|
|
169
|
+
window.removeEventListener(event, wrapped);
|
|
170
|
+
listeners.get(event)?.delete(wrapped);
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
off(event) {
|
|
174
|
+
const remove = (name, handlers) => {
|
|
175
|
+
handlers.forEach((h) => window.removeEventListener(name, h));
|
|
176
|
+
};
|
|
177
|
+
if (event) {
|
|
178
|
+
const handlers = listeners.get(event);
|
|
179
|
+
if (handlers)
|
|
180
|
+
remove(event, handlers);
|
|
181
|
+
listeners.delete(event);
|
|
182
|
+
} else {
|
|
183
|
+
listeners.forEach((handlers, name) => remove(name, handlers));
|
|
184
|
+
listeners.clear();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
37
189
|
function createCallbackEventBus() {
|
|
38
190
|
const listeners = /* @__PURE__ */ new Map();
|
|
39
191
|
return {
|
|
@@ -88,6 +240,13 @@ var silentNotificationPlayer = {
|
|
|
88
240
|
};
|
|
89
241
|
|
|
90
242
|
// ../core/dist/api/client.js
|
|
243
|
+
function createStaticUrlResolver(urls) {
|
|
244
|
+
return {
|
|
245
|
+
resolve(mode) {
|
|
246
|
+
return urls[mode];
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
91
250
|
function createRinggApiClient(config) {
|
|
92
251
|
const { backendUrl, livekitUrl } = config.urlResolver.resolve(config.mode);
|
|
93
252
|
const authHeaders = config.authorization ? { Authorization: config.authorization } : config.xApiKey ? { "X-API-KEY": config.xApiKey } : {};
|
|
@@ -212,11 +371,47 @@ function getDominantColor(color) {
|
|
|
212
371
|
return color;
|
|
213
372
|
return extractColorsFromGradient(color)[0] ?? color;
|
|
214
373
|
}
|
|
374
|
+
function colorToBackground(color) {
|
|
375
|
+
if (isGradient(color)) {
|
|
376
|
+
return { background: color };
|
|
377
|
+
}
|
|
378
|
+
return { backgroundColor: color };
|
|
379
|
+
}
|
|
380
|
+
function colorToSolid(color) {
|
|
381
|
+
return getDominantColor(color);
|
|
382
|
+
}
|
|
383
|
+
function colorToBorder(color) {
|
|
384
|
+
if (isGradient(color)) {
|
|
385
|
+
return {
|
|
386
|
+
borderImage: `${color} 1`,
|
|
387
|
+
borderStyle: "solid"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return { borderColor: color };
|
|
391
|
+
}
|
|
215
392
|
|
|
216
393
|
// ../core/dist/theme/theme-engine.js
|
|
217
394
|
function mergeWidgetTheme(theme) {
|
|
218
395
|
return { ...DEFAULT_WIDGET_THEME, ...theme };
|
|
219
396
|
}
|
|
397
|
+
function mergeComponentTheme(componentTheme, widgetTheme) {
|
|
398
|
+
const base = widgetTheme ?? DEFAULT_WIDGET_THEME;
|
|
399
|
+
return {
|
|
400
|
+
primaryColor: base.primaryColor,
|
|
401
|
+
primaryTextColor: base.primaryTextColor,
|
|
402
|
+
backgroundColor: base.backgroundColor,
|
|
403
|
+
surfaceColor: base.surfaceColor,
|
|
404
|
+
textColor: base.textColor,
|
|
405
|
+
mutedTextColor: base.mutedTextColor,
|
|
406
|
+
borderColor: base.borderColor,
|
|
407
|
+
errorColor: base.errorColor,
|
|
408
|
+
successColor: base.successColor,
|
|
409
|
+
fontFamily: base.fontFamily,
|
|
410
|
+
borderRadius: base.borderRadius,
|
|
411
|
+
buttonStyle: base.buttonStyle,
|
|
412
|
+
...componentTheme
|
|
413
|
+
};
|
|
414
|
+
}
|
|
220
415
|
function getButtonRadius(style) {
|
|
221
416
|
switch (style) {
|
|
222
417
|
case "pill":
|
|
@@ -228,6 +423,25 @@ function getButtonRadius(style) {
|
|
|
228
423
|
return "12px";
|
|
229
424
|
}
|
|
230
425
|
}
|
|
426
|
+
function getLuminance(hexColor) {
|
|
427
|
+
const solid = getDominantColor(hexColor);
|
|
428
|
+
const hex = solid.replace("#", "");
|
|
429
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
430
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
431
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
432
|
+
const toLinear = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
433
|
+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
434
|
+
}
|
|
435
|
+
function isLightColor(hexColor) {
|
|
436
|
+
try {
|
|
437
|
+
return getLuminance(hexColor) > 0.5;
|
|
438
|
+
} catch {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function getContrastingTextColor(bgColor) {
|
|
443
|
+
return isLightColor(bgColor) ? "#212121" : "#ffffff";
|
|
444
|
+
}
|
|
231
445
|
|
|
232
446
|
// ../core/dist/rpc/formatter.js
|
|
233
447
|
function transformRpcToComponent(componentName, rpcPayload) {
|
|
@@ -411,6 +625,68 @@ function formatDynamicData(payload) {
|
|
|
411
625
|
};
|
|
412
626
|
}
|
|
413
627
|
|
|
628
|
+
// ../core/dist/components/helpers.js
|
|
629
|
+
function groupSlotsByDate(slots) {
|
|
630
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
631
|
+
slots.forEach((slot) => {
|
|
632
|
+
const dateKey = new Date(slot.datetime).toISOString().split("T")[0];
|
|
633
|
+
if (!grouped.has(dateKey)) {
|
|
634
|
+
grouped.set(dateKey, []);
|
|
635
|
+
}
|
|
636
|
+
grouped.get(dateKey).push(slot);
|
|
637
|
+
});
|
|
638
|
+
return Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([dateKey, dateSlots]) => ({
|
|
639
|
+
date: dateKey,
|
|
640
|
+
dateLabel: new Date(dateKey).toLocaleDateString("en-US", {
|
|
641
|
+
weekday: "short",
|
|
642
|
+
month: "short",
|
|
643
|
+
day: "numeric"
|
|
644
|
+
}),
|
|
645
|
+
slots: dateSlots.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime())
|
|
646
|
+
}));
|
|
647
|
+
}
|
|
648
|
+
function formatSlotTime(datetime, timezone) {
|
|
649
|
+
return new Date(datetime).toLocaleTimeString("en-US", {
|
|
650
|
+
hour: "numeric",
|
|
651
|
+
minute: "2-digit",
|
|
652
|
+
hour12: true,
|
|
653
|
+
timeZone: timezone
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function formatSlotDate(datetime, timezone) {
|
|
657
|
+
return new Date(datetime).toLocaleDateString("en-US", {
|
|
658
|
+
weekday: "long",
|
|
659
|
+
month: "long",
|
|
660
|
+
day: "numeric",
|
|
661
|
+
timeZone: timezone
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function buildPayload(template, values) {
|
|
665
|
+
const result = {};
|
|
666
|
+
for (const [key, raw] of Object.entries(template)) {
|
|
667
|
+
result[key] = raw.replace(/\{\{(\w+)\}\}/g, (_, name) => values[name] ?? "");
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
}
|
|
671
|
+
function isCalendarBooking(payload) {
|
|
672
|
+
return payload.component_type === "calendar_booking";
|
|
673
|
+
}
|
|
674
|
+
function isForm(payload) {
|
|
675
|
+
return payload.component_type === "form";
|
|
676
|
+
}
|
|
677
|
+
function isButtons(payload) {
|
|
678
|
+
return payload.component_type === "buttons";
|
|
679
|
+
}
|
|
680
|
+
function isConfirmation(payload) {
|
|
681
|
+
return payload.component_type === "confirmation";
|
|
682
|
+
}
|
|
683
|
+
function isInteractiveFlow(payload) {
|
|
684
|
+
return payload.component_type === "interactive_flow";
|
|
685
|
+
}
|
|
686
|
+
function isBlocks(payload) {
|
|
687
|
+
return payload.component_type === "blocks";
|
|
688
|
+
}
|
|
689
|
+
|
|
414
690
|
// ../core/dist/stores/store.js
|
|
415
691
|
function createStore(build) {
|
|
416
692
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1200,7 +1476,7 @@ var useRinggSlashCommands = (controller) => useStoreSnapshot(controller.slashCom
|
|
|
1200
1476
|
|
|
1201
1477
|
// src/RinggWidget.tsx
|
|
1202
1478
|
import { Fragment as Fragment7, useEffect as useEffect16, useMemo as useMemo11, useRef as useRef15, useState as useState16 } from "react";
|
|
1203
|
-
import { Animated as Animated15, Image as Image3, KeyboardAvoidingView as KeyboardAvoidingView3, Platform as
|
|
1479
|
+
import { Animated as Animated15, Image as Image3, KeyboardAvoidingView as KeyboardAvoidingView3, Platform as Platform6, ScrollView as ScrollView5, StyleSheet as StyleSheet24, Text as Text18, View as View21, useWindowDimensions } from "react-native";
|
|
1204
1480
|
import { Minus, X } from "lucide-react-native";
|
|
1205
1481
|
|
|
1206
1482
|
// src/components/action-button.tsx
|
|
@@ -1808,7 +2084,7 @@ import { Animated as Animated7, Linking, StyleSheet as StyleSheet12, Text as Tex
|
|
|
1808
2084
|
import { Check as Check3, ChevronLeft } from "lucide-react-native";
|
|
1809
2085
|
|
|
1810
2086
|
// src/lib/component-types.ts
|
|
1811
|
-
function
|
|
2087
|
+
function groupSlotsByDate2(slots) {
|
|
1812
2088
|
const grouped = /* @__PURE__ */ new Map();
|
|
1813
2089
|
slots.forEach((slot) => {
|
|
1814
2090
|
const date = new Date(slot.datetime);
|
|
@@ -1827,7 +2103,7 @@ function groupSlotsByDate(slots) {
|
|
|
1827
2103
|
};
|
|
1828
2104
|
});
|
|
1829
2105
|
}
|
|
1830
|
-
function
|
|
2106
|
+
function formatSlotTime2(datetime, timezone) {
|
|
1831
2107
|
const date = new Date(datetime);
|
|
1832
2108
|
return date.toLocaleTimeString("en-US", {
|
|
1833
2109
|
hour: "numeric",
|
|
@@ -1836,7 +2112,7 @@ function formatSlotTime(datetime, timezone) {
|
|
|
1836
2112
|
timeZone: timezone
|
|
1837
2113
|
});
|
|
1838
2114
|
}
|
|
1839
|
-
function
|
|
2115
|
+
function formatSlotDate2(datetime, timezone) {
|
|
1840
2116
|
const date = new Date(datetime);
|
|
1841
2117
|
return date.toLocaleDateString("en-US", {
|
|
1842
2118
|
weekday: "short",
|
|
@@ -1845,7 +2121,7 @@ function formatSlotDate(datetime, timezone) {
|
|
|
1845
2121
|
timeZone: timezone
|
|
1846
2122
|
});
|
|
1847
2123
|
}
|
|
1848
|
-
function
|
|
2124
|
+
function buildPayload2(template, values) {
|
|
1849
2125
|
const result = {};
|
|
1850
2126
|
for (const [key, value] of Object.entries(template)) {
|
|
1851
2127
|
if (typeof value === "string" && value.startsWith("{") && value.endsWith("}")) {
|
|
@@ -1857,13 +2133,13 @@ function buildPayload(template, values) {
|
|
|
1857
2133
|
}
|
|
1858
2134
|
return result;
|
|
1859
2135
|
}
|
|
1860
|
-
function
|
|
2136
|
+
function isButtons2(payload) {
|
|
1861
2137
|
return payload.component_type === "buttons";
|
|
1862
2138
|
}
|
|
1863
|
-
function
|
|
2139
|
+
function isInteractiveFlow2(payload) {
|
|
1864
2140
|
return payload.component_type === "interactive_flow";
|
|
1865
2141
|
}
|
|
1866
|
-
function
|
|
2142
|
+
function isBlocks2(payload) {
|
|
1867
2143
|
return payload.component_type === "blocks";
|
|
1868
2144
|
}
|
|
1869
2145
|
|
|
@@ -2075,7 +2351,7 @@ var CalendarStep = ({ payload, isProcessing, onComplete }) => {
|
|
|
2075
2351
|
const buttonRadius = getButtonRadius2(theme.buttonStyle);
|
|
2076
2352
|
const fontFamily = resolveFontFamily(theme.fontFamily);
|
|
2077
2353
|
const { data } = payload;
|
|
2078
|
-
const groupedSlots = useMemo4(() =>
|
|
2354
|
+
const groupedSlots = useMemo4(() => groupSlotsByDate2(data.available_slots), [data.available_slots]);
|
|
2079
2355
|
const [selectedDate, setSelectedDate] = useState4(groupedSlots[0]?.date || "");
|
|
2080
2356
|
const [selectedSlot, setSelectedSlot] = useState4(null);
|
|
2081
2357
|
const selectedDateGroup = groupedSlots.find((g) => g.date === selectedDate);
|
|
@@ -2139,7 +2415,7 @@ var CalendarStep = ({ payload, isProcessing, onComplete }) => {
|
|
|
2139
2415
|
{
|
|
2140
2416
|
color: isSelected ? theme.primaryColor : theme.backgroundColor,
|
|
2141
2417
|
style: [styles9.slot, { borderRadius: buttonRadius, borderColor: isSelected ? solidColor(theme.primaryColor) : theme.borderColor }, isSelected ? styles9.selectedShadow : null],
|
|
2142
|
-
children: /* @__PURE__ */ jsx11(Text5, { style: [styles9.slotLabel, { color: isSelected ? theme.primaryTextColor : theme.textColor, fontFamily }], numberOfLines: 1, children:
|
|
2418
|
+
children: /* @__PURE__ */ jsx11(Text5, { style: [styles9.slotLabel, { color: isSelected ? theme.primaryTextColor : theme.textColor, fontFamily }], numberOfLines: 1, children: formatSlotTime2(slot.datetime, data.timezone) })
|
|
2143
2419
|
}
|
|
2144
2420
|
) }) }, slot.id);
|
|
2145
2421
|
}) })
|
|
@@ -2681,8 +2957,8 @@ var EnterFade = ({ translateX = 0, translateY = 0, durationMs = ENTER_DURATION_M
|
|
|
2681
2957
|
);
|
|
2682
2958
|
};
|
|
2683
2959
|
var InteractiveFlow = ({ initialComponent, onApiCall, onComplete, onSendResponse, onSelectionDisplayed, quickReplyAlign = "end" }) => {
|
|
2684
|
-
const isMultiStepFlow =
|
|
2685
|
-
const isFreeButtonGroup = !isMultiStepFlow &&
|
|
2960
|
+
const isMultiStepFlow = isInteractiveFlow2(initialComponent);
|
|
2961
|
+
const isFreeButtonGroup = !isMultiStepFlow && isButtons2(initialComponent) && initialComponent.data.presentation === "free";
|
|
2686
2962
|
const widgetTheme = useWidgetTheme();
|
|
2687
2963
|
const theme = useMemo6(
|
|
2688
2964
|
() => mergeTheme({
|
|
@@ -2728,7 +3004,7 @@ var InteractiveFlow = ({ initialComponent, onApiCall, onComplete, onSendResponse
|
|
|
2728
3004
|
try {
|
|
2729
3005
|
const isLastStep = currentStepIndex === totalSteps - 1;
|
|
2730
3006
|
const selectedButtonId = typeof stepData.selected_button === "string" ? stepData.selected_button : null;
|
|
2731
|
-
const buttonsComponent = currentComponent !== void 0 &&
|
|
3007
|
+
const buttonsComponent = currentComponent !== void 0 && isButtons2(currentComponent) ? currentComponent : null;
|
|
2732
3008
|
const submittedButton = buttonsComponent && selectedButtonId ? buttonsComponent.data.buttons.find((button) => button.id === selectedButtonId) ?? { id: selectedButtonId, label: selectedButtonId, action: { type: "trigger_component" } } : null;
|
|
2733
3009
|
const shouldShowSubmittedSelection = buttonsComponent?.data.completionDisplay === "selected_item" && !!submittedButton;
|
|
2734
3010
|
if (onSendResponse) {
|
|
@@ -2767,7 +3043,7 @@ var InteractiveFlow = ({ initialComponent, onApiCall, onComplete, onSendResponse
|
|
|
2767
3043
|
for (const [key, value] of Object.entries(newAllData)) {
|
|
2768
3044
|
stringifiedData[key] = Array.isArray(value) ? JSON.stringify(value) : String(value);
|
|
2769
3045
|
}
|
|
2770
|
-
const apiPayload =
|
|
3046
|
+
const apiPayload = buildPayload2(on_complete.payload, {
|
|
2771
3047
|
...stringifiedData,
|
|
2772
3048
|
all_data: JSON.stringify(newAllData)
|
|
2773
3049
|
});
|
|
@@ -2846,8 +3122,8 @@ var InteractiveFlow = ({ initialComponent, onApiCall, onComplete, onSendResponse
|
|
|
2846
3122
|
handleStepComplete({
|
|
2847
3123
|
slot_id: slotId,
|
|
2848
3124
|
slot_datetime: slotDatetime,
|
|
2849
|
-
slot_time:
|
|
2850
|
-
slot_date:
|
|
3125
|
+
slot_time: formatSlotTime2(slotDatetime, timezone),
|
|
3126
|
+
slot_date: formatSlotDate2(slotDatetime, timezone)
|
|
2851
3127
|
});
|
|
2852
3128
|
}
|
|
2853
3129
|
}
|
|
@@ -4182,7 +4458,7 @@ var FeedbackScreen = ({ callMode, isSubmitting, feedbackScreenConfig, messages,
|
|
|
4182
4458
|
transcript.map((message, index) => {
|
|
4183
4459
|
if (message.componentType && message.componentData) {
|
|
4184
4460
|
const componentData = message.componentData;
|
|
4185
|
-
if (
|
|
4461
|
+
if (isBlocks2(componentData)) {
|
|
4186
4462
|
return /* @__PURE__ */ jsx19(View14, { testID: ringgId("blocks-widget"), style: styles16.replayed, children: /* @__PURE__ */ jsx19(blocks_step_default, { config: componentData.data, disabled: true, onAction: async () => {
|
|
4187
4463
|
} }) }, `c_${message.timestamp}__${index}`);
|
|
4188
4464
|
}
|
|
@@ -4878,374 +5154,87 @@ var renderTextWithLinks = (text, links, linkStyle) => {
|
|
|
4878
5154
|
return parts;
|
|
4879
5155
|
};
|
|
4880
5156
|
|
|
4881
|
-
// src/
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
var isFreeButtonsMessage = (message) => {
|
|
4896
|
-
const componentData = message?.componentData;
|
|
4897
|
-
return !!componentData && isButtons(componentData) && componentData.data.presentation === "free";
|
|
5157
|
+
// src/config/default-urls.ts
|
|
5158
|
+
var DEFAULT_ENVIRONMENT_URLS = {
|
|
5159
|
+
dev: {
|
|
5160
|
+
backendUrl: "https://calling-dev.ringg.ai/ca/api/v0",
|
|
5161
|
+
livekitUrl: "wss://ringg-ai-dev-92tubwpz.livekit.cloud"
|
|
5162
|
+
},
|
|
5163
|
+
stage: {
|
|
5164
|
+
backendUrl: "https://stage-api.ringg.ai/ca/api/v0",
|
|
5165
|
+
livekitUrl: "wss://mercury.webrtc-stage.ringg.ai"
|
|
5166
|
+
},
|
|
5167
|
+
prod: {
|
|
5168
|
+
backendUrl: "https://prod-api.ringg.ai/ca/api/v0",
|
|
5169
|
+
livekitUrl: "wss://mercury.webrtc.ringg.ai"
|
|
5170
|
+
}
|
|
4898
5171
|
};
|
|
4899
|
-
var
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
const
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
accessibilityLabel: isSessionLive ? "End call" : "Close widget",
|
|
4966
|
-
onPress: () => isSessionLive ? setAlertEndCall(true) : showFeedback ? handleFeedbackSkip() : controller.minimizeWidget(),
|
|
4967
|
-
style: [styles24.chromeButton, styles24.closeButton],
|
|
4968
|
-
children: /* @__PURE__ */ jsx28(X, { size: 16, color: solidColor(theme.mutedTextColor) })
|
|
4969
|
-
}
|
|
4970
|
-
) : null
|
|
4971
|
-
] });
|
|
4972
|
-
const transcript = /* @__PURE__ */ jsxs20(
|
|
4973
|
-
ScrollView5,
|
|
4974
|
-
{
|
|
4975
|
-
testID: ringgId("transcript"),
|
|
4976
|
-
ref: transcriptRef,
|
|
4977
|
-
style: styles24.transcript,
|
|
4978
|
-
contentContainerStyle: [styles24.transcriptList, !showVoiceAnimation && styles24.transcriptTopClearance, !isAudioMode && styles24.transcriptBottomClearance],
|
|
4979
|
-
keyboardShouldPersistTaps: "handled",
|
|
4980
|
-
showsVerticalScrollIndicator: false,
|
|
4981
|
-
children: [
|
|
4982
|
-
sortedMessages.map((segment, index) => {
|
|
4983
|
-
if (segment.kind === "system") return /* @__PURE__ */ jsx28(system_log_default, { label: segment.message, meta: segment.systemMeta, level: segment.systemLevel }, `sys_${segment.timestamp}_${index}`);
|
|
4984
|
-
const previousSegment = sortedMessages[index - 1];
|
|
4985
|
-
const nextSegment = sortedMessages[index + 1];
|
|
4986
|
-
const attachFreeButtonsToPrevious = isFreeButtonsMessage(segment) && isAttachableAgentMessage(previousSegment);
|
|
4987
|
-
const skipStandaloneRender = isAttachableAgentMessage(segment) && isFreeButtonsMessage(nextSegment);
|
|
4988
|
-
if (skipStandaloneRender) return null;
|
|
4989
|
-
if (attachFreeButtonsToPrevious && previousSegment && segment.componentData) {
|
|
4990
|
-
const componentData = segment.componentData;
|
|
4991
|
-
const isCompletedSelectedItem = componentData.component_type === "buttons" && componentData.data.completionDisplay === "selected_item" && completedFlowIds.has(componentData.component_id);
|
|
4992
|
-
if (isCompletedSelectedItem) return /* @__PURE__ */ jsx28(message_item_default, { itemIndex: index, message: previousSegment }, `message_with_buttons_${segment.componentData.component_id}_${index}`);
|
|
4993
|
-
return /* @__PURE__ */ jsxs20(Fragment7, { children: [
|
|
4994
|
-
/* @__PURE__ */ jsx28(message_item_default, { itemIndex: index, message: previousSegment }),
|
|
4995
|
-
/* @__PURE__ */ jsx28(View21, { testID: ringgId("attached-buttons"), style: styles24.fullWidth, children: /* @__PURE__ */ jsx28(
|
|
4996
|
-
interactive_flow_default,
|
|
4997
|
-
{
|
|
4998
|
-
initialComponent: segment.componentData,
|
|
4999
|
-
onApiCall: callComponentApi,
|
|
5000
|
-
onComplete: () => handleFlowComplete(componentData.component_id),
|
|
5001
|
-
onSendResponse: sendComponentResponse,
|
|
5002
|
-
onSelectionDisplayed: handleSelectionDisplayed,
|
|
5003
|
-
quickReplyAlign: isAudioMode ? "center" : "end"
|
|
5004
|
-
}
|
|
5005
|
-
) })
|
|
5006
|
-
] }, `message_with_buttons_${segment.componentData.component_id}_${index}`);
|
|
5007
|
-
}
|
|
5008
|
-
if (segment.componentType === "blocks" && segment.componentData) {
|
|
5009
|
-
const blocksPayload = segment.componentData;
|
|
5010
|
-
if (isBlocks(blocksPayload)) {
|
|
5011
|
-
const blocksConfig = blocksPayload.data;
|
|
5012
|
-
const blockId = blocksPayload.component_id;
|
|
5013
|
-
return /* @__PURE__ */ jsx28(View21, { testID: ringgId("blocks-widget"), style: styles24.fullWidth, children: /* @__PURE__ */ jsx28(
|
|
5014
|
-
blocks_step_default,
|
|
5015
|
-
{
|
|
5016
|
-
config: blocksConfig,
|
|
5017
|
-
disabled: completedFlowIds.has(blockId),
|
|
5018
|
-
onAction: (action) => {
|
|
5019
|
-
handleUserMessageSent();
|
|
5020
|
-
handleFlowComplete(blockId);
|
|
5021
|
-
const selection = typeof action.value === "string" || typeof action.value === "number" ? String(action.value) : action.label;
|
|
5022
|
-
if (selection) handleSelectionDisplayed(blockId, selection);
|
|
5023
|
-
return controller.sendBlocksAction(blocksConfig.tool_id, blockId, { action_id: action.action_id, value: action.value, values: action.values });
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
|
-
) }, `blocks_${blockId}`);
|
|
5027
|
-
}
|
|
5028
|
-
}
|
|
5029
|
-
if (segment.componentType && segment.componentData) {
|
|
5030
|
-
const componentData = segment.componentData;
|
|
5031
|
-
const isCompletedSelectedItem = componentData.component_type === "buttons" && componentData.data.completionDisplay === "selected_item" && completedFlowIds.has(componentData.component_id);
|
|
5032
|
-
if (isCompletedSelectedItem) return null;
|
|
5033
|
-
return /* @__PURE__ */ jsx28(
|
|
5034
|
-
interactive_flow_default,
|
|
5035
|
-
{
|
|
5036
|
-
initialComponent: segment.componentData,
|
|
5037
|
-
onApiCall: callComponentApi,
|
|
5038
|
-
onComplete: () => handleFlowComplete(componentData.component_id),
|
|
5039
|
-
onSendResponse: sendComponentResponse,
|
|
5040
|
-
onSelectionDisplayed: handleSelectionDisplayed,
|
|
5041
|
-
quickReplyAlign: isAudioMode ? "center" : "end"
|
|
5042
|
-
},
|
|
5043
|
-
`flow_${segment.componentData.component_id}_${index}`
|
|
5044
|
-
);
|
|
5045
|
-
}
|
|
5046
|
-
return /* @__PURE__ */ jsx28(message_item_default, { itemIndex: index, message: segment }, `${segment.timestamp}__${index}`);
|
|
5047
|
-
}),
|
|
5048
|
-
isAgentTyping ? /* @__PURE__ */ jsx28(typing_indicator_default, { words: typingWords }) : null
|
|
5049
|
-
]
|
|
5050
|
-
}
|
|
5051
|
-
);
|
|
5052
|
-
const controls = /* @__PURE__ */ jsx28(
|
|
5053
|
-
call_controls_default,
|
|
5054
|
-
{
|
|
5055
|
-
buttonConfig: buttons,
|
|
5056
|
-
callMode,
|
|
5057
|
-
isCalling: isSessionLive,
|
|
5058
|
-
enabledSlashCommands: mergedSlashCommands,
|
|
5059
|
-
micEnabled: microphone?.enabled ?? true,
|
|
5060
|
-
onToggleMic: microphone?.toggle,
|
|
5061
|
-
onSlashCommand: sendSlashCommand,
|
|
5062
|
-
onUserMessage: handleUserMessageSent,
|
|
5063
|
-
onCallEnd: handleCallEnd,
|
|
5064
|
-
onSendMessage: (message) => controller.sendMessage(message)
|
|
5065
|
-
}
|
|
5066
|
-
);
|
|
5067
|
-
return /* @__PURE__ */ jsxs20(
|
|
5068
|
-
View21,
|
|
5069
|
-
{
|
|
5070
|
-
testID: ringgId("widget-root"),
|
|
5071
|
-
style: [styles24.panel, { width, height, borderRadius: panelRadius, backgroundColor: solidColor(theme.backgroundColor), borderColor: solidColor(theme.borderColor) }],
|
|
5072
|
-
children: [
|
|
5073
|
-
chrome,
|
|
5074
|
-
/* @__PURE__ */ jsx28(
|
|
5075
|
-
ConfirmDialog,
|
|
5076
|
-
{
|
|
5077
|
-
open: alertEndCall,
|
|
5078
|
-
title: `Are you sure you want to end ${callMode === "text" ? "chat" : "call"}?`,
|
|
5079
|
-
description: "Your current conversation will be closed and cannot be accessed later.",
|
|
5080
|
-
confirmLabel: `End ${callMode === "text" ? "Chat" : "Call"}`,
|
|
5081
|
-
cancelLabel: "Cancel",
|
|
5082
|
-
onConfirm: handleCallEnd,
|
|
5083
|
-
onCancel: () => setAlertEndCall(false)
|
|
5084
|
-
}
|
|
5085
|
-
),
|
|
5086
|
-
showFeedback ? /* @__PURE__ */ jsx28(
|
|
5087
|
-
feedback_screen_default,
|
|
5088
|
-
{
|
|
5089
|
-
callMode,
|
|
5090
|
-
isSubmitting: isLoading,
|
|
5091
|
-
feedbackScreenConfig: feedbackScreen,
|
|
5092
|
-
messages,
|
|
5093
|
-
title,
|
|
5094
|
-
logoUrl,
|
|
5095
|
-
logoStyles,
|
|
5096
|
-
onSubmit: handleFeedbackSubmit,
|
|
5097
|
-
onSkip: handleFeedbackSkip
|
|
5098
|
-
}
|
|
5099
|
-
) : /* @__PURE__ */ jsxs20(View21, { testID: ringgId("widget-body"), style: [styles24.body, isSessionLive ? styles24.bodyLive : styles24.bodyIdle], children: [
|
|
5100
|
-
/* @__PURE__ */ jsx28(header_default, { isCalling: isSessionLive, isLoading, title, description, logoUrl, logoStyles }),
|
|
5101
|
-
showVoiceAnimation ? /* @__PURE__ */ jsx28(View21, { testID: ringgId("voice-animation-wrapper"), style: [styles24.voiceWrapper, showVoiceTranscript && messages.length > 0 ? styles24.voiceWrapperCompact : styles24.voiceWrapperFull], children: /* @__PURE__ */ jsx28(VoiceAnimationLeaf, { room }) }) : null,
|
|
5102
|
-
isSessionLive && !showVoiceAnimation && (messages.length === 0 && !isAgentTyping || !showVoiceTranscript) ? /* @__PURE__ */ jsx28(View21, { testID: ringgId("body-spacer"), style: styles24.bodySpacer }) : null,
|
|
5103
|
-
(messages.length > 0 || isAgentTyping) && isSessionLive && showVoiceTranscript ? transcript : null,
|
|
5104
|
-
isSessionLive && callMode === "text" ? (
|
|
5105
|
-
// Transparent bottom bar: it only positions the composer over the
|
|
5106
|
-
// transcript, so the gutters stay clear while messages scroll under.
|
|
5107
|
-
/* @__PURE__ */ jsxs20(View21, { testID: ringgId("controls-bar"), style: styles24.controlsBar, children: [
|
|
5108
|
-
error.hasError ? /* @__PURE__ */ jsx28(Text18, { testID: ringgId("error-message"), style: [styles24.error, { color: solidColor(theme.errorColor), fontFamily }], children: error.message }) : null,
|
|
5109
|
-
controls
|
|
5110
|
-
] })
|
|
5111
|
-
) : /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
5112
|
-
controls,
|
|
5113
|
-
error.hasError ? /* @__PURE__ */ jsx28(Text18, { testID: ringgId("error-message"), style: [styles24.error, { color: solidColor(theme.errorColor), fontFamily }], children: error.message }) : null
|
|
5114
|
-
] }),
|
|
5115
|
-
!isSessionLive && isLoading ? /* @__PURE__ */ jsxs20(View21, { testID: ringgId("connecting-status"), children: [
|
|
5116
|
-
/* @__PURE__ */ jsx28(Text18, { testID: ringgId("connecting-label"), style: [styles24.connecting, { color: theme.mutedTextColor, fontFamily }], children: "Connecting..." }),
|
|
5117
|
-
/* @__PURE__ */ jsx28(loading_bar_default, { ariaLabel: callMode === "text" ? "Starting chat" : "Connecting" })
|
|
5118
|
-
] }) : null,
|
|
5119
|
-
!isLoading && connectionState === "disconnected" && (!bypassStartScreen || error.hasError) ? hideTabSelector ? /* @__PURE__ */ jsx28(action_button_default, { callMode, buttonConfig: buttons, isCalling, isLoading, onCallTrigger: handleCallStart }) : /* @__PURE__ */ jsxs20(View21, { testID: ringgId("start-actions"), style: styles24.startActions, children: [
|
|
5120
|
-
/* @__PURE__ */ jsx28(action_button_default, { dual: true, callMode: "text", buttonConfig: buttons, isCalling, isLoading, onCallTrigger: handleCallStart }),
|
|
5121
|
-
/* @__PURE__ */ jsx28(action_button_default, { dual: true, callMode: "audio", buttonConfig: buttons, isCalling, isLoading, onCallTrigger: handleCallStart })
|
|
5122
|
-
] }) : null,
|
|
5123
|
-
!isSessionLive ? /* @__PURE__ */ jsx28(Text18, { testID: ringgId("legal-disclaimer"), style: [styles24.legal, { color: theme.mutedTextColor, fontFamily }], children: legalDisclaimer ? renderTextWithLinks(legalDisclaimer.text, legalDisclaimer.links) : renderTextWithLinks(LEGAL_TEXT, LEGAL_LINKS, { color: solidColor(theme.primaryColor), textDecorationLine: "underline" }) }) : null
|
|
5124
|
-
] })
|
|
5125
|
-
]
|
|
5126
|
-
}
|
|
5127
|
-
);
|
|
5128
|
-
};
|
|
5129
|
-
var RinggWidget = ({ controller, room }) => {
|
|
5130
|
-
const shell = useRinggShell(controller);
|
|
5131
|
-
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
5132
|
-
const { buttons, defaultExpanded = WIDGET_DEFAULTS.defaultExpanded, widgetPosition } = controller.config;
|
|
5133
|
-
const isOpen = shell.viewState === "open" || shell.viewState === "feedback";
|
|
5134
|
-
const hideTriggerOnExpand = widgetPosition?.hideTriggerOnExpand ?? true;
|
|
5135
|
-
const showTrigger = !defaultExpanded && (!hideTriggerOnExpand || !isOpen);
|
|
5136
|
-
const panel = usePresence(isOpen, PANEL_SPRING);
|
|
5137
|
-
const trigger = usePresence(showTrigger, TRIGGER_SPRING);
|
|
5138
|
-
const panelWidth = Math.min(screenWidth - EDGE_INSET * 2, PANEL_MAX_WIDTH);
|
|
5139
|
-
const panelHeight = Math.min(screenHeight * PANEL_HEIGHT_RATIO, PANEL_HEIGHT);
|
|
5140
|
-
const triggerIconSize = toSize(buttons?.modalTrigger?.icon?.size, DEFAULT_TRIGGER_ICON_SIZE);
|
|
5141
|
-
return /* @__PURE__ */ jsx28(WidgetThemeProvider, { theme: controller.theme, children: /* @__PURE__ */ jsxs20(View21, { pointerEvents: "box-none", style: StyleSheet24.absoluteFill, children: [
|
|
5142
|
-
panel.mounted ? /* @__PURE__ */ jsx28(KeyboardAvoidingView3, { pointerEvents: "box-none", behavior: Platform5.OS === "ios" ? "padding" : void 0, style: styles24.overlay, children: /* @__PURE__ */ jsx28(
|
|
5143
|
-
Animated15.View,
|
|
5144
|
-
{
|
|
5145
|
-
renderToHardwareTextureAndroid: true,
|
|
5146
|
-
shouldRasterizeIOS: true,
|
|
5147
|
-
style: [
|
|
5148
|
-
styles24.panelAnchor,
|
|
5149
|
-
{
|
|
5150
|
-
opacity: panel.progress,
|
|
5151
|
-
transform: [{ translateY: panel.progress.interpolate({ inputRange: [0, 1], outputRange: [24, 0] }) }, { scale: panel.progress.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) }]
|
|
5152
|
-
}
|
|
5153
|
-
],
|
|
5154
|
-
children: /* @__PURE__ */ jsx28(WidgetPanel, { controller, room, width: panelWidth, height: panelHeight })
|
|
5155
|
-
}
|
|
5156
|
-
) }) : null,
|
|
5157
|
-
trigger.mounted ? /* @__PURE__ */ jsx28(WidgetTrigger, { controller, iconSize: triggerIconSize, progress: trigger.progress }) : null
|
|
5158
|
-
] }) });
|
|
5159
|
-
};
|
|
5160
|
-
var WidgetTrigger = ({ controller, iconSize, progress }) => {
|
|
5161
|
-
const theme = useWidgetTheme();
|
|
5162
|
-
const { buttons } = controller.config;
|
|
5163
|
-
return (
|
|
5164
|
-
// Web pops the trigger with a spring and fades it out again when the panel
|
|
5165
|
-
// takes over; both ends of that are visible on every open/close cycle.
|
|
5166
|
-
/* @__PURE__ */ jsx28(Animated15.View, { style: [styles24.trigger, { opacity: progress, transform: [{ scale: progress.interpolate({ inputRange: [0, 1], outputRange: [0.8, 1] }) }] }], children: /* @__PURE__ */ jsx28(Touchable, { testID: ringgId("trigger-button"), accessibilityLabel: "Open call widget", onPress: () => controller.handleTriggerClick(), children: /* @__PURE__ */ jsx28(GradientFill, { color: theme.primaryColor, style: [styles24.triggerSurface, toViewStyle(buttons?.modalTrigger?.styles)], children: buttons?.modalTrigger?.icon?.url ? /* @__PURE__ */ jsx28(
|
|
5167
|
-
Image3,
|
|
5168
|
-
{
|
|
5169
|
-
testID: ringgId("trigger-icon"),
|
|
5170
|
-
accessibilityLabel: "Widget trigger",
|
|
5171
|
-
source: { uri: buttons.modalTrigger.icon.url },
|
|
5172
|
-
resizeMode: "contain",
|
|
5173
|
-
style: { width: iconSize, height: iconSize }
|
|
5174
|
-
}
|
|
5175
|
-
) : /* @__PURE__ */ jsx28(PhoneCallRingIcon, { testID: ringgId("trigger-icon"), size: iconSize, fill: solidColor(theme.primaryTextColor) }) }) }) })
|
|
5176
|
-
);
|
|
5177
|
-
};
|
|
5178
|
-
var styles24 = StyleSheet24.create({
|
|
5179
|
-
overlay: { ...StyleSheet24.absoluteFill, justifyContent: "flex-end", alignItems: "flex-end" },
|
|
5180
|
-
panelAnchor: { marginHorizontal: EDGE_INSET, marginTop: EDGE_INSET, marginBottom: BOTTOM_SAFE_INSET },
|
|
5181
|
-
panel: {
|
|
5182
|
-
borderWidth: StyleSheet24.hairlineWidth,
|
|
5183
|
-
// `overflow: hidden` is what keeps the transcript, the header bar and the
|
|
5184
|
-
// rounded corners agreeing with each other.
|
|
5185
|
-
overflow: "hidden",
|
|
5186
|
-
// Web stacks three shadows at 1-2% alpha — the panel sits just off the page
|
|
5187
|
-
// rather than floating above it. 12% read as a hard slab, and on Android
|
|
5188
|
-
// `elevation` is the only one of these that applies, so it was the entire
|
|
5189
|
-
// effect instead of a supporting layer.
|
|
5190
|
-
shadowColor: "#000000",
|
|
5191
|
-
shadowOpacity: 0.05,
|
|
5192
|
-
shadowRadius: 20,
|
|
5193
|
-
shadowOffset: { width: 0, height: 8 },
|
|
5194
|
-
elevation: 4
|
|
5195
|
-
},
|
|
5196
|
-
trigger: { position: "absolute", right: EDGE_INSET, bottom: BOTTOM_SAFE_INSET },
|
|
5197
|
-
triggerSurface: { width: TRIGGER_SIZE, height: TRIGGER_SIZE, borderRadius: TRIGGER_RADIUS, alignItems: "center", justifyContent: "center" },
|
|
5198
|
-
chromeButton: { position: "absolute", top: 10, zIndex: 20, width: 28, height: 28, borderRadius: 14, alignItems: "center", justifyContent: "center" },
|
|
5199
|
-
minimizeButton: { right: 42 },
|
|
5200
|
-
closeButton: { right: 10 },
|
|
5201
|
-
body: { flex: 1 },
|
|
5202
|
-
bodyLive: { paddingHorizontal: 20, paddingVertical: 8, gap: 10 },
|
|
5203
|
-
bodyIdle: { padding: 24, gap: 12 },
|
|
5204
|
-
bodySpacer: { flex: 1, marginTop: 56 },
|
|
5205
|
-
voiceWrapper: { width: "100%", alignItems: "stretch", justifyContent: "center", marginTop: 56 },
|
|
5206
|
-
voiceWrapperCompact: { height: 56, flexShrink: 0 },
|
|
5207
|
-
voiceWrapperFull: { flex: 1, minHeight: 0 },
|
|
5208
|
-
transcript: { flex: 1 },
|
|
5209
|
-
// `justifyContent: flex-end` is web's `mt-auto`: a short conversation sits at
|
|
5210
|
-
// the bottom of the panel rather than floating at the top.
|
|
5211
|
-
transcriptList: { flexGrow: 1, justifyContent: "flex-end", gap: 12 },
|
|
5212
|
-
transcriptTopClearance: { paddingTop: 64 },
|
|
5213
|
-
transcriptBottomClearance: { paddingBottom: 80 },
|
|
5214
|
-
fullWidth: { width: "100%" },
|
|
5215
|
-
controlsBar: { position: "absolute", zIndex: 10, bottom: 0, left: 0, right: 0, paddingHorizontal: 20, paddingVertical: 12 },
|
|
5216
|
-
error: { textAlign: "center", fontSize: 14, marginBottom: 8 },
|
|
5217
|
-
connecting: { fontSize: 14, textAlign: "center" },
|
|
5218
|
-
startActions: { flexDirection: "row", alignItems: "center", gap: 8, width: "100%" },
|
|
5219
|
-
legal: { fontSize: 12, lineHeight: 20, textAlign: "center" }
|
|
5220
|
-
});
|
|
5221
|
-
|
|
5222
|
-
// src/transport/livekit-transport.ts
|
|
5223
|
-
import { registerGlobals } from "@livekit/react-native";
|
|
5224
|
-
import {
|
|
5225
|
-
ConnectionState as LKConnectionState,
|
|
5226
|
-
Room,
|
|
5227
|
-
RoomEvent as RoomEvent3
|
|
5228
|
-
} from "livekit-client";
|
|
5229
|
-
|
|
5230
|
-
// src/platform/audio-session.ts
|
|
5231
|
-
import { AndroidAudioTypePresets, AudioSession } from "@livekit/react-native";
|
|
5232
|
-
var PREFERRED_OUTPUTS = ["bluetooth", "headset", "speaker", "earpiece"];
|
|
5233
|
-
var createAudioSession = () => {
|
|
5234
|
-
let active = false;
|
|
5235
|
-
const start = async () => {
|
|
5236
|
-
if (active) return;
|
|
5237
|
-
active = true;
|
|
5238
|
-
try {
|
|
5239
|
-
await AudioSession.configureAudio({
|
|
5240
|
-
android: {
|
|
5241
|
-
audioTypeOptions: AndroidAudioTypePresets.communication,
|
|
5242
|
-
preferredOutputList: [...PREFERRED_OUTPUTS]
|
|
5243
|
-
},
|
|
5244
|
-
ios: { defaultOutput: "speaker" }
|
|
5245
|
-
});
|
|
5246
|
-
await AudioSession.startAudioSession();
|
|
5247
|
-
} catch {
|
|
5248
|
-
}
|
|
5172
|
+
var defaultUrlResolver = createStaticUrlResolver(DEFAULT_ENVIRONMENT_URLS);
|
|
5173
|
+
|
|
5174
|
+
// src/platform/mic-permission.ts
|
|
5175
|
+
import { PermissionsAndroid, Platform as Platform5 } from "react-native";
|
|
5176
|
+
import { mediaDevices } from "@livekit/react-native-webrtc";
|
|
5177
|
+
var stopTracks = (stream) => {
|
|
5178
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
5179
|
+
};
|
|
5180
|
+
var probeMicrophone = async () => {
|
|
5181
|
+
try {
|
|
5182
|
+
const stream = await mediaDevices.getUserMedia({ audio: true });
|
|
5183
|
+
stopTracks(stream);
|
|
5184
|
+
return true;
|
|
5185
|
+
} catch {
|
|
5186
|
+
return false;
|
|
5187
|
+
}
|
|
5188
|
+
};
|
|
5189
|
+
var ANDROID_RATIONALE = {
|
|
5190
|
+
title: "Microphone access",
|
|
5191
|
+
message: "Voice calls need the microphone to hear you.",
|
|
5192
|
+
buttonPositive: "Allow",
|
|
5193
|
+
buttonNegative: "Not now"
|
|
5194
|
+
};
|
|
5195
|
+
var createNativeMicPermission = () => ({
|
|
5196
|
+
async isGranted() {
|
|
5197
|
+
if (Platform5.OS === "android") {
|
|
5198
|
+
return PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO);
|
|
5199
|
+
}
|
|
5200
|
+
return probeMicrophone();
|
|
5201
|
+
},
|
|
5202
|
+
async request() {
|
|
5203
|
+
if (Platform5.OS === "android") {
|
|
5204
|
+
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, ANDROID_RATIONALE);
|
|
5205
|
+
return result === PermissionsAndroid.RESULTS.GRANTED;
|
|
5206
|
+
}
|
|
5207
|
+
return probeMicrophone();
|
|
5208
|
+
}
|
|
5209
|
+
});
|
|
5210
|
+
|
|
5211
|
+
// src/transport/livekit-transport.ts
|
|
5212
|
+
import { registerGlobals } from "@livekit/react-native";
|
|
5213
|
+
import {
|
|
5214
|
+
ConnectionState as LKConnectionState,
|
|
5215
|
+
Room,
|
|
5216
|
+
RoomEvent as RoomEvent3
|
|
5217
|
+
} from "livekit-client";
|
|
5218
|
+
|
|
5219
|
+
// src/platform/audio-session.ts
|
|
5220
|
+
import { AndroidAudioTypePresets, AudioSession } from "@livekit/react-native";
|
|
5221
|
+
var PREFERRED_OUTPUTS = ["bluetooth", "headset", "speaker", "earpiece"];
|
|
5222
|
+
var createAudioSession = () => {
|
|
5223
|
+
let active = false;
|
|
5224
|
+
const start = async () => {
|
|
5225
|
+
if (active) return;
|
|
5226
|
+
active = true;
|
|
5227
|
+
try {
|
|
5228
|
+
await AudioSession.configureAudio({
|
|
5229
|
+
android: {
|
|
5230
|
+
audioTypeOptions: AndroidAudioTypePresets.communication,
|
|
5231
|
+
preferredOutputList: [...PREFERRED_OUTPUTS]
|
|
5232
|
+
},
|
|
5233
|
+
ios: { defaultOutput: "speaker" }
|
|
5234
|
+
});
|
|
5235
|
+
await AudioSession.startAudioSession();
|
|
5236
|
+
} catch {
|
|
5237
|
+
}
|
|
5249
5238
|
};
|
|
5250
5239
|
const stop = async () => {
|
|
5251
5240
|
if (!active) return;
|
|
@@ -5552,60 +5541,390 @@ var createLiveKitTransport = (options = {}) => {
|
|
|
5552
5541
|
room.off(RoomEvent3.MediaDevicesError, listener);
|
|
5553
5542
|
};
|
|
5554
5543
|
}
|
|
5555
|
-
};
|
|
5556
|
-
const dispose = () => {
|
|
5557
|
-
room.off(RoomEvent3.DataReceived, handleDataReceived);
|
|
5558
|
-
room.off(RoomEvent3.Disconnected, handleRoomDisconnected);
|
|
5559
|
-
for (const topic of Array.from(registeredTextStreamTopics)) {
|
|
5560
|
-
room.unregisterTextStreamHandler(topic);
|
|
5544
|
+
};
|
|
5545
|
+
const dispose = () => {
|
|
5546
|
+
room.off(RoomEvent3.DataReceived, handleDataReceived);
|
|
5547
|
+
room.off(RoomEvent3.Disconnected, handleRoomDisconnected);
|
|
5548
|
+
for (const topic of Array.from(registeredTextStreamTopics)) {
|
|
5549
|
+
room.unregisterTextStreamHandler(topic);
|
|
5550
|
+
}
|
|
5551
|
+
registeredTextStreamTopics.clear();
|
|
5552
|
+
for (const method of Array.from(registeredRpcMethods)) {
|
|
5553
|
+
room.unregisterRpcMethod(method);
|
|
5554
|
+
}
|
|
5555
|
+
registeredRpcMethods.clear();
|
|
5556
|
+
chatHandlers.clear();
|
|
5557
|
+
firstSeenAt.clear();
|
|
5558
|
+
void room.disconnect().finally(() => audioSession?.stop());
|
|
5559
|
+
};
|
|
5560
|
+
return { transport, room, dispose };
|
|
5561
|
+
};
|
|
5562
|
+
|
|
5563
|
+
// src/RinggWidget.tsx
|
|
5564
|
+
import { Fragment as Fragment8, jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
5565
|
+
var PANEL_MAX_WIDTH = 400;
|
|
5566
|
+
var PANEL_HEIGHT = 456;
|
|
5567
|
+
var PANEL_HEIGHT_RATIO = 0.9;
|
|
5568
|
+
var EDGE_INSET = 16;
|
|
5569
|
+
var BOTTOM_SAFE_INSET = 24;
|
|
5570
|
+
var TRIGGER_SIZE = 52;
|
|
5571
|
+
var TRIGGER_RADIUS = 20;
|
|
5572
|
+
var DEFAULT_TRIGGER_ICON_SIZE = 24;
|
|
5573
|
+
var PANEL_SPRING = { stiffness: 380, damping: 30 };
|
|
5574
|
+
var TRIGGER_SPRING = { stiffness: 400, damping: 25 };
|
|
5575
|
+
var LEGAL_LINKS = { "Privacy Policy": "https://www.ringg.ai/privacy", "T&C": "https://www.ringg.ai/terms" };
|
|
5576
|
+
var LEGAL_TEXT = "By continuing, you agree to our\nPrivacy Policy and T&C.";
|
|
5577
|
+
var isFreeButtonsMessage = (message) => {
|
|
5578
|
+
const componentData = message?.componentData;
|
|
5579
|
+
return !!componentData && isButtons2(componentData) && componentData.data.presentation === "free";
|
|
5580
|
+
};
|
|
5581
|
+
var isAttachableAgentMessage = (message) => !!message && !message.componentType && !message.isSelf && Boolean(message.message.trim());
|
|
5582
|
+
var WidgetPanel = ({ controller, room, width, height }) => {
|
|
5583
|
+
const theme = useWidgetTheme();
|
|
5584
|
+
const { config } = controller;
|
|
5585
|
+
const {
|
|
5586
|
+
title = WIDGET_DEFAULTS.title,
|
|
5587
|
+
description = WIDGET_DEFAULTS.description,
|
|
5588
|
+
buttons,
|
|
5589
|
+
defaultExpanded = WIDGET_DEFAULTS.defaultExpanded,
|
|
5590
|
+
hideTabSelector = WIDGET_DEFAULTS.hideTabSelector,
|
|
5591
|
+
bypassStartScreen = WIDGET_DEFAULTS.bypassStartScreen,
|
|
5592
|
+
logoUrl,
|
|
5593
|
+
logoStyles,
|
|
5594
|
+
typingWords,
|
|
5595
|
+
legalDisclaimer,
|
|
5596
|
+
feedbackScreen,
|
|
5597
|
+
voiceCall
|
|
5598
|
+
} = config;
|
|
5599
|
+
const shell = useRinggShell(controller);
|
|
5600
|
+
const session = useRinggSession(controller);
|
|
5601
|
+
const { messages } = useRinggMessages(controller);
|
|
5602
|
+
const { isTyping: isAgentTyping } = useRinggTyping(controller);
|
|
5603
|
+
const { completedFlowIds } = useRinggComponents(controller);
|
|
5604
|
+
const { commands } = useRinggSlashCommands(controller);
|
|
5605
|
+
const [alertEndCall, setAlertEndCall] = useState16(false);
|
|
5606
|
+
const microphone = useMicrophone(room);
|
|
5607
|
+
const callMode = shell.callMode;
|
|
5608
|
+
const showFeedback = shell.viewState === "feedback";
|
|
5609
|
+
const isLoading = session.isLoading;
|
|
5610
|
+
const error = session.error;
|
|
5611
|
+
const connectionState = session.connectionState;
|
|
5612
|
+
const isCalling = connectionState === "connected";
|
|
5613
|
+
const isSessionLive = session.isSessionLive;
|
|
5614
|
+
const isAudioMode = callMode === "audio";
|
|
5615
|
+
const showVoiceAnimation = isAudioMode && isCalling && (voiceCall?.showAnimation ?? WIDGET_DEFAULTS.voiceCallShowAnimation);
|
|
5616
|
+
const showVoiceTranscript = !isAudioMode || (voiceCall?.showTranscript ?? WIDGET_DEFAULTS.voiceCallShowTranscript);
|
|
5617
|
+
const mergedSlashCommands = useMemo11(() => [...commands], [commands]);
|
|
5618
|
+
const sortedMessages = useMemo11(() => [...messages].sort((a, b) => a.timestamp - b.timestamp), [messages]);
|
|
5619
|
+
const transcriptRef = useRef15(null);
|
|
5620
|
+
const previousMessagesCountRef = useRef15(0);
|
|
5621
|
+
useEffect16(() => {
|
|
5622
|
+
const newMessageAdded = messages.length > previousMessagesCountRef.current;
|
|
5623
|
+
previousMessagesCountRef.current = messages.length;
|
|
5624
|
+
transcriptRef.current?.scrollToEnd({ animated: newMessageAdded });
|
|
5625
|
+
}, [messages, isAgentTyping]);
|
|
5626
|
+
const handleCallStart = (mediaType) => void controller.startCall(mediaType);
|
|
5627
|
+
const handleCallEnd = () => {
|
|
5628
|
+
setAlertEndCall(false);
|
|
5629
|
+
void controller.endCall();
|
|
5630
|
+
};
|
|
5631
|
+
const handleFeedbackSubmit = (rating, comment) => void controller.submitFeedback(rating, comment);
|
|
5632
|
+
const handleFeedbackSkip = () => controller.skipFeedback();
|
|
5633
|
+
const handleUserMessageSent = () => controller.typing.showOnUserSend();
|
|
5634
|
+
const handleFlowComplete = (componentId) => controller.markFlowComplete(componentId);
|
|
5635
|
+
const handleSelectionDisplayed = (componentId, label) => controller.displaySelection(componentId, label);
|
|
5636
|
+
const sendComponentResponse = (componentId, responseData) => controller.sendComponentResponse(componentId, responseData);
|
|
5637
|
+
const sendSlashCommand = (command) => void controller.sendSlashCommand(command);
|
|
5638
|
+
const callComponentApi = controller.callComponentApi;
|
|
5639
|
+
const panelRadius = resolveRadius(theme.borderRadius, 16);
|
|
5640
|
+
const fontFamily = resolveFontFamily(theme.fontFamily);
|
|
5641
|
+
const chrome = /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
5642
|
+
!defaultExpanded && (isSessionLive || showFeedback) ? /* @__PURE__ */ jsx28(Touchable, { testID: ringgId("minimize-button"), accessibilityLabel: "Minimize widget", onPress: () => controller.minimizeWidget(), style: [styles24.chromeButton, styles24.minimizeButton], children: /* @__PURE__ */ jsx28(Minus, { size: 16, color: solidColor(theme.mutedTextColor) }) }) : null,
|
|
5643
|
+
!defaultExpanded ? /* @__PURE__ */ jsx28(
|
|
5644
|
+
Touchable,
|
|
5645
|
+
{
|
|
5646
|
+
testID: ringgId("close-button"),
|
|
5647
|
+
accessibilityLabel: isSessionLive ? "End call" : "Close widget",
|
|
5648
|
+
onPress: () => isSessionLive ? setAlertEndCall(true) : showFeedback ? handleFeedbackSkip() : controller.minimizeWidget(),
|
|
5649
|
+
style: [styles24.chromeButton, styles24.closeButton],
|
|
5650
|
+
children: /* @__PURE__ */ jsx28(X, { size: 16, color: solidColor(theme.mutedTextColor) })
|
|
5651
|
+
}
|
|
5652
|
+
) : null
|
|
5653
|
+
] });
|
|
5654
|
+
const transcript = /* @__PURE__ */ jsxs20(
|
|
5655
|
+
ScrollView5,
|
|
5656
|
+
{
|
|
5657
|
+
testID: ringgId("transcript"),
|
|
5658
|
+
ref: transcriptRef,
|
|
5659
|
+
style: styles24.transcript,
|
|
5660
|
+
contentContainerStyle: [styles24.transcriptList, !showVoiceAnimation && styles24.transcriptTopClearance, !isAudioMode && styles24.transcriptBottomClearance],
|
|
5661
|
+
keyboardShouldPersistTaps: "handled",
|
|
5662
|
+
showsVerticalScrollIndicator: false,
|
|
5663
|
+
children: [
|
|
5664
|
+
sortedMessages.map((segment, index) => {
|
|
5665
|
+
if (segment.kind === "system") return /* @__PURE__ */ jsx28(system_log_default, { label: segment.message, meta: segment.systemMeta, level: segment.systemLevel }, `sys_${segment.timestamp}_${index}`);
|
|
5666
|
+
const previousSegment = sortedMessages[index - 1];
|
|
5667
|
+
const nextSegment = sortedMessages[index + 1];
|
|
5668
|
+
const attachFreeButtonsToPrevious = isFreeButtonsMessage(segment) && isAttachableAgentMessage(previousSegment);
|
|
5669
|
+
const skipStandaloneRender = isAttachableAgentMessage(segment) && isFreeButtonsMessage(nextSegment);
|
|
5670
|
+
if (skipStandaloneRender) return null;
|
|
5671
|
+
if (attachFreeButtonsToPrevious && previousSegment && segment.componentData) {
|
|
5672
|
+
const componentData = segment.componentData;
|
|
5673
|
+
const isCompletedSelectedItem = componentData.component_type === "buttons" && componentData.data.completionDisplay === "selected_item" && completedFlowIds.has(componentData.component_id);
|
|
5674
|
+
if (isCompletedSelectedItem) return /* @__PURE__ */ jsx28(message_item_default, { itemIndex: index, message: previousSegment }, `message_with_buttons_${segment.componentData.component_id}_${index}`);
|
|
5675
|
+
return /* @__PURE__ */ jsxs20(Fragment7, { children: [
|
|
5676
|
+
/* @__PURE__ */ jsx28(message_item_default, { itemIndex: index, message: previousSegment }),
|
|
5677
|
+
/* @__PURE__ */ jsx28(View21, { testID: ringgId("attached-buttons"), style: styles24.fullWidth, children: /* @__PURE__ */ jsx28(
|
|
5678
|
+
interactive_flow_default,
|
|
5679
|
+
{
|
|
5680
|
+
initialComponent: segment.componentData,
|
|
5681
|
+
onApiCall: callComponentApi,
|
|
5682
|
+
onComplete: () => handleFlowComplete(componentData.component_id),
|
|
5683
|
+
onSendResponse: sendComponentResponse,
|
|
5684
|
+
onSelectionDisplayed: handleSelectionDisplayed,
|
|
5685
|
+
quickReplyAlign: isAudioMode ? "center" : "end"
|
|
5686
|
+
}
|
|
5687
|
+
) })
|
|
5688
|
+
] }, `message_with_buttons_${segment.componentData.component_id}_${index}`);
|
|
5689
|
+
}
|
|
5690
|
+
if (segment.componentType === "blocks" && segment.componentData) {
|
|
5691
|
+
const blocksPayload = segment.componentData;
|
|
5692
|
+
if (isBlocks2(blocksPayload)) {
|
|
5693
|
+
const blocksConfig = blocksPayload.data;
|
|
5694
|
+
const blockId = blocksPayload.component_id;
|
|
5695
|
+
return /* @__PURE__ */ jsx28(View21, { testID: ringgId("blocks-widget"), style: styles24.fullWidth, children: /* @__PURE__ */ jsx28(
|
|
5696
|
+
blocks_step_default,
|
|
5697
|
+
{
|
|
5698
|
+
config: blocksConfig,
|
|
5699
|
+
disabled: completedFlowIds.has(blockId),
|
|
5700
|
+
onAction: (action) => {
|
|
5701
|
+
handleUserMessageSent();
|
|
5702
|
+
handleFlowComplete(blockId);
|
|
5703
|
+
const selection = typeof action.value === "string" || typeof action.value === "number" ? String(action.value) : action.label;
|
|
5704
|
+
if (selection) handleSelectionDisplayed(blockId, selection);
|
|
5705
|
+
return controller.sendBlocksAction(blocksConfig.tool_id, blockId, { action_id: action.action_id, value: action.value, values: action.values });
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
) }, `blocks_${blockId}`);
|
|
5709
|
+
}
|
|
5710
|
+
}
|
|
5711
|
+
if (segment.componentType && segment.componentData) {
|
|
5712
|
+
const componentData = segment.componentData;
|
|
5713
|
+
const isCompletedSelectedItem = componentData.component_type === "buttons" && componentData.data.completionDisplay === "selected_item" && completedFlowIds.has(componentData.component_id);
|
|
5714
|
+
if (isCompletedSelectedItem) return null;
|
|
5715
|
+
return /* @__PURE__ */ jsx28(
|
|
5716
|
+
interactive_flow_default,
|
|
5717
|
+
{
|
|
5718
|
+
initialComponent: segment.componentData,
|
|
5719
|
+
onApiCall: callComponentApi,
|
|
5720
|
+
onComplete: () => handleFlowComplete(componentData.component_id),
|
|
5721
|
+
onSendResponse: sendComponentResponse,
|
|
5722
|
+
onSelectionDisplayed: handleSelectionDisplayed,
|
|
5723
|
+
quickReplyAlign: isAudioMode ? "center" : "end"
|
|
5724
|
+
},
|
|
5725
|
+
`flow_${segment.componentData.component_id}_${index}`
|
|
5726
|
+
);
|
|
5727
|
+
}
|
|
5728
|
+
return /* @__PURE__ */ jsx28(message_item_default, { itemIndex: index, message: segment }, `${segment.timestamp}__${index}`);
|
|
5729
|
+
}),
|
|
5730
|
+
isAgentTyping ? /* @__PURE__ */ jsx28(typing_indicator_default, { words: typingWords }) : null
|
|
5731
|
+
]
|
|
5732
|
+
}
|
|
5733
|
+
);
|
|
5734
|
+
const controls = /* @__PURE__ */ jsx28(
|
|
5735
|
+
call_controls_default,
|
|
5736
|
+
{
|
|
5737
|
+
buttonConfig: buttons,
|
|
5738
|
+
callMode,
|
|
5739
|
+
isCalling: isSessionLive,
|
|
5740
|
+
enabledSlashCommands: mergedSlashCommands,
|
|
5741
|
+
micEnabled: microphone?.enabled ?? true,
|
|
5742
|
+
onToggleMic: microphone?.toggle,
|
|
5743
|
+
onSlashCommand: sendSlashCommand,
|
|
5744
|
+
onUserMessage: handleUserMessageSent,
|
|
5745
|
+
onCallEnd: handleCallEnd,
|
|
5746
|
+
onSendMessage: (message) => controller.sendMessage(message)
|
|
5561
5747
|
}
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5748
|
+
);
|
|
5749
|
+
return /* @__PURE__ */ jsxs20(
|
|
5750
|
+
View21,
|
|
5751
|
+
{
|
|
5752
|
+
testID: ringgId("widget-root"),
|
|
5753
|
+
style: [styles24.panel, { width, height, borderRadius: panelRadius, backgroundColor: solidColor(theme.backgroundColor), borderColor: solidColor(theme.borderColor) }],
|
|
5754
|
+
children: [
|
|
5755
|
+
chrome,
|
|
5756
|
+
/* @__PURE__ */ jsx28(
|
|
5757
|
+
ConfirmDialog,
|
|
5758
|
+
{
|
|
5759
|
+
open: alertEndCall,
|
|
5760
|
+
title: `Are you sure you want to end ${callMode === "text" ? "chat" : "call"}?`,
|
|
5761
|
+
description: "Your current conversation will be closed and cannot be accessed later.",
|
|
5762
|
+
confirmLabel: `End ${callMode === "text" ? "Chat" : "Call"}`,
|
|
5763
|
+
cancelLabel: "Cancel",
|
|
5764
|
+
onConfirm: handleCallEnd,
|
|
5765
|
+
onCancel: () => setAlertEndCall(false)
|
|
5766
|
+
}
|
|
5767
|
+
),
|
|
5768
|
+
showFeedback ? /* @__PURE__ */ jsx28(
|
|
5769
|
+
feedback_screen_default,
|
|
5770
|
+
{
|
|
5771
|
+
callMode,
|
|
5772
|
+
isSubmitting: isLoading,
|
|
5773
|
+
feedbackScreenConfig: feedbackScreen,
|
|
5774
|
+
messages,
|
|
5775
|
+
title,
|
|
5776
|
+
logoUrl,
|
|
5777
|
+
logoStyles,
|
|
5778
|
+
onSubmit: handleFeedbackSubmit,
|
|
5779
|
+
onSkip: handleFeedbackSkip
|
|
5780
|
+
}
|
|
5781
|
+
) : /* @__PURE__ */ jsxs20(View21, { testID: ringgId("widget-body"), style: [styles24.body, isSessionLive ? styles24.bodyLive : styles24.bodyIdle], children: [
|
|
5782
|
+
/* @__PURE__ */ jsx28(header_default, { isCalling: isSessionLive, isLoading, title, description, logoUrl, logoStyles }),
|
|
5783
|
+
showVoiceAnimation ? /* @__PURE__ */ jsx28(View21, { testID: ringgId("voice-animation-wrapper"), style: [styles24.voiceWrapper, showVoiceTranscript && messages.length > 0 ? styles24.voiceWrapperCompact : styles24.voiceWrapperFull], children: /* @__PURE__ */ jsx28(VoiceAnimationLeaf, { room }) }) : null,
|
|
5784
|
+
isSessionLive && !showVoiceAnimation && (messages.length === 0 && !isAgentTyping || !showVoiceTranscript) ? /* @__PURE__ */ jsx28(View21, { testID: ringgId("body-spacer"), style: styles24.bodySpacer }) : null,
|
|
5785
|
+
(messages.length > 0 || isAgentTyping) && isSessionLive && showVoiceTranscript ? transcript : null,
|
|
5786
|
+
isSessionLive && callMode === "text" ? (
|
|
5787
|
+
// Transparent bottom bar: it only positions the composer over the
|
|
5788
|
+
// transcript, so the gutters stay clear while messages scroll under.
|
|
5789
|
+
/* @__PURE__ */ jsxs20(View21, { testID: ringgId("controls-bar"), style: styles24.controlsBar, children: [
|
|
5790
|
+
error.hasError ? /* @__PURE__ */ jsx28(Text18, { testID: ringgId("error-message"), style: [styles24.error, { color: solidColor(theme.errorColor), fontFamily }], children: error.message }) : null,
|
|
5791
|
+
controls
|
|
5792
|
+
] })
|
|
5793
|
+
) : /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
5794
|
+
controls,
|
|
5795
|
+
error.hasError ? /* @__PURE__ */ jsx28(Text18, { testID: ringgId("error-message"), style: [styles24.error, { color: solidColor(theme.errorColor), fontFamily }], children: error.message }) : null
|
|
5796
|
+
] }),
|
|
5797
|
+
!isSessionLive && isLoading ? /* @__PURE__ */ jsxs20(View21, { testID: ringgId("connecting-status"), children: [
|
|
5798
|
+
/* @__PURE__ */ jsx28(Text18, { testID: ringgId("connecting-label"), style: [styles24.connecting, { color: theme.mutedTextColor, fontFamily }], children: "Connecting..." }),
|
|
5799
|
+
/* @__PURE__ */ jsx28(loading_bar_default, { ariaLabel: callMode === "text" ? "Starting chat" : "Connecting" })
|
|
5800
|
+
] }) : null,
|
|
5801
|
+
!isLoading && connectionState === "disconnected" && (!bypassStartScreen || error.hasError) ? hideTabSelector ? /* @__PURE__ */ jsx28(action_button_default, { callMode, buttonConfig: buttons, isCalling, isLoading, onCallTrigger: handleCallStart }) : /* @__PURE__ */ jsxs20(View21, { testID: ringgId("start-actions"), style: styles24.startActions, children: [
|
|
5802
|
+
/* @__PURE__ */ jsx28(action_button_default, { dual: true, callMode: "text", buttonConfig: buttons, isCalling, isLoading, onCallTrigger: handleCallStart }),
|
|
5803
|
+
/* @__PURE__ */ jsx28(action_button_default, { dual: true, callMode: "audio", buttonConfig: buttons, isCalling, isLoading, onCallTrigger: handleCallStart })
|
|
5804
|
+
] }) : null,
|
|
5805
|
+
!isSessionLive ? /* @__PURE__ */ jsx28(Text18, { testID: ringgId("legal-disclaimer"), style: [styles24.legal, { color: theme.mutedTextColor, fontFamily }], children: legalDisclaimer ? renderTextWithLinks(legalDisclaimer.text, legalDisclaimer.links) : renderTextWithLinks(LEGAL_TEXT, LEGAL_LINKS, { color: solidColor(theme.primaryColor), textDecorationLine: "underline" }) }) : null
|
|
5806
|
+
] })
|
|
5807
|
+
]
|
|
5565
5808
|
}
|
|
5566
|
-
|
|
5567
|
-
chatHandlers.clear();
|
|
5568
|
-
firstSeenAt.clear();
|
|
5569
|
-
void room.disconnect().finally(() => audioSession?.stop());
|
|
5570
|
-
};
|
|
5571
|
-
return { transport, room, dispose };
|
|
5809
|
+
);
|
|
5572
5810
|
};
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5811
|
+
var ControlledRinggWidget = ({ controller, room }) => {
|
|
5812
|
+
const shell = useRinggShell(controller);
|
|
5813
|
+
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
5814
|
+
const { buttons, defaultExpanded = WIDGET_DEFAULTS.defaultExpanded, widgetPosition } = controller.config;
|
|
5815
|
+
const isOpen = shell.viewState === "open" || shell.viewState === "feedback";
|
|
5816
|
+
const hideTriggerOnExpand = widgetPosition?.hideTriggerOnExpand ?? true;
|
|
5817
|
+
const showTrigger = !defaultExpanded && (!hideTriggerOnExpand || !isOpen);
|
|
5818
|
+
const panel = usePresence(isOpen, PANEL_SPRING);
|
|
5819
|
+
const trigger = usePresence(showTrigger, TRIGGER_SPRING);
|
|
5820
|
+
const panelWidth = Math.min(screenWidth - EDGE_INSET * 2, PANEL_MAX_WIDTH);
|
|
5821
|
+
const panelHeight = Math.min(screenHeight * PANEL_HEIGHT_RATIO, PANEL_HEIGHT);
|
|
5822
|
+
const triggerIconSize = toSize(buttons?.modalTrigger?.icon?.size, DEFAULT_TRIGGER_ICON_SIZE);
|
|
5823
|
+
return /* @__PURE__ */ jsx28(WidgetThemeProvider, { theme: controller.theme, children: /* @__PURE__ */ jsxs20(View21, { pointerEvents: "box-none", style: StyleSheet24.absoluteFill, children: [
|
|
5824
|
+
panel.mounted ? /* @__PURE__ */ jsx28(KeyboardAvoidingView3, { pointerEvents: "box-none", behavior: Platform6.OS === "ios" ? "padding" : void 0, style: styles24.overlay, children: /* @__PURE__ */ jsx28(
|
|
5825
|
+
Animated15.View,
|
|
5826
|
+
{
|
|
5827
|
+
renderToHardwareTextureAndroid: true,
|
|
5828
|
+
shouldRasterizeIOS: true,
|
|
5829
|
+
style: [
|
|
5830
|
+
styles24.panelAnchor,
|
|
5831
|
+
{
|
|
5832
|
+
opacity: panel.progress,
|
|
5833
|
+
transform: [{ translateY: panel.progress.interpolate({ inputRange: [0, 1], outputRange: [24, 0] }) }, { scale: panel.progress.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) }]
|
|
5834
|
+
}
|
|
5835
|
+
],
|
|
5836
|
+
children: /* @__PURE__ */ jsx28(WidgetPanel, { controller, room, width: panelWidth, height: panelHeight })
|
|
5837
|
+
}
|
|
5838
|
+
) }) : null,
|
|
5839
|
+
trigger.mounted ? /* @__PURE__ */ jsx28(WidgetTrigger, { controller, iconSize: triggerIconSize, progress: trigger.progress }) : null
|
|
5840
|
+
] }) });
|
|
5579
5841
|
};
|
|
5580
|
-
var
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5842
|
+
var WidgetTrigger = ({ controller, iconSize, progress }) => {
|
|
5843
|
+
const theme = useWidgetTheme();
|
|
5844
|
+
const { buttons } = controller.config;
|
|
5845
|
+
return (
|
|
5846
|
+
// Web pops the trigger with a spring and fades it out again when the panel
|
|
5847
|
+
// takes over; both ends of that are visible on every open/close cycle.
|
|
5848
|
+
/* @__PURE__ */ jsx28(Animated15.View, { style: [styles24.trigger, { opacity: progress, transform: [{ scale: progress.interpolate({ inputRange: [0, 1], outputRange: [0.8, 1] }) }] }], children: /* @__PURE__ */ jsx28(Touchable, { testID: ringgId("trigger-button"), accessibilityLabel: "Open call widget", onPress: () => controller.handleTriggerClick(), children: /* @__PURE__ */ jsx28(GradientFill, { color: theme.primaryColor, style: [styles24.triggerSurface, toViewStyle(buttons?.modalTrigger?.styles)], children: buttons?.modalTrigger?.icon?.url ? /* @__PURE__ */ jsx28(
|
|
5849
|
+
Image3,
|
|
5850
|
+
{
|
|
5851
|
+
testID: ringgId("trigger-icon"),
|
|
5852
|
+
accessibilityLabel: "Widget trigger",
|
|
5853
|
+
source: { uri: buttons.modalTrigger.icon.url },
|
|
5854
|
+
resizeMode: "contain",
|
|
5855
|
+
style: { width: iconSize, height: iconSize }
|
|
5856
|
+
}
|
|
5857
|
+
) : /* @__PURE__ */ jsx28(PhoneCallRingIcon, { testID: ringgId("trigger-icon"), size: iconSize, fill: solidColor(theme.primaryTextColor) }) }) }) })
|
|
5858
|
+
);
|
|
5588
5859
|
};
|
|
5589
|
-
var
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5860
|
+
var ManagedRinggWidget = ({ config, ports, onReady }) => {
|
|
5861
|
+
const stack = useMemo11(() => {
|
|
5862
|
+
const livekit = ports?.transport ? null : createLiveKitTransport();
|
|
5863
|
+
const controller = createRinggWidgetController(config, {
|
|
5864
|
+
urlResolver: defaultUrlResolver,
|
|
5865
|
+
micPermission: createNativeMicPermission(),
|
|
5866
|
+
...ports,
|
|
5867
|
+
transport: ports?.transport ?? livekit.transport
|
|
5868
|
+
});
|
|
5869
|
+
return { controller, livekit };
|
|
5870
|
+
}, [config]);
|
|
5871
|
+
useEffect16(
|
|
5872
|
+
() => () => {
|
|
5873
|
+
stack.controller.destroy();
|
|
5874
|
+
stack.livekit?.dispose();
|
|
5875
|
+
},
|
|
5876
|
+
[stack]
|
|
5877
|
+
);
|
|
5878
|
+
const onReadyRef = useRef15(onReady);
|
|
5879
|
+
onReadyRef.current = onReady;
|
|
5880
|
+
useEffect16(() => {
|
|
5881
|
+
onReadyRef.current?.(stack.controller);
|
|
5882
|
+
}, [stack]);
|
|
5883
|
+
return /* @__PURE__ */ jsx28(ControlledRinggWidget, { controller: stack.controller, room: stack.livekit?.room });
|
|
5594
5884
|
};
|
|
5595
|
-
var
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5885
|
+
var RinggWidget = (props) => props.config ? /* @__PURE__ */ jsx28(ManagedRinggWidget, { config: props.config, ports: props.ports, onReady: props.onReady }) : /* @__PURE__ */ jsx28(ControlledRinggWidget, { controller: props.controller, room: props.room });
|
|
5886
|
+
var styles24 = StyleSheet24.create({
|
|
5887
|
+
overlay: { ...StyleSheet24.absoluteFill, justifyContent: "flex-end", alignItems: "flex-end" },
|
|
5888
|
+
panelAnchor: { marginHorizontal: EDGE_INSET, marginTop: EDGE_INSET, marginBottom: BOTTOM_SAFE_INSET },
|
|
5889
|
+
panel: {
|
|
5890
|
+
borderWidth: StyleSheet24.hairlineWidth,
|
|
5891
|
+
// `overflow: hidden` is what keeps the transcript, the header bar and the
|
|
5892
|
+
// rounded corners agreeing with each other.
|
|
5893
|
+
overflow: "hidden",
|
|
5894
|
+
// Web stacks three shadows at 1-2% alpha — the panel sits just off the page
|
|
5895
|
+
// rather than floating above it. 12% read as a hard slab, and on Android
|
|
5896
|
+
// `elevation` is the only one of these that applies, so it was the entire
|
|
5897
|
+
// effect instead of a supporting layer.
|
|
5898
|
+
shadowColor: "#000000",
|
|
5899
|
+
shadowOpacity: 0.05,
|
|
5900
|
+
shadowRadius: 20,
|
|
5901
|
+
shadowOffset: { width: 0, height: 8 },
|
|
5902
|
+
elevation: 4
|
|
5601
5903
|
},
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
}
|
|
5904
|
+
trigger: { position: "absolute", right: EDGE_INSET, bottom: BOTTOM_SAFE_INSET },
|
|
5905
|
+
triggerSurface: { width: TRIGGER_SIZE, height: TRIGGER_SIZE, borderRadius: TRIGGER_RADIUS, alignItems: "center", justifyContent: "center" },
|
|
5906
|
+
chromeButton: { position: "absolute", top: 10, zIndex: 20, width: 28, height: 28, borderRadius: 14, alignItems: "center", justifyContent: "center" },
|
|
5907
|
+
minimizeButton: { right: 42 },
|
|
5908
|
+
closeButton: { right: 10 },
|
|
5909
|
+
body: { flex: 1 },
|
|
5910
|
+
bodyLive: { paddingHorizontal: 20, paddingVertical: 8, gap: 10 },
|
|
5911
|
+
bodyIdle: { padding: 24, gap: 12 },
|
|
5912
|
+
bodySpacer: { flex: 1, marginTop: 56 },
|
|
5913
|
+
voiceWrapper: { width: "100%", alignItems: "stretch", justifyContent: "center", marginTop: 56 },
|
|
5914
|
+
voiceWrapperCompact: { height: 56, flexShrink: 0 },
|
|
5915
|
+
voiceWrapperFull: { flex: 1, minHeight: 0 },
|
|
5916
|
+
transcript: { flex: 1 },
|
|
5917
|
+
// `justifyContent: flex-end` is web's `mt-auto`: a short conversation sits at
|
|
5918
|
+
// the bottom of the panel rather than floating at the top.
|
|
5919
|
+
transcriptList: { flexGrow: 1, justifyContent: "flex-end", gap: 12 },
|
|
5920
|
+
transcriptTopClearance: { paddingTop: 64 },
|
|
5921
|
+
transcriptBottomClearance: { paddingBottom: 80 },
|
|
5922
|
+
fullWidth: { width: "100%" },
|
|
5923
|
+
controlsBar: { position: "absolute", zIndex: 10, bottom: 0, left: 0, right: 0, paddingHorizontal: 20, paddingVertical: 12 },
|
|
5924
|
+
error: { textAlign: "center", fontSize: 14, marginBottom: 8 },
|
|
5925
|
+
connecting: { fontSize: 14, textAlign: "center" },
|
|
5926
|
+
startActions: { flexDirection: "row", alignItems: "center", gap: 8, width: "100%" },
|
|
5927
|
+
legal: { fontSize: 12, lineHeight: 20, textAlign: "center" }
|
|
5609
5928
|
});
|
|
5610
5929
|
|
|
5611
5930
|
// src/platform/app-origin.ts
|
|
@@ -5673,26 +5992,73 @@ var createHostActionDispatcher = (onAction) => (action, onLog) => {
|
|
|
5673
5992
|
}
|
|
5674
5993
|
};
|
|
5675
5994
|
export {
|
|
5995
|
+
CHAT_WIDGET_AFTER_TEXT_MS,
|
|
5996
|
+
CHAT_WIDGET_GRACE_MS,
|
|
5997
|
+
WIDGET_DEFAULTS as DEFAULT_CONFIG,
|
|
5998
|
+
DEFAULT_ENVIRONMENT_URLS,
|
|
5999
|
+
DEFAULT_WIDGET_THEME,
|
|
5676
6000
|
GradientFill,
|
|
6001
|
+
MIN_TYPING_INDICATOR_MS,
|
|
5677
6002
|
Markdown,
|
|
5678
6003
|
RinggWidget,
|
|
6004
|
+
WIDGET_PRESET_THEMES,
|
|
5679
6005
|
WidgetThemeProvider,
|
|
5680
6006
|
appOrigin,
|
|
6007
|
+
buildPayload,
|
|
6008
|
+
colorToBackground,
|
|
6009
|
+
colorToBorder,
|
|
6010
|
+
colorToSolid,
|
|
5681
6011
|
createAudioSession,
|
|
5682
6012
|
createCallbackEventBus,
|
|
6013
|
+
createComponentStore,
|
|
6014
|
+
createDomEventBus,
|
|
5683
6015
|
createHostActionDispatcher,
|
|
5684
6016
|
createLiveKitTransport,
|
|
6017
|
+
createMessageStore,
|
|
5685
6018
|
createNativeMicPermission,
|
|
5686
6019
|
createNotificationPlayer,
|
|
6020
|
+
createRinggApiClient,
|
|
5687
6021
|
createRinggWidgetController,
|
|
6022
|
+
createSessionStore,
|
|
6023
|
+
createShellStore,
|
|
5688
6024
|
createSilentNotificationPlayer,
|
|
6025
|
+
createSlashCommandStore,
|
|
6026
|
+
createStaticUrlResolver,
|
|
6027
|
+
createStore,
|
|
6028
|
+
createSystemClock,
|
|
6029
|
+
createTypingStore,
|
|
6030
|
+
defaultUrlResolver,
|
|
6031
|
+
extractColorsFromGradient,
|
|
6032
|
+
formatBlocksAction,
|
|
6033
|
+
formatComponentResponse,
|
|
6034
|
+
formatDynamicData,
|
|
6035
|
+
formatSlashCommand,
|
|
6036
|
+
formatSlotDate,
|
|
6037
|
+
formatSlotTime,
|
|
6038
|
+
getButtonRadius,
|
|
6039
|
+
getContrastingTextColor,
|
|
6040
|
+
getDominantColor,
|
|
6041
|
+
grantedMicPermission,
|
|
6042
|
+
groupSlotsByDate,
|
|
6043
|
+
isBlocks,
|
|
6044
|
+
isButtons,
|
|
6045
|
+
isCalendarBooking,
|
|
6046
|
+
isConfirmation,
|
|
6047
|
+
isForm,
|
|
6048
|
+
isGradient,
|
|
6049
|
+
isInteractiveFlow,
|
|
6050
|
+
isLightColor,
|
|
6051
|
+
mergeComponentTheme,
|
|
6052
|
+
mergeWidgetTheme,
|
|
5689
6053
|
resolveButtonRadius,
|
|
5690
6054
|
resolveFontFamily,
|
|
5691
6055
|
resolveRadius,
|
|
5692
6056
|
ringgId,
|
|
6057
|
+
silentNotificationPlayer,
|
|
5693
6058
|
solidColor,
|
|
5694
6059
|
toSize,
|
|
5695
6060
|
toViewStyle,
|
|
6061
|
+
transformRpcToComponent,
|
|
5696
6062
|
useRinggComponents,
|
|
5697
6063
|
useRinggMessages,
|
|
5698
6064
|
useRinggSession,
|