@akhilpulse/samadhaan-session-replay 0.4.0 → 0.4.2

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
@@ -74,12 +74,24 @@ const { pause, resume, sessionId, status } = useSessionReplay();
74
74
 
75
75
  ### Shake-to-Assist (AI voice guide)
76
76
 
77
- **New in 0.4.0.** The user shakes their phone, a voice assistant connects, they ask
78
- a question out loud ("how do I pay my bill?"), the AI looks at their current screen
79
- and draws an **on-screen arrow** pointing at the next element to tap — step by step.
77
+ **New in 0.4.0.** The user opens the voice assistant, asks a question out loud
78
+ ("how do I pay my bill?"), the AI looks at their current screen and draws an
79
+ **on-screen arrow** pointing at the next element to tap — step by step.
80
80
  Works in **English and Hindi**. Every assistant session (questions + AI steps +
81
81
  screenshots) is recorded and visible in the dashboard.
82
82
 
83
+ **New in 0.4.2 — floating help button (default trigger).** A small draggable
84
+ `?` button floats at the top-right of the screen. Tapping it starts the voice
85
+ guide; the user can drag it anywhere and it stays inside the screen. This is
86
+ the definitive trigger for **Expo** apps, where the native shake module isn't
87
+ available. Shake (if `react-native-shake` is installed) and `triggerAssist()`
88
+ still work as alternate triggers.
89
+
90
+ ```tsx
91
+ // The button shows by default. To hide it:
92
+ <SessionReplayProvider config={{ appId: "app_xxx", showAssistButton: false }}>
93
+ ```
94
+
83
95
  ```tsx
84
96
  <SessionReplayProvider
85
97
  config={{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akhilpulse/samadhaan-session-replay",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "React Native SDK for Samadhaan Guided Support — session recording, live remote screen-share, take-control, guided-tour overlays, and Shake-to-Assist AI voice guide.",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -53,7 +53,7 @@ const SessionReplayPixelCopy: PixelCopyNative | undefined =
53
53
  let pixelCopyDisabled = false; // turned true after a runtime failure to avoid retrying every frame
54
54
 
55
55
  const STORAGE_KEY = "@akhilpulse/samadhaan-session-replay/sessionId";
56
- const SDK_VERSION = "0.4.0";
56
+ const SDK_VERSION = "0.4.2";
57
57
 
58
58
  // ---- Module-level session singleton --------------------------------------
59
59
  // Multiple SessionReplayProvider mounts (intentional or via React StrictMode's
@@ -93,6 +93,12 @@ type Config = {
93
93
  assistantLang?: "en" | "hi";
94
94
  /** Disable the shake-to-assist voice guide entirely. */
95
95
  disableAssistant?: boolean;
96
+ /**
97
+ * Show a small draggable floating help button (top-right) that opens the
98
+ * voice guide on tap. This is the definitive trigger for Expo apps where
99
+ * the native shake module isn't available. Default: true.
100
+ */
101
+ showAssistButton?: boolean;
96
102
  /**
97
103
  * Optional streaming-PCM player for the agent's voice. Without it the
98
104
  * assistant still listens + shows the on-screen arrow, but won't speak aloud.
@@ -671,6 +677,43 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
671
677
  [endTourPan]
672
678
  );
673
679
 
680
+ // ---- Draggable floating Assist help button (definitive trigger for Expo) ----
681
+ const assistFabPan = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current;
682
+ const assistFabPanResponder = useMemo(
683
+ () =>
684
+ PanResponder.create({
685
+ // Don't claim taps — let the inner Pressable receive onPress.
686
+ onStartShouldSetPanResponder: () => false,
687
+ // Only claim once the user clearly starts dragging (>4px).
688
+ onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 4 || Math.abs(g.dy) > 4,
689
+ onPanResponderGrant: () => {
690
+ assistFabPan.extractOffset();
691
+ },
692
+ onPanResponderMove: Animated.event(
693
+ [null, { dx: assistFabPan.x, dy: assistFabPan.y }],
694
+ { useNativeDriver: false }
695
+ ),
696
+ onPanResponderRelease: () => {
697
+ assistFabPan.flattenOffset();
698
+ // Clamp inside the visible root-view bounds so the button never escapes off-screen.
699
+ const { w, h } = viewSizeRef.current;
700
+ const FAB = 44, ANCHOR_TOP = 96, ANCHOR_RIGHT = 12, MARGIN = 8;
701
+ const minX = -(Math.max(0, w - ANCHOR_RIGHT - FAB - MARGIN));
702
+ const maxX = 0;
703
+ const minY = -(ANCHOR_TOP - MARGIN);
704
+ const maxY = Math.max(0, h - ANCHOR_TOP - FAB - MARGIN);
705
+ const cx = (assistFabPan.x as any)._value;
706
+ const cy = (assistFabPan.y as any)._value;
707
+ const nx = Math.min(maxX, Math.max(minX, cx));
708
+ const ny = Math.min(maxY, Math.max(minY, cy));
709
+ if (nx !== cx || ny !== cy) {
710
+ Animated.spring(assistFabPan, { toValue: { x: nx, y: ny }, useNativeDriver: false, friction: 7 }).start();
711
+ }
712
+ },
713
+ }),
714
+ [assistFabPan]
715
+ );
716
+
674
717
  function consentTour(accept: boolean) {
675
718
  if (accept) {
676
719
  setTourState((s) => ({ phase: "active", agentName: s.agentName }));
@@ -811,6 +854,26 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
811
854
  );
812
855
  })()}
813
856
 
857
+ {/* Floating help button — draggable, tap to open the AI voice guide.
858
+ This is the definitive trigger for Expo apps (no native shake module).
859
+ Hidden during guided tours so it never collides with the End-tour pill. */}
860
+ {!config.disableAssistant && config.showAssistButton !== false && !assistantActive && tourState.phase === "idle" && (
861
+ <Animated.View
862
+ style={[styles.assistFab, { transform: assistFabPan.getTranslateTransform() }]}
863
+ {...assistFabPanResponder.panHandlers}
864
+ >
865
+ <Pressable
866
+ onPress={() => startAssist()}
867
+ style={({ pressed }) => [styles.assistFabPressable, pressed && styles.btnPressed]}
868
+ hitSlop={8}
869
+ accessibilityRole="button"
870
+ accessibilityLabel="Open voice help"
871
+ >
872
+ <Text style={styles.assistFabIcon}>?</Text>
873
+ </Pressable>
874
+ </Animated.View>
875
+ )}
876
+
814
877
  {/* Shake-to-Assist: bottom voice "wave" control + status. */}
815
878
  {assistantActive && (
816
879
  <View style={styles.assistDock} pointerEvents="box-none">
@@ -1029,6 +1092,19 @@ const styles = StyleSheet.create({
1029
1092
  paddingVertical: 8, paddingHorizontal: 16, minHeight: 36, justifyContent: "center",
1030
1093
  },
1031
1094
  assistCloseText: { color: "#fff", fontSize: 14, fontWeight: "700" },
1095
+ assistFab: {
1096
+ position: "absolute", top: 96, right: 12,
1097
+ width: 44, height: 44, borderRadius: 22,
1098
+ shadowColor: "#000", shadowOpacity: 0.3, shadowRadius: 8, shadowOffset: { width: 0, height: 4 },
1099
+ elevation: 6,
1100
+ },
1101
+ assistFabPressable: {
1102
+ width: 44, height: 44, borderRadius: 22,
1103
+ backgroundColor: "#3b82f6",
1104
+ alignItems: "center", justifyContent: "center",
1105
+ borderWidth: 2, borderColor: "rgba(255,255,255,0.85)",
1106
+ },
1107
+ assistFabIcon: { color: "#fff", fontSize: 20, fontWeight: "800", lineHeight: 24 },
1032
1108
  waveRow: { flexDirection: "row", alignItems: "center", height: 28, width: 44 },
1033
1109
  waveBar: {
1034
1110
  width: 4, height: 22, marginHorizontal: 2, borderRadius: 2, backgroundColor: "#60a5fa",