mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-30 21:00:30 +08:00
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:
@@ -3832,6 +3832,72 @@ pub async fn hermes_session_export(session_id: String) -> Result<Value, String>
|
||||
resp.json::<Value>().await.map_err(|e| format!("解析 JSON 失败: {e}"))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Batch 2 §H 基础设施: hermes_dashboard_api_proxy
|
||||
//
|
||||
// 通用 Dashboard 9119 HTTP 代理 — 让前端直接调任意 /api/* 端点。
|
||||
// Profiles / Kanban / OAuth / Sessions(高级)等都走这一个入口,
|
||||
// 避免给每个端点都写专属 Tauri 命令。
|
||||
//
|
||||
// 与 hermes_api_proxy 区别:
|
||||
// - hermes_api_proxy 走 Gateway 8642(含 API_SERVER_KEY 认证)
|
||||
// - hermes_dashboard_api_proxy 走 Dashboard 9119(无需 token,本地绑定)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hermes_dashboard_api_proxy(
|
||||
method: String,
|
||||
path: String,
|
||||
body: Option<String>,
|
||||
headers: Option<Value>,
|
||||
) -> Result<Value, String> {
|
||||
let port = hermes_dashboard_port();
|
||||
let url = format!("http://127.0.0.1:{port}{path}");
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.map_err(|e| format!("HTTP 客户端创建失败: {e}"))?;
|
||||
|
||||
let mut req = match method.to_uppercase().as_str() {
|
||||
"GET" => client.get(&url),
|
||||
"POST" => client.post(&url),
|
||||
"PUT" => client.put(&url),
|
||||
"PATCH" => client.patch(&url),
|
||||
"DELETE" => client.delete(&url),
|
||||
_ => return Err(format!("不支持的方法: {method}")),
|
||||
};
|
||||
|
||||
// 自定义 headers
|
||||
if let Some(Value::Object(map)) = headers {
|
||||
for (k, v) in map.iter() {
|
||||
if let Some(s) = v.as_str() {
|
||||
req = req.header(k, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// body(POST/PUT/PATCH/DELETE)— 假定是 JSON 字符串
|
||||
if let Some(b) = body {
|
||||
req = req
|
||||
.header("Content-Type", "application/json")
|
||||
.body(b);
|
||||
}
|
||||
|
||||
let resp = req
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Dashboard 请求失败: {}(提示:请先启动 Dashboard)", reqwest_error_detail(&e)))?;
|
||||
let status = resp.status();
|
||||
let resp_body = resp.text().await.unwrap_or_default();
|
||||
if !status.is_success() {
|
||||
return Err(format!("HTTP {}: {}", status.as_u16(), resp_body));
|
||||
}
|
||||
// 尝试解析 JSON,失败回退到字符串包装
|
||||
Ok(serde_json::from_str::<Value>(&resp_body)
|
||||
.unwrap_or_else(|_| Value::String(resp_body)))
|
||||
}
|
||||
|
||||
/// Batch 3 §K: 多模态附件结构
|
||||
///
|
||||
/// 前端传过来的附件描述(图片用 base64 直传)。
|
||||
|
||||
@@ -242,6 +242,7 @@ pub fn run() {
|
||||
hermes::hermes_run_stop,
|
||||
hermes::hermes_run_approval,
|
||||
hermes::hermes_session_export,
|
||||
hermes::hermes_dashboard_api_proxy,
|
||||
hermes::hermes_read_config,
|
||||
hermes::hermes_read_config_full,
|
||||
hermes::hermes_lazy_deps_features,
|
||||
|
||||
Reference in New Issue
Block a user