From 63533036cf8142800b24430f02f592ada0c222f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Wed, 27 May 2026 06:13:49 +0800 Subject: [PATCH] feat(hermes): add display analytics controls --- scripts/dev-api.js | 11 ++++++++ src-tauri/src/commands/hermes.rs | 41 +++++++++++++++++++++++++++++ src/engines/hermes/pages/config.js | 12 +++++++++ src/locales/modules/engine.js | 4 ++- tests/hermes-config-page-ui.test.js | 2 ++ tests/hermes-display-config.test.js | 16 +++++++++++ 6 files changed, 85 insertions(+), 1 deletion(-) diff --git a/scripts/dev-api.js b/scripts/dev-api.js index a613e55..cf0b261 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -3806,6 +3806,9 @@ export function buildHermesDisplayConfigValues(config = {}) { const display = root.display && typeof root.display === 'object' && !Array.isArray(root.display) ? root.display : {} + const dashboard = root.dashboard && typeof root.dashboard === 'object' && !Array.isArray(root.dashboard) + ? root.dashboard + : {} const runtimeFooter = display.runtime_footer && typeof display.runtime_footer === 'object' && !Array.isArray(display.runtime_footer) ? display.runtime_footer : {} @@ -3822,6 +3825,8 @@ export function buildHermesDisplayConfigValues(config = {}) { displayRuntimeFooterEnabled: readHermesBool(runtimeFooter.enabled, false), displayRuntimeFooterFields: normalizeHermesRuntimeFooterFields(runtimeFooter.fields, false).join('\n'), displayFileMutationVerifier: readHermesBool(display.file_mutation_verifier, true), + displayShowCost: readHermesBool(display.show_cost, false), + dashboardShowTokenAnalytics: readHermesBool(dashboard.show_token_analytics, false), displayLanguage: normalizeHermesDisplayLanguage(display.language, false), displayResumeDisplay: normalizeHermesDisplayResume(display.resume_display, false), displayBusyInputMode: normalizeHermesDisplayBusyInputMode(display.busy_input_mode, false), @@ -3857,6 +3862,7 @@ export function mergeHermesDisplayConfig(config = {}, form = {}) { runtimeFooter.fields = normalizeHermesRuntimeFooterFields(Object.hasOwn(form, 'displayRuntimeFooterFields') ? form.displayRuntimeFooterFields : currentValues.displayRuntimeFooterFields, true) display.runtime_footer = runtimeFooter display.file_mutation_verifier = formHermesBool(form, 'displayFileMutationVerifier', currentValues.displayFileMutationVerifier) + display.show_cost = formHermesBool(form, 'displayShowCost', currentValues.displayShowCost) display.language = normalizeHermesDisplayLanguage(Object.hasOwn(form, 'displayLanguage') ? form.displayLanguage : currentValues.displayLanguage, true) display.resume_display = normalizeHermesDisplayResume(Object.hasOwn(form, 'displayResumeDisplay') ? form.displayResumeDisplay : currentValues.displayResumeDisplay, true) display.busy_input_mode = normalizeHermesDisplayBusyInputMode(Object.hasOwn(form, 'displayBusyInputMode') ? form.displayBusyInputMode : currentValues.displayBusyInputMode, true) @@ -3867,6 +3873,11 @@ export function mergeHermesDisplayConfig(config = {}, form = {}) { display.persistent_output = formHermesBool(form, 'displayPersistentOutput', currentValues.displayPersistentOutput) display.persistent_output_max_lines = parseHermesInteger(Object.hasOwn(form, 'displayPersistentOutputMaxLines') ? form.displayPersistentOutputMaxLines : currentValues.displayPersistentOutputMaxLines, 'display.persistent_output_max_lines', 200, 0, 100000, true) next.display = display + const dashboard = next.dashboard && typeof next.dashboard === 'object' && !Array.isArray(next.dashboard) + ? mergeConfigsPreservingFields(next.dashboard, {}) + : {} + dashboard.show_token_analytics = formHermesBool(form, 'dashboardShowTokenAnalytics', currentValues.dashboardShowTokenAnalytics) + next.dashboard = dashboard return next } diff --git a/src-tauri/src/commands/hermes.rs b/src-tauri/src/commands/hermes.rs index a7cbd55..994c142 100644 --- a/src-tauri/src/commands/hermes.rs +++ b/src-tauri/src/commands/hermes.rs @@ -6515,6 +6515,7 @@ fn normalize_hermes_runtime_footer_fields( fn build_hermes_display_config_values(config: &serde_yaml::Value) -> Value { let root = config.as_mapping(); let display = root.and_then(|map| yaml_get_mapping(map, "display")); + let dashboard = root.and_then(|map| yaml_get_mapping(map, "dashboard")); let runtime_footer = display.and_then(|map| yaml_get_mapping(map, "runtime_footer")); let runtime_footer_fields = normalize_hermes_runtime_footer_fields( runtime_footer.and_then(|map| yaml_get(map, "fields")), @@ -6553,6 +6554,8 @@ fn build_hermes_display_config_values(config: &serde_yaml::Value) -> Value { "displayRuntimeFooterEnabled": runtime_footer.and_then(|map| yaml_bool_field(map, "enabled")).unwrap_or(false), "displayRuntimeFooterFields": runtime_footer_fields.join("\n"), "displayFileMutationVerifier": display.and_then(|map| yaml_bool_field(map, "file_mutation_verifier")).unwrap_or(true), + "displayShowCost": display.and_then(|map| yaml_bool_field(map, "show_cost")).unwrap_or(false), + "dashboardShowTokenAnalytics": dashboard.and_then(|map| yaml_bool_field(map, "show_token_analytics")).unwrap_or(false), "displayLanguage": normalize_hermes_display_language( display.and_then(|map| yaml_string_field(map, "language")), false, @@ -6707,6 +6710,13 @@ fn merge_hermes_display_config(config: &mut serde_yaml::Value, form: &Value) -> }), ), ); + display.insert( + yaml_key("show_cost"), + serde_yaml::Value::Bool( + form_bool(form, "displayShowCost") + .unwrap_or_else(|| current["displayShowCost"].as_bool().unwrap_or(false)), + ), + ); display.insert( yaml_key("language"), serde_yaml::Value::String(normalize_hermes_display_language( @@ -6797,6 +6807,17 @@ fn merge_hermes_display_config(config: &mut serde_yaml::Value, form: &Value) -> .collect(), ), ); + let dashboard = yaml_child_object(ensure_yaml_object(config)?, "dashboard")?; + dashboard.insert( + yaml_key("show_token_analytics"), + serde_yaml::Value::Bool( + form_bool(form, "dashboardShowTokenAnalytics").unwrap_or_else(|| { + current["dashboardShowTokenAnalytics"] + .as_bool() + .unwrap_or(false) + }), + ), + ); Ok(()) } @@ -22263,6 +22284,8 @@ mod hermes_display_config_tests { "model\ncontext_pct\ncwd" ); assert_eq!(values["displayFileMutationVerifier"], true); + assert_eq!(values["displayShowCost"], false); + assert_eq!(values["dashboardShowTokenAnalytics"], false); assert_eq!(values["displayLanguage"], "en"); assert_eq!(values["displayResumeDisplay"], "full"); assert_eq!(values["displayBusyInputMode"], "interrupt"); @@ -22295,6 +22318,7 @@ display: - duration - cost file_mutation_verifier: false + show_cost: true language: ZH resume_display: minimal busy_input_mode: QUEUE @@ -22304,6 +22328,8 @@ display: bell_on_complete: true persistent_output: false persistent_output_max_lines: 80 +dashboard: + show_token_analytics: true "#, ) .unwrap(); @@ -22323,6 +22349,8 @@ display: "model\nduration\ncost" ); assert_eq!(values["displayFileMutationVerifier"], false); + assert_eq!(values["displayShowCost"], true); + assert_eq!(values["dashboardShowTokenAnalytics"], true); assert_eq!(values["displayLanguage"], "zh"); assert_eq!(values["displayResumeDisplay"], "minimal"); assert_eq!(values["displayBusyInputMode"], "queue"); @@ -22348,6 +22376,8 @@ display: platforms: telegram: tool_progress: new +dashboard: + custom_flag: keep-dashboard memory: memory_enabled: true "#, @@ -22369,6 +22399,8 @@ memory: "displayRuntimeFooterEnabled": true, "displayRuntimeFooterFields": "model\ncontext_pct\nduration", "displayFileMutationVerifier": true, + "displayShowCost": true, + "dashboardShowTokenAnalytics": true, "displayLanguage": "zh-hant", "displayResumeDisplay": "minimal", "displayBusyInputMode": "steer", @@ -22384,6 +22416,14 @@ memory: assert_eq!(config["model"]["provider"].as_str(), Some("anthropic")); assert_eq!(config["memory"]["memory_enabled"].as_bool(), Some(true)); + assert_eq!( + config["dashboard"]["custom_flag"].as_str(), + Some("keep-dashboard") + ); + assert_eq!( + config["dashboard"]["show_token_analytics"].as_bool(), + Some(true) + ); assert_eq!(config["display"]["compact"].as_bool(), Some(true)); assert_eq!(config["display"]["skin"].as_str(), Some("slate")); assert_eq!(config["display"]["tool_prefix"].as_str(), Some("│")); @@ -22424,6 +22464,7 @@ memory: config["display"]["file_mutation_verifier"].as_bool(), Some(true) ); + assert_eq!(config["display"]["show_cost"].as_bool(), Some(true)); assert_eq!(config["display"]["language"].as_str(), Some("zh-hant")); assert_eq!( config["display"]["resume_display"].as_str(), diff --git a/src/engines/hermes/pages/config.js b/src/engines/hermes/pages/config.js index 482723d..eaac9ac 100644 --- a/src/engines/hermes/pages/config.js +++ b/src/engines/hermes/pages/config.js @@ -200,6 +200,8 @@ const DISPLAY_DEFAULTS = { displayRuntimeFooterEnabled: false, displayRuntimeFooterFields: 'model\ncontext_pct\ncwd', displayFileMutationVerifier: true, + displayShowCost: false, + dashboardShowTokenAnalytics: false, displayLanguage: 'en', displayResumeDisplay: 'full', displayBusyInputMode: 'interrupt', @@ -1815,6 +1817,14 @@ export function render() { ${t('engine.hermesDisplayConfigFileMutationVerifier')} + +