diff --git a/app/routers/cache.py b/app/routers/cache.py index 69afa05..4eac9a1 100644 --- a/app/routers/cache.py +++ b/app/routers/cache.py @@ -50,7 +50,9 @@ async def get_cached( auth_service = AuthService(get_redis_client()) ctx = auth_service.get_user_from_cookie(request) - cache_item = await cache_service.get_cache_item(cid) + # Pass actor_id to get friendly name and user-specific metadata + actor_id = ctx.actor_id if ctx else None + cache_item = await cache_service.get_cache_item(cid, actor_id=actor_id) if not cache_item: if wants_html(request): templates = get_templates(request) diff --git a/app/routers/runs.py b/app/routers/runs.py index 0793854..a5fe593 100644 --- a/app/routers/runs.py +++ b/app/routers/runs.py @@ -181,7 +181,13 @@ async def get_run( plan_sexp = None # Native S-expression if available recipe_ipfs_cid = None recipe_id = run.get("recipe") - if recipe_id and len(recipe_id) == 64: # Looks like a hash + # Check for valid recipe ID (64-char hash, IPFS CIDv0 "Qm...", or CIDv1 "bafy...") + is_valid_recipe_id = recipe_id and ( + len(recipe_id) == 64 or + recipe_id.startswith("Qm") or + recipe_id.startswith("bafy") + ) + if is_valid_recipe_id: try: from ..services.recipe_service import RecipeService recipe_service = RecipeService(get_redis_client(), get_cache_manager()) diff --git a/app/services/cache_service.py b/app/services/cache_service.py index d09ec09..516509c 100644 --- a/app/services/cache_service.py +++ b/app/services/cache_service.py @@ -95,7 +95,7 @@ class CacheService: self.cache = cache_manager self.cache_dir = Path(os.environ.get("CACHE_DIR", "/tmp/artdag-cache")) - async def get_cache_item(self, cid: str) -> Optional[Dict[str, Any]]: + async def get_cache_item(self, cid: str, actor_id: str = None) -> Optional[Dict[str, Any]]: """Get cached item with full metadata for display.""" # Check if content exists if not self.cache.has_content(cid): @@ -106,14 +106,14 @@ class CacheService: return None # Get metadata from database - meta = await self.db.load_item_metadata(cid, None) + meta = await self.db.load_item_metadata(cid, actor_id) cache_item = await self.db.get_cache_item(cid) media_type = detect_media_type(path) mime_type = get_mime_type(path) size = path.stat().st_size - return { + result = { "cid": cid, "path": str(path), "media_type": media_type, @@ -123,6 +123,28 @@ class CacheService: "meta": meta, } + # Unpack meta fields to top level for template convenience + if meta: + result["title"] = meta.get("title") + result["description"] = meta.get("description") + result["tags"] = meta.get("tags", []) + result["source_type"] = meta.get("source_type") + result["source_note"] = meta.get("source_note") + result["created_at"] = meta.get("created_at") + result["filename"] = meta.get("filename") + + # Get friendly name if actor_id provided + if actor_id: + from .naming_service import get_naming_service + naming = get_naming_service() + friendly = await naming.get_by_cid(actor_id, cid) + if friendly: + result["friendly_name"] = friendly["friendly_name"] + result["base_name"] = friendly["base_name"] + result["version_id"] = friendly["version_id"] + + return result + async def check_access(self, cid: str, actor_id: str, username: str) -> bool: """Check if user has access to content.""" user_hashes = await self._get_user_cache_hashes(username, actor_id) @@ -508,6 +530,19 @@ class CacheService: limit=limit, offset=offset, ) + + # Add friendly names to items + if actor_id: + from .naming_service import get_naming_service + naming = get_naming_service() + for item in items: + cid = item.get("cid") + if cid: + friendly = await naming.get_by_cid(actor_id, cid) + if friendly: + item["friendly_name"] = friendly["friendly_name"] + item["base_name"] = friendly["base_name"] + return items # Legacy compatibility methods diff --git a/app/services/recipe_service.py b/app/services/recipe_service.py index 05a53f6..565a610 100644 --- a/app/services/recipe_service.py +++ b/app/services/recipe_service.py @@ -75,8 +75,9 @@ class RecipeService: if hasattr(self.cache, 'list_by_type'): items = self.cache.list_by_type('recipe') - logger.info(f"Found {len(items)} recipes in cache") + logger.info(f"Found {len(items)} recipe CIDs in cache: {items[:5]}...") for cid in items: + logger.debug(f"Attempting to get recipe {cid[:16]}...") recipe = await self.get_recipe(cid) if recipe and not recipe.get("error"): owner = recipe.get("owner") @@ -85,9 +86,25 @@ class RecipeService: # means the recipe is shared/public and visible to all users if actor_id is None or owner is None or owner == actor_id: recipes.append(recipe) + elif recipe and recipe.get("error"): + logger.warning(f"Recipe {cid[:16]}... has error: {recipe.get('error')}") + else: + logger.warning(f"Recipe {cid[:16]}... returned None") else: logger.warning("Cache does not have list_by_type method") + # Add friendly names + if actor_id: + from .naming_service import get_naming_service + naming = get_naming_service() + for recipe in recipes: + recipe_id = recipe.get("recipe_id") + if recipe_id: + friendly = await naming.get_by_cid(actor_id, recipe_id) + if friendly: + recipe["friendly_name"] = friendly["friendly_name"] + recipe["base_name"] = friendly["base_name"] + # Sort by name recipes.sort(key=lambda r: r.get("name", "")) diff --git a/app/templates/cache/detail.html b/app/templates/cache/detail.html index 6571800..da68606 100644 --- a/app/templates/cache/detail.html +++ b/app/templates/cache/detail.html @@ -39,6 +39,17 @@ {% endif %} + + {% if cache.friendly_name %} +
{{ cache.friendly_name }}
+Use in recipes: {{ cache.base_name }}
{{ cache.description }}
{% endif %}No title or description set. Click Edit to add metadata.
{% endif %} {% if cache.tags %}@@ -13,10 +17,10 @@
{% if recipes %} -No recipes available.
-Recipes are defined in YAML format and submitted via API.
++ Recipes are S-expression files (.sexp) that define processing pipelines. +
+