From 6160d9671627d0d456d23b17501f8dd76d1e500f Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 29 Dec 2025 09:09:53 +0700 Subject: [PATCH] Fix web image base64 encoding --- packages/app/src/contexts/session-context.tsx | 45 +++++++++++++++++-- plan.md | 3 +- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/packages/app/src/contexts/session-context.tsx b/packages/app/src/contexts/session-context.tsx index 84f3c92d4..6f32d6289 100644 --- a/packages/app/src/contexts/session-context.tsx +++ b/packages/app/src/contexts/session-context.tsx @@ -1,4 +1,5 @@ import { createContext, useRef, ReactNode, useCallback, useEffect, useMemo } from "react"; +import { Platform } from "react-native"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useWebSocket, type UseWebSocketReturn } from "@/hooks/use-websocket"; import { useDaemonRequest } from "@/hooks/use-daemon-request"; @@ -18,7 +19,7 @@ import type { import type { AgentPermissionRequest, } from "@server/server/agent/agent-sdk-types"; -import * as FileSystem from 'expo-file-system'; +import * as FileSystem from "expo-file-system"; import { useDaemonConnections } from "./daemon-connections-context"; import { useSessionStore } from "@/stores/session-store"; import type { AgentDirectoryEntry } from "@/types/agent-directory"; @@ -1293,9 +1294,45 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid const encodedImages = await Promise.all( images.map(async ({ uri, mimeType }) => { try { - const data = await FileSystem.readAsStringAsync(uri, { - encoding: "base64", - }); + const data = await (async () => { + if (Platform.OS === "web") { + if (uri.startsWith("data:")) { + const [, base64] = uri.split(",", 2); + if (!base64) { + throw new Error("Malformed data URI for image."); + } + return base64; + } + const response = await fetch(uri); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.status}`); + } + const blob = await response.blob(); + const base64 = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result !== "string") { + reject(new Error("Unexpected FileReader result type.")); + return; + } + const [, resultBase64] = reader.result.split(",", 2); + if (!resultBase64) { + reject(new Error("Failed to read image data as base64.")); + return; + } + resolve(resultBase64); + }; + reader.onerror = () => { + reject(reader.error ?? new Error("Failed to read image data.")); + }; + reader.readAsDataURL(blob); + }); + return base64; + } + return await FileSystem.readAsStringAsync(uri, { + encoding: "base64", + }); + })(); return { data, mimeType: mimeType ?? "image/jpeg", diff --git a/plan.md b/plan.md index b85430a1e..b92d8c633 100644 --- a/plan.md +++ b/plan.md @@ -140,10 +140,11 @@ Improvements to the new agent screen in the app. - Remove warning in `new.tsx:576-580` once implemented - **Done (2025-12-29 09:07)**: WHAT: `packages/server/src/server/messages.ts:363-373` adds optional `images` to `create_agent_request`; `packages/server/src/server/session.ts:1268-1311` forwards images into `handleSendAgentMessage`; `packages/app/src/contexts/session-context.tsx:1287-1459` encodes image attachments and includes them in create-agent websocket payloads; `packages/app/src/stores/session-store.ts:175-206` updates createAgent type to accept images/async; `packages/app/src/app/agent/new.tsx:560-606` forwards images to createAgent and removes the warning. RESULT: agent creation requests now carry image attachments through to the initial prompt path. EVIDENCE: Not run (not requested). -- [ ] **Fix**: Fix web platform image base64 encoding +- [x] **Fix**: Fix web platform image base64 encoding - Replace `FileSystem.readAsStringAsync` in `session-context.tsx:1312` with cross-platform solution - Use blob URL + fetch or FileReader API for web - Test on web, iOS, and Android + - **Done (2025-12-29 09:09)**: WHAT: `packages/app/src/contexts/session-context.tsx:1288-1334` adds web-specific image base64 conversion using fetch + FileReader (including data URI handling) and keeps native `FileSystem.readAsStringAsync` for non-web. RESULT: web image attachments are encoded without relying on `expo-file-system` web support. EVIDENCE: Not run (not requested). - [ ] **Fix**: Implement multimodal prompt building on server - Modify `buildAgentPrompt` in `session.ts:316-334` to return structured content with images