feat(setup): add local/custom install mode for Hermes Agent

安装步骤新增"本地"和"自定义"两种模式切换:
- 本地模式:原有一键安装流程(uv tool install)
- 自定义模式:输入已有 Hermes Gateway URL,测试连接后保存并跳到配置步骤
  适用于已在其他机器安装或手动安装的用户
- URL 格式校验 + 连接失败错误提示
- 新增 10 个 i18n 键
This commit is contained in:
晴天
2026-04-13 11:01:47 +08:00
parent 5df0cd36ae
commit c02cb8e659
2 changed files with 100 additions and 1 deletions

View File

@@ -34,6 +34,8 @@ export function render() {
let logs = []
let installing = false
let installError = null
let installMode = 'local' // 'local' | 'custom'
let customGatewayUrl = 'http://127.0.0.1:8642'
let progress = 0
let unlisten = null
@@ -130,6 +132,46 @@ export function render() {
// --- 安装阶段 ---
function renderInstall() {
// 模式切换按钮
const modeSwitch = `
<div style="display:flex;gap:8px;margin-bottom:20px">
<button class="btn btn-sm hermes-mode-btn ${installMode === 'local' ? 'btn-primary' : 'btn-secondary'}" data-mode="local">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14" style="vertical-align:-2px"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
${t('engine.installModeLocal')}
</button>
<button class="btn btn-sm hermes-mode-btn ${installMode === 'custom' ? 'btn-primary' : 'btn-secondary'}" data-mode="custom">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14" style="vertical-align:-2px"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
${t('engine.installModeCustom')}
</button>
</div>`
if (installMode === 'custom') {
// 自定义模式:输入已有 Gateway 地址
return `<div class="card" style="margin-bottom:16px">
<div class="card-body" style="padding:24px">
<h3 style="margin:0 0 4px;font-size:16px">${t('engine.installTitle')}</h3>
<p style="color:var(--text-secondary);margin:0 0 16px;font-size:13px">${t('engine.installCustomDesc')}</p>
${modeSwitch}
${installError ? `
<div style="margin-bottom:14px;padding:10px 14px;background:var(--error-bg, #fef2f2);border:1px solid var(--error, #ef4444);border-radius:var(--radius-sm,6px);font-size:13px;color:var(--error, #ef4444)">
${esc(installError)}
</div>
` : ''}
<div class="hermes-form">
<label class="hermes-field">
<span>Gateway URL</span>
<input type="text" id="hm-custom-url" class="input" placeholder="http://127.0.0.1:8642" value="${esc(customGatewayUrl)}">
<div style="font-size:11px;color:var(--text-tertiary);margin-top:4px">${t('engine.installCustomHint')}</div>
</label>
</div>
<div style="display:flex;gap:10px;align-items:center;margin-top:16px">
<button class="btn btn-primary hermes-custom-connect" ${installing ? 'disabled' : ''}>${installing ? ICONS.spinner + ' ' + t('engine.installCustomTesting') : t('engine.installCustomConnect')}</button>
</div>
</div>
</div>`
}
// 本地模式:一键安装
const btnText = installing ? `${ICONS.spinner} ${t('engine.installingBtn')}` : `${ICONS.rocket} ${t('engine.installBtn')}`
const btnDisabled = installing ? 'disabled' : ''
@@ -174,6 +216,7 @@ export function render() {
<div class="card-body" style="padding:24px">
<h3 style="margin:0 0 4px;font-size:16px">${t('engine.installTitle')}</h3>
<p style="color:var(--text-secondary);margin:0 0 16px;font-size:13px">${t('engine.installDescSimple')}</p>
${modeSwitch}
${errorBlock}
${progressBlock}
<div style="display:flex;gap:10px;align-items:center;margin-top:16px">
@@ -274,8 +317,21 @@ export function render() {
draw()
})
})
// 安装按钮
// 安装模式切换
el.querySelectorAll('.hermes-mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
const mode = btn.dataset.mode
if (mode && mode !== installMode) {
installMode = mode
installError = null
draw()
}
})
})
// 安装按钮(本地模式)
el.querySelector('.hermes-install-btn')?.addEventListener('click', doInstall)
// 自定义连接按钮
el.querySelector('.hermes-custom-connect')?.addEventListener('click', doCustomConnect)
// 服务商预设按钮
el.querySelectorAll('.hermes-preset-btn').forEach(btn => {
btn.addEventListener('click', () => {
@@ -366,6 +422,39 @@ export function render() {
}
}
// --- 自定义连接流程 ---
async function doCustomConnect() {
const urlInput = el.querySelector('#hm-custom-url')
const url = urlInput?.value?.trim()
if (!url) { installError = t('engine.installCustomEmpty'); draw(); return }
// 基础 URL 格式检查
try { new URL(url) } catch { installError = t('engine.installCustomInvalidUrl'); draw(); return }
installing = true
installError = null
draw()
try {
// 保存 Gateway URL
await api.hermesSetGatewayUrl(url)
// 测试连接
const health = await api.hermesHealthCheck()
if (!health) throw new Error(t('engine.installCustomNoResponse'))
installing = false
customGatewayUrl = url
// 连接成功,跳到配置步骤
phase = 'configure'
draw()
} catch (e) {
installing = false
installError = t('engine.installCustomFailed', { error: e.message || e })
draw()
}
}
// --- 安装流程 ---
async function doInstall() {
installing = true

View File

@@ -30,6 +30,16 @@ export default {
installInfoUv: _('自动下载 uv 包管理器(如未安装)', 'Auto-download uv package manager (if not installed)', '自動下載 uv 包管理器(如未安裝)'),
installInfoCore: _('安装 hermes-agent 核心包', 'Install hermes-agent core package', '安裝 hermes-agent 核心包'),
installInfoExtrasLater: _('扩展组件定时任务、MCP、消息渠道等可在安装后按需添加', 'Extensions (cron, MCP, messaging, etc.) can be added later as needed', '擴展組件定時任務、MCP、訊息頻道等可在安裝後按需添加'),
installModeLocal: _('本地', 'Local', '本地'),
installModeCustom: _('自定义', 'Custom', '自訂'),
installCustomDesc: _('连接到已有的 Hermes Agent Gateway 实例,适用于已在其他机器或手动安装的场景。', 'Connect to an existing Hermes Agent Gateway instance, for setups on other machines or manual installations.', '連接到已有的 Hermes Agent Gateway 實例,適用於已在其他機器或手動安裝的場景。'),
installCustomHint: _('输入已运行的 Hermes Agent Gateway 地址,例如 http://192.168.1.100:8642', 'Enter the URL of a running Hermes Agent Gateway, e.g. http://192.168.1.100:8642', '輸入已運行的 Hermes Agent Gateway 地址,例如 http://192.168.1.100:8642'),
installCustomConnect: _('测试连接', 'Test Connection', '測試連接'),
installCustomTesting: _('连接中...', 'Connecting...', '連接中...'),
installCustomEmpty: _('请输入 Gateway URL', 'Please enter Gateway URL', '請輸入 Gateway URL'),
installCustomInvalidUrl: _('URL 格式不正确', 'Invalid URL format', 'URL 格式不正確'),
installCustomNoResponse: _('Gateway 无响应', 'Gateway not responding', 'Gateway 無回應'),
installCustomFailed: _('连接失败: {error}', 'Connection failed: {error}', '連接失敗: {error}'),
installBtn: _('一键安装', 'Install Now', '一鍵安裝'),
installingBtn: _('正在安装...', 'Installing...', '正在安裝...'),
installSuccess: _('安装成功!', 'Installation successful!', '安裝成功!'),