Add in-app debug log panel
build-push / docker (push) Successful in 4m17s

This commit is contained in:
AI Assistant
2026-03-13 16:50:51 +09:00
parent 823bf12b6f
commit 8ed1e84772
4 changed files with 191 additions and 1 deletions
+122
View File
@@ -30,6 +30,13 @@ const startLabel = document.getElementById("startLabel");
const endLabel = document.getElementById("endLabel");
const setStartFromPreview = document.getElementById("setStartFromPreview");
const setEndFromPreview = document.getElementById("setEndFromPreview");
const debugToggle = document.getElementById("debugToggle");
const debugPanel = document.getElementById("debugPanel");
const closeDebugPanel = document.getElementById("closeDebugPanel");
const clearLogs = document.getElementById("clearLogs");
const downloadLogs = document.getElementById("downloadLogs");
const debugLogList = document.getElementById("debugLogList");
const debugSummary = document.getElementById("debugSummary");
let pendingDownload = null;
let cropStart = 0;
@@ -38,10 +45,50 @@ let cropMax = 0;
let activeThumb = null;
const activePlatforms = new Set(["envato", "artgrid", "google video"]);
const hlsInstances = new WeakMap();
const debugEntries = [];
function setStatus(label, progress) {
statusLabel.textContent = label;
statusBar.style.width = `${Math.max(0, Math.min(100, progress))}%`;
logEvent("status", { label, progress });
}
function logEvent(type, payload) {
const entry = {
ts: new Date().toISOString(),
type,
payload,
};
debugEntries.push(entry);
if (debugEntries.length > 400) {
debugEntries.shift();
}
renderLogs();
}
function safeStringify(value) {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
function renderLogs() {
debugSummary.textContent = `${debugEntries.length} events captured`;
debugLogList.innerHTML = "";
for (const entry of debugEntries.slice().reverse()) {
const node = document.createElement("div");
node.className = "debug-entry";
node.innerHTML = `
<div class="debug-entry__meta">
<span>${entry.type}</span>
<span>${entry.ts}</span>
</div>
<div class="debug-entry__payload">${safeStringify(entry.payload)}</div>
`;
debugLogList.appendChild(node);
}
}
function toClock(totalSeconds) {
@@ -101,6 +148,7 @@ function syncPlatformButtons() {
button.classList.toggle("text-zinc-300", !active);
button.classList.toggle("border-white/20", !active);
}
logEvent("platforms:update", { active: Array.from(activePlatforms) });
}
function connectWS() {
@@ -109,8 +157,10 @@ function connectWS() {
socket.addEventListener("message", (event) => {
const payload = JSON.parse(event.data);
if (payload.event !== "progress") {
logEvent("ws:message:ignored", payload);
return;
}
logEvent("ws:message", payload);
const data = payload.data;
const label = data.status || `${data.type || "task"} in progress`;
setStatus(label, Number(data.progress ?? 0));
@@ -125,13 +175,32 @@ function connectWS() {
}
});
socket.addEventListener("close", () => {
logEvent("ws:close", { reason: "socket closed" });
setTimeout(connectWS, 1000);
});
socket.addEventListener("open", () => {
logEvent("ws:open", { url: socket.url });
});
socket.addEventListener("error", () => {
logEvent("ws:error", { url: socket.url });
});
}
async function api(path, options = {}) {
logEvent("api:request", {
path,
method: options.method || "GET",
hasBody: Boolean(options.body),
bodyPreview: typeof options.body === "string" ? options.body.slice(0, 800) : "[non-string body]",
});
const response = await fetch(path, options);
const data = await response.json().catch(() => ({}));
logEvent("api:response", {
path,
status: response.status,
ok: response.ok,
body: data,
});
if (!response.ok) {
const error = new Error(data.error || "request failed");
error.status = response.status;
@@ -144,6 +213,7 @@ async function api(path, options = {}) {
function attachVideoSource(video, src) {
detachVideoSource(video);
if (!src) {
logEvent("preview:attach:skipped", { reason: "empty src" });
return;
}
if (src.endsWith(".m3u8") && window.Hls && window.Hls.isSupported()) {
@@ -151,9 +221,11 @@ function attachVideoSource(video, src) {
hls.loadSource(src);
hls.attachMedia(video);
hlsInstances.set(video, hls);
logEvent("preview:attach:hls", { src });
return;
}
video.src = src;
logEvent("preview:attach:file", { src });
}
function detachVideoSource(video) {
@@ -161,6 +233,7 @@ function detachVideoSource(video) {
if (existing) {
existing.destroy();
hlsInstances.delete(video);
logEvent("preview:detach:hls", { ok: true });
}
video.removeAttribute("src");
video.load();
@@ -188,11 +261,13 @@ function renderResults(results) {
previewVideo.poster = item.thumbnailUrl || "";
const mediaArea = node.querySelector(".relative");
mediaArea.addEventListener("mouseenter", () => {
logEvent("preview:hover:start", { title: item.title, source: item.source, previewVideoUrl: item.previewVideoUrl });
overlays.forEach((overlay) => overlay.classList.add("hidden"));
previewVideo.classList.remove("hidden");
previewVideo.play().catch(() => {});
});
mediaArea.addEventListener("mouseleave", () => {
logEvent("preview:hover:end", { title: item.title, source: item.source });
previewVideo.pause();
previewVideo.currentTime = 0;
previewVideo.classList.add("hidden");
@@ -219,6 +294,7 @@ searchForm.addEventListener("submit", async (event) => {
searchWarning.textContent = data.warning;
searchWarning.classList.remove("hidden");
}
logEvent("search:completed", { results: data.results?.length || 0, queries: data.queries || [] });
setStatus("search complete", 100);
} catch (error) {
searchWarning.textContent = error.message;
@@ -234,12 +310,14 @@ async function uploadFile(file) {
uploadResult.textContent = "uploading...";
try {
await api("/api/upload", { method: "POST", body: formData });
logEvent("upload:completed", { fileName: file.name, size: file.size });
} catch (error) {
uploadResult.textContent = error.message;
}
}
function openPreviewModal(preview) {
logEvent("preview:modal:open", preview);
previewTitle.textContent = preview.title;
previewThumbnail.src = preview.thumbnail;
previewThumbnail.alt = preview.title;
@@ -271,6 +349,7 @@ function openPreviewModal(preview) {
}
function closeModal() {
logEvent("preview:modal:close", { title: previewTitle.textContent });
previewVideo.pause();
detachVideoSource(previewVideo);
previewMediaFrame.style.aspectRatio = "";
@@ -332,6 +411,7 @@ downloadForm.addEventListener("submit", async (event) => {
downloadResult.textContent = "preview loaded";
} catch (error) {
downloadResult.textContent = error.message;
logEvent("download:preview:error", { message: error.message, data: error.data || null });
}
});
@@ -355,6 +435,7 @@ confirmDownload.addEventListener("click", async () => {
downloadResult.textContent = data.message;
} catch (error) {
downloadResult.textContent = error.message;
logEvent("download:start:error", { message: error.message, data: error.data || null });
}
});
@@ -399,11 +480,21 @@ for (const [thumb, name] of [[startThumb, "start"], [endThumb, "end"]]) {
});
}
previewVideo.addEventListener("loadedmetadata", () => {
logEvent("preview:modal:loadedmetadata", {
width: previewVideo.videoWidth,
height: previewVideo.videoHeight,
src: previewVideo.currentSrc || previewVideo.src,
});
if (previewVideo.videoWidth > 0 && previewVideo.videoHeight > 0) {
previewMediaFrame.style.aspectRatio = `${previewVideo.videoWidth} / ${previewVideo.videoHeight}`;
}
});
previewThumbnail.addEventListener("load", () => {
logEvent("preview:thumbnail:loaded", {
width: previewThumbnail.naturalWidth,
height: previewThumbnail.naturalHeight,
src: previewThumbnail.currentSrc || previewThumbnail.src,
});
if (!previewVideo.src && previewThumbnail.naturalWidth > 0 && previewThumbnail.naturalHeight > 0) {
previewMediaFrame.style.aspectRatio = `${previewThumbnail.naturalWidth} / ${previewThumbnail.naturalHeight}`;
}
@@ -421,6 +512,37 @@ for (const button of platformToggles) {
});
}
debugToggle.addEventListener("click", () => {
debugPanel.classList.remove("hidden");
logEvent("debug:panel:open", {});
});
closeDebugPanel.addEventListener("click", () => {
debugPanel.classList.add("hidden");
});
clearLogs.addEventListener("click", () => {
debugEntries.length = 0;
renderLogs();
});
downloadLogs.addEventListener("click", () => {
const blob = new Blob(
[debugEntries.map((entry) => `[${entry.ts}] ${entry.type}\n${safeStringify(entry.payload)}\n`).join("\n")],
{ type: "text/plain;charset=utf-8" },
);
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = `ai-media-hub-${new Date().toISOString().replace(/[:.]/g, "-")}.log`;
anchor.click();
URL.revokeObjectURL(url);
});
window.addEventListener("error", (event) => {
logEvent("window:error", { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno });
});
window.addEventListener("unhandledrejection", (event) => {
logEvent("window:unhandledrejection", { reason: String(event.reason) });
});
connectWS();
syncPlatformButtons();
setStatus(`active platforms: ${Array.from(activePlatforms).join(", ")}`, 0);
logEvent("app:ready", { activePlatforms: Array.from(activePlatforms) });