Whisper / static /app.js
basyx's picture
Create app.js
7c5359f verified
Raw
History Blame Contribute Delete
5.62 kB
/* =========================================================
BASYX V11 UI ENGINE
Production-grade frontend runtime
========================================================= */
const API_BASE = "";
/* =========================================================
STATE
========================================================= */
const state = {
currentTask: "transcribe",
loading: false,
};
/* =========================================================
SAFE ELEMENT ACCESS
========================================================= */
function el(id) {
const node = document.getElementById(id);
if (!node) console.warn(`[UI] Missing element: ${id}`);
return node;
}
/* =========================================================
NAVIGATION SYSTEM
========================================================= */
function nav(task) {
state.currentTask = task;
document.querySelectorAll(".section").forEach((s) =>
s.classList.remove("active")
);
const page = el(task);
if (page) page.classList.add("active");
document.querySelectorAll(".nav-item").forEach((n) =>
n.classList.remove("active")
);
if (event && event.target) {
event.target.classList.add("active");
}
location.hash = task;
}
/* Restore on reload */
window.addEventListener("load", () => {
const hash = location.hash.replace("#", "");
if (hash && el(hash)) nav(hash);
});
/* =========================================================
UI LOADING STATE
========================================================= */
function setLoading(task, isLoading) {
const btn = el(`btn-${task}`);
if (!btn) return;
btn.disabled = isLoading;
btn.innerText = isLoading ? "Processing..." : "Execute";
}
/* =========================================================
ERROR HANDLING
========================================================= */
async function safeFetch(url, options, retries = 2) {
try {
const res = await fetch(url, options);
if (!res.ok) {
const text = await res.text();
throw new Error(`HTTP ${res.status}: ${text}`);
}
return res;
} catch (err) {
if (retries > 0) {
await new Promise((r) => setTimeout(r, 800));
return safeFetch(url, options, retries - 1);
}
throw err;
}
}
/* =========================================================
RESPONSE PARSER (JSON / BLOB / VIDEO)
========================================================= */
async function parseResponse(res, task) {
const contentType = res.headers.get("content-type") || "";
// VIDEO OR FILE OUTPUT
if (
contentType.includes("video") ||
contentType.includes("octet-stream")
) {
const blob = await res.blob();
return {
type: "blob",
url: URL.createObjectURL(blob),
};
}
// JSON OUTPUT
const data = await res.json();
return {
type: "json",
data,
};
}
/* =========================================================
INPUT COLLECTION
========================================================= */
function collectInput(task) {
const file = el(`file-${task}`)?.files?.[0];
const url = el(`url-${task}`)?.value?.trim();
const fd = new FormData();
if (file) fd.append("file", file);
if (url) fd.append("url_input", url);
return fd;
}
/* =========================================================
CORE EXECUTION
========================================================= */
async function run(task) {
if (state.loading) return;
state.loading = true;
setLoading(task, true);
try {
const fd = collectInput(task);
const res = await safeFetch(`${API_BASE}/execute/${task}`, {
method: "POST",
body: fd,
});
const parsed = await parseResponse(res, task);
renderOutput(task, parsed);
} catch (err) {
console.error(err);
renderError(task, err.message);
} finally {
state.loading = false;
setLoading(task, false);
}
}
/* =========================================================
OUTPUT RENDERING
========================================================= */
function renderOutput(task, result) {
const outputBox = el(`result-${task}`);
const videoBox = el(`video-${task}`);
if (result.type === "blob") {
if (videoBox) {
videoBox.src = result.url;
videoBox.style.display = "block";
}
return;
}
if (outputBox) {
outputBox.textContent = JSON.stringify(result.data, null, 2);
}
}
/* =========================================================
ERROR UI
========================================================= */
function renderError(task, message) {
const outputBox = el(`result-${task}`);
if (outputBox) {
outputBox.textContent = `ERROR:\n${message}`;
outputBox.style.color = "#ff4d4d";
}
}
/* =========================================================
HEALTH CHECK (optional future UI use)
========================================================= */
async function checkHealth() {
try {
const res = await fetch("/health");
return await res.json();
} catch {
return { status: "offline" };
}
}
/* =========================================================
MOBILE SIDEBAR TOGGLE
========================================================= */
function toggleSidebar() {
const sidebar = document.querySelector(".sidebar");
if (!sidebar) return;
sidebar.classList.toggle("open");
}
/* Close sidebar on nav click (mobile UX) */
document.addEventListener("click", (e) => {
if (e.target.classList.contains("nav-item")) {
const sidebar = document.querySelector(".sidebar");
if (sidebar) sidebar.classList.remove("open");
}
});