feat(hermes): launch dashboard from toolbar when Web UI is offline

When the Hermes Web UI probe fails, the toolbar entry now opens an info
confirm dialog offering to run `hermes dashboard` in the user's preferred
terminal. Accepting spawns it via a temp bash/batch script; `hermes
dashboard` itself opens the browser once ready, so we do not poll.
The Memory panel and Health banner keep the existing toast behavior.

Also corrects the stale `hermes web` hint in the offline toast (the real
command is `hermes dashboard`) and reorders Linux terminal detection to
try `which` before stat'ing /usr/bin, /bin, /usr/local/bin.
This commit is contained in:
Jason
2026-04-21 21:11:15 +08:00
parent 38709c94db
commit 8e9452b7ec
9 changed files with 259 additions and 12 deletions
+27 -1
View File
@@ -46,6 +46,7 @@ import {
useHermesHealth,
useOpenHermesWebUI,
} from "@/hooks/useHermes";
import { hermesApi } from "@/lib/api/hermes";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import { useAutoCompact } from "@/hooks/useAutoCompact";
import { useLastValidValue } from "@/hooks/useLastValidValue";
@@ -628,7 +629,10 @@ function App() {
};
}, []);
const openHermesWebUI = useOpenHermesWebUI();
const [launchDashboardOpen, setLaunchDashboardOpen] = useState(false);
const openHermesWebUI = useOpenHermesWebUI(() =>
setLaunchDashboardOpen(true),
);
const handleOpenWebsite = async (url: string) => {
try {
@@ -1611,6 +1615,28 @@ function App() {
onCancel={() => setConfirmAction(null)}
/>
<ConfirmDialog
isOpen={launchDashboardOpen}
title={t("hermes.webui.launchConfirmTitle")}
message={t("hermes.webui.launchConfirmMessage")}
confirmText={t("hermes.webui.launchConfirmAction")}
variant="info"
onConfirm={() => {
setLaunchDashboardOpen(false);
void (async () => {
try {
await hermesApi.launchDashboard();
toast.success(t("hermes.webui.launching"));
} catch (error) {
toast.error(t("hermes.webui.launchFailed"), {
description: extractErrorMessage(error) || undefined,
});
}
})();
}}
onCancel={() => setLaunchDashboardOpen(false)}
/>
<DeepLinkImportDialog />
<FirstRunNoticeDialog />
</div>
+10 -5
View File
@@ -155,10 +155,11 @@ export function useToggleHermesMemoryEnabled() {
/**
* Returns a handler that probes the local Hermes Web UI, opens it in the
* system browser, and surfaces a localized toast on failure. Callers only
* need to wire the returned function to a click handler.
* system browser, and surfaces a localized toast on failure. When
* `onOffline` is provided, it replaces the default offline toast —
* callers can use this to open a launch-dashboard confirm dialog instead.
*/
export function useOpenHermesWebUI() {
export function useOpenHermesWebUI(onOffline?: () => void) {
const { t } = useTranslation();
return useCallback(
async (path?: string) => {
@@ -167,7 +168,11 @@ export function useOpenHermesWebUI() {
} catch (error) {
const detail = extractErrorMessage(error);
if (detail === HERMES_WEB_OFFLINE_ERROR) {
toast.error(t("hermes.webui.offline"));
if (onOffline) {
onOffline();
} else {
toast.error(t("hermes.webui.offline"));
}
} else {
toast.error(t("hermes.webui.openFailed"), {
description: detail || undefined,
@@ -175,6 +180,6 @@ export function useOpenHermesWebUI() {
}
}
},
[t],
[t, onOffline],
);
}
+7 -2
View File
@@ -1677,9 +1677,14 @@
},
"webui": {
"open": "Open Hermes Web UI",
"offline": "Hermes Web UI is not running. Start it with `hermes web` first.",
"offline": "Hermes Web UI is not running. Start it with `hermes dashboard` first.",
"openFailed": "Failed to open Hermes Web UI",
"fixInWebUI": "Fix in Hermes Web UI"
"fixInWebUI": "Fix in Hermes Web UI",
"launchConfirmTitle": "Hermes Dashboard is not running",
"launchConfirmMessage": "Open a terminal and start it now with `hermes dashboard`?\n\nThe browser will open automatically once startup completes.\n\nIf the terminal reports that `hermes` cannot be found or the web extras are missing, run first:\npip install hermes-agent[web]",
"launchConfirmAction": "Open terminal & launch",
"launching": "Started `hermes dashboard` in a terminal.",
"launchFailed": "Failed to open terminal"
},
"health": {
"title": "Hermes config warnings detected",
+7 -2
View File
@@ -1677,9 +1677,14 @@
},
"webui": {
"open": "Hermes Web UI を開く",
"offline": "Hermes Web UI が起動していません。まず `hermes web` を実行してください。",
"offline": "Hermes Web UI が起動していません。まず `hermes dashboard` を実行してください。",
"openFailed": "Hermes Web UI を開けませんでした",
"fixInWebUI": "Hermes Web UI で修正"
"fixInWebUI": "Hermes Web UI で修正",
"launchConfirmTitle": "Hermes Dashboard が起動していません",
"launchConfirmMessage": "ターミナルを開いて `hermes dashboard` を実行しますか?\n\n起動完了後、ブラウザが自動的に開きます。\n\nターミナルに `hermes` が見つからない、または web 依存が不足するというエラーが表示される場合は、まず次を実行してください:\npip install hermes-agent[web]",
"launchConfirmAction": "ターミナルを開いて起動",
"launching": "ターミナルで hermes dashboard を起動しました。",
"launchFailed": "ターミナルを開けませんでした"
},
"health": {
"title": "Hermes 設定の警告を検出",
+7 -2
View File
@@ -1677,9 +1677,14 @@
},
"webui": {
"open": "打开 Hermes Web UI",
"offline": "Hermes Web UI 未启动,请先运行 `hermes web` 启动服务。",
"offline": "Hermes Web UI 未启动,请先运行 `hermes dashboard` 启动服务。",
"openFailed": "打开 Hermes Web UI 失败",
"fixInWebUI": "在 Hermes Web UI 修复"
"fixInWebUI": "在 Hermes Web UI 修复",
"launchConfirmTitle": "Hermes Dashboard 未启动",
"launchConfirmMessage": "是否打开终端并运行 `hermes dashboard` 启动服务?\n\n启动完成后会自动打开浏览器。\n\n若终端提示找不到 hermes 或缺少 web 依赖,请先运行:\npip install hermes-agent[web]",
"launchConfirmAction": "打开终端并启动",
"launching": "已在终端启动 hermes dashboard",
"launchFailed": "打开终端失败"
},
"health": {
"title": "检测到 Hermes 配置警告",
+5
View File
@@ -33,6 +33,11 @@ export const hermesApi = {
await invoke("open_hermes_web_ui", { path: path ?? null });
},
/** Open the preferred terminal and run `hermes dashboard` (non-blocking). */
async launchDashboard(): Promise<void> {
await invoke("launch_hermes_dashboard");
},
/**
* Read one of Hermes' memory blobs (`MEMORY.md` or `USER.md`). Returns an
* empty string when the file hasn't been created yet.