@mobileai/react-native 0.9.17 → 0.9.19
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/LICENSE +28 -20
- package/MobileAIFloatingOverlay.podspec +25 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +151 -0
- package/android/src/main/java/com/mobileai/overlay/MobileAIOverlayPackage.kt +23 -0
- package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +45 -0
- package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +29 -0
- package/ios/MobileAIFloatingOverlayComponentView.mm +73 -0
- package/lib/module/components/AIAgent.js +902 -136
- package/lib/module/components/AIConsentDialog.js +439 -0
- package/lib/module/components/AgentChatBar.js +828 -134
- package/lib/module/components/AgentOverlay.js +2 -1
- package/lib/module/components/DiscoveryTooltip.js +21 -9
- package/lib/module/components/FloatingOverlayWrapper.js +108 -0
- package/lib/module/components/Icons.js +123 -0
- package/lib/module/config/endpoints.js +12 -2
- package/lib/module/core/AgentRuntime.js +373 -27
- package/lib/module/core/FiberAdapter.js +56 -0
- package/lib/module/core/FiberTreeWalker.js +186 -80
- package/lib/module/core/IdleDetector.js +19 -0
- package/lib/module/core/NativeAlertInterceptor.js +191 -0
- package/lib/module/core/systemPrompt.js +203 -45
- package/lib/module/index.js +3 -0
- package/lib/module/providers/GeminiProvider.js +72 -56
- package/lib/module/providers/ProviderFactory.js +6 -2
- package/lib/module/services/AudioInputService.js +3 -12
- package/lib/module/services/AudioOutputService.js +1 -13
- package/lib/module/services/ConversationService.js +166 -0
- package/lib/module/services/MobileAIKnowledgeRetriever.js +41 -0
- package/lib/module/services/VoiceService.js +29 -8
- package/lib/module/services/telemetry/MobileAI.js +44 -0
- package/lib/module/services/telemetry/TelemetryService.js +13 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js +44 -18
- package/lib/module/specs/FloatingOverlayNativeComponent.ts +19 -0
- package/lib/module/support/CSATSurvey.js +95 -12
- package/lib/module/support/EscalationSocket.js +70 -1
- package/lib/module/support/ReportedIssueEventSource.js +148 -0
- package/lib/module/support/escalateTool.js +4 -2
- package/lib/module/support/index.js +1 -0
- package/lib/module/support/reportIssueTool.js +127 -0
- package/lib/module/support/supportPrompt.js +77 -9
- package/lib/module/tools/guideTool.js +2 -1
- package/lib/module/tools/longPressTool.js +4 -3
- package/lib/module/tools/pickerTool.js +6 -4
- package/lib/module/tools/tapTool.js +12 -3
- package/lib/module/tools/typeTool.js +19 -10
- package/lib/module/utils/logger.js +175 -6
- package/lib/typescript/react-native.config.d.ts +11 -0
- package/lib/typescript/src/components/AIAgent.d.ts +28 -2
- package/lib/typescript/src/components/AIConsentDialog.d.ts +153 -0
- package/lib/typescript/src/components/AgentChatBar.d.ts +15 -2
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts +3 -1
- package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +51 -0
- package/lib/typescript/src/components/Icons.d.ts +8 -0
- package/lib/typescript/src/config/endpoints.d.ts +5 -3
- package/lib/typescript/src/core/AgentRuntime.d.ts +4 -0
- package/lib/typescript/src/core/FiberAdapter.d.ts +25 -0
- package/lib/typescript/src/core/FiberTreeWalker.d.ts +2 -0
- package/lib/typescript/src/core/IdleDetector.d.ts +11 -0
- package/lib/typescript/src/core/NativeAlertInterceptor.d.ts +55 -0
- package/lib/typescript/src/core/types.d.ts +106 -1
- package/lib/typescript/src/index.d.ts +9 -4
- package/lib/typescript/src/providers/GeminiProvider.d.ts +6 -5
- package/lib/typescript/src/services/ConversationService.d.ts +55 -0
- package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +9 -0
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts +7 -0
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +1 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +9 -6
- package/lib/typescript/src/services/telemetry/types.d.ts +3 -1
- package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +17 -0
- package/lib/typescript/src/support/EscalationSocket.d.ts +17 -0
- package/lib/typescript/src/support/ReportedIssueEventSource.d.ts +24 -0
- package/lib/typescript/src/support/escalateTool.d.ts +5 -0
- package/lib/typescript/src/support/index.d.ts +2 -1
- package/lib/typescript/src/support/reportIssueTool.d.ts +20 -0
- package/lib/typescript/src/support/types.d.ts +56 -1
- package/lib/typescript/src/utils/logger.d.ts +15 -0
- package/package.json +20 -9
- package/react-native.config.js +12 -0
- 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
|
@@ -16,51 +16,77 @@
|
|
|
16
16
|
|
|
17
17
|
// React Native imports not needed — we use Fiber internals directly
|
|
18
18
|
|
|
19
|
+
// ─── Rage Click Detection ──────────────────────────────────────────
|
|
20
|
+
//
|
|
21
|
+
// Industry-standard approach (FullStory, PostHog, LogRocket):
|
|
22
|
+
// - 3+ taps on the SAME element within a SHORT window
|
|
23
|
+
// - Must be on the SAME screen (screen changes = intentional navigation)
|
|
24
|
+
// - Navigation-style labels ("Next", "Continue") are excluded
|
|
25
|
+
// - 1-second window (PostHog standard) instead of 2s to reduce false positives
|
|
26
|
+
|
|
19
27
|
const recentTaps = [];
|
|
20
|
-
const RAGE_WINDOW_MS =
|
|
28
|
+
const RAGE_WINDOW_MS = 1000; // PostHog uses 1s — tighter = fewer false positives
|
|
21
29
|
const RAGE_THRESHOLD = 3;
|
|
30
|
+
const MAX_TAP_BUFFER = 8;
|
|
31
|
+
|
|
32
|
+
// Labels that are naturally tapped multiple times in sequence (wizards, onboarding, etc.)
|
|
33
|
+
const NAVIGATION_LABELS = new Set(['next', 'continue', 'skip', 'back', 'done', 'ok', 'cancel', 'previous', 'dismiss', 'close', 'got it', 'confirm', 'proceed', 'التالي', 'متابعة', 'تخطي', 'رجوع', 'تم', 'إلغاء', 'إغلاق', 'حسناً']);
|
|
34
|
+
function isNavigationLabel(label) {
|
|
35
|
+
return NAVIGATION_LABELS.has(label.toLowerCase().trim());
|
|
36
|
+
}
|
|
22
37
|
|
|
23
38
|
/**
|
|
24
|
-
* Checks if the user is
|
|
25
|
-
*
|
|
39
|
+
* Checks if the user is rage-tapping an element.
|
|
40
|
+
*
|
|
41
|
+
* Industry best-practice criteria:
|
|
42
|
+
* 1. Same label tapped 3+ times within 1 second
|
|
43
|
+
* 2. Taps must be on the SAME screen (screen change = not rage, it's navigation)
|
|
44
|
+
* 3. Navigation labels ("Next", "Skip", etc.) are excluded
|
|
26
45
|
*/
|
|
27
46
|
export function checkRageClick(label, telemetry) {
|
|
47
|
+
// Skip navigation-style labels — sequential tapping is by design
|
|
48
|
+
if (isNavigationLabel(label)) return;
|
|
28
49
|
const now = Date.now();
|
|
50
|
+
const currentScreen = telemetry.screen;
|
|
29
51
|
recentTaps.push({
|
|
30
52
|
label,
|
|
53
|
+
screen: currentScreen,
|
|
31
54
|
ts: now
|
|
32
55
|
});
|
|
33
56
|
|
|
34
|
-
// Keep buffer
|
|
35
|
-
if (recentTaps.length >
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
// Keep buffer bounded
|
|
58
|
+
if (recentTaps.length > MAX_TAP_BUFFER) recentTaps.shift();
|
|
59
|
+
|
|
60
|
+
// Count taps on the SAME label AND SAME screen within the time window
|
|
61
|
+
const matching = recentTaps.filter(t => t.label === label && t.screen === currentScreen && now - t.ts < RAGE_WINDOW_MS);
|
|
62
|
+
if (matching.length >= RAGE_THRESHOLD) {
|
|
38
63
|
telemetry.track('rage_click', {
|
|
39
64
|
label,
|
|
40
|
-
count:
|
|
41
|
-
screen:
|
|
65
|
+
count: matching.length,
|
|
66
|
+
screen: currentScreen
|
|
42
67
|
});
|
|
43
|
-
|
|
68
|
+
// Reset buffer after emitting to avoid duplicate rage events
|
|
69
|
+
recentTaps.length = 0;
|
|
44
70
|
}
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
/**
|
|
48
|
-
* Extract a label from a GestureResponderEvent
|
|
74
|
+
* Extract a label from a GestureResponderEvent.
|
|
49
75
|
*
|
|
50
|
-
* @param
|
|
51
|
-
* @param rootRef - The root View ref (to resolve relative positions)
|
|
76
|
+
* @param event - The GestureResponderEvent from onStartShouldSetResponderCapture
|
|
52
77
|
* @returns A descriptive label string for the tapped element
|
|
53
78
|
*/
|
|
54
|
-
export function extractTouchLabel(
|
|
79
|
+
export function extractTouchLabel(event) {
|
|
55
80
|
// Try accessible properties first (most reliable)
|
|
56
|
-
const target = nativeEvent?.target;
|
|
81
|
+
const target = event?.nativeEvent?.target;
|
|
57
82
|
if (!target) return 'Unknown Element';
|
|
58
83
|
|
|
59
|
-
// React Native internal:
|
|
84
|
+
// React Native internal: _targetInst (synthetic event Fiber ref)
|
|
60
85
|
// We can walk the Fiber tree from the target to find text
|
|
61
86
|
try {
|
|
62
|
-
// Strategy 1:
|
|
63
|
-
|
|
87
|
+
// Strategy 1: Fiber from the SyntheticEvent (works in dev and production RN >= 0.60)
|
|
88
|
+
// Strategy 2: Walk up the Fiber tree from the touched element via DevTools hook
|
|
89
|
+
let fiber = event?._targetInst || getFiberFromNativeTag(target);
|
|
64
90
|
if (fiber) {
|
|
65
91
|
// Walk up looking for text content or accessibility labels
|
|
66
92
|
let current = fiber;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codegen spec for MobileAIFloatingOverlay native view.
|
|
3
|
+
*
|
|
4
|
+
* This file is required by React Native's Codegen (New Architecture / Fabric).
|
|
5
|
+
* It defines the TypeScript interface for the native view. During the build,
|
|
6
|
+
* Codegen uses this spec to generate C++ glue code that bridges JS and native.
|
|
7
|
+
*
|
|
8
|
+
* Consumers don't use this directly — use FloatingOverlayWrapper.tsx instead.
|
|
9
|
+
*
|
|
10
|
+
* Naming convention: file must end in NativeComponent.ts (Codegen convention).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ViewProps } from 'react-native';
|
|
14
|
+
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
15
|
+
|
|
16
|
+
export interface NativeProps extends ViewProps {}
|
|
17
|
+
|
|
18
|
+
// Codegen reads this export to generate the native component interfaces.
|
|
19
|
+
export default codegenNativeComponent<NativeProps>('MobileAIFloatingOverlay');
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { useState } from 'react';
|
|
11
11
|
import { View, Text, TouchableOpacity, TextInput, StyleSheet } from 'react-native';
|
|
12
|
+
import { MobileAI } from "../services/telemetry/index.js";
|
|
12
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
14
|
const EMOJI_OPTIONS = [{
|
|
14
15
|
emoji: '😡',
|
|
@@ -31,6 +32,22 @@ const EMOJI_OPTIONS = [{
|
|
|
31
32
|
label: 'Amazing',
|
|
32
33
|
score: 5
|
|
33
34
|
}];
|
|
35
|
+
const CES_OPTIONS = [{
|
|
36
|
+
label: 'Very Difficult',
|
|
37
|
+
score: 1
|
|
38
|
+
}, {
|
|
39
|
+
label: 'Difficult',
|
|
40
|
+
score: 2
|
|
41
|
+
}, {
|
|
42
|
+
label: 'Neutral',
|
|
43
|
+
score: 3
|
|
44
|
+
}, {
|
|
45
|
+
label: 'Easy',
|
|
46
|
+
score: 4
|
|
47
|
+
}, {
|
|
48
|
+
label: 'Very Easy',
|
|
49
|
+
score: 5
|
|
50
|
+
}];
|
|
34
51
|
const STAR_COUNT = 5;
|
|
35
52
|
export function CSATSurvey({
|
|
36
53
|
config,
|
|
@@ -44,8 +61,10 @@ export function CSATSurvey({
|
|
|
44
61
|
const primary = theme?.primaryColor ?? '#8b5cf6';
|
|
45
62
|
const textColor = theme?.textColor ?? '#ffffff';
|
|
46
63
|
const bgColor = theme?.backgroundColor ?? 'rgba(26, 26, 46, 0.98)';
|
|
64
|
+
const surveyType = config.surveyType ?? 'csat';
|
|
47
65
|
const ratingType = config.ratingType ?? 'emoji';
|
|
48
|
-
const
|
|
66
|
+
const defaultQuestion = surveyType === 'ces' ? 'How easy was it to get the help you needed?' : 'How was your experience?';
|
|
67
|
+
const question = config.question ?? defaultQuestion;
|
|
49
68
|
const handleSubmit = () => {
|
|
50
69
|
if (selectedScore === null) return;
|
|
51
70
|
const rating = {
|
|
@@ -56,6 +75,21 @@ export function CSATSurvey({
|
|
|
56
75
|
config.onSubmit(rating);
|
|
57
76
|
setSubmitted(true);
|
|
58
77
|
|
|
78
|
+
// Track CSAT/CES response
|
|
79
|
+
const fcrAchieved = selectedScore >= 4 || ratingType === 'thumbs' && selectedScore === 5;
|
|
80
|
+
const eventName = surveyType === 'ces' ? 'ces_response' : 'csat_response';
|
|
81
|
+
MobileAI.track(eventName, {
|
|
82
|
+
score: selectedScore,
|
|
83
|
+
fcrAchieved,
|
|
84
|
+
ticketId: metadata?.ticketId
|
|
85
|
+
});
|
|
86
|
+
if (fcrAchieved) {
|
|
87
|
+
MobileAI.track('fcr_achieved', {
|
|
88
|
+
score: selectedScore,
|
|
89
|
+
ticketId: metadata?.ticketId
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
59
93
|
// Auto-dismiss after 1.5s
|
|
60
94
|
setTimeout(onDismiss, 1500);
|
|
61
95
|
};
|
|
@@ -83,7 +117,28 @@ export function CSATSurvey({
|
|
|
83
117
|
children: question
|
|
84
118
|
}), /*#__PURE__*/_jsxs(View, {
|
|
85
119
|
style: styles.ratingContainer,
|
|
86
|
-
children: [
|
|
120
|
+
children: [surveyType === 'ces' && /*#__PURE__*/_jsx(View, {
|
|
121
|
+
style: styles.cesRow,
|
|
122
|
+
children: CES_OPTIONS.map(opt => /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
123
|
+
onPress: () => setSelectedScore(opt.score),
|
|
124
|
+
style: [styles.cesButton, selectedScore === opt.score && {
|
|
125
|
+
backgroundColor: `${primary}30`,
|
|
126
|
+
borderColor: primary
|
|
127
|
+
}],
|
|
128
|
+
activeOpacity: 0.7,
|
|
129
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
130
|
+
style: [styles.cesNumber, {
|
|
131
|
+
color: selectedScore === opt.score ? primary : textColor
|
|
132
|
+
}],
|
|
133
|
+
children: opt.score
|
|
134
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
135
|
+
style: [styles.cesLabel, {
|
|
136
|
+
color: selectedScore === opt.score ? primary : '#71717a'
|
|
137
|
+
}],
|
|
138
|
+
children: opt.label
|
|
139
|
+
})]
|
|
140
|
+
}, opt.score))
|
|
141
|
+
}), surveyType === 'csat' && ratingType === 'emoji' && /*#__PURE__*/_jsx(View, {
|
|
87
142
|
style: styles.emojiRow,
|
|
88
143
|
children: EMOJI_OPTIONS.map(opt => /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
89
144
|
onPress: () => setSelectedScore(opt.score),
|
|
@@ -102,7 +157,7 @@ export function CSATSurvey({
|
|
|
102
157
|
children: opt.label
|
|
103
158
|
})]
|
|
104
159
|
}, opt.score))
|
|
105
|
-
}), ratingType === 'stars' && /*#__PURE__*/_jsx(View, {
|
|
160
|
+
}), surveyType === 'csat' && ratingType === 'stars' && /*#__PURE__*/_jsx(View, {
|
|
106
161
|
style: styles.starsRow,
|
|
107
162
|
children: Array.from({
|
|
108
163
|
length: STAR_COUNT
|
|
@@ -116,28 +171,32 @@ export function CSATSurvey({
|
|
|
116
171
|
children: "\u2605"
|
|
117
172
|
})
|
|
118
173
|
}, star))
|
|
119
|
-
}), ratingType === 'thumbs' && /*#__PURE__*/_jsxs(View, {
|
|
174
|
+
}), surveyType === 'csat' && ratingType === 'thumbs' && /*#__PURE__*/_jsxs(View, {
|
|
120
175
|
style: styles.thumbsRow,
|
|
121
176
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
122
|
-
onPress: () => setSelectedScore(
|
|
123
|
-
style: [styles.thumbButton, selectedScore ===
|
|
124
|
-
backgroundColor: '
|
|
177
|
+
onPress: () => setSelectedScore(1),
|
|
178
|
+
style: [styles.thumbButton, selectedScore === 1 && {
|
|
179
|
+
backgroundColor: 'rgba(239, 68, 68, 0.2)',
|
|
125
180
|
borderColor: '#ef4444'
|
|
126
181
|
}],
|
|
127
182
|
activeOpacity: 0.7,
|
|
128
183
|
children: /*#__PURE__*/_jsx(Text, {
|
|
129
|
-
style: styles.thumbEmoji,
|
|
184
|
+
style: [styles.thumbEmoji, selectedScore === 1 && {
|
|
185
|
+
opacity: 1
|
|
186
|
+
}],
|
|
130
187
|
children: "\uD83D\uDC4E"
|
|
131
188
|
})
|
|
132
189
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
133
|
-
onPress: () => setSelectedScore(
|
|
134
|
-
style: [styles.thumbButton, selectedScore ===
|
|
135
|
-
backgroundColor: '
|
|
190
|
+
onPress: () => setSelectedScore(5),
|
|
191
|
+
style: [styles.thumbButton, selectedScore === 5 && {
|
|
192
|
+
backgroundColor: 'rgba(34, 197, 94, 0.2)',
|
|
136
193
|
borderColor: '#22c55e'
|
|
137
194
|
}],
|
|
138
195
|
activeOpacity: 0.7,
|
|
139
196
|
children: /*#__PURE__*/_jsx(Text, {
|
|
140
|
-
style: styles.thumbEmoji,
|
|
197
|
+
style: [styles.thumbEmoji, selectedScore === 5 && {
|
|
198
|
+
opacity: 1
|
|
199
|
+
}],
|
|
141
200
|
children: "\uD83D\uDC4D"
|
|
142
201
|
})
|
|
143
202
|
})]
|
|
@@ -219,6 +278,30 @@ const styles = StyleSheet.create({
|
|
|
219
278
|
marginTop: 4,
|
|
220
279
|
fontWeight: '500'
|
|
221
280
|
},
|
|
281
|
+
cesRow: {
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
justifyContent: 'center',
|
|
284
|
+
gap: 4
|
|
285
|
+
},
|
|
286
|
+
cesButton: {
|
|
287
|
+
alignItems: 'center',
|
|
288
|
+
paddingHorizontal: 8,
|
|
289
|
+
paddingVertical: 10,
|
|
290
|
+
borderRadius: 8,
|
|
291
|
+
borderWidth: 1,
|
|
292
|
+
borderColor: 'transparent',
|
|
293
|
+
minWidth: 60
|
|
294
|
+
},
|
|
295
|
+
cesNumber: {
|
|
296
|
+
fontSize: 22,
|
|
297
|
+
fontWeight: 'bold',
|
|
298
|
+
marginBottom: 4
|
|
299
|
+
},
|
|
300
|
+
cesLabel: {
|
|
301
|
+
fontSize: 9,
|
|
302
|
+
fontWeight: '600',
|
|
303
|
+
textAlign: 'center'
|
|
304
|
+
},
|
|
222
305
|
starsRow: {
|
|
223
306
|
flexDirection: 'row',
|
|
224
307
|
justifyContent: 'center',
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* Handles:
|
|
15
15
|
* - Server heartbeat pings (type: 'ping') — acknowledged silently
|
|
16
16
|
* - Auto-reconnect on unexpected close (max 3 attempts, exponential backoff)
|
|
17
|
+
* - Message queue — buffers sendText calls while connecting, flushes on open
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
export class EscalationSocket {
|
|
@@ -22,6 +23,10 @@ export class EscalationSocket {
|
|
|
22
23
|
reconnectAttempts = 0;
|
|
23
24
|
reconnectTimer = null;
|
|
24
25
|
intentionalClose = false;
|
|
26
|
+
_hasErrored = false;
|
|
27
|
+
|
|
28
|
+
/** Messages buffered while the socket is connecting / reconnecting. */
|
|
29
|
+
messageQueue = [];
|
|
25
30
|
constructor(options) {
|
|
26
31
|
this.onReply = options.onReply;
|
|
27
32
|
this.onError = options.onError;
|
|
@@ -32,9 +37,33 @@ export class EscalationSocket {
|
|
|
32
37
|
connect(wsUrl) {
|
|
33
38
|
this.wsUrl = wsUrl;
|
|
34
39
|
this.intentionalClose = false;
|
|
40
|
+
this._hasErrored = false;
|
|
35
41
|
this.openConnection();
|
|
36
42
|
}
|
|
43
|
+
|
|
44
|
+
/** True if the underlying WebSocket is open and ready to send. */
|
|
45
|
+
get isConnected() {
|
|
46
|
+
return this.ws?.readyState === 1; // WebSocket.OPEN
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** True if the socket encountered an error (and may not be reliable to reuse). */
|
|
50
|
+
get hasErrored() {
|
|
51
|
+
return this._hasErrored;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Send a text message to the live agent.
|
|
56
|
+
*
|
|
57
|
+
* If the socket is currently connecting or reconnecting, the message is
|
|
58
|
+
* buffered and sent automatically once the connection is established.
|
|
59
|
+
* Returns `true` in both cases (connected send + queued send).
|
|
60
|
+
* Returns `false` only if the socket has no URL (was never connected).
|
|
61
|
+
*/
|
|
37
62
|
sendText(text) {
|
|
63
|
+
if (!this.wsUrl) {
|
|
64
|
+
// No URL at all — nothing we can do.
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
38
67
|
if (this.ws?.readyState === 1) {
|
|
39
68
|
// WebSocket.OPEN
|
|
40
69
|
this.ws.send(JSON.stringify({
|
|
@@ -43,7 +72,23 @@ export class EscalationSocket {
|
|
|
43
72
|
}));
|
|
44
73
|
return true;
|
|
45
74
|
}
|
|
46
|
-
|
|
75
|
+
|
|
76
|
+
// Socket is connecting (CONNECTING=0) or reconnecting (CLOSED=3 → scheduleReconnect).
|
|
77
|
+
// Queue the message so it is flushed as soon as onopen fires.
|
|
78
|
+
console.log('[EscalationSocket] ⏳ Socket not open — queuing message for when connected');
|
|
79
|
+
this.messageQueue.push(JSON.stringify({
|
|
80
|
+
type: 'user_message',
|
|
81
|
+
content: text
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
// If the socket is fully closed (not just connecting), kick off a reconnect now
|
|
85
|
+
// rather than waiting for scheduleReconnect's timeout to fire.
|
|
86
|
+
const state = this.ws?.readyState;
|
|
87
|
+
if (state === undefined || state === 3 /* CLOSED */) {
|
|
88
|
+
console.log('[EscalationSocket] Socket CLOSED — initiating reconnect to flush queue');
|
|
89
|
+
this.openConnection();
|
|
90
|
+
}
|
|
91
|
+
return true; // optimistic — message is queued
|
|
47
92
|
}
|
|
48
93
|
sendTypingStatus(isTyping) {
|
|
49
94
|
if (this.ws?.readyState === 1) {
|
|
@@ -56,6 +101,7 @@ export class EscalationSocket {
|
|
|
56
101
|
}
|
|
57
102
|
disconnect() {
|
|
58
103
|
this.intentionalClose = true;
|
|
104
|
+
this.messageQueue = []; // drop queued messages on intentional close
|
|
59
105
|
if (this.reconnectTimer) {
|
|
60
106
|
clearTimeout(this.reconnectTimer);
|
|
61
107
|
this.reconnectTimer = null;
|
|
@@ -65,8 +111,26 @@ export class EscalationSocket {
|
|
|
65
111
|
this.ws = null;
|
|
66
112
|
}
|
|
67
113
|
}
|
|
114
|
+
flushQueue() {
|
|
115
|
+
if (this.messageQueue.length === 0) return;
|
|
116
|
+
console.log(`[EscalationSocket] 🚀 Flushing ${this.messageQueue.length} queued message(s)`);
|
|
117
|
+
const queue = this.messageQueue.splice(0); // drain atomically
|
|
118
|
+
for (const payload of queue) {
|
|
119
|
+
try {
|
|
120
|
+
this.ws?.send(payload);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error('[EscalationSocket] Failed to flush queued message:', err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
68
126
|
openConnection() {
|
|
69
127
|
if (!this.wsUrl) return;
|
|
128
|
+
|
|
129
|
+
// Don't open a second socket if one is already connecting
|
|
130
|
+
if (this.ws && this.ws.readyState === 0 /* CONNECTING */) {
|
|
131
|
+
console.log('[EscalationSocket] Already connecting — skipping duplicate openConnection');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
70
134
|
try {
|
|
71
135
|
this.ws = new WebSocket(this.wsUrl);
|
|
72
136
|
} catch (err) {
|
|
@@ -76,6 +140,8 @@ export class EscalationSocket {
|
|
|
76
140
|
this.ws.onopen = () => {
|
|
77
141
|
console.log('[EscalationSocket] ✅ Connected to:', this.wsUrl);
|
|
78
142
|
this.reconnectAttempts = 0;
|
|
143
|
+
this._hasErrored = false;
|
|
144
|
+
this.flushQueue(); // send any messages that arrived while connecting
|
|
79
145
|
};
|
|
80
146
|
this.ws.onmessage = event => {
|
|
81
147
|
try {
|
|
@@ -107,6 +173,7 @@ export class EscalationSocket {
|
|
|
107
173
|
};
|
|
108
174
|
this.ws.onerror = event => {
|
|
109
175
|
console.error('[EscalationSocket] ❌ WebSocket error. URL was:', this.wsUrl, event);
|
|
176
|
+
this._hasErrored = true;
|
|
110
177
|
this.onError?.(event);
|
|
111
178
|
};
|
|
112
179
|
this.ws.onclose = event => {
|
|
@@ -118,12 +185,14 @@ export class EscalationSocket {
|
|
|
118
185
|
scheduleReconnect() {
|
|
119
186
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
120
187
|
console.warn('[EscalationSocket] Max reconnect attempts reached — giving up');
|
|
188
|
+
this.messageQueue = []; // drop queued messages — connection is permanently lost
|
|
121
189
|
return;
|
|
122
190
|
}
|
|
123
191
|
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 16_000);
|
|
124
192
|
this.reconnectAttempts++;
|
|
125
193
|
console.log(`[EscalationSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
126
194
|
this.reconnectTimer = setTimeout(() => {
|
|
195
|
+
this._hasErrored = false; // clear error flag before reconnect attempt
|
|
127
196
|
this.openConnection();
|
|
128
197
|
}, delay);
|
|
129
198
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
export class ReportedIssueEventSource {
|
|
5
|
+
abortController = null;
|
|
6
|
+
intentionalClose = false;
|
|
7
|
+
reconnectAttempts = 0;
|
|
8
|
+
reconnectTimer = null;
|
|
9
|
+
maxReconnectAttempts = 5;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
connect() {
|
|
14
|
+
this.intentionalClose = false;
|
|
15
|
+
this.reconnectAttempts = 0;
|
|
16
|
+
this.openConnection();
|
|
17
|
+
}
|
|
18
|
+
disconnect() {
|
|
19
|
+
this.intentionalClose = true;
|
|
20
|
+
if (this.reconnectTimer) {
|
|
21
|
+
clearTimeout(this.reconnectTimer);
|
|
22
|
+
this.reconnectTimer = null;
|
|
23
|
+
}
|
|
24
|
+
if (this.abortController) {
|
|
25
|
+
this.abortController.abort();
|
|
26
|
+
this.abortController = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async openConnection() {
|
|
30
|
+
if (this.intentionalClose) return;
|
|
31
|
+
this.abortController = new AbortController();
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(this.options.url, {
|
|
34
|
+
signal: this.abortController.signal,
|
|
35
|
+
headers: {
|
|
36
|
+
Accept: 'text/event-stream'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
this.scheduleReconnect();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!response.body) {
|
|
44
|
+
await this.readFullResponse(response);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.reconnectAttempts = 0;
|
|
48
|
+
await this.readStream(response.body);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (this.intentionalClose) return;
|
|
51
|
+
if (error.name === 'AbortError') return;
|
|
52
|
+
this.options.onError?.(error);
|
|
53
|
+
this.scheduleReconnect();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async readStream(body) {
|
|
57
|
+
const reader = body.getReader();
|
|
58
|
+
const decoder = new TextDecoder();
|
|
59
|
+
let buffer = '';
|
|
60
|
+
let currentEvent = '';
|
|
61
|
+
let currentData = '';
|
|
62
|
+
try {
|
|
63
|
+
while (true) {
|
|
64
|
+
const {
|
|
65
|
+
done,
|
|
66
|
+
value
|
|
67
|
+
} = await reader.read();
|
|
68
|
+
if (done) break;
|
|
69
|
+
buffer += decoder.decode(value, {
|
|
70
|
+
stream: true
|
|
71
|
+
});
|
|
72
|
+
const lines = buffer.split('\n');
|
|
73
|
+
buffer = lines.pop() ?? '';
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
if (line.startsWith('event: ')) {
|
|
76
|
+
currentEvent = line.slice(7).trim();
|
|
77
|
+
} else if (line.startsWith('data: ')) {
|
|
78
|
+
currentData = line.slice(6).trim();
|
|
79
|
+
} else if (line === '' && currentEvent) {
|
|
80
|
+
this.handleEvent(currentEvent, currentData);
|
|
81
|
+
currentEvent = '';
|
|
82
|
+
currentData = '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (this.intentionalClose) return;
|
|
88
|
+
if (error.name === 'AbortError') return;
|
|
89
|
+
logger.warn('ReportedIssueSSE', 'Stream read error:', error.message);
|
|
90
|
+
}
|
|
91
|
+
if (!this.intentionalClose) {
|
|
92
|
+
this.scheduleReconnect();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async readFullResponse(response) {
|
|
96
|
+
try {
|
|
97
|
+
const text = await response.text();
|
|
98
|
+
let currentEvent = '';
|
|
99
|
+
let currentData = '';
|
|
100
|
+
for (const line of text.split('\n')) {
|
|
101
|
+
if (line.startsWith('event: ')) {
|
|
102
|
+
currentEvent = line.slice(7).trim();
|
|
103
|
+
} else if (line.startsWith('data: ')) {
|
|
104
|
+
currentData = line.slice(6).trim();
|
|
105
|
+
} else if (line === '' && currentEvent) {
|
|
106
|
+
this.handleEvent(currentEvent, currentData);
|
|
107
|
+
currentEvent = '';
|
|
108
|
+
currentData = '';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (this.intentionalClose) return;
|
|
113
|
+
logger.warn('ReportedIssueSSE', 'Full response read error:', error.message);
|
|
114
|
+
}
|
|
115
|
+
if (!this.intentionalClose) {
|
|
116
|
+
this.scheduleReconnect();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
handleEvent(event, data) {
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(data);
|
|
122
|
+
if (event === 'connected') {
|
|
123
|
+
this.options.onConnected?.();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (event === 'reported_issue_update' && parsed?.issue) {
|
|
127
|
+
this.options.onIssueUpdate?.(parsed.issue);
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
// ignore bad payload
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
scheduleReconnect() {
|
|
134
|
+
if (this.intentionalClose) return;
|
|
135
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
136
|
+
logger.warn('ReportedIssueSSE', 'Max reconnect attempts reached — giving up');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 16_000);
|
|
140
|
+
this.reconnectAttempts++;
|
|
141
|
+
this.reconnectTimer = setTimeout(() => {
|
|
142
|
+
this.openConnection().catch(() => {
|
|
143
|
+
// Connection errors are handled inside openConnection.
|
|
144
|
+
});
|
|
145
|
+
}, delay);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=ReportedIssueEventSource.js.map
|
|
@@ -36,6 +36,7 @@ export function createEscalateTool(depsOrConfig, legacyGetContext) {
|
|
|
36
36
|
analyticsKey,
|
|
37
37
|
getContext,
|
|
38
38
|
getHistory,
|
|
39
|
+
getToolCalls,
|
|
39
40
|
onHumanReply,
|
|
40
41
|
onEscalationStarted,
|
|
41
42
|
onTypingChange,
|
|
@@ -87,6 +88,7 @@ export function createEscalateTool(depsOrConfig, legacyGetContext) {
|
|
|
87
88
|
device: getDeviceMetadata()
|
|
88
89
|
},
|
|
89
90
|
screenFlow: getScreenFlow?.() ?? [],
|
|
91
|
+
toolCalls: getToolCalls?.() ?? [],
|
|
90
92
|
pushToken,
|
|
91
93
|
pushTokenType,
|
|
92
94
|
deviceId: getDeviceId()
|
|
@@ -131,7 +133,7 @@ export function createEscalateTool(depsOrConfig, legacyGetContext) {
|
|
|
131
133
|
} catch (err) {
|
|
132
134
|
logger.error('Escalation', 'Network error:', err.message);
|
|
133
135
|
}
|
|
134
|
-
const message = config.escalationMessage ??
|
|
136
|
+
const message = config.escalationMessage ?? "Your request has been sent to our support team. A human agent will reply here as soon as possible.";
|
|
135
137
|
return `ESCALATED: ${message}`;
|
|
136
138
|
}
|
|
137
139
|
}
|
|
@@ -144,7 +146,7 @@ export function createEscalateTool(depsOrConfig, legacyGetContext) {
|
|
|
144
146
|
stepsBeforeEscalation: context.stepsBeforeEscalation
|
|
145
147
|
};
|
|
146
148
|
config.onEscalate?.(escalationContext);
|
|
147
|
-
const message = config.escalationMessage ??
|
|
149
|
+
const message = config.escalationMessage ?? "Your request has been sent to our support team. A human agent will reply here as soon as possible.";
|
|
148
150
|
return `ESCALATED: ${message}`;
|
|
149
151
|
}
|
|
150
152
|
};
|
|
@@ -11,6 +11,7 @@ export { buildSupportPrompt } from "./supportPrompt.js";
|
|
|
11
11
|
|
|
12
12
|
// Escalation tool + WebSocket manager
|
|
13
13
|
export { createEscalateTool } from "./escalateTool.js";
|
|
14
|
+
export { createReportIssueTool } from "./reportIssueTool.js";
|
|
14
15
|
export { EscalationSocket } from "./EscalationSocket.js";
|
|
15
16
|
export { EscalationEventSource } from "./EscalationEventSource.js";
|
|
16
17
|
|