From fc8f209e01ba84e64b1f52a38e07a5d9e8c888ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Tue, 26 May 2026 04:55:29 +0800 Subject: [PATCH] feat(hermes): add display skin config --- scripts/dev-api.js | 12 ++++++ src-tauri/src/commands/hermes.rs | 62 ++++++++++++++++++++++++++++- src/engines/hermes/pages/config.js | 15 +++++++ src/locales/modules/engine.js | 15 ++++++- tests/hermes-config-page-ui.test.js | 2 + tests/hermes-display-config.test.js | 15 ++++++- 6 files changed, 117 insertions(+), 4 deletions(-) diff --git a/scripts/dev-api.js b/scripts/dev-api.js index a652d33..1eaf9d9 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -3345,6 +3345,7 @@ const HERMES_DISPLAY_BUSY_INPUT_MODES = new Set(['interrupt', 'queue', 'steer']) const HERMES_DISPLAY_BACKGROUND_PROCESS_NOTIFICATIONS = new Set(['off', 'result', 'error', 'all']) const HERMES_DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES = new Set(['render', 'strip', 'raw']) const HERMES_DISPLAY_LANGUAGE_VALUES = new Set(['en', 'zh', 'zh-hant', 'ja', 'de', 'es', 'fr', 'tr', 'uk', 'af', 'ko', 'it', 'ga', 'pt', 'ru', 'hu']) +const HERMES_DISPLAY_SKINS = new Set(['default', 'ares', 'mono', 'slate', 'daylight', 'warm-lightmode', 'poseidon', 'sisyphus', 'charizard']) const HERMES_RUNTIME_FOOTER_FIELDS = new Set(['model', 'context_pct', 'cwd', 'duration', 'tokens', 'cost']) const HERMES_HOOK_EVENTS = new Set([ 'pre_tool_call', @@ -3624,6 +3625,13 @@ function normalizeHermesDisplayLanguage(value, strict = false) { return 'en' } +function normalizeHermesDisplaySkin(value, strict = false) { + const skin = String(value ?? '').trim().toLowerCase() || 'default' + if (HERMES_DISPLAY_SKINS.has(skin)) return skin + if (strict) throw new Error('display.skin 必须是内置皮肤 default、ares、mono、slate、daylight、warm-lightmode、poseidon、sisyphus 或 charizard') + return 'default' +} + function normalizeHermesRuntimeFooterFields(value, strict = false) { const items = Array.isArray(value) ? value @@ -3660,6 +3668,8 @@ export function buildHermesDisplayConfigValues(config = {}) { ? display.runtime_footer : {} return { + displayCompact: readHermesBool(display.compact, false), + displaySkin: normalizeHermesDisplaySkin(display.skin, false), displayToolProgress: normalizeHermesDisplayToolProgress(display.tool_progress, false), displayToolProgressCommand: readHermesBool(display.tool_progress_command, false), displayInterimAssistantMessages: readHermesBool(display.interim_assistant_messages, true), @@ -3688,6 +3698,8 @@ export function mergeHermesDisplayConfig(config = {}, form = {}) { ? mergeConfigsPreservingFields(display.runtime_footer, {}) : {} + display.compact = formHermesBool(form, 'displayCompact', currentValues.displayCompact) + display.skin = normalizeHermesDisplaySkin(Object.hasOwn(form, 'displaySkin') ? form.displaySkin : currentValues.displaySkin, true) display.tool_progress = normalizeHermesDisplayToolProgress(Object.hasOwn(form, 'displayToolProgress') ? form.displayToolProgress : currentValues.displayToolProgress, true, 'display.tool_progress') display.tool_progress_command = formHermesBool(form, 'displayToolProgressCommand', currentValues.displayToolProgressCommand) display.interim_assistant_messages = formHermesBool(form, 'displayInterimAssistantMessages', currentValues.displayInterimAssistantMessages) diff --git a/src-tauri/src/commands/hermes.rs b/src-tauri/src/commands/hermes.rs index 5051e74..7110dab 100644 --- a/src-tauri/src/commands/hermes.rs +++ b/src-tauri/src/commands/hermes.rs @@ -5651,6 +5651,17 @@ const HERMES_DISPLAY_LANGUAGE_VALUES: &[&str] = &[ const HERMES_DISPLAY_BUSY_INPUT_MODES: &[&str] = &["interrupt", "queue", "steer"]; const HERMES_DISPLAY_BACKGROUND_PROCESS_NOTIFICATIONS: &[&str] = &["off", "result", "error", "all"]; const HERMES_DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES: &[&str] = &["render", "strip", "raw"]; +const HERMES_DISPLAY_SKINS: &[&str] = &[ + "default", + "ares", + "mono", + "slate", + "daylight", + "warm-lightmode", + "poseidon", + "sisyphus", + "charizard", +]; const HERMES_RUNTIME_FOOTER_FIELDS: &[&str] = &["model", "context_pct", "cwd", "duration", "tokens", "cost"]; @@ -5674,6 +5685,22 @@ fn normalize_hermes_display_language( } } +fn normalize_hermes_display_skin(value: Option, strict: bool) -> Result { + let skin = value.unwrap_or_default().trim().to_ascii_lowercase(); + let skin = if skin.is_empty() { + "default".to_string() + } else { + skin + }; + if HERMES_DISPLAY_SKINS.contains(&skin.as_str()) { + Ok(skin) + } else if strict { + Err("display.skin 必须是内置皮肤 default、ares、mono、slate、daylight、warm-lightmode、poseidon、sisyphus 或 charizard".to_string()) + } else { + Ok("default".to_string()) + } +} + fn normalize_hermes_display_resume(value: Option, strict: bool) -> Result { let mode = value.unwrap_or_default().trim().to_ascii_lowercase(); let mode = if mode.is_empty() { @@ -5841,6 +5868,11 @@ fn build_hermes_display_config_values(config: &serde_yaml::Value) -> Value { }); serde_json::json!({ + "displayCompact": display.and_then(|map| yaml_bool_field(map, "compact")).unwrap_or(false), + "displaySkin": normalize_hermes_display_skin( + display.and_then(|map| yaml_string_field(map, "skin")), + false, + ).unwrap_or_else(|_| "default".to_string()), "displayToolProgress": normalize_hermes_display_tool_progress( display.and_then(|map| yaml_string_field(map, "tool_progress")), false, @@ -5919,6 +5951,21 @@ fn merge_hermes_display_config(config: &mut serde_yaml::Value, form: &Value) -> )?; let display = yaml_child_object(ensure_yaml_object(config)?, "display")?; + display.insert( + yaml_key("compact"), + serde_yaml::Value::Bool( + form_bool(form, "displayCompact") + .unwrap_or_else(|| current["displayCompact"].as_bool().unwrap_or(false)), + ), + ); + display.insert( + yaml_key("skin"), + serde_yaml::Value::String(normalize_hermes_display_skin( + form_string(form, "displaySkin") + .or_else(|| current["displaySkin"].as_str().map(ToString::to_string)), + true, + )?), + ); display.insert( yaml_key("tool_progress"), serde_yaml::Value::String(tool_progress), @@ -17785,6 +17832,8 @@ mod hermes_display_config_tests { let config: serde_yaml::Value = serde_yaml::from_str("{}").unwrap(); let values = build_hermes_display_config_values(&config); assert_eq!(values["displayToolProgress"], "all"); + assert_eq!(values["displayCompact"], false); + assert_eq!(values["displaySkin"], "default"); assert_eq!(values["displayToolProgressCommand"], false); assert_eq!(values["displayInterimAssistantMessages"], true); assert_eq!(values["displayRuntimeFooterEnabled"], false); @@ -17810,6 +17859,8 @@ mod hermes_display_config_tests { r#" display: tool_progress: VERBOSE + compact: true + skin: MONO tool_progress_command: true interim_assistant_messages: false runtime_footer: @@ -17833,6 +17884,8 @@ display: .unwrap(); let values = build_hermes_display_config_values(&config); assert_eq!(values["displayToolProgress"], "verbose"); + assert_eq!(values["displayCompact"], true); + assert_eq!(values["displaySkin"], "mono"); assert_eq!(values["displayToolProgressCommand"], true); assert_eq!(values["displayInterimAssistantMessages"], false); assert_eq!(values["displayRuntimeFooterEnabled"], true); @@ -17876,6 +17929,8 @@ memory: &mut config, &json!({ "displayToolProgress": "off", + "displayCompact": true, + "displaySkin": "slate", "displayToolProgressCommand": true, "displayInterimAssistantMessages": false, "displayRuntimeFooterEnabled": true, @@ -17896,7 +17951,8 @@ memory: assert_eq!(config["model"]["provider"].as_str(), Some("anthropic")); assert_eq!(config["memory"]["memory_enabled"].as_bool(), Some(true)); - assert_eq!(config["display"]["skin"].as_str(), Some("midnight")); + assert_eq!(config["display"]["compact"].as_bool(), Some(true)); + assert_eq!(config["display"]["skin"].as_str(), Some("slate")); assert_eq!( config["display"]["platforms"]["telegram"]["tool_progress"].as_str(), Some("new") @@ -17967,6 +18023,10 @@ memory: .unwrap_err(); assert!(err.contains("display.tool_progress")); + let err = merge_hermes_display_config(&mut config, &json!({ "displaySkin": "unknown" })) + .unwrap_err(); + assert!(err.contains("display.skin")); + let err = merge_hermes_display_config(&mut config, &json!({ "displayResumeDisplay": "compact" })) .unwrap_err(); diff --git a/src/engines/hermes/pages/config.js b/src/engines/hermes/pages/config.js index 22efe57..99a911c 100644 --- a/src/engines/hermes/pages/config.js +++ b/src/engines/hermes/pages/config.js @@ -136,6 +136,8 @@ const SECURITY_DEFAULTS = { const DISPLAY_DEFAULTS = { displayToolProgress: 'all', + displayCompact: false, + displaySkin: 'default', displayToolProgressCommand: false, displayInterimAssistantMessages: true, displayRuntimeFooterEnabled: false, @@ -265,6 +267,7 @@ const UNAUTHORIZED_DM_BEHAVIORS = ['pair', 'ignore'] const IMAGE_INPUT_MODES = ['auto', 'native', 'text'] const REASONING_EFFORTS = ['xhigh', 'high', 'medium', 'low', 'minimal', 'none'] const DISPLAY_TOOL_PROGRESS_VALUES = ['off', 'new', 'all', 'verbose'] +const DISPLAY_SKINS = ['default', 'ares', 'mono', 'slate', 'daylight', 'warm-lightmode', 'poseidon', 'sisyphus', 'charizard'] const DISPLAY_LANGUAGE_VALUES = ['en', 'zh', 'zh-hant', 'ja', 'de', 'es', 'fr', 'tr', 'uk', 'af', 'ko', 'it', 'ga', 'pt', 'ru', 'hu'] const DISPLAY_RESUME_VALUES = ['full', 'minimal'] const DISPLAY_BUSY_INPUT_MODES = ['interrupt', 'queue', 'steer'] @@ -1243,6 +1246,12 @@ export function render() { ${DISPLAY_TOOL_PROGRESS_VALUES.map(mode => option(`engine.hermesDisplayConfigToolProgress_${mode}`, mode, displayValues.displayToolProgress)).join('')} +