feat(electrobun): add xterm.js terminal with image addon (Sixel/iTerm2)
- Terminal.svelte component with @xterm/xterm + Canvas + Fit + Image addons - Catppuccin Mocha terminal theme matching main app - Sixel, iTerm2 inline image protocol support via xterm-addon-image - ResizeObserver for responsive terminal sizing - Demo cargo test output in terminal section below agent messages
This commit is contained in:
parent
b79fbf688e
commit
f97ea95373
10 changed files with 241 additions and 23 deletions
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import Terminal from './Terminal.svelte';
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────
|
||||
type AgentStatus = 'running' | 'idle' | 'stalled';
|
||||
type MsgRole = 'user' | 'assistant' | 'tool-call' | 'tool-result';
|
||||
|
|
@ -165,16 +167,22 @@
|
|||
role="tabpanel"
|
||||
aria-label="Model"
|
||||
>
|
||||
{#each project.messages as msg (msg.id)}
|
||||
<div class="msg">
|
||||
<span class="msg-role {msg.role.split('-')[0]}">{msg.role}</span>
|
||||
<div
|
||||
class="msg-body"
|
||||
class:tool-call={msg.role === 'tool-call'}
|
||||
class:tool-result={msg.role === 'tool-result'}
|
||||
>{msg.content}</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="agent-messages">
|
||||
{#each project.messages as msg (msg.id)}
|
||||
<div class="msg">
|
||||
<span class="msg-role {msg.role.split('-')[0]}">{msg.role}</span>
|
||||
<div
|
||||
class="msg-body"
|
||||
class:tool-call={msg.role === 'tool-call'}
|
||||
class:tool-result={msg.role === 'tool-result'}
|
||||
>{msg.content}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- Terminal section -->
|
||||
<div class="terminal-section">
|
||||
<Terminal />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Docs tab placeholder -->
|
||||
|
|
|
|||
97
ui-electrobun/src/mainview/Terminal.svelte
Normal file
97
ui-electrobun/src/mainview/Terminal.svelte
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { CanvasAddon } from '@xterm/addon-canvas';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { ImageAddon } from '@xterm/addon-image';
|
||||
|
||||
// Catppuccin Mocha terminal theme
|
||||
const THEME = {
|
||||
background: '#1e1e2e',
|
||||
foreground: '#cdd6f4',
|
||||
cursor: '#f5e0dc',
|
||||
cursorAccent: '#1e1e2e',
|
||||
selectionBackground: '#585b7066',
|
||||
black: '#45475a',
|
||||
red: '#f38ba8',
|
||||
green: '#a6e3a1',
|
||||
yellow: '#f9e2af',
|
||||
blue: '#89b4fa',
|
||||
magenta: '#f5c2e7',
|
||||
cyan: '#94e2d5',
|
||||
white: '#bac2de',
|
||||
brightBlack: '#585b70',
|
||||
brightRed: '#f38ba8',
|
||||
brightGreen: '#a6e3a1',
|
||||
brightYellow: '#f9e2af',
|
||||
brightBlue: '#89b4fa',
|
||||
brightMagenta: '#f5c2e7',
|
||||
brightCyan: '#94e2d5',
|
||||
brightWhite: '#a6adc8',
|
||||
};
|
||||
|
||||
let termEl: HTMLDivElement;
|
||||
let term: Terminal;
|
||||
let fitAddon: FitAddon;
|
||||
|
||||
onMount(() => {
|
||||
term = new Terminal({
|
||||
theme: THEME,
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
fontSize: 13,
|
||||
cursorBlink: true,
|
||||
allowProposedApi: true,
|
||||
scrollback: 5000,
|
||||
});
|
||||
|
||||
fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.loadAddon(new CanvasAddon());
|
||||
|
||||
// Image addon — enables Sixel, iTerm2, and Kitty inline image protocols
|
||||
term.loadAddon(new ImageAddon({
|
||||
enableSizeReports: true,
|
||||
sixelSupport: true,
|
||||
sixelScrolling: true,
|
||||
sixelPaletteLimit: 4096,
|
||||
showPlaceholder: true,
|
||||
}));
|
||||
|
||||
term.open(termEl);
|
||||
fitAddon.fit();
|
||||
|
||||
// Demo content with ANSI colors
|
||||
term.writeln('\x1b[1;34m~/code/ai/agent-orchestrator\x1b[0m \x1b[32m$\x1b[0m cargo test --workspace');
|
||||
term.writeln(' \x1b[1;32mCompiling\x1b[0m agor-core v0.1.0');
|
||||
term.writeln(' \x1b[1;32mCompiling\x1b[0m agor-gpui v0.1.0');
|
||||
term.writeln(' \x1b[1;32mRunning\x1b[0m tests/unit.rs');
|
||||
term.writeln('test result: ok. \x1b[32m47 passed\x1b[0m; 0 failed');
|
||||
term.writeln('');
|
||||
term.writeln('\x1b[1;34m~/code/ai/agent-orchestrator\x1b[0m \x1b[32m$\x1b[0m \x1b[5m▊\x1b[0m');
|
||||
|
||||
// Resize on window resize
|
||||
const ro = new ResizeObserver(() => fitAddon.fit());
|
||||
ro.observe(termEl);
|
||||
|
||||
return () => ro.disconnect();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
term?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="terminal-container" bind:this={termEl}></div>
|
||||
|
||||
<style>
|
||||
.terminal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
||||
/* xterm.js base styles */
|
||||
:global(.xterm) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -401,3 +401,20 @@ html, body {
|
|||
color: var(--ctp-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── Terminal section ─────────────────────────────────────── */
|
||||
.agent-messages {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.terminal-section {
|
||||
height: 12rem;
|
||||
min-height: 8rem;
|
||||
border-top: 1px solid var(--ctp-surface0);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "./app.css";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import App from "./App.svelte";
|
||||
import { mount } from "svelte";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue