From 26423984d1e039f780b9b3c94514111193530d32 Mon Sep 17 00:00:00 2001 From: ekko Date: Thu, 16 Apr 2026 15:19:05 +0800 Subject: [PATCH] fix: profile import file upload, startup health check, sidebar scroll, node-pty fallback - Change profile import from server path input to browser file upload (multipart) - Fix startup script to wait for health check before opening browser - Add overflow scroll with hidden scrollbar to sidebar nav - Graceful degradation when node-pty fails to load (WSL compatibility) - Remove rename button from profile cards - Restrict profile name input to English letters, numbers, hyphens - Use raw.githubusercontent.com URLs in README setup script Co-Authored-By: Claude Opus 4.6 --- README.md | 4 +- bin/hermes-web-ui.mjs | 63 +++++++++++++++---- package.json | 2 +- packages/client/src/api/hermes/profiles.ts | 17 +++-- .../hermes/profiles/ProfileCard.vue | 5 +- .../hermes/profiles/ProfileCreateModal.vue | 3 +- .../hermes/profiles/ProfileImportModal.vue | 63 +++++++++++-------- .../src/components/layout/AppSidebar.vue | 7 +++ .../src/components/layout/ProfileSelector.vue | 2 - packages/client/src/i18n/locales/en.ts | 4 +- packages/client/src/i18n/locales/zh.ts | 4 +- packages/client/src/stores/hermes/profiles.ts | 4 +- packages/server/src/routes/hermes/profiles.ts | 59 +++++++++++++++-- packages/server/src/routes/hermes/terminal.ts | 22 +++++-- 14 files changed, 192 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 4670c7d..7a78eed 100644 --- a/README.md +++ b/README.md @@ -132,13 +132,13 @@ Open **http://localhost:8648** Automatically installs Node.js (if missing) and hermes-web-ui on Debian/Ubuntu/macOS: ```bash -bash <(curl -fsSL https://cdn.jsdelivr.net/gh/EKKOLearnAI/hermes-web-ui@main/scripts/setup.sh) +bash <(curl -fsSL https://raw.githubusercontent.com/EKKOLearnAI/hermes-web-ui/main/scripts/setup.sh) ``` ### WSL ```bash -bash <(curl -fsSL https://cdn.jsdelivr.net/gh/EKKOLearnAI/hermes-web-ui@main/scripts/setup.sh) +bash <(curl -fsSL https://raw.githubusercontent.com/EKKOLearnAI/hermes-web-ui/main/scripts/setup.sh) hermes-web-ui start ``` diff --git a/bin/hermes-web-ui.mjs b/bin/hermes-web-ui.mjs index 52cc1b2..2a9ba49 100755 --- a/bin/hermes-web-ui.mjs +++ b/bin/hermes-web-ui.mjs @@ -135,23 +135,60 @@ function startDaemon(port) { child.unref() writePid(child.pid) - setTimeout(() => { - if (isRunning(child.pid)) { - console.log(` ✓ hermes-web-ui started (PID: ${child.pid}, port: ${port})`) - const url = token - ? `http://localhost:${port}/#/?token=${token}` - : `http://localhost:${port}` - console.log(` ${url}`) - console.log(` Log: ${LOG_FILE}`) - const isWin = process.platform === 'win32' - const cmd = isWin ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}` - try { execSync(cmd, { stdio: 'ignore' }) } catch {} - } else { + // Poll health endpoint until server is ready (setTimeout to avoid overlapping requests) + const healthUrl = `http://127.0.0.1:${port}/health` + const maxWait = 30000 + const interval = 500 + let waited = 0 + + console.log(` ⏳ Starting hermes-web-ui (PID: ${child.pid}, port: ${port})...`) + + function poll() { + waited += interval + if (!isRunning(child.pid)) { console.log(' ✗ Failed to start hermes-web-ui') + console.log(` Check log: ${LOG_FILE}`) removePid() process.exit(1) + return } - }, 500) + + fetch(healthUrl).then(res => { + if (res.ok) { + const url = token + ? `http://localhost:${port}/#/?token=${token}` + : `http://localhost:${port}` + console.log(` ✓ hermes-web-ui started`) + console.log(` ${url}`) + console.log(` Log: ${LOG_FILE}`) + const isWin = process.platform === 'win32' + const cmd = isWin ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}` + try { execSync(cmd, { stdio: 'ignore' }) } catch {} + } else if (waited < maxWait) { + setTimeout(poll, interval) + } else { + console.log(` ⚠ Server process is running but health check failed after ${maxWait / 1000}s`) + console.log(` Check log: ${LOG_FILE}`) + const url = token + ? `http://localhost:${port}/#/?token=${token}` + : `http://localhost:${port}` + console.log(` ${url}`) + } + }).catch(() => { + if (waited < maxWait) { + setTimeout(poll, interval) + } else { + console.log(` ⚠ Server process is running but health check failed after ${maxWait / 1000}s`) + console.log(` Check log: ${LOG_FILE}`) + const url = token + ? `http://localhost:${port}/#/?token=${token}` + : `http://localhost:${port}` + console.log(` ${url}`) + } + }) + } + + setTimeout(poll, interval) } function stopDaemon() { diff --git a/package.json b/package.json index 9e3a00c..2444eba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hermes-web-ui", - "version": "0.2.6", + "version": "0.2.9", "description": "Web dashboard for Hermes Agent — multi-platform AI chat, session management, scheduled jobs, usage analytics & channel configuration (Telegram, Discord, Slack, WhatsApp)", "repository": { "type": "git", diff --git a/packages/client/src/api/hermes/profiles.ts b/packages/client/src/api/hermes/profiles.ts index 2073966..773e674 100644 --- a/packages/client/src/api/hermes/profiles.ts +++ b/packages/client/src/api/hermes/profiles.ts @@ -100,13 +100,22 @@ export async function exportProfile(name: string): Promise { } } -export async function importProfile(archive: string, name?: string): Promise { +export async function importProfile(file: File): Promise { try { - await request('/api/hermes/profiles/import', { + const baseUrl = getBaseUrlValue() + const token = getApiKey() + const headers: Record = {} + if (token) headers['Authorization'] = `Bearer ${token}` + + const formData = new FormData() + formData.append('file', file) + + const res = await fetch(`${baseUrl}/api/hermes/profiles/import`, { method: 'POST', - body: JSON.stringify({ archive, name }), + headers, + body: formData, }) - return true + return res.ok } catch { return false } diff --git a/packages/client/src/components/hermes/profiles/ProfileCard.vue b/packages/client/src/components/hermes/profiles/ProfileCard.vue index 5bb0b57..69db9f4 100644 --- a/packages/client/src/components/hermes/profiles/ProfileCard.vue +++ b/packages/client/src/components/hermes/profiles/ProfileCard.vue @@ -6,7 +6,7 @@ import { useProfilesStore } from '@/stores/hermes/profiles' import { useI18n } from 'vue-i18n' const props = defineProps<{ profile: HermesProfile }>() -const emit = defineEmits<{ rename: [name: string] }>() +const emit = defineEmits<{}>() const { t } = useI18n() const profilesStore = useProfilesStore() @@ -154,9 +154,6 @@ async function handleExport() { > {{ t('profiles.switchTo') }} - - {{ t('profiles.rename') }} - diff --git a/packages/client/src/components/hermes/profiles/ProfileImportModal.vue b/packages/client/src/components/hermes/profiles/ProfileImportModal.vue index d9d3f94..e9056a9 100644 --- a/packages/client/src/components/hermes/profiles/ProfileImportModal.vue +++ b/packages/client/src/components/hermes/profiles/ProfileImportModal.vue @@ -1,6 +1,7 @@