mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
Add recipes checkpoint
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
134
py/routes/recipe_routes.py
Normal 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')
|
||||
Reference in New Issue
Block a user