feat: last-used persona cookie, emoji dropdown, theme support, auth status move
- cx_last_persona cookie set on serve_ui; root/login/help/settings
redirects use preferred persona from cookie instead of alphabetically first
- /api/personas returns [{name, emoji}] objects; persona switcher dropdown
renders emoji + name with flex layout and .pd-emoji span
- Help, Settings, Model Registry pages apply localStorage theme on load
(no flash); CSS variables for dark/light replacing all hardcoded hex values
- Claude CLI auth status moved from prominent chat banner to Anthropic
provider block in Model Registry — live dot indicator (ok/warn/err)
- Auth banner removed from main chat UI (index.html, app.js, style.css)
- Add Model collapsed into Models section as <details> to shorten page
- Light-mode overrides for provider icons, model badges, ctx-badge, tags
(Anthropic/Google/local colors now readable in both themes)
- Help page gains table, pre/code, hr styles for HELP.md rendered content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -360,10 +360,18 @@
|
||||
personaDropEl.innerHTML = '';
|
||||
|
||||
personas.forEach(p => {
|
||||
const name = p.name || p;
|
||||
const emoji = p.emoji || '✨';
|
||||
const a = document.createElement('a');
|
||||
a.href = `/${CORTEX_USER}/${p}`;
|
||||
a.textContent = p.charAt(0).toUpperCase() + p.slice(1);
|
||||
if (p === CORTEX_PERSONA) a.classList.add('active');
|
||||
a.href = `/${CORTEX_USER}/${name}`;
|
||||
if (name === CORTEX_PERSONA) a.classList.add('active');
|
||||
const emojiEl = document.createElement('span');
|
||||
emojiEl.className = 'pd-emoji';
|
||||
emojiEl.textContent = emoji;
|
||||
a.appendChild(emojiEl);
|
||||
const nameEl = document.createElement('span');
|
||||
nameEl.textContent = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
a.appendChild(nameEl);
|
||||
personaDropEl.appendChild(a);
|
||||
});
|
||||
|
||||
@@ -1493,19 +1501,6 @@
|
||||
try { data = JSON.parse(e.data); } catch { return; }
|
||||
if (data.type === 'keepalive') return;
|
||||
|
||||
if (data.type === 'claude_auth_expired') {
|
||||
let banner = document.getElementById('claude-auth-banner');
|
||||
if (!banner) {
|
||||
banner = document.createElement('div');
|
||||
banner.id = 'claude-auth-banner';
|
||||
banner.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;background:#7c2d12;color:#fef2f2;padding:0.6rem 1rem;font-size:0.85rem;display:flex;align-items:center;justify-content:space-between;gap:1rem;';
|
||||
banner.innerHTML = '<span>⚠️ Claude authentication expired — run <code style="background:#991b1b;padding:0.1rem 0.3rem;border-radius:3px;">claude</code> in your terminal to re-authenticate, then reload.</span>'
|
||||
+ '<button onclick="this.parentElement.remove()" style="background:none;border:none;color:#fef2f2;font-size:1.1rem;cursor:pointer;padding:0 0.3rem;">✕</button>';
|
||||
document.body.prepend(banner);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type !== 'nct_message' && data.type !== 'nct_response') return;
|
||||
|
||||
if (sessionId === data.session_id) {
|
||||
@@ -1704,57 +1699,6 @@
|
||||
syncHeight();
|
||||
addMessage('system', 'Session started');
|
||||
|
||||
// ── Auth token warning banner ─────────────────────────────
|
||||
const authBanner = document.getElementById('auth-banner');
|
||||
const authBannerMsg = document.getElementById('auth-banner-msg');
|
||||
const authBannerHint = document.getElementById('auth-banner-hint');
|
||||
const authBannerClose = document.getElementById('auth-banner-close');
|
||||
|
||||
async function checkAuthStatus() {
|
||||
try {
|
||||
const res = await fetch('/auth/status');
|
||||
if (!res.ok) return;
|
||||
const d = await res.json();
|
||||
|
||||
const warnings = [];
|
||||
const fixes = [];
|
||||
let anyExpired = false;
|
||||
|
||||
if (d.claude?.warning) {
|
||||
if (d.claude.expired) {
|
||||
warnings.push('✕ Claude CLI token has expired');
|
||||
anyExpired = true;
|
||||
} else {
|
||||
warnings.push(`⚠ Claude CLI token expires in ${d.claude.access_token_hours_remaining}h`);
|
||||
}
|
||||
fixes.push('<code>claude</code>');
|
||||
}
|
||||
|
||||
if (d.gemini?.warning) {
|
||||
warnings.push('⚠ Gemini CLI not authenticated');
|
||||
fixes.push('<code>gemini</code>');
|
||||
}
|
||||
|
||||
if (!warnings.length) {
|
||||
authBanner.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
|
||||
authBannerMsg.innerHTML = warnings.join('<br>');
|
||||
authBannerHint.innerHTML =
|
||||
`To fix: SSH into the Cortex host and run ${fixes.join(' and/or ')} — `
|
||||
+ 'follow the login prompt, then restart Cortex.';
|
||||
authBanner.classList.toggle('expired', anyExpired);
|
||||
authBanner.classList.add('show');
|
||||
} catch { /* silently ignore — don't break the UI */ }
|
||||
}
|
||||
|
||||
authBannerClose.addEventListener('click', () => authBanner.classList.remove('show'));
|
||||
|
||||
checkAuthStatus();
|
||||
// Re-check every 30 minutes
|
||||
setInterval(checkAuthStatus, 30 * 60 * 1000);
|
||||
|
||||
// ── Initial render ────────────────────────────────────────────
|
||||
// Process all static Lucide SVGs in the header + stop button,
|
||||
// and seed the mode UI (which also calls render_icons internally).
|
||||
|
||||
Reference in New Issue
Block a user