Implement local server file browsing and selection to bypass client uploads
This commit is contained in:
+267
-6
@@ -595,7 +595,7 @@ window.addEventListener("mousemove", (e) => {
|
||||
|
||||
// Generate single frame preview trigger
|
||||
generatePreviewBtn.addEventListener("click", async () => {
|
||||
if (!uploadFileId) return;
|
||||
if (!uploadFileId && !window.selectedServerFilePath) return;
|
||||
|
||||
const loaderTitle = document.getElementById("preview-loader-title");
|
||||
const loaderContainer = document.getElementById("preview-loader-progress-container");
|
||||
@@ -618,7 +618,8 @@ generatePreviewBtn.addEventListener("click", async () => {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
file_id: uploadFileId,
|
||||
file_id: uploadFileId || null,
|
||||
server_file_path: window.selectedServerFilePath || null,
|
||||
timestamp_sec: parseFloat(timelineSlider.value),
|
||||
model: modelSelect.value,
|
||||
scale: parseInt(scaleRange.value),
|
||||
@@ -801,7 +802,7 @@ function initComparisonSlider() {
|
||||
6. Video Pipeline Jobs Orchestration
|
||||
---------------------------------------------------- */
|
||||
startUpscaleBtn.addEventListener("click", async () => {
|
||||
if (!uploadFileId) return;
|
||||
if (!uploadFileId && !window.selectedServerFilePath && (!window.selectedServerFilePaths || window.selectedServerFilePaths.length === 0)) return;
|
||||
|
||||
startUpscaleBtn.disabled = true;
|
||||
startUpscaleBtn.innerHTML = `<div class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block;"></div> Starting Job...`;
|
||||
@@ -821,12 +822,13 @@ startUpscaleBtn.addEventListener("click", async () => {
|
||||
const aiAudioDenoise = document.getElementById("ai-audio-denoise");
|
||||
|
||||
try {
|
||||
if (window.isBatchMode && window.batchFileIds && window.batchFileIds.length > 0) {
|
||||
if (window.isBatchMode && ((window.batchFileIds && window.batchFileIds.length > 0) || (window.selectedServerFilePaths && window.selectedServerFilePaths.length > 0))) {
|
||||
const res = await fetch("/api/upscale/start/batch", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
file_ids: window.batchFileIds,
|
||||
file_ids: (window.batchFileIds && window.batchFileIds.length > 0) ? window.batchFileIds : null,
|
||||
server_file_paths: (window.selectedServerFilePaths && window.selectedServerFilePaths.length > 0) ? window.selectedServerFilePaths : null,
|
||||
model: modelSelect.value,
|
||||
scale: parseInt(scaleRange.value),
|
||||
tile_size: parseInt(tileRange.value),
|
||||
@@ -857,6 +859,7 @@ startUpscaleBtn.addEventListener("click", async () => {
|
||||
|
||||
window.isBatchMode = false;
|
||||
window.batchFileIds = [];
|
||||
window.selectedServerFilePaths = null;
|
||||
|
||||
document.querySelectorAll(".batch-select-checkbox").forEach(cb => cb.checked = false);
|
||||
const batchConfigBar = document.getElementById("batch-config-bar");
|
||||
@@ -875,7 +878,8 @@ startUpscaleBtn.addEventListener("click", async () => {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
file_id: uploadFileId,
|
||||
file_id: uploadFileId || null,
|
||||
server_file_path: window.selectedServerFilePath || null,
|
||||
model: modelSelect.value,
|
||||
scale: parseInt(scaleRange.value),
|
||||
tile_size: parseInt(tileRange.value),
|
||||
@@ -2077,6 +2081,8 @@ async function initializeApp() {
|
||||
document.querySelectorAll(".step-container").forEach(el => el.classList.remove("active"));
|
||||
document.getElementById("step-upload").classList.add("active");
|
||||
uploadFileId = null;
|
||||
window.selectedServerFilePath = null;
|
||||
window.selectedServerFilePaths = null;
|
||||
loadUploadsBrowser();
|
||||
});
|
||||
}
|
||||
@@ -3052,3 +3058,258 @@ if (reimportForm) {
|
||||
}
|
||||
|
||||
initializeApp();
|
||||
|
||||
/* ----------------------------------------------------
|
||||
7. Server File Browser logic
|
||||
---------------------------------------------------- */
|
||||
let selectedServerFiles = [];
|
||||
let fileBrowserCurrentPath = "";
|
||||
|
||||
window.selectedServerFilePath = null;
|
||||
window.selectedServerFilePaths = null;
|
||||
|
||||
function openServerFileBrowser() {
|
||||
const modal = document.getElementById("file-browser-modal");
|
||||
if (!modal) return;
|
||||
|
||||
selectedServerFiles = [];
|
||||
document.getElementById("file-browser-selected-count").textContent = "0";
|
||||
document.getElementById("file-browser-select-btn").disabled = true;
|
||||
document.getElementById("file-browser-modal-status").textContent = "";
|
||||
|
||||
modal.style.display = "flex";
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
loadFileBrowserDir("");
|
||||
}
|
||||
|
||||
function closeServerFileBrowser() {
|
||||
const modal = document.getElementById("file-browser-modal");
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFileBrowserDir(path) {
|
||||
const listContainer = document.getElementById("file-browser-list");
|
||||
const currentPathInput = document.getElementById("file-browser-current-path");
|
||||
const statusDiv = document.getElementById("file-browser-modal-status");
|
||||
if (!listContainer) return;
|
||||
|
||||
listContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-muted);"><i class="fa-solid fa-spinner fa-spin"></i> Loading...</div>';
|
||||
|
||||
try {
|
||||
const url = `/api/system/browse-dir?path=${encodeURIComponent(path)}&show_files=true`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.detail || "Failed to load directory.");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
fileBrowserCurrentPath = data.current_path;
|
||||
if (currentPathInput) currentPathInput.value = fileBrowserCurrentPath;
|
||||
|
||||
listContainer.innerHTML = "";
|
||||
const entries = data.entries || [];
|
||||
|
||||
if (entries.length === 0) {
|
||||
listContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--text-muted);">Empty directory</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Parent directory entry first
|
||||
const parentEntry = entries.find(e => e.name === "..");
|
||||
const restEntries = entries.filter(e => e.name !== "..");
|
||||
|
||||
const allEntries = parentEntry ? [parentEntry, ...restEntries] : restEntries;
|
||||
|
||||
allEntries.forEach(entry => {
|
||||
const row = document.createElement("div");
|
||||
row.style.display = "flex";
|
||||
row.style.alignItems = "center";
|
||||
row.style.gap = "0.75rem";
|
||||
row.style.padding = "0.6rem 0.85rem";
|
||||
row.style.borderRadius = "6px";
|
||||
row.style.cursor = "pointer";
|
||||
row.style.transition = "background 0.2s";
|
||||
row.style.borderBottom = "1px solid rgba(255, 255, 255, 0.02)";
|
||||
|
||||
const isSelected = selectedServerFiles.includes(entry.path);
|
||||
if (isSelected) {
|
||||
row.style.background = "rgba(var(--primary-rgb), 0.15)";
|
||||
row.style.borderLeft = "3px solid var(--primary)";
|
||||
}
|
||||
|
||||
row.addEventListener("mouseenter", () => {
|
||||
if (!selectedServerFiles.includes(entry.path)) {
|
||||
row.style.background = "rgba(255, 255, 255, 0.05)";
|
||||
}
|
||||
});
|
||||
row.addEventListener("mouseleave", () => {
|
||||
if (!selectedServerFiles.includes(entry.path)) {
|
||||
row.style.background = "";
|
||||
}
|
||||
});
|
||||
|
||||
const icon = document.createElement("i");
|
||||
if (entry.name === "..") {
|
||||
icon.className = "fa-solid fa-arrow-turn-up";
|
||||
icon.style.color = "var(--text-muted)";
|
||||
icon.style.transform = "rotate(-90deg)";
|
||||
} else if (entry.is_dir) {
|
||||
icon.className = "fa-solid fa-folder";
|
||||
icon.style.color = "var(--primary)";
|
||||
} else {
|
||||
icon.className = "fa-solid fa-file-video";
|
||||
icon.style.color = "#00bcd4";
|
||||
}
|
||||
row.appendChild(icon);
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = entry.name;
|
||||
label.style.fontSize = "0.9rem";
|
||||
label.style.fontFamily = "monospace";
|
||||
row.appendChild(label);
|
||||
|
||||
row.addEventListener("click", () => {
|
||||
if (entry.name === "..") {
|
||||
loadFileBrowserDir(entry.path);
|
||||
} else if (entry.is_dir) {
|
||||
loadFileBrowserDir(entry.path);
|
||||
} else {
|
||||
const idx = selectedServerFiles.indexOf(entry.path);
|
||||
if (idx > -1) {
|
||||
selectedServerFiles.splice(idx, 1);
|
||||
row.style.background = "";
|
||||
row.style.borderLeft = "";
|
||||
} else {
|
||||
selectedServerFiles.push(entry.path);
|
||||
row.style.background = "rgba(var(--primary-rgb), 0.15)";
|
||||
row.style.borderLeft = "3px solid var(--primary)";
|
||||
}
|
||||
|
||||
const countSpan = document.getElementById("file-browser-selected-count");
|
||||
if (countSpan) countSpan.textContent = selectedServerFiles.length;
|
||||
|
||||
const selectBtn = document.getElementById("file-browser-select-btn");
|
||||
if (selectBtn) selectBtn.disabled = selectedServerFiles.length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
listContainer.appendChild(row);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
if (statusDiv) statusDiv.textContent = err.message;
|
||||
listContainer.innerHTML = '<div style="text-align: center; padding: 2rem; color: var(--danger);"><i class="fa-solid fa-circle-exclamation"></i> Error loading directory</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Wire up events once DOM is ready or after initializeApp
|
||||
setTimeout(() => {
|
||||
const browseServerBtn = document.getElementById("browse-server-btn");
|
||||
const fileBrowserCloseBtn = document.getElementById("file-browser-modal-close-btn");
|
||||
const fileBrowserCancelBtn = document.getElementById("file-browser-cancel-btn");
|
||||
const fileBrowserSelectBtn = document.getElementById("file-browser-select-btn");
|
||||
const fileBrowserUpBtn = document.getElementById("file-browser-up-btn");
|
||||
|
||||
if (browseServerBtn) {
|
||||
browseServerBtn.addEventListener("click", openServerFileBrowser);
|
||||
}
|
||||
if (fileBrowserCloseBtn) {
|
||||
fileBrowserCloseBtn.addEventListener("click", closeServerFileBrowser);
|
||||
}
|
||||
if (fileBrowserCancelBtn) {
|
||||
fileBrowserCancelBtn.addEventListener("click", closeServerFileBrowser);
|
||||
}
|
||||
|
||||
// Close modal when clicking outside card
|
||||
const fileBrowserModal = document.getElementById("file-browser-modal");
|
||||
if (fileBrowserModal) {
|
||||
fileBrowserModal.addEventListener("click", (e) => {
|
||||
if (e.target === fileBrowserModal) {
|
||||
closeServerFileBrowser();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fileBrowserUpBtn) {
|
||||
fileBrowserUpBtn.addEventListener("click", () => {
|
||||
if (fileBrowserCurrentPath) {
|
||||
const parts = fileBrowserCurrentPath.split("/");
|
||||
if (parts.length > 1) {
|
||||
parts.pop();
|
||||
const parent = parts.join("/") || "/";
|
||||
loadFileBrowserDir(parent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fileBrowserSelectBtn) {
|
||||
fileBrowserSelectBtn.addEventListener("click", async () => {
|
||||
if (selectedServerFiles.length === 0) return;
|
||||
|
||||
fileBrowserSelectBtn.disabled = true;
|
||||
fileBrowserSelectBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Analyzing...';
|
||||
|
||||
try {
|
||||
if (selectedServerFiles.length === 1) {
|
||||
const path = selectedServerFiles[0];
|
||||
const url = `/api/system/file-metadata?path=${encodeURIComponent(path)}`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.detail || "Failed to read file metadata.");
|
||||
}
|
||||
const data = await res.json();
|
||||
|
||||
window.selectedServerFilePath = path;
|
||||
window.selectedServerFilePaths = null;
|
||||
uploadFileId = null;
|
||||
|
||||
useUploadedVideo({
|
||||
file_id: null,
|
||||
original_filename: data.original_filename,
|
||||
size_bytes: data.size_bytes,
|
||||
metadata: data.metadata
|
||||
});
|
||||
} else {
|
||||
const entries = [];
|
||||
for (const path of selectedServerFiles) {
|
||||
const url = `/api/system/file-metadata?path=${encodeURIComponent(path)}`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(`Failed to read metadata for: ${path}`);
|
||||
}
|
||||
const data = await res.json();
|
||||
entries.push({
|
||||
file_id: null,
|
||||
server_file_path: path,
|
||||
original_filename: data.original_filename,
|
||||
size_bytes: data.size_bytes,
|
||||
metadata: data.metadata
|
||||
});
|
||||
}
|
||||
|
||||
window.selectedServerFilePath = null;
|
||||
window.selectedServerFilePaths = selectedServerFiles;
|
||||
uploadFileId = null;
|
||||
|
||||
useBatchUploadedVideos(entries);
|
||||
}
|
||||
closeServerFileBrowser();
|
||||
} catch (err) {
|
||||
const statusDiv = document.getElementById("file-browser-modal-status");
|
||||
if (statusDiv) statusDiv.textContent = err.message;
|
||||
} finally {
|
||||
fileBrowserSelectBtn.disabled = false;
|
||||
fileBrowserSelectBtn.innerHTML = 'Select File(s)';
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user