Files
hermes-agent-home/hermes-agent-web/templates/guide_terminal.html
T
carlandCursor 5341a7617b Initial commit: Hermes Agent 多实例管理与 Web 控制台
基于官方 nousresearch/hermes-agent 镜像的 CLI、Docker Compose 与 Web 管理界面,支持多实例部署;敏感配置与实例数据由 .gitignore 排除。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 13:56:04 +08:00

69 lines
2.9 KiB
HTML

{% extends "base.html" %}
{% block title %}实例 {{ iid }} · Hermes 引导 · Hermes Agent Web{% endblock %}
{% block body %}
<p><a href="{{ url_for('index') }}">← 返回实例列表</a></p>
<h2 style="margin-top:0;">实例 <code>{{ iid }}</code> · 引导终端</h2>
<p class="muted">打开本页后会<strong>自动</strong>在后台执行官方引导链路(未完成 setup 时先跑向导,再启动网关,再进入 <code>onboard</code>)。
你一般<strong>不用输入命令</strong>,只需在终端里按程序提问操作;需要改配置可用「编辑配置」。</p>
<div id="term-wrap" style="height:min(75vh,620px);border:1px solid var(--border);border-radius:8px;padding:2px;background:#0c1017;"></div>
<p class="muted" style="margin-top:0.75rem;">关闭本页请先 <kbd>Ctrl+C</kbd> 结束当前命令。需联网加载 xterm(CDN)。</p>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" crossorigin="anonymous" />
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.min.js" crossorigin="anonymous"></script>
<script>
(function () {
var wrap = document.getElementById('term-wrap');
var term = new Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: 'ui-monospace, SF Mono, Menlo, Consolas, monospace',
theme: { background: '#0c1017', foreground: '#e7edf4' }
});
var FitCtor = (typeof FitAddon !== 'undefined' && FitAddon.FitAddon) ? FitAddon.FitAddon : FitAddon;
var fit = new FitCtor();
term.loadAddon(fit);
term.open(wrap);
fit.fit();
term.writeln('\x1b[90m正在连接引导终端…\x1b[0m');
var proto = (location.protocol === 'https:') ? 'wss:' : 'ws:';
var tok = {{ ws_token | tojson }};
var ws = new WebSocket(proto + '//' + location.host + '/ws/instance/{{ iid }}/pty?token=' + encodeURIComponent(tok));
ws.onopen = function () {
term.reset();
fit.fit();
sendResize();
};
ws.onmessage = function (ev) {
var d = ev.data;
if (typeof d === 'string') term.write(d);
else term.write(new Uint8Array(d));
};
ws.onerror = function () {
term.write('\r\n\x1b[31m[WebSocket 错误]\x1b[0m\r\n');
};
ws.onclose = function (ev) {
term.write('\r\n\x1b[33m[连接已关闭' + (ev && ev.code ? ' code=' + ev.code : '') + ']\x1b[0m\r\n');
};
term.onData(function (data) {
if (ws.readyState === WebSocket.OPEN) ws.send(data);
});
function sendResize() {
if (ws.readyState !== WebSocket.OPEN) return;
try {
fit.fit();
ws.send(JSON.stringify({ t: 'resize', cols: term.cols, rows: term.rows }));
} catch (e) {}
}
window.addEventListener('resize', function () {
fit.fit();
sendResize();
});
new ResizeObserver(sendResize).observe(wrap);
setTimeout(sendResize, 100);
})();
</script>
{% endblock %}