Fix item visibility bugs and add effects web UI

- Fix recipe filter to allow owner=None (S-expression compiled recipes)
- Fix media uploads to use category (video/image/audio) not MIME type
- Fix IPFS imports to detect and store correct media type
- Add Effects navigation link between Recipes and Media
- Create effects list and detail templates with upload functionality
- Add cache/not_found.html template (was missing)
- Add type annotations to service classes
- Add tests for item visibility and effects web UI (30 tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-12 12:01:54 +00:00
parent 19e2277155
commit 585c75e846
12 changed files with 1090 additions and 53 deletions

View File

@@ -11,10 +11,17 @@ import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, List, Dict, Any, Tuple
from typing import Optional, List, Dict, Any, Tuple, Union, TYPE_CHECKING
if TYPE_CHECKING:
import redis
from cache_manager import L1CacheManager
from database import Database
from ..types import RunResult
def compute_run_id(input_hashes: list, recipe: str, recipe_hash: str = None) -> str:
def compute_run_id(input_hashes: Union[List[str], Dict[str, str]], recipe: str, recipe_hash: Optional[str] = None) -> str:
"""
Compute a deterministic run_id from inputs and recipe.
@@ -89,14 +96,14 @@ class RunService:
Redis is only used for task_id mapping (ephemeral).
"""
def __init__(self, database, redis, cache):
def __init__(self, database: "Database", redis: "redis.Redis[bytes]", cache: "L1CacheManager") -> None:
self.db = database
self.redis = redis # Only for task_id mapping
self.cache = cache
self.task_key_prefix = "artdag:task:" # run_id -> task_id mapping only
self.cache_dir = Path(os.environ.get("CACHE_DIR", "/tmp/artdag-cache"))
def _ensure_inputs_list(self, inputs) -> list:
def _ensure_inputs_list(self, inputs: Any) -> List[str]:
"""Ensure inputs is a list, parsing JSON string if needed."""
if inputs is None:
return []
@@ -112,7 +119,7 @@ class RunService:
return []
return []
async def get_run(self, run_id: str) -> Optional[Dict[str, Any]]:
async def get_run(self, run_id: str) -> Optional[RunResult]:
"""Get a run by ID. Checks database first, then Celery task state."""
# Check database for completed run
cached = await self.db.get_run_cache(run_id)
@@ -267,7 +274,7 @@ class RunService:
return None
async def list_runs(self, actor_id: str, offset: int = 0, limit: int = 20) -> list:
async def list_runs(self, actor_id: str, offset: int = 0, limit: int = 20) -> List[RunResult]:
"""List runs for a user. Returns completed and pending runs from database."""
# Get completed runs from database
completed_runs = await self.db.list_runs_by_actor(actor_id, offset=0, limit=limit + 50)
@@ -297,14 +304,14 @@ class RunService:
async def create_run(
self,
recipe: str,
inputs: list,
output_name: str = None,
inputs: Union[List[str], Dict[str, str]],
output_name: Optional[str] = None,
use_dag: bool = True,
dag_json: str = None,
actor_id: str = None,
l2_server: str = None,
recipe_name: str = None,
) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
dag_json: Optional[str] = None,
actor_id: Optional[str] = None,
l2_server: Optional[str] = None,
recipe_name: Optional[str] = None,
) -> Tuple[Optional[RunResult], Optional[str]]:
"""
Create a new rendering run. Checks cache before executing.
@@ -604,7 +611,7 @@ class RunService:
"""Detect media type for a file path."""
return detect_media_type(path)
async def recover_pending_runs(self) -> Dict[str, int]:
async def recover_pending_runs(self) -> Dict[str, Union[int, str]]:
"""
Recover pending runs after restart.