evOo-local/apps/mobile/lib/artifact-open.ts
Claude c008112ab4
fix(mobile): show video, audio and media cards as plain rows
Mobile has no media UI yet, so video, audio and media-card blocks rendered
as empty bubbles or fell out of previews.

- Thread text and reply previews describe video and audio files and the
  media card's title and status.
- The bubble lists video, audio and a finished card's outputs as tappable
  rows that open the file like other attachments.
- Opening video or audio downloads it from the artifact route to the cache
  instead of a base64 RPC, so long recordings open without the server
  holding them in memory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-03 13:53:55 +00:00

57 lines
1.9 KiB
TypeScript

import { attachmentKindOf } from "@rakazo/contracts";
import { File, Paths } from "expo-file-system";
import * as Sharing from "expo-sharing";
import { captureApiRequestContext, rpc } from "./api";
import { artifactCacheFileName } from "./artifact-file";
import { t } from "./i18n";
export type MobileArtifactTarget = { botId: string } | { groupId: string };
async function cacheMobileArtifact(
target: MobileArtifactTarget,
artifactId: string,
mimeType: string,
): Promise<File> {
const kind = attachmentKindOf(mimeType);
if (kind === "video" || kind === "audio") {
// Media can be hundreds of megabytes: stream it to disk instead of a base64 RPC payload.
const { apiBase, headers } = await captureApiRequestContext();
return File.downloadFileAsync(
`${apiBase}/api/artifacts/${encodeURIComponent(artifactId)}`,
new File(Paths.cache, artifactCacheFileName(artifactId, mimeType)),
{ headers, idempotent: true },
);
}
const artifact = await rpc<{ contentBase64: string }>("artifacts/get", {
...target,
artifactId,
});
const file = new File(Paths.cache, artifactCacheFileName(artifactId, mimeType));
file.create({ overwrite: true });
file.write(artifact.contentBase64, { encoding: "base64" });
return file;
}
export async function readMobileArtifactText(
target: MobileArtifactTarget,
artifactId: string,
mimeType: string,
): Promise<string> {
const file = await cacheMobileArtifact(target, artifactId, mimeType);
return file.text();
}
export async function openMobileArtifact(
target: MobileArtifactTarget,
artifactId: string,
name: string,
mimeType: string,
): Promise<void> {
const file = await cacheMobileArtifact(target, artifactId, mimeType);
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(file.uri, { mimeType });
return;
}
throw new Error(t("Saved {name} locally", { name }));
}