closed2open / generate_recipes.py
abidlabs's picture
abidlabs HF Staff
recipes
adc9dea
#!/usr/bin/env python3
# /// script
# dependencies = [
# "nbgradio",
# ]
# ///
"""
Script to convert Jupyter notebooks to recipe pages using nbgradio.
This script processes notebooks in the notebooks/ directory and generates
recipe pages in the recipes/ directory with proper styling integration.
"""
import os
import subprocess
import sys
from pathlib import Path
import shutil
import json
def get_notebook_files():
"""Get all notebook files from the notebooks directory."""
notebooks_dir = Path("notebooks")
if not notebooks_dir.exists():
print("❌ notebooks/ directory not found")
return []
notebook_files = list(notebooks_dir.glob("*.ipynb"))
print(f"πŸ“š Found {len(notebook_files)} notebook(s): {[f.name for f in notebook_files]}")
return notebook_files
def get_existing_recipes():
"""Get list of existing recipe files."""
recipes_dir = Path("recipes")
if not recipes_dir.exists():
return []
recipe_files = list(recipes_dir.glob("*.html"))
return [f.stem for f in recipe_files]
def convert_notebook_to_recipe(notebook_path):
"""Convert a single notebook to a recipe page."""
notebook_name = notebook_path.stem
print(f"\nπŸ”„ Converting {notebook_name}...")
# Create temporary directory for nbgradio output
temp_dir = Path(f"temp_{notebook_name}")
temp_dir.mkdir(exist_ok=True)
try:
# Run nbgradio with --spaces and --fragment
cmd = [
"nbgradio", "build",
str(notebook_path),
"--spaces",
"--fragment",
"--output-dir", str(temp_dir)
]
print(f"πŸš€ Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"❌ Error converting {notebook_name}:")
print(result.stderr)
return False
print(f"βœ… Successfully converted {notebook_name}")
# Process the generated fragment
fragment_file = temp_dir / "fragments" / f"{notebook_name}.html"
if fragment_file.exists():
create_recipe_page(notebook_name, fragment_file)
return True
else:
print(f"❌ Fragment file not found: {fragment_file}")
return False
except Exception as e:
print(f"❌ Error processing {notebook_name}: {e}")
return False
finally:
# Clean up temporary directory
if temp_dir.exists():
shutil.rmtree(temp_dir)
def create_recipe_page(notebook_name, fragment_file):
"""Create a complete recipe page from the fragment."""
recipes_dir = Path("recipes")
recipes_dir.mkdir(exist_ok=True)
# Read the fragment content
with open(fragment_file, 'r', encoding='utf-8') as f:
fragment_content = f.read()
# Extract title from notebook name (convert snake_case to Title Case)
title = notebook_name.replace('_', ' ').replace('-', ' ').title()
# Create the complete recipe page
recipe_html = f"""<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{title} - ClosedToOpen</title>
<link rel="stylesheet" href="../style.css" />
</head>
<body>
<nav class="navbar">
<div class="nav-content">
<h1 class="nav-title">ClosedToOpen</h1>
<p class="nav-subtitle">how to stop relying on proprietary APIs for AI and start living freely</p>
</div>
</nav>
<main class="main-content">
<div class="breadcrumb">
<a href="../index.html">← Back to Migration Guides</a>
</div>
<section class="recipe-header">
<h1 class="recipe-title">{title}</h1>
<p class="recipe-description">Migrate from proprietary APIs to open-source alternatives</p>
<div class="recipe-meta">
<span class="difficulty">Difficulty: Medium</span>
<span class="time">Time: 45 minutes</span>
<span class="category">Category: Migration</span>
</div>
</section>
<section class="recipe-content">
<div class="notebook-content">
{fragment_content}
</div>
</section>
</main>
</body>
</html>"""
# Write the recipe page
recipe_file = recipes_dir / f"{notebook_name}.html"
with open(recipe_file, 'w', encoding='utf-8') as f:
f.write(recipe_html)
print(f"πŸ“„ Created recipe page: {recipe_file}")
def update_notebook_for_nbgradio(notebook_path):
"""Update notebook to include nbgradio syntax for interactive cells."""
print(f"πŸ”§ Updating {notebook_path.name} for nbgradio...")
# Read the notebook
with open(notebook_path, 'r', encoding='utf-8') as f:
notebook = json.load(f)
updated = False
# Look for cells with gradio code and add nbgradio comments
for cell in notebook['cells']:
if cell['cell_type'] == 'code':
source = ''.join(cell['source'])
# Check if this cell contains gradio code
if 'import gradio' in source or 'gr.Interface' in source or 'demo.launch()' in source:
# Check if it already has nbgradio comment
if not source.startswith('#nbgradio'):
# Add nbgradio comment
notebook_name = notebook_path.stem
nbgradio_comment = f"#nbgradio name=\"{notebook_name}\"\n"
cell['source'] = [nbgradio_comment] + cell['source']
updated = True
print(f" βœ… Added nbgradio comment to gradio cell")
# Write back if updated
if updated:
with open(notebook_path, 'w', encoding='utf-8') as f:
json.dump(notebook, f, indent=2)
print(f" πŸ“ Updated notebook saved")
else:
print(f" ℹ️ No gradio cells found or already updated")
def main():
"""Main function to convert all notebooks to recipes."""
print("πŸš€ Starting notebook to recipe conversion...")
# Get notebook files
notebook_files = get_notebook_files()
if not notebook_files:
print("❌ No notebooks found to convert")
return
# Get existing recipes
existing_recipes = get_existing_recipes()
print(f"πŸ“‹ Existing recipes: {existing_recipes}")
# Convert each notebook
converted_count = 0
for notebook_path in notebook_files:
notebook_name = notebook_path.stem
# Skip if recipe already exists
if notebook_name in existing_recipes:
print(f"⏭️ Skipping {notebook_name} (recipe already exists)")
continue
# Update notebook for nbgradio
update_notebook_for_nbgradio(notebook_path)
# Convert to recipe
if convert_notebook_to_recipe(notebook_path):
converted_count += 1
print(f"\nπŸŽ‰ Conversion complete! {converted_count} new recipe(s) created.")
print("πŸ“ Check the recipes/ directory for the generated files.")
if __name__ == "__main__":
main()