feat(hermes): Batch 2 §H - Profiles 管理 UI + Dashboard API 通用代理

校对稿要点:「Profiles 全部走 HTTP /api/profiles*,无需自己写 CLI 桥接」。

## 基础设施: hermes_dashboard_api_proxy(通用 9119 HTTP 代理)

新增 Tauri 命令 hermes_dashboard_api_proxy(method, path, body, headers):
- 支持 GET/POST/PUT/PATCH/DELETE
- 走 Dashboard 9119 端口(无需 API_SERVER_KEY,本地绑定)
- 自动 JSON parse + 错误时友好提示「请先启动 Dashboard」
- 一次实现,未来 Batch 2/3 的 Profiles/Kanban/OAuth/Sessions 都走这一个入口

前端 wrapper: api.hermesDashboardApi(method, path, body, headers)
dev-api.js: Web 模式同步实现

## Profiles 管理页 /h/profiles

新文件 src/engines/hermes/pages/profiles.js:
- GET /api/profiles 列表 → 渲染卡片网格(复用 .lazy-deps-grid 样式)
- 每张卡片:profile 名 + active 徽章 + Switch/Rename/Delete 按钮
- 「+ 新建」按钮 → showModal 弹窗(name + clone_from_default 选项)
- 「重命名」→ PATCH /api/profiles/{name} { new_name }
- 「删除」→ showConfirm(带 impact 提示「永久清除会话/凭据/记忆」)→ DELETE /api/profiles/{name}
- 「切换到此」→ 复用现有 chatStore.switchProfile(CLI 实现)
- 失败走 humanizeError 友好提示
- active profile 与 chatStore.state.activeProfile 对齐

## sidebar + 路由
- Hermes 引擎「管理」section 加 Profile 管理入口
- 路由 /h/profiles 注册到 hermes/index.js

## i18n
- engine.hermesProfilesTitle / hermesProfilesDesc / hermesProfilesEmpty
- hermesProfileNew / NewTitle / NameLabel / NameRequired / CloneFromDefault / CloneHint
- hermesProfileSwitch / Switched / SwitchFailed
- hermesProfileRename / RenameTitle / NewNameLabel / Renamed / RenameFailed
- hermesProfileDelete / DeleteConfirm / DeleteImpact / Deleted / DeleteFailed
- hermesProfileActive / Created / CreateFailed
- 共 21 个键 × 3 语言

## 累计
- Rust: 1 个新命令(hermes_dashboard_api_proxy ~50 行)
- 前端: 1 个 wrapper + 新页面 ~180 行
- dev-api.js: 1 个 handler
- i18n: 21 个新键 × 3 语言
- cargo check ✓ + npm build ✓
This commit is contained in:
晴天
2026-05-14 05:04:53 +08:00
parent 8eb8a7666e
commit 3168551569
7 changed files with 319 additions and 0 deletions

View File

@@ -7209,6 +7209,25 @@ const handlers = {
return await resp.json().catch(() => ({ ok: true }))
},
// Batch 2 §H 基础设施: 通用 Dashboard 9119 HTTP 代理
async hermes_dashboard_api_proxy({ method = 'GET', path: reqPath = '/', body = null, headers: customHeaders } = {}) {
const port = handlers._hermesDashboardPort()
const url = `http://127.0.0.1:${port}${reqPath}`
const opts = { method: String(method).toUpperCase(), headers: { 'User-Agent': 'ClawPanel-Web' } }
opts.signal = AbortSignal.timeout(30000)
if (customHeaders && typeof customHeaders === 'object') {
Object.assign(opts.headers, customHeaders)
}
if (body != null && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(opts.method)) {
opts.headers['Content-Type'] = 'application/json'
opts.body = typeof body === 'string' ? body : JSON.stringify(body)
}
const resp = await globalThis.fetch(url, opts)
const text = await resp.text().catch(() => '')
if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text}(提示:请先启动 Dashboard`)
try { return JSON.parse(text) } catch { return text }
},
// Batch 1 §E: Sessions 导出(走 dashboard 9119
async hermes_session_export({ sessionId } = {}) {
if (!sessionId) throw new Error('session_id 不能为空')