Add recipes checkpoint

This commit is contained in:
Will Miao
2025-03-08 23:10:24 +08:00
parent e8e5012f0c
commit e6aafe8773
12 changed files with 1581 additions and 5 deletions

View File

@@ -14,6 +14,7 @@ from ..services.websocket_manager import ws_manager
from ..services.settings_manager import settings
import asyncio
from .update_routes import UpdateRoutes
from ..services.recipe_scanner import RecipeScanner
logger = logging.getLogger(__name__)
@@ -44,6 +45,7 @@ class ApiRoutes:
app.router.add_post('/loras/api/save-metadata', routes.save_metadata)
app.router.add_get('/api/lora-preview-url', routes.get_lora_preview_url) # Add new route
app.router.add_post('/api/move_models_bulk', routes.move_models_bulk)
app.router.add_get('/api/recipes', cls.handle_get_recipes)
# Add update check routes
UpdateRoutes.setup_routes(app)
@@ -691,3 +693,29 @@ class ApiRoutes:
except Exception as e:
logger.error(f"Error moving models in bulk: {e}", exc_info=True)
return web.Response(text=str(e), status=500)
@staticmethod
async def handle_get_recipes(request):
"""API endpoint for getting paginated recipes"""
try:
# Get query parameters with defaults
page = int(request.query.get('page', '1'))
page_size = int(request.query.get('page_size', '20'))
sort_by = request.query.get('sort_by', 'date')
search = request.query.get('search', None)
# Get scanner instance
scanner = RecipeScanner(LoraScanner())
# Get paginated data
result = await scanner.get_paginated_data(
page=page,
page_size=page_size,
sort_by=sort_by,
search=search
)
return web.json_response(result)
except Exception as e:
logger.error(f"Error retrieving recipes: {e}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)

View File

@@ -4,6 +4,7 @@ import jinja2
from typing import Dict, List
import logging
from ..services.lora_scanner import LoraScanner
from ..services.recipe_scanner import RecipeScanner
from ..config import config
from ..services.settings_manager import settings # Add this import
@@ -15,6 +16,7 @@ class LoraRoutes:
def __init__(self):
self.scanner = LoraScanner()
self.recipe_scanner = RecipeScanner(self.scanner)
self.template_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(config.templates_path),
autoescape=True
@@ -87,6 +89,46 @@ class LoraRoutes:
status=500
)
async def handle_recipes_page(self, request: web.Request) -> web.Response:
"""Handle GET /loras/recipes request"""
try:
# Check cache initialization status
is_initializing = (
self.recipe_scanner._cache is None and
(self.recipe_scanner._initialization_task is not None and
not self.recipe_scanner._initialization_task.done())
)
if is_initializing:
# If initializing, return a loading page
template = self.template_env.get_template('recipes.html')
rendered = template.render(
is_initializing=True,
settings=settings
)
else:
# Normal flow
cache = await self.recipe_scanner.get_cached_data()
template = self.template_env.get_template('recipes.html')
rendered = template.render(
recipes=cache.sorted_by_date[:20], # Show first 20 recipes by date
is_initializing=False,
settings=settings
)
return web.Response(
text=rendered,
content_type='text/html'
)
except Exception as e:
logger.error(f"Error handling recipes request: {e}", exc_info=True)
return web.Response(
text="Error loading recipes page",
status=500
)
def setup_routes(self, app: web.Application):
"""Register routes with the application"""
app.router.add_get('/loras', self.handle_loras_page)
app.router.add_get('/loras/recipes', self.handle_recipes_page)

134
py/routes/recipe_routes.py Normal file
View File

@@ -0,0 +1,134 @@
import os
import logging
import sys
from aiohttp import web
from typing import Dict
from ..services.recipe_scanner import RecipeScanner
from ..services.lora_scanner import LoraScanner
from ..config import config
logger = logging.getLogger(__name__)
print("Recipe Routes module loaded", file=sys.stderr)
class RecipeRoutes:
"""API route handlers for Recipe management"""
def __init__(self):
print("Initializing RecipeRoutes", file=sys.stderr)
self.recipe_scanner = RecipeScanner(LoraScanner())
# Pre-warm the cache
self._init_cache_task = None
@classmethod
def setup_routes(cls, app: web.Application):
"""Register API routes"""
print("Setting up recipe routes", file=sys.stderr)
routes = cls()
app.router.add_get('/api/recipes', routes.get_recipes)
app.router.add_get('/api/recipe/{recipe_id}', routes.get_recipe_detail)
# Start cache initialization
app.on_startup.append(routes._init_cache)
print("Recipe routes setup complete", file=sys.stderr)
async def _init_cache(self, app):
"""Initialize cache on startup"""
print("Pre-warming recipe cache...", file=sys.stderr)
try:
# Diagnose lora scanner first
await self.recipe_scanner._lora_scanner.diagnose_hash_index()
# Force a cache refresh
await self.recipe_scanner.get_cached_data(force_refresh=True)
print("Recipe cache pre-warming complete", file=sys.stderr)
except Exception as e:
print(f"Error pre-warming recipe cache: {e}", file=sys.stderr)
async def get_recipes(self, request: web.Request) -> web.Response:
"""API endpoint for getting paginated recipes"""
try:
print("API: GET /api/recipes", file=sys.stderr)
# Get query parameters with defaults
page = int(request.query.get('page', '1'))
page_size = int(request.query.get('page_size', '20'))
sort_by = request.query.get('sort_by', 'date')
search = request.query.get('search', None)
# Get paginated data
result = await self.recipe_scanner.get_paginated_data(
page=page,
page_size=page_size,
sort_by=sort_by,
search=search
)
# Format the response data with static URLs for file paths
for item in result['items']:
item['preview_url'] = item['file_path']
# Convert file path to URL
item['file_url'] = self._format_recipe_file_url(item['file_path'])
return web.json_response(result)
except Exception as e:
logger.error(f"Error retrieving recipes: {e}", exc_info=True)
print(f"API Error: {e}", file=sys.stderr)
return web.json_response({"error": str(e)}, status=500)
async def get_recipe_detail(self, request: web.Request) -> web.Response:
"""Get detailed information about a specific recipe"""
try:
recipe_id = request.match_info['recipe_id']
# Get all recipes from cache
cache = await self.recipe_scanner.get_cached_data()
# Find the specific recipe
recipe = next((r for r in cache.raw_data if str(r.get('id', '')) == recipe_id), None)
if not recipe:
return web.json_response({"error": "Recipe not found"}, status=404)
# Format recipe data
formatted_recipe = self._format_recipe_data(recipe)
return web.json_response(formatted_recipe)
except Exception as e:
logger.error(f"Error retrieving recipe details: {e}", exc_info=True)
return web.json_response({"error": str(e)}, status=500)
def _format_recipe_file_url(self, file_path: str) -> str:
"""Format file path for recipe image as a URL"""
# This is a simplified example - in real implementation,
# you would map this to a static route that can serve the file
# For recipes folder in the first lora root
for idx, root in enumerate(config.loras_roots, start=1):
recipes_dir = os.path.join(root, "recipes")
if file_path.startswith(recipes_dir):
relative_path = os.path.relpath(file_path, root)
return f"/loras_static/root{idx}/{relative_path}"
return file_path # Return original path if no mapping found
def _format_recipe_data(self, recipe: Dict) -> Dict:
"""Format recipe data for API response"""
formatted = {**recipe} # Copy all fields
# Format file paths to URLs
if 'file_path' in formatted:
formatted['file_url'] = self._format_recipe_file_url(formatted['file_path'])
# Format dates for display
for date_field in ['created_date', 'modified']:
if date_field in formatted:
formatted[f"{date_field}_formatted"] = self._format_timestamp(formatted[date_field])
return formatted
def _format_timestamp(self, timestamp: float) -> str:
"""Format timestamp for display"""
from datetime import datetime
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')