Spaces:
Running
Running
| #!/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() | |