Files
fuyouawa-comfyui-easytoolkit/web/base64_loader.js
T

276 lines
12 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import { apiGet, apiPost, apiSilent } from "./api_utils.js";
app.registerExtension({
name: "EasyToolkit.Misc.Base64Uploader",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "Base64Uploader") return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
if (onNodeCreated) onNodeCreated.apply(this, arguments);
let uuid_widget = this.widgets?.find(w => w.name === "uuid");
try {
if (uuid_widget && (!uuid_widget.value || uuid_widget.value === "")) {
uuid_widget.value = crypto.randomUUID();
}
} catch (error) {
console.error("Failed to generate UUID:", error);
alert(`Failed to generate UUID: ${error.message}`);
}
uuid_widget.disabled = true;
// Initialize progress tracking
this.uploadProgress = 0; // 0-100
this.isUploading = false;
this.uploadStatus = "Standby..."; // Status message
// Initialize access update timer
this.accessUpdateTimer = null;
// Function to update access time - simplified with apiSilent
const updateAccess = async () => {
const currentUuid = this.widgets?.find(w => w.name === "uuid")?.value;
if (!currentUuid) return;
await apiSilent("/base64_cache_loader/update_access", {
method: "POST",
data: { uuid: currentUuid }
});
};
// Get configuration from backend - simplified with apiGet
try {
const configData = await apiGet("/base64_cache_loader/config");
if (configData.success && configData.access_update_interval > 0) {
const intervalMs = configData.access_update_interval * 1000;
// Immediately update access time on initialization
await updateAccess();
// Start periodic access updates
this.accessUpdateTimer = setInterval(updateAccess, intervalMs);
}
} catch (error) {
console.error("Failed to get config for access updates:", error);
}
this.addWidget("button", "Upload File", null, async () => {
try {
const input = document.createElement("input");
input.type = "file";
input.accept = "*/*";
input.style.display = "none";
input.onchange = async (event) => {
try {
const file = event.target.files[0];
if (!file) return;
uuid_widget = this.widgets?.find(w => w.name === "uuid");
let filename_widget = this.widgets?.find(w => w.name === "filename");
if (!uuid_widget || !filename_widget) {
alert("Cannot find uuid or filename widget.");
return;
}
filename_widget.value = file.name;
// Prepare upload
this.uploadStatus = "Preparing upload...";
this.isUploading = true;
this.uploadProgress = 0;
app.canvas.setDirty(true);
try {
// Use chunked upload to avoid freezing with large files
await this.uploadFileInChunks(file, uuid_widget.value, file.name);
this.uploadStatus = "Upload complete!";
this.isUploading = false;
this.uploadProgress = 100;
app.canvas.setDirty(true);
app.extensionManager.toast.add({
severity: "info",
summary: "Upload successful",
detail: `File ${file.name} uploaded`,
life: 3000
});
// Reset progress after a delay
setTimeout(() => {
this.uploadProgress = 0;
this.uploadStatus = "Standby...";
app.canvas.setDirty(true);
}, 2000);
} catch (error) {
console.error("Upload failed:", error);
this.uploadStatus = `Upload failed: ${error.message}`;
this.isUploading = false;
this.uploadProgress = 0;
app.canvas.setDirty(true);
alert(`Upload failed: ${error.message}`);
}
} catch (error) {
console.error("File selection handling failed:", error);
alert(`File selection handling failed: ${error.message}`);
}
};
input.click(); // Open file selection dialog
} catch (error) {
console.error("Failed to create file selector:", error);
alert(`Failed to create file selector: ${error.message}`);
}
});
// Draw progress bar
const onDrawForeground = this.onDrawForeground;
this.onDrawForeground = function(ctx) {
if (onDrawForeground) {
onDrawForeground.apply(this, arguments);
}
if (this.isUploading && this.uploadProgress > 0) {
const margin = 15;
const barHeight = 20;
const statusTextHeight = 16;
const spacing = 8;
const totalHeight = statusTextHeight + spacing + barHeight;
const barY = this.size[1] - totalHeight - margin;
const statusY = barY - spacing;
const barWidth = this.size[0] - margin * 2;
// Draw status text
ctx.fillStyle = "#cccccc";
ctx.font = "12px Arial";
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
ctx.fillText(this.uploadStatus || "", margin, statusY);
// Draw background
ctx.fillStyle = "#2a2a2a";
ctx.fillRect(margin, barY, barWidth, barHeight);
// Draw progress
const progressWidth = (barWidth * this.uploadProgress) / 100;
const gradient = ctx.createLinearGradient(margin, barY, margin + progressWidth, barY);
gradient.addColorStop(0, "#4a9eff");
gradient.addColorStop(1, "#2d7dd2");
ctx.fillStyle = gradient;
ctx.fillRect(margin, barY, progressWidth, barHeight);
// Draw border
ctx.strokeStyle = "#555";
ctx.lineWidth = 2;
ctx.strokeRect(margin, barY, barWidth, barHeight);
// Draw percentage text
ctx.fillStyle = "#ffffff";
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(`${Math.round(this.uploadProgress)}%`, margin + barWidth / 2, barY + barHeight / 2);
}
};
};
// Clean up timer and context data when node is removed
const onRemoved = nodeType.prototype.onRemoved;
nodeType.prototype.onRemoved = function () {
// Clear the timer
if (this.accessUpdateTimer) {
clearInterval(this.accessUpdateTimer);
this.accessUpdateTimer = null;
}
// Clear the context data - simplified with apiSilent
const uuid_widget = this.widgets?.find(w => w.name === "uuid");
if (uuid_widget && uuid_widget.value) {
apiSilent("/context/remove_key", {
method: "POST",
data: { key: uuid_widget.value }
});
}
if (onRemoved) {
return onRemoved.apply(this, arguments);
}
};
},
});
// Add chunked upload method to node prototype
app.registerExtension({
name: "EasyToolkit.Misc.Base64Uploader.Upload",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "Base64Uploader") return;
nodeType.prototype.uploadFileInChunks = async function(file, uuid, filename) {
try {
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
// Initialize upload - simplified with apiPost
this.uploadProgress = 0;
this.uploadStatus = "Initializing upload...";
app.canvas.setDirty(true);
const initResult = await apiPost("/base64_cache_loader/init_upload", {
uuid: uuid,
filename: filename,
total_chunks: totalChunks,
file_size: file.size
});
if (!initResult.success) {
throw new Error(initResult.error || "Initialization failed");
}
// Upload file in chunks
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
// Update progress
this.uploadProgress = Math.round(((chunkIndex + 1) / totalChunks) * 100);
this.uploadStatus = `Uploading... (${chunkIndex + 1}/${totalChunks})`;
app.canvas.setDirty(true);
// Give UI a chance to update
await new Promise(resolve => setTimeout(resolve, 0));
// Upload chunk with FormData
const formData = new FormData();
formData.append("uuid", uuid);
formData.append("chunk_index", chunkIndex.toString());
formData.append("chunk_data", chunk);
const result = await apiPost("/base64_cache_loader/upload_chunk", formData);
if (!result.success) {
throw new Error(result.error || `Upload chunk ${chunkIndex + 1} failed`);
}
}
// Finalize upload - simplified with apiPost
this.uploadProgress = 100;
this.uploadStatus = "Finalizing upload...";
app.canvas.setDirty(true);
const finalizeResult = await apiPost("/base64_cache_loader/finalize_upload", {
uuid: uuid
});
if (!finalizeResult.success) {
throw new Error(finalizeResult.error || "Finalization failed");
}
} catch (error) {
console.error("Chunked upload failed:", error);
throw error; // Re-throw error for outer handling
}
};
},
});