@adminide-stack/yantra-mobile 12.0.53-alpha.1 → 12.0.53-alpha.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.
Files changed (35) hide show
  1. package/lib/components/KeyboardComposerDock.js +41 -5
  2. package/lib/components/KeyboardComposerDock.js.map +1 -1
  3. package/lib/features/attachments/ComposerAttachMenu.js +189 -0
  4. package/lib/features/attachments/ComposerAttachMenu.js.map +1 -0
  5. package/lib/features/attachments/useImageAttachments.js +23 -15
  6. package/lib/features/attachments/useImageAttachments.js.map +1 -1
  7. package/lib/features/canvas/canvasBoardsSnapshot.js +37 -0
  8. package/lib/features/canvas/canvasBoardsSnapshot.js.map +1 -0
  9. package/lib/features/canvas/nativeViewerRegistry.js +9 -1
  10. package/lib/features/canvas/nativeViewerRegistry.js.map +1 -1
  11. package/lib/features/chat/ChatTranscript.js +7 -0
  12. package/lib/features/chat/ChatTranscript.js.map +1 -1
  13. package/lib/features/chat/chatHistorySnapshot.js +103 -0
  14. package/lib/features/chat/chatHistorySnapshot.js.map +1 -0
  15. package/lib/graphql/typePolicies.js +139 -0
  16. package/lib/graphql/typePolicies.js.map +1 -0
  17. package/lib/hooks/useChatApi.js +202 -25
  18. package/lib/hooks/useChatApi.js.map +1 -1
  19. package/lib/hooks/useChatStream.js +56 -30
  20. package/lib/hooks/useChatStream.js.map +1 -1
  21. package/lib/index.js +1 -1
  22. package/lib/index.js.map +1 -1
  23. package/lib/module.js +5 -2
  24. package/lib/module.js.map +1 -1
  25. package/lib/screens/Chat/index.js +7 -35
  26. package/lib/screens/Chat/index.js.map +1 -1
  27. package/lib/screens/Home/HomeScreen.js +18 -31
  28. package/lib/screens/Home/HomeScreen.js.map +1 -1
  29. package/lib/screens/Home/components/CanvasPrewarm.js +3 -2
  30. package/lib/screens/Home/components/CanvasPrewarm.js.map +1 -1
  31. package/lib/screens/Home/components/ChatHistoryLanding.js +46 -8
  32. package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -1
  33. package/lib/state/chatThreadMerge.js +76 -2
  34. package/lib/state/chatThreadMerge.js.map +1 -1
  35. package/package.json +3 -3
@@ -38,22 +38,58 @@ function configureKeyboardLayout(duration) {
38
38
  }
39
39
  });
40
40
  }
41
+ function listenKeyboardController(onHeight) {
42
+ if (!isKeyboardControllerAvailable()) return null;
43
+ try {
44
+ const {
45
+ KeyboardEvents
46
+ } = require("react-native-keyboard-controller");
47
+ if (!(KeyboardEvents == null ? void 0 : KeyboardEvents.addListener)) return null;
48
+ const show = KeyboardEvents.addListener("keyboardWillShow", (e) => {
49
+ var _a;
50
+ onHeight((_a = e == null ? void 0 : e.height) != null ? _a : 0, e == null ? void 0 : e.duration);
51
+ });
52
+ const shown = KeyboardEvents.addListener("keyboardDidShow", (e) => {
53
+ var _a;
54
+ onHeight((_a = e == null ? void 0 : e.height) != null ? _a : 0, e == null ? void 0 : e.duration);
55
+ });
56
+ const hide = KeyboardEvents.addListener("keyboardWillHide", (e) => {
57
+ onHeight(0, e == null ? void 0 : e.duration);
58
+ });
59
+ const hidden = KeyboardEvents.addListener("keyboardDidHide", (e) => {
60
+ onHeight(0, e == null ? void 0 : e.duration);
61
+ });
62
+ return {
63
+ remove: () => {
64
+ show.remove();
65
+ shown.remove();
66
+ hide.remove();
67
+ hidden.remove();
68
+ }
69
+ };
70
+ } catch (e) {
71
+ return null;
72
+ }
73
+ }
41
74
  function useKeyboardLiftHeight() {
42
75
  const [height, setHeight] = useState(0);
43
76
  useEffect(() => {
77
+ const apply = (next, duration) => {
78
+ configureKeyboardLayout(duration);
79
+ setHeight(next);
80
+ };
44
81
  const show = Keyboard.addListener(Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow", (e) => {
45
82
  var _a, _b;
46
- const next = (_b = (_a = e.endCoordinates) == null ? void 0 : _a.height) != null ? _b : 0;
47
- configureKeyboardLayout(e.duration);
48
- setHeight(next);
83
+ apply((_b = (_a = e.endCoordinates) == null ? void 0 : _a.height) != null ? _b : 0, e.duration);
49
84
  });
50
85
  const hide = Keyboard.addListener(Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide", (e) => {
51
- configureKeyboardLayout(e.duration);
52
- setHeight(0);
86
+ apply(0, e.duration);
53
87
  });
88
+ const controller = listenKeyboardController(apply);
54
89
  return () => {
55
90
  show.remove();
56
91
  hide.remove();
92
+ controller == null ? void 0 : controller.remove();
57
93
  };
58
94
  }, []);
59
95
  return Platform.OS === "android" ? 0 : height;
@@ -1 +1 @@
1
- {"version":3,"file":"KeyboardComposerDock.js","sources":["../../src/components/KeyboardComposerDock.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\nimport {\n Keyboard,\n LayoutAnimation,\n PanResponder,\n Platform,\n StyleSheet,\n View,\n type StyleProp,\n type ViewStyle,\n} from 'react-native';\nimport { dismissKeyboard, isKeyboardControllerAvailable } from '../utils/keyboardController';\n\nexport const COMPOSER_INPUT_NATIVE_ID = 'yantra-composer';\n\nexport type KeyboardComposerDockProps = {\n children: React.ReactNode;\n /** Home-indicator inset when the keyboard is closed. */\n closedInset: number;\n keyboardVisible?: boolean;\n /** Expo Go: swipe up on the composer focuses this field (opens the keyboard). */\n onSwipeUp?: () => void;\n};\n\n/**\n * Native swipe region when keyboard-controller is linked: swipe up opens the\n * keyboard, swipe down closes it and the keys follow the finger (iOS interpolator).\n */\nexport function KeyboardAwareScreen({\n children,\n style,\n testID,\n}: {\n children: React.ReactNode;\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}) {\n if (isKeyboardControllerAvailable()) {\n const KeyboardGestureArea = require('react-native-keyboard-controller')\n .KeyboardGestureArea as React.ComponentType<{\n style?: StyleProp<ViewStyle>;\n interpolator?: 'ios' | 'linear';\n showOnSwipeUp?: boolean;\n enableSwipeToDismiss?: boolean;\n textInputNativeID?: string;\n testID?: string;\n children?: React.ReactNode;\n }>;\n return (\n <KeyboardGestureArea\n style={[styles.flex, style]}\n interpolator=\"ios\"\n showOnSwipeUp\n enableSwipeToDismiss\n textInputNativeID={COMPOSER_INPUT_NATIVE_ID}\n testID={testID}\n >\n {children}\n </KeyboardGestureArea>\n );\n }\n return (\n <View style={[styles.flex, style]} testID={testID}>\n {children}\n </View>\n );\n}\n\n/** In-flow iOS spacer so a flex sibling (stage/canvas) shrinks above the keys. */\nexport function KeyboardLiftSpacer() {\n const height = useKeyboardLiftHeight();\n if (Platform.OS !== 'ios' || height <= 0) return null;\n return <View style={{ height }} pointerEvents=\"none\" />;\n}\n\nfunction configureKeyboardLayout(duration?: number) {\n if (Platform.OS !== 'ios') return;\n LayoutAnimation.configureNext({\n duration: duration && duration > 0 ? duration : 250,\n update: {\n type: LayoutAnimation.Types.keyboard,\n },\n });\n}\n\n/** Keyboard height used by the dock spacer. Chat reads this so it scrolls after the list shrinks. */\nexport function useKeyboardLiftHeight(): number {\n const [height, setHeight] = useState(0);\n\n useEffect(() => {\n const show = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', (e) => {\n const next = e.endCoordinates?.height ?? 0;\n configureKeyboardLayout(e.duration);\n setHeight(next);\n });\n const hide = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', (e) => {\n configureKeyboardLayout(e.duration);\n setHeight(0);\n });\n return () => {\n show.remove();\n hide.remove();\n };\n }, []);\n\n return Platform.OS === 'android' ? 0 : height;\n}\n\n/**\n * In-flow spacer = full keyboard height so the composer docks on the keys.\n * Native swipe lives on KeyboardAwareScreen; Expo Go uses a composer pan.\n */\nexport function KeyboardComposerDock({ children, closedInset, onSwipeUp }: KeyboardComposerDockProps) {\n const keyboardHeight = useKeyboardLiftHeight();\n const closedPadding = Math.max(closedInset, 0);\n const useNativeSwipe = isKeyboardControllerAvailable();\n const onSwipeUpRef = useRef(onSwipeUp);\n onSwipeUpRef.current = onSwipeUp;\n const fallbackGestures = useRef(\n PanResponder.create({\n onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dy) > 12 && Math.abs(g.dy) > Math.abs(g.dx) * 1.2,\n onPanResponderRelease: (_, g) => {\n if (g.dy < -28 || g.vy < -0.55) onSwipeUpRef.current?.();\n else if (g.dy > 28 || g.vy > 0.55) dismissKeyboard();\n },\n }),\n ).current;\n\n return (\n <>\n <View\n style={[styles.dock, { paddingBottom: Math.max(0, closedPadding - keyboardHeight) }]}\n {...(useNativeSwipe ? null : fallbackGestures.panHandlers)}\n >\n {children}\n </View>\n {Platform.OS === 'ios' ? <View style={{ height: keyboardHeight }} pointerEvents=\"none\" /> : null}\n </>\n );\n}\n\nconst styles = StyleSheet.create({\n flex: {\n flex: 1,\n },\n dock: {\n width: '100%',\n alignItems: 'center',\n zIndex: 20,\n elevation: 20,\n },\n});\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"KeyboardComposerDock.js","sources":["../../src/components/KeyboardComposerDock.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\nimport {\n Keyboard,\n LayoutAnimation,\n PanResponder,\n Platform,\n StyleSheet,\n View,\n type StyleProp,\n type ViewStyle,\n} from 'react-native';\nimport { dismissKeyboard, isKeyboardControllerAvailable } from '../utils/keyboardController';\n\nexport const COMPOSER_INPUT_NATIVE_ID = 'yantra-composer';\n\nexport type KeyboardComposerDockProps = {\n children: React.ReactNode;\n /** Home-indicator inset when the keyboard is closed. */\n closedInset: number;\n keyboardVisible?: boolean;\n /** Expo Go: swipe up on the composer focuses this field (opens the keyboard). */\n onSwipeUp?: () => void;\n};\n\n/**\n * Native swipe region when keyboard-controller is linked: swipe up opens the\n * keyboard, swipe down closes it and the keys follow the finger (iOS interpolator).\n */\nexport function KeyboardAwareScreen({\n children,\n style,\n testID,\n}: {\n children: React.ReactNode;\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}) {\n if (isKeyboardControllerAvailable()) {\n const KeyboardGestureArea = require('react-native-keyboard-controller')\n .KeyboardGestureArea as React.ComponentType<{\n style?: StyleProp<ViewStyle>;\n interpolator?: 'ios' | 'linear';\n showOnSwipeUp?: boolean;\n enableSwipeToDismiss?: boolean;\n textInputNativeID?: string;\n testID?: string;\n children?: React.ReactNode;\n }>;\n return (\n <KeyboardGestureArea\n style={[styles.flex, style]}\n interpolator=\"ios\"\n showOnSwipeUp\n enableSwipeToDismiss\n textInputNativeID={COMPOSER_INPUT_NATIVE_ID}\n testID={testID}\n >\n {children}\n </KeyboardGestureArea>\n );\n }\n return (\n <View style={[styles.flex, style]} testID={testID}>\n {children}\n </View>\n );\n}\n\n/** In-flow iOS spacer so a flex sibling (stage/canvas) shrinks above the keys. */\nexport function KeyboardLiftSpacer() {\n const height = useKeyboardLiftHeight();\n if (Platform.OS !== 'ios' || height <= 0) return null;\n return <View style={{ height }} pointerEvents=\"none\" />;\n}\n\nfunction configureKeyboardLayout(duration?: number) {\n if (Platform.OS !== 'ios') return;\n LayoutAnimation.configureNext({\n duration: duration && duration > 0 ? duration : 250,\n update: {\n type: LayoutAnimation.Types.keyboard,\n },\n });\n}\n\ntype KeyboardHeightListener = { remove: () => void };\n\nfunction listenKeyboardController(\n onHeight: (height: number, duration?: number) => void,\n): KeyboardHeightListener | null {\n if (!isKeyboardControllerAvailable()) return null;\n try {\n const { KeyboardEvents } = require('react-native-keyboard-controller') as {\n KeyboardEvents?: {\n addListener: (\n event: string,\n cb: (e: { height?: number; duration?: number }) => void,\n ) => {\n remove: () => void;\n };\n };\n };\n if (!KeyboardEvents?.addListener) return null;\n const show = KeyboardEvents.addListener('keyboardWillShow', (e) => {\n onHeight(e?.height ?? 0, e?.duration);\n });\n const shown = KeyboardEvents.addListener('keyboardDidShow', (e) => {\n onHeight(e?.height ?? 0, e?.duration);\n });\n const hide = KeyboardEvents.addListener('keyboardWillHide', (e) => {\n onHeight(0, e?.duration);\n });\n const hidden = KeyboardEvents.addListener('keyboardDidHide', (e) => {\n onHeight(0, e?.duration);\n });\n return {\n remove: () => {\n show.remove();\n shown.remove();\n hide.remove();\n hidden.remove();\n },\n };\n } catch {\n return null;\n }\n}\n\n/** Keyboard height used by the dock spacer. Chat reads this so it scrolls after the list shrinks. */\nexport function useKeyboardLiftHeight(): number {\n const [height, setHeight] = useState(0);\n\n useEffect(() => {\n const apply = (next: number, duration?: number) => {\n configureKeyboardLayout(duration);\n setHeight(next);\n };\n const show = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', (e) => {\n apply(e.endCoordinates?.height ?? 0, e.duration);\n });\n const hide = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', (e) => {\n apply(0, e.duration);\n });\n // KeyboardProvider swallows RN Keyboard events on staging binaries, so\n // Create image / video inputs sat under the keys. Mirror height from\n // keyboard-controller when it is linked.\n const controller = listenKeyboardController(apply);\n return () => {\n show.remove();\n hide.remove();\n controller?.remove();\n };\n }, []);\n\n return Platform.OS === 'android' ? 0 : height;\n}\n\n/**\n * In-flow spacer = full keyboard height so the composer docks on the keys.\n * Native swipe lives on KeyboardAwareScreen; Expo Go uses a composer pan.\n */\nexport function KeyboardComposerDock({ children, closedInset, onSwipeUp }: KeyboardComposerDockProps) {\n const keyboardHeight = useKeyboardLiftHeight();\n const closedPadding = Math.max(closedInset, 0);\n const useNativeSwipe = isKeyboardControllerAvailable();\n const onSwipeUpRef = useRef(onSwipeUp);\n onSwipeUpRef.current = onSwipeUp;\n const fallbackGestures = useRef(\n PanResponder.create({\n onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dy) > 12 && Math.abs(g.dy) > Math.abs(g.dx) * 1.2,\n onPanResponderRelease: (_, g) => {\n if (g.dy < -28 || g.vy < -0.55) onSwipeUpRef.current?.();\n else if (g.dy > 28 || g.vy > 0.55) dismissKeyboard();\n },\n }),\n ).current;\n\n return (\n <>\n <View\n style={[styles.dock, { paddingBottom: Math.max(0, closedPadding - keyboardHeight) }]}\n {...(useNativeSwipe ? null : fallbackGestures.panHandlers)}\n >\n {children}\n </View>\n {Platform.OS === 'ios' ? <View style={{ height: keyboardHeight }} pointerEvents=\"none\" /> : null}\n </>\n );\n}\n\nconst styles = StyleSheet.create({\n flex: {\n flex: 1,\n },\n dock: {\n width: '100%',\n alignItems: 'center',\n zIndex: 20,\n elevation: 20,\n },\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAGO,MAAM,wBAA2B,GAAA;AAcjC,SAAS,mBAAoB,CAAA;AAAA,EAClC,QAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAIG,EAAA;AACD,EAAA,IAAI,+BAAiC,EAAA;AACnC,IAAM,MAAA,mBAAA,GAAsB,OAAQ,CAAA,kCAAkC,CAAE,CAAA,mBAAA;AASxE,IAAA,2BAAQ,mBAAoB,EAAA,EAAA,KAAA,EAAO,CAAC,MAAA,CAAO,MAAM,KAAK,CAAA,EAAG,YAAa,EAAA,KAAA,EAAM,eAAa,IAAC,EAAA,oBAAA,EAAoB,MAAC,iBAAmB,EAAA,wBAAA,EAA0B,QAC/I,QACL,EAAA,CAAA;AAAA;AAEV,EAAO,uBAAA,GAAA,CAAC,QAAK,KAAO,EAAA,CAAC,OAAO,IAAM,EAAA,KAAK,CAAG,EAAA,MAAA,EAC/B,QACL,EAAA,CAAA;AACR;AAUA,SAAS,wBAAwB,QAAmB,EAAA;AAClD,EAAI,IAAA,QAAA,CAAS,OAAO,KAAO,EAAA;AAC3B,EAAA,eAAA,CAAgB,aAAc,CAAA;AAAA,IAC5B,QAAU,EAAA,QAAA,IAAY,QAAW,GAAA,CAAA,GAAI,QAAW,GAAA,GAAA;AAAA,IAChD,MAAQ,EAAA;AAAA,MACN,IAAA,EAAM,gBAAgB,KAAM,CAAA;AAAA;AAC9B,GACD,CAAA;AACH;AAIA,SAAS,yBAAyB,QAAsF,EAAA;AACtH,EAAI,IAAA,CAAC,6BAA8B,EAAA,EAAU,OAAA,IAAA;AAC7C,EAAI,IAAA;AACF,IAAM,MAAA;AAAA,MACJ;AAAA,KACF,GAAI,QAAQ,kCAAkC,CAAA;AAU9C,IAAI,IAAA,EAAC,cAAgB,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,cAAA,CAAA,WAAA,CAAA,EAAoB,OAAA,IAAA;AACzC,IAAA,MAAM,IAAO,GAAA,cAAA,CAAe,WAAY,CAAA,kBAAA,EAAoB,CAAK,CAAA,KAAA;AAjFrE,MAAA,IAAA,EAAA;AAkFM,MAAA,QAAA,CAAA,CAAS,EAAG,GAAA,CAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,CAAA,CAAA,MAAA,KAAH,IAAa,GAAA,EAAA,GAAA,CAAA,EAAG,uBAAG,QAAQ,CAAA;AAAA,KACrC,CAAA;AACD,IAAA,MAAM,KAAQ,GAAA,cAAA,CAAe,WAAY,CAAA,iBAAA,EAAmB,CAAK,CAAA,KAAA;AApFrE,MAAA,IAAA,EAAA;AAqFM,MAAA,QAAA,CAAA,CAAS,EAAG,GAAA,CAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,CAAA,CAAA,MAAA,KAAH,IAAa,GAAA,EAAA,GAAA,CAAA,EAAG,uBAAG,QAAQ,CAAA;AAAA,KACrC,CAAA;AACD,IAAA,MAAM,IAAO,GAAA,cAAA,CAAe,WAAY,CAAA,kBAAA,EAAoB,CAAK,CAAA,KAAA;AAC/D,MAAS,QAAA,CAAA,CAAA,EAAG,uBAAG,QAAQ,CAAA;AAAA,KACxB,CAAA;AACD,IAAA,MAAM,MAAS,GAAA,cAAA,CAAe,WAAY,CAAA,iBAAA,EAAmB,CAAK,CAAA,KAAA;AAChE,MAAS,QAAA,CAAA,CAAA,EAAG,uBAAG,QAAQ,CAAA;AAAA,KACxB,CAAA;AACD,IAAO,OAAA;AAAA,MACL,QAAQ,MAAM;AACZ,QAAA,IAAA,CAAK,MAAO,EAAA;AACZ,QAAA,KAAA,CAAM,MAAO,EAAA;AACb,QAAA,IAAA,CAAK,MAAO,EAAA;AACZ,QAAA,MAAA,CAAO,MAAO,EAAA;AAAA;AAChB,KACF;AAAA,GACM,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,IAAA;AAAA;AAEX;AAGO,SAAS,qBAAgC,GAAA;AAC9C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,CAAC,CAAA;AACtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAM,MAAA,KAAA,GAAQ,CAAC,IAAA,EAAc,QAAsB,KAAA;AACjD,MAAA,uBAAA,CAAwB,QAAQ,CAAA;AAChC,MAAA,SAAA,CAAU,IAAI,CAAA;AAAA,KAChB;AACA,IAAM,MAAA,IAAA,GAAO,SAAS,WAAY,CAAA,QAAA,CAAS,OAAO,KAAQ,GAAA,kBAAA,GAAqB,mBAAmB,CAAK,CAAA,KAAA;AAlH3G,MAAA,IAAA,EAAA,EAAA,EAAA;AAmHM,MAAA,KAAA,CAAA,CAAM,aAAE,cAAF,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAkB,WAAlB,IAA4B,GAAA,EAAA,GAAA,CAAA,EAAG,EAAE,QAAQ,CAAA;AAAA,KAChD,CAAA;AACD,IAAM,MAAA,IAAA,GAAO,SAAS,WAAY,CAAA,QAAA,CAAS,OAAO,KAAQ,GAAA,kBAAA,GAAqB,mBAAmB,CAAK,CAAA,KAAA;AACrG,MAAM,KAAA,CAAA,CAAA,EAAG,EAAE,QAAQ,CAAA;AAAA,KACpB,CAAA;AAID,IAAM,MAAA,UAAA,GAAa,yBAAyB,KAAK,CAAA;AACjD,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,MAAO,EAAA;AACZ,MAAA,IAAA,CAAK,MAAO,EAAA;AACZ,MAAY,UAAA,IAAA,IAAA,GAAA,MAAA,GAAA,UAAA,CAAA,MAAA,EAAA;AAAA,KACd;AAAA,GACF,EAAG,EAAE,CAAA;AACL,EAAO,OAAA,QAAA,CAAS,EAAO,KAAA,SAAA,GAAY,CAAI,GAAA,MAAA;AACzC;AAMO,SAAS,oBAAqB,CAAA;AAAA,EACnC,QAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAA8B,EAAA;AAC5B,EAAA,MAAM,iBAAiB,qBAAsB,EAAA;AAC7C,EAAA,MAAM,aAAgB,GAAA,IAAA,CAAK,GAAI,CAAA,WAAA,EAAa,CAAC,CAAA;AAC7C,EAAA,MAAM,iBAAiB,6BAA8B,EAAA;AACrD,EAAM,MAAA,YAAA,GAAe,OAAO,SAAS,CAAA;AACrC,EAAA,YAAA,CAAa,OAAU,GAAA,SAAA;AACvB,EAAM,MAAA,gBAAA,GAAmB,MAAO,CAAA,YAAA,CAAa,MAAO,CAAA;AAAA,IAClD,6BAA6B,CAAC,CAAA,EAAG,MAAM,IAAK,CAAA,GAAA,CAAI,EAAE,EAAE,CAAA,GAAI,MAAM,IAAK,CAAA,GAAA,CAAI,EAAE,EAAE,CAAA,GAAI,KAAK,GAAI,CAAA,CAAA,CAAE,EAAE,CAAI,GAAA,GAAA;AAAA,IAChG,qBAAA,EAAuB,CAAC,CAAA,EAAG,CAAM,KAAA;AArJrC,MAAA,IAAA,EAAA;AAsJM,MAAA,IAAI,EAAE,EAAK,GAAA,GAAA,IAAO,EAAE,EAAK,GAAA,KAAA,qBAAoB,OAAb,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,YAAA,CAAA;AAAA,WAAA,IAAkC,EAAE,EAAK,GAAA,EAAA,IAAM,CAAE,CAAA,EAAA,GAAK,MAAsB,eAAA,EAAA;AAAA;AAC9G,GACD,CAAC,CAAE,CAAA,OAAA;AACJ,EAAA,uBACU,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,IAAK,EAAA,aAAA,CAAA,cAAA,CAAA,EAAA,KAAA,EAAO,CAAC,MAAA,CAAO,IAAM,EAAA;AAAA,MACjC,aAAe,EAAA,IAAA,CAAK,GAAI,CAAA,CAAA,EAAG,gBAAgB,cAAc;AAAA,KAC1D,CAAO,EAAA,EAAA,cAAA,GAAiB,OAAO,gBAAiB,CAAA,WAAA,CAAA,EAFxC,EAGI,QACL,EAAA,CAAA,CAAA;AAAA,IACC,QAAS,CAAA,EAAA,KAAO,KAAQ,mBAAA,GAAA,CAAC,QAAK,KAAO,EAAA;AAAA,MAC5C,MAAQ,EAAA;AAAA,KACV,EAAG,aAAc,EAAA,MAAA,EAAO,CAAK,GAAA;AAAA,GACzB,EAAA,CAAA;AACR;AACA,MAAM,MAAA,GAAS,WAAW,MAAO,CAAA;AAAA,EAC/B,IAAM,EAAA;AAAA,IACJ,IAAM,EAAA;AAAA,GACR;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,KAAO,EAAA,MAAA;AAAA,IACP,UAAY,EAAA,QAAA;AAAA,IACZ,MAAQ,EAAA,EAAA;AAAA,IACR,SAAW,EAAA;AAAA;AAEf,CAAC,CAAA"}
@@ -0,0 +1,189 @@
1
+ import {jsx,jsxs}from'react/jsx-runtime';import {useState,useRef,useEffect,useMemo}from'react';import {Pressable,StyleSheet,View,Text}from'react-native';import {Ionicons,MaterialCommunityIcons}from'@expo/vector-icons';import {getDefaultRightItems}from'@admin-layout/gluestack-ui-mobile';import {useKeyboardLiftHeight}from'../../components/KeyboardComposerDock.js';var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ const CHOICES = [{
21
+ id: "library",
22
+ label: "Photo Library",
23
+ icon: "image-multiple-outline"
24
+ }, {
25
+ id: "camera",
26
+ label: "Take Photo or Video",
27
+ icon: "camera-outline"
28
+ }, {
29
+ id: "documents",
30
+ label: "Choose File",
31
+ icon: "folder-outline"
32
+ }];
33
+ function AttachTriggerButton({
34
+ isDark,
35
+ onPress
36
+ }) {
37
+ const border = isDark ? "rgba(148, 163, 184, 0.4)" : "#e4e4e7";
38
+ const bg = isDark ? "#1e293b" : "#f4f4f5";
39
+ const ink = isDark ? "#e2e8f0" : "#18181b";
40
+ return /* @__PURE__ */ jsx(Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: "Add photo or file", hitSlop: 6, style: ({
41
+ pressed
42
+ }) => [styles.trigger, {
43
+ backgroundColor: bg,
44
+ borderColor: border,
45
+ opacity: pressed ? 0.7 : 1
46
+ }], children: /* @__PURE__ */ jsx(Ionicons, { name: "add", size: 18, color: ink }) });
47
+ }
48
+ function ComposerAttachMenu({
49
+ visible,
50
+ isDark,
51
+ onClose,
52
+ onSelect
53
+ }) {
54
+ const keyboardHeight = useKeyboardLiftHeight();
55
+ const [backdropArmed, setBackdropArmed] = useState(false);
56
+ const ink = isDark ? "#f8fafc" : "#0f172a";
57
+ const iconColor = isDark ? "#94a3b8" : "#64748b";
58
+ const sheetBg = isDark ? "rgba(30, 41, 59, 0.96)" : "rgba(255, 255, 255, 0.96)";
59
+ const divider = isDark ? "rgba(148, 163, 184, 0.18)" : "rgba(15, 23, 42, 0.08)";
60
+ useEffect(() => {
61
+ if (!visible) {
62
+ setBackdropArmed(false);
63
+ return;
64
+ }
65
+ const timer = setTimeout(() => setBackdropArmed(true), 450);
66
+ return () => clearTimeout(timer);
67
+ }, [visible]);
68
+ if (!visible) return null;
69
+ return /* @__PURE__ */ jsxs(View, { style: styles.layer, pointerEvents: "box-none", children: [
70
+ backdropArmed ? /* @__PURE__ */ jsx(Pressable, { style: StyleSheet.absoluteFill, onPress: onClose, accessibilityLabel: "Dismiss attach menu" }) : /* @__PURE__ */ jsx(View, { style: StyleSheet.absoluteFill, pointerEvents: "none" }),
71
+ /* @__PURE__ */ jsx(View, { style: [styles.sheetWrap, {
72
+ paddingBottom: Math.max(72, keyboardHeight + 56)
73
+ }], pointerEvents: "box-none", children: /* @__PURE__ */ jsx(View, { style: [styles.sheet, {
74
+ backgroundColor: sheetBg,
75
+ borderColor: isDark ? "rgba(148, 163, 184, 0.28)" : "rgba(15, 23, 42, 0.08)"
76
+ }], onStartShouldSetResponder: () => true, children: CHOICES.map((choice, index) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => onSelect(choice.id), accessibilityRole: "button", accessibilityLabel: choice.label, style: ({
77
+ pressed
78
+ }) => [styles.row, index < CHOICES.length - 1 ? {
79
+ borderBottomWidth: StyleSheet.hairlineWidth,
80
+ borderBottomColor: divider
81
+ } : null, pressed && {
82
+ backgroundColor: isDark ? "rgba(148, 163, 184, 0.12)" : "rgba(15, 23, 42, 0.04)"
83
+ }], children: [
84
+ /* @__PURE__ */ jsx(Text, { style: [styles.rowLabel, {
85
+ color: ink
86
+ }], children: choice.label }),
87
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: choice.icon, size: 16, color: iconColor })
88
+ ] }, choice.id)) }) })
89
+ ] });
90
+ }
91
+ function launchAttachChoice(api, choice) {
92
+ if (choice === "library") void api.pickFromLibrary();
93
+ else if (choice === "camera") void api.captureFromCamera();
94
+ else void api.pickDocuments();
95
+ }
96
+ function useComposerAttachMenu(attachments, isDark) {
97
+ const [open, setOpen] = useState(false);
98
+ const pendingChoiceRef = useRef(null);
99
+ const attachmentsRef = useRef(attachments);
100
+ attachmentsRef.current = attachments;
101
+ useEffect(() => {
102
+ if (open) return;
103
+ const choice = pendingChoiceRef.current;
104
+ if (!choice) return;
105
+ pendingChoiceRef.current = null;
106
+ const timer = setTimeout(() => launchAttachChoice(attachmentsRef.current, choice), 50);
107
+ return () => clearTimeout(timer);
108
+ }, [open]);
109
+ const openMenu = () => setOpen(true);
110
+ const rightItems = useMemo(() => getDefaultRightItems({
111
+ tag: {
112
+ enabled: false
113
+ },
114
+ chip: {
115
+ enabled: false
116
+ },
117
+ camera: {
118
+ enabled: false
119
+ },
120
+ image: {
121
+ enabled: false
122
+ },
123
+ attach: {
124
+ enabled: true,
125
+ label: "Add photo or file",
126
+ customButton: /* @__PURE__ */ jsx(AttachTriggerButton, { isDark, onPress: openMenu })
127
+ }
128
+ }), [isDark]);
129
+ const menu = /* @__PURE__ */ jsx(ComposerAttachMenu, { visible: open, isDark, onClose: () => {
130
+ pendingChoiceRef.current = null;
131
+ setOpen(false);
132
+ }, onSelect: (choice) => {
133
+ pendingChoiceRef.current = choice;
134
+ setOpen(false);
135
+ } });
136
+ return {
137
+ rightItems,
138
+ menu
139
+ };
140
+ }
141
+ const styles = StyleSheet.create({
142
+ trigger: {
143
+ width: 28,
144
+ height: 28,
145
+ borderRadius: 14,
146
+ borderWidth: 1,
147
+ alignItems: "center",
148
+ justifyContent: "center"
149
+ },
150
+ layer: __spreadProps(__spreadValues({}, StyleSheet.absoluteFillObject), {
151
+ zIndex: 80,
152
+ elevation: 80
153
+ }),
154
+ sheetWrap: {
155
+ flex: 1,
156
+ justifyContent: "flex-end",
157
+ alignItems: "flex-end",
158
+ paddingRight: 18
159
+ },
160
+ sheet: {
161
+ width: 220,
162
+ borderRadius: 10,
163
+ borderWidth: StyleSheet.hairlineWidth,
164
+ overflow: "hidden",
165
+ shadowColor: "#0f172a",
166
+ shadowOpacity: 0.16,
167
+ shadowRadius: 12,
168
+ shadowOffset: {
169
+ width: 0,
170
+ height: 6
171
+ },
172
+ elevation: 10
173
+ },
174
+ row: {
175
+ minHeight: 34,
176
+ paddingHorizontal: 12,
177
+ paddingVertical: 6,
178
+ flexDirection: "row",
179
+ alignItems: "center",
180
+ justifyContent: "space-between",
181
+ gap: 10
182
+ },
183
+ rowLabel: {
184
+ flex: 1,
185
+ fontSize: 13,
186
+ fontWeight: "500",
187
+ letterSpacing: -0.1
188
+ }
189
+ });export{ComposerAttachMenu,useComposerAttachMenu};//# sourceMappingURL=ComposerAttachMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComposerAttachMenu.js","sources":["../../../src/features/attachments/ComposerAttachMenu.tsx"],"sourcesContent":["/**\n * One composer control (plus) opens this sheet — Photo Library, Camera, File.\n * Rendered as an in-tree overlay (not RN Modal) so iOS can present the system\n * pickers, and so the plus tap does not immediately dismiss the sheet.\n */\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';\nimport { getDefaultRightItems } from '@admin-layout/gluestack-ui-mobile';\nimport { useKeyboardLiftHeight } from '../../components/KeyboardComposerDock';\nimport type { ImageAttachmentsApi } from './useImageAttachments';\n\ntype AttachChoice = 'library' | 'camera' | 'documents';\n\nconst CHOICES: Array<{\n id: AttachChoice;\n label: string;\n icon: React.ComponentProps<typeof MaterialCommunityIcons>['name'];\n}> = [\n { id: 'library', label: 'Photo Library', icon: 'image-multiple-outline' },\n { id: 'camera', label: 'Take Photo or Video', icon: 'camera-outline' },\n { id: 'documents', label: 'Choose File', icon: 'folder-outline' },\n];\n\nfunction AttachTriggerButton({ isDark, onPress }: { isDark: boolean; onPress: () => void }) {\n const border = isDark ? 'rgba(148, 163, 184, 0.4)' : '#e4e4e7';\n const bg = isDark ? '#1e293b' : '#f4f4f5';\n const ink = isDark ? '#e2e8f0' : '#18181b';\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Add photo or file\"\n hitSlop={6}\n style={({ pressed }) => [\n styles.trigger,\n { backgroundColor: bg, borderColor: border, opacity: pressed ? 0.7 : 1 },\n ]}\n >\n <Ionicons name=\"add\" size={18} color={ink} />\n </Pressable>\n );\n}\n\nexport function ComposerAttachMenu({\n visible,\n isDark,\n onClose,\n onSelect,\n}: {\n visible: boolean;\n isDark: boolean;\n onClose: () => void;\n onSelect: (choice: AttachChoice) => void;\n}) {\n const keyboardHeight = useKeyboardLiftHeight();\n const [backdropArmed, setBackdropArmed] = useState(false);\n const ink = isDark ? '#f8fafc' : '#0f172a';\n const iconColor = isDark ? '#94a3b8' : '#64748b';\n const sheetBg = isDark ? 'rgba(30, 41, 59, 0.96)' : 'rgba(255, 255, 255, 0.96)';\n const divider = isDark ? 'rgba(148, 163, 184, 0.18)' : 'rgba(15, 23, 42, 0.08)';\n\n useEffect(() => {\n if (!visible) {\n setBackdropArmed(false);\n return;\n }\n const timer = setTimeout(() => setBackdropArmed(true), 450);\n return () => clearTimeout(timer);\n }, [visible]);\n\n if (!visible) return null;\n\n return (\n <View style={styles.layer} pointerEvents=\"box-none\">\n {backdropArmed ? (\n <Pressable style={StyleSheet.absoluteFill} onPress={onClose} accessibilityLabel=\"Dismiss attach menu\" />\n ) : (\n <View style={StyleSheet.absoluteFill} pointerEvents=\"none\" />\n )}\n <View\n style={[styles.sheetWrap, { paddingBottom: Math.max(72, keyboardHeight + 56) }]}\n pointerEvents=\"box-none\"\n >\n <View\n style={[\n styles.sheet,\n {\n backgroundColor: sheetBg,\n borderColor: isDark ? 'rgba(148, 163, 184, 0.28)' : 'rgba(15, 23, 42, 0.08)',\n },\n ]}\n onStartShouldSetResponder={() => true}\n >\n {CHOICES.map((choice, index) => (\n <Pressable\n key={choice.id}\n onPress={() => onSelect(choice.id)}\n accessibilityRole=\"button\"\n accessibilityLabel={choice.label}\n style={({ pressed }) => [\n styles.row,\n index < CHOICES.length - 1\n ? { borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: divider }\n : null,\n pressed && {\n backgroundColor: isDark ? 'rgba(148, 163, 184, 0.12)' : 'rgba(15, 23, 42, 0.04)',\n },\n ]}\n >\n <Text style={[styles.rowLabel, { color: ink }]}>{choice.label}</Text>\n <MaterialCommunityIcons name={choice.icon} size={16} color={iconColor} />\n </Pressable>\n ))}\n </View>\n </View>\n </View>\n );\n}\n\nfunction launchAttachChoice(api: ImageAttachmentsApi, choice: AttachChoice): void {\n if (choice === 'library') void api.pickFromLibrary();\n else if (choice === 'camera') void api.captureFromCamera();\n else void api.pickDocuments();\n}\n\n/** Shared Home + Chat wiring: one attach icon, camera/library/file behind the sheet. */\nexport function useComposerAttachMenu(attachments: ImageAttachmentsApi, isDark: boolean) {\n const [open, setOpen] = useState(false);\n const pendingChoiceRef = useRef<AttachChoice | null>(null);\n const attachmentsRef = useRef(attachments);\n attachmentsRef.current = attachments;\n\n useEffect(() => {\n if (open) return;\n const choice = pendingChoiceRef.current;\n if (!choice) return;\n pendingChoiceRef.current = null;\n const timer = setTimeout(() => launchAttachChoice(attachmentsRef.current, choice), 50);\n return () => clearTimeout(timer);\n }, [open]);\n\n const openMenu = () => setOpen(true);\n\n const rightItems = useMemo(\n () =>\n getDefaultRightItems({\n tag: { enabled: false },\n chip: { enabled: false },\n camera: { enabled: false },\n image: { enabled: false },\n attach: {\n enabled: true,\n label: 'Add photo or file',\n customButton: <AttachTriggerButton isDark={isDark} onPress={openMenu} />,\n },\n }),\n [isDark],\n );\n\n const menu = (\n <ComposerAttachMenu\n visible={open}\n isDark={isDark}\n onClose={() => {\n pendingChoiceRef.current = null;\n setOpen(false);\n }}\n onSelect={(choice) => {\n pendingChoiceRef.current = choice;\n setOpen(false);\n }}\n />\n );\n\n return { rightItems, menu };\n}\n\nconst styles = StyleSheet.create({\n trigger: {\n width: 28,\n height: 28,\n borderRadius: 14,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n layer: {\n ...StyleSheet.absoluteFillObject,\n zIndex: 80,\n elevation: 80,\n },\n sheetWrap: {\n flex: 1,\n justifyContent: 'flex-end',\n alignItems: 'flex-end',\n paddingRight: 18,\n },\n sheet: {\n width: 220,\n borderRadius: 10,\n borderWidth: StyleSheet.hairlineWidth,\n overflow: 'hidden',\n shadowColor: '#0f172a',\n shadowOpacity: 0.16,\n shadowRadius: 12,\n shadowOffset: { width: 0, height: 6 },\n elevation: 10,\n },\n row: {\n minHeight: 34,\n paddingHorizontal: 12,\n paddingVertical: 6,\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 10,\n },\n rowLabel: {\n flex: 1,\n fontSize: 13,\n fontWeight: '500',\n letterSpacing: -0.1,\n },\n});\n"],"names":[],"mappings":"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"}
@@ -80,22 +80,30 @@ function loadPicker() {
80
80
  return require("expo-image-picker");
81
81
  }
82
82
  function loadDocumentPicker() {
83
- var _a, _b, _c;
84
- const core = require("expo-modules-core");
85
- const native = (_c = (_a = core.requireOptionalNativeModule) == null ? void 0 : _a.call(core, "ExpoDocumentPicker")) != null ? _c : (_b = core.requireNativeModule) == null ? void 0 : _b.call(core, "ExpoDocumentPicker");
86
- if (!(native == null ? void 0 : native.getDocumentAsync)) {
87
- throw new Error("File picker is not available in this build.");
88
- }
89
- return {
90
- getDocumentAsync: (options) => {
91
- var _a2, _b2, _c2;
92
- return native.getDocumentAsync({
93
- type: Array.isArray(options.type) ? options.type : [(_a2 = options.type) != null ? _a2 : "*/*"],
94
- multiple: (_b2 = options.multiple) != null ? _b2 : false,
95
- copyToCacheDirectory: (_c2 = options.copyToCacheDirectory) != null ? _c2 : true
96
- });
83
+ var _a;
84
+ try {
85
+ const core = require("expo-modules-core");
86
+ const native = (_a = core.requireOptionalNativeModule) == null ? void 0 : _a.call(core, "ExpoDocumentPicker");
87
+ if (native == null ? void 0 : native.getDocumentAsync) {
88
+ return {
89
+ getDocumentAsync: (options) => {
90
+ var _a2, _b, _c;
91
+ return native.getDocumentAsync({
92
+ type: Array.isArray(options.type) ? options.type : [(_a2 = options.type) != null ? _a2 : "*/*"],
93
+ multiple: (_b = options.multiple) != null ? _b : false,
94
+ copyToCacheDirectory: (_c = options.copyToCacheDirectory) != null ? _c : true
95
+ });
96
+ }
97
+ };
97
98
  }
98
- };
99
+ return require("expo-document-picker");
100
+ } catch (err) {
101
+ const msg = err instanceof Error ? err.message : String(err != null ? err : "");
102
+ if (/Cannot find native module|ExpoDocumentPicker|Cannot find module/i.test(msg)) {
103
+ throw new Error("File picker is not included in this app build. Use Photos, or install a staging build that includes expo-document-picker.");
104
+ }
105
+ throw err;
106
+ }
99
107
  }
100
108
  function loadFileSystem() {
101
109
  return require("expo-file-system");
@@ -1 +1 @@
1
- {"version":3,"file":"useImageAttachments.js","sources":["../../../src/features/attachments/useImageAttachments.ts"],"sourcesContent":["/**\n * Image attachments for the native composer.\n *\n * The transport was already there — `useChatStream.sendMessage(content,\n * attachments)` carries `MessageAttachment[]` and the gateway media lane is\n * covered by `aiwork/e2e-tests/gateway-media-upload.spec.ts`. What was missing\n * was any way to produce one on a phone: the composer shipped with\n * `image`/`camera`/`attach` hard-disabled, and nothing in the mobile package\n * touched `expo-image-picker` even though it is a dependency and the photo and\n * camera permission strings are already declared in `app.json`.\n *\n * Payload size is the whole game here. A modern iPhone photo is ~3-5 MB and\n * ~4000px wide; base64 inflates that by a third, and it travels inline on the\n * chat message. Camera captures can be JPEG-compressed via `quality`. Library\n * picks must NOT pass `quality`: that asks iOS for `public.jpeg` and fails on\n * HEIC / iCloud photos with \"Cannot load representation of type public.jpeg\".\n * The canvas picker already uses `Current` for that reason.\n */\nimport { useCallback, useMemo, useState } from 'react';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\n\n/** Longest edge, in px, we send. Plenty for a model to read; ~10x smaller than source. */\nconst MAX_EDGE = 1280;\n/** JPEG quality handed to the picker (0-1). */\nconst PICKER_QUALITY = 0.7;\n/** Refuse anything above this after re-encoding — a send is better failed than hung. */\nconst MAX_BYTES = 4 * 1024 * 1024;\n/** Nothing sensible is this small; treat as a failed decode. */\nconst MIN_BYTES = 64;\n\nexport type AttachmentBusyKind = 'camera' | 'library' | 'documents' | null;\n\nexport interface ImageAttachmentsApi {\n /** Attachments staged for the next send. */\n pending: MessageAttachment[];\n /** True while any picker/encode is in flight. */\n busy: boolean;\n /** Which toolbar control is waiting — only that one should spin. */\n busyKind: AttachmentBusyKind;\n /** Human-readable failure for the composer's error banner, or null. */\n error: string | null;\n pickFromLibrary: () => Promise<void>;\n captureFromCamera: () => Promise<void>;\n pickDocuments: () => Promise<void>;\n remove: (id: string) => void;\n clear: () => void;\n /** Pass to `sendMessage` — undefined when empty, which is what it expects. */\n forSend: () => MessageAttachment[] | undefined;\n}\n\ntype PickedAsset = {\n uri?: string | null;\n base64?: string | null;\n mimeType?: string | null;\n fileName?: string | null;\n fileSize?: number | null;\n width?: number | null;\n height?: number | null;\n};\n\n/** Base64 decodes to 3 bytes per 4 chars, minus `=` padding. */\nfunction base64Bytes(base64: string): number {\n const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;\n return Math.floor((base64.length * 3) / 4) - padding;\n}\n\nfunction isJpegRepresentationError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err ?? '');\n return /public\\.jpeg|Failed to read picked image/i.test(msg);\n}\n\nfunction attachErrorMessage(err: unknown): string {\n if (isJpegRepresentationError(err)) {\n return 'Could not read that photo. Try another one, or save it to Recents first.';\n }\n return err instanceof Error ? err.message : 'Could not attach that image.';\n}\n\nfunction guessImageMime(asset: PickedAsset): string {\n const mime = asset.mimeType?.trim();\n if (mime) return mime;\n const name = `${asset.fileName ?? ''} ${asset.uri ?? ''}`;\n if (/\\.png(\\?|$)/i.test(name)) return 'image/png';\n if (/\\.gif(\\?|$)/i.test(name)) return 'image/gif';\n if (/\\.webp(\\?|$)/i.test(name)) return 'image/webp';\n if (/\\.(heic|heif)(\\?|$)/i.test(name)) return 'image/heic';\n return 'image/jpeg';\n}\n\nasync function readUriBase64(uri: string): Promise<string | null> {\n const fs = loadFileSystem() as {\n readAsStringAsync?: (uri: string, options?: { encoding?: string }) => Promise<string>;\n EncodingType?: { Base64?: string };\n };\n if (typeof fs.readAsStringAsync !== 'function') return null;\n const encoding = fs.EncodingType?.Base64 ?? 'base64';\n const base64 = (await fs.readAsStringAsync(uri, { encoding })).trim();\n return base64 || null;\n}\n\nasync function assetToAttachment(asset: PickedAsset, index: number): Promise<MessageAttachment | null> {\n let base64 = asset.base64?.trim() || '';\n if (!base64 && asset.uri?.trim()) {\n base64 = (await readUriBase64(asset.uri.trim())) || '';\n }\n if (!base64) return null;\n const size = base64Bytes(base64);\n if (size < MIN_BYTES || size > MAX_BYTES) return null;\n const mimeType = guessImageMime(asset);\n const name = asset.fileName?.trim() || `image-${Date.now()}-${index}.jpg`;\n return {\n id: `img-${Date.now()}-${index}`,\n name,\n type: 'file',\n mimeType,\n dataUrl: `data:${mimeType};base64,${base64}`,\n url: asset.uri?.trim() || undefined,\n size,\n };\n}\n\ntype PickedDocument = {\n uri?: string | null;\n name?: string | null;\n mimeType?: string | null;\n size?: number | null;\n};\n\n/**\n * Lazily required so a unit test or a non-Expo bundle importing this hook does\n * not hard-fail on the native module.\n */\nfunction loadPicker() {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-image-picker') as typeof import('expo-image-picker');\n}\n\nfunction loadDocumentPicker(): {\n getDocumentAsync: (options: {\n type?: string | string[];\n multiple?: boolean;\n copyToCacheDirectory?: boolean;\n }) => Promise<{\n canceled?: boolean;\n type?: string;\n uri?: string;\n name?: string;\n mimeType?: string;\n size?: number;\n assets?: PickedDocument[];\n }>;\n} {\n // Use the native module Expo Go already ships. Do not `require('expo-document-picker')`\n // — that package's published `main` is `build/index.js`, which is often missing\n // from this workspace install, and Metro then fails the whole bundle.\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n const core = require('expo-modules-core') as {\n requireOptionalNativeModule?: (name: string) => { getDocumentAsync?: Function } | null;\n requireNativeModule?: (name: string) => { getDocumentAsync?: Function };\n };\n const native =\n core.requireOptionalNativeModule?.('ExpoDocumentPicker') ?? core.requireNativeModule?.('ExpoDocumentPicker');\n if (!native?.getDocumentAsync) {\n throw new Error('File picker is not available in this build.');\n }\n return {\n getDocumentAsync: (options) =>\n native.getDocumentAsync({\n type: Array.isArray(options.type) ? options.type : [options.type ?? '*/*'],\n multiple: options.multiple ?? false,\n copyToCacheDirectory: options.copyToCacheDirectory ?? true,\n }) as Promise<{\n canceled?: boolean;\n type?: string;\n uri?: string;\n name?: string;\n mimeType?: string;\n size?: number;\n assets?: PickedDocument[];\n }>,\n };\n}\n\nfunction loadFileSystem() {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-file-system') as typeof import('expo-file-system');\n}\n\n/** Web `ACCEPTED_FILE_TYPES` — docs + images. */\nconst DOCUMENT_MIME_TYPES = [\n 'application/pdf',\n 'text/plain',\n 'text/markdown',\n 'text/csv',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'image/*',\n];\nconst MAX_FILES = 6;\nconst MAX_DOCUMENT_BYTES = 15 * 1024 * 1024;\n\nfunction normalizeDocumentAssets(result: {\n canceled?: boolean;\n type?: string;\n uri?: string;\n name?: string;\n mimeType?: string;\n size?: number;\n assets?: PickedDocument[];\n}): PickedDocument[] {\n if (!result || result.canceled || result.type === 'cancel') return [];\n if (Array.isArray(result.assets) && result.assets.length > 0) return result.assets;\n if (result.uri) {\n return [{ uri: result.uri, name: result.name, mimeType: result.mimeType, size: result.size }];\n }\n return [];\n}\n\nasync function documentToAttachment(asset: PickedDocument, index: number): Promise<MessageAttachment | null> {\n const uri = asset.uri?.trim();\n if (!uri) return null;\n if (typeof asset.size === 'number' && asset.size > MAX_DOCUMENT_BYTES) return null;\n\n const fs = loadFileSystem() as {\n readAsStringAsync?: (uri: string, options?: { encoding?: string }) => Promise<string>;\n EncodingType?: { Base64?: string };\n };\n const encoding = fs.EncodingType?.Base64 ?? 'base64';\n if (typeof fs.readAsStringAsync !== 'function') return null;\n const base64 = (await fs.readAsStringAsync(uri, { encoding })).trim();\n if (!base64) return null;\n const size = typeof asset.size === 'number' && asset.size > 0 ? asset.size : base64Bytes(base64);\n if (size < MIN_BYTES || size > MAX_DOCUMENT_BYTES) return null;\n const mimeType = asset.mimeType?.trim() || 'application/octet-stream';\n const name = asset.name?.trim() || `file-${Date.now()}-${index}`;\n return {\n id: `file-${Date.now()}-${index}`,\n name,\n type: 'file',\n mimeType,\n dataUrl: `data:${mimeType};base64,${base64}`,\n url: uri,\n size,\n };\n}\n\nexport function useImageAttachments(): ImageAttachmentsApi {\n const [pending, setPending] = useState<MessageAttachment[]>([]);\n const [busyKind, setBusyKind] = useState<AttachmentBusyKind>(null);\n const [error, setError] = useState<string | null>(null);\n\n const run = useCallback(async (mode: 'library' | 'camera') => {\n setError(null);\n try {\n const picker = loadPicker();\n\n const permission =\n mode === 'camera'\n ? await picker.requestCameraPermissionsAsync()\n : await picker.requestMediaLibraryPermissionsAsync();\n if (!permission.granted) {\n setError(\n mode === 'camera'\n ? 'Camera access is off for Yantra. Turn it on in Settings to attach a photo.'\n : 'Photo access is off for Yantra. Turn it on in Settings to attach an image.',\n );\n return;\n }\n\n // Spin only the control that was tapped, and only after permission\n // so the iOS prompt does not turn camera+gallery+attach into loaders.\n setBusyKind(mode);\n const representation =\n picker.UIImagePickerPreferredAssetRepresentationMode?.Current ?? ('current' as never);\n const options: import('expo-image-picker').ImagePickerOptions = {\n base64: true,\n allowsMultipleSelection: mode === 'library',\n mediaTypes: ['images'],\n selectionLimit: 4,\n exif: false,\n // Library: never pass `quality` — that requests public.jpeg and fails\n // on HEIC / iCloud (see pickLocalMedia). Camera JPEGs can compress.\n ...(mode === 'camera' ? { quality: PICKER_QUALITY } : {}),\n ...(mode === 'library' ? { preferredAssetRepresentationMode: representation } : {}),\n };\n\n let result: import('expo-image-picker').ImagePickerResult;\n try {\n result =\n mode === 'camera'\n ? await picker.launchCameraAsync({ ...options, allowsMultipleSelection: false })\n : await picker.launchImageLibraryAsync(options);\n } catch (e) {\n if (mode !== 'library' || !isJpegRepresentationError(e)) throw e;\n result = await picker.launchImageLibraryAsync({ ...options, base64: false });\n }\n\n if (result.canceled) return;\n\n const picked = result.assets ?? [];\n const mapped: MessageAttachment[] = [];\n for (let i = 0; i < picked.length; i += 1) {\n const next = await assetToAttachment(picked[i] as PickedAsset, i);\n if (next) mapped.push(next);\n }\n\n if (mapped.length === 0) {\n setError('That image was too large to attach. Try a smaller one.');\n return;\n }\n setPending((prev) => [...prev, ...mapped].slice(0, MAX_FILES));\n } catch (e) {\n setError(attachErrorMessage(e));\n } finally {\n setBusyKind(null);\n }\n }, []);\n\n const pickFromLibrary = useCallback(() => run('library'), [run]);\n const captureFromCamera = useCallback(() => run('camera'), [run]);\n const pickDocuments = useCallback(async () => {\n setError(null);\n try {\n const picker = loadDocumentPicker();\n setBusyKind('documents');\n const result = await picker.getDocumentAsync({\n type: DOCUMENT_MIME_TYPES,\n multiple: true,\n copyToCacheDirectory: true,\n });\n const assets = normalizeDocumentAssets(result as Parameters<typeof normalizeDocumentAssets>[0]);\n if (assets.length === 0) return;\n\n const mapped: MessageAttachment[] = [];\n for (let i = 0; i < assets.length; i += 1) {\n const next = await documentToAttachment(assets[i], i);\n if (next) mapped.push(next);\n }\n if (mapped.length === 0) {\n setError('That file was too large to attach. Try a smaller one.');\n return;\n }\n setPending((prev) => [...prev, ...mapped].slice(0, MAX_FILES));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not attach that file.');\n } finally {\n setBusyKind(null);\n }\n }, []);\n const remove = useCallback((id: string) => setPending((prev) => prev.filter((a) => a.id !== id)), []);\n const clear = useCallback(() => setPending([]), []);\n const forSend = useCallback(() => (pending.length > 0 ? pending.map((a) => ({ ...a })) : undefined), [pending]);\n const busy = busyKind !== null;\n\n return useMemo(\n () => ({\n pending,\n busy,\n busyKind,\n error,\n pickFromLibrary,\n captureFromCamera,\n pickDocuments,\n remove,\n clear,\n forSend,\n }),\n [pending, busy, busyKind, error, pickFromLibrary, captureFromCamera, pickDocuments, remove, clear, forSend],\n );\n}\n\nexport const IMAGE_ATTACHMENT_LIMITS = { MAX_EDGE, MAX_BYTES, PICKER_QUALITY } as const;\n"],"names":["_a","_b","_c"],"mappings":"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"}
1
+ {"version":3,"file":"useImageAttachments.js","sources":["../../../src/features/attachments/useImageAttachments.ts"],"sourcesContent":["/**\n * Image attachments for the native composer.\n *\n * The transport was already there — `useChatStream.sendMessage(content,\n * attachments)` carries `MessageAttachment[]` and the gateway media lane is\n * covered by `aiwork/e2e-tests/gateway-media-upload.spec.ts`. What was missing\n * was any way to produce one on a phone: the composer shipped with\n * `image`/`camera`/`attach` hard-disabled, and nothing in the mobile package\n * touched `expo-image-picker` even though it is a dependency and the photo and\n * camera permission strings are already declared in `app.json`.\n *\n * Payload size is the whole game here. A modern iPhone photo is ~3-5 MB and\n * ~4000px wide; base64 inflates that by a third, and it travels inline on the\n * chat message. Camera captures can be JPEG-compressed via `quality`. Library\n * picks must NOT pass `quality`: that asks iOS for `public.jpeg` and fails on\n * HEIC / iCloud photos with \"Cannot load representation of type public.jpeg\".\n * The canvas picker already uses `Current` for that reason.\n */\nimport { useCallback, useMemo, useState } from 'react';\nimport type { MessageAttachment } from '../../hooks/useChatStream';\n\n/** Longest edge, in px, we send. Plenty for a model to read; ~10x smaller than source. */\nconst MAX_EDGE = 1280;\n/** JPEG quality handed to the picker (0-1). */\nconst PICKER_QUALITY = 0.7;\n/** Refuse anything above this after re-encoding — a send is better failed than hung. */\nconst MAX_BYTES = 4 * 1024 * 1024;\n/** Nothing sensible is this small; treat as a failed decode. */\nconst MIN_BYTES = 64;\n\nexport type AttachmentBusyKind = 'camera' | 'library' | 'documents' | null;\n\nexport interface ImageAttachmentsApi {\n /** Attachments staged for the next send. */\n pending: MessageAttachment[];\n /** True while any picker/encode is in flight. */\n busy: boolean;\n /** Which toolbar control is waiting — only that one should spin. */\n busyKind: AttachmentBusyKind;\n /** Human-readable failure for the composer's error banner, or null. */\n error: string | null;\n pickFromLibrary: () => Promise<void>;\n captureFromCamera: () => Promise<void>;\n pickDocuments: () => Promise<void>;\n remove: (id: string) => void;\n clear: () => void;\n /** Pass to `sendMessage` — undefined when empty, which is what it expects. */\n forSend: () => MessageAttachment[] | undefined;\n}\n\ntype PickedAsset = {\n uri?: string | null;\n base64?: string | null;\n mimeType?: string | null;\n fileName?: string | null;\n fileSize?: number | null;\n width?: number | null;\n height?: number | null;\n};\n\n/** Base64 decodes to 3 bytes per 4 chars, minus `=` padding. */\nfunction base64Bytes(base64: string): number {\n const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;\n return Math.floor((base64.length * 3) / 4) - padding;\n}\n\nfunction isJpegRepresentationError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err ?? '');\n return /public\\.jpeg|Failed to read picked image/i.test(msg);\n}\n\nfunction attachErrorMessage(err: unknown): string {\n if (isJpegRepresentationError(err)) {\n return 'Could not read that photo. Try another one, or save it to Recents first.';\n }\n return err instanceof Error ? err.message : 'Could not attach that image.';\n}\n\nfunction guessImageMime(asset: PickedAsset): string {\n const mime = asset.mimeType?.trim();\n if (mime) return mime;\n const name = `${asset.fileName ?? ''} ${asset.uri ?? ''}`;\n if (/\\.png(\\?|$)/i.test(name)) return 'image/png';\n if (/\\.gif(\\?|$)/i.test(name)) return 'image/gif';\n if (/\\.webp(\\?|$)/i.test(name)) return 'image/webp';\n if (/\\.(heic|heif)(\\?|$)/i.test(name)) return 'image/heic';\n return 'image/jpeg';\n}\n\nasync function readUriBase64(uri: string): Promise<string | null> {\n const fs = loadFileSystem() as {\n readAsStringAsync?: (uri: string, options?: { encoding?: string }) => Promise<string>;\n EncodingType?: { Base64?: string };\n };\n if (typeof fs.readAsStringAsync !== 'function') return null;\n const encoding = fs.EncodingType?.Base64 ?? 'base64';\n const base64 = (await fs.readAsStringAsync(uri, { encoding })).trim();\n return base64 || null;\n}\n\nasync function assetToAttachment(asset: PickedAsset, index: number): Promise<MessageAttachment | null> {\n let base64 = asset.base64?.trim() || '';\n if (!base64 && asset.uri?.trim()) {\n base64 = (await readUriBase64(asset.uri.trim())) || '';\n }\n if (!base64) return null;\n const size = base64Bytes(base64);\n if (size < MIN_BYTES || size > MAX_BYTES) return null;\n const mimeType = guessImageMime(asset);\n const name = asset.fileName?.trim() || `image-${Date.now()}-${index}.jpg`;\n return {\n id: `img-${Date.now()}-${index}`,\n name,\n type: 'file',\n mimeType,\n dataUrl: `data:${mimeType};base64,${base64}`,\n url: asset.uri?.trim() || undefined,\n size,\n };\n}\n\ntype PickedDocument = {\n uri?: string | null;\n name?: string | null;\n mimeType?: string | null;\n size?: number | null;\n};\n\n/**\n * Lazily required so a unit test or a non-Expo bundle importing this hook does\n * not hard-fail on the native module.\n */\nfunction loadPicker() {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-image-picker') as typeof import('expo-image-picker');\n}\n\nfunction loadDocumentPicker(): {\n getDocumentAsync: (options: {\n type?: string | string[];\n multiple?: boolean;\n copyToCacheDirectory?: boolean;\n }) => Promise<{\n canceled?: boolean;\n type?: string;\n uri?: string;\n name?: string;\n mimeType?: string;\n size?: number;\n assets?: PickedDocument[];\n }>;\n} {\n // Staging/dev-client binaries only include modules that are dependencies +\n // autolinked at EAS/prebuild time. Expo Go already ships ExpoDocumentPicker,\n // which is why paperclip works locally without that native rebuild.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n const core = require('expo-modules-core') as {\n requireOptionalNativeModule?: (name: string) => { getDocumentAsync?: Function } | null;\n };\n const native = core.requireOptionalNativeModule?.('ExpoDocumentPicker');\n if (native?.getDocumentAsync) {\n return {\n getDocumentAsync: (options) =>\n native.getDocumentAsync({\n type: Array.isArray(options.type) ? options.type : [options.type ?? '*/*'],\n multiple: options.multiple ?? false,\n copyToCacheDirectory: options.copyToCacheDirectory ?? true,\n }) as Promise<{\n canceled?: boolean;\n type?: string;\n uri?: string;\n name?: string;\n mimeType?: string;\n size?: number;\n assets?: PickedDocument[];\n }>,\n };\n }\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-document-picker') as typeof import('expo-document-picker');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err ?? '');\n if (/Cannot find native module|ExpoDocumentPicker|Cannot find module/i.test(msg)) {\n throw new Error(\n 'File picker is not included in this app build. Use Photos, or install a staging build that includes expo-document-picker.',\n );\n }\n throw err;\n }\n}\n\nfunction loadFileSystem() {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-file-system') as typeof import('expo-file-system');\n}\n\n/** Web `ACCEPTED_FILE_TYPES` — docs + images. */\nconst DOCUMENT_MIME_TYPES = [\n 'application/pdf',\n 'text/plain',\n 'text/markdown',\n 'text/csv',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'image/*',\n];\nconst MAX_FILES = 6;\nconst MAX_DOCUMENT_BYTES = 15 * 1024 * 1024;\n\nfunction normalizeDocumentAssets(result: {\n canceled?: boolean;\n type?: string;\n uri?: string;\n name?: string;\n mimeType?: string;\n size?: number;\n assets?: PickedDocument[];\n}): PickedDocument[] {\n if (!result || result.canceled || result.type === 'cancel') return [];\n if (Array.isArray(result.assets) && result.assets.length > 0) return result.assets;\n if (result.uri) {\n return [{ uri: result.uri, name: result.name, mimeType: result.mimeType, size: result.size }];\n }\n return [];\n}\n\nasync function documentToAttachment(asset: PickedDocument, index: number): Promise<MessageAttachment | null> {\n const uri = asset.uri?.trim();\n if (!uri) return null;\n if (typeof asset.size === 'number' && asset.size > MAX_DOCUMENT_BYTES) return null;\n\n const fs = loadFileSystem() as {\n readAsStringAsync?: (uri: string, options?: { encoding?: string }) => Promise<string>;\n EncodingType?: { Base64?: string };\n };\n const encoding = fs.EncodingType?.Base64 ?? 'base64';\n if (typeof fs.readAsStringAsync !== 'function') return null;\n const base64 = (await fs.readAsStringAsync(uri, { encoding })).trim();\n if (!base64) return null;\n const size = typeof asset.size === 'number' && asset.size > 0 ? asset.size : base64Bytes(base64);\n if (size < MIN_BYTES || size > MAX_DOCUMENT_BYTES) return null;\n const mimeType = asset.mimeType?.trim() || 'application/octet-stream';\n const name = asset.name?.trim() || `file-${Date.now()}-${index}`;\n return {\n id: `file-${Date.now()}-${index}`,\n name,\n type: 'file',\n mimeType,\n dataUrl: `data:${mimeType};base64,${base64}`,\n url: uri,\n size,\n };\n}\n\nexport function useImageAttachments(): ImageAttachmentsApi {\n const [pending, setPending] = useState<MessageAttachment[]>([]);\n const [busyKind, setBusyKind] = useState<AttachmentBusyKind>(null);\n const [error, setError] = useState<string | null>(null);\n\n const run = useCallback(async (mode: 'library' | 'camera') => {\n setError(null);\n try {\n const picker = loadPicker();\n\n const permission =\n mode === 'camera'\n ? await picker.requestCameraPermissionsAsync()\n : await picker.requestMediaLibraryPermissionsAsync();\n if (!permission.granted) {\n setError(\n mode === 'camera'\n ? 'Camera access is off for Yantra. Turn it on in Settings to attach a photo.'\n : 'Photo access is off for Yantra. Turn it on in Settings to attach an image.',\n );\n return;\n }\n\n // Spin only the control that was tapped, and only after permission\n // so the iOS prompt does not turn camera+gallery+attach into loaders.\n setBusyKind(mode);\n const representation =\n picker.UIImagePickerPreferredAssetRepresentationMode?.Current ?? ('current' as never);\n const options: import('expo-image-picker').ImagePickerOptions = {\n base64: true,\n allowsMultipleSelection: mode === 'library',\n mediaTypes: ['images'],\n selectionLimit: 4,\n exif: false,\n // Library: never pass `quality` — that requests public.jpeg and fails\n // on HEIC / iCloud (see pickLocalMedia). Camera JPEGs can compress.\n ...(mode === 'camera' ? { quality: PICKER_QUALITY } : {}),\n ...(mode === 'library' ? { preferredAssetRepresentationMode: representation } : {}),\n };\n\n let result: import('expo-image-picker').ImagePickerResult;\n try {\n result =\n mode === 'camera'\n ? await picker.launchCameraAsync({ ...options, allowsMultipleSelection: false })\n : await picker.launchImageLibraryAsync(options);\n } catch (e) {\n if (mode !== 'library' || !isJpegRepresentationError(e)) throw e;\n result = await picker.launchImageLibraryAsync({ ...options, base64: false });\n }\n\n if (result.canceled) return;\n\n const picked = result.assets ?? [];\n const mapped: MessageAttachment[] = [];\n for (let i = 0; i < picked.length; i += 1) {\n const next = await assetToAttachment(picked[i] as PickedAsset, i);\n if (next) mapped.push(next);\n }\n\n if (mapped.length === 0) {\n setError('That image was too large to attach. Try a smaller one.');\n return;\n }\n setPending((prev) => [...prev, ...mapped].slice(0, MAX_FILES));\n } catch (e) {\n setError(attachErrorMessage(e));\n } finally {\n setBusyKind(null);\n }\n }, []);\n\n const pickFromLibrary = useCallback(() => run('library'), [run]);\n const captureFromCamera = useCallback(() => run('camera'), [run]);\n const pickDocuments = useCallback(async () => {\n setError(null);\n try {\n const picker = loadDocumentPicker();\n setBusyKind('documents');\n const result = await picker.getDocumentAsync({\n type: DOCUMENT_MIME_TYPES,\n multiple: true,\n copyToCacheDirectory: true,\n });\n const assets = normalizeDocumentAssets(result as Parameters<typeof normalizeDocumentAssets>[0]);\n if (assets.length === 0) return;\n\n const mapped: MessageAttachment[] = [];\n for (let i = 0; i < assets.length; i += 1) {\n const next = await documentToAttachment(assets[i], i);\n if (next) mapped.push(next);\n }\n if (mapped.length === 0) {\n setError('That file was too large to attach. Try a smaller one.');\n return;\n }\n setPending((prev) => [...prev, ...mapped].slice(0, MAX_FILES));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not attach that file.');\n } finally {\n setBusyKind(null);\n }\n }, []);\n const remove = useCallback((id: string) => setPending((prev) => prev.filter((a) => a.id !== id)), []);\n const clear = useCallback(() => setPending([]), []);\n const forSend = useCallback(() => (pending.length > 0 ? pending.map((a) => ({ ...a })) : undefined), [pending]);\n const busy = busyKind !== null;\n\n return useMemo(\n () => ({\n pending,\n busy,\n busyKind,\n error,\n pickFromLibrary,\n captureFromCamera,\n pickDocuments,\n remove,\n clear,\n forSend,\n }),\n [pending, busy, busyKind, error, pickFromLibrary, captureFromCamera, pickDocuments, remove, clear, forSend],\n );\n}\n\nexport const IMAGE_ATTACHMENT_LIMITS = { MAX_EDGE, MAX_BYTES, PICKER_QUALITY } as const;\n"],"names":["_a"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import AsyncStorage from'@react-native-async-storage/async-storage';import {getCachedCanvasBoards,setCachedCanvasBoards}from'./surfaceIndex.js';const SNAPSHOT_KEY = "yantra.canvas-boards.snapshot.v1";
2
+ let lastWritten = "";
3
+ function isBoardSummary(value) {
4
+ if (!value || typeof value !== "object") return false;
5
+ const row = value;
6
+ return typeof row.channelId === "string" && row.channelId.length > 0 && Boolean(row.board);
7
+ }
8
+ function parseCanvasBoardsSnapshot(raw) {
9
+ if (!raw) return null;
10
+ try {
11
+ const parsed = JSON.parse(raw);
12
+ if (!(parsed == null ? void 0 : parsed.url) || !Array.isArray(parsed.boards)) return null;
13
+ const boards = parsed.boards.filter(isBoardSummary);
14
+ return {
15
+ url: parsed.url,
16
+ boards
17
+ };
18
+ } catch (e) {
19
+ return null;
20
+ }
21
+ }
22
+ async function hydrateCanvasBoardsSnapshot(indexUrl) {
23
+ const trimmed = (indexUrl || "").trim();
24
+ if (!trimmed) return null;
25
+ const memory = getCachedCanvasBoards(trimmed);
26
+ if (memory) return memory;
27
+ try {
28
+ const raw = await AsyncStorage.getItem(SNAPSHOT_KEY);
29
+ const parsed = parseCanvasBoardsSnapshot(raw);
30
+ if (!parsed || parsed.url !== trimmed) return null;
31
+ setCachedCanvasBoards(trimmed, parsed.boards);
32
+ lastWritten = raw != null ? raw : lastWritten;
33
+ return parsed.boards;
34
+ } catch (e) {
35
+ return null;
36
+ }
37
+ }export{hydrateCanvasBoardsSnapshot,parseCanvasBoardsSnapshot};//# sourceMappingURL=canvasBoardsSnapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasBoardsSnapshot.js","sources":["../../../src/features/canvas/canvasBoardsSnapshot.ts"],"sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage';\nimport { getCachedCanvasBoards, setCachedCanvasBoards, type CanvasBoardSummary } from './surfaceIndex';\n\nconst SNAPSHOT_KEY = 'yantra.canvas-boards.snapshot.v1';\n\ntype StoredSnapshot = {\n url: string;\n boards: CanvasBoardSummary[];\n};\n\nlet lastWritten = '';\n\nfunction isBoardSummary(value: unknown): value is CanvasBoardSummary {\n if (!value || typeof value !== 'object') return false;\n const row = value as CanvasBoardSummary;\n return typeof row.channelId === 'string' && row.channelId.length > 0 && Boolean(row.board);\n}\n\nexport function parseCanvasBoardsSnapshot(raw: string | null | undefined): StoredSnapshot | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as StoredSnapshot;\n if (!parsed?.url || !Array.isArray(parsed.boards)) return null;\n const boards = parsed.boards.filter(isBoardSummary);\n return { url: parsed.url, boards };\n } catch {\n return null;\n }\n}\n\nexport async function hydrateCanvasBoardsSnapshot(indexUrl: string): Promise<CanvasBoardSummary[] | null> {\n const trimmed = (indexUrl || '').trim();\n if (!trimmed) return null;\n const memory = getCachedCanvasBoards(trimmed);\n if (memory) return memory;\n try {\n const raw = await AsyncStorage.getItem(SNAPSHOT_KEY);\n const parsed = parseCanvasBoardsSnapshot(raw);\n if (!parsed || parsed.url !== trimmed) return null;\n setCachedCanvasBoards(trimmed, parsed.boards);\n lastWritten = raw ?? lastWritten;\n return parsed.boards;\n } catch {\n return null;\n }\n}\n\nexport function persistCanvasBoardsSnapshot(indexUrl: string, boards: CanvasBoardSummary[]): void {\n const trimmed = (indexUrl || '').trim();\n if (!trimmed) return;\n setCachedCanvasBoards(trimmed, boards);\n const raw = JSON.stringify({ url: trimmed, boards } satisfies StoredSnapshot);\n if (raw === lastWritten) return;\n lastWritten = raw;\n void AsyncStorage.setItem(SNAPSHOT_KEY, raw).catch(() => {\n /* disk write is best-effort */\n });\n}\n"],"names":[],"mappings":"gJAEA,MAAM,YAAe,GAAA,kCAAA;AAKrB,IAAI,WAAc,GAAA,EAAA;AAClB,SAAS,eAAe,KAA6C,EAAA;AACnE,EAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,UAAiB,OAAA,KAAA;AAChD,EAAA,MAAM,GAAM,GAAA,KAAA;AACZ,EAAO,OAAA,OAAO,GAAI,CAAA,SAAA,KAAc,QAAY,IAAA,GAAA,CAAI,UAAU,MAAS,GAAA,CAAA,IAAK,OAAQ,CAAA,GAAA,CAAI,KAAK,CAAA;AAC3F;AACO,SAAS,0BAA0B,GAAuD,EAAA;AAC/F,EAAI,IAAA,CAAC,KAAY,OAAA,IAAA;AACjB,EAAI,IAAA;AACF,IAAM,MAAA,MAAA,GAAS,IAAK,CAAA,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAI,IAAA,EAAC,iCAAQ,GAAO,CAAA,IAAA,CAAC,MAAM,OAAQ,CAAA,MAAA,CAAO,MAAM,CAAA,EAAU,OAAA,IAAA;AAC1D,IAAA,MAAM,MAAS,GAAA,MAAA,CAAO,MAAO,CAAA,MAAA,CAAO,cAAc,CAAA;AAClD,IAAO,OAAA;AAAA,MACL,KAAK,MAAO,CAAA,GAAA;AAAA,MACZ;AAAA,KACF;AAAA,GACM,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,IAAA;AAAA;AAEX;AACA,eAAsB,4BAA4B,QAAwD,EAAA;AACxG,EAAM,MAAA,OAAA,GAAA,CAAW,QAAY,IAAA,EAAA,EAAI,IAAK,EAAA;AACtC,EAAI,IAAA,CAAC,SAAgB,OAAA,IAAA;AACrB,EAAM,MAAA,MAAA,GAAS,sBAAsB,OAAO,CAAA;AAC5C,EAAA,IAAI,QAAe,OAAA,MAAA;AACnB,EAAI,IAAA;AACF,IAAA,MAAM,GAAM,GAAA,MAAM,YAAa,CAAA,OAAA,CAAQ,YAAY,CAAA;AACnD,IAAM,MAAA,MAAA,GAAS,0BAA0B,GAAG,CAAA;AAC5C,IAAA,IAAI,CAAC,MAAA,IAAU,MAAO,CAAA,GAAA,KAAQ,SAAgB,OAAA,IAAA;AAC9C,IAAsB,qBAAA,CAAA,OAAA,EAAS,OAAO,MAAM,CAAA;AAC5C,IAAA,WAAA,GAAc,GAAO,IAAA,IAAA,GAAA,GAAA,GAAA,WAAA;AACrB,IAAA,OAAO,MAAO,CAAA,MAAA;AAAA,GACR,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,IAAA;AAAA;AAEX"}
@@ -6,9 +6,17 @@ function getNativeCanvasViewer() {
6
6
  return registered;
7
7
  }
8
8
  let experience = null;
9
+ let experiencePreload = null;
9
10
  function registerNativeCanvasExperience(component) {
10
11
  experience = component;
11
12
  }
12
13
  function getNativeCanvasExperience() {
13
14
  return experience;
14
- }export{getNativeCanvasExperience,getNativeCanvasViewer,registerNativeCanvasExperience,registerNativeCanvasViewer};//# sourceMappingURL=nativeViewerRegistry.js.map
15
+ }
16
+ function registerCanvasExperiencePreload(fn) {
17
+ experiencePreload = fn;
18
+ }
19
+ function preloadNativeCanvasExperience() {
20
+ var _a;
21
+ return (_a = experiencePreload == null ? void 0 : experiencePreload()) != null ? _a : Promise.resolve();
22
+ }export{getNativeCanvasExperience,getNativeCanvasViewer,preloadNativeCanvasExperience,registerCanvasExperiencePreload,registerNativeCanvasExperience,registerNativeCanvasViewer};//# sourceMappingURL=nativeViewerRegistry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nativeViewerRegistry.js","sources":["../../../src/features/canvas/nativeViewerRegistry.ts"],"sourcesContent":["/**\n * Seam between the app shell and this package for the FEDERATED canvas viewer.\n *\n * The federated loader (`import('canvas_native/Viewer')`) must live in the app\n * (portable-devices/mobile): this package also builds through rollup for lib/,\n * and rollup must never see a bare `canvas_native/...` specifier. The screen,\n * however, lives HERE, where routes register. So the app injects its loader at\n * startup and the screen reads it back - the same inversion the surfaces use\n * for host services, scaled down to one component.\n */\nimport type { ComponentType } from 'react';\nimport type { NativeCanvasViewerProps } from './NativeCanvasViewer';\n\nlet registered: ComponentType<NativeCanvasViewerProps> | null = null;\n\nexport function registerNativeCanvasViewer(component: ComponentType<NativeCanvasViewerProps>): void {\n registered = component;\n}\n\n/** Null when the host app has not injected a loader (e.g. a build without federation). */\nexport function getNativeCanvasViewer(): ComponentType<NativeCanvasViewerProps> | null {\n return registered;\n}\n\n/**\n * The federated canvas EXPERIENCE (boards + viewer), registered by the app the\n * same way the viewer is. Kept as a separate seam so a host that only wants the\n * raw renderer - a preview, a test - is unaffected.\n */\nexport type CanvasExperienceComponent = ComponentType<{\n token?: string | null;\n indexUrl: string;\n channelId?: string;\n sessionError?: string | null;\n}>;\n\nlet experience: CanvasExperienceComponent | null = null;\n\nexport function registerNativeCanvasExperience(component: CanvasExperienceComponent): void {\n experience = component;\n}\n\nexport function getNativeCanvasExperience(): CanvasExperienceComponent | null {\n return experience;\n}\n"],"names":[],"mappings":"AAYA,IAAI,UAA4D,GAAA,IAAA;AACzD,SAAS,2BAA2B,SAAyD,EAAA;AAClG,EAAa,UAAA,GAAA,SAAA;AACf;AAGO,SAAS,qBAAuE,GAAA;AACrF,EAAO,OAAA,UAAA;AACT;AAaA,IAAI,UAA+C,GAAA,IAAA;AAC5C,SAAS,+BAA+B,SAA4C,EAAA;AACzF,EAAa,UAAA,GAAA,SAAA;AACf;AACO,SAAS,yBAA8D,GAAA;AAC5E,EAAO,OAAA,UAAA;AACT"}
1
+ {"version":3,"file":"nativeViewerRegistry.js","sources":["../../../src/features/canvas/nativeViewerRegistry.ts"],"sourcesContent":["/**\n * Seam between the app shell and this package for the FEDERATED canvas viewer.\n *\n * The federated loader (`import('canvas_native/Viewer')`) must live in the app\n * (portable-devices/mobile): this package also builds through rollup for lib/,\n * and rollup must never see a bare `canvas_native/...` specifier. The screen,\n * however, lives HERE, where routes register. So the app injects its loader at\n * startup and the screen reads it back - the same inversion the surfaces use\n * for host services, scaled down to one component.\n */\nimport type { ComponentType } from 'react';\nimport type { NativeCanvasViewerProps } from './NativeCanvasViewer';\n\nlet registered: ComponentType<NativeCanvasViewerProps> | null = null;\n\nexport function registerNativeCanvasViewer(component: ComponentType<NativeCanvasViewerProps>): void {\n registered = component;\n}\n\n/** Null when the host app has not injected a loader (e.g. a build without federation). */\nexport function getNativeCanvasViewer(): ComponentType<NativeCanvasViewerProps> | null {\n return registered;\n}\n\n/**\n * The federated canvas EXPERIENCE (boards + viewer), registered by the app the\n * same way the viewer is. Kept as a separate seam so a host that only wants the\n * raw renderer - a preview, a test - is unaffected.\n */\nexport type CanvasExperienceComponent = ComponentType<{\n token?: string | null;\n indexUrl: string;\n channelId?: string;\n sessionError?: string | null;\n}>;\n\nlet experience: CanvasExperienceComponent | null = null;\nlet experiencePreload: (() => Promise<void>) | null = null;\n\nexport function registerNativeCanvasExperience(component: CanvasExperienceComponent): void {\n experience = component;\n}\n\nexport function getNativeCanvasExperience(): CanvasExperienceComponent | null {\n return experience;\n}\n\n/**\n * Host registers the federated `import('canvas_native/Viewer')` so Home can\n * start the download before the user opens Canvas Native.\n */\nexport function registerCanvasExperiencePreload(fn: () => Promise<void>): void {\n experiencePreload = fn;\n}\n\nexport function preloadNativeCanvasExperience(): Promise<void> {\n return experiencePreload?.() ?? Promise.resolve();\n}\n"],"names":[],"mappings":"AAYA,IAAI,UAA4D,GAAA,IAAA;AACzD,SAAS,2BAA2B,SAAyD,EAAA;AAClG,EAAa,UAAA,GAAA,SAAA;AACf;AAGO,SAAS,qBAAuE,GAAA;AACrF,EAAO,OAAA,UAAA;AACT;AAaA,IAAI,UAA+C,GAAA,IAAA;AACnD,IAAI,iBAAkD,GAAA,IAAA;AAC/C,SAAS,+BAA+B,SAA4C,EAAA;AACzF,EAAa,UAAA,GAAA,SAAA;AACf;AACO,SAAS,yBAA8D,GAAA;AAC5E,EAAO,OAAA,UAAA;AACT;AAMO,SAAS,gCAAgC,EAA+B,EAAA;AAC7E,EAAoB,iBAAA,GAAA,EAAA;AACtB;AACO,SAAS,6BAA+C,GAAA;AAjD/D,EAAA,IAAA,EAAA;AAkDE,EAAO,OAAA,CAAA,EAAA,GAAA,iBAAA,IAAA,IAAA,GAAA,MAAA,GAAA,iBAAA,EAAA,KAAA,IAAA,GAAA,EAAA,GAAyB,QAAQ,OAAQ,EAAA;AAClD"}
@@ -74,6 +74,13 @@ function ChatTranscript({
74
74
  const streamProse = displayAskUserAssistantText(rawStream, streamAskAnswered).trim();
75
75
  const streamTools = parseToolActivity(stripAskUser(rawStream), true);
76
76
  if (!streamProse && streamTools.steps.length === 0) return history;
77
+ if ((last == null ? void 0 : last.role) === "assistant") {
78
+ const lastText = last.content.trim();
79
+ const streamText = rawStream.trim();
80
+ if (lastText === streamText || lastText.startsWith(streamText) || streamText.startsWith(lastText)) {
81
+ return history;
82
+ }
83
+ }
77
84
  return [...history, {
78
85
  id: "__streaming__",
79
86
  role: "assistant",