Rebranding

This commit is contained in:
Fred N. Garvin, Esq.
2026-01-11 22:32:27 -06:00
parent f7cc02cccb
commit 80909269c8
5 changed files with 99 additions and 109 deletions
+8
View File
@@ -0,0 +1,8 @@
__pycache__/
*.pyc
.vscode/
.devcontainer/
.gemini/
*.egg-info/
dist/
build/
+40 -58
View File
@@ -1,73 +1,59 @@
# ComfyUI RunpodDirect
# ComfyUI-AutoModelDownloader
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![GitHub](https://img.shields.io/github/stars/MadiatorLabs/ComfyUI-RunpodDirect?style=social)](https://github.com/MadiatorLabs/ComfyUI-RunpodDirect)
Direct model downloads to your Runpod pod with blazing-fast multi-connection support. No more downloading models to your local machine and re-uploading!
A pretty straightforward rebranding of [ComfyUI-RunpodDirect](https://github.com/MadiatorLabs/ComfyUI-RunpodDirect) with the Runpod bits removed in order to make the addon ideally suited for "offline" use.
## Features
- **Multi-Connection Downloads**: Up to 4x faster with parallel chunk downloading (4 connections per file)
- **Pause/Resume/Cancel**: Full control over active downloads
- **Download All Models**: Queue and download all missing models sequentially with one click
- **Real-Time Progress**: Live progress tracking with download speed, file size, and completion percentage
## Installation
1. Clone or download this repository into your ComfyUI `custom_nodes` directory:
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/MadiatorLabs/ComfyUI-RunpodDirect.git
```
Or manually:
- Create a folder: `ComfyUI/custom_nodes/ComfyUI-RunpodDirect`
- Copy all files from this repository into that folder
2. Restart ComfyUI
3. **Hard refresh your browser** to load the new extension:
- Windows/Linux: `Ctrl + Shift + R` or `Ctrl + F5`
- Mac: `Cmd + Shift + R`
- Or: Open DevTools (F12) → Right-click refresh → "Empty Cache and Hard Reload"
4. Verify the version loaded by checking the browser console (F12) for:
```
[RunpodDirect] v1.0.0
```
- **Direct Downloads**: Download models directly to your ComfyUI instance.
- **Background Downloads**: Downloads happen in the background with progress tracking.
- **Multi-Connection Support**: Accelerated downloads using multiple connections.
- **Cross-Platform**: Works on Linux, Windows, and macOS.
- **Secure**: Validates filenames and paths to prevent security issues.
- **Privacy Focused**: No external tracking or analytics.
## Usage
### Via Missing Models Dialog
1. Load a workflow that references missing models
1. Load a workflow that references missing models.
2. When the "Missing Models" dialog appears, you'll see:
- **Download All Models to Pod** button (downloads all models sequentially)
- **Download All Models** button (downloads all models sequentially)
- Individual buttons for each model:
- **Download** (browser download - original functionality)
- **Copy URL** (copy model URL - original functionality)
- **Download to Pod** (NEW - downloads directly to server)
- **Download** (browser download)
- **Copy URL** (copy model URL)
- **Download to Comfy** (NEW - downloads directly to the appropriate Comfy directory)
3. **Option A: Download All Models**
- Click "Download All Models to Pod (X)" button
- Click "Download All Models" button.
- Watch the progress area showing:
- Current file being downloaded
- Overall progress (X/Y completed)
- Download speed and file size
- Pause/Resume/Cancel controls
- Current file being downloaded.
- Overall progress.
- Download speed and file size.
- Pause/Resume/Cancel controls.
4. **Option B: Download Individual Model**
- Click "Download to Pod" button for a specific model
- Button shows status with icons (spinner → checkmark/error)
- Download happens in the background with multi-connection support
- Click "Download to Comfy" button for a specific model.
- Button shows status with icons (spinner → checkmark/error).
- Download happens in the background.
## Security
## Installation
- Only allows downloads from whitelisted sources (as defined in ComfyUI's frontend)
- Downloads are saved to ComfyUI's model directories only
- Prevents overwriting existing files
- Validates all input parameters
### ComfyUI Manager (Recommended)
1. Search for "ComfyUI-AutoModelDownloader" in ComfyUI Manager.
2. Install the node.
3. Restart ComfyUI.
### Manual Installation
1. Navigate to your ComfyUI `custom_nodes` directory.
2. Clone this repository:
```bash
git clone https://github.com/FNGarvin/ComfyUI-AutoModelDownloader.git
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Restart ComfyUI.
## License
@@ -77,12 +63,8 @@ This program is free software: you can redistribute it and/or modify it under th
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request or open an Issue on [GitHub](https://github.com/MadiatorLabs/ComfyUI-RunpodDirect).
Contributions are welcome! Please feel free to submit a Pull Request or open an Issue.
## Credits
Developed by [MadiatorLabs](https://github.com/MadiatorLabs)
## Support
If you find this useful, please ⭐ star the repo on [GitHub](https://github.com/MadiatorLabs/ComfyUI-RunpodDirect)!
Since this is basically a Rebranded ComfyUI-RunpodDirect, all credit goes to MadiatorLabs.
+8 -7
View File
@@ -1,5 +1,5 @@
"""
ComfyUI RunpodDirect - Direct Model Downloads for RunPod
ComfyUI AutoModelDownloader - Direct Model Downloads for RunPod
Download models directly to your RunPod instance with multi-connection support
"""
@@ -133,11 +133,11 @@ async def process_download_queue():
# Check if already downloading
if current_download_task is not None and not current_download_task.done():
logging.info("[RunpodDirect] Download already in progress, waiting...")
logging.info("[AutoModelDownloader] Download already in progress, waiting...")
return # Already downloading
if len(download_queue) == 0:
logging.info("[RunpodDirect] Queue is empty")
logging.info("[AutoModelDownloader] Queue is empty")
return # Nothing to process
# Get next download from queue
@@ -151,7 +151,7 @@ async def process_download_queue():
active_downloads[download_id]["progress"] = 0
active_downloads[download_id]["downloaded"] = 0
logging.info(f"[RunpodDirect] Starting download {download_id} with {NUM_CONNECTIONS} connections (full speed)")
logging.info(f"[AutoModelDownloader] Starting download {download_id} with {NUM_CONNECTIONS} connections (full speed)")
# Notify frontend that download is starting
await PromptServer.instance.send("server_download_progress", {
@@ -173,7 +173,7 @@ def on_download_complete(download_id):
global current_download_task
current_download_task = None
logging.info(f"[RunpodDirect] Download completed: {download_id}, processing next in queue...")
logging.info(f"[AutoModelDownloader] Download completed: {download_id}, processing next in queue...")
# Process next in queue
asyncio.create_task(process_download_queue())
@@ -205,7 +205,7 @@ async def download_file(url, output_path, download_id):
"""Download file with multi-connection support and progress tracking"""
import aiohttp
logging.info(f"[RunpodDirect] Download {download_id} using {NUM_CONNECTIONS} connections (full speed)")
logging.info(f"[AutoModelDownloader] Download {download_id} using {NUM_CONNECTIONS} connections (full speed)")
try:
# Initialize control for this download
@@ -540,7 +540,7 @@ async def cancel_download(request):
return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.get("/extensions/ComfyUI-RunpodDirect/serverDownload.js")
@PromptServer.instance.routes.get("/extensions/ComfyUI-AutoModelDownloader/serverDownload.js")
async def serve_js_with_version(request):
"""Serve JS file with cache-busting headers"""
js_path = os.path.join(os.path.dirname(__file__), "web", "serverDownload.js")
@@ -565,3 +565,4 @@ NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
print("[AutoModelDownloader] Linked and Loaded!", flush=True)
+3 -4
View File
@@ -24,12 +24,11 @@ classifiers = [
]
[project.urls]
Repository = "https://github.com/MadiatorLabs/ComfyUI-RunpodDirect"
Documentation = "https://github.com/MadiatorLabs/ComfyUI-RunpodDirect#readme"
"Bug Tracker" = "https://github.com/MadiatorLabs/ComfyUI-RunpodDirect/issues"
Repository = "https://github.com/MadiatorLabs/ComfyUI-AutoModelDownloader"
Documentation = "https://github.com/MadiatorLabs/ComfyUI-AutoModelDownloader#readme"
"Bug Tracker" = "https://github.com/MadiatorLabs/ComfyUI-AutoModelDownloader/issues"
[tool.comfy]
PublisherId = "madiator2011"
DisplayName = "Madiator2011"
Icon = "https://peer.madiator.cloud/lazy-static/avatars/769f014a-9bc4-45e4-8fb5-57512aea2e97.png"
includes = []
+40 -40
View File
@@ -1,9 +1,9 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
// ComfyUI RunpodDirect Extension
// ComfyUI.AutoModelDownloader Extension
// Version: 1.0.6
console.log('[RunpodDirect] v1.0.6');
console.log('[AutoModelDownloader] v1.0.6');
// Track download states
const downloadStates = new Map();
@@ -64,7 +64,7 @@ api.addEventListener("server_download_complete", ({ detail }) => {
// Increment counter BEFORE updating UI
if (isDownloadingAll) {
completedDownloads++;
console.log(`[RunpodDirect] Progress: ${completedDownloads}/${totalDownloads} completed`);
console.log(`[AutoModelDownloader] Progress: ${completedDownloads}/${totalDownloads} completed`);
}
downloadStates.set(download_id, {
@@ -82,7 +82,7 @@ api.addEventListener("server_download_complete", ({ detail }) => {
// Check if all downloads are done
if (isDownloadingAll && completedDownloads >= totalDownloads) {
console.log('[RunpodDirect] All downloads completed!');
console.log('[AutoModelDownloader] All downloads completed!');
isDownloadingAll = false;
showRefreshPrompt();
}
@@ -94,7 +94,7 @@ api.addEventListener("server_download_error", ({ detail }) => {
// Increment counter BEFORE updating UI
if (isDownloadingAll) {
completedDownloads++;
console.log(`[RunpodDirect] Progress: ${completedDownloads}/${totalDownloads} completed (1 error)`);
console.log(`[AutoModelDownloader] Progress: ${completedDownloads}/${totalDownloads} completed (1 error)`);
}
downloadStates.set(download_id, {
@@ -110,7 +110,7 @@ api.addEventListener("server_download_error", ({ detail }) => {
// Check if all downloads are done (including failed ones)
if (isDownloadingAll && completedDownloads >= totalDownloads) {
console.log('[RunpodDirect] All downloads completed!');
console.log('[AutoModelDownloader] All downloads completed!');
isDownloadingAll = false;
showRefreshPrompt();
}
@@ -229,12 +229,12 @@ async function cancelDownload(downloadId) {
// Process download queue - Sends all downloads to backend which handles queue management
async function processDownloadQueue() {
if (downloadQueue.length === 0) {
console.log('[RunpodDirect] No downloads in queue');
console.log('[AutoModelDownloader] No downloads in queue');
return;
}
// Send all downloads to the backend (backend handles queue and priorities)
console.log(`[RunpodDirect] Starting ${downloadQueue.length} downloads (backend will queue and prioritize)`);
console.log(`[AutoModelDownloader] Starting ${downloadQueue.length} downloads (backend will queue and prioritize)`);
const downloadsToStart = [...downloadQueue];
downloadQueue = []; // Clear queue as we're sending all to backend
@@ -242,11 +242,11 @@ async function processDownloadQueue() {
// Start all downloads - backend will queue and manage priorities
// Pass markAsQueued=true so buttons show "Queued" status immediately
for (const download of downloadsToStart) {
console.log(`[RunpodDirect] Queuing download ${download.filename}`);
console.log(`[AutoModelDownloader] Queuing download ${download.filename}`);
await startServerDownload(download.url, download.directory, download.filename, true);
}
console.log(`[RunpodDirect] All ${downloadsToStart.length} downloads queued on backend`);
console.log(`[AutoModelDownloader] All ${downloadsToStart.length} downloads queued on backend`);
}
// Show refresh prompt
@@ -260,7 +260,7 @@ function showRefreshPrompt() {
// Check if prompt already exists
if (document.querySelector('.server-download-refresh-prompt')) {
console.log('[RunpodDirect] Refresh prompt already shown');
console.log('[AutoModelDownloader] Refresh prompt already shown');
return;
}
@@ -371,7 +371,7 @@ function updateDownloadProgressItem(download_id, status, progress, downloaded, t
try {
if (item && item.parentNode) item.remove();
} catch (e) {
console.error('[RunpodDirect] Error removing progress item:', e);
console.error('[AutoModelDownloader] Error removing progress item:', e);
}
}, 2000);
}
@@ -390,7 +390,7 @@ function updateDownloadProgressItem(download_id, status, progress, downloaded, t
container.appendChild(item);
}
} catch (e) {
console.error('[RunpodDirect] Error in updateDownloadProgressItem:', e);
console.error('[AutoModelDownloader] Error in updateDownloadProgressItem:', e);
return;
}
@@ -436,7 +436,7 @@ function updateDownloadProgressItem(download_id, status, progress, downloaded, t
`;
}
} catch (e) {
console.error('[RunpodDirect] Error updating progress item HTML:', e);
console.error('[AutoModelDownloader] Error updating progress item HTML:', e);
}
}
@@ -449,7 +449,7 @@ window.serverDownload = {
// Helper to inject buttons using MutationObserver
function setupDialogObserver() {
console.log('[RunpodDirect] Setting up dialog observer');
console.log('[AutoModelDownloader] Setting up dialog observer');
// Watch for the missing models dialog to appear
const observer = new MutationObserver((mutations) => {
@@ -464,7 +464,7 @@ function setupDialogObserver() {
);
if (hasDialog) {
console.log('[RunpodDirect] Detected missing models dialog, injecting buttons...');
console.log('[AutoModelDownloader] Detected missing models dialog, injecting buttons...');
setTimeout(() => {
injectServerDownloadButtons();
}, 500);
@@ -481,32 +481,32 @@ function setupDialogObserver() {
subtree: true
});
console.log('[RunpodDirect] Observer active');
console.log('[AutoModelDownloader] Observer active');
}
function injectServerDownloadButtons() {
console.log('[RunpodDirect] injectServerDownloadButtons called');
console.log('[AutoModelDownloader] injectServerDownloadButtons called');
// Find the missing models listbox
const listbox = document.querySelector('.comfy-missing-models');
if (!listbox) {
console.log('[RunpodDirect] Missing models listbox not found');
console.log('[AutoModelDownloader] Missing models listbox not found');
return;
}
console.log('[RunpodDirect] Found .comfy-missing-models listbox');
console.log('[AutoModelDownloader] Found .comfy-missing-models listbox');
// Check if we already added our UI
if (document.querySelector('.server-download-all-btn')) {
console.log('[RunpodDirect] Buttons already injected');
console.log('[AutoModelDownloader] Buttons already injected');
return;
}
const listItems = listbox.querySelectorAll('.p-listbox-option');
console.log(`[RunpodDirect] Found ${listItems.length} list items`);
console.log(`[AutoModelDownloader] Found ${listItems.length} list items`);
if (listItems.length === 0) {
console.log('[RunpodDirect] No list items found');
console.log('[AutoModelDownloader] No list items found');
return;
}
@@ -521,7 +521,7 @@ function injectServerDownloadButtons() {
const downloadAllLabel = document.createElement('span');
downloadAllLabel.className = 'p-button-label';
downloadAllLabel.textContent = `Download All Models to Pod (${listItems.length})`;
downloadAllLabel.textContent = `Download All Models (${listItems.length})`;
downloadAllBtn.appendChild(downloadAllLabel);
downloadAllBtn.onclick = async (e) => {
@@ -566,11 +566,11 @@ function injectServerDownloadButtons() {
listbox.parentElement.insertBefore(downloadAllContainer, listbox);
listItems.forEach((item, index) => {
console.log(`[RunpodDirect] Processing item ${index + 1}`);
console.log(`[AutoModelDownloader] Processing item ${index + 1}`);
// Check if we already added the button
if (item.querySelector('.server-download-btn')) {
console.log(`[RunpodDirect] Item ${index + 1} already has server download button, skipping`);
console.log(`[AutoModelDownloader] Item ${index + 1} already has server download button, skipping`);
return;
}
@@ -586,11 +586,11 @@ function injectServerDownloadButtons() {
// Find the main flex container
const mainContainer = item.querySelector('.flex.flex-row.items-center.gap-2');
if (!mainContainer) {
console.log('[RunpodDirect] Main flex container not found');
console.log('[AutoModelDownloader] Main flex container not found');
return;
}
console.log('[RunpodDirect] Found main container');
console.log('[AutoModelDownloader] Found main container');
// We'll create a new div for our button (following the same pattern)
const buttonWrapper = document.createElement('div');
@@ -598,25 +598,25 @@ function injectServerDownloadButtons() {
// Get model info from the item
const labelElement = item.querySelector('[title]');
if (!labelElement) {
console.log('[RunpodDirect] No title element found');
console.log('[AutoModelDownloader] No title element found');
return;
}
const label = labelElement.textContent.trim();
const url = labelElement.getAttribute('title');
console.log(`[RunpodDirect] Model: ${label}, URL: ${url}`);
console.log(`[AutoModelDownloader] Model: ${label}, URL: ${url}`);
// Parse "checkpoints / model.safetensors" format
const parts = label.split('/').map(p => p.trim());
if (parts.length !== 2) {
console.log(`[RunpodDirect] Could not parse label format: ${label}`);
console.log(`[AutoModelDownloader] 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}`);
console.log(`[AutoModelDownloader] Creating button for ${download_id}`);
// Create server download button
const serverDownloadBtn = document.createElement('button');
@@ -626,7 +626,7 @@ function injectServerDownloadButtons() {
// Create button content
const btnLabel = document.createElement('span');
btnLabel.className = 'p-button-label';
btnLabel.textContent = 'Download to Pod';
btnLabel.textContent = 'Download to Host';
serverDownloadBtn.appendChild(btnLabel);
// Status indicator (icon)
@@ -696,34 +696,34 @@ function injectServerDownloadButtons() {
// Add wrapper to main container (alongside Download and Copy URL)
mainContainer.appendChild(buttonWrapper);
console.log(`[RunpodDirect] Button added to main container for ${download_id}`);
console.log(`[AutoModelDownloader] Button added to main container for ${download_id}`);
});
console.log('[RunpodDirect] Button injection complete');
console.log('[AutoModelDownloader] Button injection complete');
}
// Register the extension
app.registerExtension({
name: "ComfyUI.RunpodDirect",
name: "ComfyUI.AutoModelDownloader",
async setup() {
console.log("[RunpodDirect] Extension setup starting");
console.log("[AutoModelDownloader] Extension setup starting");
// Set up observer to watch for missing models dialog
setupDialogObserver();
// Also try to inject immediately if dialog already exists
setTimeout(() => {
console.log('[RunpodDirect] Checking for existing dialog...');
console.log('[AutoModelDownloader] Checking for existing dialog...');
injectServerDownloadButtons();
}, 1000);
// Try again after a longer delay
setTimeout(() => {
console.log('[RunpodDirect] Second check for dialog...');
console.log('[AutoModelDownloader] Second check for dialog...');
injectServerDownloadButtons();
}, 3000);
console.log("[RunpodDirect] Extension setup complete");
console.log("[AutoModelDownloader] Extension setup complete");
}
});