Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df1ffdd504 | ||
|
|
6c74c6dafa |
3 changed files with 103 additions and 16 deletions
|
|
@ -63,18 +63,28 @@ through. Three flex columns:
|
||||||
click opens the loose-ends flyout.
|
click opens the loose-ends flyout.
|
||||||
- **Overflow button** (`⋯`): always visible. Opens a frosted popover
|
- **Overflow button** (`⋯`): always visible. Opens a frosted popover
|
||||||
(`#overflow-menu`, positioned outside the header to escape any
|
(`#overflow-menu`, positioned outside the header to escape any
|
||||||
stacking context) with four rows: `↑ dashboard` (link), `↻ rebuild
|
stacking context) with four management rows followed by a model
|
||||||
container` (POST confirm, same action as the dashboard R3BU1LD
|
quick-picker section: `↑ dashboard` (link), `↻ rebuild container`
|
||||||
button), `↻ new claude session` (POST confirm → `POST
|
(POST confirm, same action as the dashboard R3BU1LD button),
|
||||||
/api/new-session`; next turn drops `--continue`), `🔓 logout`
|
`↻ new claude session` (POST confirm → `POST /api/new-session`;
|
||||||
(POST confirm → `POST /api/logout`; SIGINTs any in-flight turn,
|
next turn drops `--continue`), `🔓 logout` (POST confirm → `POST
|
||||||
wipes OAuth credential files, flips the agent to `needs_login`
|
/api/logout`; SIGINTs any in-flight turn, wipes OAuth credential
|
||||||
— session history preserved). All destructive actions require
|
files, flips the agent to `needs_login` — session history
|
||||||
one extra click to acknowledge — rare ops shouldn't live in the
|
preserved). All destructive actions require one extra click to
|
||||||
primary state strip. The popover's display rules are scoped to
|
acknowledge — rare ops shouldn't live in the primary state strip.
|
||||||
`:not([hidden])` so the `[hidden]` HTML attribute's UA `display:
|
Below a separator, a **model quick-picker** section labelled
|
||||||
none` isn't overridden by the author CSS's `display: flex` —
|
`model` renders three one-click shortcuts: `haiku (fast)`,
|
||||||
the popover stays hidden until JS removes the attribute.
|
`sonnet (balanced)`, `opus (powerful)`. Clicking a button POSTs
|
||||||
|
`/api/model` with the alias (same path as the `/model <name>`
|
||||||
|
slash command). The button for the currently-active model is
|
||||||
|
highlighted via the `active` class; `renderModelChip` keeps the
|
||||||
|
picker state in sync with live `model_changed` events so it stays
|
||||||
|
accurate when the model is changed from another session. Clicking
|
||||||
|
the already-active model closes the menu without an extra POST.
|
||||||
|
The popover's display rules are scoped to `:not([hidden])` so the
|
||||||
|
`[hidden]` HTML attribute's UA `display: none` isn't overridden by
|
||||||
|
the author CSS's `display: flex` — the popover stays hidden until
|
||||||
|
JS removes the attribute.
|
||||||
|
|
||||||
`/api/state` is fetched once on cold load (+ while
|
`/api/state` is fetched once on cold load (+ while
|
||||||
`status === 'needs_login_in_progress'`); all other updates arrive via
|
`status === 'needs_login_in_progress'`); all other updates arrive via
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,32 @@ h2, h3 {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
}
|
}
|
||||||
|
.overflow-sep {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--border);
|
||||||
|
margin: 0.3em 0.4em;
|
||||||
|
}
|
||||||
|
.overflow-section-label {
|
||||||
|
font-size: 0.72em;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 0 0.7em 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.overflow-item-model {
|
||||||
|
color: var(--muted);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.overflow-item-model:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
background: rgba(203, 166, 247, 0.08);
|
||||||
|
border-color: var(--purple-dim);
|
||||||
|
}
|
||||||
|
.overflow-item-model.active {
|
||||||
|
color: var(--purple);
|
||||||
|
border-color: var(--purple-dim);
|
||||||
|
background: rgba(203, 166, 247, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */
|
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */
|
||||||
.header-pill {
|
.header-pill {
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,39 @@ window.marked = marked;
|
||||||
});
|
});
|
||||||
menu.append(logoutBtn);
|
menu.append(logoutBtn);
|
||||||
|
|
||||||
|
// ─── model quick-picker ────────────────────────────────────────
|
||||||
|
// Three one-click shortcuts for the most common model aliases.
|
||||||
|
// The active model is highlighted via the `active` class; see
|
||||||
|
// `renderModelChip` which updates `modelPickerBtns` live.
|
||||||
|
const modelSep = el('div', { class: 'overflow-sep', 'aria-hidden': 'true' });
|
||||||
|
const modelLabel = el('div', { class: 'overflow-section-label' }, 'model');
|
||||||
|
menu.append(modelSep, modelLabel);
|
||||||
|
const MODEL_ALIASES = [
|
||||||
|
{ name: 'haiku', label: 'haiku (fast)' },
|
||||||
|
{ name: 'sonnet', label: 'sonnet (balanced)' },
|
||||||
|
{ name: 'opus', label: 'opus (powerful)' },
|
||||||
|
];
|
||||||
|
modelPickerBtns = [];
|
||||||
|
for (const m of MODEL_ALIASES) {
|
||||||
|
const btn = el('button', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'overflow-item overflow-item-model',
|
||||||
|
role: 'menuitem',
|
||||||
|
title: `/model ${m.name}`,
|
||||||
|
'data-model': m.name,
|
||||||
|
},
|
||||||
|
el('span', { class: 'overflow-item-icon', 'aria-hidden': 'true' }, '⊞'),
|
||||||
|
m.label,
|
||||||
|
);
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
if (currentModel === m.name) { closeOverflowMenu(); return; }
|
||||||
|
closeOverflowMenu();
|
||||||
|
postModel(m.name);
|
||||||
|
});
|
||||||
|
modelPickerBtns.push(btn);
|
||||||
|
menu.append(btn);
|
||||||
|
}
|
||||||
|
|
||||||
overflowMenuPopulated = true;
|
overflowMenuPopulated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,6 +464,13 @@ window.marked = marked;
|
||||||
// bus-driven `status_changed` handler so it can re-enable the
|
// bus-driven `status_changed` handler so it can re-enable the
|
||||||
// composer without waiting for the next snapshot fetch.
|
// composer without waiting for the next snapshot fetch.
|
||||||
let currentLabel = '';
|
let currentLabel = '';
|
||||||
|
// Tracked so the overflow-menu model picker can highlight the active
|
||||||
|
// model and avoid a redundant `/api/model` POST on same-model click.
|
||||||
|
let currentModel = null;
|
||||||
|
// References to the model picker buttons populated in the overflow
|
||||||
|
// menu so renderModelChip can update their `active` state without
|
||||||
|
// rebuilding the whole menu.
|
||||||
|
let modelPickerBtns = [];
|
||||||
|
|
||||||
const SLASH_COMMANDS = [
|
const SLASH_COMMANDS = [
|
||||||
{ name: '/help', desc: 'list slash commands' },
|
{ name: '/help', desc: 'list slash commands' },
|
||||||
|
|
@ -969,12 +1009,23 @@ window.marked = marked;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderModelChip(model) {
|
function renderModelChip(model) {
|
||||||
|
currentModel = model || null;
|
||||||
const el_ = $('model-chip');
|
const el_ = $('model-chip');
|
||||||
if (!el_) return;
|
if (!el_) return;
|
||||||
if (!model) { el_.hidden = true; return; }
|
if (!model) { el_.hidden = true; } else {
|
||||||
el_.hidden = false;
|
el_.hidden = false;
|
||||||
el_.textContent = 'model · ' + model;
|
el_.textContent = 'model · ' + model;
|
||||||
el_.title = `claude --model ${model}\nset via the operator's /model command; persists across turns until changed`;
|
el_.title = `claude --model ${model}\nset via the operator's /model command; persists across turns until changed`;
|
||||||
|
}
|
||||||
|
// Sync model picker buttons: highlight the active alias. A short alias
|
||||||
|
// (haiku/sonnet/opus) matches if `model` ends with that string; full
|
||||||
|
// API names like `claude-3-5-haiku-20241022` match on the suffix too.
|
||||||
|
for (const btn of modelPickerBtns) {
|
||||||
|
const alias = btn.dataset.model || '';
|
||||||
|
const isActive = !!model && (model === alias || model.endsWith(alias));
|
||||||
|
btn.classList.toggle('active', isActive);
|
||||||
|
btn.setAttribute('aria-pressed', String(isActive));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Token badges — two separate chips:
|
// Token badges — two separate chips:
|
||||||
// ctx · N last inference's prompt size = current context window
|
// ctx · N last inference's prompt size = current context window
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue