feat: add cache view page with media display
- /ui/cache/{hash} shows image/video inline
- File details: hash, type, size
- Download button
- All cache links now go to view page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
97
server.py
97
server.py
@@ -299,6 +299,91 @@ async def get_cached(content_hash: str):
|
|||||||
return FileResponse(cache_path)
|
return FileResponse(cache_path)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/ui/cache/{content_hash}", response_class=HTMLResponse)
|
||||||
|
async def ui_cache_view(content_hash: str):
|
||||||
|
"""View cached content with appropriate display."""
|
||||||
|
cache_path = CACHE_DIR / content_hash
|
||||||
|
|
||||||
|
if not cache_path.exists():
|
||||||
|
return HTMLResponse(f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Not Found | Art DAG L1</title><style>{UI_CSS}</style></head>
|
||||||
|
<body>
|
||||||
|
<h1><a href="/ui" style="color: inherit;">Art DAG L1 Server</a></h1>
|
||||||
|
<a href="/ui" class="back-btn">← Back to runs</a>
|
||||||
|
<div class="run"><p>Content not found: {content_hash}</p></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""", status_code=404)
|
||||||
|
|
||||||
|
media_type = detect_media_type(cache_path)
|
||||||
|
file_size = cache_path.stat().st_size
|
||||||
|
size_str = f"{file_size:,} bytes"
|
||||||
|
if file_size > 1024*1024:
|
||||||
|
size_str = f"{file_size/(1024*1024):.1f} MB"
|
||||||
|
elif file_size > 1024:
|
||||||
|
size_str = f"{file_size/1024:.1f} KB"
|
||||||
|
|
||||||
|
html = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{content_hash[:16]}... | Art DAG L1</title>
|
||||||
|
<style>{UI_CSS}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href="/ui" style="color: inherit;">Art DAG L1 Server</a></h1>
|
||||||
|
<a href="/ui" class="back-btn">← Back to runs</a>
|
||||||
|
|
||||||
|
<div class="run">
|
||||||
|
<div class="run-header">
|
||||||
|
<div>
|
||||||
|
<span class="run-recipe">{media_type.capitalize()}</span>
|
||||||
|
<span class="run-id">{content_hash[:32]}...</span>
|
||||||
|
</div>
|
||||||
|
<a href="/cache/{content_hash}" download class="status completed" style="text-decoration:none;">Download</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="media-container" style="margin: 20px 0;">
|
||||||
|
"""
|
||||||
|
|
||||||
|
if media_type == "video":
|
||||||
|
html += f'<video src="/cache/{content_hash}" controls autoplay muted loop style="max-width:100%;max-height:500px;"></video>'
|
||||||
|
elif media_type == "image":
|
||||||
|
html += f'<img src="/cache/{content_hash}" alt="{content_hash}" style="max-width:100%;max-height:500px;">'
|
||||||
|
else:
|
||||||
|
html += f'<p>Unknown file type. <a href="/cache/{content_hash}" download>Download file</a></p>'
|
||||||
|
|
||||||
|
html += f"""
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="provenance">
|
||||||
|
<h2>Details</h2>
|
||||||
|
<div class="prov-item">
|
||||||
|
<div class="prov-label">Content Hash (SHA3-256)</div>
|
||||||
|
<div class="prov-value">{content_hash}</div>
|
||||||
|
</div>
|
||||||
|
<div class="prov-item">
|
||||||
|
<div class="prov-label">Type</div>
|
||||||
|
<div class="prov-value">{media_type}</div>
|
||||||
|
</div>
|
||||||
|
<div class="prov-item">
|
||||||
|
<div class="prov-label">Size</div>
|
||||||
|
<div class="prov-value">{size_str}</div>
|
||||||
|
</div>
|
||||||
|
<div class="prov-item">
|
||||||
|
<div class="prov-label">Raw URL</div>
|
||||||
|
<div class="prov-value"><a href="/cache/{content_hash}">/cache/{content_hash}</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
@app.get("/cache")
|
@app.get("/cache")
|
||||||
async def list_cache():
|
async def list_cache():
|
||||||
"""List cached content hashes."""
|
"""List cached content hashes."""
|
||||||
@@ -600,7 +685,7 @@ async def ui_detail_page(run_id: str):
|
|||||||
input_media_type = detect_media_type(CACHE_DIR / input_hash)
|
input_media_type = detect_media_type(CACHE_DIR / input_hash)
|
||||||
html += f'''
|
html += f'''
|
||||||
<div class="media-box">
|
<div class="media-box">
|
||||||
<label>Input: <a href="/cache/{input_hash}">{input_hash[:24]}...</a></label>
|
<label>Input: <a href="/ui/cache/{input_hash}">{input_hash[:24]}...</a></label>
|
||||||
<div class="media-container">
|
<div class="media-container">
|
||||||
'''
|
'''
|
||||||
if input_media_type == "video":
|
if input_media_type == "video":
|
||||||
@@ -614,7 +699,7 @@ async def ui_detail_page(run_id: str):
|
|||||||
output_media_type = detect_media_type(CACHE_DIR / output_hash)
|
output_media_type = detect_media_type(CACHE_DIR / output_hash)
|
||||||
html += f'''
|
html += f'''
|
||||||
<div class="media-box">
|
<div class="media-box">
|
||||||
<label>Output: <a href="/cache/{output_hash}">{output_hash[:24]}...</a></label>
|
<label>Output: <a href="/ui/cache/{output_hash}">{output_hash[:24]}...</a></label>
|
||||||
<div class="media-container">
|
<div class="media-container">
|
||||||
'''
|
'''
|
||||||
if output_media_type == "video":
|
if output_media_type == "video":
|
||||||
@@ -638,7 +723,7 @@ async def ui_detail_page(run_id: str):
|
|||||||
<div class="prov-value">
|
<div class="prov-value">
|
||||||
'''
|
'''
|
||||||
for inp in run.inputs:
|
for inp in run.inputs:
|
||||||
html += f'<a href="/cache/{inp}">{inp}</a><br>'
|
html += f'<a href="/ui/cache/{inp}">{inp}</a><br>'
|
||||||
html += f'''
|
html += f'''
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -648,7 +733,7 @@ async def ui_detail_page(run_id: str):
|
|||||||
html += f'''
|
html += f'''
|
||||||
<div class="prov-item">
|
<div class="prov-item">
|
||||||
<div class="prov-label">Output</div>
|
<div class="prov-label">Output</div>
|
||||||
<div class="prov-value"><a href="/cache/{run.output_hash}">{run.output_hash}</a></div>
|
<div class="prov-value"><a href="/ui/cache/{run.output_hash}">{run.output_hash}</a></div>
|
||||||
</div>
|
</div>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -739,7 +824,7 @@ async def ui_run_partial(run_id: str):
|
|||||||
if has_input:
|
if has_input:
|
||||||
input_hash = run.inputs[0]
|
input_hash = run.inputs[0]
|
||||||
input_media_type = detect_media_type(CACHE_DIR / input_hash)
|
input_media_type = detect_media_type(CACHE_DIR / input_hash)
|
||||||
html += f'<div class="media-box"><label>Input: {input_hash[:24]}...</label><div class="media-container">'
|
html += f'<div class="media-box"><label>Input: <a href="/ui/cache/{input_hash}">{input_hash[:24]}...</a></label><div class="media-container">'
|
||||||
if input_media_type == "video":
|
if input_media_type == "video":
|
||||||
html += f'<video src="/cache/{input_hash}" controls muted loop></video>'
|
html += f'<video src="/cache/{input_hash}" controls muted loop></video>'
|
||||||
elif input_media_type == "image":
|
elif input_media_type == "image":
|
||||||
@@ -749,7 +834,7 @@ async def ui_run_partial(run_id: str):
|
|||||||
if has_output:
|
if has_output:
|
||||||
output_hash = run.output_hash
|
output_hash = run.output_hash
|
||||||
output_media_type = detect_media_type(CACHE_DIR / output_hash)
|
output_media_type = detect_media_type(CACHE_DIR / output_hash)
|
||||||
html += f'<div class="media-box"><label>Output: {output_hash[:24]}...</label><div class="media-container">'
|
html += f'<div class="media-box"><label>Output: <a href="/ui/cache/{output_hash}">{output_hash[:24]}...</a></label><div class="media-container">'
|
||||||
if output_media_type == "video":
|
if output_media_type == "video":
|
||||||
html += f'<video src="/cache/{output_hash}" controls autoplay muted loop></video>'
|
html += f'<video src="/cache/{output_hash}" controls autoplay muted loop></video>'
|
||||||
elif output_media_type == "image":
|
elif output_media_type == "image":
|
||||||
|
|||||||
Reference in New Issue
Block a user