fix(hermes): add --force to uv tool install, improve install error handling

1. Rust + dev-api: uv tool install 添加 --force 标志,解决
   "Executable already exists: hermes.exe" 错误
2. 安装失败时显示醒目的错误提示框(红色边框+图标+详细错误信息)
3. 失败后保留日志面板 + 进度条变红,方便排查
4. 安装按钮变为"重试",点击后清除错误状态重新安装
5. 进度条 error 状态 CSS 样式
This commit is contained in:
晴天
2026-04-13 10:51:41 +08:00
parent b676db6a46
commit 5df0cd36ae
4 changed files with 48 additions and 26 deletions

View File

@@ -6437,7 +6437,7 @@ const handlers = {
: 'hermes-agent @ git+https://github.com/NousResearch/hermes-agent.git'
const installArgs = method === 'uv-pip'
? ['pip', 'install', pkg]
: ['tool', 'install', pkg, '--python', '3.11']
: ['tool', 'install', '--force', pkg, '--python', '3.11']
const result = spawnSync(uv, installArgs, {
env: { ...process.env, PATH: hermesEnhancedPath(), GIT_TERMINAL_PROMPT: '0' },
timeout: 600000,

View File

@@ -946,7 +946,7 @@ async fn install_via_uv_tool(
};
let mut cmd = tokio::process::Command::new(uv_path);
cmd.args(["tool", "install", &pkg, "--python", "3.11"]);
cmd.args(["tool", "install", "--force", &pkg, "--python", "3.11"]);
// 配置 PyPI 镜像extras 的依赖仍从 PyPI 下载)
if let Some(mirror) = pypi_mirror_url() {

View File

@@ -33,6 +33,7 @@ export function render() {
let hermesInfo = null
let logs = []
let installing = false
let installError = null
let progress = 0
let unlisten = null
@@ -132,35 +133,51 @@ export function render() {
const btnText = installing ? `${ICONS.spinner} ${t('engine.installingBtn')}` : `${ICONS.rocket} ${t('engine.installBtn')}`
const btnDisabled = installing ? 'disabled' : ''
// 错误提示块
const errorBlock = installError ? `
<div style="margin-bottom:14px;padding:12px 16px;background:var(--error-bg, #fef2f2);border:1px solid var(--error, #ef4444);border-radius:var(--radius-sm,6px);font-size:13px;line-height:1.6">
<div style="display:flex;align-items:flex-start;gap:8px">
${ICONS.error}
<div>
<div style="font-weight:600;color:var(--error, #ef4444);margin-bottom:4px">${t('engine.installFailed')}</div>
<div style="color:var(--text-secondary);word-break:break-all">${esc(installError)}</div>
</div>
</div>
</div>
` : ''
// 进度 + 日志区(安装中或安装失败后都显示)
const hasLogs = installing || logs.length > 0
const progressBlock = hasLogs ? `
<div class="hermes-install-status">
<div class="hermes-progress"><div class="hermes-progress-bar${installError ? ' error' : ''}" style="width:${progress}%"></div></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px">
<span class="hermes-progress-text" style="font-size:12px;color:${installError ? 'var(--error, #ef4444)' : 'var(--text-tertiary)'}">${installError ? t('engine.installFailed') : progress >= 100 ? t('engine.installSuccess') : t('engine.installingBtn')}</span>
<span style="font-size:12px;color:var(--text-tertiary);font-family:monospace">${Math.min(progress, 100)}%</span>
</div>
</div>
<div class="hermes-log-panel" style="margin-top:12px">
<div class="hermes-log-content">${logs.map(l => `<div>${esc(l)}</div>`).join('')}</div>
</div>
` : `
<div class="hermes-install-info">
<div class="hermes-detect-row" style="margin-bottom:6px">${ICONS.check} <span>${t('engine.installInfoUv')}</span></div>
<div class="hermes-detect-row" style="margin-bottom:6px">${ICONS.check} <span>${t('engine.installInfoCore')}</span></div>
<div class="hermes-detect-row" style="color:var(--text-tertiary)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<span>${t('engine.installInfoExtrasLater')}</span>
</div>
</div>
`
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.installDescSimple')}</p>
${installing || progress > 0 ? `
<div class="hermes-install-status">
<div class="hermes-progress"><div class="hermes-progress-bar" style="width:${progress}%"></div></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px">
<span class="hermes-progress-text" style="font-size:12px;color:var(--text-tertiary)">${progress >= 100 ? t('engine.installSuccess') : t('engine.installingBtn')}</span>
<span style="font-size:12px;color:var(--text-tertiary);font-family:monospace">${Math.min(progress, 100)}%</span>
</div>
</div>
<div class="hermes-log-panel" style="margin-top:12px">
<div class="hermes-log-content">${logs.map(l => `<div>${esc(l)}</div>`).join('')}</div>
</div>
` : `
<div class="hermes-install-info">
<div class="hermes-detect-row" style="margin-bottom:6px">${ICONS.check} <span>${t('engine.installInfoUv')}</span></div>
<div class="hermes-detect-row" style="margin-bottom:6px">${ICONS.check} <span>${t('engine.installInfoCore')}</span></div>
<div class="hermes-detect-row" style="color:var(--text-tertiary)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<span>${t('engine.installInfoExtrasLater')}</span>
</div>
</div>
`}
${errorBlock}
${progressBlock}
<div style="display:flex;gap:10px;align-items:center;margin-top:16px">
<button class="btn btn-primary hermes-install-btn" ${btnDisabled}>${btnText}</button>
<button class="btn btn-primary hermes-install-btn" ${btnDisabled}>${installError ? `${ICONS.rocket} ${t('engine.retryBtn')}` : btnText}</button>
</div>
</div>
</div>`
@@ -352,6 +369,7 @@ export function render() {
// --- 安装流程 ---
async function doInstall() {
installing = true
installError = null
progress = 0
logs = []
draw()
@@ -392,6 +410,7 @@ export function render() {
draw()
} catch (e) {
installing = false
installError = String(e.message || e)
logs.push(`${t('engine.installFailed')}: ${e}`)
draw()
} finally {

View File

@@ -1797,6 +1797,9 @@
border-radius: 4px;
transition: width 0.4s ease;
}
.hermes-progress-bar.error {
background: linear-gradient(90deg, var(--error, #ef4444), color-mix(in srgb, var(--error, #ef4444) 80%, #fff));
}
/* === Hermes Setup — Log Panel === */
.hermes-log-panel {