384 lines
14 KiB
Python
Executable File
384 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate index.html dashboard by scanning demo directories.
|
|
Automatically discovers all HTML demos and updates the dashboard.
|
|
|
|
Usage:
|
|
python3 generate_index.py
|
|
|
|
Or make executable and run:
|
|
chmod +x generate_index.py
|
|
./generate_index.py
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
def scan_directory(path, pattern="*.html"):
|
|
"""Scan directory for HTML files matching pattern."""
|
|
if not os.path.exists(path):
|
|
return []
|
|
|
|
files = sorted(Path(path).glob(pattern))
|
|
return [str(f.relative_to('.')) for f in files]
|
|
|
|
def extract_title_from_html(filepath):
|
|
"""Extract title from HTML file."""
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
content = f.read(2000) # Read first 2KB
|
|
|
|
# Try to find title in various formats
|
|
title_match = re.search(r'<title>(.*?)</title>', content, re.IGNORECASE)
|
|
if title_match:
|
|
return title_match.group(1).strip()
|
|
|
|
# Try h1 tag
|
|
h1_match = re.search(r'<h1[^>]*>(.*?)</h1>', content, re.IGNORECASE)
|
|
if h1_match:
|
|
return h1_match.group(1).strip()
|
|
|
|
# Try h2 in info div
|
|
h2_match = re.search(r'<h2[^>]*>(.*?)</h2>', content, re.IGNORECASE)
|
|
if h2_match:
|
|
return h2_match.group(1).strip()
|
|
|
|
except Exception as e:
|
|
print(f"Warning: Could not read {filepath}: {e}")
|
|
|
|
return None
|
|
|
|
def extract_description_from_html(filepath):
|
|
"""Extract description from HTML file."""
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
content = f.read(5000) # Read first 5KB
|
|
|
|
# Look for description in meta tag
|
|
meta_match = re.search(r'<meta name="description" content="(.*?)"', content, re.IGNORECASE)
|
|
if meta_match:
|
|
return meta_match.group(1).strip()
|
|
|
|
# Look for technique/learning in info panel
|
|
technique_match = re.search(r'<strong>Technique:</strong>\s*(.*?)</p>', content, re.IGNORECASE | re.DOTALL)
|
|
if technique_match:
|
|
return technique_match.group(1).strip()
|
|
|
|
# Look for first paragraph in info div
|
|
p_match = re.search(r'<div[^>]*id="info"[^>]*>.*?<p[^>]*>(.*?)</p>', content, re.IGNORECASE | re.DOTALL)
|
|
if p_match:
|
|
text = p_match.group(1).strip()
|
|
# Remove HTML tags
|
|
text = re.sub(r'<[^>]+>', '', text)
|
|
return text[:150]
|
|
|
|
except Exception as e:
|
|
print(f"Warning: Could not read {filepath}: {e}")
|
|
|
|
return "Interactive demo"
|
|
|
|
def generate_demo_data():
|
|
"""Generate demo data by scanning directories."""
|
|
demos = {
|
|
'threejs': [],
|
|
'sdg': [],
|
|
'd3': [],
|
|
'mapbox': [],
|
|
'claudeDevTools': [],
|
|
'uiSingle': [],
|
|
'uiModular': [],
|
|
'infiniteVariants': []
|
|
}
|
|
|
|
# Scan Three.js demos
|
|
threejs_files = scan_directory('threejs_viz', 'threejs_viz_*.html')
|
|
for i, filepath in enumerate(threejs_files, 1):
|
|
title = extract_title_from_html(filepath) or f"Three.js Viz {i}"
|
|
description = extract_description_from_html(filepath)
|
|
|
|
demos['threejs'].append({
|
|
'number': i,
|
|
'title': title.replace('Three.js - ', ''),
|
|
'description': description,
|
|
'path': filepath,
|
|
'type': 'Foundation' if i <= 5 else 'Intermediate' if i <= 12 else 'Advanced',
|
|
'techniques': []
|
|
})
|
|
|
|
# Scan SDG demos
|
|
sdg_files = scan_directory('sdg_viz', 'sdg_viz_*.html')
|
|
for i, filepath in enumerate(sdg_files, 1):
|
|
title = extract_title_from_html(filepath) or f"SDG Network Viz {i}"
|
|
description = extract_description_from_html(filepath)
|
|
|
|
demos['sdg'].append({
|
|
'number': i,
|
|
'title': title,
|
|
'description': description,
|
|
'path': filepath,
|
|
'type': 'Network',
|
|
'techniques': ['D3.js', 'Force Simulation']
|
|
})
|
|
|
|
# Scan D3 demos
|
|
d3_files = scan_directory('d3_test', 'd3_viz_*.html')
|
|
for i, filepath in enumerate(d3_files, 1):
|
|
title = extract_title_from_html(filepath) or f"D3 Viz {i}"
|
|
description = extract_description_from_html(filepath)
|
|
|
|
# Remove "D3 Visualization N: " prefix if present
|
|
if title.startswith('D3 Visualization'):
|
|
parts = title.split(':', 1)
|
|
if len(parts) > 1:
|
|
title = parts[1].strip()
|
|
|
|
demos['d3'].append({
|
|
'number': i,
|
|
'title': title,
|
|
'description': description,
|
|
'path': filepath,
|
|
'type': 'D3 Visualization',
|
|
'techniques': ['D3.js', 'Data Visualization', 'SVG']
|
|
})
|
|
|
|
# Scan Mapbox Globe demos
|
|
mapbox_dirs = sorted(Path('mapbox_test').glob('mapbox_globe_*/index.html')) if os.path.exists('mapbox_test') else []
|
|
for i, filepath in enumerate(mapbox_dirs, 1):
|
|
title = extract_title_from_html(str(filepath)) or f"Mapbox Globe {i}"
|
|
description = extract_description_from_html(str(filepath))
|
|
|
|
# Remove "Globe Viz N: " prefix if present
|
|
if title.startswith('Globe Viz'):
|
|
parts = title.split(':', 1)
|
|
if len(parts) > 1:
|
|
title = parts[1].strip()
|
|
|
|
demos['mapbox'].append({
|
|
'number': i,
|
|
'title': title,
|
|
'description': description,
|
|
'path': str(filepath),
|
|
'type': 'Globe Visualization',
|
|
'techniques': ['Mapbox GL JS', '3D Globe', 'GeoJSON']
|
|
})
|
|
|
|
# Scan Claude Code DevTools demos
|
|
devtools_files = scan_directory('claude_code_devtools', 'claude_devtool_*.html')
|
|
for i, filepath in enumerate(devtools_files, 1):
|
|
title = extract_title_from_html(filepath) or f"DevTool {i}"
|
|
description = extract_description_from_html(filepath)
|
|
|
|
# Remove " - Claude Code DevTools" suffix if present
|
|
if ' - Claude Code DevTools' in title:
|
|
title = title.replace(' - Claude Code DevTools', '')
|
|
|
|
demos['claudeDevTools'].append({
|
|
'number': i,
|
|
'title': title,
|
|
'description': description,
|
|
'path': filepath,
|
|
'type': 'DevTool',
|
|
'techniques': ['Developer Tools', 'Web APIs']
|
|
})
|
|
|
|
# Scan UI Single File demos
|
|
src_files = scan_directory('src', 'ui_hybrid_*.html')
|
|
for i, filepath in enumerate(src_files, 1):
|
|
demos['uiSingle'].append({
|
|
'number': i,
|
|
'title': f"UI Hybrid {i}",
|
|
'description': 'Themed hybrid UI component combining multiple interface elements',
|
|
'path': filepath,
|
|
'type': 'Single File',
|
|
'techniques': ['Themed Design', 'Hybrid Components']
|
|
})
|
|
|
|
# Scan UI Infinite demos
|
|
src_infinite_files = scan_directory('src_infinite', 'ui_hybrid_*.html')
|
|
offset = len(demos['uiSingle'])
|
|
for i, filepath in enumerate(src_infinite_files, 1):
|
|
demos['uiSingle'].append({
|
|
'number': offset + i,
|
|
'title': f"UI Hybrid {i} (Infinite)",
|
|
'description': 'Infinite mode generated themed component',
|
|
'path': filepath,
|
|
'type': 'Single File (Infinite)',
|
|
'techniques': ['Infinite Generation', 'Progressive Complexity']
|
|
})
|
|
|
|
# Scan UI Modular demos
|
|
modular_dirs = sorted(Path('src_group').glob('ui_hybrid_*/index.html')) if os.path.exists('src_group') else []
|
|
for i, filepath in enumerate(modular_dirs, 1):
|
|
demos['uiModular'].append({
|
|
'number': i,
|
|
'title': f"UI Hybrid {i} (Modular)",
|
|
'description': 'Professional 3-file architecture with separated HTML, CSS, and JavaScript',
|
|
'path': str(filepath),
|
|
'type': 'Modular',
|
|
'techniques': ['Separation of Concerns', 'Modular Architecture']
|
|
})
|
|
|
|
# Scan Infinite Variants test outputs
|
|
if os.path.exists('infinite_variants'):
|
|
variant_dirs = sorted(Path('infinite_variants').glob('infinite_variant_*/test_output'))
|
|
for variant_dir in variant_dirs:
|
|
# Extract variant number
|
|
variant_match = re.search(r'infinite_variant_(\d+)', str(variant_dir))
|
|
variant_num = variant_match.group(1) if variant_match else '?'
|
|
|
|
# Variant names for better titles
|
|
variant_names = {
|
|
'1': 'Pattern Synthesis',
|
|
'2': 'Utility Commands',
|
|
'3': 'Pluggable Templates',
|
|
'4': 'Quality Evaluation',
|
|
'5': 'Config-Driven',
|
|
'6': 'State Management',
|
|
'7': 'Meta Self-Improvement'
|
|
}
|
|
variant_name = variant_names.get(variant_num, f'Variant {variant_num}')
|
|
|
|
# Scan HTML and JS files in this variant's test_output
|
|
variant_files = sorted(list(variant_dir.glob('*.html')) + list(variant_dir.glob('*.js')))
|
|
for filepath in variant_files:
|
|
# Extract title based on file type
|
|
if filepath.suffix == '.html':
|
|
title = extract_title_from_html(str(filepath)) or filepath.name
|
|
description = extract_description_from_html(str(filepath))
|
|
else:
|
|
# For .js files, use filename as title
|
|
title = filepath.stem.replace('_', ' ').title()
|
|
description = f"Meta-aware JavaScript code pattern"
|
|
|
|
demos['infiniteVariants'].append({
|
|
'number': len(demos['infiniteVariants']) + 1,
|
|
'title': title,
|
|
'description': f"[Variant {variant_num}: {variant_name}] {description}",
|
|
'path': str(filepath),
|
|
'type': variant_name,
|
|
'techniques': ['Infinite Loop Variant', variant_name]
|
|
})
|
|
|
|
return demos
|
|
|
|
def generate_index_html(demos):
|
|
"""Generate the complete index.html file."""
|
|
|
|
total_demos = sum(len(demos[cat]) for cat in demos)
|
|
threejs_count = len(demos['threejs'])
|
|
sdg_count = len(demos['sdg'])
|
|
d3_count = len(demos['d3'])
|
|
mapbox_count = len(demos['mapbox'])
|
|
devtools_count = len(demos['claudeDevTools'])
|
|
ui_count = len(demos['uiSingle']) + len(demos['uiModular'])
|
|
|
|
# Read template (current index.html structure)
|
|
template_path = 'index.html'
|
|
if os.path.exists(template_path):
|
|
with open(template_path, 'r', encoding='utf-8') as f:
|
|
template = f.read()
|
|
else:
|
|
print("Error: index.html template not found")
|
|
return None
|
|
|
|
# Replace the demos data in the JavaScript section
|
|
demos_json = json.dumps(demos, indent=8)
|
|
|
|
# Find and replace the demos object in the script
|
|
pattern = r'const demos = \{[\s\S]*?\};'
|
|
replacement = f'const demos = {demos_json};'
|
|
|
|
updated_html = re.sub(pattern, replacement, template)
|
|
|
|
# Update stats in HTML
|
|
updated_html = re.sub(
|
|
r'<div class="stat-number" id="totalDemos">\d+</div>',
|
|
f'<div class="stat-number" id="totalDemos">{total_demos}</div>',
|
|
updated_html
|
|
)
|
|
|
|
updated_html = re.sub(
|
|
r'<div class="stat-number" id="threejsCount">\d+</div>',
|
|
f'<div class="stat-number" id="threejsCount">{threejs_count}</div>',
|
|
updated_html
|
|
)
|
|
|
|
updated_html = re.sub(
|
|
r'<div class="stat-number" id="uiCount">\d+</div>',
|
|
f'<div class="stat-number" id="uiCount">{ui_count}</div>',
|
|
updated_html
|
|
)
|
|
|
|
# Update category counts
|
|
updated_html = re.sub(
|
|
r'(<div class="category-title">[\s\S]*?Three\.js 3D Visualizations[\s\S]*?</div>\s*<div class="category-count">)\d+ demos',
|
|
f'\\g<1>{threejs_count} demos',
|
|
updated_html
|
|
)
|
|
|
|
updated_html = re.sub(
|
|
r'(<div class="category-title">[\s\S]*?SDG Network Visualizations[\s\S]*?</div>\s*<div class="category-count">)\d+ demos',
|
|
f'\\g<1>{sdg_count} demos',
|
|
updated_html
|
|
)
|
|
|
|
updated_html = re.sub(
|
|
r'(<div class="category-title">[\s\S]*?Themed Hybrid UI Components[\s\S]*?</div>\s*<div class="category-count">)\d+ demos',
|
|
f'\\g<1>{len(demos["uiSingle"])} demos',
|
|
updated_html
|
|
)
|
|
|
|
updated_html = re.sub(
|
|
r'(<div class="category-title">[\s\S]*?Modular UI Components[\s\S]*?</div>\s*<div class="category-count">)\d+ demos',
|
|
f'\\g<1>{len(demos["uiModular"])} demos',
|
|
updated_html
|
|
)
|
|
|
|
# Add generation timestamp comment
|
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
comment = f'<!-- Auto-generated: {timestamp} by generate_index.py -->\n'
|
|
updated_html = re.sub(r'<!DOCTYPE html>', f'{comment}<!DOCTYPE html>', updated_html)
|
|
|
|
return updated_html
|
|
|
|
def main():
|
|
"""Main execution."""
|
|
print("🔍 Scanning demo directories...")
|
|
|
|
demos = generate_demo_data()
|
|
|
|
# Print summary
|
|
print(f"\n📊 Found demos:")
|
|
print(f" • Three.js: {len(demos['threejs'])}")
|
|
print(f" • SDG Networks: {len(demos['sdg'])}")
|
|
print(f" • D3 Visualizations: {len(demos['d3'])}")
|
|
print(f" • Mapbox Globes: {len(demos['mapbox'])}")
|
|
print(f" • Claude DevTools: {len(demos['claudeDevTools'])}")
|
|
print(f" • UI Single File: {len(demos['uiSingle'])}")
|
|
print(f" • UI Modular: {len(demos['uiModular'])}")
|
|
print(f" • Infinite Variants: {len(demos['infiniteVariants'])}")
|
|
print(f" • Total: {sum(len(demos[cat]) for cat in demos)}")
|
|
|
|
print("\n✨ Generating index.html...")
|
|
|
|
html = generate_index_html(demos)
|
|
|
|
if html:
|
|
# Write updated index.html
|
|
with open('index.html', 'w', encoding='utf-8') as f:
|
|
f.write(html)
|
|
|
|
print("✅ index.html updated successfully!")
|
|
print("\n🚀 View dashboard: http://localhost:8889/")
|
|
else:
|
|
print("❌ Failed to generate index.html")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
exit(main())
|