UI: context panel popover, light/dark theme, contrast improvements
- Light/dark mode: full palette via @media + [data-theme] override; theme toggle button (☾/☀) persists to localStorage; no flash on load - Dark mode softened: #1a1228 bg (was near-pure black), improved muted text contrast (#9080a8) - CSS variables: --shadow, --modal-overlay, --code-bg, --pre-bg, --success, --success-dim replace all hardcoded rgba/hex values - Context panel: ⚙ header button opens dropdown (like Sessions); tier badge shows current tier; closes on outside click - Removed always-visible context bar; controls now in panel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,28 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cortex — Inara</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✨</text></svg>">
|
||||
<!-- Apply saved theme before first paint to avoid flash -->
|
||||
<script>
|
||||
(function(){
|
||||
var t = localStorage.getItem('theme');
|
||||
if (!t) t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
})();
|
||||
</script>
|
||||
<script src="/static/marked.min.js"></script>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
/* ── Dark theme (default) ───────────────────────────────── */
|
||||
:root {
|
||||
--bg: #0d0a14;
|
||||
--surface: #16101f;
|
||||
--border: #2d1f3d;
|
||||
--bg: #1a1228;
|
||||
--surface: #221840;
|
||||
--border: #3a2852;
|
||||
--user-bg: #5c1528;
|
||||
--user-border: #7a1f36;
|
||||
--inara-bg: #1e1530;
|
||||
--inara-bg: #261d42;
|
||||
--inara-border: #3d2a55;
|
||||
--accent: #c4935a;
|
||||
--text: #e8e0f0;
|
||||
--muted: #6b5a80;
|
||||
--muted: #9080a8;
|
||||
--error-bg: #3b0f0f;
|
||||
--error-border: #7f1d1d;
|
||||
--error-text: #fca5a5;
|
||||
--shadow: rgba(0,0,0,0.55);
|
||||
--modal-overlay: rgba(0,0,0,0.72);
|
||||
--code-bg: rgba(0,0,0,0.30);
|
||||
--pre-bg: rgba(0,0,0,0.35);
|
||||
--success: #6abf6a;
|
||||
--success-dim: #2a4a2a;
|
||||
}
|
||||
|
||||
/* ── Light theme ─────────────────────────────────────────── */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme="dark"]) {
|
||||
--bg: #f2eef9;
|
||||
--surface: #e8e1f4;
|
||||
--border: #c0afd8;
|
||||
--user-bg: #f5dae2;
|
||||
--user-border: #d890aa;
|
||||
--inara-bg: #ede5f8;
|
||||
--inara-border: #b8a0d8;
|
||||
--accent: #7a4818;
|
||||
--text: #1c1030;
|
||||
--muted: #60487a;
|
||||
--error-bg: #fde8e8;
|
||||
--error-border: #d88888;
|
||||
--error-text: #8b0f0f;
|
||||
--shadow: rgba(0,0,0,0.18);
|
||||
--modal-overlay: rgba(0,0,0,0.45);
|
||||
--code-bg: rgba(0,0,0,0.06);
|
||||
--pre-bg: rgba(0,0,0,0.07);
|
||||
--success: #1e6e1e;
|
||||
--success-dim: #5aaa5a;
|
||||
}
|
||||
}
|
||||
|
||||
/* Manual overrides — take precedence over system preference */
|
||||
[data-theme="dark"] {
|
||||
--bg: #1a1228;
|
||||
--surface: #221840;
|
||||
--border: #3a2852;
|
||||
--user-bg: #5c1528;
|
||||
--user-border: #7a1f36;
|
||||
--inara-bg: #261d42;
|
||||
--inara-border: #3d2a55;
|
||||
--accent: #c4935a;
|
||||
--text: #e8e0f0;
|
||||
--muted: #9080a8;
|
||||
--error-bg: #3b0f0f;
|
||||
--error-border: #7f1d1d;
|
||||
--error-text: #fca5a5;
|
||||
--shadow: rgba(0,0,0,0.55);
|
||||
--modal-overlay: rgba(0,0,0,0.72);
|
||||
--code-bg: rgba(0,0,0,0.30);
|
||||
--pre-bg: rgba(0,0,0,0.35);
|
||||
--success: #6abf6a;
|
||||
--success-dim: #2a4a2a;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg: #f2eef9;
|
||||
--surface: #e8e1f4;
|
||||
--border: #c0afd8;
|
||||
--user-bg: #f5dae2;
|
||||
--user-border: #d890aa;
|
||||
--inara-bg: #ede5f8;
|
||||
--inara-border: #b8a0d8;
|
||||
--accent: #7a4818;
|
||||
--text: #1c1030;
|
||||
--muted: #60487a;
|
||||
--error-bg: #fde8e8;
|
||||
--error-border: #d88888;
|
||||
--error-text: #8b0f0f;
|
||||
--shadow: rgba(0,0,0,0.18);
|
||||
--modal-overlay: rgba(0,0,0,0.45);
|
||||
--code-bg: rgba(0,0,0,0.06);
|
||||
--pre-bg: rgba(0,0,0,0.07);
|
||||
--success: #1e6e1e;
|
||||
--success-dim: #5aaa5a;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -74,7 +159,7 @@
|
||||
|
||||
.hdr-btn:hover { border-color: var(--muted); color: var(--text); }
|
||||
|
||||
#backend-toggle.gemini { border-color: #2a4a2a; color: #6abf6a; }
|
||||
#backend-toggle.gemini { border-color: var(--success-dim); color: var(--success); }
|
||||
#sessions-btn { margin-left: auto; }
|
||||
|
||||
/* Sessions panel */
|
||||
@@ -90,7 +175,7 @@
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
||||
box-shadow: 0 8px 24px var(--shadow);
|
||||
}
|
||||
|
||||
#sessions-panel.open { display: block; }
|
||||
@@ -185,13 +270,13 @@
|
||||
.message.assistant code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.88em;
|
||||
background: rgba(0,0,0,0.3);
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.35em;
|
||||
}
|
||||
.message.assistant pre {
|
||||
background: rgba(0,0,0,0.35);
|
||||
background: var(--pre-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
@@ -243,7 +328,7 @@
|
||||
|
||||
.message.assistant:hover .copy-btn { opacity: 1; }
|
||||
.copy-btn:hover { color: var(--text); border-color: var(--muted); }
|
||||
.copy-btn.copied { color: #6abf6a; border-color: #2a4a2a; }
|
||||
.copy-btn.copied { color: var(--success); border-color: var(--success-dim); }
|
||||
|
||||
/* Note messages */
|
||||
.message.note-private {
|
||||
@@ -528,7 +613,7 @@
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.7);
|
||||
background: var(--modal-overlay);
|
||||
z-index: 200;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -637,21 +722,41 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* ── Context bar ─────────────────────────────────────────── */
|
||||
#context-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 20px;
|
||||
/* ── Context panel ───────────────────────────────────────── */
|
||||
#ctx-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 20px;
|
||||
width: 280px;
|
||||
background: var(--surface);
|
||||
border-top: 1px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 8px 24px var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
#ctx-panel.open { display: block; }
|
||||
|
||||
.ctx-section {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.ctx-section:last-child { border-bottom: none; }
|
||||
|
||||
.ctx-section-title {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ctx-label {
|
||||
font-size: 0.63rem;
|
||||
color: var(--muted);
|
||||
flex-shrink: 0;
|
||||
.ctx-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ctx-btn {
|
||||
@@ -659,27 +764,36 @@
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--muted);
|
||||
font-size: 0.63rem;
|
||||
padding: 2px 7px;
|
||||
font-size: 0.73rem;
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.ctx-btn:hover { color: var(--text); border-color: var(--muted); }
|
||||
.ctx-btn.active { color: var(--accent); border-color: var(--accent); }
|
||||
.ctx-btn.mem-on { color: #6abf6a; border-color: #2a4a2a; }
|
||||
|
||||
.ctx-sep { flex: 1; min-width: 8px; }
|
||||
.ctx-btn.mem-on { color: var(--success); border-color: var(--success-dim); }
|
||||
|
||||
#ctx-distill-status {
|
||||
font-size: 0.62rem;
|
||||
color: #6abf6a;
|
||||
margin-top: 6px;
|
||||
font-size: 0.68rem;
|
||||
color: var(--success);
|
||||
min-height: 1em;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#ctx-distill-status.show { opacity: 1; }
|
||||
#ctx-distill-status.err { color: var(--error-text); }
|
||||
|
||||
/* Theme toggle */
|
||||
#theme-btn { font-size: 0.85rem; padding: 5px 8px; }
|
||||
|
||||
/* Ctx header button — shows current tier as a dim superscript */
|
||||
#ctx-open-btn .tier-badge {
|
||||
font-size: 0.6em;
|
||||
opacity: 0.7;
|
||||
margin-left: 2px;
|
||||
vertical-align: super;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -691,9 +805,42 @@
|
||||
</div>
|
||||
<button id="sessions-btn" class="hdr-btn">Sessions</button>
|
||||
<button id="files-btn" class="hdr-btn">Files</button>
|
||||
<button id="ctx-open-btn" class="hdr-btn" title="Context & memory settings">⚙<span class="tier-badge">2</span></button>
|
||||
<button id="backend-toggle" class="hdr-btn" title="Click to switch primary backend">claude</button>
|
||||
<button id="theme-btn" class="hdr-btn" title="Toggle light/dark mode">☾</button>
|
||||
|
||||
<div id="sessions-panel"></div>
|
||||
|
||||
<!-- Context / memory panel -->
|
||||
<div id="ctx-panel">
|
||||
<div class="ctx-section">
|
||||
<div class="ctx-section-title">Context Tier</div>
|
||||
<div class="ctx-row">
|
||||
<button class="ctx-btn" data-tier="1" id="tier-1" title="Minimal (~1.5k tokens)">T1</button>
|
||||
<button class="ctx-btn active" data-tier="2" id="tier-2" title="Standard (~5k tokens)">T2</button>
|
||||
<button class="ctx-btn" data-tier="3" id="tier-3" title="Extended (~15k tokens)">T3</button>
|
||||
<button class="ctx-btn" data-tier="4" id="tier-4" title="Full (~50k tokens)">T4</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ctx-section">
|
||||
<div class="ctx-section-title">Memory Layers</div>
|
||||
<div class="ctx-row">
|
||||
<button class="ctx-btn mem-on" id="mem-long-btn" title="Long-term (MEMORY_LONG.md)">Long</button>
|
||||
<button class="ctx-btn mem-on" id="mem-mid-btn" title="Mid-term (MEMORY_MID.md)">Mid</button>
|
||||
<button class="ctx-btn mem-on" id="mem-short-btn" title="Short-term (MEMORY_SHORT.md)">Short</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ctx-section">
|
||||
<div class="ctx-section-title">Distill Memory</div>
|
||||
<div class="ctx-row">
|
||||
<button class="ctx-btn" id="distill-short-btn" title="Roll session logs → MEMORY_SHORT (no LLM)">short</button>
|
||||
<button class="ctx-btn" id="distill-mid-btn" title="Summarize short → MEMORY_MID (LLM)">mid</button>
|
||||
<button class="ctx-btn" id="distill-long-btn" title="Integrate mid → MEMORY_LONG (LLM)">long</button>
|
||||
<button class="ctx-btn" id="distill-all-btn" title="Run all three steps in sequence">all</button>
|
||||
</div>
|
||||
<div id="ctx-distill-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- File editor modal -->
|
||||
@@ -718,27 +865,6 @@
|
||||
<div id="messages"></div>
|
||||
<div id="session-id"></div>
|
||||
|
||||
<!-- Context / memory controls -->
|
||||
<div id="context-bar">
|
||||
<span class="ctx-label">Tier:</span>
|
||||
<button class="ctx-btn" data-tier="1" id="tier-1">1</button>
|
||||
<button class="ctx-btn active" data-tier="2" id="tier-2">2</button>
|
||||
<button class="ctx-btn" data-tier="3" id="tier-3">3</button>
|
||||
<button class="ctx-btn" data-tier="4" id="tier-4">4</button>
|
||||
<span class="ctx-sep"></span>
|
||||
<span class="ctx-label">Mem:</span>
|
||||
<button class="ctx-btn mem-on" id="mem-long-btn" title="Long-term memory (MEMORY_LONG.md)">L</button>
|
||||
<button class="ctx-btn mem-on" id="mem-mid-btn" title="Mid-term memory (MEMORY_MID.md)">M</button>
|
||||
<button class="ctx-btn mem-on" id="mem-short-btn" title="Short-term memory (MEMORY_SHORT.md)">S</button>
|
||||
<span class="ctx-sep"></span>
|
||||
<span class="ctx-label">Distill:</span>
|
||||
<button class="ctx-btn" id="distill-short-btn" title="Roll session logs → MEMORY_SHORT">short</button>
|
||||
<button class="ctx-btn" id="distill-mid-btn" title="Summarize short → MEMORY_MID (LLM)">mid</button>
|
||||
<button class="ctx-btn" id="distill-long-btn" title="Integrate mid → MEMORY_LONG (LLM)">long</button>
|
||||
<button class="ctx-btn" id="distill-all-btn" title="Run all three distillation steps">all</button>
|
||||
<span id="ctx-distill-status"></span>
|
||||
</div>
|
||||
|
||||
<div id="input-area">
|
||||
<textarea id="input" rows="1" placeholder="Message Inara… (Ctrl+Enter to send)" autofocus></textarea>
|
||||
<div id="right-col">
|
||||
@@ -1505,29 +1631,62 @@
|
||||
}
|
||||
};
|
||||
|
||||
// ── Context bar — tier + memory toggles + distill ────────────
|
||||
let currentTier = parseInt(localStorage.getItem('ctx-tier') || '2');
|
||||
let memLong = localStorage.getItem('mem-long') !== 'false';
|
||||
let memMid = localStorage.getItem('mem-mid') !== 'false';
|
||||
let memShort = localStorage.getItem('mem-short') !== 'false';
|
||||
// ── Theme toggle ──────────────────────────────────────────────
|
||||
const themeBtn = document.getElementById('theme-btn');
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
themeBtn.textContent = theme === 'dark' ? '☀' : '☾';
|
||||
themeBtn.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
||||
}
|
||||
|
||||
{
|
||||
const saved = localStorage.getItem('theme');
|
||||
const sysDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
applyTheme(saved || (sysDark ? 'dark' : 'light'));
|
||||
}
|
||||
|
||||
themeBtn.addEventListener('click', () => {
|
||||
const current = document.documentElement.getAttribute('data-theme');
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem('theme', next);
|
||||
applyTheme(next);
|
||||
});
|
||||
|
||||
// ── Context panel — tier + memory toggles + distill ───────────
|
||||
const ctxOpenBtn = document.getElementById('ctx-open-btn');
|
||||
const ctxPanel = document.getElementById('ctx-panel');
|
||||
const distillStatus = document.getElementById('ctx-distill-status');
|
||||
|
||||
let currentTier = parseInt(localStorage.getItem('ctx-tier') || '2');
|
||||
let memLong = localStorage.getItem('mem-long') !== 'false';
|
||||
let memMid = localStorage.getItem('mem-mid') !== 'false';
|
||||
let memShort = localStorage.getItem('mem-short') !== 'false';
|
||||
|
||||
function updateTierUI() {
|
||||
document.querySelectorAll('.ctx-btn[data-tier]').forEach(btn => {
|
||||
btn.classList.toggle('active', parseInt(btn.dataset.tier) === currentTier);
|
||||
});
|
||||
ctxOpenBtn.querySelector('.tier-badge').textContent = currentTier;
|
||||
}
|
||||
|
||||
function updateMemUI() {
|
||||
document.getElementById('mem-long-btn').classList.toggle('mem-on', memLong);
|
||||
document.getElementById('mem-mid-btn').classList.toggle('mem-on', memMid);
|
||||
document.getElementById('mem-short-btn').classList.toggle('mem-on', memShort);
|
||||
document.getElementById('mem-long-btn').classList.toggle('active', false);
|
||||
document.getElementById('mem-mid-btn').classList.toggle('active', false);
|
||||
document.getElementById('mem-short-btn').classList.toggle('active', false);
|
||||
}
|
||||
|
||||
ctxOpenBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
ctxPanel.classList.toggle('open');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!ctxPanel.contains(e.target) && e.target !== ctxOpenBtn) {
|
||||
ctxPanel.classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.ctx-btn[data-tier]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
currentTier = parseInt(btn.dataset.tier);
|
||||
@@ -1537,26 +1696,20 @@
|
||||
});
|
||||
|
||||
document.getElementById('mem-long-btn').addEventListener('click', () => {
|
||||
memLong = !memLong;
|
||||
localStorage.setItem('mem-long', memLong);
|
||||
updateMemUI();
|
||||
memLong = !memLong; localStorage.setItem('mem-long', memLong); updateMemUI();
|
||||
});
|
||||
document.getElementById('mem-mid-btn').addEventListener('click', () => {
|
||||
memMid = !memMid;
|
||||
localStorage.setItem('mem-mid', memMid);
|
||||
updateMemUI();
|
||||
memMid = !memMid; localStorage.setItem('mem-mid', memMid); updateMemUI();
|
||||
});
|
||||
document.getElementById('mem-short-btn').addEventListener('click', () => {
|
||||
memShort = !memShort;
|
||||
localStorage.setItem('mem-short', memShort);
|
||||
updateMemUI();
|
||||
memShort = !memShort; localStorage.setItem('mem-short', memShort); updateMemUI();
|
||||
});
|
||||
|
||||
function showDistillStatus(msg, isErr) {
|
||||
distillStatus.textContent = msg;
|
||||
distillStatus.classList.toggle('err', !!isErr);
|
||||
distillStatus.classList.add('show');
|
||||
setTimeout(() => distillStatus.classList.remove('show'), 4000);
|
||||
setTimeout(() => distillStatus.classList.remove('show'), 5000);
|
||||
}
|
||||
|
||||
async function runDistill(endpoint) {
|
||||
|
||||
Reference in New Issue
Block a user