47 lines
1.7 KiB
TypeScript
47 lines
1.7 KiB
TypeScript
// Helpers for resolving uploaded-image file parts to private-storage objects.
|
|
//
|
|
// Uploaded images live in the (private, RLS-protected) `images` bucket at
|
|
// `${userId}/${conversationId}/${imageId}`. An AI SDK file part carries the
|
|
// image id in its `filename` (`${imageId}.png`); the part's `url` is a stable
|
|
// REFERENCE string, not something to fetch directly. The bytes are resolved
|
|
// from storage by id at the two boundaries that actually need them:
|
|
// * the chat server downloads them to base64 for the model, and
|
|
// * the client downloads them via a signed URL for display.
|
|
//
|
|
// Persisting a base64 data URL in the part (as the AI SDK migration
|
|
// accidentally did) duplicates the whole image into `messages.parts`; a raw
|
|
// `/storage/.../public/...` path (as the backfill wrote) never resolves
|
|
// because the bucket is private. Both are avoided by keeping `url` a
|
|
// reference and resolving by id.
|
|
|
|
export function imageIdFromFilename(
|
|
filename: string | null | undefined,
|
|
): string | null {
|
|
if (!filename) return null;
|
|
return filename.replace(/\.[^.]+$/, '') || null;
|
|
}
|
|
|
|
export function imageStoragePath(
|
|
userId: string,
|
|
conversationId: string,
|
|
imageId: string,
|
|
): string {
|
|
return `${userId}/${conversationId}/${imageId}`;
|
|
}
|
|
|
|
// Canonical reference persisted in a file part's `url`. Kept in the exact
|
|
// shape the 2026-05-18 AI-SDK backfill produced so every row in
|
|
// `messages.parts` carries a single, uniform format. Never fetched directly —
|
|
// see the note above.
|
|
export function imageFilePartUrl(
|
|
userId: string,
|
|
conversationId: string,
|
|
imageId: string,
|
|
): string {
|
|
return `/storage/v1/object/public/images/${imageStoragePath(
|
|
userId,
|
|
conversationId,
|
|
imageId,
|
|
)}`;
|
|
}
|