@adminide-stack/yantra-mobile 12.0.52-alpha.0 → 12.0.52-alpha.1
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/lib/components/KeyboardComposerDock.js +4 -3
- package/lib/components/KeyboardComposerDock.js.map +1 -1
- package/lib/features/attachments/buildGatewayMedia.js +22 -15
- package/lib/features/attachments/buildGatewayMedia.js.map +1 -1
- package/lib/features/attachments/uploadChatAttachments.js +6 -4
- package/lib/features/attachments/uploadChatAttachments.js.map +1 -1
- package/lib/features/attachments/useImageAttachments.js +63 -17
- package/lib/features/attachments/useImageAttachments.js.map +1 -1
- package/lib/features/chat/AskUserCard.js +244 -121
- package/lib/features/chat/AskUserCard.js.map +1 -1
- package/lib/features/chat/ChatTranscript.js +206 -130
- package/lib/features/chat/ChatTranscript.js.map +1 -1
- package/lib/features/chat/askUser.js +111 -15
- package/lib/features/chat/askUser.js.map +1 -1
- package/lib/features/chat/streamingMarkdown.js +7 -0
- package/lib/features/chat/streamingMarkdown.js.map +1 -0
- package/lib/features/chat/transcriptGroups.js +29 -0
- package/lib/features/chat/transcriptGroups.js.map +1 -0
- package/lib/hooks/mintSurfaceSystemToken.js +28 -14
- package/lib/hooks/mintSurfaceSystemToken.js.map +1 -1
- package/lib/hooks/systemTokenJwt.js +75 -0
- package/lib/hooks/systemTokenJwt.js.map +1 -0
- package/lib/hooks/useCdecliAutoConnect.js +116 -138
- package/lib/hooks/useCdecliAutoConnect.js.map +1 -1
- package/lib/hooks/useChatApi.js +43 -17
- package/lib/hooks/useChatApi.js.map +1 -1
- package/lib/hooks/useChatStream.js +61 -13
- package/lib/hooks/useChatStream.js.map +1 -1
- package/lib/hooks/useObtainSystemToken.js +121 -36
- package/lib/hooks/useObtainSystemToken.js.map +1 -1
- package/lib/screens/Chat/index.js +25 -6
- package/lib/screens/Chat/index.js.map +1 -1
- package/lib/screens/Home/HomeScreen.js +21 -6
- package/lib/screens/Home/HomeScreen.js.map +1 -1
- package/lib/screens/Home/components/ChatHistoryLanding.js +46 -16
- package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -1
- package/package.json +2 -2
|
@@ -20,13 +20,14 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
|
20
20
|
const COMPOSER_INPUT_NATIVE_ID = "yantra-composer";
|
|
21
21
|
function KeyboardAwareScreen({
|
|
22
22
|
children,
|
|
23
|
-
style
|
|
23
|
+
style,
|
|
24
|
+
testID
|
|
24
25
|
}) {
|
|
25
26
|
if (isKeyboardControllerAvailable()) {
|
|
26
27
|
const KeyboardGestureArea = require("react-native-keyboard-controller").KeyboardGestureArea;
|
|
27
|
-
return /* @__PURE__ */ jsx(KeyboardGestureArea, { style: [styles.flex, style], interpolator: "ios", showOnSwipeUp: true, enableSwipeToDismiss: true, textInputNativeID: COMPOSER_INPUT_NATIVE_ID, children });
|
|
28
|
+
return /* @__PURE__ */ jsx(KeyboardGestureArea, { style: [styles.flex, style], interpolator: "ios", showOnSwipeUp: true, enableSwipeToDismiss: true, textInputNativeID: COMPOSER_INPUT_NATIVE_ID, testID, children });
|
|
28
29
|
}
|
|
29
|
-
return /* @__PURE__ */ jsx(View, { style: [styles.flex, style], children });
|
|
30
|
+
return /* @__PURE__ */ jsx(View, { style: [styles.flex, style], testID, children });
|
|
30
31
|
}
|
|
31
32
|
function configureKeyboardLayout(duration) {
|
|
32
33
|
if (Platform.OS !== "ios") return;
|
|
@@ -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({
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import {uploadChatAttachments}from'./uploadChatAttachments.js';var __defProp = Object.defineProperty;
|
|
1
|
+
import {isLoopbackOrPrivateUrl}from'../../hooks/systemTokenJwt.js';import {uploadChatAttachments}from'./uploadChatAttachments.js';var __defProp = Object.defineProperty;
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
@@ -49,6 +49,10 @@ function buildGatewayMedia(attachments, opts = {
|
|
|
49
49
|
}).filter((m) => m.url.startsWith("data:"));
|
|
50
50
|
}
|
|
51
51
|
const GATEWAY_MEDIA_INLINE_MAX_B64 = 14e6;
|
|
52
|
+
function isAgentFetchableMediaUrl(url) {
|
|
53
|
+
if (!url || !/^https:\/\//i.test(url) || url.startsWith("data:")) return false;
|
|
54
|
+
return !isLoopbackOrPrivateUrl(url);
|
|
55
|
+
}
|
|
52
56
|
async function buildGatewayMediaPreferUrls(attachments, createFileUploadLinks, createFileDownloadLinks) {
|
|
53
57
|
if (!(attachments == null ? void 0 : attachments.length)) return [];
|
|
54
58
|
let uploaded;
|
|
@@ -81,27 +85,30 @@ async function buildGatewayMediaPreferUrls(attachments, createFileUploadLinks, c
|
|
|
81
85
|
const base64Payload = dataUrl.includes("base64,") ? dataUrl.split("base64,")[1] : "";
|
|
82
86
|
if (s3Url) {
|
|
83
87
|
const presigned = presignedByBareUrl.get(s3Url);
|
|
84
|
-
|
|
88
|
+
const fetchUrl = isAgentFetchableMediaUrl(presigned) ? presigned : isAgentFetchableMediaUrl(s3Url) ? s3Url : void 0;
|
|
89
|
+
if (fetchUrl && isAgentFetchableMediaUrl(presigned)) {
|
|
85
90
|
return __spreadProps(__spreadValues({
|
|
86
91
|
type,
|
|
87
|
-
url:
|
|
92
|
+
url: fetchUrl
|
|
88
93
|
}, mimeType ? {
|
|
89
94
|
mimeType
|
|
90
95
|
} : {}), {
|
|
91
96
|
filename: a.name
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
mimeType
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
if (fetchUrl) {
|
|
100
|
+
const inline = base64Payload && base64Payload.length <= GATEWAY_MEDIA_INLINE_MAX_B64 ? {
|
|
101
|
+
data: base64Payload
|
|
102
|
+
} : {};
|
|
103
|
+
return __spreadProps(__spreadValues(__spreadValues({
|
|
104
|
+
type,
|
|
105
|
+
url: fetchUrl
|
|
106
|
+
}, inline), mimeType ? {
|
|
107
|
+
mimeType
|
|
108
|
+
} : {}), {
|
|
109
|
+
filename: a.name
|
|
110
|
+
});
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
if (!dataUrl.startsWith("data:")) return null;
|
|
107
114
|
return __spreadProps(__spreadValues(__spreadValues({
|
|
@@ -115,4 +122,4 @@ async function buildGatewayMediaPreferUrls(attachments, createFileUploadLinks, c
|
|
|
115
122
|
filename: a.name
|
|
116
123
|
});
|
|
117
124
|
}).filter((m) => m !== null);
|
|
118
|
-
}export{buildGatewayMedia,buildGatewayMediaPreferUrls};//# sourceMappingURL=buildGatewayMedia.js.map
|
|
125
|
+
}export{buildGatewayMedia,buildGatewayMediaPreferUrls,isAgentFetchableMediaUrl};//# sourceMappingURL=buildGatewayMedia.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildGatewayMedia.js","sources":["../../../src/features/attachments/buildGatewayMedia.ts"],"sourcesContent":["import type { MessageAttachment } from '../../hooks/useChatStream';\nimport { uploadChatAttachments } from './uploadChatAttachments';\n\nexport type GatewayMediaItem = {\n type: string;\n url: string;\n data?: string;\n mimeType?: string;\n filename?: string;\n};\n\nfunction isImageMedia(attachment: MessageAttachment): boolean {\n const dataUrl = attachment.dataUrl ?? '';\n return (\n dataUrl.startsWith('data:image/') ||\n Boolean(attachment.mimeType?.startsWith('image/')) ||\n attachment.type === 'screenshot'\n );\n}\n\nfunction mimeOf(attachment: MessageAttachment): string {\n const dataUrl = attachment.dataUrl ?? '';\n return attachment.mimeType || (dataUrl.startsWith('data:') ? dataUrl.slice(5).split(/[;,]/)[0] : '');\n}\n\n/**\n * Browser-parity gateway `media` payload (`ChatLayout.buildGatewayMedia`).\n * Images → IMAGE, everything else → DOCUMENT. Only `data:` URLs are forwarded.\n */\nexport function buildGatewayMedia(\n attachments: MessageAttachment[] | undefined,\n opts: { includeData?: boolean } = { includeData: true },\n): GatewayMediaItem[] {\n return (attachments ?? [])\n .map((a) => {\n const dataUrl = a.dataUrl ?? a.url ?? '';\n const isImage = isImageMedia(a) || dataUrl.startsWith('data:image/');\n const base64 = dataUrl.includes('base64,') ? dataUrl.split('base64,')[1] : '';\n const mimeType = mimeOf(a);\n return {\n type: isImage ? 'IMAGE' : 'DOCUMENT',\n url: dataUrl,\n ...(opts.includeData && base64 ? { data: base64 } : {}),\n ...(mimeType ? { mimeType } : {}),\n filename: a.name,\n };\n })\n .filter((m) => m.url.startsWith('data:'));\n}\n\n/** Inline-bytes cap matching web (~10 MB decoded). */\nconst GATEWAY_MEDIA_INLINE_MAX_B64 = 14_000_000;\n\n/**\n * Browser `buildGatewayMediaPreferUrls` (yantra-app#607): upload to S3, then\n * send presigned GET URLs so the GraphQL send is not a multi-MB base64 blob.\n * Falls back to inline `data:` when upload/presign fails.\n */\nexport async function buildGatewayMediaPreferUrls(\n attachments: MessageAttachment[] | undefined,\n createFileUploadLinks: (filenames: string[]) => Promise<string[]>,\n createFileDownloadLinks?: (urls: string[]) => Promise<string[]>,\n): Promise<GatewayMediaItem[]> {\n if (!attachments?.length) return [];\n\n let uploaded: Array<MessageAttachment & { objectUrl?: string }>;\n try {\n uploaded = await uploadChatAttachments(attachments, createFileUploadLinks);\n } catch (error) {\n console.warn('[mobile] S3 upload for gateway media failed — falling back to inline base64:', error);\n return buildGatewayMedia(attachments, { includeData: true });\n }\n\n const bareUrls = uploaded.map((a) => a.objectUrl).filter((u): u is string => Boolean(u));\n const presignedByBareUrl = new Map<string, string>();\n if (createFileDownloadLinks && bareUrls.length > 0) {\n try {\n const signed = await createFileDownloadLinks(bareUrls);\n if (signed.length === bareUrls.length) {\n bareUrls.forEach((u, i) => presignedByBareUrl.set(u, signed[i]));\n }\n } catch (error) {\n console.warn('[mobile] download-link mint failed — falling back to inline bytes on media:', error);\n }\n }\n\n return uploaded\n .map((a) => {\n const type = isImageMedia(a) ? 'IMAGE' : 'DOCUMENT';\n const mimeType = mimeOf(a);\n const s3Url = a.objectUrl;\n const dataUrl = a.dataUrl ?? '';\n const base64Payload = dataUrl.includes('base64,') ? dataUrl.split('base64,')[1] : '';\n if (s3Url) {\n const presigned = presignedByBareUrl.get(s3Url);\n if (presigned) {\n return { type, url:
|
|
1
|
+
{"version":3,"file":"buildGatewayMedia.js","sources":["../../../src/features/attachments/buildGatewayMedia.ts"],"sourcesContent":["import type { MessageAttachment } from '../../hooks/useChatStream';\nimport { isLoopbackOrPrivateUrl } from '../../hooks/systemTokenJwt';\nimport { uploadChatAttachments } from './uploadChatAttachments';\n\nexport type GatewayMediaItem = {\n type: string;\n url: string;\n data?: string;\n mimeType?: string;\n filename?: string;\n};\n\nfunction isImageMedia(attachment: MessageAttachment): boolean {\n const dataUrl = attachment.dataUrl ?? '';\n return (\n dataUrl.startsWith('data:image/') ||\n Boolean(attachment.mimeType?.startsWith('image/')) ||\n attachment.type === 'screenshot'\n );\n}\n\nfunction mimeOf(attachment: MessageAttachment): string {\n const dataUrl = attachment.dataUrl ?? '';\n return attachment.mimeType || (dataUrl.startsWith('data:') ? dataUrl.slice(5).split(/[;,]/)[0] : '');\n}\n\n/**\n * Browser-parity gateway `media` payload (`ChatLayout.buildGatewayMedia`).\n * Images → IMAGE, everything else → DOCUMENT. Only `data:` URLs are forwarded.\n */\nexport function buildGatewayMedia(\n attachments: MessageAttachment[] | undefined,\n opts: { includeData?: boolean } = { includeData: true },\n): GatewayMediaItem[] {\n return (attachments ?? [])\n .map((a) => {\n const dataUrl = a.dataUrl ?? a.url ?? '';\n const isImage = isImageMedia(a) || dataUrl.startsWith('data:image/');\n const base64 = dataUrl.includes('base64,') ? dataUrl.split('base64,')[1] : '';\n const mimeType = mimeOf(a);\n return {\n type: isImage ? 'IMAGE' : 'DOCUMENT',\n url: dataUrl,\n ...(opts.includeData && base64 ? { data: base64 } : {}),\n ...(mimeType ? { mimeType } : {}),\n filename: a.name,\n };\n })\n .filter((m) => m.url.startsWith('data:'));\n}\n\n/** Inline-bytes cap matching web (~10 MB decoded). */\nconst GATEWAY_MEDIA_INLINE_MAX_B64 = 14_000_000;\n\n/** Hosted cdecli-agent can GET this URL. Localhost/LAN/MinIO links hang the turn. */\nexport function isAgentFetchableMediaUrl(url: string | undefined): boolean {\n if (!url || !/^https:\\/\\//i.test(url) || url.startsWith('data:')) return false;\n return !isLoopbackOrPrivateUrl(url);\n}\n\n/**\n * Browser `buildGatewayMediaPreferUrls` (yantra-app#607): upload to S3, then\n * send presigned GET URLs so the GraphQL send is not a multi-MB base64 blob.\n * Falls back to inline `data:` when upload/presign fails.\n */\nexport async function buildGatewayMediaPreferUrls(\n attachments: MessageAttachment[] | undefined,\n createFileUploadLinks: (filenames: string[]) => Promise<string[]>,\n createFileDownloadLinks?: (urls: string[]) => Promise<string[]>,\n): Promise<GatewayMediaItem[]> {\n if (!attachments?.length) return [];\n\n let uploaded: Array<MessageAttachment & { objectUrl?: string }>;\n try {\n uploaded = await uploadChatAttachments(attachments, createFileUploadLinks);\n } catch (error) {\n console.warn('[mobile] S3 upload for gateway media failed — falling back to inline base64:', error);\n return buildGatewayMedia(attachments, { includeData: true });\n }\n\n const bareUrls = uploaded.map((a) => a.objectUrl).filter((u): u is string => Boolean(u));\n const presignedByBareUrl = new Map<string, string>();\n if (createFileDownloadLinks && bareUrls.length > 0) {\n try {\n const signed = await createFileDownloadLinks(bareUrls);\n if (signed.length === bareUrls.length) {\n bareUrls.forEach((u, i) => presignedByBareUrl.set(u, signed[i]));\n }\n } catch (error) {\n console.warn('[mobile] download-link mint failed — falling back to inline bytes on media:', error);\n }\n }\n\n return uploaded\n .map((a): GatewayMediaItem | null => {\n const type = isImageMedia(a) ? 'IMAGE' : 'DOCUMENT';\n const mimeType = mimeOf(a);\n const s3Url = a.objectUrl;\n const dataUrl = a.dataUrl ?? '';\n const base64Payload = dataUrl.includes('base64,') ? dataUrl.split('base64,')[1] : '';\n if (s3Url) {\n const presigned = presignedByBareUrl.get(s3Url);\n const fetchUrl = isAgentFetchableMediaUrl(presigned)\n ? presigned\n : isAgentFetchableMediaUrl(s3Url)\n ? s3Url\n : undefined;\n if (fetchUrl && isAgentFetchableMediaUrl(presigned)) {\n return { type, url: fetchUrl, ...(mimeType ? { mimeType } : {}), filename: a.name };\n }\n if (fetchUrl) {\n const inline =\n base64Payload && base64Payload.length <= GATEWAY_MEDIA_INLINE_MAX_B64\n ? { data: base64Payload }\n : {};\n return { type, url: fetchUrl, ...inline, ...(mimeType ? { mimeType } : {}), filename: a.name };\n }\n // Laptop-only object URL — do not hand it to the hosted agent.\n }\n if (!dataUrl.startsWith('data:')) return null;\n return {\n type,\n url: dataUrl,\n ...(base64Payload ? { data: base64Payload } : {}),\n ...(mimeType ? { mimeType } : {}),\n filename: a.name,\n };\n })\n .filter((m): m is GatewayMediaItem => m !== null);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -28,11 +28,13 @@ function loadFileSystem() {
|
|
|
28
28
|
}
|
|
29
29
|
async function localFileForUpload(attachment) {
|
|
30
30
|
var _a, _b, _c, _d;
|
|
31
|
-
const
|
|
32
|
-
if (url && /^(file:|content:)/i.test(url)) return url;
|
|
33
|
-
const dataUrl = (_b = attachment.dataUrl) != null ? _b : "";
|
|
31
|
+
const dataUrl = (_a = attachment.dataUrl) != null ? _a : "";
|
|
34
32
|
const base64 = dataUrl.includes("base64,") ? dataUrl.split("base64,")[1] : "";
|
|
35
|
-
if (!base64)
|
|
33
|
+
if (!base64) {
|
|
34
|
+
const url = (_b = attachment.url) == null ? void 0 : _b.trim();
|
|
35
|
+
if (url && /^(file:|content:)/i.test(url)) return url;
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
36
38
|
const fs = loadFileSystem();
|
|
37
39
|
const dir = fs.cacheDirectory;
|
|
38
40
|
if (!dir || typeof fs.writeAsStringAsync !== "function") return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadChatAttachments.js","sources":["../../../src/features/attachments/uploadChatAttachments.ts"],"sourcesContent":["import type { MessageAttachment } from '../../hooks/useChatStream';\n\nfunction getBaseUrlFromSignedUrl(signedUrl: string): string {\n return signedUrl.split('?')[0];\n}\n\nfunction isRemoteObjectUrl(url: string | undefined): boolean {\n return Boolean(url && /^https:\\/\\//i.test(url) && !url.startsWith('data:'));\n}\n\nfunction loadFileSystem(): {\n cacheDirectory?: string | null;\n writeAsStringAsync?: (uri: string, data: string, options?: { encoding?: string }) => Promise<void>;\n uploadAsync?: (\n url: string,\n fileUri: string,\n options?: { httpMethod?: string; headers?: Record<string, string>; uploadType?: number },\n ) => Promise<{ status?: number }>;\n EncodingType?: { Base64?: string };\n FileSystemUploadType?: { BINARY_CONTENT?: number };\n} {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-file-system') as ReturnType<typeof loadFileSystem>;\n}\n\nasync function localFileForUpload(attachment: MessageAttachment): Promise<string | null> {\n
|
|
1
|
+
{"version":3,"file":"uploadChatAttachments.js","sources":["../../../src/features/attachments/uploadChatAttachments.ts"],"sourcesContent":["import type { MessageAttachment } from '../../hooks/useChatStream';\n\nfunction getBaseUrlFromSignedUrl(signedUrl: string): string {\n return signedUrl.split('?')[0];\n}\n\nfunction isRemoteObjectUrl(url: string | undefined): boolean {\n return Boolean(url && /^https:\\/\\//i.test(url) && !url.startsWith('data:'));\n}\n\nfunction loadFileSystem(): {\n cacheDirectory?: string | null;\n writeAsStringAsync?: (uri: string, data: string, options?: { encoding?: string }) => Promise<void>;\n uploadAsync?: (\n url: string,\n fileUri: string,\n options?: { httpMethod?: string; headers?: Record<string, string>; uploadType?: number },\n ) => Promise<{ status?: number }>;\n EncodingType?: { Base64?: string };\n FileSystemUploadType?: { BINARY_CONTENT?: number };\n} {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('expo-file-system') as ReturnType<typeof loadFileSystem>;\n}\n\nasync function localFileForUpload(attachment: MessageAttachment): Promise<string | null> {\n // Prefer the encoded payload (JPEG/PNG dataUrl) over the picker `file:` URI.\n // On iOS that URI is often the original HEIC; uploading it makes the hosted\n // agent mkdir /tmp/imgview and stall instead of viewing the photo.\n const dataUrl = attachment.dataUrl ?? '';\n const base64 = dataUrl.includes('base64,') ? dataUrl.split('base64,')[1] : '';\n if (!base64) {\n const url = attachment.url?.trim();\n if (url && /^(file:|content:)/i.test(url)) return url;\n return null;\n }\n\n const fs = loadFileSystem();\n const dir = fs.cacheDirectory;\n if (!dir || typeof fs.writeAsStringAsync !== 'function') return null;\n const safeName = (attachment.name || 'upload').replace(/[^\\w.-]+/g, '_');\n const dest = `${dir}yantra-upload-${Date.now()}-${safeName}`;\n const encoding = fs.EncodingType?.Base64 ?? 'base64';\n await fs.writeAsStringAsync(dest, base64, { encoding });\n return dest;\n}\n\nasync function putObject(signedUrl: string, attachment: MessageAttachment): Promise<void> {\n const fileUri = await localFileForUpload(attachment);\n if (!fileUri) {\n throw new Error(`No bytes to upload for ${attachment.name}`);\n }\n const mimeType = attachment.mimeType || 'application/octet-stream';\n const fs = loadFileSystem();\n if (typeof fs.uploadAsync === 'function') {\n const result = await fs.uploadAsync(signedUrl, fileUri, {\n httpMethod: 'PUT',\n headers: { 'Content-Type': mimeType },\n uploadType: fs.FileSystemUploadType?.BINARY_CONTENT ?? 0,\n });\n if (typeof result.status === 'number' && result.status >= 400) {\n throw new Error(`S3 upload failed for \"${attachment.name}\" with status ${result.status}`);\n }\n return;\n }\n const body = await fetch(fileUri).then((res) => res.blob());\n const res = await fetch(signedUrl, {\n method: 'PUT',\n headers: { 'Content-Type': mimeType },\n body,\n });\n if (!res.ok) {\n throw new Error(`S3 upload failed for \"${attachment.name}\" with status ${res.status}`);\n }\n}\n\n/**\n * Browser `buildAttachmentsForMessage`: PUT each local file to a presigned\n * chat-attachment URL and return copies with the bare S3 object URL set.\n * Local `file:` / `data:` preview fields are left intact on the original objects.\n */\nexport async function uploadChatAttachments(\n attachments: MessageAttachment[],\n createFileUploadLinks: (filenames: string[]) => Promise<string[]>,\n): Promise<Array<MessageAttachment & { objectUrl?: string }>> {\n const uploadEntries = attachments\n .map((attachment, index) => ({ attachment, index }))\n .filter(({ attachment }) => !isRemoteObjectUrl(attachment.url));\n\n let signedUrls: string[] = [];\n if (uploadEntries.length > 0) {\n const filenames = uploadEntries.map(({ attachment }) => attachment.name || `file-${Date.now()}`);\n signedUrls = await createFileUploadLinks(filenames);\n if (signedUrls.length !== uploadEntries.length) {\n throw new Error(`Expected ${uploadEntries.length} signed URLs but received ${signedUrls.length}`);\n }\n await Promise.all(uploadEntries.map((entry, i) => putObject(signedUrls[i], entry.attachment)));\n }\n\n const urlByIndex = new Map(uploadEntries.map((entry, i) => [entry.index, getBaseUrlFromSignedUrl(signedUrls[i])]));\n\n return attachments.map((attachment, index) => ({\n ...attachment,\n objectUrl: isRemoteObjectUrl(attachment.url) ? attachment.url : urlByIndex.get(index),\n }));\n}\n"],"names":["res"],"mappings":"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"}
|
|
@@ -24,16 +24,49 @@ function base64Bytes(base64) {
|
|
|
24
24
|
const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
25
25
|
return Math.floor(base64.length * 3 / 4) - padding;
|
|
26
26
|
}
|
|
27
|
-
function
|
|
27
|
+
function isJpegRepresentationError(err) {
|
|
28
|
+
const msg = err instanceof Error ? err.message : String(err != null ? err : "");
|
|
29
|
+
return /public\.jpeg|Failed to read picked image/i.test(msg);
|
|
30
|
+
}
|
|
31
|
+
function attachErrorMessage(err) {
|
|
32
|
+
if (isJpegRepresentationError(err)) {
|
|
33
|
+
return "Could not read that photo. Try another one, or save it to Recents first.";
|
|
34
|
+
}
|
|
35
|
+
return err instanceof Error ? err.message : "Could not attach that image.";
|
|
36
|
+
}
|
|
37
|
+
function guessImageMime(asset) {
|
|
38
|
+
var _a, _b, _c;
|
|
39
|
+
const mime = (_a = asset.mimeType) == null ? void 0 : _a.trim();
|
|
40
|
+
if (mime) return mime;
|
|
41
|
+
const name = `${(_b = asset.fileName) != null ? _b : ""} ${(_c = asset.uri) != null ? _c : ""}`;
|
|
42
|
+
if (/\.png(\?|$)/i.test(name)) return "image/png";
|
|
43
|
+
if (/\.gif(\?|$)/i.test(name)) return "image/gif";
|
|
44
|
+
if (/\.webp(\?|$)/i.test(name)) return "image/webp";
|
|
45
|
+
if (/\.(heic|heif)(\?|$)/i.test(name)) return "image/heic";
|
|
46
|
+
return "image/jpeg";
|
|
47
|
+
}
|
|
48
|
+
async function readUriBase64(uri) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
const fs = loadFileSystem();
|
|
51
|
+
if (typeof fs.readAsStringAsync !== "function") return null;
|
|
52
|
+
const encoding = (_b = (_a = fs.EncodingType) == null ? void 0 : _a.Base64) != null ? _b : "base64";
|
|
53
|
+
const base64 = (await fs.readAsStringAsync(uri, {
|
|
54
|
+
encoding
|
|
55
|
+
})).trim();
|
|
56
|
+
return base64 || null;
|
|
57
|
+
}
|
|
58
|
+
async function assetToAttachment(asset, index) {
|
|
28
59
|
var _a, _b, _c, _d;
|
|
29
|
-
|
|
60
|
+
let base64 = ((_a = asset.base64) == null ? void 0 : _a.trim()) || "";
|
|
61
|
+
if (!base64 && ((_b = asset.uri) == null ? void 0 : _b.trim())) {
|
|
62
|
+
base64 = await readUriBase64(asset.uri.trim()) || "";
|
|
63
|
+
}
|
|
30
64
|
if (!base64) return null;
|
|
31
65
|
const size = base64Bytes(base64);
|
|
32
66
|
if (size < MIN_BYTES || size > MAX_BYTES) return null;
|
|
33
|
-
const mimeType = (
|
|
67
|
+
const mimeType = guessImageMime(asset);
|
|
34
68
|
const name = ((_c = asset.fileName) == null ? void 0 : _c.trim()) || `image-${Date.now()}-${index}.jpg`;
|
|
35
69
|
return {
|
|
36
|
-
// Local id only — the gateway assigns its own.
|
|
37
70
|
id: `img-${Date.now()}-${index}`,
|
|
38
71
|
name,
|
|
39
72
|
type: "file",
|
|
@@ -114,7 +147,7 @@ function useImageAttachments() {
|
|
|
114
147
|
const [busyKind, setBusyKind] = useState(null);
|
|
115
148
|
const [error, setError] = useState(null);
|
|
116
149
|
const run = useCallback(async (mode) => {
|
|
117
|
-
var _a;
|
|
150
|
+
var _a, _b, _c;
|
|
118
151
|
setError(null);
|
|
119
152
|
try {
|
|
120
153
|
const picker = loadPicker();
|
|
@@ -124,30 +157,43 @@ function useImageAttachments() {
|
|
|
124
157
|
return;
|
|
125
158
|
}
|
|
126
159
|
setBusyKind(mode);
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
// own resize keep it small enough to travel inline.
|
|
160
|
+
const representation = (_b = (_a = picker.UIImagePickerPreferredAssetRepresentationMode) == null ? void 0 : _a.Current) != null ? _b : "current";
|
|
161
|
+
const options = __spreadValues(__spreadValues({
|
|
130
162
|
base64: true,
|
|
131
|
-
quality: PICKER_QUALITY,
|
|
132
163
|
allowsMultipleSelection: mode === "library",
|
|
133
164
|
mediaTypes: ["images"],
|
|
134
|
-
// Selection limit mirrors MAX_BYTES: four 1280px JPEGs still fit
|
|
135
|
-
// comfortably inside a single turn.
|
|
136
165
|
selectionLimit: 4,
|
|
137
166
|
exif: false
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
}, mode === "camera" ? {
|
|
168
|
+
quality: PICKER_QUALITY
|
|
169
|
+
} : {}), mode === "library" ? {
|
|
170
|
+
preferredAssetRepresentationMode: representation
|
|
171
|
+
} : {});
|
|
172
|
+
let result;
|
|
173
|
+
try {
|
|
174
|
+
result = mode === "camera" ? await picker.launchCameraAsync(__spreadProps(__spreadValues({}, options), {
|
|
175
|
+
allowsMultipleSelection: false
|
|
176
|
+
})) : await picker.launchImageLibraryAsync(options);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (mode !== "library" || !isJpegRepresentationError(e)) throw e;
|
|
179
|
+
result = await picker.launchImageLibraryAsync(__spreadProps(__spreadValues({}, options), {
|
|
180
|
+
base64: false
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
142
183
|
if (result.canceled) return;
|
|
143
|
-
const
|
|
184
|
+
const picked = (_c = result.assets) != null ? _c : [];
|
|
185
|
+
const mapped = [];
|
|
186
|
+
for (let i = 0; i < picked.length; i += 1) {
|
|
187
|
+
const next = await assetToAttachment(picked[i], i);
|
|
188
|
+
if (next) mapped.push(next);
|
|
189
|
+
}
|
|
144
190
|
if (mapped.length === 0) {
|
|
145
191
|
setError("That image was too large to attach. Try a smaller one.");
|
|
146
192
|
return;
|
|
147
193
|
}
|
|
148
194
|
setPending((prev) => [...prev, ...mapped].slice(0, MAX_FILES));
|
|
149
195
|
} catch (e) {
|
|
150
|
-
setError(e
|
|
196
|
+
setError(attachErrorMessage(e));
|
|
151
197
|
} finally {
|
|
152
198
|
setBusyKind(null);
|
|
153
199
|
}
|
|
@@ -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. So we ask the picker to downscale and re-encode before we ever\n * see the bytes, then hard-reject anything still oversized rather than let a\n * send hang on a slow uplink.\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 toAttachment(asset: PickedAsset, index: number): MessageAttachment | null {\n const base64 = asset.base64?.trim();\n if (!base64) return null;\n const size = base64Bytes(base64);\n if (size < MIN_BYTES || size > MAX_BYTES) return null;\n const mimeType = asset.mimeType?.trim() || 'image/jpeg';\n const name = asset.fileName?.trim() || `image-${Date.now()}-${index}.jpg`;\n return {\n // Local id only — the gateway assigns its own.\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 options: import('expo-image-picker').ImagePickerOptions = {\n // `base64` is what the message carries; `quality` + the picker's\n // own resize keep it small enough to travel inline.\n base64: true,\n quality: PICKER_QUALITY,\n allowsMultipleSelection: mode === 'library',\n mediaTypes: ['images'],\n // Selection limit mirrors MAX_BYTES: four 1280px JPEGs still fit\n // comfortably inside a single turn.\n selectionLimit: 4,\n exif: false,\n };\n\n const result =\n mode === 'camera'\n ? await picker.launchCameraAsync({ ...options, allowsMultipleSelection: false })\n : await picker.launchImageLibraryAsync(options);\n\n if (result.canceled) return;\n\n const mapped = (result.assets ?? [])\n .map((asset, i) => toAttachment(asset as PickedAsset, i))\n .filter((a): a is MessageAttachment => a !== null);\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(e instanceof Error ? e.message : 'Could not attach that image.');\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 // 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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"}
|