Fix recipe listing, effects count, and add nav counts to all pages
- Fix list_by_type to return node_id (IPFS CID) instead of local hash - Fix effects count on home page (count from _effects/ directory) - Add nav_counts to all page templates (recipes, effects, runs, media, storage) - Add editable metadata section to cache/media detail page - Show more metadata on recipe detail page (ID, IPFS CID, step count) - Update tests for new list_by_type behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
38
app/templates/cache/detail.html
vendored
38
app/templates/cache/detail.html
vendored
@@ -39,7 +39,43 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Metadata -->
|
||||
<!-- User Metadata (editable) -->
|
||||
<div id="metadata-section" class="bg-gray-800 rounded-lg border border-gray-700 p-4 mb-6">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-lg font-semibold">Details</h3>
|
||||
<button hx-get="/cache/{{ cache.cid }}/meta-form"
|
||||
hx-target="#metadata-section"
|
||||
hx-swap="innerHTML"
|
||||
class="text-blue-400 hover:text-blue-300 text-sm">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
{% if cache.title or cache.description %}
|
||||
<div class="space-y-2 mb-4">
|
||||
{% if cache.title %}
|
||||
<h4 class="text-white font-medium">{{ cache.title }}</h4>
|
||||
{% endif %}
|
||||
{% if cache.description %}
|
||||
<p class="text-gray-400">{{ cache.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if cache.tags %}
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
{% for tag in cache.tags %}
|
||||
<span class="bg-gray-700 text-gray-300 px-2 py-1 rounded text-sm">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if cache.source_type or cache.source_note %}
|
||||
<div class="text-sm text-gray-500">
|
||||
{% if cache.source_type %}Source: {{ cache.source_type }}{% endif %}
|
||||
{% if cache.source_note %} - {{ cache.source_note }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Technical Metadata -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-gray-800 rounded-lg p-4">
|
||||
<div class="text-gray-500 text-sm">CID</div>
|
||||
|
||||
@@ -14,16 +14,42 @@
|
||||
<!-- Header -->
|
||||
<div class="flex items-center space-x-4 mb-6">
|
||||
<a href="/recipes" class="text-gray-400 hover:text-white">← Recipes</a>
|
||||
<h1 class="text-2xl font-bold">{{ recipe.name }}</h1>
|
||||
<h1 class="text-2xl font-bold">{{ recipe.name or 'Unnamed Recipe' }}</h1>
|
||||
{% if recipe.version %}
|
||||
<span class="text-gray-500">v{{ recipe.version }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if recipe.description %}
|
||||
<p class="text-gray-400 mb-6">{{ recipe.description }}</p>
|
||||
<p class="text-gray-400 mb-4">{{ recipe.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700 mb-6">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-500">Recipe ID</span>
|
||||
<p class="text-gray-300 font-mono text-xs truncate" title="{{ recipe.recipe_id }}">{{ recipe.recipe_id[:16] }}...</p>
|
||||
</div>
|
||||
{% if recipe.ipfs_cid %}
|
||||
<div>
|
||||
<span class="text-gray-500">IPFS CID</span>
|
||||
<p class="text-gray-300 font-mono text-xs truncate" title="{{ recipe.ipfs_cid }}">{{ recipe.ipfs_cid[:16] }}...</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<span class="text-gray-500">Steps</span>
|
||||
<p class="text-gray-300">{{ recipe.step_count or recipe.steps|length }}</p>
|
||||
</div>
|
||||
{% if recipe.author %}
|
||||
<div>
|
||||
<span class="text-gray-500">Author</span>
|
||||
<p class="text-gray-300">{{ recipe.author }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DAG Visualization -->
|
||||
<div class="bg-gray-800 rounded-lg border border-gray-700 mb-6">
|
||||
<div class="border-b border-gray-700 px-4 py-2 flex items-center justify-between">
|
||||
|
||||
Reference in New Issue
Block a user