120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
import { apiFetch } from "../api";
|
|
import { downloadMarkdownAsDocx } from "../scheduled-tasks/markdown-docx";
|
|
import { getFileName } from "../utils/files";
|
|
|
|
import { urlOfArtifact } from "./utils";
|
|
|
|
function filenameFromContentDisposition(header: string | null): string | null {
|
|
if (!header) return null;
|
|
const star = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header);
|
|
if (star) {
|
|
const raw = star[1].replace(/["']/g, "").trim();
|
|
try {
|
|
return decodeURIComponent(raw);
|
|
} catch {
|
|
return raw;
|
|
}
|
|
}
|
|
const plain = /filename="?([^";\n]+)"?/i.exec(header);
|
|
return plain ? plain[1].trim() : null;
|
|
}
|
|
|
|
async function errorMessageFromResponse(res: Response): Promise<string> {
|
|
try {
|
|
const data: unknown = await res.json();
|
|
if (data && typeof data === "object" && "detail" in data) {
|
|
const detail = (data as { detail: unknown }).detail;
|
|
if (typeof detail === "string") return detail;
|
|
if (
|
|
detail &&
|
|
typeof detail === "object" &&
|
|
"message" in detail &&
|
|
typeof (detail as { message: unknown }).message === "string"
|
|
) {
|
|
return (detail as { message: string }).message;
|
|
}
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
return res.statusText || "Request failed";
|
|
}
|
|
|
|
export async function downloadArtifactFile({
|
|
filepath,
|
|
threadId,
|
|
isMock,
|
|
publicShareCode,
|
|
}: {
|
|
filepath: string;
|
|
threadId: string;
|
|
isMock?: boolean;
|
|
publicShareCode?: string;
|
|
}): Promise<void> {
|
|
const url = urlOfArtifact({ filepath, threadId, isMock, publicShareCode, download: true });
|
|
const res = await apiFetch(url);
|
|
if (!res.ok) {
|
|
throw new Error(await errorMessageFromResponse(res));
|
|
}
|
|
const cd = res.headers.get("Content-Disposition");
|
|
const filename =
|
|
filenameFromContentDisposition(cd) ?? getFileName(filepath);
|
|
|
|
const blob = await res.blob();
|
|
const contentType = res.headers.get("Content-Type")?.split(";")[0]?.trim();
|
|
const downloadBlob =
|
|
contentType && blob.type !== contentType
|
|
? new Blob([await blob.arrayBuffer()], { type: contentType })
|
|
: blob;
|
|
const objectUrl = URL.createObjectURL(downloadBlob);
|
|
const a = document.createElement("a");
|
|
a.href = objectUrl;
|
|
a.download = filename;
|
|
a.rel = "noopener";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 30_000);
|
|
}
|
|
|
|
export async function downloadArtifactMarkdownAsWord({
|
|
filepath,
|
|
threadId,
|
|
}: {
|
|
filepath: string;
|
|
threadId: string;
|
|
}): Promise<void> {
|
|
const url = urlOfArtifact({ filepath, threadId, download: false });
|
|
const res = await apiFetch(url);
|
|
if (!res.ok) {
|
|
throw new Error(await errorMessageFromResponse(res));
|
|
}
|
|
|
|
const fileName = getFileName(filepath);
|
|
const title = fileName.replace(/\.(md|markdown)$/i, "");
|
|
await downloadMarkdownAsDocx(await res.text(), fileName, title);
|
|
}
|
|
|
|
export async function openArtifactInNewTab({
|
|
filepath,
|
|
threadId,
|
|
isMock,
|
|
publicShareCode,
|
|
}: {
|
|
filepath: string;
|
|
threadId: string;
|
|
isMock?: boolean;
|
|
publicShareCode?: string;
|
|
}): Promise<void> {
|
|
const url = urlOfArtifact({ filepath, threadId, isMock, publicShareCode, download: false });
|
|
const res = await apiFetch(url);
|
|
if (!res.ok) {
|
|
throw new Error(await errorMessageFromResponse(res));
|
|
}
|
|
const blob = await res.blob();
|
|
const objectUrl = URL.createObjectURL(blob);
|
|
const w = window.open(objectUrl, "_blank", "noopener,noreferrer");
|
|
if (w) w.opener = null;
|
|
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 120_000);
|
|
}
|