@sagepilot-ai/react-native-sdk 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -38,7 +38,7 @@ You configure the SDK with:
38
38
  - `tokenStorage`: secure storage adapter for SDK-created customer session tokens
39
39
  - optional request, presentation, and behavior settings
40
40
 
41
- For normal Sagepilot cloud usage, omit `host`; API calls and the WebView default to `https://app.sagepilot.ai`.
41
+ For standard Sagepilot cloud usage, omit `host`. The SDK connects to Sagepilot's hosted endpoints automatically. Set `host` only when Sagepilot provides a dedicated endpoint for your workspace.
42
42
 
43
43
  ```ts
44
44
  await SagepilotChat.configure({
@@ -46,7 +46,7 @@ await SagepilotChat.configure({
46
46
  });
47
47
  ```
48
48
 
49
- If your workspace uses a dedicated host:
49
+ If Sagepilot provides a dedicated endpoint for your workspace, pass it as an optional override:
50
50
 
51
51
  ```ts
52
52
  await SagepilotChat.configure({
@@ -64,8 +64,8 @@ These options are part of the supported React Native SDK configuration contract.
64
64
  | Option | Purpose |
65
65
  |---|---|
66
66
  | `key` | Public routing key in the format `workspace_id:channel_id`. |
67
- | `host` | Sagepilot app/API host override. Most apps should omit this. |
68
- | `widgetHost` | Optional hosted widget origin override for development or dedicated widget routing. Most apps should omit this. |
67
+ | `host` | Optional Sagepilot host override. Most apps should omit this unless Sagepilot provides a dedicated endpoint. |
68
+ | `widgetHost` | Optional hosted widget origin override. Use only when Sagepilot provides a separate widget endpoint. |
69
69
  | `headers` | Additional headers for SDK service requests. Do not pass server API keys or long-lived secrets from a mobile app. |
70
70
  | `fetch` | Custom fetch implementation for runtimes that need one. |
71
71
  | `tokenStorage` | Secure storage adapter for SDK-created customer session tokens. |
@@ -83,7 +83,7 @@ These options are part of the supported React Native SDK configuration contract.
83
83
  | `behavior.enableUnreadPolling` | Starts or disables automatic unread-count polling after configuration. |
84
84
  | `behavior.unreadPollIntervalMs` | Sets the unread polling interval in milliseconds. |
85
85
 
86
- `SagepilotChatProvider` renders the Sagepilot-hosted chat UI in a React Native WebView. Most Sagepilot cloud apps do not need any host override.
86
+ `SagepilotChatProvider` renders the Sagepilot-hosted chat UI in a React Native WebView. Most apps do not need any host override.
87
87
 
88
88
  ### Chat Launcher Theme
89
89
 
@@ -312,8 +312,13 @@ Other open helpers:
312
312
  SagepilotChat.present();
313
313
  SagepilotChat.presentMessages();
314
314
  SagepilotChat.presentMessageComposer("I need help with my order");
315
+ SagepilotChat.presentMessageComposer("Start a new request", { mode: "new" });
315
316
  ```
316
317
 
318
+ `presentMessageComposer(message)` pre-fills the composer and lets Sagepilot reuse an existing conversation when one is available. If there is no existing conversation, Sagepilot starts a new one when the customer sends the message.
319
+
320
+ Use `{ mode: "new" }` only when the button or workflow should always start a fresh conversation.
321
+
317
322
  ## Identity
318
323
 
319
324
  Call `identify()` when you know the signed-in app user.
@@ -375,7 +380,7 @@ await SagepilotChat.destroy();
375
380
 
376
381
  - `SagepilotChat.present()`: opens the hosted chat home screen.
377
382
  - `SagepilotChat.presentMessages()`: opens the hosted conversations/messages screen.
378
- - `SagepilotChat.presentMessageComposer(message?)`: opens a fresh message composer and optionally pre-fills the composer text. It does not auto-send.
383
+ - `SagepilotChat.presentMessageComposer(message?, options?)`: opens the message composer and optionally pre-fills the composer text. By default, Sagepilot reuses an existing conversation when one is available. Pass `{ mode: "new" }` to force a fresh conversation. It does not auto-send.
379
384
  - `SagepilotChat.dismiss()`: closes the hosted chat modal.
380
385
  - `SagepilotChat.hide()`: alias for `dismiss()`.
381
386
  - `SagepilotChat.toggle()`: opens the chat when closed and closes it when open.
@@ -0,0 +1,53 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+ }
7
+
8
+ plugins {
9
+ id "com.android.library"
10
+ }
11
+
12
+ def isNewArchitectureEnabled() {
13
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
14
+ }
15
+
16
+ if (isNewArchitectureEnabled()) {
17
+ apply plugin: "com.facebook.react"
18
+ }
19
+
20
+ def safeExtGet(prop, fallback) {
21
+ return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
22
+ }
23
+
24
+ android {
25
+ namespace "ai.sagepilot.reactnativesdk"
26
+ compileSdkVersion safeExtGet("compileSdkVersion", 35)
27
+
28
+ defaultConfig {
29
+ minSdkVersion safeExtGet("minSdkVersion", 24)
30
+ targetSdkVersion safeExtGet("targetSdkVersion", 35)
31
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
32
+ }
33
+
34
+ compileOptions {
35
+ sourceCompatibility JavaVersion.VERSION_17
36
+ targetCompatibility JavaVersion.VERSION_17
37
+ }
38
+
39
+ sourceSets.main {
40
+ java {
41
+ if (isNewArchitectureEnabled()) {
42
+ srcDirs += "${project.buildDir}/generated/source/codegen/java"
43
+ } else {
44
+ srcDirs += "src/paper/java"
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ dependencies {
51
+ implementation "com.facebook.react:react-android"
52
+ implementation "androidx.core:core:1.13.1"
53
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,26 @@
1
+ package ai.sagepilot.reactnativesdk;
2
+
3
+ import androidx.annotation.Nullable;
4
+ import com.facebook.react.bridge.WritableMap;
5
+ import com.facebook.react.uimanager.events.Event;
6
+
7
+ public class SagepilotInsetsChangeEvent extends Event<SagepilotInsetsChangeEvent> {
8
+ public static final String EVENT_NAME = "topInsetsChange";
9
+ private final WritableMap eventData;
10
+
11
+ public SagepilotInsetsChangeEvent(int surfaceId, int viewTag, WritableMap eventData) {
12
+ super(surfaceId, viewTag);
13
+ this.eventData = eventData;
14
+ }
15
+
16
+ @Override
17
+ public String getEventName() {
18
+ return EVENT_NAME;
19
+ }
20
+
21
+ @Nullable
22
+ @Override
23
+ protected WritableMap getEventData() {
24
+ return eventData;
25
+ }
26
+ }
@@ -0,0 +1,78 @@
1
+ package ai.sagepilot.reactnativesdk;
2
+
3
+ import android.content.Context;
4
+ import android.view.View;
5
+ import android.widget.FrameLayout;
6
+ import com.facebook.react.bridge.Arguments;
7
+ import com.facebook.react.bridge.ReactContext;
8
+ import com.facebook.react.bridge.WritableMap;
9
+ import com.facebook.react.uimanager.PixelUtil;
10
+ import com.facebook.react.uimanager.UIManagerHelper;
11
+ import com.facebook.react.uimanager.events.EventDispatcher;
12
+ import androidx.core.graphics.Insets;
13
+ import androidx.core.view.ViewCompat;
14
+ import androidx.core.view.WindowInsetsCompat;
15
+
16
+ public class SagepilotInsetsView extends FrameLayout {
17
+ private int appliedTopInset;
18
+ private int appliedBottomInset;
19
+
20
+ public SagepilotInsetsView(Context context) {
21
+ super(context);
22
+ installInsetsListener();
23
+ }
24
+
25
+ @Override
26
+ protected void onAttachedToWindow() {
27
+ super.onAttachedToWindow();
28
+ ViewCompat.requestApplyInsets(this);
29
+ }
30
+
31
+ private void installInsetsListener() {
32
+ ViewCompat.setOnApplyWindowInsetsListener(this, (view, windowInsets) -> {
33
+ Insets imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
34
+ Insets systemBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
35
+ int topInset = systemBarInsets.top;
36
+ int bottomInset = Math.max(imeInsets.bottom, systemBarInsets.bottom);
37
+ updateInsets(topInset, bottomInset, imeInsets.bottom, systemBarInsets.bottom);
38
+ return WindowInsetsCompat.CONSUMED;
39
+ });
40
+ }
41
+
42
+ private void updateInsets(int topInset, int bottomInset, int imeBottom, int systemBarsBottom) {
43
+ int nextTopInset = Math.max(0, topInset);
44
+ int nextBottomInset = Math.max(0, bottomInset);
45
+ if (nextTopInset == appliedTopInset && nextBottomInset == appliedBottomInset) {
46
+ return;
47
+ }
48
+
49
+ appliedTopInset = nextTopInset;
50
+ appliedBottomInset = nextBottomInset;
51
+ emitInsetsChange(nextTopInset, nextBottomInset, Math.max(0, imeBottom), Math.max(0, systemBarsBottom));
52
+ }
53
+
54
+ private void emitInsetsChange(int topInset, int bottomInset, int imeBottom, int systemBarsBottom) {
55
+ if (getId() == View.NO_ID || !(getContext() instanceof ReactContext)) {
56
+ return;
57
+ }
58
+
59
+ WritableMap event = Arguments.createMap();
60
+ event.putDouble("top", PixelUtil.toDIPFromPixel(topInset));
61
+ event.putDouble("bottom", PixelUtil.toDIPFromPixel(bottomInset));
62
+ event.putDouble("imeBottom", PixelUtil.toDIPFromPixel(imeBottom));
63
+ event.putDouble("systemBarsBottom", PixelUtil.toDIPFromPixel(systemBarsBottom));
64
+ event.putBoolean("keyboardVisible", imeBottom > systemBarsBottom);
65
+
66
+ ReactContext reactContext = (ReactContext) getContext();
67
+ EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
68
+ if (eventDispatcher == null) {
69
+ return;
70
+ }
71
+
72
+ eventDispatcher.dispatchEvent(new SagepilotInsetsChangeEvent(
73
+ UIManagerHelper.getSurfaceId(this),
74
+ getId(),
75
+ event
76
+ ));
77
+ }
78
+ }
@@ -0,0 +1,59 @@
1
+ package ai.sagepilot.reactnativesdk;
2
+
3
+ import android.view.View;
4
+ import com.facebook.react.common.MapBuilder;
5
+ import com.facebook.react.uimanager.ViewManagerDelegate;
6
+ import com.facebook.react.uimanager.ThemedReactContext;
7
+ import com.facebook.react.uimanager.ViewGroupManager;
8
+ import com.facebook.react.viewmanagers.SagepilotInsetsViewManagerDelegate;
9
+ import com.facebook.react.viewmanagers.SagepilotInsetsViewManagerInterface;
10
+ import java.util.Map;
11
+
12
+ public class SagepilotInsetsViewManager extends ViewGroupManager<SagepilotInsetsView>
13
+ implements SagepilotInsetsViewManagerInterface<SagepilotInsetsView> {
14
+ public static final String REACT_CLASS = "SagepilotInsetsView";
15
+ private final ViewManagerDelegate<SagepilotInsetsView> delegate = new SagepilotInsetsViewManagerDelegate<>(this);
16
+
17
+ @Override
18
+ protected ViewManagerDelegate<SagepilotInsetsView> getDelegate() {
19
+ return delegate;
20
+ }
21
+
22
+ @Override
23
+ public String getName() {
24
+ return REACT_CLASS;
25
+ }
26
+
27
+ @Override
28
+ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
29
+ return MapBuilder.of(
30
+ SagepilotInsetsChangeEvent.EVENT_NAME,
31
+ MapBuilder.of("registrationName", "onInsetsChange")
32
+ );
33
+ }
34
+
35
+ @Override
36
+ protected SagepilotInsetsView createViewInstance(ThemedReactContext reactContext) {
37
+ return new SagepilotInsetsView(reactContext);
38
+ }
39
+
40
+ @Override
41
+ public void addView(SagepilotInsetsView parent, View child, int index) {
42
+ parent.addView(child, index);
43
+ }
44
+
45
+ @Override
46
+ public int getChildCount(SagepilotInsetsView parent) {
47
+ return parent.getChildCount();
48
+ }
49
+
50
+ @Override
51
+ public View getChildAt(SagepilotInsetsView parent, int index) {
52
+ return parent.getChildAt(index);
53
+ }
54
+
55
+ @Override
56
+ public void removeViewAt(SagepilotInsetsView parent, int index) {
57
+ parent.removeViewAt(index);
58
+ }
59
+ }
@@ -0,0 +1,20 @@
1
+ package ai.sagepilot.reactnativesdk;
2
+
3
+ import com.facebook.react.ReactPackage;
4
+ import com.facebook.react.bridge.NativeModule;
5
+ import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.facebook.react.uimanager.ViewManager;
7
+ import java.util.Collections;
8
+ import java.util.List;
9
+
10
+ public class SagepilotReactNativeSdkPackage implements ReactPackage {
11
+ @Override
12
+ public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
13
+ return Collections.emptyList();
14
+ }
15
+
16
+ @Override
17
+ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
18
+ return Collections.singletonList(new SagepilotInsetsViewManager());
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This code mirrors react-native-codegen output so Paper builds can compile when codegen is off.
3
+ */
4
+ package com.facebook.react.viewmanagers;
5
+
6
+ import android.view.View;
7
+ import androidx.annotation.Nullable;
8
+ import com.facebook.react.uimanager.BaseViewManager;
9
+ import com.facebook.react.uimanager.BaseViewManagerDelegate;
10
+ import com.facebook.react.uimanager.LayoutShadowNode;
11
+
12
+ public class SagepilotInsetsViewManagerDelegate<
13
+ T extends View,
14
+ U extends
15
+ BaseViewManager<T, ? extends LayoutShadowNode> & SagepilotInsetsViewManagerInterface<T>>
16
+ extends BaseViewManagerDelegate<T, U> {
17
+ public SagepilotInsetsViewManagerDelegate(U viewManager) {
18
+ super(viewManager);
19
+ }
20
+
21
+ @Override
22
+ public void setProperty(T view, String propName, @Nullable Object value) {
23
+ super.setProperty(view, propName, value);
24
+ }
25
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This code mirrors react-native-codegen output so Paper builds can compile when codegen is off.
3
+ */
4
+ package com.facebook.react.viewmanagers;
5
+
6
+ import android.view.View;
7
+
8
+ public interface SagepilotInsetsViewManagerInterface<T extends View> {
9
+ // No props
10
+ }
@@ -216,6 +216,15 @@ type SagepilotMobileState = SagepilotLifecycleState & {
216
216
  theme: SagepilotMobileThemeState;
217
217
  };
218
218
  type SagepilotMobileSubscription = () => void;
219
+ type SagepilotMessageComposerMode = "auto" | "new";
220
+ type SagepilotMessageComposerOptions = {
221
+ /**
222
+ * auto: prefill the composer and let the hosted widget reuse an existing
223
+ * conversation when one is available.
224
+ * new: force the hosted widget to start a fresh conversation.
225
+ */
226
+ mode?: SagepilotMessageComposerMode;
227
+ };
219
228
  type SagepilotChatHookState = {
220
229
  configured: boolean;
221
230
  isPresented: boolean;
@@ -224,7 +233,7 @@ type SagepilotChatHookState = {
224
233
  theme: SagepilotMobileThemeState;
225
234
  present: () => boolean;
226
235
  presentMessages: () => boolean;
227
- presentMessageComposer: (message?: string) => boolean;
236
+ presentMessageComposer: (message?: string, options?: SagepilotMessageComposerOptions) => boolean;
228
237
  dismiss: () => boolean;
229
238
  hide: () => boolean;
230
239
  toggle: () => boolean;
@@ -256,7 +265,7 @@ declare const SagepilotChat: {
256
265
  stopUnreadPolling: () => void;
257
266
  present: () => boolean;
258
267
  presentMessages: () => boolean;
259
- presentMessageComposer: (message?: string) => boolean;
268
+ presentMessageComposer: (message?: string, options?: SagepilotMessageComposerOptions) => boolean;
260
269
  dismiss: () => boolean;
261
270
  hide: () => boolean;
262
271
  toggle: () => boolean;
package/dist/index.d.ts CHANGED
@@ -216,6 +216,15 @@ type SagepilotMobileState = SagepilotLifecycleState & {
216
216
  theme: SagepilotMobileThemeState;
217
217
  };
218
218
  type SagepilotMobileSubscription = () => void;
219
+ type SagepilotMessageComposerMode = "auto" | "new";
220
+ type SagepilotMessageComposerOptions = {
221
+ /**
222
+ * auto: prefill the composer and let the hosted widget reuse an existing
223
+ * conversation when one is available.
224
+ * new: force the hosted widget to start a fresh conversation.
225
+ */
226
+ mode?: SagepilotMessageComposerMode;
227
+ };
219
228
  type SagepilotChatHookState = {
220
229
  configured: boolean;
221
230
  isPresented: boolean;
@@ -224,7 +233,7 @@ type SagepilotChatHookState = {
224
233
  theme: SagepilotMobileThemeState;
225
234
  present: () => boolean;
226
235
  presentMessages: () => boolean;
227
- presentMessageComposer: (message?: string) => boolean;
236
+ presentMessageComposer: (message?: string, options?: SagepilotMessageComposerOptions) => boolean;
228
237
  dismiss: () => boolean;
229
238
  hide: () => boolean;
230
239
  toggle: () => boolean;
@@ -256,7 +265,7 @@ declare const SagepilotChat: {
256
265
  stopUnreadPolling: () => void;
257
266
  present: () => boolean;
258
267
  presentMessages: () => boolean;
259
- presentMessageComposer: (message?: string) => boolean;
268
+ presentMessageComposer: (message?: string, options?: SagepilotMessageComposerOptions) => boolean;
260
269
  dismiss: () => boolean;
261
270
  hide: () => boolean;
262
271
  toggle: () => boolean;