Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/web/src/chat-artifact-open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
resolveRendererKindFromMediaType,
} from "@/library";
import { artifactPreviewPath, type ArtifactDetail } from "./api";
import { base64ToUtf8 } from "./chat/threads-api";
import type { CanvasArtifactContent } from "./shell/canvas-availability";

/** Decodes a Library artifact detail into the canvas's typed content — the
Expand Down Expand Up @@ -76,7 +77,7 @@ export function artifactContentFromBlob(
id: blobId,
title: part.name,
rendererKind,
content: atob(contentBase64),
content: base64ToUtf8(contentBase64),
};
}

Expand Down
15 changes: 11 additions & 4 deletions apps/web/src/chat/threads-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,19 @@ async function getJson<T>(path: string, schema: (value: unknown) => T | type.err
return parsed;
}

/** Base64 to text as UTF-8. `atob` alone yields one char per byte, which
* turns any non-ASCII body into mojibake. */
export function base64ToUtf8(base64: string): string {
const binary = atob(base64);
return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
}

/** The readable text of an RFC 5322 frame: the bytes after the header
* section, or the first `text/plain` part of a multipart one. */
export function frameBody(raw: string): string {
let decoded: string;
try {
decoded = atob(raw);
decoded = base64ToUtf8(raw);
} catch {
return "";
}
Expand Down Expand Up @@ -263,7 +270,7 @@ function textPart(entity: string): string | undefined {
export function frameAttachments(raw: string): readonly MailAttachment[] {
let decoded: string;
try {
decoded = atob(raw);
decoded = base64ToUtf8(raw);
} catch (cause) {
reportError(cause, { operation: "chat_frame_attachments" });
return [];
Expand Down Expand Up @@ -295,7 +302,7 @@ function attachmentParts(entity: string): MailAttachment[] {

function decodeBase64(body: string): string {
try {
return atob(body.replace(/\s+/g, ""));
return base64ToUtf8(body.replace(/\s+/g, ""));
} catch (cause) {
reportError(cause, { operation: "chat_attachment_decode" });
return "";
Expand All @@ -312,7 +319,7 @@ function extractAddress(raw: string): string {
function toHeaderAddresses(raw: string): string[] {
let decoded: string;
try {
decoded = atob(raw);
decoded = base64ToUtf8(raw);
} catch {
return [];
}
Expand Down
Loading