| author | |
| committer | |
| log | 2a33a18716854b8d07378008cf3101ad999209ae |
| tree | 90ac69468b735187863c075bf415cec620dc75c5 |
| parent | bde0a4c0dd2d420a5fd71f39448d5db1bab078da |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 48 insertions(+), 4 deletions(-)
apps/mobile/src/lib/composerImages.test.ts+31-1| ... | @@ -36,7 +36,37 @@ vi.mock("./uuid", () => ({ | ... | @@ -36,7 +36,37 @@ vi.mock("./uuid", () => ({ |
| 36 | uuidv4: () => "attachment-id", | 36 | uuidv4: () => "attachment-id", |
| 37 | })); | 37 | })); |
| 38 | 38 | ||
| 39 | import { convertPastedImagesToAttachments, isOwnedPastedImageUri } from "./composerImages"; | 39 | import { |
| 40 | convertPastedImagesToAttachments, | ||
| 41 | isOwnedPastedImageUri, | ||
| 42 | toUploadChatImageAttachments, | ||
| 43 | } from "./composerImages"; | ||
| 44 | |||
| 45 | describe("toUploadChatImageAttachments", () => { | ||
| 46 | it("strips client draft id and previewUri for the startTurn wire shape", () => { | ||
| 47 | expect( | ||
| 48 | toUploadChatImageAttachments([ | ||
| 49 | { | ||
| 50 | id: "client-draft-id", | ||
| 51 | type: "image", | ||
| 52 | name: "pasted-image.png", | ||
| 53 | mimeType: "image/png", | ||
| 54 | sizeBytes: 12, | ||
| 55 | dataUrl: "data:image/png;base64,AA==", | ||
| 56 | previewUri: "file:///tmp/preview.png", | ||
| 57 | }, | ||
| 58 | ]), | ||
| 59 | ).toEqual([ | ||
| 60 | { | ||
| 61 | type: "image", | ||
| 62 | name: "pasted-image.png", | ||
| 63 | mimeType: "image/png", | ||
| 64 | sizeBytes: 12, | ||
| 65 | dataUrl: "data:image/png;base64,AA==", | ||
| 66 | }, | ||
| 67 | ]); | ||
| 68 | }); | ||
| 69 | }); | ||
| 40 | 70 | ||
| 41 | describe("native pasted image cleanup", () => { | 71 | describe("native pasted image cleanup", () => { |
| 42 | beforeEach(() => { | 72 | beforeEach(() => { |
apps/mobile/src/lib/composerImages.ts+13| ... | @@ -10,6 +10,19 @@ export interface DraftComposerImageAttachment extends UploadChatImageAttachment | ... | @@ -10,6 +10,19 @@ export interface DraftComposerImageAttachment extends UploadChatImageAttachment |
| 10 | readonly previewUri: string; | 10 | readonly previewUri: string; |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | /** Wire shape for startTurn: pure uploads without client draft id / previewUri. */ | ||
| 14 | export function toUploadChatImageAttachments( | ||
| 15 | attachments: ReadonlyArray<DraftComposerImageAttachment>, | ||
| 16 | ): ReadonlyArray<UploadChatImageAttachment> { | ||
| 17 | return attachments.map((attachment) => ({ | ||
| 18 | type: attachment.type, | ||
| 19 | name: attachment.name, | ||
| 20 | mimeType: attachment.mimeType, | ||
| 21 | sizeBytes: attachment.sizeBytes, | ||
| 22 | dataUrl: attachment.dataUrl, | ||
| 23 | })); | ||
| 24 | } | ||
| 25 | |||
| 13 | const OWNED_PASTED_IMAGE_DIRECTORY = "t3-composer-paste"; | 26 | const OWNED_PASTED_IMAGE_DIRECTORY = "t3-composer-paste"; |
| 14 | 27 | ||
| 15 | function estimateBase64ByteSize(base64: string): number { | 28 | function estimateBase64ByteSize(base64: string): number { |
apps/mobile/src/lib/projectThreadStartTurn.ts+2-2| ... | @@ -8,7 +8,7 @@ import { | ... | @@ -8,7 +8,7 @@ import { |
| 8 | type RuntimeMode, | 8 | type RuntimeMode, |
| 9 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 10 | 10 | ||
| 11 | import type { DraftComposerImageAttachment } from "./composerImages"; | 11 | import { toUploadChatImageAttachments, type DraftComposerImageAttachment } from "./composerImages"; |
| 12 | 12 | ||
| 13 | export function deriveThreadTitleFromPrompt(value: string): string { | 13 | export function deriveThreadTitleFromPrompt(value: string): string { |
| 14 | const trimmed = value.trim(); | 14 | const trimmed = value.trim(); |
| ... | @@ -55,7 +55,7 @@ export function buildProjectThreadStartTurnInput(spec: ProjectThreadStartTurnSpe | ... | @@ -55,7 +55,7 @@ export function buildProjectThreadStartTurnInput(spec: ProjectThreadStartTurnSpe |
| 55 | messageId: MessageId.make(spec.messageId), | 55 | messageId: MessageId.make(spec.messageId), |
| 56 | role: "user" as const, | 56 | role: "user" as const, |
| 57 | text: spec.text, | 57 | text: spec.text, |
| 58 | attachments: spec.attachments, | 58 | attachments: toUploadChatImageAttachments(spec.attachments), |
| 59 | }, | 59 | }, |
| 60 | modelSelection: spec.modelSelection, | 60 | modelSelection: spec.modelSelection, |
| 61 | titleSeed: title, | 61 | titleSeed: title, |
apps/mobile/src/state/use-thread-outbox-drain.ts+2-1| ... | @@ -17,6 +17,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; | ... | @@ -17,6 +17,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; |
| 17 | 17 | ||
| 18 | import { scopedThreadKey } from "../lib/scopedEntities"; | 18 | import { scopedThreadKey } from "../lib/scopedEntities"; |
| 19 | import { buildProjectThreadStartTurnInput } from "../lib/projectThreadStartTurn"; | 19 | import { buildProjectThreadStartTurnInput } from "../lib/projectThreadStartTurn"; |
| 20 | import { toUploadChatImageAttachments } from "../lib/composerImages"; | ||
| 20 | import { randomHex } from "../lib/uuid"; | 21 | import { randomHex } from "../lib/uuid"; |
| 21 | import { appAtomRegistry } from "./atom-registry"; | 22 | import { appAtomRegistry } from "./atom-registry"; |
| 22 | import { useProjects, useThreadShells } from "./entities"; | 23 | import { useProjects, useThreadShells } from "./entities"; |
| ... | @@ -221,7 +222,7 @@ export function useThreadOutboxDrain(): void { | ... | @@ -221,7 +222,7 @@ export function useThreadOutboxDrain(): void { |
| 221 | messageId: queuedMessage.messageId, | 222 | messageId: queuedMessage.messageId, |
| 222 | role: "user", | 223 | role: "user", |
| 223 | text: queuedMessage.text, | 224 | text: queuedMessage.text, |
| 224 | attachments: queuedMessage.attachments, | 225 | attachments: toUploadChatImageAttachments(queuedMessage.attachments), |
| 225 | }, | 226 | }, |
| 226 | modelSelection: settings.modelSelection, | 227 | modelSelection: settings.modelSelection, |
| 227 | runtimeMode: settings.runtimeMode, | 228 | runtimeMode: settings.runtimeMode, |