| | import { app } from "../../scripts/app.js";
|
| | import { api } from "../../scripts/api.js";
|
| |
|
| |
|
| |
|
| | console.log('[RunpodDirect] v1.0.6');
|
| |
|
| |
|
| | const downloadStates = new Map();
|
| | let downloadQueue = [];
|
| | let isDownloadingAll = false;
|
| | let completedDownloads = 0;
|
| | let totalDownloads = 0;
|
| | let downloadStartTimes = new Map();
|
| |
|
| |
|
| | function formatBytes(bytes) {
|
| | if (bytes === 0) return '0 B';
|
| | const k = 1024;
|
| | const sizes = ['B', 'KB', 'MB', 'GB'];
|
| | const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| | return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
| | }
|
| |
|
| |
|
| | function calculateSpeed(downloadId, downloaded) {
|
| | const startTime = downloadStartTimes.get(downloadId);
|
| | if (!startTime) return '0 MB/s';
|
| |
|
| | const elapsedSeconds = (Date.now() - startTime) / 1000;
|
| | if (elapsedSeconds < 1) return 'Calculating...';
|
| |
|
| | const bytesPerSecond = downloaded / elapsedSeconds;
|
| | return formatBytes(bytesPerSecond) + '/s';
|
| | }
|
| |
|
| |
|
| | api.addEventListener("server_download_progress", ({ detail }) => {
|
| | const { download_id, progress, downloaded, total } = detail;
|
| |
|
| | if (!downloadStartTimes.has(download_id)) {
|
| | downloadStartTimes.set(download_id, Date.now());
|
| | }
|
| |
|
| | const speed = calculateSpeed(download_id, downloaded);
|
| |
|
| | downloadStates.set(download_id, {
|
| | status: 'downloading',
|
| | progress,
|
| | downloaded,
|
| | total,
|
| | speed
|
| | });
|
| |
|
| |
|
| | window.dispatchEvent(new CustomEvent('serverDownloadUpdate', {
|
| | detail: { download_id, ...downloadStates.get(download_id) }
|
| | }));
|
| | });
|
| |
|
| | api.addEventListener("server_download_complete", ({ detail }) => {
|
| | const { download_id, path, size } = detail;
|
| |
|
| |
|
| | if (isDownloadingAll) {
|
| | completedDownloads++;
|
| | console.log(`[RunpodDirect] Progress: ${completedDownloads}/${totalDownloads} completed`);
|
| | }
|
| |
|
| | downloadStates.set(download_id, {
|
| | status: 'completed',
|
| | progress: 100,
|
| | path,
|
| | size
|
| | });
|
| |
|
| | window.dispatchEvent(new CustomEvent('serverDownloadUpdate', {
|
| | detail: { download_id, ...downloadStates.get(download_id) }
|
| | }));
|
| |
|
| | console.log(`Download completed: ${download_id} -> ${path}`);
|
| |
|
| |
|
| | if (isDownloadingAll && completedDownloads >= totalDownloads) {
|
| | console.log('[RunpodDirect] All downloads completed!');
|
| | isDownloadingAll = false;
|
| | showRefreshPrompt();
|
| | }
|
| | });
|
| |
|
| | api.addEventListener("server_download_error", ({ detail }) => {
|
| | const { download_id, error } = detail;
|
| |
|
| |
|
| | if (isDownloadingAll) {
|
| | completedDownloads++;
|
| | console.log(`[RunpodDirect] Progress: ${completedDownloads}/${totalDownloads} completed (1 error)`);
|
| | }
|
| |
|
| | downloadStates.set(download_id, {
|
| | status: 'error',
|
| | error
|
| | });
|
| |
|
| | window.dispatchEvent(new CustomEvent('serverDownloadUpdate', {
|
| | detail: { download_id, ...downloadStates.get(download_id) }
|
| | }));
|
| |
|
| | console.error(`Download error: ${download_id} - ${error}`);
|
| |
|
| |
|
| | if (isDownloadingAll && completedDownloads >= totalDownloads) {
|
| | console.log('[RunpodDirect] All downloads completed!');
|
| | isDownloadingAll = false;
|
| | showRefreshPrompt();
|
| | }
|
| | });
|
| |
|
| |
|
| | async function startServerDownload(url, savePath, filename, markAsQueued = false) {
|
| | try {
|
| | const download_id = `${savePath}/${filename}`;
|
| |
|
| |
|
| | if (markAsQueued) {
|
| | downloadStates.set(download_id, {
|
| | status: 'queued',
|
| | progress: 0
|
| | });
|
| |
|
| | window.dispatchEvent(new CustomEvent('serverDownloadUpdate', {
|
| | detail: { download_id, ...downloadStates.get(download_id) }
|
| | }));
|
| | }
|
| |
|
| | const response = await api.fetchApi("/server_download/start", {
|
| | method: "POST",
|
| | headers: {
|
| | "Content-Type": "application/json",
|
| | },
|
| | body: JSON.stringify({
|
| | url,
|
| | save_path: savePath,
|
| | filename
|
| | })
|
| | });
|
| |
|
| | const result = await response.json();
|
| |
|
| | if (response.ok) {
|
| |
|
| | if (!markAsQueued) {
|
| | downloadStates.set(download_id, {
|
| | status: 'queued',
|
| | progress: 0
|
| | });
|
| |
|
| | window.dispatchEvent(new CustomEvent('serverDownloadUpdate', {
|
| | detail: { download_id, ...downloadStates.get(download_id) }
|
| | }));
|
| | }
|
| |
|
| | return { success: true, download_id };
|
| | } else {
|
| | return { success: false, error: result.error };
|
| | }
|
| | } catch (error) {
|
| | console.error("Failed to start download:", error);
|
| | return { success: false, error: error.message };
|
| | }
|
| | }
|
| |
|
| |
|
| | function getDownloadStatus(downloadId) {
|
| | return downloadStates.get(downloadId) || null;
|
| | }
|
| |
|
| |
|
| | async function pauseDownload(downloadId) {
|
| | try {
|
| | const response = await api.fetchApi("/server_download/pause", {
|
| | method: "POST",
|
| | headers: { "Content-Type": "application/json" },
|
| | body: JSON.stringify({ download_id: downloadId })
|
| | });
|
| |
|
| | const result = await response.json();
|
| | return { success: response.ok, ...result };
|
| | } catch (error) {
|
| | console.error("Failed to pause download:", error);
|
| | return { success: false, error: error.message };
|
| | }
|
| | }
|
| |
|
| |
|
| | async function resumeDownload(downloadId) {
|
| | try {
|
| | const response = await api.fetchApi("/server_download/resume", {
|
| | method: "POST",
|
| | headers: { "Content-Type": "application/json" },
|
| | body: JSON.stringify({ download_id: downloadId })
|
| | });
|
| |
|
| | const result = await response.json();
|
| | return { success: response.ok, ...result };
|
| | } catch (error) {
|
| | console.error("Failed to resume download:", error);
|
| | return { success: false, error: error.message };
|
| | }
|
| | }
|
| |
|
| |
|
| | async function cancelDownload(downloadId) {
|
| | try {
|
| | const response = await api.fetchApi("/server_download/cancel", {
|
| | method: "POST",
|
| | headers: { "Content-Type": "application/json" },
|
| | body: JSON.stringify({ download_id: downloadId })
|
| | });
|
| |
|
| | const result = await response.json();
|
| | return { success: response.ok, ...result };
|
| | } catch (error) {
|
| | console.error("Failed to cancel download:", error);
|
| | return { success: false, error: error.message };
|
| | }
|
| | }
|
| |
|
| |
|
| | async function processDownloadQueue() {
|
| | if (downloadQueue.length === 0) {
|
| | console.log('[RunpodDirect] No downloads in queue');
|
| | return;
|
| | }
|
| |
|
| |
|
| | console.log(`[RunpodDirect] Starting ${downloadQueue.length} downloads (backend will queue and prioritize)`);
|
| |
|
| | const downloadsToStart = [...downloadQueue];
|
| | downloadQueue = [];
|
| |
|
| |
|
| |
|
| | for (const download of downloadsToStart) {
|
| | console.log(`[RunpodDirect] Queuing download ${download.filename}`);
|
| | await startServerDownload(download.url, download.directory, download.filename, true);
|
| | }
|
| |
|
| | console.log(`[RunpodDirect] All ${downloadsToStart.length} downloads queued on backend`);
|
| | }
|
| |
|
| |
|
| | function showRefreshPrompt() {
|
| | const dialog = document.querySelector('[role="dialog"]');
|
| | if (!dialog) return;
|
| |
|
| |
|
| | const dialogContent = dialog.querySelector('.p-dialog-content');
|
| | if (!dialogContent) return;
|
| |
|
| |
|
| | if (document.querySelector('.server-download-refresh-prompt')) {
|
| | console.log('[RunpodDirect] Refresh prompt already shown');
|
| | return;
|
| | }
|
| |
|
| |
|
| | const refreshPrompt = document.createElement('div');
|
| | refreshPrompt.className = 'server-download-refresh-prompt';
|
| | refreshPrompt.style.cssText = `
|
| | margin-top: 20px;
|
| | padding: 16px;
|
| | background: #4caf50;
|
| | color: white;
|
| | border-radius: 8px;
|
| | text-align: center;
|
| | font-weight: 500;
|
| | `;
|
| |
|
| | refreshPrompt.innerHTML = `
|
| | <div style="margin-bottom: 12px;">
|
| | ✅ All models downloaded successfully!
|
| | </div>
|
| | <button class="p-button p-component p-button-sm"
|
| | style="background: white; color: #4caf50; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-weight: 600;"
|
| | onclick="location.reload()">
|
| | Refresh Page
|
| | </button>
|
| | `;
|
| |
|
| | dialogContent.appendChild(refreshPrompt);
|
| | }
|
| |
|
| |
|
| | function createProgressArea(listbox) {
|
| |
|
| | const existing = document.querySelector('.server-download-progress-area');
|
| | if (existing) existing.remove();
|
| |
|
| | const progressArea = document.createElement('div');
|
| | progressArea.className = 'server-download-progress-area';
|
| | progressArea.style.cssText = `
|
| | margin-top: 20px;
|
| | padding: 16px;
|
| | background: var(--p-content-background, #1e1e1e);
|
| | border: 1px solid var(--p-content-border-color, #333);
|
| | border-radius: 8px;
|
| | `;
|
| |
|
| | progressArea.innerHTML = `
|
| | <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
| | <div style="font-weight: 600; color: var(--p-text-color);">
|
| | Download Progress
|
| | </div>
|
| | </div>
|
| | <div id="server-download-overall-progress" style="margin-bottom: 12px; font-size: 13px; color: var(--p-text-muted-color);">
|
| | Overall: 0/${totalDownloads} models completed
|
| | </div>
|
| | <div id="server-download-items-container" style="display: flex; flex-direction: column; gap: 12px;">
|
| | <!-- Individual download progress items will be added here -->
|
| | </div>
|
| | `;
|
| |
|
| | listbox.parentElement.appendChild(progressArea);
|
| |
|
| |
|
| | const updateHandler = (event) => {
|
| | const { download_id, status, progress, downloaded, total, speed } = event.detail;
|
| |
|
| | if (!isDownloadingAll) {
|
| | return;
|
| | }
|
| |
|
| |
|
| | const overallProgress = document.getElementById('server-download-overall-progress');
|
| | if (overallProgress) {
|
| | overallProgress.textContent = `Overall: ${completedDownloads}/${totalDownloads} models completed`;
|
| | }
|
| |
|
| |
|
| | updateDownloadProgressItem(download_id, status, progress, downloaded, total, speed);
|
| | };
|
| |
|
| | window.addEventListener('serverDownloadUpdate', updateHandler);
|
| | }
|
| |
|
| |
|
| | function updateDownloadProgressItem(download_id, status, progress, downloaded, total, speed) {
|
| |
|
| | let item = null;
|
| | let container = null;
|
| | const itemId = `download-item-${download_id.replace(/\//g, '-')}`;
|
| |
|
| | try {
|
| | container = document.getElementById('server-download-items-container');
|
| | if (!container) return;
|
| |
|
| | item = document.getElementById(itemId);
|
| |
|
| |
|
| | if (status === 'queued') {
|
| | if (item) item.remove();
|
| | return;
|
| | }
|
| |
|
| |
|
| | if (status === 'completed' || status === 'error') {
|
| | if (item && !item.dataset.removing) {
|
| | item.dataset.removing = 'true';
|
| | setTimeout(() => {
|
| | try {
|
| | if (item && item.parentNode) item.remove();
|
| | } catch (e) {
|
| | console.error('[RunpodDirect] Error removing progress item:', e);
|
| | }
|
| | }, 2000);
|
| | }
|
| | }
|
| |
|
| |
|
| | if (!item) {
|
| | item = document.createElement('div');
|
| | item.id = itemId;
|
| | item.style.cssText = `
|
| | padding: 12px;
|
| | background: rgba(255, 255, 255, 0.05);
|
| | border-radius: 6px;
|
| | border: 1px solid rgba(255, 255, 255, 0.1);
|
| | `;
|
| | container.appendChild(item);
|
| | }
|
| | } catch (e) {
|
| | console.error('[RunpodDirect] Error in updateDownloadProgressItem:', e);
|
| | return;
|
| | }
|
| |
|
| | try {
|
| |
|
| | let statusIcon = '';
|
| | let statusColor = '#2196F3';
|
| | if (status === 'downloading') {
|
| | statusIcon = '<i class="pi pi-spin pi-spinner" style="margin-right: 6px;"></i>';
|
| | statusColor = '#2196F3';
|
| | } else if (status === 'completed') {
|
| | statusIcon = '<i class="pi pi-check-circle" style="margin-right: 6px;"></i>';
|
| | statusColor = '#4CAF50';
|
| | } else if (status === 'error') {
|
| | statusIcon = '<i class="pi pi-times-circle" style="margin-right: 6px;"></i>';
|
| | statusColor = '#ef4444';
|
| | } else if (status === 'paused') {
|
| | statusIcon = '<i class="pi pi-pause" style="margin-right: 6px;"></i>';
|
| | statusColor = '#FF9800';
|
| | }
|
| |
|
| | const progressPercent = progress || 0;
|
| | const speedText = speed || '--';
|
| | const sizeText = downloaded && total ? `${formatBytes(downloaded)} / ${formatBytes(total)}` : '--';
|
| |
|
| | if (item) {
|
| | item.innerHTML = `
|
| | <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
| | <div style="font-size: 13px; color: ${statusColor}; font-weight: 500; display: flex; align-items: center;">
|
| | ${statusIcon}${download_id}
|
| | </div>
|
| | <div style="font-size: 12px; color: var(--p-text-muted-color);">
|
| | ${progressPercent.toFixed(1)}%
|
| | </div>
|
| | </div>
|
| | <div style="width: 100%; height: 8px; background: rgba(0,0,0,0.3); border-radius: 4px; overflow: hidden; margin-bottom: 6px; border: 1px solid rgba(255,255,255,0.1);">
|
| | <div style="height: 100%; background: linear-gradient(90deg, ${statusColor}, ${statusColor}aa); width: ${progressPercent}%; transition: width 0.3s;"></div>
|
| | </div>
|
| | <div style="display: flex; justify-content: space-between; font-size: 11px; color: var(--p-text-muted-color);">
|
| | <span>Speed: ${speedText}</span>
|
| | <span>${sizeText}</span>
|
| | </div>
|
| | `;
|
| | }
|
| | } catch (e) {
|
| | console.error('[RunpodDirect] Error updating progress item HTML:', e);
|
| | }
|
| | }
|
| |
|
| |
|
| | window.serverDownload = {
|
| | start: startServerDownload,
|
| | getStatus: getDownloadStatus,
|
| | states: downloadStates
|
| | };
|
| |
|
| |
|
| | function setupDialogObserver() {
|
| | console.log('[RunpodDirect] Setting up dialog observer');
|
| |
|
| |
|
| | const observer = new MutationObserver((mutations) => {
|
| | for (const mutation of mutations) {
|
| | if (mutation.addedNodes.length) {
|
| | mutation.addedNodes.forEach(node => {
|
| | if (node.nodeType === Node.ELEMENT_NODE) {
|
| |
|
| | const hasDialog = node.querySelector && (
|
| | node.querySelector('.comfy-missing-models') ||
|
| | node.classList?.contains('comfy-missing-models')
|
| | );
|
| |
|
| | if (hasDialog) {
|
| | console.log('[RunpodDirect] Detected missing models dialog, injecting buttons...');
|
| | setTimeout(() => {
|
| | injectServerDownloadButtons();
|
| | }, 500);
|
| | }
|
| | }
|
| | });
|
| | }
|
| | }
|
| | });
|
| |
|
| |
|
| | observer.observe(document.body, {
|
| | childList: true,
|
| | subtree: true
|
| | });
|
| |
|
| | console.log('[RunpodDirect] Observer active');
|
| | }
|
| |
|
| | function injectServerDownloadButtons() {
|
| | console.log('[RunpodDirect] injectServerDownloadButtons called');
|
| |
|
| |
|
| | const listbox = document.querySelector('.comfy-missing-models');
|
| | if (!listbox) {
|
| | console.log('[RunpodDirect] Missing models listbox not found');
|
| | return;
|
| | }
|
| |
|
| | console.log('[RunpodDirect] Found .comfy-missing-models listbox');
|
| |
|
| |
|
| | if (document.querySelector('.server-download-all-btn')) {
|
| | console.log('[RunpodDirect] Buttons already injected');
|
| | return;
|
| | }
|
| |
|
| | const listItems = listbox.querySelectorAll('.p-listbox-option');
|
| | console.log(`[RunpodDirect] Found ${listItems.length} list items`);
|
| |
|
| | if (listItems.length === 0) {
|
| | console.log('[RunpodDirect] No list items found');
|
| | return;
|
| | }
|
| |
|
| |
|
| | const downloadAllContainer = document.createElement('div');
|
| | downloadAllContainer.style.cssText = 'margin-bottom: 16px; display: flex; justify-content: center;';
|
| |
|
| | const downloadAllBtn = document.createElement('button');
|
| | downloadAllBtn.className = 'server-download-all-btn p-button p-component p-button-sm';
|
| | downloadAllBtn.type = 'button';
|
| | downloadAllBtn.style.cssText = 'background: #2196F3; color: white; border: none; padding: 10px 20px; font-weight: 600;';
|
| |
|
| | const downloadAllLabel = document.createElement('span');
|
| | downloadAllLabel.className = 'p-button-label';
|
| | downloadAllLabel.textContent = `Download All Models to Pod (${listItems.length})`;
|
| | downloadAllBtn.appendChild(downloadAllLabel);
|
| |
|
| | downloadAllBtn.onclick = async (e) => {
|
| | e.stopPropagation();
|
| | downloadAllBtn.disabled = true;
|
| | downloadAllLabel.textContent = 'Starting downloads...';
|
| |
|
| |
|
| | downloadQueue = [];
|
| | const models = [];
|
| |
|
| | listItems.forEach((item) => {
|
| | const labelElement = item.querySelector('[title]');
|
| | if (!labelElement) return;
|
| |
|
| | const label = labelElement.textContent.trim();
|
| | const url = labelElement.getAttribute('title');
|
| | const parts = label.split('/').map(p => p.trim());
|
| | if (parts.length !== 2) return;
|
| |
|
| | const directory = parts[0];
|
| | const filename = parts[1];
|
| |
|
| | models.push({ url, directory, filename });
|
| | });
|
| |
|
| | downloadQueue = [...models];
|
| | totalDownloads = models.length;
|
| | completedDownloads = 0;
|
| | isDownloadingAll = true;
|
| |
|
| |
|
| | createProgressArea(listbox);
|
| |
|
| |
|
| | if (downloadQueue.length > 0) {
|
| | processDownloadQueue();
|
| | }
|
| | };
|
| |
|
| | downloadAllContainer.appendChild(downloadAllBtn);
|
| | listbox.parentElement.insertBefore(downloadAllContainer, listbox);
|
| |
|
| | listItems.forEach((item, index) => {
|
| | console.log(`[RunpodDirect] Processing item ${index + 1}`);
|
| |
|
| |
|
| | if (item.querySelector('.server-download-btn')) {
|
| | console.log(`[RunpodDirect] Item ${index + 1} already has server download button, skipping`);
|
| | return;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | const mainContainer = item.querySelector('.flex.flex-row.items-center.gap-2');
|
| | if (!mainContainer) {
|
| | console.log('[RunpodDirect] Main flex container not found');
|
| | return;
|
| | }
|
| |
|
| | console.log('[RunpodDirect] Found main container');
|
| |
|
| |
|
| | const buttonWrapper = document.createElement('div');
|
| |
|
| |
|
| | const labelElement = item.querySelector('[title]');
|
| | if (!labelElement) {
|
| | console.log('[RunpodDirect] No title element found');
|
| | return;
|
| | }
|
| |
|
| | const label = labelElement.textContent.trim();
|
| | const url = labelElement.getAttribute('title');
|
| | console.log(`[RunpodDirect] Model: ${label}, URL: ${url}`);
|
| |
|
| |
|
| | const parts = label.split('/').map(p => p.trim());
|
| | if (parts.length !== 2) {
|
| | console.log(`[RunpodDirect] Could not parse label format: ${label}`);
|
| | return;
|
| | }
|
| |
|
| | const directory = parts[0];
|
| | const filename = parts[1];
|
| | const download_id = `${directory}/${filename}`;
|
| | console.log(`[RunpodDirect] Creating button for ${download_id}`);
|
| |
|
| |
|
| | const serverDownloadBtn = document.createElement('button');
|
| | serverDownloadBtn.className = 'server-download-btn p-button p-component p-button-outlined p-button-sm';
|
| | serverDownloadBtn.type = 'button';
|
| |
|
| |
|
| | const btnLabel = document.createElement('span');
|
| | btnLabel.className = 'p-button-label';
|
| | btnLabel.textContent = 'Download to Pod';
|
| | serverDownloadBtn.appendChild(btnLabel);
|
| |
|
| |
|
| | const statusIcon = document.createElement('i');
|
| | statusIcon.style.cssText = 'margin-left: 6px; font-size: 14px; display: none;';
|
| | serverDownloadBtn.appendChild(statusIcon);
|
| |
|
| |
|
| | serverDownloadBtn.onclick = async (e) => {
|
| | e.stopPropagation();
|
| | serverDownloadBtn.disabled = true;
|
| | btnLabel.textContent = 'Starting...';
|
| |
|
| | const result = await startServerDownload(url, directory, filename);
|
| |
|
| | if (result.success) {
|
| | btnLabel.textContent = 'Queued';
|
| | statusIcon.className = 'pi pi-clock';
|
| | statusIcon.style.display = 'inline';
|
| | statusIcon.style.color = '#FF9800';
|
| | } else {
|
| | btnLabel.textContent = 'Error';
|
| | statusIcon.className = 'pi pi-times-circle';
|
| | statusIcon.style.display = 'inline';
|
| | statusIcon.style.color = '#ef4444';
|
| | console.error('Download start failed:', result.error);
|
| | }
|
| | };
|
| |
|
| |
|
| | const updateHandler = (event) => {
|
| | if (event.detail.download_id === download_id) {
|
| | const { status, error } = event.detail;
|
| |
|
| | if (status === 'queued') {
|
| | serverDownloadBtn.disabled = true;
|
| | btnLabel.textContent = 'Queued';
|
| | statusIcon.className = 'pi pi-clock';
|
| | statusIcon.style.display = 'inline';
|
| | statusIcon.style.color = '#FF9800';
|
| | } else if (status === 'downloading') {
|
| | serverDownloadBtn.disabled = true;
|
| | btnLabel.textContent = 'Downloading';
|
| | statusIcon.className = 'pi pi-spin pi-spinner';
|
| | statusIcon.style.display = 'inline';
|
| | statusIcon.style.color = '#2196F3';
|
| | } else if (status === 'completed') {
|
| | btnLabel.textContent = 'Completed';
|
| | statusIcon.className = 'pi pi-check-circle';
|
| | statusIcon.style.display = 'inline';
|
| | statusIcon.style.color = '#4caf50';
|
| | serverDownloadBtn.style.borderColor = '#4caf50';
|
| | } else if (status === 'error') {
|
| | btnLabel.textContent = 'Failed';
|
| | statusIcon.className = 'pi pi-times-circle';
|
| | statusIcon.style.display = 'inline';
|
| | statusIcon.style.color = '#ef4444';
|
| | serverDownloadBtn.title = error;
|
| | }
|
| | }
|
| | };
|
| |
|
| | window.addEventListener('serverDownloadUpdate', updateHandler);
|
| |
|
| |
|
| | buttonWrapper.appendChild(serverDownloadBtn);
|
| |
|
| |
|
| | mainContainer.appendChild(buttonWrapper);
|
| | console.log(`[RunpodDirect] Button added to main container for ${download_id}`);
|
| | });
|
| |
|
| | console.log('[RunpodDirect] Button injection complete');
|
| | }
|
| |
|
| |
|
| | app.registerExtension({
|
| | name: "ComfyUI.RunpodDirect",
|
| |
|
| | async setup() {
|
| | console.log("[RunpodDirect] Extension setup starting");
|
| |
|
| |
|
| | setupDialogObserver();
|
| |
|
| |
|
| | setTimeout(() => {
|
| | console.log('[RunpodDirect] Checking for existing dialog...');
|
| | injectServerDownloadButtons();
|
| | }, 1000);
|
| |
|
| |
|
| | setTimeout(() => {
|
| | console.log('[RunpodDirect] Second check for dialog...');
|
| | injectServerDownloadButtons();
|
| | }, 3000);
|
| |
|
| | console.log("[RunpodDirect] Extension setup complete");
|
| | }
|
| | });
|
| |
|