diff --git a/server.py b/server.py index bab876f..46b0aa4 100644 --- a/server.py +++ b/server.py @@ -796,7 +796,7 @@ async def run_detail(run_id: str, request: Request): media_html += f'''
Input
- {input_hash[:24]}... + {input_hash[:24]}...
{input_elem}
''' @@ -813,14 +813,14 @@ async def run_detail(run_id: str, request: Request): media_html += f'''
Output
- {output_hash[:24]}... + {output_hash[:24]}...
{output_elem}
''' media_html += '' # Build inputs list - inputs_html = ''.join([f'{inp}' for inp in run.inputs]) + inputs_html = ''.join([f'{inp}' for inp in run.inputs]) # Infrastructure section infra_html = "" @@ -888,7 +888,7 @@ async def run_detail(run_id: str, request: Request): if run.output_hash: output_link = f'''
Output
- {run.output_hash} + {run.output_hash}
''' completed_html = "" @@ -1654,8 +1654,179 @@ async def ui_discard_recipe(recipe_id: str, request: Request): @app.get("/cache/{content_hash}") -async def get_cached(content_hash: str): - """Get cached content by hash.""" +async def get_cached(content_hash: str, request: Request): + """Get cached content by hash. Content negotiation: HTML for browsers, JSON for APIs, file for downloads.""" + ctx = get_user_context_from_cookie(request) + cache_path = get_cache_path(content_hash) + + if not cache_path: + if wants_html(request): + content = f'

Content not found: {content_hash}

' + return HTMLResponse(render_page("Not Found", content, ctx.actor_id if ctx else None, active_tab="media"), status_code=404) + raise HTTPException(404, f"Content {content_hash} not in cache") + + # JSON response for API clients + accept = request.headers.get("accept", "") + if "application/json" in accept: + meta = await database.load_item_metadata(content_hash, ctx.actor_id if ctx else None) + cache_item = await database.get_cache_item(content_hash) + ipfs_cid = cache_item.get("ipfs_cid") if cache_item else None + file_size = cache_path.stat().st_size + media_type = detect_media_type(cache_path) + return { + "content_hash": content_hash, + "size": file_size, + "media_type": media_type, + "ipfs_cid": ipfs_cid, + "meta": meta + } + + # HTML response for browsers - show detail page + if wants_html(request): + if not ctx: + content = '

Login via L2 to view cached content.

' + return HTMLResponse(render_page("Login Required", content, None, active_tab="media"), status_code=401) + + # Check user has access + user_hashes = await get_user_cache_hashes(ctx.username, ctx.actor_id) + if content_hash not in user_hashes: + content = '

Access denied.

' + return HTMLResponse(render_page("Access Denied", content, ctx.actor_id, active_tab="media"), status_code=403) + + 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" + + # Get IPFS CID from database + cache_item = await database.get_cache_item(content_hash) + ipfs_cid = cache_item.get("ipfs_cid") if cache_item else None + + # Build media display HTML + if media_type == "video": + video_src = video_src_for_request(content_hash, request) + media_html = f'' + elif media_type == "image": + media_html = f'{content_hash}' + else: + media_html = f'

Unknown file type. Download file

' + + content = f''' + + + + + Back to media + + +
+
+
+ {media_type.capitalize()} + {content_hash[:24]}... +
+ + Download + +
+ +
+ {media_html} +
+ +
+

Details

+
+
+
Content Hash (SHA3-256)
+
{content_hash}
+
+
+
Type
+
{media_type}
+
+
+
Size
+
{size_str}
+
+
+
Raw URL
+ +
+
+
+ ''' + + # Add IPFS section if we have a CID + if ipfs_cid: + gateway_links = [] + if IPFS_GATEWAY_URL: + gateway_links.append(f''' + + Local Gateway + ''') + gateway_links.extend([ + f''' + ipfs.io + ''', + f''' + dweb.link + ''', + f''' + Cloudflare + ''', + ]) + gateways_html = '\n'.join(gateway_links) + + content += f''' +
+

IPFS

+
+
Content Identifier (CID)
+
{ipfs_cid}
+
+
Gateways:
+
+ {gateways_html} +
+
+ ''' + else: + content += ''' +
+

IPFS

+
Not yet uploaded to IPFS
+
+ ''' + + content += f''' + +
+
Loading metadata...
+
+
+ ''' + + return HTMLResponse(render_page(f"Cache: {content_hash[:16]}...", content, ctx.actor_id, active_tab="media")) + + # Default: return raw file + return FileResponse(cache_path) + + +@app.get("/cache/{content_hash}/raw") +async def get_cached_raw(content_hash: str): + """Get raw cached content (file download).""" cache_path = get_cache_path(content_hash) if not cache_path: raise HTTPException(404, f"Content {content_hash} not in cache") @@ -1724,172 +1895,6 @@ async def get_cached_mp4(content_hash: str): return FileResponse(mp4_path, media_type="video/mp4") -@app.get("/cache/{content_hash}/detail") -async def cache_detail(content_hash: str, request: Request): - """View cached content detail. HTML for browsers, JSON for APIs.""" - ctx = get_user_context_from_cookie(request) - - cache_path = get_cache_path(content_hash) - if not cache_path: - if wants_html(request): - content = f'

Content not found: {content_hash}

' - return HTMLResponse(render_page("Not Found", content, ctx.actor_id if ctx else None, active_tab="media"), status_code=404) - raise HTTPException(404, f"Content {content_hash} not in cache") - - if wants_html(request): - if not ctx: - content = '

Login via L2 to view cached content.

' - return HTMLResponse(render_page("Login Required", content, None, active_tab="media"), status_code=401) - - # Check user has access - user_hashes = await get_user_cache_hashes(ctx.username, ctx.actor_id) - if content_hash not in user_hashes: - content = '

Access denied.

' - return HTMLResponse(render_page("Access Denied", content, ctx.actor_id, active_tab="media"), status_code=403) - - 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" - - # Get IPFS CID from database - cache_item = await database.get_cache_item(content_hash) - ipfs_cid = cache_item.get("ipfs_cid") if cache_item else None - - # Build media display HTML - if media_type == "video": - video_src = video_src_for_request(content_hash, request) - media_html = f'' - elif media_type == "image": - media_html = f'{content_hash}' - else: - media_html = f'

Unknown file type. Download file

' - - content = f''' - - - - - Back to media - - -
-
-
- {media_type.capitalize()} - {content_hash[:24]}... -
- - Download - -
- -
- {media_html} -
- -
-

Details

-
-
-
Content Hash (SHA3-256)
-
{content_hash}
-
-
-
Type
-
{media_type}
-
-
-
Size
-
{size_str}
-
-
-
Raw URL
- -
-
-
- ''' - - # Add IPFS section if we have a CID - if ipfs_cid: - # Build gateway links - local gateway first if configured - gateway_links = [] - if IPFS_GATEWAY_URL: - gateway_links.append(f''' - - Local Gateway - ''') - gateway_links.extend([ - f''' - ipfs.io - ''', - f''' - dweb.link - ''', - f''' - Cloudflare - ''', - ]) - gateways_html = '\n'.join(gateway_links) - - content += f''' -
-

IPFS

-
-
Content Identifier (CID)
-
{ipfs_cid}
-
-
Gateways:
-
- {gateways_html} -
-
- ''' - else: - content += ''' -
-

IPFS

-
Not yet uploaded to IPFS
-
- ''' - - content += f''' - -
-
Loading metadata...
-
-
- ''' - - return HTMLResponse(render_page(f"Cache: {content_hash[:16]}...", content, ctx.actor_id, active_tab="media")) - - # JSON response - return metadata - meta = await database.load_item_metadata(content_hash, ctx.actor_id if ctx else None) - cache_item = await database.get_cache_item(content_hash) - ipfs_cid = cache_item.get("ipfs_cid") if cache_item else None - file_size = cache_path.stat().st_size - media_type = detect_media_type(cache_path) - return { - "content_hash": content_hash, - "size": file_size, - "media_type": media_type, - "ipfs_cid": ipfs_cid, - "meta": meta - } - - @app.get("/cache/{content_hash}/meta-form", response_class=HTMLResponse) async def cache_meta_form(content_hash: str, request: Request): """Clean URL redirect to the HTMX meta form.""" @@ -1898,12 +1903,6 @@ async def cache_meta_form(content_hash: str, request: Request): return RedirectResponse(f"/ui/cache/{content_hash}/meta-form", status_code=302) -@app.get("/ui/cache/{content_hash}") -async def ui_cache_view(content_hash: str): - """Redirect to clean URL.""" - return RedirectResponse(url=f"/cache/{content_hash}/detail", status_code=302) - - @app.get("/ui/cache/{content_hash}/meta-form", response_class=HTMLResponse) async def ui_cache_meta_form(content_hash: str, request: Request): """HTMX partial: metadata editing form for a cached item.""" @@ -2387,7 +2386,7 @@ async def list_media( size_str = f"{size} bytes" html_parts.append(f''' - +
{media_type}