|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Manus AI Terminal</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
|
background: #0d1117; |
|
color: #c9d1d9; |
|
height: 100vh; |
|
overflow: hidden; |
|
} |
|
|
|
.terminal-container { |
|
display: flex; |
|
flex-direction: column; |
|
height: 100vh; |
|
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%); |
|
border: 1px solid #30363d; |
|
} |
|
|
|
.terminal-header { |
|
display: flex; |
|
align-items: center; |
|
justify-content: space-between; |
|
padding: 12px 16px; |
|
background: #161b22; |
|
border-bottom: 1px solid #30363d; |
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.terminal-title { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
font-size: 14px; |
|
font-weight: 600; |
|
color: #f0f6fc; |
|
} |
|
|
|
.terminal-icon { |
|
width: 16px; |
|
height: 16px; |
|
background: #238636; |
|
border-radius: 50%; |
|
position: relative; |
|
} |
|
|
|
.terminal-icon::after { |
|
content: '>'; |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
font-size: 10px; |
|
color: white; |
|
font-weight: bold; |
|
} |
|
|
|
.terminal-controls { |
|
display: flex; |
|
gap: 8px; |
|
} |
|
|
|
.control-btn { |
|
width: 12px; |
|
height: 12px; |
|
border-radius: 50%; |
|
border: none; |
|
cursor: pointer; |
|
transition: opacity 0.2s; |
|
} |
|
|
|
.control-btn:hover { |
|
opacity: 0.8; |
|
} |
|
|
|
.close { background: #ff5f56; } |
|
.minimize { background: #ffbd2e; } |
|
.maximize { background: #27ca3f; } |
|
|
|
.terminal-body { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
overflow: hidden; |
|
} |
|
|
|
.terminal-output { |
|
flex: 1; |
|
padding: 16px; |
|
overflow-y: auto; |
|
font-size: 13px; |
|
line-height: 1.4; |
|
background: #0d1117; |
|
scrollbar-width: thin; |
|
scrollbar-color: #30363d #0d1117; |
|
} |
|
|
|
.terminal-output::-webkit-scrollbar { |
|
width: 8px; |
|
} |
|
|
|
.terminal-output::-webkit-scrollbar-track { |
|
background: #0d1117; |
|
} |
|
|
|
.terminal-output::-webkit-scrollbar-thumb { |
|
background: #30363d; |
|
border-radius: 4px; |
|
} |
|
|
|
.terminal-output::-webkit-scrollbar-thumb:hover { |
|
background: #484f58; |
|
} |
|
|
|
.terminal-line { |
|
margin-bottom: 2px; |
|
white-space: pre-wrap; |
|
word-wrap: break-word; |
|
} |
|
|
|
.command-line { |
|
color: #58a6ff; |
|
font-weight: 600; |
|
} |
|
|
|
.output-line { |
|
color: #c9d1d9; |
|
} |
|
|
|
.error-line { |
|
color: #f85149; |
|
} |
|
|
|
.success-line { |
|
color: #56d364; |
|
} |
|
|
|
.system-line { |
|
color: #ffa657; |
|
font-style: italic; |
|
} |
|
|
|
.timestamp { |
|
color: #7d8590; |
|
font-size: 11px; |
|
margin-right: 8px; |
|
} |
|
|
|
.terminal-input { |
|
display: flex; |
|
align-items: center; |
|
padding: 12px 16px; |
|
background: #161b22; |
|
border-top: 1px solid #30363d; |
|
} |
|
|
|
.prompt { |
|
color: #58a6ff; |
|
margin-right: 8px; |
|
font-weight: 600; |
|
} |
|
|
|
.input-field { |
|
flex: 1; |
|
background: transparent; |
|
border: none; |
|
color: #c9d1d9; |
|
font-family: inherit; |
|
font-size: 13px; |
|
outline: none; |
|
} |
|
|
|
.input-field::placeholder { |
|
color: #7d8590; |
|
} |
|
|
|
.status-indicator { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
margin-left: 12px; |
|
} |
|
|
|
.status-dot { |
|
width: 8px; |
|
height: 8px; |
|
border-radius: 50%; |
|
background: #7d8590; |
|
transition: background-color 0.3s; |
|
} |
|
|
|
.status-dot.connected { |
|
background: #56d364; |
|
box-shadow: 0 0 8px rgba(86, 211, 100, 0.5); |
|
} |
|
|
|
.status-dot.running { |
|
background: #ffa657; |
|
animation: pulse 1.5s infinite; |
|
} |
|
|
|
.status-dot.error { |
|
background: #f85149; |
|
} |
|
|
|
@keyframes pulse { |
|
0%, 100% { opacity: 1; } |
|
50% { opacity: 0.5; } |
|
} |
|
|
|
.typing-indicator { |
|
display: none; |
|
color: #7d8590; |
|
font-style: italic; |
|
animation: blink 1s infinite; |
|
} |
|
|
|
@keyframes blink { |
|
0%, 50% { opacity: 1; } |
|
51%, 100% { opacity: 0; } |
|
} |
|
|
|
.command-history { |
|
position: absolute; |
|
bottom: 60px; |
|
left: 16px; |
|
right: 16px; |
|
background: #21262d; |
|
border: 1px solid #30363d; |
|
border-radius: 6px; |
|
max-height: 200px; |
|
overflow-y: auto; |
|
display: none; |
|
z-index: 1000; |
|
} |
|
|
|
.history-item { |
|
padding: 8px 12px; |
|
cursor: pointer; |
|
border-bottom: 1px solid #30363d; |
|
transition: background-color 0.2s; |
|
} |
|
|
|
.history-item:hover { |
|
background: #30363d; |
|
} |
|
|
|
.history-item:last-child { |
|
border-bottom: none; |
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
.terminal-header { |
|
padding: 8px 12px; |
|
} |
|
|
|
.terminal-output { |
|
padding: 12px; |
|
font-size: 12px; |
|
} |
|
|
|
.terminal-input { |
|
padding: 8px 12px; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="terminal-container"> |
|
<div class="terminal-header"> |
|
<div class="terminal-title"> |
|
<div class="terminal-icon"></div> |
|
<span>Manus AI Terminal</span> |
|
</div> |
|
<div class="terminal-controls"> |
|
<button class="control-btn close" onclick="closeTerminal()"></button> |
|
<button class="control-btn minimize" onclick="minimizeTerminal()"></button> |
|
<button class="control-btn maximize" onclick="maximizeTerminal()"></button> |
|
</div> |
|
</div> |
|
|
|
<div class="terminal-body"> |
|
<div class="terminal-output" id="output"></div> |
|
<div class="command-history" id="history"></div> |
|
|
|
<div class="terminal-input"> |
|
<span class="prompt">$</span> |
|
<input type="text" class="input-field" id="commandInput" |
|
placeholder="Type a command and press Enter..." |
|
autocomplete="off" spellcheck="false"> |
|
<div class="status-indicator"> |
|
<div class="status-dot" id="statusDot"></div> |
|
<span id="statusText">Disconnected</span> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
class ManusTerminal { |
|
constructor() { |
|
this.ws = null; |
|
this.output = document.getElementById('output'); |
|
this.input = document.getElementById('commandInput'); |
|
this.statusDot = document.getElementById('statusDot'); |
|
this.statusText = document.getElementById('statusText'); |
|
this.history = document.getElementById('history'); |
|
|
|
this.commandHistory = []; |
|
this.historyIndex = -1; |
|
this.isConnected = false; |
|
this.isRunning = false; |
|
|
|
this.init(); |
|
} |
|
|
|
init() { |
|
this.setupEventListeners(); |
|
this.connect(); |
|
this.addWelcomeMessage(); |
|
} |
|
|
|
setupEventListeners() { |
|
this.input.addEventListener('keydown', (e) => this.handleKeyDown(e)); |
|
this.input.addEventListener('keyup', (e) => this.handleKeyUp(e)); |
|
|
|
|
|
window.addEventListener('focus', () => { |
|
if (!this.isConnected) { |
|
this.connect(); |
|
} |
|
}); |
|
} |
|
|
|
connect() { |
|
try { |
|
this.ws = new WebSocket('ws://localhost:8765'); |
|
|
|
this.ws.onopen = () => { |
|
this.isConnected = true; |
|
this.updateStatus('connected', 'Connected'); |
|
this.addSystemMessage('π Connected to terminal server'); |
|
}; |
|
|
|
this.ws.onmessage = (event) => { |
|
const data = JSON.parse(event.data); |
|
this.handleMessage(data); |
|
}; |
|
|
|
this.ws.onclose = () => { |
|
this.isConnected = false; |
|
this.isRunning = false; |
|
this.updateStatus('error', 'Disconnected'); |
|
this.addSystemMessage('β Connection lost. Attempting to reconnect...'); |
|
|
|
|
|
setTimeout(() => this.connect(), 3000); |
|
}; |
|
|
|
this.ws.onerror = (error) => { |
|
this.addSystemMessage('β οΈ Connection error. Check if the server is running.'); |
|
}; |
|
|
|
} catch (error) { |
|
this.addSystemMessage('β Failed to connect to terminal server'); |
|
} |
|
} |
|
|
|
handleMessage(data) { |
|
const timestamp = new Date(data.timestamp).toLocaleTimeString(); |
|
|
|
switch (data.type) { |
|
case 'connected': |
|
this.addSystemMessage(data.message); |
|
break; |
|
|
|
case 'command_start': |
|
this.isRunning = true; |
|
this.updateStatus('running', 'Running'); |
|
this.addCommandLine(data.message); |
|
break; |
|
|
|
case 'output': |
|
this.addOutputLine(data.data, data.stream); |
|
break; |
|
|
|
case 'command_complete': |
|
this.isRunning = false; |
|
this.updateStatus('connected', 'Connected'); |
|
this.addSystemMessage(`Process completed with exit code ${data.exit_code}`); |
|
break; |
|
|
|
case 'error': |
|
this.addErrorLine(data.data); |
|
break; |
|
|
|
case 'interrupted': |
|
this.isRunning = false; |
|
this.updateStatus('connected', 'Connected'); |
|
this.addSystemMessage(data.message); |
|
break; |
|
} |
|
} |
|
|
|
handleKeyDown(e) { |
|
switch (e.key) { |
|
case 'Enter': |
|
e.preventDefault(); |
|
this.executeCommand(); |
|
break; |
|
|
|
case 'ArrowUp': |
|
e.preventDefault(); |
|
this.navigateHistory(-1); |
|
break; |
|
|
|
case 'ArrowDown': |
|
e.preventDefault(); |
|
this.navigateHistory(1); |
|
break; |
|
|
|
case 'Tab': |
|
e.preventDefault(); |
|
|
|
break; |
|
|
|
case 'c': |
|
if (e.ctrlKey) { |
|
e.preventDefault(); |
|
this.interruptCommand(); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
handleKeyUp(e) { |
|
|
|
if (e.target.value.length > 0) { |
|
|
|
} |
|
} |
|
|
|
executeCommand() { |
|
const command = this.input.value.trim(); |
|
if (!command || !this.isConnected) return; |
|
|
|
|
|
if (this.commandHistory[this.commandHistory.length - 1] !== command) { |
|
this.commandHistory.push(command); |
|
} |
|
this.historyIndex = this.commandHistory.length; |
|
|
|
|
|
this.ws.send(JSON.stringify({ |
|
type: 'command', |
|
command: command |
|
})); |
|
|
|
|
|
this.input.value = ''; |
|
} |
|
|
|
interruptCommand() { |
|
if (this.isRunning && this.isConnected) { |
|
this.ws.send(JSON.stringify({ |
|
type: 'interrupt' |
|
})); |
|
} |
|
} |
|
|
|
navigateHistory(direction) { |
|
if (this.commandHistory.length === 0) return; |
|
|
|
this.historyIndex += direction; |
|
|
|
if (this.historyIndex < 0) { |
|
this.historyIndex = 0; |
|
} else if (this.historyIndex >= this.commandHistory.length) { |
|
this.historyIndex = this.commandHistory.length; |
|
this.input.value = ''; |
|
return; |
|
} |
|
|
|
this.input.value = this.commandHistory[this.historyIndex] || ''; |
|
} |
|
|
|
updateStatus(status, text) { |
|
this.statusDot.className = `status-dot ${status}`; |
|
this.statusText.textContent = text; |
|
} |
|
|
|
addWelcomeMessage() { |
|
this.addSystemMessage('π― Manus AI Terminal - Ready for commands'); |
|
this.addSystemMessage('π‘ Use Ctrl+C to interrupt running commands'); |
|
this.addSystemMessage('π Use β/β arrows to navigate command history'); |
|
} |
|
|
|
addCommandLine(text) { |
|
this.addLine(text, 'command-line'); |
|
} |
|
|
|
addOutputLine(text, stream = 'stdout') { |
|
const className = stream === 'stderr' ? 'error-line' : 'output-line'; |
|
this.addLine(text, className); |
|
} |
|
|
|
addErrorLine(text) { |
|
this.addLine(text, 'error-line'); |
|
} |
|
|
|
addSystemMessage(text) { |
|
this.addLine(text, 'system-line'); |
|
} |
|
|
|
addLine(text, className = 'output-line') { |
|
const line = document.createElement('div'); |
|
line.className = `terminal-line ${className}`; |
|
|
|
const timestamp = document.createElement('span'); |
|
timestamp.className = 'timestamp'; |
|
timestamp.textContent = new Date().toLocaleTimeString(); |
|
|
|
const content = document.createElement('span'); |
|
content.textContent = text; |
|
|
|
line.appendChild(timestamp); |
|
line.appendChild(content); |
|
|
|
this.output.appendChild(line); |
|
this.scrollToBottom(); |
|
} |
|
|
|
scrollToBottom() { |
|
this.output.scrollTop = this.output.scrollHeight; |
|
} |
|
|
|
clear() { |
|
this.output.innerHTML = ''; |
|
this.addWelcomeMessage(); |
|
} |
|
} |
|
|
|
|
|
function closeTerminal() { |
|
if (confirm('Are you sure you want to close the terminal?')) { |
|
window.close(); |
|
} |
|
} |
|
|
|
function minimizeTerminal() { |
|
|
|
console.log('Minimize terminal'); |
|
} |
|
|
|
function maximizeTerminal() { |
|
if (document.fullscreenElement) { |
|
document.exitFullscreen(); |
|
} else { |
|
document.documentElement.requestFullscreen(); |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
window.terminal = new ManusTerminal(); |
|
}); |
|
|
|
|
|
window.addEventListener('keydown', (e) => { |
|
if (e.ctrlKey && e.key === 'l') { |
|
e.preventDefault(); |
|
window.terminal.clear(); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |