woiceatus's picture
fix audio convert
8b5482f
const IMAGE_PREFIX = "image/";
const AUDIO_PREFIX = "audio/";
export async function createAttachments(fileList) {
return Promise.all(Array.from(fileList, (file) => createFileAttachment(file)));
}
export function createLinkAttachment(kind, url) {
const trimmedUrl = String(url || "").trim();
if (!trimmedUrl) {
throw new Error("Enter an image or audio link before adding it.");
}
if (!/^https?:\/\//i.test(trimmedUrl)) {
throw new Error("Attachment links must start with http:// or https://.");
}
return {
id: crypto.randomUUID(),
kind,
name: inferNameFromUrl(trimmedUrl),
sizeLabel: "link",
previewUrl: trimmedUrl,
sourceType: "link",
url: trimmedUrl
};
}
export async function buildAttachmentParts(attachments) {
return Promise.all(attachments.map((attachment) => buildAttachmentPart(attachment)));
}
export function releaseAttachment(attachment) {
if (attachment?.sourceType === "file" && attachment.previewUrl?.startsWith("blob:")) {
URL.revokeObjectURL(attachment.previewUrl);
}
}
export function hydrateAttachment(savedAttachment) {
if (savedAttachment?.sourceType === "link") {
return savedAttachment;
}
if (savedAttachment?.sourceType === "file" && typeof savedAttachment.dataUrl === "string") {
return {
id: savedAttachment.id,
kind: savedAttachment.kind,
name: savedAttachment.name,
sizeLabel: savedAttachment.sizeLabel,
previewUrl: savedAttachment.dataUrl,
sourceType: "file",
dataUrl: savedAttachment.dataUrl,
mimeType: savedAttachment.mimeType
};
}
return null;
}
async function createFileAttachment(file) {
const kind = classifyFile(file);
const dataUrl = await readFileAsDataUrl(file);
return {
id: crypto.randomUUID(),
kind,
name: file.name,
sizeLabel: formatBytes(file.size),
previewUrl: dataUrl,
sourceType: "file",
dataUrl,
mimeType: file.type
};
}
async function buildAttachmentPart(attachment) {
if (attachment.sourceType === "link") {
return attachment.kind === "image"
? { type: "image_url", image_url: { url: attachment.url } }
: { type: "input_audio", input_audio: { url: attachment.url } };
}
if (attachment.kind === "image") {
return {
type: "image_url",
image_url: {
url: attachment.dataUrl ?? await readFileAsDataUrl(attachment.file)
}
};
}
return {
type: "input_audio",
input_audio: {
data: extractBase64(attachment.dataUrl ?? await readFileAsDataUrl(attachment.file)),
format: inferAudioFormat(attachment.file ?? attachment)
}
};
}
function classifyFile(file) {
if (file.type.startsWith(IMAGE_PREFIX)) {
return "image";
}
if (file.type.startsWith(AUDIO_PREFIX)) {
return "audio";
}
throw new Error(`Unsupported file type: ${file.type || file.name}`);
}
function inferAudioFormat(file) {
const mimeType = file?.type ?? file?.mimeType ?? "";
const name = (file?.name ?? "").toLowerCase();
if (mimeType === "audio/mp4" || mimeType === "audio/x-m4a" || name.endsWith(".m4a")) {
return "m4a";
}
if (mimeType === "audio/wav" || mimeType === "audio/x-wav" || name.endsWith(".wav")) {
return "wav";
}
return "mp3";
}
function readFileAsDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result));
reader.onerror = () => reject(new Error("Failed to read the selected file."));
reader.readAsDataURL(file);
});
}
async function readFileAsBase64(file) {
const dataUrl = await readFileAsDataUrl(file);
return extractBase64(dataUrl);
}
function extractBase64(dataUrl) {
return dataUrl.split(",")[1] || "";
}
function formatBytes(value) {
if (value < 1024) {
return `${value} B`;
}
if (value < 1024 * 1024) {
return `${(value / 1024).toFixed(1)} KB`;
}
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
}
function inferNameFromUrl(url) {
try {
const { pathname, hostname } = new URL(url);
const lastSegment = pathname.split("/").filter(Boolean).at(-1);
return lastSegment || hostname;
} catch (_error) {
return "remote-file";
}
}