mirror of
https://github.com/hanzoui/workflow-templates-broken-zap-2026-06-08.git
synced 2026-10-30 20:44:35 +00:00
320 lines
12 KiB
Python
320 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Calculate total model sizes for Hanzo Studio workflow templates and update index.json.
|
|
|
|
This script:
|
|
1. Reads the index.json file to get all template names
|
|
2. For each template, extracts model URLs from the properties.models array
|
|
3. Fetches model sizes from Hugging Face repositories
|
|
4. Calculates total size after deduplication
|
|
5. Updates the index.json with a new "size" field for each template
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
import urllib.error
|
|
import time
|
|
from typing import Dict, List, Set, Tuple, Optional
|
|
from urllib.parse import unquote
|
|
import re
|
|
|
|
class IndexModelSizeCalculator:
|
|
def __init__(self, templates_dir: str):
|
|
self.templates_dir = templates_dir
|
|
self.index_path = os.path.join(templates_dir, "index.json")
|
|
self.model_size_cache: Dict[str, float] = {}
|
|
|
|
# Set up urllib with user agent
|
|
self.opener = urllib.request.build_opener()
|
|
self.opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36')]
|
|
|
|
def parse_huggingface_url(self, url: str) -> Tuple[str, str, str]:
|
|
"""Parse Hugging Face URL to extract repo, branch, and filename."""
|
|
# Remove query parameters (like ?download=true)
|
|
url_without_params = url.split('?')[0]
|
|
|
|
pattern = r'https://huggingface\.co/([^/]+/[^/]+)/resolve/([^/]+)/(.+)'
|
|
match = re.match(pattern, url_without_params)
|
|
|
|
if not match:
|
|
raise ValueError(f"Invalid Hugging Face URL format: {url}")
|
|
|
|
repo_id = match.group(1)
|
|
branch = match.group(2)
|
|
file_path = unquote(match.group(3))
|
|
|
|
return repo_id, branch, file_path
|
|
|
|
def get_file_size_direct(self, repo_id: str, branch: str, file_path: str) -> float:
|
|
"""Get file size using direct API call to the specific path."""
|
|
|
|
# Try to get the parent directory first, then look for the file
|
|
path_parts = file_path.split('/')
|
|
|
|
if len(path_parts) > 1:
|
|
# Try getting the parent directory
|
|
parent_path = '/'.join(path_parts[:-1])
|
|
filename = path_parts[-1]
|
|
|
|
api_url = f"https://huggingface.co/api/models/{repo_id}/tree/{branch}/{parent_path}"
|
|
else:
|
|
# File is in root
|
|
api_url = f"https://huggingface.co/api/models/{repo_id}/tree/{branch}"
|
|
filename = file_path
|
|
|
|
try:
|
|
request = urllib.request.Request(api_url)
|
|
with self.opener.open(request, timeout=30) as response:
|
|
data = json.loads(response.read().decode('utf-8'))
|
|
|
|
# Look for the file in the response
|
|
for item in data:
|
|
item_path = item.get('path', '')
|
|
# Extract just the filename from the full path
|
|
item_filename = item_path.split('/')[-1] if '/' in item_path else item_path
|
|
|
|
if item_filename == filename and item.get('type') == 'file':
|
|
size_bytes = item.get('size', 0)
|
|
size_gb = size_bytes / (1024 ** 3)
|
|
return size_gb
|
|
|
|
return 0.0
|
|
|
|
except Exception as e:
|
|
print(f" Error fetching from {api_url}: {e}")
|
|
return 0.0
|
|
|
|
def get_file_size_from_huggingface(self, url: str) -> float:
|
|
"""Get file size from Hugging Face repository in GB."""
|
|
if url in self.model_size_cache:
|
|
return self.model_size_cache[url]
|
|
|
|
try:
|
|
repo_id, branch, file_path = self.parse_huggingface_url(url)
|
|
print(f" Fetching: {repo_id}/{file_path}")
|
|
|
|
size = self.get_file_size_direct(repo_id, branch, file_path)
|
|
|
|
# Cache the result
|
|
self.model_size_cache[url] = size
|
|
|
|
if size > 0:
|
|
print(f" Size: {size:.2f} GB")
|
|
else:
|
|
print(f" Warning: Could not get size")
|
|
|
|
# Add delay to avoid rate limiting
|
|
time.sleep(0.5)
|
|
return size
|
|
|
|
except Exception as e:
|
|
print(f" Error processing {url}: {e}")
|
|
return 0.0
|
|
|
|
def load_index(self) -> List[Dict]:
|
|
"""Load the index.json file."""
|
|
try:
|
|
with open(self.index_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except FileNotFoundError:
|
|
print(f"Error: {self.index_path} not found")
|
|
sys.exit(1)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error: Invalid JSON in {self.index_path}: {e}")
|
|
sys.exit(1)
|
|
|
|
def save_index(self, index_data: List[Dict]):
|
|
"""Save the updated index.json file."""
|
|
try:
|
|
with open(self.index_path, 'w', encoding='utf-8') as f:
|
|
json.dump(index_data, f, indent=2, ensure_ascii=False)
|
|
print(f"✓ Updated index.json")
|
|
except Exception as e:
|
|
print(f"Error saving index.json: {e}")
|
|
|
|
def load_template(self, template_name: str) -> Dict:
|
|
"""Load a single template file."""
|
|
template_path = os.path.join(self.templates_dir, f"{template_name}.json")
|
|
try:
|
|
with open(template_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except FileNotFoundError:
|
|
print(f" Warning: Template file {template_path} not found")
|
|
return {}
|
|
except json.JSONDecodeError as e:
|
|
print(f" Warning: Invalid JSON in {template_path}: {e}")
|
|
return {}
|
|
|
|
def extract_model_urls(self, template_data: Dict) -> Set[str]:
|
|
"""Extract model URLs from template data."""
|
|
urls = set()
|
|
|
|
if 'nodes' not in template_data:
|
|
return urls
|
|
|
|
for node in template_data['nodes']:
|
|
if 'properties' in node and 'models' in node['properties']:
|
|
for model in node['properties']['models']:
|
|
if isinstance(model, dict) and 'url' in model:
|
|
urls.add(model['url'])
|
|
|
|
return urls
|
|
|
|
def calculate_template_size(self, template_name: str) -> float:
|
|
"""Calculate total model size for a template."""
|
|
print(f"Processing template: {template_name}")
|
|
|
|
# Skip API templates (they don't have local models to download)
|
|
if template_name.startswith('api_'):
|
|
print(f" ⚠️ Skipping API template - no local models to calculate")
|
|
return -1.0 # Use -1 to indicate this should be skipped entirely
|
|
|
|
template_data = self.load_template(template_name)
|
|
if not template_data:
|
|
return 0.0
|
|
|
|
model_urls = self.extract_model_urls(template_data)
|
|
|
|
if not model_urls:
|
|
print(f" No model URLs found")
|
|
return 0.0
|
|
|
|
print(f" Found {len(model_urls)} unique model URLs")
|
|
|
|
total_size = 0.0
|
|
for url in model_urls:
|
|
size = self.get_file_size_from_huggingface(url)
|
|
total_size += size
|
|
|
|
print(f" Total size: {total_size:.2f} GB")
|
|
return total_size
|
|
|
|
def update_template_size_in_index(self, index_data: List[Dict], template_name: str, size_gb: float) -> bool:
|
|
"""Update the size for a specific template in the index data."""
|
|
updated = False
|
|
|
|
# Skip adding size field if size is 0 or -1 (API templates)
|
|
if size_gb == 0:
|
|
print(f" ⚠️ Skipping {template_name} - size is 0 GB (no helpful information)")
|
|
return False
|
|
elif size_gb == -1:
|
|
print(f" ⚠️ Skipping {template_name} - API template (no size calculation needed)")
|
|
return False
|
|
|
|
for category in index_data:
|
|
if 'templates' in category:
|
|
for template in category['templates']:
|
|
if template.get('name') == template_name:
|
|
template['size'] = round(size_gb, 2)
|
|
print(f" ✓ Updated {template_name} in index with size: {size_gb:.2f} GB")
|
|
updated = True
|
|
break
|
|
if updated:
|
|
break
|
|
|
|
if not updated:
|
|
print(f" Warning: Could not find template {template_name} in index.json")
|
|
|
|
return updated
|
|
|
|
def run(self, limit: Optional[int] = None):
|
|
"""Main execution function."""
|
|
print("=" * 80)
|
|
print("Hanzo Studio Template Model Size Calculator (Index Update)")
|
|
print("=" * 80)
|
|
print(f"Templates directory: {self.templates_dir}")
|
|
|
|
# Load index
|
|
index_data = self.load_index()
|
|
|
|
# Extract all template names from index
|
|
template_names = []
|
|
for category in index_data:
|
|
if 'templates' in category:
|
|
for template in category['templates']:
|
|
if 'name' in template:
|
|
template_names.append(template['name'])
|
|
|
|
if limit:
|
|
template_names = template_names[:limit]
|
|
print(f"LIMITED TO FIRST {limit} TEMPLATES FOR TESTING")
|
|
|
|
print(f"Found {len(template_names)} templates to process")
|
|
print("=" * 80)
|
|
|
|
# Process each template
|
|
total_processed = 0
|
|
total_size = 0.0
|
|
updated_count = 0
|
|
|
|
for i, template_name in enumerate(template_names, 1):
|
|
print(f"\n[{i}/{len(template_names)}] {template_name}")
|
|
|
|
try:
|
|
size = self.calculate_template_size(template_name)
|
|
if size >= 0: # Even 0 size is valid (templates without HF models), -1 means skip
|
|
if self.update_template_size_in_index(index_data, template_name, size):
|
|
updated_count += 1
|
|
if size > 0:
|
|
total_size += size
|
|
elif size == -1:
|
|
# API template was skipped - this is expected
|
|
pass
|
|
total_processed += 1
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n⚠️ Operation cancelled by user")
|
|
break
|
|
except Exception as e:
|
|
print(f" Error processing {template_name}: {e}")
|
|
continue
|
|
|
|
# Save the updated index
|
|
if updated_count > 0:
|
|
self.save_index(index_data)
|
|
|
|
print("\n" + "=" * 80)
|
|
print("SUMMARY")
|
|
print("=" * 80)
|
|
print(f"Processed: {total_processed}/{len(template_names)} templates")
|
|
print(f"Updated in index: {updated_count}")
|
|
print(f"Total size across all templates: {total_size:.2f} GB")
|
|
print(f"Unique models cached: {len(self.model_size_cache)}")
|
|
|
|
if self.model_size_cache:
|
|
cache_total = sum(self.model_size_cache.values())
|
|
print(f"Cache summary:")
|
|
print(f" Total unique model size: {cache_total:.2f} GB")
|
|
print(f" Average model size: {cache_total / len(self.model_size_cache):.2f} GB")
|
|
|
|
print("✅ Done! Index has been updated with size information.")
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Calculate model sizes and update index.json")
|
|
parser.add_argument("templates_dir", nargs='?', default=None,
|
|
help="Path to templates directory (default: templates/)")
|
|
parser.add_argument("--limit", type=int, default=None,
|
|
help="Limit number of templates to process (for testing)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.templates_dir:
|
|
templates_dir = args.templates_dir
|
|
else:
|
|
# Default to templates directory relative to script location
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
templates_dir = os.path.join(os.path.dirname(script_dir), "templates")
|
|
|
|
if not os.path.exists(templates_dir):
|
|
print(f"Error: Templates directory not found: {templates_dir}")
|
|
sys.exit(1)
|
|
|
|
calculator = IndexModelSizeCalculator(templates_dir)
|
|
calculator.run(limit=args.limit)
|
|
|
|
if __name__ == "__main__":
|
|
main() |