agent web UI: add terminal verbosity setting (expand tool output by default)
Adds a browser-local (localStorage only, no backend field) toggle in the per-agent overflow menu's new settings section: whether otherwise- collapsed <details> rows in the live terminal (long tool-results, Write/Edit diffs, ...) default open. Message-bearing rows that already default open (send/ask/answer/recv) are unaffected either way. The shared terminal factory (frontend/packages/shared/src/terminal/terminal.js) gains an optional expandDetails option (boolean or zero-arg function), read live on every details()/detailsDiff() call rather than captured once, so flipping the toggle mid-session applies to the next rendered row without a reload. Unused by the dashboard's own terminal pane, so its default-closed behaviour is unchanged. Closes #2961.
This commit is contained in:
parent
edb4aa98c6
commit
198db326b3
5 changed files with 94 additions and 1 deletions
|
|
@ -101,6 +101,16 @@ through. Three flex columns:
|
||||||
the `/effort <level>` slash command). The active level's button is
|
the `/effort <level>` slash command). The active level's button is
|
||||||
highlighted; `renderEffortChip` keeps the picker in sync with
|
highlighted; `renderEffortChip` keeps the picker in sync with
|
||||||
`StateSnapshot.effort` from the cold-load snapshot.
|
`StateSnapshot.effort` from the cold-load snapshot.
|
||||||
|
A third separator + **settings** section holds a single
|
||||||
|
`role="menuitemcheckbox"` toggle, `☐/☑ expand tool output` — flips
|
||||||
|
whether otherwise-collapsed `<details>` rows in the terminal (long
|
||||||
|
tool-results, Write/Edit diffs, …) default open. Pure client-side:
|
||||||
|
persisted to this browser's `localStorage` only (no backend field,
|
||||||
|
no `/api/*` call), read live by the shared terminal factory's
|
||||||
|
`expandDetails` option (see docs/web-ui/shape.md::Shared terminal
|
||||||
|
pane) so toggling mid-session applies to the next rendered row
|
||||||
|
without a reload. Rows that already default open regardless (send /
|
||||||
|
ask / answer / recv) are unaffected either way.
|
||||||
The popover's display rules are scoped to `:not([hidden])` so the
|
The popover's display rules are scoped to `:not([hidden])` so the
|
||||||
`[hidden]` HTML attribute's UA `display: none` isn't overridden by
|
`[hidden]` HTML attribute's UA `display: none` isn't overridden by
|
||||||
the author CSS's `display: flex` — the popover stays hidden until
|
the author CSS's `display: flex` — the popover stays hidden until
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,11 @@ event picture), `onBackfillDone?(count)` (one-shot after replay; `count=0` on
|
||||||
failed/skipped fetch), `onStreamOpen?()` (fires on every EventSource
|
failed/skipped fetch), `onStreamOpen?()` (fires on every EventSource
|
||||||
(re)connect — use to re-sync snapshot-derived state after a reconnect gap),
|
(re)connect — use to re-sync snapshot-derived state after a reconnect gap),
|
||||||
`pillAnchor?` (parent element for the "↓ N new" pill; defaults to
|
`pillAnchor?` (parent element for the "↓ N new" pill; defaults to
|
||||||
`logEl.parentElement`).
|
`logEl.parentElement`), `expandDetails?` (boolean or zero-arg function
|
||||||
|
returning one, re-read on every `api.details`/`api.detailsDiff` call rather
|
||||||
|
than captured once — lets a page default otherwise-collapsed panels open
|
||||||
|
per a live browser-local preference; renderers that force a row open
|
||||||
|
regardless, e.g. message-bearing tool_use, are unaffected either way).
|
||||||
|
|
||||||
**Sticky-bottom + snap animation.** `stickToBottom` is the
|
**Sticky-bottom + snap animation.** `stickToBottom` is the
|
||||||
operator's intent: true means "keep snapping to bottom on every
|
operator's intent: true means "keep snapping to bottom on every
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,19 @@ h2, h3 {
|
||||||
border-color: var(--purple-dim);
|
border-color: var(--purple-dim);
|
||||||
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
||||||
}
|
}
|
||||||
|
.overflow-item-verbosity {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.overflow-item-verbosity:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
background: color-mix(in srgb, var(--purple) 8%, transparent);
|
||||||
|
border-color: var(--purple-dim);
|
||||||
|
}
|
||||||
|
.overflow-item-verbosity.active {
|
||||||
|
color: var(--purple);
|
||||||
|
border-color: var(--purple-dim);
|
||||||
|
background: color-mix(in srgb, var(--purple) 6%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */
|
/* Header pill — inbox / loose-ends triggers. Compact, count-prominent. */
|
||||||
.header-pill {
|
.header-pill {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,22 @@ window.marked = marked;
|
||||||
({ '&':'&', '<':'<', '>':'>', '"':'"' }[c])
|
({ '&':'&', '<':'<', '>':'>', '"':'"' }[c])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// "Expand tool output by default" terminal-verbosity preference —
|
||||||
|
// pure client-side, browser-local (localStorage only, no backend
|
||||||
|
// field). See the overflow-menu settings toggle below + the
|
||||||
|
// `expandDetails` option passed to HiveTerminal.create().
|
||||||
|
const EXPAND_DETAILS_KEY = 'hive-agent-expand-details';
|
||||||
|
function getExpandDetailsPref() {
|
||||||
|
try { return localStorage.getItem(EXPAND_DETAILS_KEY) === '1'; }
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
function setExpandDetailsPref(v) {
|
||||||
|
try {
|
||||||
|
if (v) localStorage.setItem(EXPAND_DETAILS_KEY, '1');
|
||||||
|
else localStorage.removeItem(EXPAND_DETAILS_KEY);
|
||||||
|
} catch { /* localStorage unavailable — preference is session-only */ }
|
||||||
|
}
|
||||||
|
|
||||||
// Base URL of the host dashboard (core backend). Set once the first
|
// Base URL of the host dashboard (core backend). Set once the first
|
||||||
// /api/state lands. Operator-authority actions (answering a question
|
// /api/state lands. Operator-authority actions (answering a question
|
||||||
// as the operator) POST here rather than to this agent's own socket —
|
// as the operator) POST here rather than to this agent's own socket —
|
||||||
|
|
@ -251,6 +267,40 @@ window.marked = marked;
|
||||||
menu.append(btn);
|
menu.append(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── settings: expand tool output by default ───────────────────
|
||||||
|
// Pure client-side, browser-local preference (no backend involved) —
|
||||||
|
// see docs/web-ui/agent.md::Overflow button. Controls the *default*
|
||||||
|
// open state of otherwise-collapsed `<details>` rows in the live
|
||||||
|
// terminal (long tool-results, Write/Edit diffs, …); rows that
|
||||||
|
// already default open (send/ask/answer/recv) are unaffected.
|
||||||
|
// Read live by the shared terminal factory (HiveTerminal.create's
|
||||||
|
// `expandDetails` option below), so toggling mid-session applies to
|
||||||
|
// the next rendered row without a reload.
|
||||||
|
const settingsSep = el('div', { class: 'overflow-sep', 'aria-hidden': 'true' });
|
||||||
|
const settingsLabel = el('div', { class: 'overflow-section-label' }, 'settings');
|
||||||
|
menu.append(settingsSep, settingsLabel);
|
||||||
|
const expandIcon = el('span', { class: 'overflow-item-icon', 'aria-hidden': 'true' },
|
||||||
|
getExpandDetailsPref() ? '☑' : '☐');
|
||||||
|
const expandBtn = el('button', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'overflow-item overflow-item-verbosity' + (getExpandDetailsPref() ? ' active' : ''),
|
||||||
|
role: 'menuitemcheckbox',
|
||||||
|
'aria-checked': String(getExpandDetailsPref()),
|
||||||
|
title: 'expand tool output panels by default in this terminal (persisted to this browser only)',
|
||||||
|
id: 'expand-details-btn',
|
||||||
|
},
|
||||||
|
expandIcon,
|
||||||
|
'expand tool output',
|
||||||
|
);
|
||||||
|
expandBtn.addEventListener('click', () => {
|
||||||
|
const next = !getExpandDetailsPref();
|
||||||
|
setExpandDetailsPref(next);
|
||||||
|
expandBtn.classList.toggle('active', next);
|
||||||
|
expandBtn.setAttribute('aria-checked', String(next));
|
||||||
|
expandIcon.textContent = next ? '☑' : '☐';
|
||||||
|
});
|
||||||
|
menu.append(expandBtn);
|
||||||
|
|
||||||
// ─── effort quick-picker ───────────────────────────────────────
|
// ─── effort quick-picker ───────────────────────────────────────
|
||||||
// One-click shortcuts for each reasoning-effort level the backend
|
// One-click shortcuts for each reasoning-effort level the backend
|
||||||
// declares in `availableEfforts` (from `state.available_efforts`).
|
// declares in `availableEfforts` (from `state.available_efforts`).
|
||||||
|
|
@ -1613,6 +1663,9 @@ window.marked = marked;
|
||||||
// (e.g. /agent/<name>/) still hits the right SSE upstream.
|
// (e.g. /agent/<name>/) still hits the right SSE upstream.
|
||||||
historyUrl: 'events/history',
|
historyUrl: 'events/history',
|
||||||
streamUrl: 'events/stream',
|
streamUrl: 'events/stream',
|
||||||
|
// Re-read live (not captured once) so flipping the overflow-menu
|
||||||
|
// toggle mid-session affects the next rendered row immediately.
|
||||||
|
expandDetails: () => getExpandDetailsPref(),
|
||||||
renderers: {
|
renderers: {
|
||||||
turn_start(ev, api) {
|
turn_start(ev, api) {
|
||||||
if (api.fromHistory) openTurnsFromHistory += 1;
|
if (api.fromHistory) openTurnsFromHistory += 1;
|
||||||
|
|
|
||||||
|
|
@ -231,11 +231,23 @@ export function create(opts) {
|
||||||
afterAppend(wasNearBottom);
|
afterAppend(wasNearBottom);
|
||||||
return [e, tn];
|
return [e, tn];
|
||||||
}
|
}
|
||||||
|
// `opts.expandDetails` (boolean or zero-arg function returning one) lets
|
||||||
|
// the caller default every otherwise-collapsed `<details>` row open —
|
||||||
|
// e.g. an "expand panels by default" browser-local preference. Read
|
||||||
|
// live (not captured once) so a mid-session preference flip takes
|
||||||
|
// effect on the next row without recreating the terminal. Renderers
|
||||||
|
// that need a row open regardless (message-bearing tool_use/tool_result)
|
||||||
|
// already set `d.open = true` themselves after calling this — this only
|
||||||
|
// changes the *default* for panels that would otherwise start closed.
|
||||||
|
function wantsExpandedDefault() {
|
||||||
|
return typeof opts.expandDetails === 'function' ? !!opts.expandDetails() : !!opts.expandDetails;
|
||||||
|
}
|
||||||
function details(cls, summary, body, icon) {
|
function details(cls, summary, body, icon) {
|
||||||
clearPlaceholder();
|
clearPlaceholder();
|
||||||
const wasNearBottom = isNearBottom();
|
const wasNearBottom = isNearBottom();
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
|
if (wantsExpandedDefault()) d.open = true;
|
||||||
d.appendChild(buildSummary(summary, icon));
|
d.appendChild(buildSummary(summary, icon));
|
||||||
const pre = document.createElement('pre');
|
const pre = document.createElement('pre');
|
||||||
pre.className = 'tool-body';
|
pre.className = 'tool-body';
|
||||||
|
|
@ -250,6 +262,7 @@ export function create(opts) {
|
||||||
const wasNearBottom = isNearBottom();
|
const wasNearBottom = isNearBottom();
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : '');
|
||||||
|
if (wantsExpandedDefault()) d.open = true;
|
||||||
d.appendChild(buildSummary(summary, icon));
|
d.appendChild(buildSummary(summary, icon));
|
||||||
const pre = document.createElement('pre');
|
const pre = document.createElement('pre');
|
||||||
pre.className = 'tool-body diff-body';
|
pre.className = 'tool-body diff-body';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue