diff --git a/server.py b/server.py
index 3be7ff7..d49225c 100644
--- a/server.py
+++ b/server.py
@@ -592,7 +592,7 @@ UI_CSS = """
code { background: #222; padding: 2px 6px; border-radius: 4px; }
"""
-def render_ui_html(username: Optional[str] = None) -> str:
+def render_ui_html(username: Optional[str] = None, tab: str = "runs") -> str:
"""Render main UI HTML with optional user context."""
user_info = ""
if username:
@@ -609,23 +609,52 @@ def render_ui_html(username: Optional[str] = None) -> str:
'''
+ runs_active = "active" if tab == "runs" else ""
+ cache_active = "active" if tab == "cache" else ""
+
+ runs_content = ""
+ cache_content = ""
+ if tab == "runs":
+ runs_content = '''
+
- Loading...
+
+ {runs_content}
+ {cache_content}
"""
@@ -721,10 +750,10 @@ UI_REGISTER_HTML = """
@app.get("/ui", response_class=HTMLResponse)
-async def ui_index(request: Request):
- """Web UI for viewing runs."""
+async def ui_index(request: Request, tab: str = "runs"):
+ """Web UI for viewing runs and cache."""
username = get_user_from_cookie(request)
- return render_ui_html(username)
+ return render_ui_html(username, tab)
@app.get("/ui/login", response_class=HTMLResponse)
@@ -887,6 +916,79 @@ async def ui_runs(request: Request):
return '\n'.join(html_parts)
+@app.get("/ui/cache-list", response_class=HTMLResponse)
+async def ui_cache_list(request: Request):
+ """HTMX partial: list of cached items."""
+ current_user = get_user_from_cookie(request)
+
+ # Get all cached files
+ cache_items = []
+ if CACHE_DIR.exists():
+ for f in CACHE_DIR.iterdir():
+ if f.is_file() and not f.name.endswith('.provenance.json'):
+ stat = f.stat()
+ cache_items.append({
+ "hash": f.name,
+ "size": stat.st_size,
+ "mtime": stat.st_mtime
+ })
+
+ # Sort by modification time (newest first)
+ cache_items.sort(key=lambda x: x["mtime"], reverse=True)
+
+ if not cache_items:
+ return '
Cache is empty.
'
+
+ html_parts = ['
']
+
+ for item in cache_items[:50]: # Limit to 50 items
+ content_hash = item["hash"]
+ cache_path = CACHE_DIR / content_hash
+ media_type = detect_media_type(cache_path)
+
+ # Format size
+ size = item["size"]
+ if size > 1024*1024:
+ size_str = f"{size/(1024*1024):.1f} MB"
+ elif size > 1024:
+ size_str = f"{size/1024:.1f} KB"
+ else:
+ size_str = f"{size} bytes"
+
+ html_parts.append(f'''
+
+
+
+ ''')
+
+ html_parts.append('
')
+ return '\n'.join(html_parts)
+
+
@app.get("/ui/detail/{run_id}", response_class=HTMLResponse)
async def ui_detail_page(run_id: str):
"""Full detail page for a run."""