@mobileai/react-native 0.9.26 → 0.9.28

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 (67) hide show
  1. package/README.md +28 -15
  2. package/android/build.gradle +17 -0
  3. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayDialogRootViewGroup.kt +243 -0
  4. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +281 -87
  5. package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +52 -17
  6. package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +49 -2
  7. package/bin/generate-map.cjs +556 -126
  8. package/ios/Podfile +63 -0
  9. package/ios/Podfile.lock +2290 -0
  10. package/ios/Podfile.properties.json +4 -0
  11. package/ios/mobileaireactnative/AppDelegate.swift +69 -0
  12. package/ios/mobileaireactnative/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  13. package/ios/mobileaireactnative/Images.xcassets/Contents.json +6 -0
  14. package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +21 -0
  15. package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/SplashScreenLegacy.png +0 -0
  16. package/ios/mobileaireactnative/Info.plist +55 -0
  17. package/ios/mobileaireactnative/PrivacyInfo.xcprivacy +48 -0
  18. package/ios/mobileaireactnative/SplashScreen.storyboard +47 -0
  19. package/ios/mobileaireactnative/Supporting/Expo.plist +6 -0
  20. package/ios/mobileaireactnative/mobileaireactnative-Bridging-Header.h +3 -0
  21. package/ios/mobileaireactnative.xcodeproj/project.pbxproj +547 -0
  22. package/ios/mobileaireactnative.xcodeproj/xcshareddata/xcschemes/mobileaireactnative.xcscheme +88 -0
  23. package/ios/mobileaireactnative.xcworkspace/contents.xcworkspacedata +10 -0
  24. package/lib/module/components/AIAgent.js +407 -148
  25. package/lib/module/components/AgentChatBar.js +253 -62
  26. package/lib/module/components/FloatingOverlayWrapper.js +68 -32
  27. package/lib/module/config/endpoints.js +22 -1
  28. package/lib/module/core/AgentRuntime.js +192 -24
  29. package/lib/module/core/FiberTreeWalker.js +410 -34
  30. package/lib/module/core/OutcomeVerifier.js +149 -0
  31. package/lib/module/core/systemPrompt.js +126 -44
  32. package/lib/module/providers/GeminiProvider.js +9 -3
  33. package/lib/module/services/MobileAIKnowledgeRetriever.js +1 -1
  34. package/lib/module/services/telemetry/MobileAI.js +1 -1
  35. package/lib/module/services/telemetry/TelemetryService.js +21 -2
  36. package/lib/module/services/telemetry/TouchAutoCapture.js +45 -35
  37. package/lib/module/specs/FloatingOverlayNativeComponent.ts +7 -1
  38. package/lib/module/support/supportPrompt.js +22 -7
  39. package/lib/module/support/supportStyle.js +55 -0
  40. package/lib/module/support/types.js +2 -0
  41. package/lib/module/tools/tapTool.js +77 -6
  42. package/lib/module/tools/typeTool.js +20 -0
  43. package/lib/module/utils/humanizeScreenName.js +49 -0
  44. package/lib/typescript/src/components/AIAgent.d.ts +6 -2
  45. package/lib/typescript/src/components/AgentChatBar.d.ts +15 -1
  46. package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +22 -10
  47. package/lib/typescript/src/config/endpoints.d.ts +4 -0
  48. package/lib/typescript/src/core/AgentRuntime.d.ts +17 -1
  49. package/lib/typescript/src/core/FiberTreeWalker.d.ts +12 -1
  50. package/lib/typescript/src/core/OutcomeVerifier.d.ts +46 -0
  51. package/lib/typescript/src/core/systemPrompt.d.ts +3 -10
  52. package/lib/typescript/src/core/types.d.ts +37 -1
  53. package/lib/typescript/src/index.d.ts +1 -0
  54. package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +1 -1
  55. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +7 -1
  56. package/lib/typescript/src/services/telemetry/types.d.ts +1 -1
  57. package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +5 -0
  58. package/lib/typescript/src/support/index.d.ts +1 -0
  59. package/lib/typescript/src/support/supportStyle.d.ts +9 -0
  60. package/lib/typescript/src/support/types.d.ts +3 -0
  61. package/lib/typescript/src/tools/tapTool.d.ts +3 -2
  62. package/lib/typescript/src/utils/humanizeScreenName.d.ts +6 -0
  63. package/lib/typescript/test-tree.d.ts +2 -0
  64. package/package.json +5 -2
  65. package/src/specs/FloatingOverlayNativeComponent.ts +7 -1
  66. package/ios/MobileAIFloatingOverlayComponentView.mm +0 -73
  67. package/ios/MobileAIPilotIntents.swift +0 -51
package/README.md CHANGED
@@ -256,7 +256,7 @@ Your app becomes MCP-compatible with one prop. Connect any AI — Antigravity, C
256
256
  <AIAgent
257
257
  showChatBar={false}
258
258
  mcpServerUrl="ws://localhost:3101"
259
- apiKey="YOUR_KEY"
259
+ analyticsKey="mobileai_pub_xxx"
260
260
  navRef={navRef}
261
261
  >
262
262
  <App />
@@ -391,6 +391,8 @@ npx @mobileai/react-native generate-map
391
391
 
392
392
  ### 2. Wrap Your App
393
393
 
394
+ If you use a MobileAI publishable key, the SDK now defaults to the hosted MobileAI text and voice proxies automatically. You only need to pass `proxyUrl` and `voiceProxyUrl` when you want to override them with your own backend.
395
+
394
396
  #### React Navigation
395
397
 
396
398
  ```tsx
@@ -403,12 +405,9 @@ export default function App() {
403
405
 
404
406
  return (
405
407
  <AIAgent
406
- // ⚠️ Prototyping ONLY — don't ship API keys in production
407
- apiKey="YOUR_API_KEY"
408
-
409
- // ✅ Production: route through your secure backend proxy
410
- // proxyUrl="https://api.yourdomain.com/ai-proxy"
411
- // proxyHeaders={{ Authorization: `Bearer ${userToken}` }}
408
+ // Your MobileAI Dashboard ID
409
+ // This now auto-configures the hosted MobileAI text + voice proxies too.
410
+ analyticsKey="mobileai_pub_xxxxxxxx"
412
411
 
413
412
  navRef={navRef}
414
413
  screenMap={screenMap} // optional but recommended
@@ -435,7 +434,8 @@ export default function RootLayout() {
435
434
 
436
435
  return (
437
436
  <AIAgent
438
- apiKey={process.env.AI_API_KEY!}
437
+ // Hosted MobileAI proxies are inferred automatically from analyticsKey
438
+ analyticsKey="mobileai_pub_xxxxxxxx"
439
439
  navRef={navRef}
440
440
  screenMap={screenMap}
441
441
  >
@@ -462,6 +462,19 @@ The examples above use **Gemini** (default). To use **OpenAI** for text mode, ad
462
462
 
463
463
  A floating chat bar appears automatically. Ask the AI to navigate, tap buttons, fill forms, answer questions.
464
464
 
465
+ ### Hosted MobileAI Defaults
466
+
467
+ For the standard MobileAI Cloud setup, this is enough:
468
+
469
+ ```tsx
470
+ <AIAgent analyticsKey="mobileai_pub_xxxxxxxx" navRef={navRef} />
471
+ ```
472
+
473
+ Only pass explicit proxy props when:
474
+ - you want to use your own backend proxy
475
+ - you want a dedicated voice proxy
476
+ - you are self-hosting the MobileAI backend
477
+
465
478
  ### Knowledge-Only Mode — AI Assistant Without UI Automation
466
479
 
467
480
  Set `enableUIControl={false}` for a lightweight FAQ / support assistant. Single LLM call, ~70% fewer tokens:
@@ -485,7 +498,7 @@ The agent operates in **copilot mode** by default. It navigates, scrolls, types,
485
498
 
486
499
  ```tsx
487
500
  // Default — copilot mode, zero extra config:
488
- <AIAgent apiKey="..." navRef={navRef}>
501
+ <AIAgent analyticsKey="mobileai_pub_xxx" navRef={navRef}>
489
502
  <App />
490
503
  </AIAgent>
491
504
  ```
@@ -551,7 +564,6 @@ Transform the AI agent into a production-grade support system. The AI resolves i
551
564
  import { SupportGreeting, buildSupportPrompt, createEscalateTool } from '@mobileai/react-native';
552
565
 
553
566
  <AIAgent
554
- apiKey="..."
555
567
  analyticsKey="mobileai_pub_xxx" // required for MobileAI escalation
556
568
  instructions={{
557
569
  system: buildSupportPrompt({
@@ -870,9 +882,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
870
882
  |------|------|---------|-------------|
871
883
  | `apiKey` | `string` | — | API key for your provider (prototyping only — use `proxyUrl` in production). |
872
884
  | `provider` | `'gemini' \| 'openai'` | `'gemini'` | LLM provider for text mode. |
873
- | `proxyUrl` | `string` | | Backend proxy URL (production). Routes all LLM traffic through your server. |
885
+ | `proxyUrl` | `string` | Hosted MobileAI text proxy when `analyticsKey` is set | Backend proxy URL (production). Routes all LLM traffic through your server. |
874
886
  | `proxyHeaders` | `Record<string, string>` | — | Auth headers for proxy (e.g., `Authorization: Bearer ${token}`). |
875
- | `voiceProxyUrl` | `string` | | Dedicated proxy for Voice Mode WebSockets. Falls back to `proxyUrl`. |
887
+ | `voiceProxyUrl` | `string` | Hosted MobileAI voice proxy when `analyticsKey` is set; otherwise falls back to `proxyUrl` | Dedicated proxy for Voice Mode WebSockets. |
876
888
  | `voiceProxyHeaders` | `Record<string, string>` | — | Auth headers for voice proxy. |
877
889
  | `model` | `string` | Provider default | Model name (e.g. `gemini-2.5-flash`, `gpt-4.1-mini`). |
878
890
  | `navRef` | `NavigationContainerRef` | — | Navigation ref for auto-navigation. |
@@ -952,7 +964,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
952
964
 
953
965
  | Prop | Type | Default | Description |
954
966
  |------|------|---------|-------------|
955
- | `analyticsKey` | `string` | — | Publishable key (`mobileai_pub_xxx`) — enables auto-analytics. |
967
+ | `analyticsKey` | `string` | — | Publishable key (`mobileai_pub_xxx`) — enables auto-analytics and, by default, the hosted MobileAI text/voice proxies. |
956
968
  | `analyticsProxyUrl` | `string` | — | Enterprise: route events through your backend. |
957
969
  | `analyticsProxyHeaders` | `Record<string, string>` | — | Auth headers for analytics proxy. |
958
970
 
@@ -1064,11 +1076,10 @@ const { send } = useAI({
1064
1076
 
1065
1077
  ## 📊 Zero-Config Analytics — Auto-Capture Every Tap
1066
1078
 
1067
- Just add `analyticsKey` — every button tap, screen navigation, and session is tracked automatically. **Zero code changes** to your app components.
1079
+ Just add `analyticsKey` — every button tap, screen navigation, and session is tracked automatically. It also enables the default hosted MobileAI AI proxies unless you override them. **Zero code changes** to your app components.
1068
1080
 
1069
1081
  ```tsx
1070
1082
  <AIAgent
1071
- apiKey="YOUR_KEY"
1072
1083
  analyticsKey="mobileai_pub_abc123" // ← enables full auto-capture
1073
1084
  navRef={navRef}
1074
1085
  >
@@ -1116,6 +1127,8 @@ MobileAI.identify('user_123', { plan: 'pro' });
1116
1127
 
1117
1128
  ### Backend Proxy — Keep API Keys Secure
1118
1129
 
1130
+ Use this only when you want to override the default hosted MobileAI proxy behavior or route traffic through your own backend.
1131
+
1119
1132
  ```tsx
1120
1133
  <AIAgent
1121
1134
  proxyUrl="https://myapp.vercel.app/api/gemini"
@@ -10,6 +10,15 @@ buildscript {
10
10
  }
11
11
  }
12
12
 
13
+ def isNewArchitectureEnabled() {
14
+ // Libraries opt into Fabric/TurboModule codegen only when the consuming app enables it.
15
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
16
+ }
17
+
18
+ if (isNewArchitectureEnabled()) {
19
+ apply plugin: "com.facebook.react"
20
+ }
21
+
13
22
  apply plugin: "com.android.library"
14
23
  apply plugin: "kotlin-android"
15
24
 
@@ -59,3 +68,11 @@ repositories {
59
68
  dependencies {
60
69
  implementation "com.facebook.react:react-android"
61
70
  }
71
+
72
+ if (isNewArchitectureEnabled()) {
73
+ react {
74
+ jsRootDir = file("../")
75
+ libraryName = "RNMobileAIOverlaySpec"
76
+ codegenJavaPackageName = "com.mobileai.overlay"
77
+ }
78
+ }
@@ -0,0 +1,243 @@
1
+ package com.mobileai.overlay
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.view.MotionEvent
6
+ import android.view.View
7
+ import android.view.ViewConfiguration
8
+ import com.facebook.react.bridge.GuardedRunnable
9
+ import com.facebook.react.bridge.WritableMap
10
+ import com.facebook.react.bridge.WritableNativeMap
11
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
12
+ import com.facebook.react.common.build.ReactBuildConfig
13
+ import com.facebook.react.config.ReactFeatureFlags
14
+ import com.facebook.react.uimanager.JSPointerDispatcher
15
+ import com.facebook.react.uimanager.JSTouchDispatcher
16
+ import com.facebook.react.uimanager.PixelUtil.pxToDp
17
+ import com.facebook.react.uimanager.RootView
18
+ import com.facebook.react.uimanager.StateWrapper
19
+ import com.facebook.react.uimanager.ThemedReactContext
20
+ import com.facebook.react.uimanager.UIManagerModule
21
+ import com.facebook.react.uimanager.events.EventDispatcher
22
+ import com.facebook.react.views.view.ReactViewGroup
23
+ import kotlin.math.abs
24
+ import kotlin.math.roundToInt
25
+
26
+ /**
27
+ * Root view rendered inside the Android floating panel dialog.
28
+ *
29
+ * This mirrors the touch and layout behavior of React Native's modal dialog
30
+ * host so JS children remain fully interactive even though they render inside
31
+ * a separate native window.
32
+ */
33
+ internal class FloatingOverlayDialogRootViewGroup(context: Context) :
34
+ ReactViewGroup(context),
35
+ RootView {
36
+
37
+ var stateWrapper: StateWrapper? = null
38
+ var eventDispatcher: EventDispatcher? = null
39
+ var overlayHost: FloatingOverlayView? = null
40
+
41
+ private var viewWidth: Int = 0
42
+ private var viewHeight: Int = 0
43
+ private val jsTouchDispatcher = JSTouchDispatcher(this)
44
+ private var jsPointerDispatcher: JSPointerDispatcher? = null
45
+ private val touchSlop: Float = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
46
+ private var dragCandidate: Boolean = false
47
+ private var isDraggingWindow: Boolean = false
48
+ private var dragStartRawX: Float = 0f
49
+ private var dragStartRawY: Float = 0f
50
+ private var dragStartWindowX: Int = 0
51
+ private var dragStartWindowY: Int = 0
52
+
53
+ private val reactContext: ThemedReactContext
54
+ get() = context as ThemedReactContext
55
+
56
+ init {
57
+ if (ReactFeatureFlags.dispatchPointerEvents) {
58
+ jsPointerDispatcher = JSPointerDispatcher(this)
59
+ }
60
+ }
61
+
62
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
63
+ super.onSizeChanged(w, h, oldw, oldh)
64
+ viewWidth = w
65
+ viewHeight = h
66
+ updateState(viewWidth, viewHeight)
67
+ }
68
+
69
+ fun updateState(width: Int, height: Int) {
70
+ val realWidth = width.toFloat().pxToDp()
71
+ val realHeight = height.toFloat().pxToDp()
72
+
73
+ val wrapper = stateWrapper
74
+ if (wrapper != null) {
75
+ val newStateData: WritableMap = WritableNativeMap()
76
+ newStateData.putDouble("screenWidth", realWidth.toDouble())
77
+ newStateData.putDouble("screenHeight", realHeight.toDouble())
78
+ wrapper.updateState(newStateData)
79
+ } else if (
80
+ !ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE &&
81
+ !reactContext.isBridgeless
82
+ ) {
83
+ @Suppress("DEPRECATION")
84
+ reactContext.runOnNativeModulesQueueThread(
85
+ object : GuardedRunnable(reactContext) {
86
+ override fun runGuarded() {
87
+ reactContext.reactApplicationContext
88
+ .getNativeModule(UIManagerModule::class.java)
89
+ ?.updateNodeSize(id, viewWidth, viewHeight)
90
+ }
91
+ }
92
+ )
93
+ }
94
+ }
95
+
96
+ override fun handleException(t: Throwable) {
97
+ reactContext.reactApplicationContext.handleException(RuntimeException(t))
98
+ }
99
+
100
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
101
+ when (event.actionMasked) {
102
+ MotionEvent.ACTION_DOWN -> {
103
+ dragCandidate = isInNativeDragRegion(event.x, event.y)
104
+ isDraggingWindow = false
105
+ if (dragCandidate) {
106
+ dragStartRawX = event.rawX
107
+ dragStartRawY = event.rawY
108
+ dragStartWindowX = overlayHost?.getWindowXPx() ?: 0
109
+ dragStartWindowY = overlayHost?.getWindowYPx() ?: 0
110
+ }
111
+ }
112
+
113
+ MotionEvent.ACTION_MOVE -> {
114
+ if (dragCandidate && !isDraggingWindow && hasExceededTouchSlop(event)) {
115
+ isDraggingWindow = true
116
+ parent?.requestDisallowInterceptTouchEvent(true)
117
+ updateNativeWindowDrag(event)
118
+ return true
119
+ }
120
+ }
121
+
122
+ MotionEvent.ACTION_UP,
123
+ MotionEvent.ACTION_CANCEL -> resetNativeDrag()
124
+ }
125
+
126
+ if (isDraggingWindow) return true
127
+
128
+ eventDispatcher?.let { dispatcher ->
129
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
130
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
131
+ }
132
+ return super.onInterceptTouchEvent(event)
133
+ }
134
+
135
+ @SuppressLint("ClickableViewAccessibility")
136
+ override fun onTouchEvent(event: MotionEvent): Boolean {
137
+ if (isDraggingWindow || dragCandidate) {
138
+ when (event.actionMasked) {
139
+ MotionEvent.ACTION_MOVE -> {
140
+ if (!isDraggingWindow && hasExceededTouchSlop(event)) {
141
+ isDraggingWindow = true
142
+ parent?.requestDisallowInterceptTouchEvent(true)
143
+ }
144
+
145
+ if (isDraggingWindow) {
146
+ updateNativeWindowDrag(event)
147
+ return true
148
+ }
149
+ }
150
+
151
+ MotionEvent.ACTION_UP -> {
152
+ if (isDraggingWindow) {
153
+ updateNativeWindowDrag(event)
154
+ overlayHost?.emitWindowDragEnd()
155
+ resetNativeDrag()
156
+ return true
157
+ }
158
+ resetNativeDrag()
159
+ }
160
+
161
+ MotionEvent.ACTION_CANCEL -> {
162
+ if (isDraggingWindow) {
163
+ overlayHost?.emitWindowDragEnd()
164
+ resetNativeDrag()
165
+ return true
166
+ }
167
+ resetNativeDrag()
168
+ }
169
+ }
170
+ }
171
+
172
+ eventDispatcher?.let { dispatcher ->
173
+ jsTouchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
174
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
175
+ }
176
+ super.onTouchEvent(event)
177
+ return true
178
+ }
179
+
180
+ override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
181
+ eventDispatcher?.let { dispatcher ->
182
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, true)
183
+ }
184
+ return super.onInterceptHoverEvent(event)
185
+ }
186
+
187
+ override fun onHoverEvent(event: MotionEvent): Boolean {
188
+ eventDispatcher?.let { dispatcher ->
189
+ jsPointerDispatcher?.handleMotionEvent(event, dispatcher, false)
190
+ }
191
+ return super.onHoverEvent(event)
192
+ }
193
+
194
+ @OptIn(UnstableReactNativeAPI::class)
195
+ override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
196
+ eventDispatcher?.let { dispatcher ->
197
+ jsTouchDispatcher.onChildStartedNativeGesture(ev, dispatcher, reactContext)
198
+ jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, dispatcher)
199
+ }
200
+ }
201
+
202
+ override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
203
+ eventDispatcher?.let { dispatcher ->
204
+ jsTouchDispatcher.onChildEndedNativeGesture(ev, dispatcher)
205
+ }
206
+ jsPointerDispatcher?.onChildEndedNativeGesture()
207
+ }
208
+
209
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
210
+ // No-op so the root can continue receiving events for JS dispatch.
211
+ }
212
+
213
+ private fun hasExceededTouchSlop(event: MotionEvent): Boolean {
214
+ return abs(event.rawX - dragStartRawX) > touchSlop || abs(event.rawY - dragStartRawY) > touchSlop
215
+ }
216
+
217
+ private fun updateNativeWindowDrag(event: MotionEvent) {
218
+ val targetX = dragStartWindowX + (event.rawX - dragStartRawX).roundToInt()
219
+ val targetY = dragStartWindowY + (event.rawY - dragStartRawY).roundToInt()
220
+ overlayHost?.updateWindowPositionFromNative(targetX, targetY)
221
+ }
222
+
223
+ private fun resetNativeDrag() {
224
+ dragCandidate = false
225
+ isDraggingWindow = false
226
+ }
227
+
228
+ private fun isInNativeDragRegion(x: Float, y: Float): Boolean {
229
+ if (viewWidth <= dpToPx(80) && viewHeight <= dpToPx(80)) {
230
+ return true
231
+ }
232
+
233
+ val handleTop = dpToPx(44)
234
+ val handleHalfWidth = dpToPx(72)
235
+ val centerX = viewWidth / 2f
236
+
237
+ return y <= handleTop && x in (centerX - handleHalfWidth)..(centerX + handleHalfWidth)
238
+ }
239
+
240
+ private fun dpToPx(value: Int): Float {
241
+ return value * resources.displayMetrics.density
242
+ }
243
+ }