refactor: extract shared settings stylesheet (pg.css) and clean up inline styles
- Create cortex/static/pg.css with shared variables, nav, containers, form elements, button classes, text utilities, and feedback messages - All four settings pages (settings, notifications, tools, help) now link to pg.css and have page-specific-only <style> blocks - Style block line counts reduced: settings 244→70, notifications 189→42, tools 126→88, help 122→75 - Inline style= attributes reduced: settings 45→4, notifications 7→3, tools 12→4, help 0 - Introduced shared CSS classes: .btn-submit, .btn-save, .btn-cancel, .btn-secondary, .action-link, .hint, .section-note, .page-title, .page-subtitle, .page-wrap, .usage-table, .tool-cat-row - Fix tools_settings.py: replace server-generated inline styles on category header rows with .tool-cat-row CSS class - Fix settings.py: add btn-save/btn-cancel/persona-rename-cancel classes to server-rendered persona rename form buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,273 +7,93 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="/static/pg.css">
|
||||
<script>(function(){var t=localStorage.getItem('theme')||(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');document.documentElement.setAttribute('data-theme',t);})();</script>
|
||||
<style>
|
||||
:root {
|
||||
--pg-bg: #0f1117; --pg-surface: #1a1d27;
|
||||
--pg-border: #2d3148;
|
||||
--pg-text: #e2e8f0; --pg-muted: #94a3b8;
|
||||
--pg-dim: #64748b; --pg-dimmer: #475569;
|
||||
--pg-bright: #cbd5e1; --pg-nav-hover: rgba(255,255,255,0.05);
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--pg-bg: #f4f2fa; --pg-surface: #ffffff;
|
||||
--pg-border: #d0c8e8;
|
||||
--pg-text: #1a1228; --pg-muted: #5a5478;
|
||||
--pg-dim: #7a7290; --pg-dimmer: #9e98b0;
|
||||
--pg-bright: #1a1228; --pg-nav-hover: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--pg-bg);
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
font-weight: 450;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: var(--pg-text);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--pg-surface);
|
||||
border: 1px solid var(--pg-border);
|
||||
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: var(--pg-dim);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.nav-link:hover { color: var(--pg-bright); background: var(--pg-nav-hover); }
|
||||
.nav-link.active { color: #a78bfa; }
|
||||
.nav-spacer { flex: 1; min-width: 0.5rem; }
|
||||
.nav-link.nav-logout { color: var(--pg-dimmer); }
|
||||
.nav-link.nav-logout:hover { color: var(--pg-muted); 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: var(--pg-muted); margin-top: 0.2rem; }
|
||||
|
||||
h2 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--pg-muted);
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px solid var(--pg-border);
|
||||
}
|
||||
|
||||
.section { margin-bottom: 2rem; }
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: var(--pg-muted);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.65rem 0.85rem;
|
||||
background: var(--pg-bg);
|
||||
border: 1px solid var(--pg-border);
|
||||
border-radius: 6px;
|
||||
color: var(--pg-text);
|
||||
font-size: 0.95rem;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
input:focus { border-color: #7c3aed; }
|
||||
input[readonly] { color: var(--pg-muted); 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 ── */
|
||||
.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;
|
||||
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: var(--pg-bg);
|
||||
border: 1px solid var(--pg-border);
|
||||
border-radius: 20px;
|
||||
color: #a78bfa;
|
||||
font-size: 0.85rem;
|
||||
text-decoration: none;
|
||||
transition: border-color 0.15s;
|
||||
display: inline-block; padding: 0.3rem 0.75rem;
|
||||
background: var(--pg-bg); border: 1px solid var(--pg-border);
|
||||
border-radius: 20px; color: var(--pg-accent); font-size: 0.85rem;
|
||||
text-decoration: none; transition: border-color 0.15s;
|
||||
}
|
||||
.persona-link:hover { border-color: #7c3aed; }
|
||||
.persona-link:hover { border-color: var(--pg-action); }
|
||||
.persona-list li em { color: var(--pg-muted); font-size: 0.85rem; }
|
||||
|
||||
.persona-rename-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--pg-muted);
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.15s, color 0.15s;
|
||||
background: none; border: 1px solid var(--pg-border);
|
||||
border-radius: 6px; color: var(--pg-muted); font-size: 0.8rem;
|
||||
padding: 0.3rem 0.6rem; margin-top: 0.25rem;
|
||||
cursor: pointer; opacity: 0.7; transition: opacity 0.15s, color 0.15s;
|
||||
}
|
||||
.persona-rename-toggle:hover { opacity: 1; color: #a78bfa; }
|
||||
|
||||
.persona-rename-toggle:hover { opacity: 1; color: var(--pg-accent); }
|
||||
.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: var(--pg-bg);
|
||||
border: 1px solid #7c3aed;
|
||||
border-radius: 6px;
|
||||
color: var(--pg-text);
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
width: 12rem; padding: 0.3rem 0.6rem;
|
||||
border-color: var(--pg-action); font-size: 0.9rem;
|
||||
}
|
||||
.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 var(--pg-border);
|
||||
border-radius: 6px;
|
||||
color: var(--pg-muted);
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0.6rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.persona-rename-cancel:hover { border-color: var(--pg-muted); color: var(--pg-text); }
|
||||
|
||||
.persona-rename-form .btn-save { padding: 0.3rem 0.75rem; font-size: 0.85rem; }
|
||||
.add-persona {
|
||||
display: inline-block;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--pg-muted);
|
||||
text-decoration: none;
|
||||
display: inline-block; margin-top: 0.75rem;
|
||||
font-size: 0.8rem; color: var(--pg-muted); text-decoration: none;
|
||||
}
|
||||
.add-persona:hover { color: #a78bfa; }
|
||||
.add-persona:hover { color: var(--pg-accent); }
|
||||
|
||||
/* ── Role badge ── */
|
||||
.role-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
display: inline-block; padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px; font-size: 0.78rem; font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: 0.06em;
|
||||
}
|
||||
.role-badge.role-admin {
|
||||
background: rgba(124, 58, 237, 0.15);
|
||||
color: #a78bfa;
|
||||
border: 1px solid rgba(124, 58, 237, 0.4);
|
||||
background: rgba(124,58,237,0.15); color: var(--pg-accent);
|
||||
border: 1px solid rgba(124,58,237,0.4);
|
||||
}
|
||||
.role-badge.role-user {
|
||||
background: rgba(100, 116, 139, 0.12);
|
||||
color: var(--pg-muted);
|
||||
background: rgba(100,116,139,0.12); color: var(--pg-muted);
|
||||
border: 1px solid var(--pg-border);
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.65rem 0.85rem;
|
||||
background: var(--pg-bg);
|
||||
border: 1px solid var(--pg-border);
|
||||
border-radius: 6px;
|
||||
color: var(--pg-text);
|
||||
font-size: 0.88rem;
|
||||
font-family: 'SF Mono', 'Fira Mono', 'Menlo', monospace;
|
||||
line-height: 1.55;
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
/* ── OpenRouter quickstart warning card ── */
|
||||
#openrouter-quickstart {
|
||||
display: none; background: #1c1a0a; border: 1px solid #78350f;
|
||||
border-radius: 8px; padding: 1rem; margin-bottom: 1rem;
|
||||
}
|
||||
textarea:focus { border-color: #7c3aed; }
|
||||
#openrouter-quickstart .qs-title {
|
||||
font-size: 0.82rem; color: #fbbf24; font-weight: 600; margin-bottom: 0.4rem;
|
||||
}
|
||||
#openrouter-quickstart .qs-body {
|
||||
font-size: 0.8rem; color: #d97706; margin-bottom: 0.75rem; line-height: 1.5;
|
||||
}
|
||||
.action-link.action-link-amber {
|
||||
background: #92400e; color: #fef3c7; font-size: 0.85rem; padding: 0.5rem 0.9rem;
|
||||
}
|
||||
.action-link.action-link-amber:hover { opacity: 0.9; background: #78350f; }
|
||||
|
||||
/* ── Inline result feedback spans ── */
|
||||
.result-ok { display: none; margin-left: 0.75rem; font-size: 0.8rem; color: #4ade80; }
|
||||
|
||||
/* ── Usage table wrapper ── */
|
||||
.usage-wrap { overflow-x: auto; }
|
||||
</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>
|
||||
<a href="/settings/notifications" class="nav-link">Notifications</a>
|
||||
<a href="/settings/tools" class="nav-link">Tools</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>
|
||||
<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>
|
||||
<a href="/settings/notifications" class="nav-link">Notifications</a>
|
||||
<a href="/settings/tools" class="nav-link">Tools</a>
|
||||
<span class="nav-spacer"></span>
|
||||
<a href="/logout" class="nav-link nav-logout">Sign out</a>
|
||||
</nav>
|
||||
<div class="page-wrap">
|
||||
<h1 class="page-title">Account Settings</h1>
|
||||
<p class="page-subtitle">Manage your account and personas.</p>
|
||||
|
||||
<!-- SUCCESS -->
|
||||
<!-- ERROR -->
|
||||
@@ -289,26 +109,21 @@
|
||||
<label>Role</label>
|
||||
<span class="role-badge role-{{ user_role }}">{{ user_role }}</span>
|
||||
</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 var(--pg-border); border-radius:6px; margin-top:0.25rem;">
|
||||
<button type="button" id="show-rename-user" class="persona-rename-toggle">
|
||||
✏ Change username
|
||||
</button>
|
||||
<form id="rename-user-form" method="POST" action="/settings/username"
|
||||
style="display:none; margin-top:0.75rem;">
|
||||
<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
|
||||
autocomplete="off" data-form-type="other">
|
||||
<p style="font-size:0.75rem; color:var(--pg-muted); margin-top:0.3rem;">
|
||||
Lowercase letters, digits, _ or - only. You will be logged out after renaming.
|
||||
</p>
|
||||
<p class="hint">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 var(--pg-border); border-radius:6px; color:var(--pg-muted); font-size:0.9rem; cursor:pointer;">Cancel</button>
|
||||
<div class="btn-row">
|
||||
<button type="submit" class="btn-save">Save</button>
|
||||
<button type="button" id="cancel-rename-user" class="btn-cancel">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -322,18 +137,15 @@
|
||||
placeholder="No Google account linked"
|
||||
style="{{ google_email == '' and 'color:var(--pg-dimmer)' or '' }}">
|
||||
</div>
|
||||
<p style="font-size:0.75rem; color:var(--pg-muted); margin-top:-0.5rem;">
|
||||
To link or change your Google account, contact Scott.
|
||||
</p>
|
||||
<p class="hint" style="margin-top:-0.5rem;">To link or change your Google account, contact Scott.</p>
|
||||
</div>
|
||||
|
||||
<!-- Email Allowlist -->
|
||||
<div class="section">
|
||||
<h2>Email Allowlist</h2>
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
One regex pattern per line. The <code style="font-size:0.82rem; background:var(--pg-bg); padding:0.1rem 0.35rem; border-radius:4px;">email_send</code>
|
||||
tool will only send to addresses that match at least one pattern.
|
||||
Leave blank to block all outbound email.
|
||||
<p class="section-note">
|
||||
One regex pattern per line. The <code>email_send</code> tool will only send to addresses
|
||||
that match at least one pattern. Leave blank to block all outbound email.
|
||||
</p>
|
||||
<form method="POST" action="/settings/email-allowlist">
|
||||
<div class="field">
|
||||
@@ -342,17 +154,16 @@
|
||||
placeholder=".*@example\.com alice@example\.com"
|
||||
spellcheck="false">{{ email_allowlist }}</textarea>
|
||||
</div>
|
||||
<button type="submit">Save allowlist</button>
|
||||
<button type="submit" class="btn-submit">Save allowlist</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- HTTP POST Allowlist -->
|
||||
<div class="section">
|
||||
<h2>HTTP POST Allowlist</h2>
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
One URL prefix per line. The <code style="font-size:0.82rem; background:var(--pg-bg); padding:0.1rem 0.35rem; border-radius:4px;">http_post</code>
|
||||
tool will only POST to URLs that start with a listed prefix.
|
||||
Leave blank to block all outbound POST requests.
|
||||
<p class="section-note">
|
||||
One URL prefix per line. The <code>http_post</code> tool will only POST to URLs that
|
||||
start with a listed prefix. Leave blank to block all outbound POST requests.
|
||||
</p>
|
||||
<form method="POST" action="/settings/http-allowlist">
|
||||
<div class="field">
|
||||
@@ -361,103 +172,73 @@
|
||||
placeholder="https://ha.dgrzone.com/api/webhook/ https://n8n.dgrzone.com/webhook/"
|
||||
spellcheck="false">{{ http_allowlist }}</textarea>
|
||||
</div>
|
||||
<button type="submit">Save allowlist</button>
|
||||
<button type="submit" class="btn-submit">Save allowlist</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="section">
|
||||
<h2>Notifications</h2>
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
<p class="section-note">
|
||||
Configure how Inara reaches out proactively — reminders, cron jobs, and memory digests.
|
||||
</p>
|
||||
<a href="/settings/notifications"
|
||||
style="display:inline-block; padding:0.55rem 1rem; background:#7c3aed; border-radius:6px;
|
||||
color:#fff; font-size:0.88rem; font-weight:600; text-decoration:none;
|
||||
transition:background 0.15s;">
|
||||
Notification settings →
|
||||
</a>
|
||||
<a href="/settings/notifications" class="action-link">Notification settings →</a>
|
||||
</div>
|
||||
|
||||
<!-- Tool Permissions → moved to /settings/tools -->
|
||||
<!-- Tool Permissions → /settings/tools -->
|
||||
<div class="section">
|
||||
<h2>Tool Permissions</h2>
|
||||
<p style="font-size:0.85rem; color:var(--pg-muted); margin-bottom:1rem; line-height:1.55;">
|
||||
<p class="section-note">
|
||||
Configure tool access, risk policy, and confirmation gate overrides on the Tools page.
|
||||
</p>
|
||||
<a href="/settings/tools"
|
||||
style="display:inline-block; padding:0.45rem 1.1rem; background:var(--pg-accent,#7c3aed);
|
||||
color:#fff; border-radius:0.5rem; font-size:0.875rem; font-weight:600;
|
||||
text-decoration:none;">
|
||||
Tool Settings →
|
||||
</a>
|
||||
<a href="/settings/tools" class="action-link">Tool settings →</a>
|
||||
</div>
|
||||
|
||||
<!-- Browser cache -->
|
||||
<!-- Usage summary -->
|
||||
<div class="section" id="usage-section">
|
||||
<h2>Usage</h2>
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
<p class="section-note">
|
||||
Token consumption tracked for API-backed models (Gemini API, local OpenAI-compatible).
|
||||
Claude CLI calls are not metered.
|
||||
</p>
|
||||
<div id="usage-table-wrap" style="overflow-x:auto;">
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted);">Loading…</p>
|
||||
<div id="usage-table-wrap" class="usage-wrap">
|
||||
<p class="section-note">Loading…</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Browser Cache -->
|
||||
<div class="section">
|
||||
<h2>Browser Cache</h2>
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
<p class="section-note">
|
||||
Clears UI preferences stored in this browser: active mode, session ID, memory toggles,
|
||||
theme, font size, and context tier. Does not sign you out.
|
||||
</p>
|
||||
<button type="button" id="clear-ls-btn"
|
||||
style="padding:0.5rem 1rem; background:none; border:1px solid var(--pg-border); border-radius:6px;
|
||||
color:var(--pg-muted); font-size:0.88rem; font-weight:500; cursor:pointer;
|
||||
transition:border-color 0.15s, color 0.15s;">
|
||||
Clear browser cache
|
||||
</button>
|
||||
<span id="clear-ls-ok" style="display:none; margin-left:0.75rem; font-size:0.8rem; color:#4ade80;">
|
||||
Cleared.
|
||||
</span>
|
||||
<button type="button" id="clear-ls-btn" class="btn-secondary">Clear browser cache</button>
|
||||
<span id="clear-ls-ok" class="result-ok">Cleared.</span>
|
||||
</div>
|
||||
|
||||
<!-- Model Registry link -->
|
||||
<!-- Model Registry -->
|
||||
<div class="section">
|
||||
<h2>Model Registry</h2>
|
||||
|
||||
<!-- Quick-start card: shown only when no model is configured for chat role -->
|
||||
<div id="openrouter-quickstart" style="display:none; background:#1c1a0a; border:1px solid #78350f;
|
||||
border-radius:8px; padding:1rem; margin-bottom:1rem;">
|
||||
<p style="font-size:0.82rem; color:#fbbf24; font-weight:600; margin-bottom:0.4rem;">
|
||||
⚡ You're on the server default model
|
||||
</p>
|
||||
<p style="font-size:0.8rem; color:#d97706; margin-bottom:0.75rem; line-height:1.5;">
|
||||
<div id="openrouter-quickstart">
|
||||
<p class="qs-title">⚡ You're on the server default model</p>
|
||||
<p class="qs-body">
|
||||
You can chat now, but adding your own model gives you more choices, lets you pick
|
||||
role-specific models, and tracks your usage separately.
|
||||
OpenRouter is the easiest way to get started — one key, many models.
|
||||
</p>
|
||||
<a href="/setup/model"
|
||||
style="display:inline-block; padding:0.5rem 0.9rem; background:#92400e; border-radius:6px;
|
||||
color:#fef3c7; font-size:0.85rem; font-weight:600; text-decoration:none;">
|
||||
Set up OpenRouter →
|
||||
</a>
|
||||
<a href="/setup/model" class="action-link action-link-amber">Set up OpenRouter →</a>
|
||||
</div>
|
||||
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
<p class="section-note">
|
||||
Configure AI providers (Anthropic, Google), local hosts (Open WebUI, Ollama, OpenRouter, etc.),
|
||||
and assign models to roles — chat, orchestrator, distill, and more.
|
||||
</p>
|
||||
<a href="/settings/models"
|
||||
style="display:inline-block; padding:0.55rem 1rem; background:#7c3aed; border-radius:6px;
|
||||
color:#fff; font-size:0.88rem; font-weight:600; text-decoration:none;
|
||||
transition:background 0.15s;">
|
||||
Manage models →
|
||||
</a>
|
||||
<a href="/settings/models" class="action-link">Manage models →</a>
|
||||
</div>
|
||||
|
||||
<!-- Change password -->
|
||||
<!-- Change Password -->
|
||||
<div class="section">
|
||||
<h2>Change Password</h2>
|
||||
<form method="POST" action="/settings/password" id="password-form">
|
||||
@@ -476,27 +257,22 @@
|
||||
<input type="password" id="confirm_password" name="confirm_password"
|
||||
autocomplete="new-password" required>
|
||||
</div>
|
||||
<button type="submit">Update password</button>
|
||||
<button type="submit" class="btn-submit">Update password</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Personas -->
|
||||
<!-- Sessions -->
|
||||
<div class="section">
|
||||
<h2>Sessions</h2>
|
||||
<p style="font-size:0.8rem; color:var(--pg-muted); margin-bottom:0.85rem; line-height:1.55;">
|
||||
<p class="section-note">
|
||||
Auto-name any sessions that still show a random ID, using their first message as the name.
|
||||
Only unnamed sessions are affected — existing names are left alone.
|
||||
</p>
|
||||
<button type="button" id="backfill-names-btn"
|
||||
style="padding:0.5rem 1rem; background:none; border:1px solid var(--pg-border); border-radius:6px;
|
||||
color:var(--pg-muted); font-size:0.88rem; font-weight:500; cursor:pointer;
|
||||
transition:border-color 0.15s, color 0.15s;">
|
||||
Auto-name old sessions
|
||||
</button>
|
||||
<span id="backfill-names-ok" style="display:none; margin-left:0.75rem; font-size:0.8rem; color:#4ade80;"></span>
|
||||
<button type="button" id="backfill-names-btn" class="btn-secondary">Auto-name old sessions</button>
|
||||
<span id="backfill-names-ok" class="result-ok"></span>
|
||||
</div>
|
||||
|
||||
<!-- Personas -->
|
||||
<div class="section">
|
||||
<h2>Personas</h2>
|
||||
<ul class="persona-list">
|
||||
@@ -528,16 +304,6 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
// Clear localStorage (keeps JWT cookie — no sign-out)
|
||||
document.getElementById('clear-ls-btn').addEventListener('click', () => {
|
||||
localStorage.clear();
|
||||
@@ -548,8 +314,7 @@
|
||||
(async () => {
|
||||
try {
|
||||
const d = await fetch('/backend').then(r => r.json());
|
||||
const roles = d.available_roles || [];
|
||||
if (roles.length === 0) {
|
||||
if ((d.available_roles || []).length === 0) {
|
||||
document.getElementById('openrouter-quickstart').style.display = 'block';
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -563,7 +328,7 @@
|
||||
if (!resp.ok) throw new Error(resp.statusText);
|
||||
const rows_data = await resp.json();
|
||||
if (!rows_data.length) {
|
||||
wrap.innerHTML = '<p style="font-size:0.8rem;color:var(--pg-muted);">No usage recorded yet.</p>';
|
||||
wrap.innerHTML = '<p class="section-note">No usage recorded yet.</p>';
|
||||
return;
|
||||
}
|
||||
const fmt = n => n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n);
|
||||
@@ -572,27 +337,22 @@
|
||||
? `<span title="${d.key}">${d.label}</span>`
|
||||
: `<span>${d.key}</span>`;
|
||||
return `<tr>
|
||||
<td style="padding:0.4rem 0.75rem 0.4rem 0; font-size:0.82rem; color:var(--pg-text); white-space:nowrap;">${labelCell}</td>
|
||||
<td style="padding:0.4rem 0.5rem; font-size:0.82rem; color:var(--pg-muted); text-align:right;">${d.calls}</td>
|
||||
<td style="padding:0.4rem 0.5rem; font-size:0.82rem; color:var(--pg-muted); text-align:right;">${fmt(d.prompt_tokens)}</td>
|
||||
<td style="padding:0.4rem 0.5rem; font-size:0.82rem; color:var(--pg-muted); text-align:right;">${fmt(d.completion_tokens)}</td>
|
||||
<td style="padding:0.4rem 0 0.4rem 0.5rem; font-size:0.82rem; color:var(--pg-text); text-align:right; font-weight:600;">${fmt(d.total_tokens)}</td>
|
||||
<td>${labelCell}</td>
|
||||
<td>${d.calls}</td>
|
||||
<td>${fmt(d.prompt_tokens)}</td>
|
||||
<td>${fmt(d.completion_tokens)}</td>
|
||||
<td>${fmt(d.total_tokens)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
wrap.innerHTML = `<table style="border-collapse:collapse; width:100%; min-width:360px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid var(--pg-border);">
|
||||
<th style="padding:0.35rem 0.75rem 0.35rem 0; font-size:0.75rem; color:var(--pg-muted); font-weight:600; text-align:left;">Model</th>
|
||||
<th style="padding:0.35rem 0.5rem; font-size:0.75rem; color:var(--pg-muted); font-weight:600; text-align:right;">Calls</th>
|
||||
<th style="padding:0.35rem 0.5rem; font-size:0.75rem; color:var(--pg-muted); font-weight:600; text-align:right;">Prompt</th>
|
||||
<th style="padding:0.35rem 0.5rem; font-size:0.75rem; color:var(--pg-muted); font-weight:600; text-align:right;">Output</th>
|
||||
<th style="padding:0.35rem 0 0.35rem 0.5rem; font-size:0.75rem; color:var(--pg-muted); font-weight:600; text-align:right;">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
wrap.innerHTML = `<table class="usage-table">
|
||||
<thead><tr>
|
||||
<th style="text-align:left">Model</th>
|
||||
<th>Calls</th><th>Prompt</th><th>Output</th><th>Total</th>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>`;
|
||||
} catch (e) {
|
||||
wrap.innerHTML = `<p style="font-size:0.8rem;color:var(--pg-muted);">Could not load usage data.</p>`;
|
||||
wrap.innerHTML = '<p class="section-note">Could not load usage data.</p>';
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user