feat(electrobun): fixes + 7 new features (terminal input, file browser, memory, toasts)
Fixes: - Terminal accepts keyboard input (echo mode with prompt) - Terminal drawer collapses properly (display:none preserves xterm state) Features: - 6 project tabs: Model | Docs | Context | Files | SSH | Memory - FileBrowser.svelte: recursive tree with expand/collapse + file preview - MemoryTab.svelte: memory cards with trust badges (human/agent/auto) - Subagent tree in AgentPane (demo: search-agent, test-runner) - Drag resize handle between agent pane and terminal - Theme dropdown in Settings (4 Catppuccin flavors) - ToastContainer.svelte: auto-dismiss notifications
This commit is contained in:
parent
b11a856b72
commit
4ae558af17
14 changed files with 1168 additions and 196 deletions
|
|
@ -9,6 +9,12 @@
|
|||
content: string;
|
||||
}
|
||||
|
||||
interface SubAgent {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'running' | 'done' | 'error';
|
||||
}
|
||||
|
||||
interface Props {
|
||||
messages: AgentMessage[];
|
||||
status: 'running' | 'idle' | 'stalled';
|
||||
|
|
@ -28,20 +34,25 @@
|
|||
costUsd,
|
||||
tokens,
|
||||
model = 'claude-opus-4-5',
|
||||
provider = 'claude',
|
||||
profile,
|
||||
provider: _provider = 'claude',
|
||||
profile: _profile,
|
||||
contextPct = 0,
|
||||
burnRate = 0,
|
||||
onSend,
|
||||
}: Props = $props();
|
||||
|
||||
// Demo subagents — in production these come from agent messages
|
||||
const SUBAGENTS: SubAgent[] = [
|
||||
{ id: 'sa1', name: 'search-agent', status: 'done' },
|
||||
{ id: 'sa2', name: 'test-runner', status: 'running' },
|
||||
];
|
||||
|
||||
let scrollEl: HTMLDivElement;
|
||||
let promptText = $state('');
|
||||
|
||||
// Auto-scroll to bottom on new messages
|
||||
$effect(() => {
|
||||
// track messages length as dependency
|
||||
const _ = messages.length;
|
||||
void messages.length;
|
||||
tick().then(() => {
|
||||
if (scrollEl) scrollEl.scrollTop = scrollEl.scrollHeight;
|
||||
});
|
||||
|
|
@ -69,13 +80,41 @@
|
|||
return `$${n.toFixed(3)}`;
|
||||
}
|
||||
|
||||
// Pair tool-calls with their results for collapsible display
|
||||
function isToolMsg(msg: AgentMessage) {
|
||||
return msg.role === 'tool-call' || msg.role === 'tool-result';
|
||||
// ── Drag-resize between messages and terminal ──────────────
|
||||
// The "terminal" section is a sibling rendered by ProjectCard.
|
||||
// We expose a flex-basis on .agent-pane via CSS variable and update it on drag.
|
||||
let agentPaneEl: HTMLDivElement;
|
||||
let isDragging = $state(false);
|
||||
|
||||
function onResizeMouseDown(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
|
||||
const startY = e.clientY;
|
||||
const startH = agentPaneEl?.getBoundingClientRect().height ?? 300;
|
||||
|
||||
function onMove(ev: MouseEvent) {
|
||||
if (!agentPaneEl) return;
|
||||
const delta = ev.clientY - startY;
|
||||
const newH = Math.max(120, startH + delta);
|
||||
// Apply as explicit height on the agent-pane element
|
||||
agentPaneEl.style.flexBasis = `${newH}px`;
|
||||
agentPaneEl.style.flexGrow = '0';
|
||||
agentPaneEl.style.flexShrink = '0';
|
||||
}
|
||||
|
||||
function onUp() {
|
||||
isDragging = false;
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('mouseup', onUp);
|
||||
}
|
||||
|
||||
window.addEventListener('mousemove', onMove);
|
||||
window.addEventListener('mouseup', onUp);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="agent-pane">
|
||||
<div class="agent-pane" bind:this={agentPaneEl}>
|
||||
<!-- Messages -->
|
||||
<div class="agent-messages" bind:this={scrollEl}>
|
||||
{#each messages as msg (msg.id)}
|
||||
|
|
@ -125,6 +164,26 @@
|
|||
<span class="strip-cost">{fmtCost(costUsd)}</span>
|
||||
</div>
|
||||
|
||||
<!-- Subagents tree -->
|
||||
{#if SUBAGENTS.length > 0}
|
||||
<div class="subagents-section" aria-label="Subagents">
|
||||
<div class="subagents-label">Subagents</div>
|
||||
<ul class="subagents-list">
|
||||
{#each SUBAGENTS as sa (sa.id)}
|
||||
<li class="subagent-row">
|
||||
<span class="subagent-indent">└</span>
|
||||
<span
|
||||
class="subagent-dot dot-{sa.status}"
|
||||
aria-label={sa.status}
|
||||
></span>
|
||||
<span class="subagent-name">{sa.name}</span>
|
||||
<span class="subagent-status">{sa.status}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Prompt input -->
|
||||
<div class="agent-prompt">
|
||||
<textarea
|
||||
|
|
@ -144,6 +203,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drag-resize handle — sits between agent pane and terminal section -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="resize-handle"
|
||||
class:dragging={isDragging}
|
||||
role="separator"
|
||||
aria-label="Drag to resize agent pane"
|
||||
onmousedown={onResizeMouseDown}
|
||||
></div>
|
||||
|
||||
<style>
|
||||
.agent-pane {
|
||||
display: flex;
|
||||
|
|
@ -213,10 +282,7 @@
|
|||
}
|
||||
|
||||
/* Tool call collapsible */
|
||||
.tool-group {
|
||||
border-radius: 0.3125rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tool-group { border-radius: 0.3125rem; overflow: hidden; }
|
||||
|
||||
.tool-summary {
|
||||
display: flex;
|
||||
|
|
@ -235,30 +301,12 @@
|
|||
|
||||
.tool-summary::-webkit-details-marker { display: none; }
|
||||
.tool-summary:hover { color: var(--ctp-text); }
|
||||
.tool-icon { font-size: 0.6875rem; color: var(--ctp-peach); }
|
||||
.tool-label { font-weight: 500; font-family: var(--term-font-family); }
|
||||
|
||||
.tool-icon {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--ctp-peach);
|
||||
}
|
||||
|
||||
.tool-label {
|
||||
font-weight: 500;
|
||||
font-family: var(--term-font-family);
|
||||
}
|
||||
|
||||
.tool-group[open] .tool-summary {
|
||||
border-radius: 0.3125rem 0.3125rem 0 0;
|
||||
}
|
||||
|
||||
.tool-group .msg-body {
|
||||
border-radius: 0 0 0.3125rem 0.3125rem;
|
||||
border-left: 2px solid var(--ctp-peach);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.tool-group .msg-body.tool-result {
|
||||
border-left-color: var(--ctp-teal);
|
||||
}
|
||||
.tool-group[open] .tool-summary { border-radius: 0.3125rem 0.3125rem 0 0; }
|
||||
.tool-group .msg-body { border-radius: 0 0 0.3125rem 0.3125rem; border-left: 2px solid var(--ctp-peach); border-top: none; }
|
||||
.tool-group .msg-body.tool-result { border-left-color: var(--ctp-teal); }
|
||||
|
||||
/* Status strip */
|
||||
.agent-status-strip {
|
||||
|
|
@ -286,14 +334,7 @@
|
|||
.badge-idle { background: color-mix(in srgb, var(--ctp-overlay0) 20%, transparent); color: var(--ctp-overlay1); }
|
||||
.badge-stalled { background: color-mix(in srgb, var(--ctp-peach) 20%, transparent); color: var(--ctp-peach); }
|
||||
|
||||
.strip-model {
|
||||
color: var(--ctp-overlay1);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
.strip-model { color: var(--ctp-overlay1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 8rem; }
|
||||
.strip-spacer { flex: 1; }
|
||||
|
||||
.strip-ctx {
|
||||
|
|
@ -304,17 +345,85 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.strip-ctx.ctx-danger {
|
||||
background: color-mix(in srgb, var(--ctp-red) 15%, transparent);
|
||||
color: var(--ctp-red);
|
||||
.strip-ctx.ctx-warn { background: color-mix(in srgb, var(--ctp-peach) 15%, transparent); color: var(--ctp-peach); }
|
||||
.strip-ctx.ctx-danger { background: color-mix(in srgb, var(--ctp-red) 15%, transparent); color: var(--ctp-red); }
|
||||
.strip-burn { color: var(--ctp-peach); }
|
||||
.strip-tokens { color: var(--ctp-subtext1); }
|
||||
.strip-cost { color: var(--ctp-text); font-weight: 500; }
|
||||
|
||||
/* Subagents section */
|
||||
.subagents-section {
|
||||
border-top: 1px solid var(--ctp-surface0);
|
||||
background: var(--ctp-mantle);
|
||||
padding: 0.3rem 0.625rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.strip-burn { color: var(--ctp-peach); }
|
||||
.strip-tokens { color: var(--ctp-subtext1); }
|
||||
.subagents-label {
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--ctp-overlay0);
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.strip-cost {
|
||||
color: var(--ctp-text);
|
||||
font-weight: 500;
|
||||
.subagents-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.subagent-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--ctp-subtext1);
|
||||
}
|
||||
|
||||
.subagent-indent {
|
||||
color: var(--ctp-overlay0);
|
||||
font-family: var(--term-font-family);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.subagent-dot {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot-running { background: var(--ctp-green); }
|
||||
.dot-done { background: var(--ctp-overlay1); }
|
||||
.dot-error { background: var(--ctp-red); }
|
||||
|
||||
.subagent-name { flex: 1; font-family: var(--term-font-family); }
|
||||
|
||||
.subagent-status {
|
||||
font-size: 0.625rem;
|
||||
color: var(--ctp-overlay0);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
/* Drag-resize handle */
|
||||
.resize-handle {
|
||||
height: 4px;
|
||||
background: transparent;
|
||||
cursor: row-resize;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.12s;
|
||||
}
|
||||
|
||||
.resize-handle:hover,
|
||||
.resize-handle.dragging {
|
||||
background: var(--ctp-surface1);
|
||||
}
|
||||
|
||||
/* Prompt area */
|
||||
|
|
@ -343,10 +452,7 @@
|
|||
transition: border-color 0.12s;
|
||||
}
|
||||
|
||||
.prompt-input:focus {
|
||||
border-color: var(--accent, var(--ctp-mauve));
|
||||
}
|
||||
|
||||
.prompt-input:focus { border-color: var(--accent, var(--ctp-mauve)); }
|
||||
.prompt-input::placeholder { color: var(--ctp-overlay0); }
|
||||
|
||||
.prompt-send {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue