feat: /{username} persona picker landing page
Visiting /scott (or any user root) now shows a clean card page listing
all their personas with emoji + name, each linking to /{user}/{persona}.
Previously the route was unhandled (404 or wildcard match).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,112 @@ async def logout():
|
||||
return resp
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# User landing — /{username} → persona picker
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get("/{username}", include_in_schema=False)
|
||||
async def user_landing(username: str, request: Request):
|
||||
session_user = _get_session_user(request)
|
||||
if not session_user:
|
||||
return RedirectResponse("/login", status_code=302)
|
||||
if session_user != username:
|
||||
return RedirectResponse(f"/{session_user}", status_code=302)
|
||||
|
||||
personas = list_user_personas(username)
|
||||
if not personas:
|
||||
return HTMLResponse("<h1>No personas configured.</h1>", status_code=404)
|
||||
|
||||
cards_html = ""
|
||||
for p in personas:
|
||||
emoji = "✨"
|
||||
identity_path = persona_path(username, p) / "IDENTITY.md"
|
||||
if identity_path.exists():
|
||||
m = re.search(r"\|\s*Emoji\s*\|\s*(.+?)\s*\|", identity_path.read_text())
|
||||
if m:
|
||||
emoji = m.group(1).strip()
|
||||
cards_html += (
|
||||
f'<a href="/{username}/{p}" class="persona-card">'
|
||||
f'<span class="p-emoji">{emoji}</span>'
|
||||
f'<span class="p-name">{p.capitalize()}</span>'
|
||||
f'</a>\n'
|
||||
)
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cortex — {username}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||||
body {{
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #1a1228;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-weight: 450;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
color: #e8e0f0;
|
||||
padding: 2rem 1.5rem;
|
||||
}}
|
||||
.card {{
|
||||
background: #221840;
|
||||
border: 1px solid #3a2852;
|
||||
border-radius: 14px;
|
||||
padding: 2.5rem 2rem;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}}
|
||||
h1 {{ font-size: 1.3rem; font-weight: 700; color: #c4935a; margin-bottom: 0.4rem; }}
|
||||
.sub {{ font-size: 0.82rem; color: #b0a2c8; margin-bottom: 2rem; }}
|
||||
.personas {{ display: flex; flex-direction: column; gap: 0.75rem; }}
|
||||
.persona-card {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.85rem 1.2rem;
|
||||
background: #1a1228;
|
||||
border: 1px solid #3a2852;
|
||||
border-radius: 10px;
|
||||
color: #e8e0f0;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}}
|
||||
.persona-card:hover {{ border-color: #c4935a; background: #261d42; }}
|
||||
.p-emoji {{ font-size: 1.6rem; line-height: 1; }}
|
||||
.p-name {{ color: #c4935a; font-weight: 600; }}
|
||||
.settings-link {{
|
||||
display: inline-block;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.78rem;
|
||||
color: #b0a2c8;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.settings-link:hover {{ color: #e8e0f0; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Cortex</h1>
|
||||
<p class="sub">Signed in as <strong>{username}</strong> — choose a persona</p>
|
||||
<div class="personas">
|
||||
{cards_html} </div>
|
||||
<a href="/settings" class="settings-link">Account settings</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main UI — /{username}/{persona}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user