import { t } from "@lingui/core/macro"; import { ArtifactSchema, ATTACHMENT_MAX_BYTES, artifactMaxBytes, attachmentKindOf, } from "@rakazo/contracts"; import type { ArtifactTarget } from "./artifact-open"; import { rpc, withSpaceHeaders } from "./rpc"; type UploadedArtifact = ReturnType; /** Largest file of this type the composer accepts. */ export function attachmentMaxBytes(mimeType: string): number { return artifactMaxBytes(mimeType); } /** Media and anything above the inline RPC cap upload as a raw stream instead of base64. */ export function usesStreamingUpload(size: number, mimeType: string): boolean { const kind = attachmentKindOf(mimeType); return size > ATTACHMENT_MAX_BYTES || kind === "video" || kind === "audio"; } export async function uploadAttachment( target: ArtifactTarget, file: File, mimeType: string, ): Promise { if (usesStreamingUpload(file.size, mimeType)) { return uploadArtifactFile(target, file, file.name, mimeType); } const contentBase64 = await readFileAsBase64(file); return rpc.artifacts.create({ ...target, name: file.name, mimeType, contentBase64 }); } /** Uploads a file as a raw streamed body to the artifact route. */ export async function uploadArtifactFile( target: ArtifactTarget, file: Blob, name: string, mimeType: string, ): Promise { const query = new URLSearchParams({ ...target, name }); const response = await fetch(`/api/artifacts?${query}`, { method: "POST", headers: withSpaceHeaders({ "content-type": mimeType }), credentials: "include", body: file, }); const body: unknown = await response.json().catch(() => null); const artifact = ArtifactSchema.safeParse(body); if (response.ok && artifact.success) return artifact.data; const error = (body as { error?: unknown } | null)?.error; throw new Error(typeof error === "string" ? error : t`Could not upload ${name}. Try again.`); } function readFileAsBase64(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const result = typeof reader.result === "string" ? reader.result : ""; const base64 = result.includes(",") ? (result.split(",")[1] ?? "") : result; resolve(base64); }; reader.onerror = () => reject(reader.error ?? new Error("Failed to read file")); reader.readAsDataURL(file); }); }