mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 19:12:04 +08:00
refactor(hermes): delegate deep config to Hermes Web UI
Slim the Hermes surface in CC Switch to match its core positioning — cross-client provider switching and shared MCP/prompts/skills — and delegate deep configuration (model, agent, env, skills, cron, logs) to the Hermes Web UI at http://127.0.0.1:9119. - Drop AgentPanel/EnvPanel/ModelPanel and their mutation commands, hooks, types, and i18n keys across zh/en/ja. - Add open_hermes_web_ui Tauri command that probes /api/status and launches the URL in the system browser. Hermes injects its own session token into the returned HTML, so CC Switch doesn't need to touch auth. - Surface the launcher from the Hermes toolbar and the health banner via a shared useOpenHermesWebUI() hook; the offline error code is defined once per side and referenced across the contract. - Keep read-only access to model.provider so ProviderList can still highlight the active supplier; apply_switch_defaults continues to write the top-level model section when switching providers. Net diff: +152 / -1253.
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
use tauri::State;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, State};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
use crate::hermes_config;
|
||||
use crate::store::AppState;
|
||||
|
||||
/// Error string returned when `open_hermes_web_ui` cannot reach the Hermes
|
||||
/// FastAPI server. Kept in sync with the `HERMES_WEB_OFFLINE_ERROR` constant
|
||||
/// in `src/hooks/useHermes.ts` so the frontend can branch on it.
|
||||
const HERMES_WEB_OFFLINE_ERROR: &str = "hermes_web_offline";
|
||||
|
||||
// ============================================================================
|
||||
// Hermes Provider Commands
|
||||
// ============================================================================
|
||||
@@ -43,52 +50,60 @@ pub fn scan_hermes_config_health() -> Result<Vec<hermes_config::HermesHealthWarn
|
||||
// Model Configuration Commands
|
||||
// ============================================================================
|
||||
|
||||
/// Get Hermes model config (model section of config.yaml)
|
||||
/// Get Hermes model config (model section of config.yaml). Read-only — writes
|
||||
/// happen implicitly through `apply_switch_defaults` when switching providers.
|
||||
#[tauri::command]
|
||||
pub fn get_hermes_model_config() -> Result<Option<hermes_config::HermesModelConfig>, String> {
|
||||
hermes_config::get_model_config().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Set Hermes model config (model section of config.yaml)
|
||||
#[tauri::command]
|
||||
pub fn set_hermes_model_config(
|
||||
model: hermes_config::HermesModelConfig,
|
||||
) -> Result<hermes_config::HermesWriteOutcome, String> {
|
||||
hermes_config::set_model_config(&model).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Configuration Commands
|
||||
// Hermes Web UI launcher
|
||||
// ============================================================================
|
||||
|
||||
/// Get Hermes agent config (agent section of config.yaml)
|
||||
/// Probe the local Hermes Web UI (FastAPI) and open it in the system browser.
|
||||
///
|
||||
/// Port discovery priority:
|
||||
/// 1. `HERMES_WEB_PORT` environment variable
|
||||
/// 2. Default 9119
|
||||
///
|
||||
/// Hermes wraps all `/api/*` routes in a Bearer-token middleware, so a GET
|
||||
/// against `/api/status` returning **either 200 or 401** confirms the server
|
||||
/// is live. The session token lives only in the Hermes process memory and is
|
||||
/// injected into the returned HTML via `window.__HERMES_SESSION_TOKEN__`, so
|
||||
/// there is no need (and no way) for CC Switch to inject it — we just open
|
||||
/// the URL and let Hermes handle auth.
|
||||
#[tauri::command]
|
||||
pub fn get_hermes_agent_config() -> Result<Option<hermes_config::HermesAgentConfig>, String> {
|
||||
hermes_config::get_agent_config().map_err(|e| e.to_string())
|
||||
}
|
||||
pub async fn open_hermes_web_ui(app: AppHandle, path: Option<String>) -> Result<(), String> {
|
||||
let port = std::env::var("HERMES_WEB_PORT")
|
||||
.ok()
|
||||
.and_then(|raw| raw.trim().parse::<u16>().ok())
|
||||
.unwrap_or(9119);
|
||||
|
||||
/// Set Hermes agent config (agent section of config.yaml)
|
||||
#[tauri::command]
|
||||
pub fn set_hermes_agent_config(
|
||||
agent: hermes_config::HermesAgentConfig,
|
||||
) -> Result<hermes_config::HermesWriteOutcome, String> {
|
||||
hermes_config::set_agent_config(&agent).map_err(|e| e.to_string())
|
||||
}
|
||||
let base = format!("http://127.0.0.1:{port}");
|
||||
|
||||
// ============================================================================
|
||||
// Env Configuration Commands
|
||||
// ============================================================================
|
||||
// Probe /api/status with a short timeout. Hermes returns 200 when open or
|
||||
// 401 when the session token is required — either way the server is live.
|
||||
// Only a connection error / timeout means the server isn't running.
|
||||
let probe_url = format!("{base}/api/status");
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_millis(1200))
|
||||
.no_proxy()
|
||||
.build()
|
||||
.map_err(|e| format!("failed to build probe client: {e}"))?;
|
||||
|
||||
/// Get Hermes env config (.env file)
|
||||
#[tauri::command]
|
||||
pub fn get_hermes_env() -> Result<hermes_config::HermesEnvConfig, String> {
|
||||
hermes_config::read_env().map_err(|e| e.to_string())
|
||||
}
|
||||
match client.get(&probe_url).send().await {
|
||||
Ok(_) => {}
|
||||
Err(_) => return Err(HERMES_WEB_OFFLINE_ERROR.to_string()),
|
||||
}
|
||||
|
||||
/// Set Hermes env config (.env file)
|
||||
#[tauri::command]
|
||||
pub fn set_hermes_env(
|
||||
env: hermes_config::HermesEnvConfig,
|
||||
) -> Result<hermes_config::HermesWriteOutcome, String> {
|
||||
hermes_config::write_env(&env).map_err(|e| e.to_string())
|
||||
let target = match path.as_deref() {
|
||||
Some(p) if p.starts_with('/') => format!("{base}{p}"),
|
||||
Some(p) if !p.is_empty() => format!("{base}/{p}"),
|
||||
_ => format!("{base}/"),
|
||||
};
|
||||
|
||||
app.opener()
|
||||
.open_url(&target, None::<String>)
|
||||
.map_err(|e| format!("failed to open Hermes Web UI: {e}"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user