diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor index 51dd8f7d..d1222bff 100644 --- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor +++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor @@ -1,5 +1,6 @@ @attribute [Route(Routes.ASSISTANT_DOCUMENT_ANALYSIS)] @inherits AssistantBaseCore +@using AIStudio.Settings @using AIStudio.Settings.DataModel @@ -108,7 +109,7 @@ else - + diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs index f58e6619..419d4c9e 100644 --- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs +++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs @@ -176,7 +176,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore loadedDocumentPaths = []; private readonly List> availableLLMProviders = new(); @@ -450,14 +450,21 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore x.Id == this.selectedPolicy.PreselectedProfile); + var policyProfile = this.SettingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == policyProfilePreselection.SpecificProfileId); if (policyProfile is not null) return policyProfile; } - return this.SettingsManager.GetPreselectedProfile(this.Component); + return this.SettingsManager.GetAppPreselectedProfile(); } private async Task PolicyMinimumConfidenceWasChangedAsync(ConfidenceLevel level) @@ -479,11 +486,11 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore - + @@ -42,4 +42,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor index ec6776f0..2902c7a6 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor @@ -27,7 +27,7 @@ { } - + @@ -38,4 +38,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor index 111b6a93..aa519781 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor @@ -18,7 +18,7 @@ - + @@ -33,4 +33,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor index dde19c0c..818dda3c 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor @@ -20,10 +20,10 @@ } - + Close - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor index cb4e20ac..831cbce1 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor @@ -13,7 +13,7 @@ - + @T("Most ERI server options can be customized and saved directly in the ERI server assistant. For this, the ERI server assistant has an auto-save function.") @@ -25,4 +25,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor index 42cb70d4..d15662e7 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor @@ -15,7 +15,7 @@ - + @@ -23,4 +23,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor index 626f421d..827ad220 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor @@ -16,7 +16,7 @@ { } - + @@ -26,4 +26,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor index 6f31f266..904635d2 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor @@ -21,7 +21,7 @@ - + @@ -29,4 +29,4 @@ @T("Close") - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs b/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs index 7aa2441e..f31db876 100644 --- a/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs +++ b/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs @@ -204,6 +204,15 @@ public static class ConfigurationSelectDataFactory yield return new(profile.GetSafeName(), profile.Id); } + public static IEnumerable> GetComponentProfilesData(IEnumerable profiles) + { + yield return new(TB("Use app default profile"), ProfilePreselection.AppDefault); + yield return new(Profile.NO_PROFILE.GetSafeName(), ProfilePreselection.NoProfile); + + foreach (var profile in profiles) + yield return new(profile.GetSafeName(), ProfilePreselection.Specific(profile.Id)); + } + public static IEnumerable> GetTranscriptionProvidersData(IEnumerable transcriptionProviders) { foreach (var provider in transcriptionProviders) diff --git a/app/MindWork AI Studio/Settings/ProfilePreselection.cs b/app/MindWork AI Studio/Settings/ProfilePreselection.cs new file mode 100644 index 00000000..ba4b55e1 --- /dev/null +++ b/app/MindWork AI Studio/Settings/ProfilePreselection.cs @@ -0,0 +1,55 @@ +namespace AIStudio.Settings; + +public readonly record struct ProfilePreselection +{ + public ProfilePreselectionMode Mode { get; } + + public string SpecificProfileId { get; } + + public bool UseAppDefault => this.Mode == ProfilePreselectionMode.USE_APP_DEFAULT; + + public bool DoNotPreselectProfile => this.Mode == ProfilePreselectionMode.USE_NO_PROFILE; + + public bool UseSpecificProfile => this.Mode == ProfilePreselectionMode.USE_SPECIFIC_PROFILE; + + public static ProfilePreselection AppDefault => new(ProfilePreselectionMode.USE_APP_DEFAULT, string.Empty); + + public static ProfilePreselection NoProfile => new(ProfilePreselectionMode.USE_NO_PROFILE, Profile.NO_PROFILE.Id); + + private ProfilePreselection(ProfilePreselectionMode mode, string specificProfileId) + { + this.Mode = mode; + this.SpecificProfileId = specificProfileId; + } + + public static ProfilePreselection Specific(string profileId) + { + if (string.IsNullOrWhiteSpace(profileId)) + throw new ArgumentException("A specific profile preselection requires a profile ID.", nameof(profileId)); + + if (profileId.Equals(Profile.NO_PROFILE.Id, StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException("Use NoProfile for the NO_PROFILE selection.", nameof(profileId)); + + return new(ProfilePreselectionMode.USE_SPECIFIC_PROFILE, profileId); + } + + public static ProfilePreselection FromStoredValue(string? storedValue) + { + if (string.IsNullOrWhiteSpace(storedValue)) + return AppDefault; + + if (storedValue.Equals(Profile.NO_PROFILE.Id, StringComparison.OrdinalIgnoreCase)) + return NoProfile; + + return new(ProfilePreselectionMode.USE_SPECIFIC_PROFILE, storedValue); + } + + public static implicit operator string(ProfilePreselection preselection) => preselection.Mode switch + { + ProfilePreselectionMode.USE_APP_DEFAULT => string.Empty, + ProfilePreselectionMode.USE_NO_PROFILE => Profile.NO_PROFILE.Id, + ProfilePreselectionMode.USE_SPECIFIC_PROFILE => preselection.SpecificProfileId, + + _ => string.Empty, + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/ProfilePreselectionMode.cs b/app/MindWork AI Studio/Settings/ProfilePreselectionMode.cs new file mode 100644 index 00000000..8addad93 --- /dev/null +++ b/app/MindWork AI Studio/Settings/ProfilePreselectionMode.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Settings; + +public enum ProfilePreselectionMode +{ + USE_APP_DEFAULT, + USE_NO_PROFILE, + USE_SPECIFIC_PROFILE, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 12634ad6..911d827f 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -294,12 +294,32 @@ public sealed class SettingsManager public Profile GetPreselectedProfile(Tools.Components component) { - var preselection = component.PreselectedProfile(this); - if (preselection != Profile.NO_PROFILE) - return preselection; - - preselection = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(this.ConfigurationData.App.PreselectedProfile, StringComparison.OrdinalIgnoreCase)); - return preselection ?? Profile.NO_PROFILE; + var preselection = component.GetProfilePreselection(this); + if (preselection.DoNotPreselectProfile) + return Profile.NO_PROFILE; + + if (preselection.UseSpecificProfile) + { + var componentProfile = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(preselection.SpecificProfileId, StringComparison.OrdinalIgnoreCase)); + return componentProfile ?? Profile.NO_PROFILE; + } + + var appPreselection = ProfilePreselection.FromStoredValue(this.ConfigurationData.App.PreselectedProfile); + if (appPreselection.DoNotPreselectProfile || !appPreselection.UseSpecificProfile) + return Profile.NO_PROFILE; + + var appProfile = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(appPreselection.SpecificProfileId, StringComparison.OrdinalIgnoreCase)); + return appProfile ?? Profile.NO_PROFILE; + } + + public Profile GetAppPreselectedProfile() + { + var appPreselection = ProfilePreselection.FromStoredValue(this.ConfigurationData.App.PreselectedProfile); + if (appPreselection.DoNotPreselectProfile || !appPreselection.UseSpecificProfile) + return Profile.NO_PROFILE; + + var appProfile = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(appPreselection.SpecificProfileId, StringComparison.OrdinalIgnoreCase)); + return appProfile ?? Profile.NO_PROFILE; } public ChatTemplate GetPreselectedChatTemplate(Tools.Components component) diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index 4e346d82..a5ff822d 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -133,24 +133,28 @@ public static class ComponentsExtensions return preselectedProvider ?? Settings.Provider.NONE; } - public static Profile PreselectedProfile(this Components component, SettingsManager settingsManager) => component switch + public static ProfilePreselection GetProfilePreselection(this Components component, SettingsManager settingsManager) { - Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Agenda.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Coding.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.EMail.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.LegalCheck.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.MyTasks.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.ERI.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, + var storedValue = component switch + { + Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Agenda.PreselectedProfile : string.Empty, + Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Coding.PreselectedProfile : string.Empty, + Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.EMail.PreselectedProfile : string.Empty, + Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.LegalCheck.PreselectedProfile : string.Empty, + Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.MyTasks.PreselectedProfile : string.Empty, + Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile : string.Empty, + Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.ERI.PreselectedProfile : string.Empty, + Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Chat.PreselectedProfile : string.Empty, - // The Document Analysis Assistant does not have a preselected profile at the component level. - // The profile is selected per policy instead. We do this inside the Document Analysis Assistant component: - Components.DOCUMENT_ANALYSIS_ASSISTANT => Profile.NO_PROFILE, - - Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, - - _ => Profile.NO_PROFILE, - }; + // The Document Analysis Assistant does not have a preselected profile at the component level. + // The profile is selected per policy instead. We do this inside the Document Analysis Assistant component: + Components.DOCUMENT_ANALYSIS_ASSISTANT => Profile.NO_PROFILE.Id, + + _ => string.Empty, + }; + + return ProfilePreselection.FromStoredValue(storedValue); + } public static ChatTemplate PreselectedChatTemplate(this Components component, SettingsManager settingsManager) => component switch { diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index 9585e6e4..8346f0d6 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -2,6 +2,7 @@ - Added support for the new Qwen 3.5 model family. - Added a reminder in chats and assistants that LLMs can make mistakes, helping you double-check important information more easily. - Added the ability to format your user prompt in the chat using icons instead of typing Markdown directly. +- Added explicit profile behavior choices for assistants and the chat. You can now decide whether AI Studio should use the app default profile, no profile, or a specific profile. - Improved the performance by caching the OS language detection and requesting the user language only once per app start. - Improved the chat performance by reducing unnecessary UI updates, making chats smoother and more responsive, especially in longer conversations. - Improved the workspace loading experience: when opening the chat for the first time, your workspaces now appear faster and load step by step in the background, with placeholder rows so the app feels responsive right away.