srs-heritage-chatbot 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +194 -0
  3. package/lib/commonjs/assets/chat-icon-mobile.svg +1 -0
  4. package/lib/commonjs/assets/heritage.png +0 -0
  5. package/lib/commonjs/assets/posiden.svg +51 -0
  6. package/lib/commonjs/components/LoadingTips.js +104 -0
  7. package/lib/commonjs/components/LoadingTips.js.map +1 -0
  8. package/lib/commonjs/components/email.js +461 -0
  9. package/lib/commonjs/components/email.js.map +1 -0
  10. package/lib/commonjs/components/feedback.js +114 -0
  11. package/lib/commonjs/components/feedback.js.map +1 -0
  12. package/lib/commonjs/components/header.js +126 -0
  13. package/lib/commonjs/components/header.js.map +1 -0
  14. package/lib/commonjs/components/input.js +144 -0
  15. package/lib/commonjs/components/input.js.map +1 -0
  16. package/lib/commonjs/components/productCard.js +688 -0
  17. package/lib/commonjs/components/productCard.js.map +1 -0
  18. package/lib/commonjs/components/progressCircle.js +99 -0
  19. package/lib/commonjs/components/progressCircle.js.map +1 -0
  20. package/lib/commonjs/components/testing.js +74 -0
  21. package/lib/commonjs/components/testing.js.map +1 -0
  22. package/lib/commonjs/components/voice.js +184 -0
  23. package/lib/commonjs/components/voice.js.map +1 -0
  24. package/lib/commonjs/components/welcomeButton.js +149 -0
  25. package/lib/commonjs/components/welcomeButton.js.map +1 -0
  26. package/lib/commonjs/components/welcomeInput.js +137 -0
  27. package/lib/commonjs/components/welcomeInput.js.map +1 -0
  28. package/lib/commonjs/contexts/AppContext.js +552 -0
  29. package/lib/commonjs/contexts/AppContext.js.map +1 -0
  30. package/lib/commonjs/hooks/Stream.js +599 -0
  31. package/lib/commonjs/hooks/Stream.js.map +1 -0
  32. package/lib/commonjs/hooks/useAsyncStorage.js +36 -0
  33. package/lib/commonjs/hooks/useAsyncStorage.js.map +1 -0
  34. package/lib/commonjs/index.js +44 -0
  35. package/lib/commonjs/index.js.map +1 -0
  36. package/lib/commonjs/layout/disclaimer.js +208 -0
  37. package/lib/commonjs/layout/disclaimer.js.map +1 -0
  38. package/lib/commonjs/layout/ex.js +254 -0
  39. package/lib/commonjs/layout/ex.js.map +1 -0
  40. package/lib/commonjs/layout/icon.js +118 -0
  41. package/lib/commonjs/layout/icon.js.map +1 -0
  42. package/lib/commonjs/layout/layout.js +168 -0
  43. package/lib/commonjs/layout/layout.js.map +1 -0
  44. package/lib/commonjs/layout/welcome.js +160 -0
  45. package/lib/commonjs/layout/welcome.js.map +1 -0
  46. package/lib/commonjs/layout/window.js +396 -0
  47. package/lib/commonjs/layout/window.js.map +1 -0
  48. package/lib/commonjs/utils/audioRecorder.js +412 -0
  49. package/lib/commonjs/utils/audioRecorder.js.map +1 -0
  50. package/lib/commonjs/utils/cloudinary.js +69 -0
  51. package/lib/commonjs/utils/cloudinary.js.map +1 -0
  52. package/lib/commonjs/utils/storage.js +76 -0
  53. package/lib/commonjs/utils/storage.js.map +1 -0
  54. package/lib/commonjs/utils/textToSpeech.js +53 -0
  55. package/lib/commonjs/utils/textToSpeech.js.map +1 -0
  56. package/lib/module/assets/chat-icon-mobile.svg +1 -0
  57. package/lib/module/assets/heritage.png +0 -0
  58. package/lib/module/assets/posiden.svg +51 -0
  59. package/lib/module/components/LoadingTips.js +95 -0
  60. package/lib/module/components/LoadingTips.js.map +1 -0
  61. package/lib/module/components/email.js +452 -0
  62. package/lib/module/components/email.js.map +1 -0
  63. package/lib/module/components/feedback.js +105 -0
  64. package/lib/module/components/feedback.js.map +1 -0
  65. package/lib/module/components/header.js +117 -0
  66. package/lib/module/components/header.js.map +1 -0
  67. package/lib/module/components/input.js +135 -0
  68. package/lib/module/components/input.js.map +1 -0
  69. package/lib/module/components/productCard.js +679 -0
  70. package/lib/module/components/productCard.js.map +1 -0
  71. package/lib/module/components/progressCircle.js +91 -0
  72. package/lib/module/components/progressCircle.js.map +1 -0
  73. package/lib/module/components/testing.js +66 -0
  74. package/lib/module/components/testing.js.map +1 -0
  75. package/lib/module/components/voice.js +175 -0
  76. package/lib/module/components/voice.js.map +1 -0
  77. package/lib/module/components/welcomeButton.js +140 -0
  78. package/lib/module/components/welcomeButton.js.map +1 -0
  79. package/lib/module/components/welcomeInput.js +128 -0
  80. package/lib/module/components/welcomeInput.js.map +1 -0
  81. package/lib/module/contexts/AppContext.js +542 -0
  82. package/lib/module/contexts/AppContext.js.map +1 -0
  83. package/lib/module/hooks/Stream.js +592 -0
  84. package/lib/module/hooks/Stream.js.map +1 -0
  85. package/lib/module/hooks/useAsyncStorage.js +29 -0
  86. package/lib/module/hooks/useAsyncStorage.js.map +1 -0
  87. package/lib/module/index.js +36 -0
  88. package/lib/module/index.js.map +1 -0
  89. package/lib/module/layout/disclaimer.js +199 -0
  90. package/lib/module/layout/disclaimer.js.map +1 -0
  91. package/lib/module/layout/ex.js +253 -0
  92. package/lib/module/layout/ex.js.map +1 -0
  93. package/lib/module/layout/icon.js +108 -0
  94. package/lib/module/layout/icon.js.map +1 -0
  95. package/lib/module/layout/layout.js +160 -0
  96. package/lib/module/layout/layout.js.map +1 -0
  97. package/lib/module/layout/welcome.js +150 -0
  98. package/lib/module/layout/welcome.js.map +1 -0
  99. package/lib/module/layout/window.js +387 -0
  100. package/lib/module/layout/window.js.map +1 -0
  101. package/lib/module/utils/audioRecorder.js +398 -0
  102. package/lib/module/utils/audioRecorder.js.map +1 -0
  103. package/lib/module/utils/cloudinary.js +61 -0
  104. package/lib/module/utils/cloudinary.js.map +1 -0
  105. package/lib/module/utils/storage.js +67 -0
  106. package/lib/module/utils/storage.js.map +1 -0
  107. package/lib/module/utils/textToSpeech.js +43 -0
  108. package/lib/module/utils/textToSpeech.js.map +1 -0
  109. package/lib/typescript/components/LoadingTips.d.ts +3 -0
  110. package/lib/typescript/components/LoadingTips.d.ts.map +1 -0
  111. package/lib/typescript/components/email.d.ts +6 -0
  112. package/lib/typescript/components/email.d.ts.map +1 -0
  113. package/lib/typescript/components/feedback.d.ts +6 -0
  114. package/lib/typescript/components/feedback.d.ts.map +1 -0
  115. package/lib/typescript/components/header.d.ts +3 -0
  116. package/lib/typescript/components/header.d.ts.map +1 -0
  117. package/lib/typescript/components/input.d.ts +6 -0
  118. package/lib/typescript/components/input.d.ts.map +1 -0
  119. package/lib/typescript/components/productCard.d.ts +7 -0
  120. package/lib/typescript/components/productCard.d.ts.map +1 -0
  121. package/lib/typescript/components/progressCircle.d.ts +3 -0
  122. package/lib/typescript/components/progressCircle.d.ts.map +1 -0
  123. package/lib/typescript/components/testing.d.ts +6 -0
  124. package/lib/typescript/components/testing.d.ts.map +1 -0
  125. package/lib/typescript/components/voice.d.ts +5 -0
  126. package/lib/typescript/components/voice.d.ts.map +1 -0
  127. package/lib/typescript/components/welcomeButton.d.ts +4 -0
  128. package/lib/typescript/components/welcomeButton.d.ts.map +1 -0
  129. package/lib/typescript/components/welcomeInput.d.ts +6 -0
  130. package/lib/typescript/components/welcomeInput.d.ts.map +1 -0
  131. package/lib/typescript/contexts/AppContext.d.ts +10 -0
  132. package/lib/typescript/contexts/AppContext.d.ts.map +1 -0
  133. package/lib/typescript/hooks/Stream.d.ts +2 -0
  134. package/lib/typescript/hooks/Stream.d.ts.map +1 -0
  135. package/lib/typescript/hooks/useAsyncStorage.d.ts +2 -0
  136. package/lib/typescript/hooks/useAsyncStorage.d.ts.map +1 -0
  137. package/lib/typescript/index.d.ts +8 -0
  138. package/lib/typescript/index.d.ts.map +1 -0
  139. package/lib/typescript/layout/disclaimer.d.ts +5 -0
  140. package/lib/typescript/layout/disclaimer.d.ts.map +1 -0
  141. package/lib/typescript/layout/ex.d.ts +1 -0
  142. package/lib/typescript/layout/ex.d.ts.map +1 -0
  143. package/lib/typescript/layout/icon.d.ts +3 -0
  144. package/lib/typescript/layout/icon.d.ts.map +1 -0
  145. package/lib/typescript/layout/layout.d.ts +3 -0
  146. package/lib/typescript/layout/layout.d.ts.map +1 -0
  147. package/lib/typescript/layout/welcome.d.ts +6 -0
  148. package/lib/typescript/layout/welcome.d.ts.map +1 -0
  149. package/lib/typescript/layout/window.d.ts +5 -0
  150. package/lib/typescript/layout/window.d.ts.map +1 -0
  151. package/lib/typescript/utils/audioRecorder.d.ts +9 -0
  152. package/lib/typescript/utils/audioRecorder.d.ts.map +1 -0
  153. package/lib/typescript/utils/cloudinary.d.ts +17 -0
  154. package/lib/typescript/utils/cloudinary.d.ts.map +1 -0
  155. package/lib/typescript/utils/storage.d.ts +29 -0
  156. package/lib/typescript/utils/storage.d.ts.map +1 -0
  157. package/lib/typescript/utils/textToSpeech.d.ts +2 -0
  158. package/lib/typescript/utils/textToSpeech.d.ts.map +1 -0
  159. package/package.json +109 -0
  160. package/src/assets/chat-icon-mobile.svg +1 -0
  161. package/src/assets/heritage.png +0 -0
  162. package/src/assets/posiden.svg +51 -0
  163. package/src/components/LoadingTips.js +99 -0
  164. package/src/components/email.js +467 -0
  165. package/src/components/feedback.js +114 -0
  166. package/src/components/header.js +119 -0
  167. package/src/components/input.js +133 -0
  168. package/src/components/productCard.js +815 -0
  169. package/src/components/progressCircle.js +88 -0
  170. package/src/components/testing.js +60 -0
  171. package/src/components/voice.js +228 -0
  172. package/src/components/welcomeButton.js +161 -0
  173. package/src/components/welcomeInput.js +133 -0
  174. package/src/contexts/AppContext.js +678 -0
  175. package/src/hooks/Stream.js +655 -0
  176. package/src/hooks/useAsyncStorage.js +33 -0
  177. package/src/index.js +30 -0
  178. package/src/layout/disclaimer.js +231 -0
  179. package/src/layout/ex.js +252 -0
  180. package/src/layout/icon.js +105 -0
  181. package/src/layout/layout.js +160 -0
  182. package/src/layout/welcome.js +172 -0
  183. package/src/layout/window.js +476 -0
  184. package/src/utils/audioRecorder.js +445 -0
  185. package/src/utils/cloudinary.js +61 -0
  186. package/src/utils/storage.ts +89 -0
  187. package/src/utils/textToSpeech.js +49 -0
@@ -0,0 +1,445 @@
1
+ // audioRecorder.js
2
+
3
+ import { Platform } from 'react-native';
4
+ import Voice from '@react-native-voice/voice';
5
+ import { check, PERMISSIONS, request, RESULTS } from 'react-native-permissions';
6
+
7
+ let resultCallback = null;
8
+ let partialResultCallback = null;
9
+ let silenceTimer = null;
10
+ let isCurrentlyRecording = false;
11
+ let finalResult = '';
12
+ const SILENCE_DURATION = 5000; // 5 seconds of silence before stopping
13
+
14
+ // Add this constant for AsyncStorage key
15
+ const PERMISSION_STORAGE_KEY = '@voice_permission_status';
16
+
17
+ // Create a function that can be called to get permission status
18
+ // This needs to be outside the React component lifecycle
19
+ let permissionStatusGetter = null;
20
+ let permissionStatusSetter = null;
21
+
22
+ export function setPermissionStatusHandlers(getter, setter) {
23
+ permissionStatusGetter = getter;
24
+ permissionStatusSetter = setter;
25
+ }
26
+
27
+ // Initialize Voice handlers - modified to support live transcription
28
+ export async function initVoice(onResult, onPartialResult = null) {
29
+ try {
30
+ resultCallback = onResult;
31
+ partialResultCallback = onPartialResult; // Store partial callback
32
+ finalResult = '';
33
+
34
+ // Check if Voice module is available
35
+ if (!Voice) {
36
+ console.error('Voice module is not available');
37
+ return false;
38
+ }
39
+
40
+ // First check if speech recognition is available
41
+ const isAvailable = await Voice.isAvailable();
42
+ if (!isAvailable) {
43
+ console.error('Speech recognition is not available on this device');
44
+ return false;
45
+ }
46
+
47
+ // Remove any existing listeners
48
+ Voice.removeAllListeners();
49
+
50
+ // Set up all event listeners
51
+ Voice.onSpeechStart = e => {
52
+ console.log('onSpeechStart: ', e);
53
+ isCurrentlyRecording = true;
54
+ finalResult = '';
55
+
56
+ // Start silence timer immediately when recording begins
57
+ handleSilenceDetection();
58
+ };
59
+
60
+ Voice.onSpeechRecognized = e => {
61
+ console.log('onSpeechRecognized: ', e);
62
+ if (e.isFinal) {
63
+ console.log('Speech recognition final');
64
+ handleFinalResult();
65
+ }
66
+ };
67
+
68
+ Voice.onSpeechEnd = async e => {
69
+ console.log('onSpeechEnd: ', e);
70
+
71
+ if (silenceTimer) {
72
+ clearTimeout(silenceTimer);
73
+ silenceTimer = null;
74
+ }
75
+
76
+ // Only handle final result if we're still recording
77
+ if (isCurrentlyRecording) {
78
+ await handleFinalResult();
79
+ }
80
+ };
81
+
82
+ Voice.onSpeechError = async e => {
83
+ console.log('onSpeechError: ', e);
84
+
85
+ if (silenceTimer) {
86
+ clearTimeout(silenceTimer);
87
+ silenceTimer = null;
88
+ }
89
+
90
+ const code = e.error?.code?.toString();
91
+ const msg = e.error?.message || '';
92
+
93
+ // Handle Android-specific errors
94
+ if (Platform.OS === 'android' && (code === '7' || code === '5')) {
95
+ if (finalResult && resultCallback) {
96
+ resultCallback(finalResult, null);
97
+ }
98
+ } else if (!msg.includes('No speech detected') && resultCallback) {
99
+ resultCallback(null, msg);
100
+ }
101
+
102
+ await cleanupVoiceSession();
103
+ };
104
+
105
+ Voice.onSpeechResults = e => {
106
+ console.log('onSpeechResults: ', e);
107
+ if (e.value && e.value.length > 0) {
108
+ finalResult = e.value[0];
109
+ handleSilenceDetection();
110
+ }
111
+ };
112
+
113
+ Voice.onSpeechPartialResults = e => {
114
+ console.log('onSpeechPartialResults: ', e);
115
+
116
+ if (silenceTimer) {
117
+ clearTimeout(silenceTimer);
118
+ }
119
+
120
+ // Only process partial results if we're still actively recording
121
+ if (e.value && e.value.length > 0 && isCurrentlyRecording) {
122
+ finalResult = e.value[0];
123
+
124
+ // Call partial callback for live transcription
125
+ if (partialResultCallback) {
126
+ partialResultCallback(finalResult);
127
+ }
128
+
129
+ handleSilenceDetection();
130
+ }
131
+ };
132
+
133
+ if (Platform.OS === 'android') {
134
+ Voice.onSpeechVolumeChanged = e => {
135
+ console.log('onSpeechVolumeChanged: ', e);
136
+ };
137
+ }
138
+
139
+ return true;
140
+ } catch (error) {
141
+ console.error('Error initializing Voice:', error);
142
+ return false;
143
+ }
144
+ }
145
+
146
+ const handleSilenceDetection = () => {
147
+ if (silenceTimer) {
148
+ clearTimeout(silenceTimer);
149
+ }
150
+
151
+ silenceTimer = setTimeout(async () => {
152
+ if (isCurrentlyRecording) {
153
+ await handleFinalResult();
154
+ }
155
+ }, SILENCE_DURATION);
156
+ };
157
+
158
+ const handleFinalResult = async () => {
159
+ if (!isCurrentlyRecording) return;
160
+
161
+ console.log('handleFinalResult called with:', finalResult);
162
+
163
+ // Set this FIRST to prevent any more partial results from being processed
164
+ isCurrentlyRecording = false;
165
+
166
+ // Call the result callback to update UI (button state)
167
+ // Always call callback even if result is empty to prevent UI getting stuck
168
+ if (resultCallback) {
169
+ resultCallback(finalResult || '', null);
170
+ }
171
+
172
+ // Then stop recording
173
+ await stopRecording();
174
+ };
175
+
176
+ const cleanupVoiceSession = async () => {
177
+ console.log('cleanupVoiceSession called');
178
+ isCurrentlyRecording = false;
179
+
180
+ if (silenceTimer) {
181
+ clearTimeout(silenceTimer);
182
+ silenceTimer = null;
183
+ }
184
+
185
+ try {
186
+ // Check if Voice module is available
187
+ if (!Voice) {
188
+ console.log('Voice module not available during cleanup');
189
+ return;
190
+ }
191
+
192
+ // Check if still recognizing
193
+ const isRecognizing = await Voice.isRecognizing();
194
+ console.log('Voice.isRecognizing() in cleanup:', isRecognizing);
195
+
196
+ if (isRecognizing) {
197
+ try {
198
+ // For iOS, use cancel for immediate termination
199
+ if (Platform.OS === 'ios') {
200
+ await Voice.cancel();
201
+ console.log('Voice.cancel() completed for iOS');
202
+ } else {
203
+ await Voice.stop();
204
+ console.log('Voice.stop() completed for Android');
205
+ }
206
+ await new Promise(resolve => setTimeout(resolve, 100));
207
+ } catch (e) {
208
+ console.log('Error stopping/canceling in cleanup:', e);
209
+ }
210
+ }
211
+
212
+ // Destroy the instance
213
+ try {
214
+ await Voice.destroy();
215
+ console.log('Voice.destroy() completed');
216
+ await new Promise(resolve => setTimeout(resolve, 200));
217
+ } catch (e) {
218
+ console.log('Error destroying in cleanup:', e);
219
+ }
220
+ } catch (error) {
221
+ console.error('Error in cleanupVoiceSession:', error);
222
+ }
223
+
224
+ finalResult = '';
225
+ };
226
+
227
+ export async function startRecording() {
228
+ try {
229
+ console.log('startRecording called');
230
+
231
+ // Check if Voice module is available
232
+ if (!Voice) {
233
+ console.error('Voice module is not available');
234
+ return false;
235
+ }
236
+
237
+ // Ensure cleanup of any existing session
238
+ await cleanupVoiceSession();
239
+
240
+ // Small delay to ensure cleanup is complete
241
+ await new Promise(resolve => setTimeout(resolve, 200));
242
+
243
+ const hasPermission = await requestAudioPermission();
244
+ if (!hasPermission) {
245
+ console.error('No permission to record audio');
246
+ return false;
247
+ }
248
+
249
+ // Re-initialize listeners each time for iOS stability
250
+ await initVoice(resultCallback, partialResultCallback);
251
+
252
+ // Start recognition
253
+ await Voice.start('en-US');
254
+ console.log('Voice.start() completed');
255
+ isCurrentlyRecording = true;
256
+
257
+ // Start silence detection timer immediately
258
+ // This ensures recording stops after 3 seconds even if no speech is detected
259
+ handleSilenceDetection();
260
+
261
+ return true;
262
+ } catch (error) {
263
+ console.error('Error starting voice recognition:', error);
264
+ await cleanupVoiceSession();
265
+ return false;
266
+ }
267
+ }
268
+
269
+ export async function stopRecording() {
270
+ try {
271
+ console.log('stopRecording called');
272
+
273
+ if (!isCurrentlyRecording || !Voice) {
274
+ console.log('Not recording or Voice not available');
275
+ return;
276
+ }
277
+
278
+ // Set this first to prevent race conditions
279
+ isCurrentlyRecording = false;
280
+
281
+ if (silenceTimer) {
282
+ clearTimeout(silenceTimer);
283
+ silenceTimer = null;
284
+ }
285
+
286
+ // Platform-specific stop
287
+ try {
288
+ if (Platform.OS === 'ios') {
289
+ // iOS: Use cancel for immediate termination - wait 1 second first
290
+ await new Promise(resolve => setTimeout(resolve, 1000));
291
+ await Voice.cancel();
292
+ console.log('Voice.cancel() completed for iOS');
293
+ } else {
294
+ // Android: Use stop - wait 1 second first
295
+ await new Promise(resolve => setTimeout(resolve, 1000));
296
+ await Voice.stop();
297
+ console.log('Voice.stop() completed for Android');
298
+ }
299
+
300
+ // Small delay
301
+ await new Promise(resolve => setTimeout(resolve, 100));
302
+ } catch (error) {
303
+ console.log('Error stopping/canceling Voice:', error);
304
+ }
305
+
306
+ // Then destroy
307
+ try {
308
+ await Voice.destroy();
309
+ console.log('Voice.destroy() completed');
310
+ await new Promise(resolve => setTimeout(resolve, 200));
311
+ } catch (error) {
312
+ console.log('Error destroying Voice:', error);
313
+ }
314
+
315
+ // Final cleanup
316
+ finalResult = '';
317
+ } catch (error) {
318
+ console.error('Error in stopRecording:', error);
319
+ await cleanupVoiceSession();
320
+ }
321
+ }
322
+
323
+ export async function cancelRecording() {
324
+ try {
325
+ if (!Voice) return;
326
+ await Voice.cancel();
327
+ await cleanupVoiceSession();
328
+ } catch (error) {
329
+ console.error('Error canceling voice recognition:', error);
330
+ await cleanupVoiceSession();
331
+ }
332
+ }
333
+
334
+ export async function requestAudioPermission() {
335
+ try {
336
+ // Get stored permission if available
337
+ const storedPermission = permissionStatusGetter
338
+ ? permissionStatusGetter()
339
+ : null;
340
+
341
+ if (storedPermission === 'denied') {
342
+ console.log('Permission previously denied by user');
343
+ return false;
344
+ }
345
+
346
+ let permissionResult = false;
347
+ if (Platform.OS === 'android') {
348
+ permissionResult = await requestAndroidPermission();
349
+ } else if (Platform.OS === 'ios') {
350
+ permissionResult = await requestIOSPermission();
351
+ }
352
+
353
+ // Store the result
354
+ if (permissionStatusSetter) {
355
+ permissionStatusSetter(permissionResult ? 'granted' : 'denied');
356
+ }
357
+
358
+ return permissionResult;
359
+ } catch (error) {
360
+ console.error('Error checking stored permission:', error);
361
+ return false;
362
+ }
363
+ }
364
+
365
+ async function requestAndroidPermission() {
366
+ try {
367
+ // Request microphone permission
368
+ const micPermission = await request(PERMISSIONS.ANDROID.RECORD_AUDIO);
369
+ if (micPermission !== RESULTS.GRANTED) {
370
+ console.log('Microphone permission denied');
371
+ return false;
372
+ }
373
+
374
+ // Check speech recognition services
375
+ try {
376
+ const services = await Voice.getSpeechRecognitionServices();
377
+ if (!services || services.length === 0) {
378
+ console.error('No speech recognition services available');
379
+ return false;
380
+ }
381
+ } catch (e) {
382
+ console.log('Error checking speech services:', e);
383
+ // Continue anyway - some devices report error but work fine
384
+ }
385
+
386
+ return true;
387
+ } catch (error) {
388
+ console.error('Error requesting Android permission:', error);
389
+ return false;
390
+ }
391
+ }
392
+
393
+ async function requestIOSPermission() {
394
+ try {
395
+ // Request microphone permission
396
+ const micPermission = await request(PERMISSIONS.IOS.MICROPHONE);
397
+ if (micPermission !== RESULTS.GRANTED) {
398
+ console.log('Microphone permission denied');
399
+ return false;
400
+ }
401
+
402
+ // Request speech recognition permission
403
+ const speechPermission = await request(PERMISSIONS.IOS.SPEECH_RECOGNITION);
404
+ if (speechPermission !== RESULTS.GRANTED) {
405
+ console.log('Speech recognition permission denied');
406
+ return false;
407
+ }
408
+
409
+ return true;
410
+ } catch (error) {
411
+ console.error('Error requesting iOS permissions:', error);
412
+ return false;
413
+ }
414
+ }
415
+
416
+ export function resetStoredPermission() {
417
+ if (permissionStatusSetter) {
418
+ permissionStatusSetter(null);
419
+ return true;
420
+ }
421
+ return false;
422
+ }
423
+
424
+ export function cleanup() {
425
+ if (!Voice) {
426
+ console.log('Voice module not available during cleanup');
427
+ return;
428
+ }
429
+
430
+ Voice.destroy()
431
+ .then(() => {
432
+ Voice.removeAllListeners();
433
+ cleanupVoiceSession();
434
+ })
435
+ .catch(error => {
436
+ console.error('Error in cleanup:', error);
437
+ // Try one more time
438
+ if (Voice) {
439
+ Voice.destroy().catch(e =>
440
+ console.error('Final cleanup attempt failed:', e),
441
+ );
442
+ }
443
+ });
444
+ }
445
+
@@ -0,0 +1,61 @@
1
+ import React, { useContext } from 'react';
2
+ import { Cloudinary } from '@cloudinary/url-gen';
3
+ import { Platform, View } from 'react-native';
4
+ import { AdvancedImage } from 'cloudinary-react-native';
5
+ import { scale } from '@cloudinary/url-gen/actions/resize';
6
+ import { AppContext } from '../contexts/AppContext';
7
+
8
+ const CloudinaryImage = ({ cldImg, imageStyle, accessibilityLabel, testID }) => {
9
+ const { brandCloudName } = useContext(AppContext);
10
+ const cld = new Cloudinary({
11
+ cloud: {
12
+ cloudName: brandCloudName || 'mktg'
13
+ }
14
+ });
15
+
16
+ const myImage = Platform.OS === 'ios' ? cld.image(`${cldImg}`) : cld.image(`${cldImg}`).format('png');
17
+ return (
18
+ <View>
19
+ <AdvancedImage
20
+ cldImg={myImage}
21
+ accessibilityLabel={accessibilityLabel}
22
+ testID={testID}
23
+ style={[{ resizeMode: 'contain' }, imageStyle]}
24
+ />
25
+ </View>
26
+ );
27
+ };
28
+
29
+ export const CloudinaryBannerImage = ({
30
+ cldImg,
31
+ imageStyle,
32
+ accessibilityLabel,
33
+ testID,
34
+ width = 345,
35
+ height = 100
36
+ }) => {
37
+ const { brandCloudName } = useContext(AppContext);
38
+ const cld = new Cloudinary({
39
+ cloud: {
40
+ cloudName: brandCloudName || 'mktg'
41
+ }
42
+ });
43
+ const myImage = cld.image(`${cldImg}`);
44
+ myImage.resize(
45
+ scale()
46
+ .width(parseInt(width * 3))
47
+ .height(parseInt(height * 3))
48
+ );
49
+ return (
50
+ <View>
51
+ <AdvancedImage
52
+ cldImg={myImage}
53
+ accessibilityLabel={accessibilityLabel}
54
+ testID={testID}
55
+ style={[{ resizeMode: 'contain', width: width, height: height }, imageStyle]}
56
+ />
57
+ </View>
58
+ );
59
+ };
60
+
61
+ export default CloudinaryImage;
@@ -0,0 +1,89 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+
3
+ export interface ChatMessage {
4
+ type: string;
5
+ text: string | string[];
6
+ form?: boolean;
7
+ }
8
+
9
+ export interface ChatState {
10
+ typingIndicator: boolean;
11
+ ghostMessage: boolean;
12
+ ghostCard: boolean;
13
+ stopActivated: boolean;
14
+ disclaimer: boolean;
15
+ startStreaming: boolean;
16
+ messages: ChatMessage[];
17
+ showIcon: boolean;
18
+ toggleChat: boolean;
19
+ showModal?: string;
20
+ }
21
+
22
+ // Module-level singleton to cache chat states by token
23
+ const chatStore: Record<string, ChatState> = {};
24
+
25
+ const getStorageKey = (token: string) => `srschat_${token}`;
26
+
27
+ export const defaultState: ChatState = {
28
+ typingIndicator: false,
29
+ ghostMessage: false,
30
+ ghostCard: false,
31
+ stopActivated: false,
32
+ disclaimer: false,
33
+ startStreaming: false,
34
+ messages: [{
35
+ type: "ai",
36
+ text: "Hi there 👋 Hi there 👋 I'm your Heritage+ AI Assistant. I'm here to help you with Product and Account information during your online visit. I'm still learning and growing - the more we interact and the more feedback you share, the better I can assist you. How can I help you today?"
37
+ }],
38
+ showIcon: true,
39
+ toggleChat: false,
40
+ showModal: "Icon"
41
+ };
42
+
43
+ /**
44
+ * Loads chat state for a specific customer token
45
+ * Returns cached state if available, otherwise loads from AsyncStorage
46
+ */
47
+ export const loadChat = async (token: string): Promise<ChatState> => {
48
+ // Return from in-memory cache if available
49
+ const cachedState = chatStore[token];
50
+ if (cachedState !== undefined) {
51
+ return cachedState;
52
+ }
53
+
54
+ // Otherwise load from AsyncStorage
55
+ try {
56
+ const key = getStorageKey(token);
57
+ const storedData = await AsyncStorage.getItem(key);
58
+
59
+ if (storedData) {
60
+ const parsedData = JSON.parse(storedData) as ChatState;
61
+ // Cache in memory for future access
62
+ chatStore[token] = parsedData;
63
+ return parsedData;
64
+ }
65
+ } catch (error) {
66
+ console.error('Error loading chat state:', error);
67
+ }
68
+
69
+ // Return default state if nothing found
70
+ return defaultState;
71
+ };
72
+
73
+ /**
74
+ * Updates chat state for a specific customer token
75
+ * Updates both in-memory cache and persists to AsyncStorage
76
+ */
77
+ export const updateChat = async (token: string, next: ChatState): Promise<void> => {
78
+ try {
79
+ const key = getStorageKey(token);
80
+
81
+ // Update in-memory cache
82
+ chatStore[token] = next;
83
+
84
+ // Persist to AsyncStorage
85
+ await AsyncStorage.setItem(key, JSON.stringify(next));
86
+ } catch (error) {
87
+ console.error('Error updating chat state:', error);
88
+ }
89
+ };
@@ -0,0 +1,49 @@
1
+ // textToSpeech.js
2
+ import React,{ useState, useContext} from 'react';
3
+ import Sound from 'react-native-sound';
4
+ import { AppContext } from '../contexts/AppContext';
5
+
6
+ export const TextToSpeech = async (inputText) => {
7
+ const { data } = useContext(AppContext)
8
+ try {
9
+ const response = await fetch(
10
+ 'https://api.openai.com/v1/audio/speech',
11
+ {
12
+ method: 'POST',
13
+ headers: {
14
+ Authorization: `Bearer ${data.openai_key}`,
15
+ 'Content-Type': 'application/json',
16
+ },
17
+ body: JSON.stringify({
18
+ model: 'tts-1',
19
+ voice: 'alloy',
20
+ input: inputText,
21
+ }),
22
+ }
23
+ );
24
+
25
+ if (!response.ok) {
26
+ throw new Error(`HTTP error! status: ${response.status}`);
27
+ }
28
+
29
+ const blob = await response.blob();
30
+ const reader = new FileReader();
31
+
32
+ reader.onloadend = () => {
33
+ const base64data = reader.result.split(',')[1];
34
+ const audioFile = `data:audio/mp3;base64,${base64data}`;
35
+
36
+ const sound = new Sound(audioFile, null, (error) => {
37
+ if (error) {
38
+ console.error('Error playing sound:', error);
39
+ } else {
40
+ sound.play();
41
+ }
42
+ });
43
+ };
44
+
45
+ reader.readAsDataURL(blob);
46
+ } catch (error) {
47
+ console.error('Error generating TTS:', error);
48
+ }
49
+ };