@vanira/sdk-react-native 0.0.2

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 (148) hide show
  1. package/README.md +239 -0
  2. package/package.json +53 -0
  3. package/src/__tests__/WebRTCClient.integration.test.ts +396 -0
  4. package/src/__tests__/adapters.test.ts +475 -0
  5. package/src/__tests__/httpResponse.test.ts +25 -0
  6. package/src/__tests__/mocks/react-native-incall-manager.ts +8 -0
  7. package/src/__tests__/mocks/react-native-permissions.ts +15 -0
  8. package/src/__tests__/mocks/react-native-webrtc.ts +6 -0
  9. package/src/__tests__/mocks/react-native.ts +28 -0
  10. package/src/__tests__/preset.test.ts +239 -0
  11. package/src/__tests__/resolveRuntimeConfig.test.ts +90 -0
  12. package/src/__tests__/storage.test.ts +211 -0
  13. package/src/__tests__/webrtcSignaling.test.ts +42 -0
  14. package/src/adapters/PeerConnectionAdapter.ts +101 -0
  15. package/src/adapters/browser/BrowserAudioAdapter.ts +43 -0
  16. package/src/adapters/browser/BrowserDataChannelAdapter.ts +69 -0
  17. package/src/adapters/browser/BrowserMediaAdapter.ts +15 -0
  18. package/src/adapters/browser/BrowserPeerAdapter.ts +14 -0
  19. package/src/adapters/browser/index.ts +4 -0
  20. package/src/adapters/interfaces.ts +84 -0
  21. package/src/adapters/react-native/RNAudioAdapter.ts +42 -0
  22. package/src/adapters/react-native/RNDataChannelAdapter.ts +79 -0
  23. package/src/adapters/react-native/RNMediaAdapter.ts +46 -0
  24. package/src/adapters/react-native/RNPeerAdapter.ts +28 -0
  25. package/src/adapters/react-native/callAudioRouting.ts +115 -0
  26. package/src/adapters/react-native/decodeUtf8.ts +72 -0
  27. package/src/adapters/react-native/index.ts +4 -0
  28. package/src/adapters/react-native/rnUploadFile.ts +76 -0
  29. package/src/adapters/storage/BrowserDualStorageAdapter.ts +71 -0
  30. package/src/adapters/storage/MemoryStorageAdapter.ts +50 -0
  31. package/src/adapters/storage/StorageAdapter.ts +21 -0
  32. package/src/adapters/storage/createSyncStorageAdapter.ts +40 -0
  33. package/src/adapters/storage/index.ts +7 -0
  34. package/src/api/services/ChatService.ts +304 -0
  35. package/src/api/services/ConfigService.ts +33 -0
  36. package/src/assets/icons.js +35 -0
  37. package/src/cdn.ts +68 -0
  38. package/src/core/CallSessionStore.ts +137 -0
  39. package/src/core/DraggableController.ts +83 -0
  40. package/src/core/SessionManager.ts +322 -0
  41. package/src/core/VaniraAI.ts +464 -0
  42. package/src/core/WebRTCClient.ts +1012 -0
  43. package/src/core/httpResponse.ts +22 -0
  44. package/src/core/iceServers.ts +18 -0
  45. package/src/core/toolCallNormalize.ts +80 -0
  46. package/src/core/voice-client.js +236 -0
  47. package/src/core/webrtcSignaling.ts +72 -0
  48. package/src/index.js +34 -0
  49. package/src/index.ts +6 -0
  50. package/src/platforms/browser.ts +67 -0
  51. package/src/platforms/react-native.ts +105 -0
  52. package/src/presets/BookingCalendarModal.tsx +457 -0
  53. package/src/presets/CameraModal.tsx +576 -0
  54. package/src/presets/DynamicFormModal.tsx +378 -0
  55. package/src/presets/NativePresetRenderer.tsx +350 -0
  56. package/src/presets/NavigateHandler.tsx +75 -0
  57. package/src/presets/PresetHost.tsx +155 -0
  58. package/src/presets/PresetShellModal.tsx +97 -0
  59. package/src/presets/UploadModal.tsx +321 -0
  60. package/src/presets/calendar/calendarUtils.ts +386 -0
  61. package/src/presets/call/CallSpeakerToggle.tsx +59 -0
  62. package/src/presets/call/callAudioRouting.ts +2 -0
  63. package/src/presets/call/useCallSpeaker.ts +31 -0
  64. package/src/presets/camera/cameraPermissions.ts +18 -0
  65. package/src/presets/camera/cameraStream.ts +19 -0
  66. package/src/presets/camera/cameraUtils.ts +21 -0
  67. package/src/presets/camera/useLivenessFlow.ts +95 -0
  68. package/src/presets/chalkboard/ChalkboardOverlay.tsx +156 -0
  69. package/src/presets/chalkboard/EraseTextHandler.tsx +95 -0
  70. package/src/presets/chalkboard/TypeTextHandler.tsx +107 -0
  71. package/src/presets/chalkboard/boardAbort.ts +36 -0
  72. package/src/presets/chalkboard/boardQueue.ts +620 -0
  73. package/src/presets/chalkboard/chalkboardSession.ts +75 -0
  74. package/src/presets/chalkboard/drawUtils.ts +123 -0
  75. package/src/presets/chalkboard/textUtils.ts +109 -0
  76. package/src/presets/clipRegion/ClipRegionModal.tsx +261 -0
  77. package/src/presets/clipRegion/clipRegionBridge.ts +19 -0
  78. package/src/presets/form/formValidation.ts +104 -0
  79. package/src/presets/form/parseFormFields.ts +171 -0
  80. package/src/presets/host/HostElementPresetHandler.tsx +155 -0
  81. package/src/presets/host/hostPresetBridge.ts +71 -0
  82. package/src/presets/index.ts +63 -0
  83. package/src/presets/liveScreen/CloseLiveScreenHandler.tsx +36 -0
  84. package/src/presets/liveScreen/LiveScreenCaptureHost.tsx +312 -0
  85. package/src/presets/liveScreen/LiveScreenHandler.tsx +25 -0
  86. package/src/presets/liveScreen/LiveScreenPipOverlay.tsx +6 -0
  87. package/src/presets/liveScreen/liveScreenSession.ts +73 -0
  88. package/src/presets/liveVision/CloseLiveVisionHandler.tsx +29 -0
  89. package/src/presets/liveVision/LiveVisionCameraHost.tsx +317 -0
  90. package/src/presets/liveVision/LiveVisionHandler.tsx +26 -0
  91. package/src/presets/liveVision/LiveVisionPipOverlay.tsx +7 -0
  92. package/src/presets/liveVision/liveVisionFrameLoop.ts +38 -0
  93. package/src/presets/liveVision/liveVisionSession.ts +75 -0
  94. package/src/presets/liveVision/liveVisionUpload.ts +62 -0
  95. package/src/presets/navigation/internalRouteRegistry.ts +25 -0
  96. package/src/presets/navigation/navigationBridge.ts +76 -0
  97. package/src/presets/navigation/navigationTypes.ts +12 -0
  98. package/src/presets/parseToolCall.ts +60 -0
  99. package/src/presets/presetClientAdapter.ts +29 -0
  100. package/src/presets/presetCompletion.ts +91 -0
  101. package/src/presets/presetEventHelpers.ts +45 -0
  102. package/src/presets/registry.ts +128 -0
  103. package/src/presets/streaming/mediaFrameUpload.ts +93 -0
  104. package/src/presets/types.ts +74 -0
  105. package/src/presets/upload/pickUploadFile.ts +256 -0
  106. package/src/presets/upload/uploadFormats.ts +163 -0
  107. package/src/presets/upload/uploadUtils.ts +68 -0
  108. package/src/react/PresetRenderer.tsx +144 -0
  109. package/src/react/index.ts +1 -0
  110. package/src/runtime/browserRuntime.ts +54 -0
  111. package/src/runtime/platform.ts +17 -0
  112. package/src/runtime/reactNativeRuntime.ts +68 -0
  113. package/src/runtime/resolveRuntimeConfig.ts +75 -0
  114. package/src/runtime/runtimeBundles.ts +74 -0
  115. package/src/runtime/types.ts +135 -0
  116. package/src/types/react-native-incall-manager.d.ts +17 -0
  117. package/src/types/react-native-webrtc.d.ts +47 -0
  118. package/src/types.ts +133 -0
  119. package/src/ui/VaniraWidget.ts +87 -0
  120. package/src/ui/abstraction/AbstractWidgetProvider.ts +18 -0
  121. package/src/ui/abstraction/interfaces.ts +12 -0
  122. package/src/ui/adapters/VaniraChatAdapter.ts +42 -0
  123. package/src/ui/components/AvatarView.ts +81 -0
  124. package/src/ui/components/ChatWindow.ts +263 -0
  125. package/src/ui/components/FloatingButton.ts +163 -0
  126. package/src/ui/components/FloatingWelcomeChips.ts +137 -0
  127. package/src/ui/components/Panel.ts +120 -0
  128. package/src/ui/components/VoiceOrb.ts +79 -0
  129. package/src/ui/components/VoiceOverlay.ts +497 -0
  130. package/src/ui/components/index.ts +7 -0
  131. package/src/ui/factory/WidgetFactory.ts +16 -0
  132. package/src/ui/icons_data.ts +2 -0
  133. package/src/ui/presets/WidgetPresetRenderer.ts +1802 -0
  134. package/src/ui/presets/types.ts +16 -0
  135. package/src/ui/providers/VaniraInternalProvider.ts +1066 -0
  136. package/src/ui/styles/index.ts +323 -0
  137. package/src/ui/styles/keyframes.ts +76 -0
  138. package/src/ui/styles/theme.ts +57 -0
  139. package/src/ui/styles/widget.css.ts +838 -0
  140. package/src/ui/utils.ts +37 -0
  141. package/src/ui/views/AbstractChatView.ts +93 -0
  142. package/src/ui/views/AbstractVoiceView.ts +57 -0
  143. package/src/ui/views/AvatarOnlyView.ts +78 -0
  144. package/src/ui/views/ChatAvatarView.ts +66 -0
  145. package/src/ui/views/ChatOnlyView.ts +28 -0
  146. package/src/ui/views/ChatVoiceView.ts +15 -0
  147. package/src/ui/views/VoiceOnlyView.ts +25 -0
  148. package/src/ui/views/index.ts +5 -0
@@ -0,0 +1,378 @@
1
+ import React, {useCallback, useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Pressable,
5
+ ScrollView,
6
+ StyleSheet,
7
+ Switch,
8
+ Text,
9
+ TextInput,
10
+ View,
11
+ } from 'react-native';
12
+ import {
13
+ buildFormSubmitPayload,
14
+ validateFormFields,
15
+ type FormValues,
16
+ } from './form/formValidation';
17
+ import {parseFormFields, type FormFieldDefinition} from './form/parseFormFields';
18
+ import {PresetShellModal} from './PresetShellModal';
19
+ import type {PresetContext} from './types';
20
+
21
+ type Props = {
22
+ visible: boolean;
23
+ ctx: PresetContext;
24
+ onClose: () => void;
25
+ onCancel: (reason?: string) => void;
26
+ onComplete: (payload: Record<string, unknown>) => void;
27
+ };
28
+
29
+ const SUBMIT_DELAY_MS = 300;
30
+
31
+ function optionLabel(opt: FormFieldDefinition['options'][number]): string {
32
+ return typeof opt === 'string' ? opt : opt.label;
33
+ }
34
+
35
+ function optionValue(opt: FormFieldDefinition['options'][number]): string {
36
+ return typeof opt === 'string' ? opt : opt.value;
37
+ }
38
+
39
+ export function DynamicFormModal({
40
+ visible,
41
+ ctx,
42
+ onClose,
43
+ onCancel,
44
+ onComplete,
45
+ }: Props) {
46
+ const merged = useMemo(
47
+ () => ({...ctx.args, ...ctx.clientFields}),
48
+ [ctx.args, ctx.clientFields],
49
+ );
50
+
51
+ const title = String(
52
+ merged.title ?? ctx.args.title ?? 'Please fill out this form',
53
+ );
54
+ const reason = String(
55
+ ctx.args.reason ?? merged.reason ?? 'Please provide the following information.',
56
+ );
57
+
58
+ const fields = useMemo(
59
+ () => parseFormFields(merged.fields ?? ctx.args.fields),
60
+ [merged.fields, ctx.args.fields],
61
+ );
62
+
63
+ const [values, setValues] = useState<FormValues>({});
64
+ const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
65
+ const [formError, setFormError] = useState<string | null>(null);
66
+ const [submitting, setSubmitting] = useState(false);
67
+
68
+ useEffect(() => {
69
+ if (!visible) {
70
+ return;
71
+ }
72
+ const initial: FormValues = {};
73
+ for (const field of fields) {
74
+ initial[field.id] = field.type === 'checkbox' ? false : '';
75
+ }
76
+ setValues(initial);
77
+ setFieldErrors({});
78
+ setFormError(null);
79
+ setSubmitting(false);
80
+ }, [visible, ctx.toolCallId, fields]);
81
+
82
+ const setFieldValue = useCallback(
83
+ (id: string, value: string | boolean) => {
84
+ setValues(prev => ({...prev, [id]: value}));
85
+ setFieldErrors(prev => {
86
+ if (!prev[id]) {
87
+ return prev;
88
+ }
89
+ const next = {...prev};
90
+ delete next[id];
91
+ return next;
92
+ });
93
+ setFormError(null);
94
+ },
95
+ [],
96
+ );
97
+
98
+ const handleSubmit = useCallback(() => {
99
+ if (submitting) {
100
+ return;
101
+ }
102
+ if (fields.length === 0) {
103
+ setFormError('No form fields configured.');
104
+ return;
105
+ }
106
+
107
+ const errors = validateFormFields(fields, values);
108
+ if (Object.keys(errors).length > 0) {
109
+ setFieldErrors(errors);
110
+ setFormError('Please fix the highlighted fields.');
111
+ return;
112
+ }
113
+
114
+ setSubmitting(true);
115
+ setFormError(null);
116
+ const payload = buildFormSubmitPayload(fields, values);
117
+
118
+ setTimeout(() => {
119
+ setSubmitting(false);
120
+ onComplete(payload);
121
+ }, SUBMIT_DELAY_MS);
122
+ }, [fields, onComplete, submitting, values]);
123
+
124
+ const renderField = (field: FormFieldDefinition) => {
125
+ const error = fieldErrors[field.id];
126
+ const label = `${field.label}${field.required ? ' *' : ''}`;
127
+
128
+ switch (field.type) {
129
+ case 'textarea':
130
+ return (
131
+ <View key={field.id} style={styles.fieldWrap}>
132
+ <Text style={styles.label}>{label}</Text>
133
+ <TextInput
134
+ style={[styles.input, styles.textarea, error && styles.inputError]}
135
+ multiline
136
+ numberOfLines={4}
137
+ value={String(values[field.id] ?? '')}
138
+ placeholder={field.placeholder}
139
+ onChangeText={text => setFieldValue(field.id, text)}
140
+ editable={!submitting}
141
+ />
142
+ {error ? <Text style={styles.fieldError}>{error}</Text> : null}
143
+ </View>
144
+ );
145
+
146
+ case 'select':
147
+ return (
148
+ <View key={field.id} style={styles.fieldWrap}>
149
+ <Text style={styles.label}>{label}</Text>
150
+ <View style={styles.optionRow}>
151
+ {(field.options.length > 0
152
+ ? field.options
153
+ : ['Option 1']
154
+ ).map(opt => {
155
+ const val = optionValue(opt);
156
+ const selected = values[field.id] === val;
157
+ return (
158
+ <Pressable
159
+ key={val}
160
+ style={[styles.chip, selected && styles.chipSelected]}
161
+ onPress={() => setFieldValue(field.id, val)}
162
+ disabled={submitting}>
163
+ <Text
164
+ style={[
165
+ styles.chipText,
166
+ selected && styles.chipTextSelected,
167
+ ]}>
168
+ {optionLabel(opt)}
169
+ </Text>
170
+ </Pressable>
171
+ );
172
+ })}
173
+ </View>
174
+ {error ? <Text style={styles.fieldError}>{error}</Text> : null}
175
+ </View>
176
+ );
177
+
178
+ case 'radio':
179
+ return (
180
+ <View key={field.id} style={styles.fieldWrap}>
181
+ <Text style={styles.label}>{label}</Text>
182
+ {(field.options.length > 0 ? field.options : ['Yes', 'No']).map(
183
+ opt => {
184
+ const val = optionValue(opt);
185
+ const selected = values[field.id] === val;
186
+ return (
187
+ <Pressable
188
+ key={val}
189
+ style={styles.radioRow}
190
+ onPress={() => setFieldValue(field.id, val)}
191
+ disabled={submitting}>
192
+ <View
193
+ style={[
194
+ styles.radioOuter,
195
+ selected && styles.radioOuterSelected,
196
+ ]}>
197
+ {selected ? <View style={styles.radioInner} /> : null}
198
+ </View>
199
+ <Text style={styles.radioLabel}>{optionLabel(opt)}</Text>
200
+ </Pressable>
201
+ );
202
+ },
203
+ )}
204
+ {error ? <Text style={styles.fieldError}>{error}</Text> : null}
205
+ </View>
206
+ );
207
+
208
+ case 'checkbox':
209
+ return (
210
+ <View key={field.id} style={styles.fieldWrap}>
211
+ <View style={styles.checkboxRow}>
212
+ <Switch
213
+ value={values[field.id] === true}
214
+ onValueChange={v => setFieldValue(field.id, v)}
215
+ disabled={submitting}
216
+ />
217
+ <Text style={styles.checkboxLabel}>{label}</Text>
218
+ </View>
219
+ {error ? <Text style={styles.fieldError}>{error}</Text> : null}
220
+ </View>
221
+ );
222
+
223
+ default: {
224
+ const keyboardType =
225
+ field.type === 'email'
226
+ ? 'email-address'
227
+ : field.type === 'phone'
228
+ ? 'phone-pad'
229
+ : field.type === 'number'
230
+ ? 'numeric'
231
+ : 'default';
232
+ return (
233
+ <View key={field.id} style={styles.fieldWrap}>
234
+ <Text style={styles.label}>{label}</Text>
235
+ <TextInput
236
+ style={[styles.input, error && styles.inputError]}
237
+ value={String(values[field.id] ?? '')}
238
+ placeholder={
239
+ field.placeholder ??
240
+ (field.type === 'date'
241
+ ? 'YYYY-MM-DD'
242
+ : `Enter your ${field.label.toLowerCase()}`)
243
+ }
244
+ keyboardType={keyboardType}
245
+ autoCapitalize={field.type === 'email' ? 'none' : 'sentences'}
246
+ onChangeText={text => setFieldValue(field.id, text)}
247
+ editable={!submitting}
248
+ />
249
+ {error ? <Text style={styles.fieldError}>{error}</Text> : null}
250
+ </View>
251
+ );
252
+ }
253
+ }
254
+ };
255
+
256
+ return (
257
+ <PresetShellModal
258
+ visible={visible}
259
+ title={title}
260
+ subtitle={reason}
261
+ onClose={onClose}
262
+ hideDefaultClose>
263
+ <ScrollView
264
+ style={styles.scroll}
265
+ contentContainerStyle={styles.scrollContent}
266
+ keyboardShouldPersistTaps="handled">
267
+ {fields.length === 0 ? (
268
+ <Text style={styles.formError}>No form fields configured.</Text>
269
+ ) : (
270
+ fields.map(renderField)
271
+ )}
272
+ {formError ? <Text style={styles.formError}>{formError}</Text> : null}
273
+ </ScrollView>
274
+
275
+ <View style={styles.actions}>
276
+ <Pressable
277
+ style={styles.cancelBtn}
278
+ onPress={() => onCancel('User cancelled')}
279
+ disabled={submitting}>
280
+ <Text style={styles.cancelBtnText}>Cancel</Text>
281
+ </Pressable>
282
+ <Pressable
283
+ style={[styles.submitBtn, submitting && styles.submitDisabled]}
284
+ onPress={handleSubmit}
285
+ disabled={submitting || fields.length === 0}>
286
+ {submitting ? (
287
+ <ActivityIndicator color="#fff" />
288
+ ) : (
289
+ <Text style={styles.submitBtnText}>Submit</Text>
290
+ )}
291
+ </Pressable>
292
+ </View>
293
+ </PresetShellModal>
294
+ );
295
+ }
296
+
297
+ const styles = StyleSheet.create({
298
+ scroll: {maxHeight: 400},
299
+ scrollContent: {paddingBottom: 8, gap: 12},
300
+ fieldWrap: {gap: 6},
301
+ label: {fontSize: 13, fontWeight: '600', color: '#374151'},
302
+ input: {
303
+ borderWidth: 1,
304
+ borderColor: '#e5e7eb',
305
+ borderRadius: 10,
306
+ paddingHorizontal: 12,
307
+ paddingVertical: 10,
308
+ fontSize: 15,
309
+ color: '#111',
310
+ backgroundColor: '#fafafa',
311
+ },
312
+ textarea: {minHeight: 96, textAlignVertical: 'top'},
313
+ inputError: {borderColor: '#ef4444'},
314
+ fieldError: {fontSize: 12, color: '#ef4444'},
315
+ formError: {
316
+ fontSize: 13,
317
+ color: '#ef4444',
318
+ textAlign: 'center',
319
+ marginTop: 8,
320
+ },
321
+ optionRow: {flexDirection: 'row', flexWrap: 'wrap', gap: 8},
322
+ chip: {
323
+ paddingHorizontal: 12,
324
+ paddingVertical: 8,
325
+ borderRadius: 8,
326
+ borderWidth: 1,
327
+ borderColor: '#e5e7eb',
328
+ backgroundColor: '#f9fafb',
329
+ },
330
+ chipSelected: {backgroundColor: '#4f46e5', borderColor: '#4f46e5'},
331
+ chipText: {fontSize: 13, color: '#111'},
332
+ chipTextSelected: {color: '#fff', fontWeight: '600'},
333
+ radioRow: {
334
+ flexDirection: 'row',
335
+ alignItems: 'center',
336
+ gap: 10,
337
+ paddingVertical: 4,
338
+ },
339
+ radioOuter: {
340
+ width: 20,
341
+ height: 20,
342
+ borderRadius: 10,
343
+ borderWidth: 2,
344
+ borderColor: '#9ca3af',
345
+ alignItems: 'center',
346
+ justifyContent: 'center',
347
+ },
348
+ radioOuterSelected: {borderColor: '#4f46e5'},
349
+ radioInner: {
350
+ width: 10,
351
+ height: 10,
352
+ borderRadius: 5,
353
+ backgroundColor: '#4f46e5',
354
+ },
355
+ radioLabel: {fontSize: 14, color: '#111'},
356
+ checkboxRow: {flexDirection: 'row', alignItems: 'center', gap: 10},
357
+ checkboxLabel: {fontSize: 14, color: '#111', flex: 1},
358
+ actions: {flexDirection: 'row', gap: 10, marginTop: 12},
359
+ cancelBtn: {
360
+ flex: 1,
361
+ paddingVertical: 12,
362
+ borderRadius: 10,
363
+ backgroundColor: '#f3f4f6',
364
+ alignItems: 'center',
365
+ },
366
+ cancelBtnText: {color: '#111', fontWeight: '600'},
367
+ submitBtn: {
368
+ flex: 1,
369
+ paddingVertical: 12,
370
+ borderRadius: 10,
371
+ backgroundColor: '#4f46e5',
372
+ alignItems: 'center',
373
+ justifyContent: 'center',
374
+ minHeight: 44,
375
+ },
376
+ submitDisabled: {opacity: 0.6},
377
+ submitBtnText: {color: '#fff', fontWeight: '700'},
378
+ });