@mobileai/react-native 0.9.17 → 0.9.18
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/package.json +2 -5
- package/lib/module/__cli_tmp__.js.map +0 -1
- package/lib/module/components/AIAgent.js.map +0 -1
- package/lib/module/components/AIZone.js.map +0 -1
- package/lib/module/components/AgentChatBar.js.map +0 -1
- package/lib/module/components/AgentErrorBoundary.js.map +0 -1
- package/lib/module/components/AgentOverlay.js.map +0 -1
- package/lib/module/components/DiscoveryTooltip.js.map +0 -1
- package/lib/module/components/HighlightOverlay.js.map +0 -1
- package/lib/module/components/Icons.js.map +0 -1
- package/lib/module/components/ProactiveHint.js.map +0 -1
- package/lib/module/components/cards/InfoCard.js.map +0 -1
- package/lib/module/components/cards/ReviewSummary.js.map +0 -1
- package/lib/module/config/endpoints.js.map +0 -1
- package/lib/module/core/ActionRegistry.js.map +0 -1
- package/lib/module/core/AgentRuntime.js.map +0 -1
- package/lib/module/core/FiberTreeWalker.js.map +0 -1
- package/lib/module/core/IdleDetector.js.map +0 -1
- package/lib/module/core/MCPBridge.js.map +0 -1
- package/lib/module/core/ScreenDehydrator.js.map +0 -1
- package/lib/module/core/ZoneRegistry.js.map +0 -1
- package/lib/module/core/systemPrompt.js.map +0 -1
- package/lib/module/core/types.js.map +0 -1
- package/lib/module/hooks/useAction.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/plugin/withAppIntents.js.map +0 -1
- package/lib/module/providers/GeminiProvider.js.map +0 -1
- package/lib/module/providers/OpenAIProvider.js.map +0 -1
- package/lib/module/providers/ProviderFactory.js.map +0 -1
- package/lib/module/services/AudioInputService.js.map +0 -1
- package/lib/module/services/AudioOutputService.js.map +0 -1
- package/lib/module/services/KnowledgeBaseService.js.map +0 -1
- package/lib/module/services/VoiceService.js.map +0 -1
- package/lib/module/services/flags/FlagService.js.map +0 -1
- package/lib/module/services/telemetry/MobileAI.js.map +0 -1
- package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
- package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
- package/lib/module/services/telemetry/device.js.map +0 -1
- package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
- package/lib/module/services/telemetry/index.js.map +0 -1
- package/lib/module/services/telemetry/types.js.map +0 -1
- package/lib/module/support/CSATSurvey.js.map +0 -1
- package/lib/module/support/EscalationEventSource.js.map +0 -1
- package/lib/module/support/EscalationSocket.js.map +0 -1
- package/lib/module/support/SupportChatModal.js.map +0 -1
- package/lib/module/support/SupportGreeting.js.map +0 -1
- package/lib/module/support/TicketStore.js.map +0 -1
- package/lib/module/support/escalateTool.js.map +0 -1
- package/lib/module/support/index.js.map +0 -1
- package/lib/module/support/supportPrompt.js.map +0 -1
- package/lib/module/support/types.js.map +0 -1
- package/lib/module/tools/datePickerTool.js.map +0 -1
- package/lib/module/tools/guideTool.js.map +0 -1
- package/lib/module/tools/index.js.map +0 -1
- package/lib/module/tools/keyboardTool.js.map +0 -1
- package/lib/module/tools/longPressTool.js.map +0 -1
- package/lib/module/tools/pickerTool.js.map +0 -1
- package/lib/module/tools/restoreTool.js.map +0 -1
- package/lib/module/tools/scrollTool.js.map +0 -1
- package/lib/module/tools/simplifyTool.js.map +0 -1
- package/lib/module/tools/sliderTool.js.map +0 -1
- package/lib/module/tools/tapTool.js.map +0 -1
- package/lib/module/tools/typeTool.js.map +0 -1
- package/lib/module/tools/types.js.map +0 -1
- package/lib/module/types/jsx.d.js.map +0 -1
- package/lib/module/utils/audioUtils.js.map +0 -1
- package/lib/module/utils/logger.js.map +0 -1
- package/lib/typescript/babel.config.d.ts.map +0 -1
- package/lib/typescript/bin/generate-map.d.cts.map +0 -1
- package/lib/typescript/eslint.config.d.mts.map +0 -1
- package/lib/typescript/generate-map.d.ts.map +0 -1
- package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
- package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
- package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
- package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/Icons.d.ts.map +0 -1
- package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
- package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
- package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
- package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
- package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
- package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
- package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
- package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/src/core/types.d.ts.map +0 -1
- package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
- package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
- package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
- package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
- package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
- package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
- package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
- package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
- package/lib/typescript/src/support/index.d.ts.map +0 -1
- package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
- package/lib/typescript/src/support/types.d.ts.map +0 -1
- package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/index.d.ts.map +0 -1
- package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/types.d.ts.map +0 -1
- package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
- package/lib/typescript/src/utils/logger.d.ts.map +0 -1
- package/src/__cli_tmp__.tsx +0 -9
- package/src/cli/analyzers/chain-analyzer.ts +0 -183
- package/src/cli/extractors/ai-extractor.ts +0 -6
- package/src/cli/extractors/ast-extractor.ts +0 -551
- package/src/cli/generate-intents.ts +0 -140
- package/src/cli/generate-map.ts +0 -121
- package/src/cli/generate-swift.ts +0 -116
- package/src/cli/scanners/expo-scanner.ts +0 -203
- package/src/cli/scanners/rn-scanner.ts +0 -445
- package/src/components/AIAgent.tsx +0 -1716
- package/src/components/AIZone.tsx +0 -147
- package/src/components/AgentChatBar.tsx +0 -1143
- package/src/components/AgentErrorBoundary.tsx +0 -78
- package/src/components/AgentOverlay.tsx +0 -73
- package/src/components/DiscoveryTooltip.tsx +0 -148
- package/src/components/HighlightOverlay.tsx +0 -136
- package/src/components/Icons.tsx +0 -253
- package/src/components/ProactiveHint.tsx +0 -145
- package/src/components/cards/InfoCard.tsx +0 -58
- package/src/components/cards/ReviewSummary.tsx +0 -76
- package/src/config/endpoints.ts +0 -22
- package/src/core/ActionRegistry.ts +0 -105
- package/src/core/AgentRuntime.ts +0 -1471
- package/src/core/FiberTreeWalker.ts +0 -930
- package/src/core/IdleDetector.ts +0 -72
- package/src/core/MCPBridge.ts +0 -163
- package/src/core/ScreenDehydrator.ts +0 -53
- package/src/core/ZoneRegistry.ts +0 -44
- package/src/core/systemPrompt.ts +0 -431
- package/src/core/types.ts +0 -521
- package/src/hooks/useAction.ts +0 -182
- package/src/index.ts +0 -83
- package/src/plugin/withAppIntents.ts +0 -98
- package/src/providers/GeminiProvider.ts +0 -357
- package/src/providers/OpenAIProvider.ts +0 -379
- package/src/providers/ProviderFactory.ts +0 -36
- package/src/services/AudioInputService.ts +0 -226
- package/src/services/AudioOutputService.ts +0 -236
- package/src/services/KnowledgeBaseService.ts +0 -156
- package/src/services/VoiceService.ts +0 -451
- package/src/services/flags/FlagService.ts +0 -137
- package/src/services/telemetry/MobileAI.ts +0 -66
- package/src/services/telemetry/PiiScrubber.ts +0 -17
- package/src/services/telemetry/TelemetryService.ts +0 -323
- package/src/services/telemetry/TouchAutoCapture.ts +0 -165
- package/src/services/telemetry/device.ts +0 -93
- package/src/services/telemetry/deviceMetadata.ts +0 -13
- package/src/services/telemetry/index.ts +0 -13
- package/src/services/telemetry/types.ts +0 -75
- package/src/support/CSATSurvey.tsx +0 -304
- package/src/support/EscalationEventSource.ts +0 -190
- package/src/support/EscalationSocket.ts +0 -152
- package/src/support/SupportChatModal.tsx +0 -563
- package/src/support/SupportGreeting.tsx +0 -161
- package/src/support/TicketStore.ts +0 -100
- package/src/support/escalateTool.ts +0 -174
- package/src/support/index.ts +0 -29
- package/src/support/supportPrompt.ts +0 -55
- package/src/support/types.ts +0 -155
- package/src/tools/datePickerTool.ts +0 -60
- package/src/tools/guideTool.ts +0 -76
- package/src/tools/index.ts +0 -20
- package/src/tools/keyboardTool.ts +0 -30
- package/src/tools/longPressTool.ts +0 -61
- package/src/tools/pickerTool.ts +0 -115
- package/src/tools/restoreTool.ts +0 -33
- package/src/tools/scrollTool.ts +0 -156
- package/src/tools/simplifyTool.ts +0 -33
- package/src/tools/sliderTool.ts +0 -65
- package/src/tools/tapTool.ts +0 -93
- package/src/tools/typeTool.ts +0 -113
- package/src/tools/types.ts +0 -58
- package/src/types/jsx.d.ts +0 -20
- package/src/utils/audioUtils.ts +0 -54
- package/src/utils/logger.ts +0 -38
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CSAT Survey — Customer Satisfaction component.
|
|
3
|
-
*
|
|
4
|
-
* Shown after a support conversation ends (or after idle timeout).
|
|
5
|
-
* Supports three rating types: emoji, stars, thumbs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { useState } from 'react';
|
|
10
|
-
import {
|
|
11
|
-
View,
|
|
12
|
-
Text,
|
|
13
|
-
TouchableOpacity,
|
|
14
|
-
TextInput,
|
|
15
|
-
StyleSheet,
|
|
16
|
-
} from 'react-native';
|
|
17
|
-
import type { CSATConfig, CSATRating } from './types';
|
|
18
|
-
|
|
19
|
-
interface CSATSurveyProps {
|
|
20
|
-
config: CSATConfig;
|
|
21
|
-
metadata: CSATRating['metadata'];
|
|
22
|
-
onDismiss: () => void;
|
|
23
|
-
theme?: {
|
|
24
|
-
primaryColor?: string;
|
|
25
|
-
textColor?: string;
|
|
26
|
-
backgroundColor?: string;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const EMOJI_OPTIONS = [
|
|
31
|
-
{ emoji: '😡', label: 'Terrible', score: 1 },
|
|
32
|
-
{ emoji: '😞', label: 'Bad', score: 2 },
|
|
33
|
-
{ emoji: '😐', label: 'Okay', score: 3 },
|
|
34
|
-
{ emoji: '😊', label: 'Good', score: 4 },
|
|
35
|
-
{ emoji: '🤩', label: 'Amazing', score: 5 },
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const STAR_COUNT = 5;
|
|
39
|
-
|
|
40
|
-
export function CSATSurvey({
|
|
41
|
-
config,
|
|
42
|
-
metadata,
|
|
43
|
-
onDismiss,
|
|
44
|
-
theme,
|
|
45
|
-
}: CSATSurveyProps) {
|
|
46
|
-
const [selectedScore, setSelectedScore] = useState<number | null>(null);
|
|
47
|
-
const [feedback, setFeedback] = useState('');
|
|
48
|
-
const [submitted, setSubmitted] = useState(false);
|
|
49
|
-
|
|
50
|
-
const primary = theme?.primaryColor ?? '#8b5cf6';
|
|
51
|
-
const textColor = theme?.textColor ?? '#ffffff';
|
|
52
|
-
const bgColor = theme?.backgroundColor ?? 'rgba(26, 26, 46, 0.98)';
|
|
53
|
-
const ratingType = config.ratingType ?? 'emoji';
|
|
54
|
-
const question = config.question ?? 'How was your experience?';
|
|
55
|
-
|
|
56
|
-
const handleSubmit = () => {
|
|
57
|
-
if (selectedScore === null) return;
|
|
58
|
-
|
|
59
|
-
const rating: CSATRating = {
|
|
60
|
-
score: selectedScore,
|
|
61
|
-
feedback: feedback.trim() || undefined,
|
|
62
|
-
metadata,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
config.onSubmit(rating);
|
|
66
|
-
setSubmitted(true);
|
|
67
|
-
|
|
68
|
-
// Auto-dismiss after 1.5s
|
|
69
|
-
setTimeout(onDismiss, 1500);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
if (submitted) {
|
|
73
|
-
return (
|
|
74
|
-
<View style={[styles.container, { backgroundColor: bgColor }]}>
|
|
75
|
-
<Text style={[styles.thankYou, { color: textColor }]}>
|
|
76
|
-
Thank you for your feedback! 🙏
|
|
77
|
-
</Text>
|
|
78
|
-
</View>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<View style={[styles.container, { backgroundColor: bgColor }]}>
|
|
84
|
-
{/* Question */}
|
|
85
|
-
<Text style={[styles.question, { color: textColor }]}>{question}</Text>
|
|
86
|
-
|
|
87
|
-
{/* Rating selector */}
|
|
88
|
-
<View style={styles.ratingContainer}>
|
|
89
|
-
{ratingType === 'emoji' && (
|
|
90
|
-
<View style={styles.emojiRow}>
|
|
91
|
-
{EMOJI_OPTIONS.map((opt) => (
|
|
92
|
-
<TouchableOpacity
|
|
93
|
-
key={opt.score}
|
|
94
|
-
onPress={() => setSelectedScore(opt.score)}
|
|
95
|
-
style={[
|
|
96
|
-
styles.emojiButton,
|
|
97
|
-
selectedScore === opt.score && {
|
|
98
|
-
backgroundColor: `${primary}30`,
|
|
99
|
-
borderColor: primary,
|
|
100
|
-
},
|
|
101
|
-
]}
|
|
102
|
-
activeOpacity={0.7}
|
|
103
|
-
>
|
|
104
|
-
<Text style={styles.emoji}>{opt.emoji}</Text>
|
|
105
|
-
<Text
|
|
106
|
-
style={[
|
|
107
|
-
styles.emojiLabel,
|
|
108
|
-
{ color: selectedScore === opt.score ? primary : '#71717a' },
|
|
109
|
-
]}
|
|
110
|
-
>
|
|
111
|
-
{opt.label}
|
|
112
|
-
</Text>
|
|
113
|
-
</TouchableOpacity>
|
|
114
|
-
))}
|
|
115
|
-
</View>
|
|
116
|
-
)}
|
|
117
|
-
|
|
118
|
-
{ratingType === 'stars' && (
|
|
119
|
-
<View style={styles.starsRow}>
|
|
120
|
-
{Array.from({ length: STAR_COUNT }, (_, i) => i + 1).map(
|
|
121
|
-
(star) => (
|
|
122
|
-
<TouchableOpacity
|
|
123
|
-
key={star}
|
|
124
|
-
onPress={() => setSelectedScore(star)}
|
|
125
|
-
activeOpacity={0.7}
|
|
126
|
-
>
|
|
127
|
-
<Text
|
|
128
|
-
style={[
|
|
129
|
-
styles.star,
|
|
130
|
-
{
|
|
131
|
-
color:
|
|
132
|
-
selectedScore !== null && star <= selectedScore
|
|
133
|
-
? '#fbbf24'
|
|
134
|
-
: '#52525b',
|
|
135
|
-
},
|
|
136
|
-
]}
|
|
137
|
-
>
|
|
138
|
-
★
|
|
139
|
-
</Text>
|
|
140
|
-
</TouchableOpacity>
|
|
141
|
-
)
|
|
142
|
-
)}
|
|
143
|
-
</View>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
{ratingType === 'thumbs' && (
|
|
147
|
-
<View style={styles.thumbsRow}>
|
|
148
|
-
<TouchableOpacity
|
|
149
|
-
onPress={() => setSelectedScore(0)}
|
|
150
|
-
style={[
|
|
151
|
-
styles.thumbButton,
|
|
152
|
-
selectedScore === 0 && {
|
|
153
|
-
backgroundColor: '#ef444430',
|
|
154
|
-
borderColor: '#ef4444',
|
|
155
|
-
},
|
|
156
|
-
]}
|
|
157
|
-
activeOpacity={0.7}
|
|
158
|
-
>
|
|
159
|
-
<Text style={styles.thumbEmoji}>👎</Text>
|
|
160
|
-
</TouchableOpacity>
|
|
161
|
-
<TouchableOpacity
|
|
162
|
-
onPress={() => setSelectedScore(1)}
|
|
163
|
-
style={[
|
|
164
|
-
styles.thumbButton,
|
|
165
|
-
selectedScore === 1 && {
|
|
166
|
-
backgroundColor: '#22c55e30',
|
|
167
|
-
borderColor: '#22c55e',
|
|
168
|
-
},
|
|
169
|
-
]}
|
|
170
|
-
activeOpacity={0.7}
|
|
171
|
-
>
|
|
172
|
-
<Text style={styles.thumbEmoji}>👍</Text>
|
|
173
|
-
</TouchableOpacity>
|
|
174
|
-
</View>
|
|
175
|
-
)}
|
|
176
|
-
</View>
|
|
177
|
-
|
|
178
|
-
{/* Optional feedback text */}
|
|
179
|
-
{selectedScore !== null && (
|
|
180
|
-
<TextInput
|
|
181
|
-
style={[styles.feedbackInput, { color: textColor }]}
|
|
182
|
-
placeholder="Any additional feedback? (optional)"
|
|
183
|
-
placeholderTextColor="#52525b"
|
|
184
|
-
value={feedback}
|
|
185
|
-
onChangeText={setFeedback}
|
|
186
|
-
multiline
|
|
187
|
-
maxLength={500}
|
|
188
|
-
/>
|
|
189
|
-
)}
|
|
190
|
-
|
|
191
|
-
{/* Actions */}
|
|
192
|
-
<View style={styles.actions}>
|
|
193
|
-
<TouchableOpacity onPress={onDismiss} activeOpacity={0.7}>
|
|
194
|
-
<Text style={styles.dismissText}>Skip</Text>
|
|
195
|
-
</TouchableOpacity>
|
|
196
|
-
{selectedScore !== null && (
|
|
197
|
-
<TouchableOpacity
|
|
198
|
-
style={[styles.submitButton, { backgroundColor: primary }]}
|
|
199
|
-
onPress={handleSubmit}
|
|
200
|
-
activeOpacity={0.7}
|
|
201
|
-
>
|
|
202
|
-
<Text style={[styles.submitText, { color: textColor }]}>
|
|
203
|
-
Submit
|
|
204
|
-
</Text>
|
|
205
|
-
</TouchableOpacity>
|
|
206
|
-
)}
|
|
207
|
-
</View>
|
|
208
|
-
</View>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const styles = StyleSheet.create({
|
|
213
|
-
container: {
|
|
214
|
-
borderRadius: 16,
|
|
215
|
-
padding: 20,
|
|
216
|
-
margin: 12,
|
|
217
|
-
},
|
|
218
|
-
question: {
|
|
219
|
-
fontSize: 16,
|
|
220
|
-
fontWeight: '600',
|
|
221
|
-
textAlign: 'center',
|
|
222
|
-
marginBottom: 16,
|
|
223
|
-
},
|
|
224
|
-
thankYou: {
|
|
225
|
-
fontSize: 16,
|
|
226
|
-
fontWeight: '500',
|
|
227
|
-
textAlign: 'center',
|
|
228
|
-
paddingVertical: 12,
|
|
229
|
-
},
|
|
230
|
-
ratingContainer: {
|
|
231
|
-
marginBottom: 12,
|
|
232
|
-
},
|
|
233
|
-
emojiRow: {
|
|
234
|
-
flexDirection: 'row',
|
|
235
|
-
justifyContent: 'center',
|
|
236
|
-
gap: 8,
|
|
237
|
-
},
|
|
238
|
-
emojiButton: {
|
|
239
|
-
alignItems: 'center',
|
|
240
|
-
paddingHorizontal: 10,
|
|
241
|
-
paddingVertical: 8,
|
|
242
|
-
borderRadius: 12,
|
|
243
|
-
borderWidth: 1,
|
|
244
|
-
borderColor: 'transparent',
|
|
245
|
-
},
|
|
246
|
-
emoji: {
|
|
247
|
-
fontSize: 28,
|
|
248
|
-
},
|
|
249
|
-
emojiLabel: {
|
|
250
|
-
fontSize: 10,
|
|
251
|
-
marginTop: 4,
|
|
252
|
-
fontWeight: '500',
|
|
253
|
-
},
|
|
254
|
-
starsRow: {
|
|
255
|
-
flexDirection: 'row',
|
|
256
|
-
justifyContent: 'center',
|
|
257
|
-
gap: 8,
|
|
258
|
-
},
|
|
259
|
-
star: {
|
|
260
|
-
fontSize: 36,
|
|
261
|
-
},
|
|
262
|
-
thumbsRow: {
|
|
263
|
-
flexDirection: 'row',
|
|
264
|
-
justifyContent: 'center',
|
|
265
|
-
gap: 20,
|
|
266
|
-
},
|
|
267
|
-
thumbButton: {
|
|
268
|
-
padding: 12,
|
|
269
|
-
borderRadius: 16,
|
|
270
|
-
borderWidth: 1,
|
|
271
|
-
borderColor: 'transparent',
|
|
272
|
-
},
|
|
273
|
-
thumbEmoji: {
|
|
274
|
-
fontSize: 36,
|
|
275
|
-
},
|
|
276
|
-
feedbackInput: {
|
|
277
|
-
borderWidth: 1,
|
|
278
|
-
borderColor: '#3f3f46',
|
|
279
|
-
borderRadius: 12,
|
|
280
|
-
padding: 12,
|
|
281
|
-
fontSize: 14,
|
|
282
|
-
minHeight: 60,
|
|
283
|
-
textAlignVertical: 'top',
|
|
284
|
-
marginBottom: 12,
|
|
285
|
-
},
|
|
286
|
-
actions: {
|
|
287
|
-
flexDirection: 'row',
|
|
288
|
-
justifyContent: 'space-between',
|
|
289
|
-
alignItems: 'center',
|
|
290
|
-
},
|
|
291
|
-
dismissText: {
|
|
292
|
-
color: '#71717a',
|
|
293
|
-
fontSize: 14,
|
|
294
|
-
},
|
|
295
|
-
submitButton: {
|
|
296
|
-
paddingHorizontal: 20,
|
|
297
|
-
paddingVertical: 10,
|
|
298
|
-
borderRadius: 10,
|
|
299
|
-
},
|
|
300
|
-
submitText: {
|
|
301
|
-
fontSize: 14,
|
|
302
|
-
fontWeight: '600',
|
|
303
|
-
},
|
|
304
|
-
});
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EscalationEventSource — SSE client using fetch + ReadableStream.
|
|
3
|
-
*
|
|
4
|
-
* Uses only the fetch API (available in all React Native runtimes)
|
|
5
|
-
* to consume Server-Sent Events — no EventSource polyfill needed.
|
|
6
|
-
* Provides a reliable, auto-reconnecting channel for server-push
|
|
7
|
-
* events like `ticket_closed` that complements the bidirectional
|
|
8
|
-
* WebSocket used for chat.
|
|
9
|
-
*
|
|
10
|
-
* Lifecycle:
|
|
11
|
-
* 1. SDK calls connect() → fetch with streaming response
|
|
12
|
-
* 2. Server holds connection open, pushes `ticket_closed` when agent resolves
|
|
13
|
-
* 3. On disconnect, auto-reconnects with exponential backoff (max 5 attempts)
|
|
14
|
-
* 4. If ticket is already closed, server responds immediately with the event
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { logger } from '../utils/logger';
|
|
18
|
-
|
|
19
|
-
export interface EscalationEventSourceOptions {
|
|
20
|
-
url: string;
|
|
21
|
-
onTicketClosed?: (ticketId: string) => void;
|
|
22
|
-
onConnected?: (ticketId: string) => void;
|
|
23
|
-
onError?: (error: Error) => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class EscalationEventSource {
|
|
27
|
-
private abortController: AbortController | null = null;
|
|
28
|
-
private intentionalClose = false;
|
|
29
|
-
private reconnectAttempts = 0;
|
|
30
|
-
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
31
|
-
private readonly maxReconnectAttempts = 5;
|
|
32
|
-
private readonly options: EscalationEventSourceOptions;
|
|
33
|
-
|
|
34
|
-
constructor(options: EscalationEventSourceOptions) {
|
|
35
|
-
this.options = options;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
connect(): void {
|
|
39
|
-
this.intentionalClose = false;
|
|
40
|
-
this.reconnectAttempts = 0;
|
|
41
|
-
this.openConnection();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
disconnect(): void {
|
|
45
|
-
this.intentionalClose = true;
|
|
46
|
-
if (this.reconnectTimer) {
|
|
47
|
-
clearTimeout(this.reconnectTimer);
|
|
48
|
-
this.reconnectTimer = null;
|
|
49
|
-
}
|
|
50
|
-
if (this.abortController) {
|
|
51
|
-
this.abortController.abort();
|
|
52
|
-
this.abortController = null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private async openConnection(): Promise<void> {
|
|
57
|
-
if (this.intentionalClose) return;
|
|
58
|
-
|
|
59
|
-
this.abortController = new AbortController();
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const response = await fetch(this.options.url, {
|
|
63
|
-
signal: this.abortController.signal,
|
|
64
|
-
headers: { Accept: 'text/event-stream' },
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
logger.warn('EscalationSSE', 'Non-OK response:', response.status);
|
|
69
|
-
this.scheduleReconnect();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!response.body) {
|
|
74
|
-
logger.warn('EscalationSSE', 'No readable body — falling back to reading full response');
|
|
75
|
-
await this.readFullResponse(response);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
this.reconnectAttempts = 0;
|
|
80
|
-
await this.readStream(response.body);
|
|
81
|
-
} catch (err) {
|
|
82
|
-
if (this.intentionalClose) return;
|
|
83
|
-
if ((err as Error).name === 'AbortError') return;
|
|
84
|
-
logger.warn('EscalationSSE', 'Connection error:', (err as Error).message);
|
|
85
|
-
this.options.onError?.(err as Error);
|
|
86
|
-
this.scheduleReconnect();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {
|
|
91
|
-
const reader = body.getReader();
|
|
92
|
-
const decoder = new TextDecoder();
|
|
93
|
-
let buffer = '';
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
while (true) {
|
|
97
|
-
const { done, value } = await reader.read();
|
|
98
|
-
if (done) break;
|
|
99
|
-
|
|
100
|
-
buffer += decoder.decode(value, { stream: true });
|
|
101
|
-
const lines = buffer.split('\n');
|
|
102
|
-
buffer = lines.pop()!;
|
|
103
|
-
|
|
104
|
-
let currentEvent = '';
|
|
105
|
-
let currentData = '';
|
|
106
|
-
|
|
107
|
-
for (const line of lines) {
|
|
108
|
-
if (line.startsWith('event: ')) {
|
|
109
|
-
currentEvent = line.slice(7).trim();
|
|
110
|
-
} else if (line.startsWith('data: ')) {
|
|
111
|
-
currentData = line.slice(6).trim();
|
|
112
|
-
} else if (line === '' && currentEvent && currentData) {
|
|
113
|
-
this.handleEvent(currentEvent, currentData);
|
|
114
|
-
currentEvent = '';
|
|
115
|
-
currentData = '';
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
} catch (err) {
|
|
120
|
-
if (this.intentionalClose) return;
|
|
121
|
-
if ((err as Error).name === 'AbortError') return;
|
|
122
|
-
logger.warn('EscalationSSE', 'Stream read error:', (err as Error).message);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!this.intentionalClose) {
|
|
126
|
-
this.scheduleReconnect();
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private async readFullResponse(response: Response): Promise<void> {
|
|
131
|
-
try {
|
|
132
|
-
const text = await response.text();
|
|
133
|
-
let currentEvent = '';
|
|
134
|
-
let currentData = '';
|
|
135
|
-
|
|
136
|
-
for (const line of text.split('\n')) {
|
|
137
|
-
if (line.startsWith('event: ')) {
|
|
138
|
-
currentEvent = line.slice(7).trim();
|
|
139
|
-
} else if (line.startsWith('data: ')) {
|
|
140
|
-
currentData = line.slice(6).trim();
|
|
141
|
-
} else if (line === '' && currentEvent && currentData) {
|
|
142
|
-
this.handleEvent(currentEvent, currentData);
|
|
143
|
-
currentEvent = '';
|
|
144
|
-
currentData = '';
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} catch (err) {
|
|
148
|
-
if (this.intentionalClose) return;
|
|
149
|
-
logger.warn('EscalationSSE', 'Full response read error:', (err as Error).message);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (!this.intentionalClose) {
|
|
153
|
-
this.scheduleReconnect();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private handleEvent(event: string, data: string): void {
|
|
158
|
-
try {
|
|
159
|
-
const parsed = JSON.parse(data);
|
|
160
|
-
|
|
161
|
-
if (event === 'connected') {
|
|
162
|
-
logger.info('EscalationSSE', 'Connected for ticket:', parsed.ticketId);
|
|
163
|
-
this.options.onConnected?.(parsed.ticketId);
|
|
164
|
-
} else if (event === 'ticket_closed') {
|
|
165
|
-
logger.info('EscalationSSE', 'Ticket closed event:', parsed.ticketId);
|
|
166
|
-
this.options.onTicketClosed?.(parsed.ticketId);
|
|
167
|
-
this.intentionalClose = true;
|
|
168
|
-
this.abortController?.abort();
|
|
169
|
-
}
|
|
170
|
-
} catch {
|
|
171
|
-
// ignore parse error
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private scheduleReconnect(): void {
|
|
176
|
-
if (this.intentionalClose) return;
|
|
177
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
178
|
-
logger.warn('EscalationSSE', 'Max reconnect attempts reached — giving up');
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 16_000);
|
|
183
|
-
this.reconnectAttempts++;
|
|
184
|
-
logger.info('EscalationSSE', `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
185
|
-
|
|
186
|
-
this.reconnectTimer = setTimeout(() => {
|
|
187
|
-
this.openConnection();
|
|
188
|
-
}, delay);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EscalationSocket — manages a WebSocket connection to the MobileAI platform
|
|
3
|
-
* for receiving real-time replies from human support agents.
|
|
4
|
-
*
|
|
5
|
-
* Lifecycle:
|
|
6
|
-
* 1. SDK calls escalate_to_human → POST /api/v1/escalations → gets { ticketId, wsUrl }
|
|
7
|
-
* 2. EscalationSocket.connect(wsUrl) opens a WS connection
|
|
8
|
-
* 3. Platform pushes { type: 'reply', ticketId, reply } when agent responds
|
|
9
|
-
* 4. onReply callback fires → shown in chat UI as "👤 Human Agent: <reply>"
|
|
10
|
-
* 5. disconnect() on chat close / unmount
|
|
11
|
-
*
|
|
12
|
-
* Handles:
|
|
13
|
-
* - Server heartbeat pings (type: 'ping') — acknowledged silently
|
|
14
|
-
* - Auto-reconnect on unexpected close (max 3 attempts, exponential backoff)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export type SocketReplyHandler = (reply: string, ticketId?: string) => void;
|
|
18
|
-
|
|
19
|
-
interface EscalationSocketOptions {
|
|
20
|
-
onReply: SocketReplyHandler;
|
|
21
|
-
onError?: (error: Event) => void;
|
|
22
|
-
onTypingChange?: (isTyping: boolean) => void;
|
|
23
|
-
onTicketClosed?: (ticketId?: string) => void;
|
|
24
|
-
maxReconnectAttempts?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class EscalationSocket {
|
|
28
|
-
private ws: WebSocket | null = null;
|
|
29
|
-
private wsUrl: string | null = null;
|
|
30
|
-
private reconnectAttempts = 0;
|
|
31
|
-
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
32
|
-
private intentionalClose = false;
|
|
33
|
-
|
|
34
|
-
private readonly onReply: SocketReplyHandler;
|
|
35
|
-
private readonly onError?: (error: Event) => void;
|
|
36
|
-
private readonly onTypingChange?: (isTyping: boolean) => void;
|
|
37
|
-
private readonly onTicketClosed?: (ticketId?: string) => void;
|
|
38
|
-
private readonly maxReconnectAttempts: number;
|
|
39
|
-
|
|
40
|
-
constructor(options: EscalationSocketOptions) {
|
|
41
|
-
this.onReply = options.onReply;
|
|
42
|
-
this.onError = options.onError;
|
|
43
|
-
this.onTypingChange = options.onTypingChange;
|
|
44
|
-
this.onTicketClosed = options.onTicketClosed;
|
|
45
|
-
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 3;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
connect(wsUrl: string): void {
|
|
49
|
-
this.wsUrl = wsUrl;
|
|
50
|
-
this.intentionalClose = false;
|
|
51
|
-
this.openConnection();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
sendText(text: string): boolean {
|
|
55
|
-
if (this.ws?.readyState === 1) { // WebSocket.OPEN
|
|
56
|
-
this.ws.send(JSON.stringify({ type: 'user_message', content: text }));
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
sendTypingStatus(isTyping: boolean): boolean {
|
|
63
|
-
if (this.ws?.readyState === 1) {
|
|
64
|
-
this.ws.send(JSON.stringify({ type: isTyping ? 'typing_start' : 'typing_stop' }));
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
disconnect(): void {
|
|
71
|
-
this.intentionalClose = true;
|
|
72
|
-
if (this.reconnectTimer) {
|
|
73
|
-
clearTimeout(this.reconnectTimer);
|
|
74
|
-
this.reconnectTimer = null;
|
|
75
|
-
}
|
|
76
|
-
if (this.ws) {
|
|
77
|
-
this.ws.close();
|
|
78
|
-
this.ws = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private openConnection(): void {
|
|
83
|
-
if (!this.wsUrl) return;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
this.ws = new WebSocket(this.wsUrl);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
console.error('[EscalationSocket] Failed to open WebSocket:', err);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
this.ws.onopen = () => {
|
|
93
|
-
console.log('[EscalationSocket] ✅ Connected to:', this.wsUrl);
|
|
94
|
-
this.reconnectAttempts = 0;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
this.ws.onmessage = (event) => {
|
|
98
|
-
try {
|
|
99
|
-
const rawData = String(event.data);
|
|
100
|
-
console.log('[EscalationSocket] Message received:', rawData);
|
|
101
|
-
const msg = JSON.parse(rawData);
|
|
102
|
-
if (msg.type === 'ping') {
|
|
103
|
-
console.log('[EscalationSocket] Heartbeat ping received');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (msg.type === 'reply' && msg.reply) {
|
|
107
|
-
console.log('[EscalationSocket] Human reply received:', msg.reply);
|
|
108
|
-
this.onTypingChange?.(false);
|
|
109
|
-
this.onReply(msg.reply, msg.ticketId);
|
|
110
|
-
} else if (msg.type === 'typing_start') {
|
|
111
|
-
this.onTypingChange?.(true);
|
|
112
|
-
} else if (msg.type === 'typing_stop') {
|
|
113
|
-
this.onTypingChange?.(false);
|
|
114
|
-
} else if (msg.type === 'ticket_closed') {
|
|
115
|
-
console.log('[EscalationSocket] Ticket closed by agent');
|
|
116
|
-
this.onTypingChange?.(false);
|
|
117
|
-
this.onTicketClosed?.(msg.ticketId);
|
|
118
|
-
this.intentionalClose = true;
|
|
119
|
-
this.ws?.close();
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
// Non-JSON message — ignore
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
this.ws.onerror = (event) => {
|
|
127
|
-
console.error('[EscalationSocket] ❌ WebSocket error. URL was:', this.wsUrl, event);
|
|
128
|
-
this.onError?.(event);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
this.ws.onclose = (event) => {
|
|
132
|
-
console.warn(`[EscalationSocket] Connection closed. Code=${event.code} Reason="${event.reason}" Intentional=${this.intentionalClose}`);
|
|
133
|
-
if (this.intentionalClose) return;
|
|
134
|
-
this.scheduleReconnect();
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private scheduleReconnect(): void {
|
|
139
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
140
|
-
console.warn('[EscalationSocket] Max reconnect attempts reached — giving up');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 16_000);
|
|
144
|
-
this.reconnectAttempts++;
|
|
145
|
-
console.log(
|
|
146
|
-
`[EscalationSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
147
|
-
);
|
|
148
|
-
this.reconnectTimer = setTimeout(() => {
|
|
149
|
-
this.openConnection();
|
|
150
|
-
}, delay);
|
|
151
|
-
}
|
|
152
|
-
}
|