feat: Lucide SVG icons throughout main UI
Replace all emoji/unicode icons with Lucide SVG icons: - Mode select dropdown: message-circle / pencil / lock / bot - Send button: arrow-up (chat/OTR), pencil (note), zap (agent) - Stop button: square icon - Header nav already had Lucide SVGs; render_icons() now called at init Add icon_html() + render_icons() helpers; update update_mode_ui() and open_mode_dropdown() to use innerHTML + lucide.createIcons(). CSS: .btn-icon alignment, inline-flex on .hdr-btn / .hdr-dd-item / #send / #stop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,12 @@
|
|||||||
const settings_btn_el = document.getElementById('settings-btn');
|
const settings_btn_el = document.getElementById('settings-btn');
|
||||||
const settings_dd_el = document.getElementById('settings-dropdown');
|
const settings_dd_el = document.getElementById('settings-dropdown');
|
||||||
|
|
||||||
|
// ── Lucide icon helpers ───────────────────────────────────────
|
||||||
|
function icon_html(name, size = 16) {
|
||||||
|
return `<svg data-lucide="${name}" width="${size}" height="${size}" class="btn-icon"></svg>`;
|
||||||
|
}
|
||||||
|
function render_icons() { if (window.lucide) lucide.createIcons(); }
|
||||||
|
|
||||||
// User/persona injected by the server at /{user}/{persona}
|
// User/persona injected by the server at /{user}/{persona}
|
||||||
const CORTEX_USER = (window.CORTEX_CONFIG || {}).user || 'scott';
|
const CORTEX_USER = (window.CORTEX_CONFIG || {}).user || 'scott';
|
||||||
const CORTEX_PERSONA = (window.CORTEX_CONFIG || {}).persona || 'inara';
|
const CORTEX_PERSONA = (window.CORTEX_CONFIG || {}).persona || 'inara';
|
||||||
@@ -69,12 +75,17 @@
|
|||||||
|
|
||||||
// ── Input mode — dropdown select with MRU ordering ──────────
|
// ── Input mode — dropdown select with MRU ordering ──────────
|
||||||
const MODES = {
|
const MODES = {
|
||||||
chat: { icon: '💬', label: 'Chat' },
|
chat: { icon: 'message-circle', label: 'Chat' },
|
||||||
note: { icon: '📝', label: 'Note' },
|
note: { icon: 'pencil', label: 'Note' },
|
||||||
otr: { icon: '🔒', label: 'OTR' },
|
otr: { icon: 'lock', label: 'OTR' },
|
||||||
agent: { icon: '🥸', label: 'Agent' },
|
agent: { icon: 'bot', label: 'Agent' },
|
||||||
|
};
|
||||||
|
const send_defs = {
|
||||||
|
chat: { icon: 'arrow-up', label: 'Send' },
|
||||||
|
note: { icon: 'pencil', label: 'Note' },
|
||||||
|
otr: { icon: 'arrow-up', label: 'Send' },
|
||||||
|
agent: { icon: 'zap', label: 'Run' },
|
||||||
};
|
};
|
||||||
const send_labels = { chat: '↑ Send', note: '📝 Note', otr: '↑ Send', agent: '⚡ Run' };
|
|
||||||
|
|
||||||
let current_mode = localStorage.getItem('current_mode') || 'chat';
|
let current_mode = localStorage.getItem('current_mode') || 'chat';
|
||||||
let note_public = false;
|
let note_public = false;
|
||||||
@@ -105,12 +116,13 @@
|
|||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = 'mode-option' + (mode === current_mode ? ' current' : '');
|
btn.className = 'mode-option' + (mode === current_mode ? ' current' : '');
|
||||||
btn.innerHTML =
|
btn.innerHTML =
|
||||||
`<span class="opt-icon">${m.icon}</span>${m.label}`
|
`<span class="opt-icon">${icon_html(m.icon, 15)}</span>${m.label}`
|
||||||
+ (mode === current_mode ? '<span class="opt-check">✓</span>' : '');
|
+ (mode === current_mode ? '<span class="opt-check">✓</span>' : '');
|
||||||
btn.addEventListener('click', () => set_mode(mode));
|
btn.addEventListener('click', () => set_mode(mode));
|
||||||
mode_dropdown_el.appendChild(btn);
|
mode_dropdown_el.appendChild(btn);
|
||||||
});
|
});
|
||||||
mode_dropdown_el.classList.add('open');
|
mode_dropdown_el.classList.add('open');
|
||||||
|
render_icons();
|
||||||
}
|
}
|
||||||
|
|
||||||
function close_mode_dropdown() {
|
function close_mode_dropdown() {
|
||||||
@@ -130,10 +142,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function update_mode_ui() {
|
function update_mode_ui() {
|
||||||
const m = MODES[current_mode];
|
const m = MODES[current_mode];
|
||||||
|
const sd = send_defs[current_mode] || send_defs.chat;
|
||||||
|
|
||||||
// Update trigger button
|
// Update trigger button
|
||||||
mode_icon_el.textContent = m.icon;
|
mode_icon_el.innerHTML = icon_html(m.icon, 15);
|
||||||
mode_label_el.textContent = m.label;
|
mode_label_el.textContent = m.label;
|
||||||
mode_select_btn_el.className = current_mode === 'chat'
|
mode_select_btn_el.className = current_mode === 'chat'
|
||||||
? '' : `mode-${current_mode}`;
|
? '' : `mode-${current_mode}`;
|
||||||
@@ -150,9 +163,10 @@
|
|||||||
inputEl.classList.toggle('mode-otr', current_mode === 'otr');
|
inputEl.classList.toggle('mode-otr', current_mode === 'otr');
|
||||||
inputEl.classList.toggle('mode-agent', current_mode === 'agent');
|
inputEl.classList.toggle('mode-agent', current_mode === 'agent');
|
||||||
|
|
||||||
// Send button label
|
// Send button label + icon
|
||||||
sendBtn.textContent = send_labels[current_mode] || 'Send';
|
sendBtn.innerHTML = icon_html(sd.icon) + ' ' + sd.label;
|
||||||
|
|
||||||
|
render_icons();
|
||||||
updateInputPlaceholder();
|
updateInputPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +730,7 @@
|
|||||||
inputEl.value = '';
|
inputEl.value = '';
|
||||||
syncHeight();
|
syncHeight();
|
||||||
sendBtn.style.display = 'none';
|
sendBtn.style.display = 'none';
|
||||||
stopBtn.style.display = 'block';
|
stopBtn.style.display = 'flex';
|
||||||
headerEmoji.classList.add('processing');
|
headerEmoji.classList.add('processing');
|
||||||
|
|
||||||
activeController = new AbortController();
|
activeController = new AbortController();
|
||||||
@@ -808,7 +822,7 @@
|
|||||||
inputEl.value = '';
|
inputEl.value = '';
|
||||||
syncHeight();
|
syncHeight();
|
||||||
sendBtn.style.display = 'none';
|
sendBtn.style.display = 'none';
|
||||||
stopBtn.style.display = 'block';
|
stopBtn.style.display = 'flex';
|
||||||
headerEmoji.classList.add('processing');
|
headerEmoji.classList.add('processing');
|
||||||
|
|
||||||
activeController = new AbortController();
|
activeController = new AbortController();
|
||||||
@@ -1286,3 +1300,9 @@
|
|||||||
checkAuthStatus();
|
checkAuthStatus();
|
||||||
// Re-check every 30 minutes
|
// Re-check every 30 minutes
|
||||||
setInterval(checkAuthStatus, 30 * 60 * 1000);
|
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).
|
||||||
|
update_mode_ui();
|
||||||
|
render_icons();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
<script src="/static/marked.min.js"></script>
|
<script src="/static/marked.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
@@ -32,20 +33,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav id="hdr-nav">
|
<nav id="hdr-nav">
|
||||||
<button id="sessions-btn" class="hdr-btn" title="Sessions">💬 <span class="btn-label">Sessions</span></button>
|
<button id="sessions-btn" class="hdr-btn" title="Sessions">
|
||||||
<button id="ctx-open-btn" class="hdr-btn" title="Context & memory">⚙<span class="tier-badge">2</span></button>
|
<svg data-lucide="history" class="btn-icon"></svg>
|
||||||
|
<span class="btn-label">Sessions</span>
|
||||||
|
</button>
|
||||||
|
<button id="ctx-open-btn" class="hdr-btn" title="Context & memory">
|
||||||
|
<svg data-lucide="sliders-horizontal" class="btn-icon"></svg><span class="tier-badge">2</span>
|
||||||
|
</button>
|
||||||
<div class="hdr-dropdown-wrap" id="settings-wrap">
|
<div class="hdr-dropdown-wrap" id="settings-wrap">
|
||||||
<button class="hdr-btn" id="settings-btn" title="Settings">≡</button>
|
<button class="hdr-btn" id="settings-btn" title="Settings">
|
||||||
|
<svg data-lucide="menu" class="btn-icon"></svg>
|
||||||
|
</button>
|
||||||
<div class="hdr-dropdown" id="settings-dropdown">
|
<div class="hdr-dropdown" id="settings-dropdown">
|
||||||
<button id="files-btn" class="hdr-dd-item">📁 Files</button>
|
<button id="files-btn" class="hdr-dd-item">
|
||||||
<a href="/settings" class="hdr-dd-item">👤 Account</a>
|
<svg data-lucide="folder-open" class="btn-icon"></svg> Files
|
||||||
|
</button>
|
||||||
|
<a href="/settings" class="hdr-dd-item">
|
||||||
|
<svg data-lucide="user" class="btn-icon"></svg> Account
|
||||||
|
</a>
|
||||||
<div class="hdr-dd-divider"></div>
|
<div class="hdr-dd-divider"></div>
|
||||||
<form method="POST" action="/logout" style="margin:0">
|
<form method="POST" action="/logout" style="margin:0">
|
||||||
<button type="submit" class="hdr-dd-item">⏏ Sign Out</button>
|
<button type="submit" class="hdr-dd-item">
|
||||||
|
<svg data-lucide="log-out" class="btn-icon"></svg> Sign Out
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/help" class="hdr-btn" title="Help & reference" style="text-decoration:none">❓</a>
|
<a href="/help" class="hdr-btn" title="Help & reference" style="text-decoration:none">
|
||||||
|
<svg data-lucide="circle-help" class="btn-icon"></svg>
|
||||||
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="sessions-panel"></div>
|
<div id="sessions-panel"></div>
|
||||||
@@ -149,7 +165,7 @@
|
|||||||
<textarea id="input" rows="1" placeholder="Message…" autofocus></textarea>
|
<textarea id="input" rows="1" placeholder="Message…" autofocus></textarea>
|
||||||
<div id="send-col">
|
<div id="send-col">
|
||||||
<button id="send">Send</button>
|
<button id="send">Send</button>
|
||||||
<button id="stop">Stop</button>
|
<button id="stop"><svg data-lucide="square" width="14" height="14" class="btn-icon"></svg> Stop</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,13 @@
|
|||||||
|
|
||||||
.persona-dropdown .pd-add:hover { color: var(--text); }
|
.persona-dropdown .pd-add:hover { color: var(--text); }
|
||||||
|
|
||||||
|
/* Lucide SVG icon alignment */
|
||||||
|
.btn-icon { display: inline-block; vertical-align: middle; flex-shrink: 0; pointer-events: none; }
|
||||||
|
|
||||||
.hdr-btn {
|
.hdr-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -224,7 +230,9 @@
|
|||||||
.hdr-dropdown.open { display: block; }
|
.hdr-dropdown.open { display: block; }
|
||||||
|
|
||||||
.hdr-dd-item {
|
.hdr-dd-item {
|
||||||
display: block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0.55rem 0.85rem;
|
padding: 0.55rem 0.85rem;
|
||||||
@@ -538,7 +546,7 @@
|
|||||||
#mode-select-btn.mode-otr { border-color: rgba(120,80,160,0.6); color: #a87fd4; }
|
#mode-select-btn.mode-otr { border-color: rgba(120,80,160,0.6); color: #a87fd4; }
|
||||||
#mode-select-btn.mode-agent { border-color: rgba(80,140,200,0.6); color: #7cb9e8; }
|
#mode-select-btn.mode-agent { border-color: rgba(80,140,200,0.6); color: #7cb9e8; }
|
||||||
|
|
||||||
#mode-icon { font-size: 1rem; line-height: 1; }
|
#mode-icon { display: flex; align-items: center; }
|
||||||
.mode-arrow { font-size: 0.55rem; color: var(--muted); margin-left: 2px; opacity: 0.5; }
|
.mode-arrow { font-size: 0.55rem; color: var(--muted); margin-left: 2px; opacity: 0.5; }
|
||||||
|
|
||||||
/* Dropdown — opens upward; MRU at bottom = closest to button */
|
/* Dropdown — opens upward; MRU at bottom = closest to button */
|
||||||
@@ -573,7 +581,7 @@
|
|||||||
}
|
}
|
||||||
.mode-option:hover { background: var(--border); color: var(--text); }
|
.mode-option:hover { background: var(--border); color: var(--text); }
|
||||||
.mode-option.current { color: var(--text); font-weight: 500; }
|
.mode-option.current { color: var(--text); font-weight: 500; }
|
||||||
.mode-option .opt-icon { font-size: 1rem; line-height: 1; }
|
.mode-option .opt-icon { display: flex; align-items: center; }
|
||||||
.mode-option .opt-check { margin-left: auto; font-size: 0.7rem; opacity: 0.7; }
|
.mode-option .opt-check { margin-left: auto; font-size: 0.7rem; opacity: 0.7; }
|
||||||
|
|
||||||
/* Note visibility sub-button — shown below mode-select when note is active */
|
/* Note visibility sub-button — shown below mode-select when note is active */
|
||||||
@@ -630,6 +638,10 @@
|
|||||||
|
|
||||||
/* Send button */
|
/* Send button */
|
||||||
#send {
|
#send {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
background: var(--user-bg);
|
background: var(--user-bg);
|
||||||
border: 1px solid var(--user-border);
|
border: 1px solid var(--user-border);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -649,11 +661,14 @@
|
|||||||
/* Stop button */
|
/* Stop button */
|
||||||
#stop {
|
#stop {
|
||||||
display: none;
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
background: var(--error-bg);
|
background: var(--error-bg);
|
||||||
border: 1px solid var(--error-border);
|
border: 1px solid var(--error-border);
|
||||||
color: var(--error-text);
|
color: var(--error-text);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 10px 0;
|
padding: 10px 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user