Fix content negotiation - default to HTML, not JSON
All public endpoints (/assets/{name}, /activities/{id}, /objects/{hash})
now default to HTML for browsers. JSON/ActivityStreams is only returned
when explicitly requested via Accept header. Fixes browser link clicks
showing JSON instead of HTML pages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
68
server.py
68
server.py
@@ -1652,22 +1652,28 @@ async def get_asset_by_name_legacy(name: str):
|
||||
|
||||
@app.get("/assets/{name}")
|
||||
async def get_asset(name: str, request: Request):
|
||||
"""Get asset by name. HTML for browsers, JSON for APIs."""
|
||||
"""Get asset by name. HTML for browsers (default), JSON only if explicitly requested."""
|
||||
registry = await load_registry()
|
||||
|
||||
# Check if JSON explicitly requested
|
||||
accept = request.headers.get("accept", "")
|
||||
wants_json = "application/json" in accept and "text/html" not in accept
|
||||
|
||||
if name not in registry.get("assets", {}):
|
||||
if wants_html(request):
|
||||
content = f'''
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Asset Not Found</h2>
|
||||
<p class="text-gray-400">No asset named "{name}" exists.</p>
|
||||
<p class="mt-4"><a href="/assets" class="text-blue-400 hover:text-blue-300">← Back to Assets</a></p>
|
||||
'''
|
||||
return HTMLResponse(base_html("Asset Not Found", content, get_user_from_cookie(request)))
|
||||
raise HTTPException(404, f"Asset not found: {name}")
|
||||
if wants_json:
|
||||
raise HTTPException(404, f"Asset not found: {name}")
|
||||
content = f'''
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Asset Not Found</h2>
|
||||
<p class="text-gray-400">No asset named "{name}" exists.</p>
|
||||
<p class="mt-4"><a href="/assets" class="text-blue-400 hover:text-blue-300">← Back to Assets</a></p>
|
||||
'''
|
||||
return HTMLResponse(base_html("Asset Not Found", content, get_user_from_cookie(request)))
|
||||
|
||||
if wants_html(request):
|
||||
return await ui_asset_detail(name, request)
|
||||
if wants_json:
|
||||
return registry["assets"][name]
|
||||
|
||||
return registry["assets"][name]
|
||||
# Default to HTML for browsers
|
||||
return await ui_asset_detail(name, request)
|
||||
|
||||
|
||||
@app.get("/assets/by-run-id/{run_id}")
|
||||
@@ -2372,26 +2378,30 @@ async def get_activities(request: Request, page: int = 1, limit: int = 20):
|
||||
|
||||
@app.get("/activities/{activity_index}")
|
||||
async def get_activity_detail(activity_index: int, request: Request):
|
||||
"""Get single activity. HTML for browsers, JSON for APIs."""
|
||||
"""Get single activity. HTML for browsers (default), JSON only if explicitly requested."""
|
||||
activities = await load_activities()
|
||||
|
||||
# Check if JSON explicitly requested
|
||||
accept = request.headers.get("accept", "")
|
||||
wants_json = ("application/json" in accept or "application/activity+json" in accept) and "text/html" not in accept
|
||||
|
||||
if activity_index < 0 or activity_index >= len(activities):
|
||||
if wants_html(request):
|
||||
content = '''
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Activity Not Found</h2>
|
||||
<p class="text-gray-400">This activity does not exist.</p>
|
||||
<p class="mt-4"><a href="/activities" class="text-blue-400 hover:text-blue-300">← Back to Activities</a></p>
|
||||
'''
|
||||
return HTMLResponse(base_html("Activity Not Found", content, get_user_from_cookie(request)))
|
||||
raise HTTPException(404, "Activity not found")
|
||||
if wants_json:
|
||||
raise HTTPException(404, "Activity not found")
|
||||
content = '''
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Activity Not Found</h2>
|
||||
<p class="text-gray-400">This activity does not exist.</p>
|
||||
<p class="mt-4"><a href="/activities" class="text-blue-400 hover:text-blue-300">← Back to Activities</a></p>
|
||||
'''
|
||||
return HTMLResponse(base_html("Activity Not Found", content, get_user_from_cookie(request)))
|
||||
|
||||
activity = activities[activity_index]
|
||||
|
||||
if wants_html(request):
|
||||
# Reuse the UI activity detail logic
|
||||
return await ui_activity_detail(activity_index, request)
|
||||
if wants_json:
|
||||
return activity
|
||||
|
||||
return activity
|
||||
# Default to HTML for browsers
|
||||
return await ui_activity_detail(activity_index, request)
|
||||
|
||||
|
||||
@app.get("/activity/{activity_index}")
|
||||
@@ -2408,12 +2418,12 @@ async def get_object(content_hash: str, request: Request):
|
||||
# Find asset by hash
|
||||
for name, asset in registry.get("assets", {}).items():
|
||||
if asset.get("content_hash") == content_hash:
|
||||
# Check Accept header for content negotiation
|
||||
# Check Accept header - only return JSON if explicitly requested
|
||||
accept = request.headers.get("accept", "")
|
||||
wants_html = "text/html" in accept and "application/json" not in accept and "application/activity+json" not in accept
|
||||
wants_json = ("application/json" in accept or "application/activity+json" in accept) and "text/html" not in accept
|
||||
|
||||
if wants_html:
|
||||
# Redirect to detail page for browsers
|
||||
if not wants_json:
|
||||
# Default: redirect to detail page for browsers
|
||||
return RedirectResponse(url=f"/assets/{name}", status_code=303)
|
||||
|
||||
owner = asset.get("owner", "unknown")
|
||||
|
||||
Reference in New Issue
Block a user