Initial AI media hub implementation
Some checks failed
build-push / docker (push) Has been cancelled
Some checks failed
build-push / docker (push) Has been cancelled
This commit is contained in:
158
frontend/app.js
Normal file
158
frontend/app.js
Normal file
@@ -0,0 +1,158 @@
|
||||
const statusBar = document.getElementById("statusBar");
|
||||
const statusLabel = document.getElementById("statusLabel");
|
||||
const searchForm = document.getElementById("searchForm");
|
||||
const searchQuery = document.getElementById("searchQuery");
|
||||
const searchResults = document.getElementById("searchResults");
|
||||
const searchWarning = document.getElementById("searchWarning");
|
||||
const dropzone = document.getElementById("dropzone");
|
||||
const fileInput = document.getElementById("fileInput");
|
||||
const uploadResult = document.getElementById("uploadResult");
|
||||
const downloadForm = document.getElementById("downloadForm");
|
||||
const downloadUrl = document.getElementById("downloadUrl");
|
||||
const startTime = document.getElementById("startTime");
|
||||
const endTime = document.getElementById("endTime");
|
||||
const downloadResult = document.getElementById("downloadResult");
|
||||
const cardTemplate = document.getElementById("searchCardTemplate");
|
||||
|
||||
function setStatus(label, progress) {
|
||||
statusLabel.textContent = label;
|
||||
statusBar.style.width = `${Math.max(0, Math.min(100, progress))}%`;
|
||||
}
|
||||
|
||||
function connectWS() {
|
||||
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const socket = new WebSocket(`${protocol}://${window.location.host}/ws`);
|
||||
socket.addEventListener("message", (event) => {
|
||||
const payload = JSON.parse(event.data);
|
||||
if (payload.event !== "progress") {
|
||||
return;
|
||||
}
|
||||
const data = payload.data;
|
||||
setStatus(`${data.type || "task"}: ${data.status}`, Number(data.progress ?? 0));
|
||||
if (data.type === "upload" && data.status === "completed") {
|
||||
uploadResult.textContent = `${data.filename} saved successfully`;
|
||||
}
|
||||
if (data.type === "download" && data.status === "completed") {
|
||||
downloadResult.textContent = data.output || "download completed";
|
||||
}
|
||||
if (data.status === "error") {
|
||||
downloadResult.textContent = data.message || "task failed";
|
||||
}
|
||||
});
|
||||
socket.addEventListener("close", () => {
|
||||
setTimeout(connectWS, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(path, options);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const error = new Error(data.error || "request failed");
|
||||
error.status = response.status;
|
||||
error.data = data;
|
||||
throw error;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function renderResults(results) {
|
||||
searchResults.innerHTML = "";
|
||||
for (const item of results) {
|
||||
const node = cardTemplate.content.firstElementChild.cloneNode(true);
|
||||
node.href = item.link;
|
||||
node.querySelector("img").src = item.thumbnailUrl;
|
||||
node.querySelector("img").alt = item.title;
|
||||
node.querySelector("h3").textContent = item.title;
|
||||
node.querySelector("p").textContent = item.reason;
|
||||
node.querySelector(".source-badge").textContent = item.source;
|
||||
searchResults.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
searchForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
setStatus("searching", 20);
|
||||
searchWarning.classList.add("hidden");
|
||||
try {
|
||||
const data = await api("/api/search", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ query: searchQuery.value }),
|
||||
});
|
||||
renderResults(data.results || []);
|
||||
if (data.warning) {
|
||||
searchWarning.textContent = data.warning;
|
||||
searchWarning.classList.remove("hidden");
|
||||
}
|
||||
setStatus("search complete", 100);
|
||||
} catch (error) {
|
||||
searchWarning.textContent = error.message;
|
||||
searchWarning.classList.remove("hidden");
|
||||
setStatus("search failed", 100);
|
||||
}
|
||||
});
|
||||
|
||||
async function uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
uploadResult.textContent = "uploading...";
|
||||
await api("/api/upload", { method: "POST", body: formData });
|
||||
}
|
||||
|
||||
dropzone.addEventListener("dragover", (event) => {
|
||||
event.preventDefault();
|
||||
dropzone.classList.add("border-white/60", "bg-white/[0.08]");
|
||||
});
|
||||
|
||||
dropzone.addEventListener("dragleave", () => {
|
||||
dropzone.classList.remove("border-white/60", "bg-white/[0.08]");
|
||||
});
|
||||
|
||||
dropzone.addEventListener("drop", async (event) => {
|
||||
event.preventDefault();
|
||||
dropzone.classList.remove("border-white/60", "bg-white/[0.08]");
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (file) {
|
||||
await uploadFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener("change", async () => {
|
||||
const [file] = fileInput.files;
|
||||
if (file) {
|
||||
await uploadFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
downloadForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
downloadResult.textContent = "checking duplicate history...";
|
||||
try {
|
||||
const dup = await api(`/api/history/check?url=${encodeURIComponent(downloadUrl.value)}`);
|
||||
let force = false;
|
||||
if (dup.exists) {
|
||||
force = window.confirm("동일 URL 다운로드 이력이 있습니다. 계속 진행할까요?");
|
||||
if (!force) {
|
||||
downloadResult.textContent = "cancelled";
|
||||
return;
|
||||
}
|
||||
}
|
||||
const data = await api("/api/download", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
url: downloadUrl.value,
|
||||
start: startTime.value,
|
||||
end: endTime.value,
|
||||
force,
|
||||
}),
|
||||
});
|
||||
downloadResult.textContent = data.message;
|
||||
} catch (error) {
|
||||
downloadResult.textContent = error.message;
|
||||
}
|
||||
});
|
||||
|
||||
connectWS();
|
||||
setStatus("idle", 0);
|
||||
Reference in New Issue
Block a user