diff --git a/app/dependencies.py b/app/dependencies.py index 5a98ac3..48fe552 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -170,9 +170,14 @@ async def get_nav_counts(actor_id: Optional[str] = None) -> dict: pass try: + # Effects are stored in _effects/ directory, not in cache + from pathlib import Path cache_mgr = get_cache_manager() - effects = cache_mgr.list_by_type('effect') - counts["effects"] = len(effects) + effects_dir = Path(cache_mgr.cache_dir) / "_effects" + if effects_dir.exists(): + counts["effects"] = len([d for d in effects_dir.iterdir() if d.is_dir()]) + else: + counts["effects"] = 0 except Exception: pass diff --git a/app/routers/cache.py b/app/routers/cache.py index 13b10f5..de678a1 100644 --- a/app/routers/cache.py +++ b/app/routers/cache.py @@ -75,10 +75,14 @@ async def get_cached( if not has_access: raise HTTPException(403, "Access denied") + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "cache/detail.html", request, cache=cache_item, user=ctx, + nav_counts=nav_counts, active_tab="media", ) @@ -260,10 +264,14 @@ async def list_media( if wants_json(request): return {"items": items, "offset": offset, "limit": limit, "has_more": has_more} + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "cache/media_list.html", request, items=items, user=ctx, + nav_counts=nav_counts, offset=offset, limit=limit, has_more=has_more, diff --git a/app/routers/effects.py b/app/routers/effects.py index b9ec4a4..9bb9a0f 100644 --- a/app/routers/effects.py +++ b/app/routers/effects.py @@ -248,10 +248,14 @@ async def get_effect( return meta # HTML response + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "effects/detail.html", request, effect=meta, user=ctx, + nav_counts=nav_counts, active_tab="effects", ) @@ -308,10 +312,14 @@ async def list_effects( if wants_json(request): return {"effects": effects} + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "effects/list.html", request, effects=effects, user=ctx, + nav_counts=nav_counts, active_tab="effects", ) diff --git a/app/routers/home.py b/app/routers/home.py index 9ecbef2..0780066 100644 --- a/app/routers/home.py +++ b/app/routers/home.py @@ -66,9 +66,14 @@ async def home(request: Request): except Exception: pass try: + # Effects are stored in _effects/ directory, not in cache + from pathlib import Path from ..dependencies import get_cache_manager - effects = get_cache_manager().list_by_type('effect') - stats["effects"] = len(effects) + effects_dir = Path(get_cache_manager().cache_dir) / "_effects" + if effects_dir.exists(): + stats["effects"] = len([d for d in effects_dir.iterdir() if d.is_dir()]) + else: + stats["effects"] = 0 except Exception: pass diff --git a/app/routers/recipes.py b/app/routers/recipes.py index 45b3b4a..cbab80d 100644 --- a/app/routers/recipes.py +++ b/app/routers/recipes.py @@ -330,10 +330,14 @@ async def list_recipes( if wants_json(request): return {"recipes": recipes, "offset": offset, "limit": limit} + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "recipes/list.html", request, recipes=recipes, user=ctx, + nav_counts=nav_counts, active_tab="recipes", ) @@ -456,11 +460,15 @@ async def get_recipe( if "sexp" not in recipe: recipe["sexp"] = "; No S-expression source available" + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "recipes/detail.html", request, recipe=recipe, dag_elements=dag_elements, user=ctx, + nav_counts=nav_counts, active_tab="recipes", ) diff --git a/app/routers/runs.py b/app/routers/runs.py index 198fdd6..0793854 100644 --- a/app/routers/runs.py +++ b/app/routers/runs.py @@ -323,6 +323,10 @@ async def get_run( if not plan_sexp and plan: plan_sexp = plan_to_sexp(plan, run.get("recipe_name")) + from ..dependencies import get_nav_counts + user = await get_current_user(request) + nav_counts = await get_nav_counts(user.actor_id if user else None) + templates = get_templates(request) return render(templates, "runs/detail.html", request, run=run, @@ -333,6 +337,7 @@ async def get_run( output_media_type=output_media_type, plan_sexp=plan_sexp, recipe_ipfs_cid=recipe_ipfs_cid, + nav_counts=nav_counts, active_tab="runs", ) @@ -418,10 +423,14 @@ async def list_runs( input_previews.append(preview) run["input_previews"] = input_previews + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "runs/list.html", request, runs=runs, user=ctx, + nav_counts=nav_counts, offset=offset, limit=limit, has_more=has_more, diff --git a/app/routers/storage.py b/app/routers/storage.py index 041e6bd..074d440 100644 --- a/app/routers/storage.py +++ b/app/routers/storage.py @@ -66,11 +66,16 @@ async def list_storage( return {"storages": storages} # Render HTML template + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "storage/list.html", request, storages=storages, user=ctx, + nav_counts=nav_counts, providers_info=STORAGE_PROVIDERS_INFO, + active_tab="storage", ) @@ -274,10 +279,15 @@ async def storage_type_page( storages = await storage_service.list_by_type(ctx.actor_id, provider_type) provider_info = STORAGE_PROVIDERS_INFO[provider_type] + from ..dependencies import get_nav_counts + nav_counts = await get_nav_counts(ctx.actor_id) + templates = get_templates(request) return render(templates, "storage/type.html", request, provider_type=provider_type, provider_info=provider_info, storages=storages, user=ctx, + nav_counts=nav_counts, + active_tab="storage", ) diff --git a/app/templates/cache/detail.html b/app/templates/cache/detail.html index 3420d2c..6571800 100644 --- a/app/templates/cache/detail.html +++ b/app/templates/cache/detail.html @@ -39,7 +39,43 @@ {% endif %} - + +
+
+

Details

+ +
+ {% if cache.title or cache.description %} +
+ {% if cache.title %} +

{{ cache.title }}

+ {% endif %} + {% if cache.description %} +

{{ cache.description }}

+ {% endif %} +
+ {% endif %} + {% if cache.tags %} +
+ {% for tag in cache.tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} + {% if cache.source_type or cache.source_note %} +
+ {% if cache.source_type %}Source: {{ cache.source_type }}{% endif %} + {% if cache.source_note %} - {{ cache.source_note }}{% endif %} +
+ {% endif %} +
+ +
CID
diff --git a/app/templates/recipes/detail.html b/app/templates/recipes/detail.html index c52b161..be6bc95 100644 --- a/app/templates/recipes/detail.html +++ b/app/templates/recipes/detail.html @@ -14,16 +14,42 @@
← Recipes -

{{ recipe.name }}

+

{{ recipe.name or 'Unnamed Recipe' }}

{% if recipe.version %} v{{ recipe.version }} {% endif %}
{% if recipe.description %} -

{{ recipe.description }}

+

{{ recipe.description }}

{% endif %} + +
+
+
+ Recipe ID +

{{ recipe.recipe_id[:16] }}...

+
+ {% if recipe.ipfs_cid %} +
+ IPFS CID +

{{ recipe.ipfs_cid[:16] }}...

+
+ {% endif %} +
+ Steps +

{{ recipe.step_count or recipe.steps|length }}

+
+ {% if recipe.author %} +
+ Author +

{{ recipe.author }}

+
+ {% endif %} +
+
+
diff --git a/cache_manager.py b/cache_manager.py index 2ec95ef..793ed1e 100644 --- a/cache_manager.py +++ b/cache_manager.py @@ -519,19 +519,20 @@ class L1CacheManager: def list_by_type(self, node_type: str) -> List[str]: """ - List content hashes of all cached files of a specific type. + List CIDs of all cached files of a specific type. Args: node_type: Type to filter by (e.g., "recipe", "upload", "effect") Returns: - List of content hashes + List of CIDs (IPFS CID if available, otherwise node_id) """ - hashes = [] + cids = [] for entry in self.cache.list_entries(): - if entry.node_type == node_type and entry.cid: - hashes.append(entry.cid) - return hashes + if entry.node_type == node_type: + # Return node_id which is the IPFS CID for uploaded content + cids.append(entry.node_id) + return cids # ============ Activity Tracking ============ diff --git a/tests/test_recipe_visibility.py b/tests/test_recipe_visibility.py index 7fea339..d0c522e 100644 --- a/tests/test_recipe_visibility.py +++ b/tests/test_recipe_visibility.py @@ -20,14 +20,14 @@ class TestRecipeListingFlow: assert 'def list_by_type' in content, \ "L1CacheManager should have list_by_type method" - def test_list_by_type_returns_cid(self) -> None: - """list_by_type should return entry.cid values.""" + def test_list_by_type_returns_node_id(self) -> None: + """list_by_type should return entry.node_id values (IPFS CID).""" path = Path('/home/giles/art/art-celery/cache_manager.py') content = path.read_text() - # Find list_by_type function and verify it appends entry.cid - assert 'hashes.append(entry.cid)' in content, \ - "list_by_type should append entry.cid to results" + # Find list_by_type function and verify it appends entry.node_id + assert 'cids.append(entry.node_id)' in content, \ + "list_by_type should append entry.node_id (IPFS CID) to results" def test_recipe_service_uses_cache_list_by_type(self) -> None: """Recipe service should use cache.list_by_type('recipe').""" @@ -110,3 +110,41 @@ class TestCacheEntryHasCid: source = inspect.getsource(Cache.put) assert 'cid=' in source, \ "Cache.put should set cid on entry" + + +class TestListByTypeReturnsEntries: + """Tests for list_by_type returning cached entries.""" + + def test_list_by_type_iterates_cache_entries(self) -> None: + """list_by_type should iterate self.cache.list_entries().""" + path = Path('/home/giles/art/art-celery/cache_manager.py') + content = path.read_text() + + assert 'self.cache.list_entries()' in content, \ + "list_by_type should iterate cache entries" + + def test_list_by_type_filters_by_node_type(self) -> None: + """list_by_type should filter entries by node_type.""" + path = Path('/home/giles/art/art-celery/cache_manager.py') + content = path.read_text() + + assert 'entry.node_type == node_type' in content, \ + "list_by_type should filter by node_type" + + def test_list_by_type_returns_node_id(self) -> None: + """list_by_type should return entry.node_id (IPFS CID).""" + path = Path('/home/giles/art/art-celery/cache_manager.py') + content = path.read_text() + + assert 'cids.append(entry.node_id)' in content, \ + "list_by_type should append entry.node_id (IPFS CID)" + + def test_artdag_cache_list_entries_returns_all(self) -> None: + """artdag Cache.list_entries should return all entries.""" + from artdag import Cache + import inspect + + source = inspect.getsource(Cache.list_entries) + # Should return self._entries.values() + assert '_entries' in source, \ + "list_entries should access _entries dict"