AIsay / static /index.html
WACATW's picture
Upload static/index.html
b667d54 verified
Raw History Blame Contribute Delete
5.67 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>终端 - 虚拟电脑</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; background: #1e1e1e; overflow: hidden; }
#terminal-container { width: 100%; height: 100%; padding: 8px; }
.xterm { height: 100%; }
</style>
</head>
<body>
<div id="terminal-container"></div>
<script>
const term = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
fontSize: 14,
fontFamily: '"Cascadia Code", "Fira Code", "JetBrains Mono", "Consolas", monospace',
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#ffffff',
selectionBackground: '#264f78',
black: '#000000',
red: '#cd3131',
green: '#0dbc79',
yellow: '#e5e510',
blue: '#2472c8',
magenta: '#bc3fbc',
cyan: '#11a8cd',
white: '#e5e5e5',
},
scrollback: 5000,
convertEol: true,
});
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal-container'));
fitAddon.fit();
window.addEventListener('resize', () => fitAddon.fit());
let ws = null;
let currentLine = '';
let commandHistory = [];
let historyIndex = -1;
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/terminal/ws/terminal`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
term.writeln('\x1b[1;32m✓ 已连接到虚拟电脑终端\x1b[0m');
term.writeln('\x1b[90m输入 Shell 命令,按 Enter 执行。\x1b[0m');
term.writeln('');
showPrompt();
};
ws.onmessage = (event) => {
const output = event.data;
term.write(output);
if (!output.endsWith('\n')) term.write('\r\n');
showPrompt();
};
ws.onclose = () => {
term.writeln('\r\n\x1b[1;31m✗ 连接已断开。请刷新页面重连。\x1b[0m');
};
ws.onerror = () => {
term.writeln('\r\n\x1b[1;31m✗ 连接错误\x1b[0m');
};
}
function showPrompt() {
term.write('\x1b[1;36m$\x1b[0m ');
}
term.onData((data) => {
if (data === '\r') {
term.write('\r\n');
if (currentLine.trim()) {
commandHistory.push(currentLine);
historyIndex = commandHistory.length;
if (currentLine.trim() === 'clear') {
term.clear();
showPrompt();
} else if (currentLine.trim() === 'exit') {
term.writeln('\x1b[90m再见!\x1b[0m');
} else {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(currentLine);
} else {
term.writeln('\x1b[1;31m✗ 未连接到后端\x1b[0m');
showPrompt();
}
}
} else {
showPrompt();
}
currentLine = '';
return;
}
if (data === '\x7f') {
if (currentLine.length > 0) {
currentLine = currentLine.slice(0, -1);
term.write('\b \b');
}
return;
}
if (data === '\x1b[A') {
if (historyIndex > 0) {
historyIndex--;
term.write('\r\x1b[K');
showPrompt();
currentLine = commandHistory[historyIndex];
term.write(currentLine);
}
return;
}
if (data === '\x1b[B') {
if (historyIndex < commandHistory.length - 1) {
historyIndex++;
term.write('\r\x1b[K');
showPrompt();
currentLine = commandHistory[historyIndex];
term.write(currentLine);
} else {
historyIndex = commandHistory.length;
term.write('\r\x1b[K');
showPrompt();
currentLine = '';
}
return;
}
if (data >= ' ' || data === '\t') {
currentLine += data;
term.write(data);
}
});
connectWebSocket();
setTimeout(() => term.focus(), 100);
</script>
</body>
</html>