feat(shell): real terminal with xterm.js + PTY over WebSocket Replace fake shell input with a full PTY-backed terminal using xterm.js. Apps like btop, vim, htop now work. AI chat panel is always visible. Backend: - Add WebSocket handler /api/ws/terminal with creack/pty - Allocate real pseudo-terminal with TERM=xterm-256color - Bidirectional I/O + dynamic resize via pty.Setsize - Skip JSON headers on /api/ws/* paths for WebSocket upgrade Frontend: - Integrate xterm.js with FitAddon and WebLinksAddon - Cyberpunk color theme matching app design - ResizeObserver for automatic terminal resizing - AI assistant panel always visible (340px, no toggle) - Connection status indicator (green/red dot) Dependencies: - Go: github.com/gorilla/websocket, github.com/creack/pty/v2 - npm: @xterm/xterm, @xterm/addon-fit, @xterm/addon-web-links 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
@@ -267,16 +267,14 @@ input::placeholder { color: var(--text-disabled); }
|
||||
.sidebar-tab:hover { background: var(--bg-card); color: var(--text-primary); }
|
||||
.sidebar-tab.active { background: var(--accent); color: #fff; font-weight: 600; }
|
||||
|
||||
.terminal { display: flex; flex-direction: column; height: 100%; background: var(--bg); }
|
||||
.terminal-output { flex: 1; padding: 16px; font-family: var(--font-mono); font-size: 13px; line-height: 1.6; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
|
||||
.terminal-line { margin-bottom: 2px; }
|
||||
.terminal-line.cmd { color: var(--accent-dim); }
|
||||
.terminal-line.out { color: var(--text-primary); }
|
||||
.terminal-line.err { color: var(--error); }
|
||||
.terminal-input-bar { display: flex; align-items: center; padding: 10px 16px; background: var(--bg-surface); border-top: 1px solid var(--border); gap: 8px; }
|
||||
.terminal-prompt { color: var(--success); font-family: var(--font-mono); font-weight: 700; font-size: 14px; flex-shrink: 0; }
|
||||
.terminal-input { flex: 1; background: transparent; border: none; outline: none; color: var(--text-primary); font-family: var(--font-mono); font-size: 13px; padding: 0; }
|
||||
.terminal-input:focus { box-shadow: none; border-color: transparent; }
|
||||
.shell-layout { display: flex; height: 100%; }
|
||||
.shell-terminal-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
||||
.shell-xterm-wrapper { flex: 1; padding: 8px; background: var(--bg); overflow: hidden; }
|
||||
.shell-xterm-wrapper .xterm { height: 100%; padding: 4px; }
|
||||
.shell-ai-col { width: 340px; border-left: 1px solid var(--border); background: var(--bg-surface); display: flex; flex-direction: column; flex-shrink: 0; }
|
||||
.connection-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-left: 8px; }
|
||||
.connection-dot.on { background: var(--success); box-shadow: 0 0 6px var(--success); }
|
||||
.connection-dot.off { background: var(--error); }
|
||||
|
||||
.config-layout { max-width: 840px; margin: 0 auto; padding: 24px; overflow-y: auto; height: 100%; }
|
||||
.config-section { margin-bottom: 28px; }
|
||||
@@ -323,7 +321,6 @@ input::placeholder { color: var(--text-disabled); }
|
||||
.agent-name { font-weight: 600; color: var(--text-primary); font-size: 13px; }
|
||||
.agent-status { font-size: 11px; color: var(--text-tertiary); margin-top: 2px; }
|
||||
|
||||
.ai-panel { width: 320px; border-left: 1px solid var(--border); background: var(--bg-surface); display: flex; flex-direction: column; }
|
||||
.ai-panel-header { padding: 12px 16px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 13px; color: var(--accent); }
|
||||
.ai-panel-messages { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.ai-message { padding: 8px 12px; border-radius: var(--radius); font-size: 13px; line-height: 1.5; word-break: break-word; }
|
||||
|
||||
Reference in New Issue
Block a user