From a41c2e3350d5145459c3bca04a8fbd91e9ec806d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 9 Jun 2026 10:29:24 +0200 Subject: [PATCH] Improved voice recording shortcut labels --- .../Components/ConfigurationShortcut.razor.cs | 36 ++++++++++++- .../Settings/SettingsPanelApp.razor | 2 +- .../Settings/SettingsPanelApp.razor.cs | 6 +++ .../Dialogs/ShortcutDialog.razor.cs | 50 ++++++++++++++++++- .../Dialogs/ShortcutDialogResult.cs | 3 ++ .../Settings/DataModel/DataApp.cs | 10 ++++ .../wwwroot/changelog/v26.6.1.md | 1 + 7 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 app/MindWork AI Studio/Dialogs/ShortcutDialogResult.cs diff --git a/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs b/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs index aaa600b7..8afdc4b6 100644 --- a/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs +++ b/app/MindWork AI Studio/Components/ConfigurationShortcut.razor.cs @@ -30,6 +30,24 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore [Parameter] public Action ShortcutUpdate { get; set; } = _ => { }; + /// + /// The optional user-facing shortcut label. + /// + [Parameter] + public Func ShortcutDisplayName { get; set; } = () => string.Empty; + + /// + /// The canonical shortcut value the optional user-facing label belongs to. + /// + [Parameter] + public Func ShortcutDisplaySource { get; set; } = () => string.Empty; + + /// + /// An action which is called when the user-facing shortcut label was changed. + /// + [Parameter] + public Action ShortcutDisplayUpdate { get; set; } = (_, _) => { }; + /// /// The name/identifier of the shortcut (used for conflict detection and registration). /// @@ -64,6 +82,14 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore if (string.IsNullOrWhiteSpace(shortcut)) return string.Empty; + var shortcutDisplayName = this.ShortcutDisplayName(); + var shortcutDisplaySource = this.ShortcutDisplaySource(); + if (!string.IsNullOrWhiteSpace(shortcutDisplayName) + && string.Equals(shortcutDisplaySource, shortcut, StringComparison.Ordinal)) + { + return shortcutDisplayName; + } + // Convert internal format to display format: return shortcut .Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl") @@ -93,9 +119,17 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore if (dialogResult is null || dialogResult.Canceled) return; - if (dialogResult.Data is string newShortcut) + if (dialogResult.Data is ShortcutDialogResult shortcutResult) + { + this.ShortcutUpdate(shortcutResult.Shortcut); + this.ShortcutDisplayUpdate(shortcutResult.DisplayName, shortcutResult.DisplaySource); + await this.SettingsManager.StoreSettings(); + await this.InformAboutChange(); + } + else if (dialogResult.Data is string newShortcut) { this.ShortcutUpdate(newShortcut); + this.ShortcutDisplayUpdate(string.Empty, string.Empty); await this.SettingsManager.StoreSettings(); await this.InformAboutChange(); } diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor index 33eb7d28..4fc867ea 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor @@ -37,7 +37,7 @@ @if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager)) { - + } @if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs index 9922291b..63350644 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs @@ -93,6 +93,12 @@ public partial class SettingsPanelApp : SettingsPanelBase this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedFeatures; } + private void UpdateShortcutVoiceRecordingDisplay(string displayName, string displaySource) + { + this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecordingDisplayName = displayName; + this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecordingDisplaySource = displaySource; + } + private async Task UpdateLangBehaviour(LangBehavior behavior) { this.SettingsManager.ConfigurationData.App.LanguageBehavior = behavior; diff --git a/app/MindWork AI Studio/Dialogs/ShortcutDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ShortcutDialog.razor.cs index 9809b818..b7872203 100644 --- a/app/MindWork AI Studio/Dialogs/ShortcutDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ShortcutDialog.razor.cs @@ -34,6 +34,7 @@ public partial class ShortcutDialog : MSGComponentBase private string currentShortcut = string.Empty; private string originalShortcut = string.Empty; + private string currentDisplayName = string.Empty; private string validationMessage = string.Empty; private Severity validationSeverity = Severity.Info; private bool hasValidationError; @@ -115,6 +116,7 @@ public partial class ShortcutDialog : MSGComponentBase { this.UpdateModifiers(e); this.currentKey = null; + this.currentDisplayName = string.Empty; this.UpdateShortcutString(); return; } @@ -123,10 +125,12 @@ public partial class ShortcutDialog : MSGComponentBase // Get the key: this.currentKey = TranslateKeyCode(e.Code); + this.currentDisplayName = this.BuildDisplayShortcut(e.Key); // Validate: must have at least one modifier + a key if (!this.hasCtrl && !this.hasShift && !this.hasAlt && !this.hasMeta) { + this.currentDisplayName = string.Empty; this.validationMessage = T("Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd)."); this.validationSeverity = Severity.Warning; this.hasValidationError = true; @@ -216,6 +220,9 @@ public partial class ShortcutDialog : MSGComponentBase private string GetDisplayShortcut() { + if (!string.IsNullOrWhiteSpace(this.currentDisplayName)) + return this.currentDisplayName; + // Convert internal format to display format: return this.currentShortcut .Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl") @@ -225,6 +232,7 @@ public partial class ShortcutDialog : MSGComponentBase private void ClearShortcut() { this.currentShortcut = string.Empty; + this.currentDisplayName = string.Empty; this.currentKey = null; this.hasCtrl = false; this.hasShift = false; @@ -237,7 +245,17 @@ public partial class ShortcutDialog : MSGComponentBase private void Cancel() => this.MudDialog.Cancel(); - private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.currentShortcut)); + private void Confirm() + { + var displaySource = string.IsNullOrWhiteSpace(this.currentDisplayName) + ? string.Empty + : this.currentShortcut; + + this.MudDialog.Close(DialogResult.Ok(new ShortcutDialogResult( + this.currentShortcut, + this.currentDisplayName, + displaySource))); + } /// /// Checks if the key code represents a modifier key. @@ -377,6 +395,36 @@ public partial class ShortcutDialog : MSGComponentBase _ => code, }; + private string BuildDisplayShortcut(string? key) + { + var displayKey = GetDisplayKey(key); + if (string.IsNullOrWhiteSpace(displayKey)) + return string.Empty; + + var parts = new List(); + + if (this.hasCtrl) + parts.Add(OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl"); + + if (this.hasShift) + parts.Add("Shift"); + + if (this.hasAlt) + parts.Add("Alt"); + + parts.Add(displayKey); + return string.Join("+", parts); + } + + private static string GetDisplayKey(string? key) => key switch + { + null or "" => string.Empty, + " " => "Space", + "Control" or "Shift" or "Alt" or "Meta" => string.Empty, + _ when key.Length == 1 && key[0] >= 'a' && key[0] <= 'z' => key.ToUpperInvariant(), + _ => key, + }; + private void HandleBlur() { // Re-focus the input field to keep capturing keys: diff --git a/app/MindWork AI Studio/Dialogs/ShortcutDialogResult.cs b/app/MindWork AI Studio/Dialogs/ShortcutDialogResult.cs new file mode 100644 index 00000000..c9b424c7 --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/ShortcutDialogResult.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Dialogs; + +public readonly record struct ShortcutDialogResult(string Shortcut, string DisplayName, string DisplaySource); \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs index 42bfdfac..c9352514 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs @@ -99,6 +99,16 @@ public sealed class DataApp(Expression>? configSelection = n /// public string ShortcutVoiceRecording { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShortcutVoiceRecording, string.Empty); + /// + /// The user-facing label for the voice recording shortcut, based on the user's keyboard layout. + /// + public string ShortcutVoiceRecordingDisplayName { get; set; } = string.Empty; + + /// + /// The canonical voice recording shortcut value this display label belongs to. + /// + public string ShortcutVoiceRecordingDisplaySource { get; set; } = string.Empty; + /// /// The HTTP timeout in seconds for external HTTP clients. /// diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md index 9742e02f..d3fd4a57 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md @@ -8,6 +8,7 @@ - Improved workspaces by highlighting the currently open chat in the workspace view. - Improved workspaces by adding a shortcut to start a new chat directly from each workspace row. - Improved workspaces by allowing new workspaces to be created while moving a chat. +- Improved voice recording shortcut labels so they match the user's keyboard layout after being configured. - Improved the enterprise configuration details on the information page by showing where each configuration comes from and which configuration slot was used. - Fixed workspace creation and renaming to prevent new workspaces from using an existing name. - Fixed an issue on Microsoft Windows where reading attached documents could briefly open a terminal window while processing files.