woiceatus's picture
fix audio convert
8b5482f
import { escapeHtml, renderRichText } from "/chatclient/richText.js";
export function renderAttachments(card, container, summary, attachments, onRemove, onPreview) {
summary.textContent = buildAttachmentSummary(attachments);
container.innerHTML = "";
if (attachments.length === 0) {
container.innerHTML = '<p class="empty-state">Add an image or audio file or link to include it with the next request.</p>';
return;
}
for (const attachment of attachments) {
const card = document.createElement("article");
card.className = "attachment-item";
card.appendChild(renderAttachmentPreview(attachment, onPreview));
card.appendChild(renderAttachmentMeta(attachment));
card.appendChild(renderRemoveButton(attachment.id, onRemove));
container.appendChild(card);
}
}
export function renderResponse(container, requestPayload, responseBody) {
const assistant = responseBody?.choices?.[0]?.message ?? {};
const text = extractAssistantText(assistant);
const audioTranscript = extractAssistantAudioTranscript(assistant);
const images = extractAssistantImages(assistant);
const audioUrl = assistant?.audio?.url || null;
const assistantBody = text || audioTranscript || "No assistant text returned.";
container.innerHTML = "";
container.appendChild(renderInfoCard("Request", describeRequest(requestPayload)));
container.appendChild(renderRichTextCard("Assistant", assistantBody));
if (audioTranscript && !sameText(text, audioTranscript)) {
container.appendChild(renderRichTextCard("Audio Transcript", audioTranscript));
}
for (const imageUrl of images) {
const imageCard = renderInfoCard("Image", imageUrl);
const image = document.createElement("img");
image.src = imageUrl;
image.alt = "assistant output";
image.className = "response-image";
imageCard.appendChild(image);
container.appendChild(imageCard);
}
if (audioUrl) {
const audioCard = renderInfoCard("Audio", audioUrl);
const audio = document.createElement("audio");
audio.controls = true;
audio.src = audioUrl;
audio.className = "response-audio";
audioCard.appendChild(audio);
container.appendChild(audioCard);
}
}
export function setStatus(statusLine, message, isOk = false) {
statusLine.textContent = message;
statusLine.classList.toggle("status-ok", isOk);
statusLine.classList.toggle("status-busy", !isOk && /sending/i.test(message));
}
export function showError(errorToast, message) {
const code = `ERR-${Date.now().toString(36).toUpperCase()}`;
errorToast.hidden = false;
errorToast.textContent = `${message} Click to copy ${code}.`;
errorToast.onclick = async () => {
try {
await navigator.clipboard.writeText(code);
errorToast.textContent = `Copied ${code}.`;
} catch (_error) {
errorToast.textContent = `Copy failed. Error code: ${code}`;
}
};
window.clearTimeout(showError.timer);
showError.timer = window.setTimeout(() => {
errorToast.hidden = true;
}, 10000);
}
showError.timer = 0;
function buildAttachmentSummary(attachments) {
if (attachments.length === 0) {
return "No files added.";
}
const imageCount = attachments.filter((item) => item.kind === "image").length;
const audioCount = attachments.length - imageCount;
return `${attachments.length} file${attachments.length === 1 ? "" : "s"} ready | ${imageCount} image | ${audioCount} audio`;
}
function renderAttachmentPreview(attachment, onPreview) {
const button = document.createElement("button");
button.type = "button";
button.className = "attachment-preview-trigger";
button.setAttribute("aria-label", `Open ${attachment.name}`);
button.addEventListener("click", () => onPreview(attachment));
if (attachment.kind === "image") {
const image = document.createElement("img");
image.src = attachment.previewUrl;
image.alt = attachment.name;
image.className = "attachment-thumb";
button.appendChild(image);
return button;
}
button.innerHTML = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 18V6"></path>
<path d="M8 15a4 4 0 1 0 0-6"></path>
<path d="M16 15a4 4 0 1 0 0-6"></path>
</svg>
<span>Open</span>
`;
button.classList.add("attachment-audio-tile");
return button;
}
function renderAttachmentMeta(attachment) {
const meta = document.createElement("div");
meta.className = "attachment-meta";
meta.innerHTML = `
<strong>${escapeHtml(attachment.name)}</strong>
<span>${escapeHtml(attachment.kind)} | ${escapeHtml(attachment.sourceType)} | ${escapeHtml(attachment.sizeLabel)}</span>
`;
return meta;
}
function renderRemoveButton(attachmentId, onRemove) {
const button = document.createElement("button");
button.type = "button";
button.className = "attachment-remove";
button.textContent = "Remove";
button.addEventListener("click", () => onRemove(attachmentId));
return button;
}
function describeRequest(payload) {
const userContent = payload.messages.at(-1)?.content ?? [];
const textPart = userContent.find((part) => part.type === "text");
const attachmentCount = userContent.filter((part) => part.type !== "text").length;
const audioOutput = payload.audio ? `Audio output: ${payload.audio.voice}` : "Audio output: off";
return `${textPart?.text || "Attachment-only request."}\n\nAttachments: ${attachmentCount}\n${audioOutput}`;
}
function extractAssistantText(message) {
if (typeof message.content === "string") {
return message.content;
}
if (!Array.isArray(message.content)) {
return "";
}
return message.content
.map((part) => part.text || part.output_text || "")
.filter(Boolean)
.join("\n\n");
}
function extractAssistantImages(message) {
if (!Array.isArray(message.content)) {
return [];
}
return message.content
.map((part) => part?.image_url?.proxy_url || part?.image_url?.url)
.filter(Boolean);
}
function extractAssistantAudioTranscript(message) {
return typeof message?.audio?.transcript === "string" ? message.audio.transcript.trim() : "";
}
function sameText(left, right) {
return normalizeText(left) === normalizeText(right);
}
function normalizeText(value) {
return typeof value === "string" ? value.trim().replaceAll(/\s+/g, " ") : "";
}
function renderInfoCard(title, body) {
const card = document.createElement("article");
card.className = "response-block";
card.innerHTML = `<strong>${escapeHtml(title)}</strong><p>${escapeHtml(body)}</p>`;
return card;
}
function renderRichTextCard(title, body) {
const card = document.createElement("article");
card.className = "response-block";
card.innerHTML = `<strong>${escapeHtml(title)}</strong>`;
const bodyElement = document.createElement("div");
bodyElement.className = "rich-response";
renderRichText(bodyElement, body);
card.appendChild(bodyElement);
return card;
}