Replaces the lone "← Back to Cortex" link with a consistent page-nav
on both pages: ← Chat | Help | Settings | Sign out
Active page is highlighted purple; others are muted gray.
Settings page gets a {{ help_href }} template var from settings.py.
Help page builds nav links from the existing cfg JS object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
384 lines
12 KiB
HTML
384 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Cortex — Account Settings</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: #0f1117;
|
|
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
font-weight: 450;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
color: #e2e8f0;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.card {
|
|
background: #1a1d27;
|
|
border: 1px solid #2d3148;
|
|
border-radius: 12px;
|
|
padding: 2.5rem 2rem;
|
|
width: 100%;
|
|
max-width: 480px;
|
|
}
|
|
|
|
.page-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
margin-bottom: 1.75rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
.nav-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.3rem 0.6rem;
|
|
border-radius: 6px;
|
|
font-size: 0.8rem;
|
|
font-weight: 500;
|
|
color: #64748b;
|
|
text-decoration: none;
|
|
transition: color 0.15s, background 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
.nav-link:hover { color: #cbd5e1; background: rgba(255,255,255,0.05); }
|
|
.nav-link.active { color: #a78bfa; }
|
|
.nav-spacer { flex: 1; min-width: 0.5rem; }
|
|
.nav-link.nav-logout { color: #475569; }
|
|
.nav-link.nav-logout:hover { color: #94a3b8; background: none; }
|
|
|
|
.logo {
|
|
margin-bottom: 1.75rem;
|
|
}
|
|
.logo h1 { font-size: 1.4rem; font-weight: 700; color: #a78bfa; }
|
|
.logo p { font-size: 0.8rem; color: #94a3b8; margin-top: 0.2rem; }
|
|
|
|
h2 {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: #94a3b8;
|
|
margin-bottom: 1rem;
|
|
padding-bottom: 0.4rem;
|
|
border-bottom: 1px solid #2d3148;
|
|
}
|
|
|
|
.section { margin-bottom: 2rem; }
|
|
|
|
label {
|
|
display: block;
|
|
font-size: 0.8rem;
|
|
font-weight: 500;
|
|
color: #94a3b8;
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
|
|
input {
|
|
width: 100%;
|
|
padding: 0.65rem 0.85rem;
|
|
background: #0f1117;
|
|
border: 1px solid #2d3148;
|
|
border-radius: 6px;
|
|
color: #e2e8f0;
|
|
font-size: 0.95rem;
|
|
outline: none;
|
|
transition: border-color 0.15s;
|
|
}
|
|
input:focus { border-color: #7c3aed; }
|
|
input[readonly] { color: #94a3b8; cursor: default; }
|
|
|
|
.field { margin-bottom: 1rem; }
|
|
|
|
button[type="submit"] {
|
|
width: 100%;
|
|
padding: 0.7rem;
|
|
margin-top: 0.25rem;
|
|
background: #7c3aed;
|
|
border: none;
|
|
border-radius: 6px;
|
|
color: #fff;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
button[type="submit"]:hover { background: #6d28d9; }
|
|
|
|
.error {
|
|
color: #f87171;
|
|
font-size: 0.85rem;
|
|
text-align: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.success {
|
|
color: #4ade80;
|
|
font-size: 0.85rem;
|
|
text-align: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.persona-list {
|
|
list-style: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
.persona-list li {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
.persona-link {
|
|
display: inline-block;
|
|
padding: 0.3rem 0.75rem;
|
|
background: #0f1117;
|
|
border: 1px solid #2d3148;
|
|
border-radius: 20px;
|
|
color: #a78bfa;
|
|
font-size: 0.85rem;
|
|
text-decoration: none;
|
|
transition: border-color 0.15s;
|
|
}
|
|
.persona-link:hover { border-color: #7c3aed; }
|
|
.persona-list li em { color: #94a3b8; font-size: 0.85rem; }
|
|
|
|
.persona-rename-toggle {
|
|
background: none;
|
|
border: none;
|
|
color: #94a3b8;
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
padding: 0.2rem 0.4rem;
|
|
border-radius: 4px;
|
|
opacity: 0.7;
|
|
transition: opacity 0.15s, color 0.15s;
|
|
}
|
|
.persona-rename-toggle:hover { opacity: 1; color: #a78bfa; }
|
|
|
|
.persona-rename-form { display: flex; align-items: center; gap: 0.4rem; }
|
|
.persona-rename-form input[type="text"] {
|
|
width: 12rem;
|
|
padding: 0.3rem 0.6rem;
|
|
background: #0f1117;
|
|
border: 1px solid #7c3aed;
|
|
border-radius: 6px;
|
|
color: #e2e8f0;
|
|
font-size: 0.9rem;
|
|
outline: none;
|
|
}
|
|
.persona-rename-form button[type="submit"] {
|
|
width: auto;
|
|
padding: 0.3rem 0.75rem;
|
|
font-size: 0.85rem;
|
|
margin-top: 0;
|
|
}
|
|
.persona-rename-cancel {
|
|
background: none;
|
|
border: 1px solid #2d3148;
|
|
border-radius: 6px;
|
|
color: #94a3b8;
|
|
font-size: 0.85rem;
|
|
padding: 0.3rem 0.6rem;
|
|
cursor: pointer;
|
|
}
|
|
.persona-rename-cancel:hover { border-color: #94a3b8; color: #e2e8f0; }
|
|
|
|
.add-persona {
|
|
display: inline-block;
|
|
margin-top: 0.75rem;
|
|
font-size: 0.8rem;
|
|
color: #94a3b8;
|
|
text-decoration: none;
|
|
}
|
|
.add-persona:hover { color: #a78bfa; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<nav class="page-nav">
|
|
<a href="{{ back_href }}" class="nav-link">← Chat</a>
|
|
<a href="{{ help_href }}" class="nav-link">Help</a>
|
|
<a href="/settings" class="nav-link active">Settings</a>
|
|
<span class="nav-spacer"></span>
|
|
<a href="/logout" class="nav-link nav-logout">Sign out</a>
|
|
</nav>
|
|
|
|
<div class="logo">
|
|
<h1>Account Settings</h1>
|
|
<p>Manage your account and personas.</p>
|
|
</div>
|
|
|
|
<!-- SUCCESS -->
|
|
<!-- ERROR -->
|
|
|
|
<!-- Account info -->
|
|
<div class="section">
|
|
<h2>Account</h2>
|
|
<div class="field">
|
|
<label>Username</label>
|
|
<input type="text" value="{{ username }}" readonly>
|
|
</div>
|
|
<button type="button" id="show-rename-user" class="persona-rename-toggle"
|
|
style="opacity:0.7; font-size:0.8rem; padding:0.3rem 0.6rem; border:1px solid #2d3148; border-radius:6px; margin-top:0.25rem;">
|
|
✏ Change username
|
|
</button>
|
|
<form id="rename-user-form" method="POST" action="/settings/username"
|
|
style="display:none; margin-top:0.75rem;">
|
|
<div class="field">
|
|
<label for="new_username">New username</label>
|
|
<input type="text" id="new_username" name="new_username"
|
|
value="{{ username }}"
|
|
pattern="[a-z_][a-z0-9_\-]{0,31}" required autofocus>
|
|
<p style="font-size:0.75rem; color:#94a3b8; margin-top:0.3rem;">
|
|
Lowercase letters, digits, _ or - only. You will be logged out after renaming.
|
|
</p>
|
|
</div>
|
|
<div style="display:flex; gap:0.5rem;">
|
|
<button type="submit" style="flex:1; padding:0.5rem; background:#7c3aed; border:none; border-radius:6px; color:#fff; font-size:0.9rem; font-weight:600; cursor:pointer;">Save</button>
|
|
<button type="button" id="cancel-rename-user"
|
|
style="padding:0.5rem 0.9rem; background:none; border:1px solid #2d3148; border-radius:6px; color:#94a3b8; font-size:0.9rem; cursor:pointer;">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Connected accounts -->
|
|
<div class="section">
|
|
<h2>Connected Accounts</h2>
|
|
<div class="field">
|
|
<label>Google Account</label>
|
|
<input type="text" value="{{ google_email }}" readonly
|
|
placeholder="No Google account linked"
|
|
style="{{ google_email == '' and 'color:#475569' or '' }}">
|
|
</div>
|
|
<p style="font-size:0.75rem; color:#94a3b8; margin-top:-0.5rem;">
|
|
To link or change your Google account, contact Scott.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Gemini API key -->
|
|
<div class="section">
|
|
<h2>Gemini API Key</h2>
|
|
<p style="font-size:0.8rem; color:#94a3b8; margin-bottom:0.85rem; line-height:1.55;">
|
|
Paste your personal key from
|
|
<a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener"
|
|
style="color:#a78bfa;">aistudio.google.com/apikey</a>
|
|
to use your own Gemini quota. Leave blank to use the shared server key.
|
|
</p>
|
|
<form method="POST" action="/settings/gemini-key">
|
|
<div class="field">
|
|
<label for="gemini_api_key">API Key</label>
|
|
<input type="text" id="gemini_api_key" name="gemini_api_key"
|
|
placeholder="{{ gemini_key_hint }}" autocomplete="off"
|
|
spellcheck="false" data-1p-ignore data-lpignore="true">
|
|
</div>
|
|
<button type="submit">Save Key</button>
|
|
</form>
|
|
<p id="gemini-key-status" style="font-size:0.75rem; color:#94a3b8; margin-top:0.5rem;">
|
|
Current: {{ gemini_key_hint }}
|
|
<span id="gemini-remove-wrap" style="{{ gemini_key_set == 'false' and 'display:none' or '' }}">
|
|
— <a href="#" id="gemini-remove-link" style="color:#f87171;">remove</a>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Change password -->
|
|
<div class="section">
|
|
<h2>Change Password</h2>
|
|
<form method="POST" action="/settings/password" id="password-form">
|
|
<div class="field">
|
|
<label for="current_password">Current password</label>
|
|
<input type="password" id="current_password" name="current_password"
|
|
autocomplete="current-password" required>
|
|
</div>
|
|
<div class="field">
|
|
<label for="new_password">New password</label>
|
|
<input type="password" id="new_password" name="new_password"
|
|
autocomplete="new-password" required minlength="8">
|
|
</div>
|
|
<div class="field">
|
|
<label for="confirm_password">Confirm new password</label>
|
|
<input type="password" id="confirm_password" name="confirm_password"
|
|
autocomplete="new-password" required>
|
|
</div>
|
|
<button type="submit">Update password</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Personas -->
|
|
<div class="section">
|
|
<h2>Personas</h2>
|
|
<ul class="persona-list">
|
|
{{ persona_items }}
|
|
</ul>
|
|
<a href="/setup/persona" class="add-persona">+ Add new persona</a>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Password confirmation check
|
|
document.getElementById('password-form').addEventListener('submit', e => {
|
|
const np = document.getElementById('new_password').value;
|
|
const cfm = document.getElementById('confirm_password').value;
|
|
if (np !== cfm) {
|
|
e.preventDefault();
|
|
alert('New passwords do not match.');
|
|
}
|
|
});
|
|
|
|
// Username rename toggle
|
|
document.getElementById('show-rename-user').addEventListener('click', () => {
|
|
document.getElementById('show-rename-user').style.display = 'none';
|
|
document.getElementById('rename-user-form').style.display = 'block';
|
|
document.getElementById('new_username').focus();
|
|
});
|
|
document.getElementById('cancel-rename-user').addEventListener('click', () => {
|
|
document.getElementById('rename-user-form').style.display = 'none';
|
|
document.getElementById('show-rename-user').style.display = '';
|
|
});
|
|
|
|
// Gemini key — "remove" link clears the input and submits the form
|
|
const geminiRemove = document.getElementById('gemini-remove-link');
|
|
if (geminiRemove) {
|
|
geminiRemove.addEventListener('click', e => {
|
|
e.preventDefault();
|
|
document.getElementById('gemini_api_key').value = '';
|
|
document.querySelector('form[action="/settings/gemini-key"]').submit();
|
|
});
|
|
}
|
|
|
|
// Persona rename toggle
|
|
document.querySelectorAll('.persona-rename-toggle').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const p = btn.dataset.persona;
|
|
const form = document.querySelector(`.persona-rename-form[data-persona="${p}"]`);
|
|
btn.style.display = 'none';
|
|
form.style.display = 'flex';
|
|
form.querySelector('input[type="text"]').focus();
|
|
});
|
|
});
|
|
document.querySelectorAll('.persona-rename-cancel').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const form = btn.closest('.persona-rename-form');
|
|
const p = form.dataset.persona;
|
|
const toggle = document.querySelector(`.persona-rename-toggle[data-persona="${p}"]`);
|
|
form.style.display = 'none';
|
|
toggle.style.display = '';
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|