@adminide-stack/yantra-mobile 12.0.43-alpha.0 → 12.0.43-alpha.14
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/AppsMenu/AppsMenu.js +12 -2
- package/lib/components/AppsMenu/AppsMenu.js.map +1 -1
- package/lib/compute.js +55 -0
- package/lib/compute.js.map +1 -1
- package/lib/config/env-config.js +12 -0
- package/lib/config/env-config.js.map +1 -1
- package/lib/features/apps/mobileAppCatalog.js +77 -22
- package/lib/features/apps/mobileAppCatalog.js.map +1 -1
- package/lib/features/apps/resolveSurfaceUrl.js +12 -0
- package/lib/features/apps/resolveSurfaceUrl.js.map +1 -0
- package/lib/features/attachments/useImageAttachments.js +106 -0
- package/lib/features/attachments/useImageAttachments.js.map +1 -0
- package/lib/features/canvas/canvasCore.js +41 -0
- package/lib/features/canvas/canvasCore.js.map +1 -0
- package/lib/screens/CanvasBoard/index.js +53 -0
- package/lib/screens/CanvasBoard/index.js.map +1 -0
- package/lib/screens/Chat/index.js +68 -32
- package/lib/screens/Chat/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {useState,useCallback,useMemo}from'react';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 PICKER_QUALITY = 0.7;
|
|
21
|
+
const MAX_BYTES = 4 * 1024 * 1024;
|
|
22
|
+
const MIN_BYTES = 64;
|
|
23
|
+
function base64Bytes(base64) {
|
|
24
|
+
const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
25
|
+
return Math.floor(base64.length * 3 / 4) - padding;
|
|
26
|
+
}
|
|
27
|
+
function toAttachment(asset, index) {
|
|
28
|
+
var _a, _b, _c;
|
|
29
|
+
const base64 = (_a = asset.base64) == null ? void 0 : _a.trim();
|
|
30
|
+
if (!base64) return null;
|
|
31
|
+
const size = base64Bytes(base64);
|
|
32
|
+
if (size < MIN_BYTES || size > MAX_BYTES) return null;
|
|
33
|
+
const mimeType = ((_b = asset.mimeType) == null ? void 0 : _b.trim()) || "image/jpeg";
|
|
34
|
+
const name = ((_c = asset.fileName) == null ? void 0 : _c.trim()) || `image-${Date.now()}-${index}.jpg`;
|
|
35
|
+
return {
|
|
36
|
+
// Local id only — the gateway assigns its own.
|
|
37
|
+
id: `img-${Date.now()}-${index}`,
|
|
38
|
+
name,
|
|
39
|
+
type: "file",
|
|
40
|
+
mimeType,
|
|
41
|
+
dataUrl: `data:${mimeType};base64,${base64}`,
|
|
42
|
+
size
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function loadPicker() {
|
|
46
|
+
return require("expo-image-picker");
|
|
47
|
+
}
|
|
48
|
+
function useImageAttachments() {
|
|
49
|
+
const [pending, setPending] = useState([]);
|
|
50
|
+
const [busy, setBusy] = useState(false);
|
|
51
|
+
const [error, setError] = useState(null);
|
|
52
|
+
const run = useCallback(async (mode) => {
|
|
53
|
+
var _a;
|
|
54
|
+
setError(null);
|
|
55
|
+
setBusy(true);
|
|
56
|
+
try {
|
|
57
|
+
const picker = loadPicker();
|
|
58
|
+
const permission = mode === "camera" ? await picker.requestCameraPermissionsAsync() : await picker.requestMediaLibraryPermissionsAsync();
|
|
59
|
+
if (!permission.granted) {
|
|
60
|
+
setError(mode === "camera" ? "Camera access is off for Yantra. Turn it on in Settings to attach a photo." : "Photo access is off for Yantra. Turn it on in Settings to attach an image.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const options = {
|
|
64
|
+
// `base64` is what the message carries; `quality` + the picker's
|
|
65
|
+
// own resize keep it small enough to travel inline.
|
|
66
|
+
base64: true,
|
|
67
|
+
quality: PICKER_QUALITY,
|
|
68
|
+
allowsMultipleSelection: mode === "library",
|
|
69
|
+
mediaTypes: ["images"],
|
|
70
|
+
// Selection limit mirrors MAX_BYTES: four 1280px JPEGs still fit
|
|
71
|
+
// comfortably inside a single turn.
|
|
72
|
+
selectionLimit: 4,
|
|
73
|
+
exif: false
|
|
74
|
+
};
|
|
75
|
+
const result = mode === "camera" ? await picker.launchCameraAsync(__spreadProps(__spreadValues({}, options), {
|
|
76
|
+
allowsMultipleSelection: false
|
|
77
|
+
})) : await picker.launchImageLibraryAsync(options);
|
|
78
|
+
if (result.canceled) return;
|
|
79
|
+
const mapped = ((_a = result.assets) != null ? _a : []).map((asset, i) => toAttachment(asset, i)).filter((a) => a !== null);
|
|
80
|
+
if (mapped.length === 0) {
|
|
81
|
+
setError("That image was too large to attach. Try a smaller one.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setPending((prev) => [...prev, ...mapped]);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
setError(e instanceof Error ? e.message : "Could not attach that image.");
|
|
87
|
+
} finally {
|
|
88
|
+
setBusy(false);
|
|
89
|
+
}
|
|
90
|
+
}, []);
|
|
91
|
+
const pickFromLibrary = useCallback(() => run("library"), [run]);
|
|
92
|
+
const captureFromCamera = useCallback(() => run("camera"), [run]);
|
|
93
|
+
const remove = useCallback((id) => setPending((prev) => prev.filter((a) => a.id !== id)), []);
|
|
94
|
+
const clear = useCallback(() => setPending([]), []);
|
|
95
|
+
const forSend = useCallback(() => pending.length > 0 ? pending : void 0, [pending]);
|
|
96
|
+
return useMemo(() => ({
|
|
97
|
+
pending,
|
|
98
|
+
busy,
|
|
99
|
+
error,
|
|
100
|
+
pickFromLibrary,
|
|
101
|
+
captureFromCamera,
|
|
102
|
+
remove,
|
|
103
|
+
clear,
|
|
104
|
+
forSend
|
|
105
|
+
}), [pending, busy, error, pickFromLibrary, captureFromCamera, remove, clear, forSend]);
|
|
106
|
+
}export{useImageAttachments};//# sourceMappingURL=useImageAttachments.js.map
|
|
@@ -0,0 +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 interface ImageAttachmentsApi {\n /** Attachments staged for the next send. */\n pending: MessageAttachment[];\n /** True while the picker/encode is in flight, for the toolbar spinner. */\n busy: boolean;\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 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 size,\n };\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\nexport function useImageAttachments(): ImageAttachmentsApi {\n const [pending, setPending] = useState<MessageAttachment[]>([]);\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const run = useCallback(async (mode: 'library' | 'camera') => {\n setError(null);\n setBusy(true);\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 const options = {\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'] as const,\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]);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Could not attach that image.');\n } finally {\n setBusy(false);\n }\n }, []);\n\n const pickFromLibrary = useCallback(() => run('library'), [run]);\n const captureFromCamera = useCallback(() => run('camera'), [run]);\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 : undefined), [pending]);\n\n return useMemo(\n () => ({ pending, busy, error, pickFromLibrary, captureFromCamera, remove, clear, forSend }),\n [pending, busy, error, pickFromLibrary, captureFromCamera, remove, clear, forSend],\n );\n}\n\nexport const IMAGE_ATTACHMENT_LIMITS = { MAX_EDGE, MAX_BYTES, PICKER_QUALITY } as const;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAuBA,MAAM,cAAiB,GAAA,GAAA;AAEvB,MAAM,SAAA,GAAY,IAAI,IAAO,GAAA,IAAA;AAE7B,MAAM,SAAY,GAAA,EAAA;AA0BlB,SAAS,YAAY,MAAwB,EAAA;AAC3C,EAAM,MAAA,OAAA,GAAU,MAAO,CAAA,QAAA,CAAS,IAAI,CAAA,GAAI,IAAI,MAAO,CAAA,QAAA,CAAS,GAAG,CAAA,GAAI,CAAI,GAAA,CAAA;AACvE,EAAA,OAAO,KAAK,KAAM,CAAA,MAAA,CAAO,MAAS,GAAA,CAAA,GAAI,CAAC,CAAI,GAAA,OAAA;AAC7C;AACA,SAAS,YAAA,CAAa,OAAoB,KAAyC,EAAA;AAzDnF,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AA0DE,EAAM,MAAA,MAAA,GAAA,CAAS,EAAM,GAAA,KAAA,CAAA,MAAA,KAAN,IAAc,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,EAAA;AAC7B,EAAI,IAAA,CAAC,QAAe,OAAA,IAAA;AACpB,EAAM,MAAA,IAAA,GAAO,YAAY,MAAM,CAAA;AAC/B,EAAA,IAAI,IAAO,GAAA,SAAA,IAAa,IAAO,GAAA,SAAA,EAAkB,OAAA,IAAA;AACjD,EAAA,MAAM,QAAW,GAAA,CAAA,CAAA,EAAA,GAAA,KAAA,CAAM,QAAN,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAgB,IAAU,EAAA,KAAA,YAAA;AAC3C,EAAM,MAAA,IAAA,GAAA,CAAA,CAAO,EAAM,GAAA,KAAA,CAAA,QAAA,KAAN,IAAgB,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,EAAA,KAAU,SAAS,IAAK,CAAA,GAAA,EAAK,CAAA,CAAA,EAAI,KAAK,CAAA,IAAA,CAAA;AACnE,EAAO,OAAA;AAAA;AAAA,IAEL,IAAI,CAAO,IAAA,EAAA,IAAA,CAAK,GAAI,EAAC,IAAI,KAAK,CAAA,CAAA;AAAA,IAC9B,IAAA;AAAA,IACA,IAAM,EAAA,MAAA;AAAA,IACN,QAAA;AAAA,IACA,OAAS,EAAA,CAAA,KAAA,EAAQ,QAAQ,CAAA,QAAA,EAAW,MAAM,CAAA,CAAA;AAAA,IAC1C;AAAA,GACF;AACF;AAMA,SAAS,UAAa,GAAA;AAEpB,EAAA,OAAO,QAAQ,mBAAmB,CAAA;AACpC;AACO,SAAS,mBAA2C,GAAA;AACzD,EAAA,MAAM,CAAC,OAAS,EAAA,UAAU,CAAI,GAAA,QAAA,CAA8B,EAAE,CAAA;AAC9D,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAM,MAAA,GAAA,GAAM,WAAY,CAAA,OAAO,IAA+B,KAAA;AAvFhE,IAAA,IAAA,EAAA;AAwFI,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,IAAI,IAAA;AACF,MAAA,MAAM,SAAS,UAAW,EAAA;AAC1B,MAAM,MAAA,UAAA,GAAa,SAAS,QAAW,GAAA,MAAM,OAAO,6BAA8B,EAAA,GAAI,MAAM,MAAA,CAAO,mCAAoC,EAAA;AACvI,MAAI,IAAA,CAAC,WAAW,OAAS,EAAA;AACvB,QAAS,QAAA,CAAA,IAAA,KAAS,QAAW,GAAA,4EAAA,GAA+E,4EAA4E,CAAA;AACxL,QAAA;AAAA;AAEF,MAAA,MAAM,OAAU,GAAA;AAAA;AAAA;AAAA,QAGd,MAAQ,EAAA,IAAA;AAAA,QACR,OAAS,EAAA,cAAA;AAAA,QACT,yBAAyB,IAAS,KAAA,SAAA;AAAA,QAClC,UAAA,EAAY,CAAC,QAAQ,CAAA;AAAA;AAAA;AAAA,QAGrB,cAAgB,EAAA,CAAA;AAAA,QAChB,IAAM,EAAA;AAAA,OACR;AACA,MAAA,MAAM,SAAS,IAAS,KAAA,QAAA,GAAW,MAAM,MAAO,CAAA,iBAAA,CAAkB,iCAC7D,OAD6D,CAAA,EAAA;AAAA,QAEhE,uBAAyB,EAAA;AAAA,OAC1B,CAAA,CAAA,GAAI,MAAM,MAAA,CAAO,wBAAwB,OAAO,CAAA;AACjD,MAAA,IAAI,OAAO,QAAU,EAAA;AACrB,MAAA,MAAM,WAAU,EAAO,GAAA,MAAA,CAAA,MAAA,KAAP,YAAiB,EAAC,EAAG,IAAI,CAAC,KAAA,EAAO,MAAM,YAAa,CAAA,KAAA,EAAsB,CAAC,CAAC,CAAA,CAAE,OAAO,CAAC,CAAA,KAA8B,MAAM,IAAI,CAAA;AAC9I,MAAI,IAAA,MAAA,CAAO,WAAW,CAAG,EAAA;AACvB,QAAA,QAAA,CAAS,wDAAwD,CAAA;AACjE,QAAA;AAAA;AAEF,MAAA,UAAA,CAAW,UAAQ,CAAC,GAAG,IAAM,EAAA,GAAG,MAAM,CAAC,CAAA;AAAA,aAChC,CAAG,EAAA;AACV,MAAA,QAAA,CAAS,CAAa,YAAA,KAAA,GAAQ,CAAE,CAAA,OAAA,GAAU,8BAA8B,CAAA;AAAA,KACxE,SAAA;AACA,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA;AACf,GACF,EAAG,EAAE,CAAA;AACL,EAAM,MAAA,eAAA,GAAkB,YAAY,MAAM,GAAA,CAAI,SAAS,CAAG,EAAA,CAAC,GAAG,CAAC,CAAA;AAC/D,EAAM,MAAA,iBAAA,GAAoB,YAAY,MAAM,GAAA,CAAI,QAAQ,CAAG,EAAA,CAAC,GAAG,CAAC,CAAA;AAChE,EAAA,MAAM,MAAS,GAAA,WAAA,CAAY,CAAC,EAAA,KAAe,WAAW,CAAQ,IAAA,KAAA,IAAA,CAAK,MAAO,CAAA,CAAA,CAAA,KAAK,EAAE,EAAO,KAAA,EAAE,CAAC,CAAA,EAAG,EAAE,CAAA;AAChG,EAAM,MAAA,KAAA,GAAQ,YAAY,MAAM,UAAA,CAAW,EAAE,CAAA,EAAG,EAAE,CAAA;AAClD,EAAM,MAAA,OAAA,GAAU,WAAY,CAAA,MAAM,OAAQ,CAAA,MAAA,GAAS,IAAI,OAAU,GAAA,MAAA,EAAW,CAAC,OAAO,CAAC,CAAA;AACrF,EAAA,OAAO,QAAQ,OAAO;AAAA,IACpB,OAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,eAAA;AAAA,IACA,iBAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF,CAAA,EAAI,CAAC,OAAA,EAAS,IAAM,EAAA,KAAA,EAAO,iBAAiB,iBAAmB,EAAA,MAAA,EAAQ,KAAO,EAAA,OAAO,CAAC,CAAA;AACxF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
function parseBoardState(raw) {
|
|
18
|
+
if (!raw || typeof raw !== "object") return null;
|
|
19
|
+
const candidate = raw;
|
|
20
|
+
if (candidate.version !== 1 || !Array.isArray(candidate.items)) return null;
|
|
21
|
+
const items = candidate.items.filter(isRenderableItem);
|
|
22
|
+
return __spreadValues({
|
|
23
|
+
version: 1,
|
|
24
|
+
savedAt: typeof candidate.savedAt === "number" ? candidate.savedAt : 0,
|
|
25
|
+
items,
|
|
26
|
+
zoom: clampZoom(typeof candidate.zoom === "number" ? candidate.zoom : 1)
|
|
27
|
+
}, typeof candidate.title === "string" && candidate.title ? {
|
|
28
|
+
title: candidate.title
|
|
29
|
+
} : {});
|
|
30
|
+
}
|
|
31
|
+
function isRenderableItem(item) {
|
|
32
|
+
if (!item || typeof item !== "object") return false;
|
|
33
|
+
const c = item;
|
|
34
|
+
return typeof c.id === "string" && (c.type === "image" || c.type === "video" || c.type === "audio") && typeof c.src === "string" && c.src.length > 0 && !c.src.startsWith("blob:") && [c.x, c.y, c.width, c.height].every((n) => typeof n === "number" && Number.isFinite(n)) && c.width > 0 && c.height > 0;
|
|
35
|
+
}
|
|
36
|
+
const MIN_ZOOM = 0.25;
|
|
37
|
+
const MAX_ZOOM = 4;
|
|
38
|
+
function clampZoom(zoom) {
|
|
39
|
+
if (!Number.isFinite(zoom)) return 1;
|
|
40
|
+
return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
41
|
+
}export{MAX_ZOOM,MIN_ZOOM,clampZoom,isRenderableItem,parseBoardState};//# sourceMappingURL=canvasCore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvasCore.js","sources":["../../../src/features/canvas/canvasCore.ts"],"sourcesContent":["/**\n * Platform-agnostic canvas document model — the native twin of the web\n * surface's `state/boards.ts` (`@yantra/canvas-surface`).\n *\n * The shapes are copied, not imported: the surface package builds for the DOM\n * (react-dom peer, vite, `crypto.randomUUID`), so importing it would drag the\n * web toolchain into Metro. The contract that actually matters is the persisted\n * JSON — `PersistedCanvasState` version 1 — and tests below pin this file to\n * fixtures of that JSON, so drift between the copies fails a test rather than\n * corrupting a board.\n *\n * Everything here is pure data + math so it runs identically under Metro,\n * vite, and jest: no RN imports, no DOM globals.\n */\n\nexport interface CanvasItem {\n id: string;\n type: 'image' | 'video' | 'audio';\n src: string;\n x: number;\n y: number;\n width: number;\n height: number;\n zIndex: number;\n generatedAssetName?: string;\n}\n\nexport interface PersistedCanvasState {\n version: 1;\n savedAt: number;\n items: CanvasItem[];\n zoom: number;\n title?: string;\n}\n\n/** Parse a persisted board, tolerating the unknown: bad rows drop, not throw. */\nexport function parseBoardState(raw: unknown): PersistedCanvasState | null {\n if (!raw || typeof raw !== 'object') return null;\n const candidate = raw as Partial<PersistedCanvasState>;\n if (candidate.version !== 1 || !Array.isArray(candidate.items)) return null;\n const items = candidate.items.filter(isRenderableItem);\n return {\n version: 1,\n savedAt: typeof candidate.savedAt === 'number' ? candidate.savedAt : 0,\n items,\n zoom: clampZoom(typeof candidate.zoom === 'number' ? candidate.zoom : 1),\n ...(typeof candidate.title === 'string' && candidate.title ? { title: candidate.title } : {}),\n };\n}\n\n/** An item the viewer can draw. `blob:` URLs died with the web tab that made them. */\nexport function isRenderableItem(item: unknown): item is CanvasItem {\n if (!item || typeof item !== 'object') return false;\n const c = item as Partial<CanvasItem>;\n return (\n typeof c.id === 'string' &&\n (c.type === 'image' || c.type === 'video' || c.type === 'audio') &&\n typeof c.src === 'string' &&\n c.src.length > 0 &&\n !c.src.startsWith('blob:') &&\n [c.x, c.y, c.width, c.height].every((n) => typeof n === 'number' && Number.isFinite(n)) &&\n (c.width as number) > 0 &&\n (c.height as number) > 0\n );\n}\n\nexport const MIN_ZOOM = 0.25;\nexport const MAX_ZOOM = 4;\n\nexport function clampZoom(zoom: number): number {\n if (!Number.isFinite(zoom)) return 1;\n return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));\n}\n\nexport interface BoardBounds {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n}\n\n/** Union of item rects; null for an empty board (an empty union has no bounds). */\nexport function boardBounds(items: readonly CanvasItem[]): BoardBounds | null {\n if (items.length === 0) return null;\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const it of items) {\n minX = Math.min(minX, it.x);\n minY = Math.min(minY, it.y);\n maxX = Math.max(maxX, it.x + it.width);\n maxY = Math.max(maxY, it.y + it.height);\n }\n return { minX, minY, maxX, maxY };\n}\n\nexport interface FitTransform {\n /** Uniform scale applied to board coordinates. */\n scale: number;\n /** Screen-space translation, applied after scaling. */\n translateX: number;\n translateY: number;\n}\n\n/**\n * Fit the whole board inside a viewport with a margin — the viewer's initial\n * camera. Pure math so pinch/pan gestures can start from a reproducible state.\n */\nexport function fitToViewport(\n bounds: BoardBounds,\n viewportWidth: number,\n viewportHeight: number,\n margin = 24,\n): FitTransform {\n const boardW = Math.max(1, bounds.maxX - bounds.minX);\n const boardH = Math.max(1, bounds.maxY - bounds.minY);\n const availW = Math.max(1, viewportWidth - margin * 2);\n const availH = Math.max(1, viewportHeight - margin * 2);\n const scale = clampZoom(Math.min(availW / boardW, availH / boardH));\n // Center the scaled board in the viewport.\n const translateX = (viewportWidth - boardW * scale) / 2 - bounds.minX * scale;\n const translateY = (viewportHeight - boardH * scale) / 2 - bounds.minY * scale;\n return { scale, translateX, translateY };\n}\n\n/** Painter's order: stable sort by zIndex so overlapping media stack as authored. */\nexport function paintOrder(items: readonly CanvasItem[]): CanvasItem[] {\n return [...items].sort((a, b) => a.zIndex - b.zIndex || a.id.localeCompare(b.id));\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAmCO,SAAS,gBAAgB,GAA2C,EAAA;AACzE,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAiB,OAAA,IAAA;AAC5C,EAAA,MAAM,SAAY,GAAA,GAAA;AAClB,EAAI,IAAA,SAAA,CAAU,YAAY,CAAK,IAAA,CAAC,MAAM,OAAQ,CAAA,SAAA,CAAU,KAAK,CAAA,EAAU,OAAA,IAAA;AACvE,EAAA,MAAM,KAAQ,GAAA,SAAA,CAAU,KAAM,CAAA,MAAA,CAAO,gBAAgB,CAAA;AACrD,EAAO,OAAA,cAAA,CAAA;AAAA,IACL,OAAS,EAAA,CAAA;AAAA,IACT,SAAS,OAAO,SAAA,CAAU,OAAY,KAAA,QAAA,GAAW,UAAU,OAAU,GAAA,CAAA;AAAA,IACrE,KAAA;AAAA,IACA,IAAA,EAAM,UAAU,OAAO,SAAA,CAAU,SAAS,QAAW,GAAA,SAAA,CAAU,OAAO,CAAC;AAAA,GAAA,EACnE,OAAO,SAAA,CAAU,KAAU,KAAA,QAAA,IAAY,UAAU,KAAQ,GAAA;AAAA,IAC3D,OAAO,SAAU,CAAA;AAAA,MACf,EAAC,CAAA;AAET;AAGO,SAAS,iBAAiB,IAAmC,EAAA;AAClE,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAiB,OAAA,KAAA;AAC9C,EAAA,MAAM,CAAI,GAAA,IAAA;AACV,EAAO,OAAA,OAAO,EAAE,EAAO,KAAA,QAAA,KAAa,EAAE,IAAS,KAAA,OAAA,IAAW,CAAE,CAAA,IAAA,KAAS,OAAW,IAAA,CAAA,CAAE,SAAS,OAAY,CAAA,IAAA,OAAO,CAAE,CAAA,GAAA,KAAQ,QAAY,IAAA,CAAA,CAAE,IAAI,MAAS,GAAA,CAAA,IAAK,CAAC,CAAA,CAAE,GAAI,CAAA,UAAA,CAAW,OAAO,CAAK,IAAA,CAAC,CAAE,CAAA,CAAA,EAAG,CAAE,CAAA,CAAA,EAAG,EAAE,KAAO,EAAA,CAAA,CAAE,MAAM,CAAA,CAAE,KAAM,CAAA,CAAA,CAAA,KAAK,OAAO,CAAM,KAAA,QAAA,IAAY,MAAO,CAAA,QAAA,CAAS,CAAC,CAAC,KAAK,CAAE,CAAA,KAAA,GAAkB,CAAK,IAAA,CAAA,CAAE,MAAmB,GAAA,CAAA;AAC/T;AACO,MAAM,QAAW,GAAA;AACjB,MAAM,QAAW,GAAA;AACjB,SAAS,UAAU,IAAsB,EAAA;AAC9C,EAAA,IAAI,CAAC,MAAA,CAAO,QAAS,CAAA,IAAI,GAAU,OAAA,CAAA;AACnC,EAAA,OAAO,KAAK,GAAI,CAAA,QAAA,EAAU,KAAK,GAAI,CAAA,QAAA,EAAU,IAAI,CAAC,CAAA;AACpD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {jsx,jsxs}from'react/jsx-runtime';import {useMemo}from'react';import {useColorScheme,StyleSheet,View,Text}from'react-native';import {SafeAreaView}from'react-native-safe-area-context';import {useRoute}from'@react-navigation/native';import {parseBoardState}from'../../features/canvas/canvasCore.js';const EMPTY_BOARD = {
|
|
2
|
+
version: 1,
|
|
3
|
+
savedAt: 0,
|
|
4
|
+
items: [],
|
|
5
|
+
zoom: 1
|
|
6
|
+
};
|
|
7
|
+
function CanvasBoardScreen() {
|
|
8
|
+
const route = useRoute();
|
|
9
|
+
const isDark = useColorScheme() === "dark";
|
|
10
|
+
useMemo(() => {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const raw = (_a = route.params) == null ? void 0 : _a.board;
|
|
13
|
+
return (_b = raw ? parseBoardState(raw) : null) != null ? _b : EMPTY_BOARD;
|
|
14
|
+
}, [route.params]);
|
|
15
|
+
{
|
|
16
|
+
return /* @__PURE__ */ jsx(SafeAreaView, { style: [styles.fill, isDark && styles.fillDark], children: /* @__PURE__ */ jsxs(View, { style: styles.center, children: [
|
|
17
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.title, isDark && styles.textLight], children: "Native canvas unavailable" }),
|
|
18
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.detail, isDark && styles.textMuted], children: "This build does not carry the federation host runtime." })
|
|
19
|
+
] }) });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
fill: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
backgroundColor: "#f8fafc"
|
|
26
|
+
},
|
|
27
|
+
fillDark: {
|
|
28
|
+
backgroundColor: "#0f172a"
|
|
29
|
+
},
|
|
30
|
+
center: {
|
|
31
|
+
flex: 1,
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
justifyContent: "center",
|
|
34
|
+
padding: 24
|
|
35
|
+
},
|
|
36
|
+
title: {
|
|
37
|
+
fontSize: 15,
|
|
38
|
+
fontWeight: "600",
|
|
39
|
+
color: "#0f172a",
|
|
40
|
+
marginBottom: 6
|
|
41
|
+
},
|
|
42
|
+
detail: {
|
|
43
|
+
fontSize: 13,
|
|
44
|
+
color: "#64748b",
|
|
45
|
+
textAlign: "center"
|
|
46
|
+
},
|
|
47
|
+
textLight: {
|
|
48
|
+
color: "#f8fafc"
|
|
49
|
+
},
|
|
50
|
+
textMuted: {
|
|
51
|
+
color: "#94a3b8"
|
|
52
|
+
}
|
|
53
|
+
});export{CanvasBoardScreen as default};//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/screens/CanvasBoard/index.tsx"],"sourcesContent":["/**\n * Native canvas board screen - the first UI reachable through Module\n * Federation on mobile.\n *\n * Opening this screen is what triggers the federated fetch: the injected\n * viewer component lazy-imports `canvas_native/Viewer`, which the MF runtime\n * resolves against the manifest on the canvas-surface origin and downloads at\n * that moment - the viewer's code is NOT in the app binary. An empty board is\n * the deliberate first scope: it renders the remote's own \"This board is\n * empty.\" UI, which is exactly the observable proof that the remote loaded.\n * Board fetch (surface-index read) is the next slice and slots in as a route\n * param without touching the delivery path.\n */\nimport React, { useMemo } from 'react';\nimport { StyleSheet, Text, View, useColorScheme } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { useRoute } from '@react-navigation/native';\nimport { getNativeCanvasViewer } from '../../features/canvas/nativeViewerRegistry';\nimport { parseBoardState, type PersistedCanvasState } from '../../features/canvas/canvasCore';\n\nconst EMPTY_BOARD: PersistedCanvasState = { version: 1, savedAt: 0, items: [], zoom: 1 };\n\nexport interface CanvasBoardRouteParams {\n /** Serialized PersistedCanvasState (v1) to render. Absent -> empty board. */\n board?: unknown;\n}\n\nexport default function CanvasBoardScreen() {\n const route = useRoute();\n const isDark = useColorScheme() === 'dark';\n const Viewer = getNativeCanvasViewer();\n\n const board = useMemo(() => {\n const raw = (route.params as CanvasBoardRouteParams | undefined)?.board;\n return (raw ? parseBoardState(raw) : null) ?? EMPTY_BOARD;\n }, [route.params]);\n\n if (!Viewer) {\n // A build without the federation host (or before App registered the\n // loader). Say so instead of rendering a blank screen.\n return (\n <SafeAreaView style={[styles.fill, isDark && styles.fillDark]}>\n <View style={styles.center}>\n <Text style={[styles.title, isDark && styles.textLight]}>Native canvas unavailable</Text>\n <Text style={[styles.detail, isDark && styles.textMuted]}>\n This build does not carry the federation host runtime.\n </Text>\n </View>\n </SafeAreaView>\n );\n }\n\n return (\n <SafeAreaView style={[styles.fill, isDark && styles.fillDark]} edges={['left', 'right', 'bottom']}>\n <Viewer board={board} />\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1, backgroundColor: '#f8fafc' },\n fillDark: { backgroundColor: '#0f172a' },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },\n title: { fontSize: 15, fontWeight: '600', color: '#0f172a', marginBottom: 6 },\n detail: { fontSize: 13, color: '#64748b', textAlign: 'center' },\n textLight: { color: '#f8fafc' },\n textMuted: { color: '#94a3b8' },\n});\n"],"names":[],"mappings":"gTAmBA,MAAM,WAAoC,GAAA;AAAA,EACxC,OAAS,EAAA,CAAA;AAAA,EACT,OAAS,EAAA,CAAA;AAAA,EACT,OAAO,EAAC;AAAA,EACR,IAAM,EAAA;AACR,CAAA;AAKA,SAAwB,iBAAoB,GAAA;AAC1C,EAAA,MAAM,QAAQ,QAAS,EAAA;AACvB,EAAM,MAAA,MAAA,GAAS,gBAAqB,KAAA,MAAA;AAEpC,EAAc,QAAQ,MAAM;AAjC9B,IAAA,IAAA,EAAA,EAAA,EAAA;AAkCI,IAAM,MAAA,GAAA,GAAA,CAAO,EAAM,GAAA,KAAA,CAAA,MAAA,KAAN,IAAqD,GAAA,MAAA,GAAA,EAAA,CAAA,KAAA;AAClE,IAAA,OAAA,CAAQ,EAAM,GAAA,GAAA,GAAA,eAAA,CAAgB,GAAG,CAAA,GAAI,SAA7B,IAAsC,GAAA,EAAA,GAAA,WAAA;AAAA,GAC7C,EAAA,CAAC,KAAM,CAAA,MAAM,CAAC;AACjB,EAAa;AAGX,IAAA,uBAAQ,GAAA,CAAA,YAAA,EAAA,EAAa,KAAO,EAAA,CAAC,OAAO,IAAM,EAAA,MAAA,IAAU,MAAO,CAAA,QAAQ,CACvD,EAAA,QAAA,kBAAA,IAAA,CAAC,IAAK,EAAA,EAAA,KAAA,EAAO,OAAO,MAChB,EAAA,QAAA,EAAA;AAAA,sBAAC,GAAA,CAAA,IAAA,EAAA,EAAK,OAAO,CAAC,MAAA,CAAO,OAAO,MAAU,IAAA,MAAA,CAAO,SAAS,CAAA,EAAG,QAAyB,EAAA,2BAAA,EAAA,CAAA;AAAA,sBAClF,GAAA,CAAC,IAAK,EAAA,EAAA,KAAA,EAAO,CAAC,MAAA,CAAO,QAAQ,MAAU,IAAA,MAAA,CAAO,SAAS,CAAA,EAAG,QAE1D,EAAA,wDAAA,EAAA;AAAA,KAAA,EACJ,CACJ,EAAA,CAAA;AAAA;AAKZ;AACA,MAAM,MAAA,GAAS,WAAW,MAAO,CAAA;AAAA,EAC/B,IAAM,EAAA;AAAA,IACJ,IAAM,EAAA,CAAA;AAAA,IACN,eAAiB,EAAA;AAAA,GACnB;AAAA,EACA,QAAU,EAAA;AAAA,IACR,eAAiB,EAAA;AAAA,GACnB;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,CAAA;AAAA,IACN,UAAY,EAAA,QAAA;AAAA,IACZ,cAAgB,EAAA,QAAA;AAAA,IAChB,OAAS,EAAA;AAAA,GACX;AAAA,EACA,KAAO,EAAA;AAAA,IACL,QAAU,EAAA,EAAA;AAAA,IACV,UAAY,EAAA,KAAA;AAAA,IACZ,KAAO,EAAA,SAAA;AAAA,IACP,YAAc,EAAA;AAAA,GAChB;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,QAAU,EAAA,EAAA;AAAA,IACV,KAAO,EAAA,SAAA;AAAA,IACP,SAAW,EAAA;AAAA,GACb;AAAA,EACA,SAAW,EAAA;AAAA,IACT,KAAO,EAAA;AAAA,GACT;AAAA,EACA,SAAW,EAAA;AAAA,IACT,KAAO,EAAA;AAAA;AAEX,CAAC,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {jsxs,jsx}from'react/jsx-runtime';import {useMemo,useEffect,useState,useRef,useCallback}from'react';import {useColorScheme,useWindowDimensions,Keyboard,StyleSheet,View}from'react-native';import {getDefaultLeftItems,getDefaultRightItems,Box,Text,InputToolBar}from'@admin-layout/gluestack-ui-mobile';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {useNavigation,useRoute,CommonActions}from'@react-navigation/native';import {MessagesContainerUI}from'@messenger-box/platform-mobile';import {useCdecliConnection}from'../../contexts/CdecliConnectionContext.js';import {useChatStream}from'../../hooks/useChatStream.js';import {useKeyboardBottomOffset}from'../../hooks/useKeyboardBottomOffset.js';import {KeyboardComposerDock}from'../../components/KeyboardComposerDock.js';import {YantraBrandLoader,YANTRA_LOADER_SIZE_COMPACT}from'../../components/YantraBrandLoader.js';import ThinkingIndicator from'../../components/ThinkingIndicator.js';import {mobileTokens}from'../../theme/mobileTokens.js';import DeepSearchModal from'../Home/components/DeepSearchModal.js';import {normalizeSummaryText,extractDeepSearchSources}from'../Home/deepSearchUtils.js';import {AudioRecorderPanel}from'../../features/audio-input/AudioRecorderPanel.js';import {MicErrorBoundary}from'../../features/audio-input/MicErrorBoundary.js';import {requestMicPermission}from'../../features/audio-input/useAudioPermission.js';var __defProp = Object.defineProperty;
|
|
1
|
+
import {jsxs,jsx}from'react/jsx-runtime';import {useMemo,useEffect,useState,useRef,useCallback}from'react';import {useColorScheme,useWindowDimensions,Keyboard,TouchableWithoutFeedback,StyleSheet,View}from'react-native';import {getDefaultLeftItems,getDefaultRightItems,Box,Text,InputToolBar}from'@admin-layout/gluestack-ui-mobile';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {useNavigation,useRoute,CommonActions}from'@react-navigation/native';import {MessagesContainerUI}from'@messenger-box/platform-mobile';import {useCdecliConnection}from'../../contexts/CdecliConnectionContext.js';import {useChatStream}from'../../hooks/useChatStream.js';import {useKeyboardBottomOffset}from'../../hooks/useKeyboardBottomOffset.js';import {KeyboardComposerDock}from'../../components/KeyboardComposerDock.js';import {YantraBrandLoader,YANTRA_LOADER_SIZE_COMPACT}from'../../components/YantraBrandLoader.js';import ThinkingIndicator from'../../components/ThinkingIndicator.js';import {mobileTokens}from'../../theme/mobileTokens.js';import DeepSearchModal from'../Home/components/DeepSearchModal.js';import {normalizeSummaryText,extractDeepSearchSources}from'../Home/deepSearchUtils.js';import {AudioRecorderPanel}from'../../features/audio-input/AudioRecorderPanel.js';import {useImageAttachments}from'../../features/attachments/useImageAttachments.js';import {MicErrorBoundary}from'../../features/audio-input/MicErrorBoundary.js';import {requestMicPermission}from'../../features/audio-input/useAudioPermission.js';var __defProp = Object.defineProperty;
|
|
2
2
|
var __defProps = Object.defineProperties;
|
|
3
3
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
@@ -17,6 +17,10 @@ var __spreadValues = (a, b) => {
|
|
|
17
17
|
return a;
|
|
18
18
|
};
|
|
19
19
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
const CHAT_CURRENT_USER = {
|
|
21
|
+
id: "user"
|
|
22
|
+
};
|
|
23
|
+
const renderNoToolbar = () => null;
|
|
20
24
|
function ChatScreen() {
|
|
21
25
|
var _a, _b;
|
|
22
26
|
const navigation = useNavigation();
|
|
@@ -59,6 +63,12 @@ function ChatScreen() {
|
|
|
59
63
|
persistenceMode: effectivePersistenceMode
|
|
60
64
|
}), [cdecli.channelConnected, cdecli.accountId, effectivePersistenceMode]);
|
|
61
65
|
const chat = useChatStream(channelId, chatRouting);
|
|
66
|
+
const transcriptRows = useMemo(() => chat.messages.map((msg, index) => ({
|
|
67
|
+
id: `msg-${index}-${msg.role}`,
|
|
68
|
+
role: msg.role,
|
|
69
|
+
content: msg.content,
|
|
70
|
+
metadata: msg.metadata
|
|
71
|
+
})), [chat.messages]);
|
|
62
72
|
const {
|
|
63
73
|
messages,
|
|
64
74
|
response,
|
|
@@ -69,6 +79,7 @@ function ChatScreen() {
|
|
|
69
79
|
sendMessage,
|
|
70
80
|
cancel
|
|
71
81
|
} = chat;
|
|
82
|
+
const imageAttachments = useImageAttachments();
|
|
72
83
|
const [value, setValue] = useState("");
|
|
73
84
|
const valueRef = useRef(value);
|
|
74
85
|
valueRef.current = value;
|
|
@@ -123,8 +134,9 @@ function ChatScreen() {
|
|
|
123
134
|
if (isDeepSearchMode) {
|
|
124
135
|
setIsDeepSearchModalOpen(true);
|
|
125
136
|
}
|
|
126
|
-
void sendMessage(t,
|
|
127
|
-
|
|
137
|
+
void sendMessage(t, imageAttachments.forSend(), channelId);
|
|
138
|
+
imageAttachments.clear();
|
|
139
|
+
}, [channelId, sendMessage, isDeepSearchMode, isBuildMode, navigation, params.orgName, imageAttachments]);
|
|
128
140
|
const handleValueChange = useCallback((e) => {
|
|
129
141
|
var _a2, _b2;
|
|
130
142
|
setValue((_b2 = (_a2 = e == null ? void 0 : e.nativeEvent) == null ? void 0 : _a2.text) != null ? _b2 : "");
|
|
@@ -234,16 +246,28 @@ function ChatScreen() {
|
|
|
234
246
|
chip: {
|
|
235
247
|
enabled: false
|
|
236
248
|
},
|
|
249
|
+
// Attaching an image was disabled here even though the transport
|
|
250
|
+
// (`sendMessage(content, attachments)`) and the gateway media lane
|
|
251
|
+
// already carried it, and `expo-image-picker` was already a
|
|
252
|
+
// dependency with its permission strings declared in app.json.
|
|
237
253
|
camera: {
|
|
238
|
-
enabled:
|
|
254
|
+
enabled: true,
|
|
255
|
+
loading: imageAttachments.busy,
|
|
256
|
+
onClick: () => {
|
|
257
|
+
void imageAttachments.captureFromCamera();
|
|
258
|
+
}
|
|
239
259
|
},
|
|
240
260
|
image: {
|
|
241
|
-
enabled:
|
|
261
|
+
enabled: true,
|
|
262
|
+
loading: imageAttachments.busy,
|
|
263
|
+
onClick: () => {
|
|
264
|
+
void imageAttachments.pickFromLibrary();
|
|
265
|
+
}
|
|
242
266
|
},
|
|
243
267
|
attach: {
|
|
244
268
|
enabled: false
|
|
245
269
|
}
|
|
246
|
-
}), []);
|
|
270
|
+
}), [imageAttachments]);
|
|
247
271
|
const inputPlaceholder = isDeepSearchMode ? "Research anything..." : isBuildMode ? "Describe an app to build..." : "Ask anything...";
|
|
248
272
|
const inputConfig = useMemo(() => ({
|
|
249
273
|
value,
|
|
@@ -273,7 +297,7 @@ function ChatScreen() {
|
|
|
273
297
|
flex: 1,
|
|
274
298
|
backgroundColor: surfaceColor
|
|
275
299
|
}, children: [
|
|
276
|
-
/* @__PURE__ */ jsxs(Box, { flex: 1, width: "100%", position: "relative", children: [
|
|
300
|
+
/* @__PURE__ */ jsx(TouchableWithoutFeedback, { onPress: Keyboard.dismiss, accessible: false, children: /* @__PURE__ */ jsxs(Box, { flex: 1, width: "100%", position: "relative", children: [
|
|
277
301
|
(chatError || cdecli.error) && /* @__PURE__ */ jsx(Box, { mb: "$2", mx: "$4", p: "$3", borderRadius: "$md", style: {
|
|
278
302
|
backgroundColor: isDark ? "#2b1a1d" : "#fef2f2",
|
|
279
303
|
borderWidth: 1,
|
|
@@ -301,30 +325,42 @@ function ChatScreen() {
|
|
|
301
325
|
/* @__PURE__ */ jsx(Text, { style: [styles.statusText, {
|
|
302
326
|
color: secondaryTextColor
|
|
303
327
|
}], children: "Connecting gateway\u2026" })
|
|
304
|
-
] }) : /* @__PURE__ */ jsx(Box, { flex: 1, width: "100%", alignSelf: "stretch", children: /* @__PURE__ */ jsx(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
+
] }) : /* @__PURE__ */ jsx(Box, { flex: 1, width: "100%", alignSelf: "stretch", children: /* @__PURE__ */ jsx(
|
|
329
|
+
MessagesContainerUI,
|
|
330
|
+
{
|
|
331
|
+
mode: "chat",
|
|
332
|
+
showBackButton: false,
|
|
333
|
+
compactTop: true,
|
|
334
|
+
isKeyboardInternallyHandled: false,
|
|
335
|
+
messagesContainerStyle: {
|
|
336
|
+
paddingHorizontal: 0,
|
|
337
|
+
paddingTop: 0,
|
|
338
|
+
paddingBottom: composerScrollBottomPadding,
|
|
339
|
+
margin: 0,
|
|
340
|
+
marginTop: 0,
|
|
341
|
+
alignSelf: "center",
|
|
342
|
+
width: contentMaxWidth
|
|
343
|
+
},
|
|
344
|
+
listContentStyle: {
|
|
345
|
+
paddingTop: 0,
|
|
346
|
+
paddingBottom: composerScrollBottomPadding,
|
|
347
|
+
margin: 0,
|
|
348
|
+
marginTop: 0,
|
|
349
|
+
width: contentMaxWidth,
|
|
350
|
+
alignSelf: "center",
|
|
351
|
+
justifyContent: "flex-end"
|
|
352
|
+
},
|
|
353
|
+
messages: transcriptRows,
|
|
354
|
+
streamingContent: response,
|
|
355
|
+
currentUser: CHAT_CURRENT_USER,
|
|
356
|
+
onSend: handleSend,
|
|
357
|
+
disabled: !channelId,
|
|
358
|
+
isLoading,
|
|
359
|
+
onStop: isStreaming ? cancel : void 0,
|
|
360
|
+
renderPlanInputToolbar: renderNoToolbar,
|
|
361
|
+
renderBuildInputToolbar: renderNoToolbar
|
|
362
|
+
}
|
|
363
|
+
) }),
|
|
328
364
|
showSendingLoader && !pinSendingLoaderNearComposer ? /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: styles.sendingLoaderEmptyState, children: /* @__PURE__ */ jsx(ThinkingIndicator, { color: secondaryTextColor }) }) : null,
|
|
329
365
|
/* @__PURE__ */ jsxs(KeyboardComposerDock, { closedInset: insets.bottom, keyboardVisible, children: [
|
|
330
366
|
showSendingLoader && pinSendingLoaderNearComposer ? /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: styles.sendingLoaderInDock, children: /* @__PURE__ */ jsx(ThinkingIndicator, { color: secondaryTextColor }) }) : null,
|
|
@@ -352,7 +388,7 @@ function ChatScreen() {
|
|
|
352
388
|
) : /* @__PURE__ */ jsx(InputToolBar, { inputConfig, leftItems, rightItems, templateButton: null, templateModalConfig: null, micSendButton })
|
|
353
389
|
] })
|
|
354
390
|
] })
|
|
355
|
-
] }),
|
|
391
|
+
] }) }),
|
|
356
392
|
isDeepSearchMode ? /* @__PURE__ */ jsx(DeepSearchModal, { visible: isDeepSearchModalOpen, query: deepSearchQuery, summaryText: deepSearchSummary, sources: deepSearchSources, isStreaming, researchProcessOpen, sourcesAccordionOpen, onToggleResearchProcess: () => setResearchProcessOpen((prev) => !prev), onToggleSourcesAccordion: () => setSourcesAccordionOpen((prev) => !prev), onRetry: handleDeepSearchRetry, onStop: cancel, onClose: handleDeepSearchClose }) : null
|
|
357
393
|
] });
|
|
358
394
|
}
|