Public Access
feat(backend): wire exclude_recipe_ids, verify MealPlan votes schema, add image generation service
- api/meal_plans.py: /regenerate now passes exclude_recipe_ids into generate_meal_plan - planner/generate.py: filter recipe_dicts by exclude_recipe_ids set - image_generation.py: OpenAI gpt-image-1 client with prompt building, b64_json handling - main.py: StaticFiles mount at /static for generated images - admin.py: POST /api/admin/trigger-images endpoint for batch generation - scripts/generate_images.py: CLI for batch image generation - docker-compose.yml + nginx: volume mounts for static/images persistence - Verify MealPlanItem.votes ↔ MealPlanVote relationship is correct; no model bug exists
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Batch-generate recipe images using OpenAI DALL-E 3.
|
||||
|
||||
Usage:
|
||||
python scripts/generate_images.py --limit 10 --missing-only
|
||||
python scripts/generate_images.py --recipe-id <uuid>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure imports resolve regardless of cwd.
|
||||
_ = sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from app.database import get_db as _get_db
|
||||
from app.services.image_generation import generate_images_batch
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Generate recipe images via DALL-E 3")
|
||||
parser.add_argument("--limit", type=int, default=10, help="Max recipes to process")
|
||||
parser.add_argument("--missing-only", action="store_true", default=True, help="Only recipes without images")
|
||||
parser.add_argument("--force", action="store_true", default=False, help="Overwrite existing images")
|
||||
parser.add_argument("--recipe-id", type=str, default=None, help="Generate for a single recipe")
|
||||
args = parser.parse_args()
|
||||
|
||||
# FastAPI DB dependency is a generator; consume it.
|
||||
# Ensure project-root .env is discoverable before importing config.
|
||||
project_root = Path(__file__).resolve().parent.parent.parent
|
||||
os.environ.setdefault("DATABASE_URL", os.environ.get("DATABASE_URL", ""))
|
||||
os.environ.setdefault("AI_IMAGE_ENABLED", os.environ.get("AI_IMAGE_ENABLED", "true"))
|
||||
os.environ.setdefault("AI_IMAGE_API_KEY", os.environ.get("AI_IMAGE_API_KEY", ""))
|
||||
os.environ.setdefault("OPENAI_API_KEY", os.environ.get("OPENAI_API_KEY", ""))
|
||||
|
||||
db_gen = _get_db()
|
||||
db = next(db_gen)
|
||||
|
||||
recipe_ids = [args.recipe_id] if args.recipe_id else None
|
||||
|
||||
result = generate_images_batch(
|
||||
db,
|
||||
recipe_ids=recipe_ids,
|
||||
missing_only=args.missing_only,
|
||||
force=args.force,
|
||||
limit=args.limit,
|
||||
)
|
||||
|
||||
print(f"Total: {result['total']}")
|
||||
print(f"Succeeded: {result['succeeded']}")
|
||||
print(f"Failed: {result['failed']}")
|
||||
print(f"Skipped: {result['skipped']}")
|
||||
for rid, info in result["results"].items():
|
||||
print(f" {rid}: {info['status']} – {info['url'] or '—'}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user