diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index a3939cf4..4d6fde09 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -184,7 +184,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher this.formChangeTimer.Elapsed += (_, _) => { this.formChangeTimer.Stop(); - this.OnFormChange().Observe($"{nameof(AssistantBase)}: handling a form change"); + this.InvokeAsync(this.OnFormChange).Observe($"{nameof(AssistantBase)}: handling a form change"); }; this.MightPreselectValues(); diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor index 99738648..b43a5cb7 100644 --- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor +++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor @@ -104,13 +104,13 @@ else @T("Note: This setting only takes effect when this policy is exported and distributed via a configuration plugin to other users. When enabled, users will only see the document selection interface and cannot view or modify the policy details. This setting does NOT affect your local view - you will always see the full policy definition for policies you create.") - + - + - + diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs index e1067b43..b9adc668 100644 --- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs +++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs @@ -180,6 +180,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore private bool documentSelectionExpanded; private string policyName = string.Empty; + private bool policyNameWasEdited; private string policyDescription = string.Empty; private string policyAnalysisRules = string.Empty; private string policyOutputRules = string.Empty; @@ -477,6 +496,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore /// Takes over the tools this policy permits. /// - private async Task PolicyAllowedToolsWasChangedAsync(HashSet allowedToolIds) + private void PolicyAllowedToolsWasChanged(HashSet allowedToolIds) { this.policyAllowedToolIds = allowedToolIds; - await this.AutoSave(); + if (this.selectedPolicy is not null) + this.selectedPolicy.AllowedToolIds = [..allowedToolIds]; } - private async Task PolicyMinimumConfidenceWasChangedAsync(ConfidenceLevel level) + private void PolicyMinimumConfidenceWasChanged(ConfidenceLevel level) { this.policyMinimumProviderConfidence = level; - await this.AutoSave(); - + if (this.selectedPolicy is not null) + this.selectedPolicy.MinimumProviderConfidence = level; + this.ApplyPolicyPreselection(); } @@ -605,14 +625,13 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore - @foreach (var providerItem in this.GetAvailableProviderSelectionItems()) + @foreach (var providerItem in availableProviderItems) { @@ -20,4 +23,10 @@ } - \ No newline at end of file + +@if (availableProviderItems.Count is 0) +{ + + @T("No LLM providers meet the confidence requirements. Configure an eligible provider in the app settings.") + +} diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 3f613f28..aeb6f31b 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -33,6 +33,7 @@ public sealed class SettingsManager private readonly ILogger logger; private readonly RustService rustService; + private readonly SemaphoreSlim settingsWriteSemaphore = new(1, 1); /// /// The settings manager. @@ -294,41 +295,56 @@ public sealed class SettingsManager /// public async Task StoreSettings() { - if(!this.IsSetUp) + await this.settingsWriteSemaphore.WaitAsync(); + try { - this.logger.LogWarning("Cannot store settings, because the configuration is not set up yet."); - return; - } + if(!this.IsSetUp) + { + this.logger.LogWarning("Cannot store settings, because the configuration is not set up yet."); + return; + } - if(this.SettingsWriteBlocked) + if(this.SettingsWriteBlocked) + { + this.logger.LogWarning($"Cannot store settings, because settings writes are blocked. Reason: '{this.SettingsWriteBlockReason}'."); + return; + } + + var settingsJson = JsonSerializer.Serialize(this.ConfigurationData, JSON_OPTIONS); + var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME); + await this.StoreSettingsSnapshot(settingsJson, settingsPath); + await this.StoreCurrentVersionBackup(this.ConfigurationData.Version, settingsJson); + } + finally { - this.logger.LogWarning($"Cannot store settings, because settings writes are blocked. Reason: '{this.SettingsWriteBlockReason}'."); - return; + this.settingsWriteSemaphore.Release(); } - - var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME); - await this.StoreSettingsSnapshot(this.ConfigurationData, settingsPath); - await this.StoreCurrentVersionBackup(this.ConfigurationData); } private static string GetBackupSettingsFilename(Version version) => $"settings.{version.ToString().ToLowerInvariant()}.json"; private static string GetBackupSettingsPath(Version version) => Path.Combine(ConfigDirectory!, GetBackupSettingsFilename(version)); - private async Task StoreCurrentVersionBackup(Data settingsData) + private Task StoreCurrentVersionBackup(Data settingsData) => + this.StoreCurrentVersionBackup(settingsData.Version, JsonSerializer.Serialize(settingsData, JSON_OPTIONS)); + + private async Task StoreCurrentVersionBackup(Version settingsVersion, string settingsJson) { - if(settingsData.Version != CURRENT_SETTINGS_VERSION) + if(settingsVersion != CURRENT_SETTINGS_VERSION) { - this.logger.LogWarning($"Skipping settings backup because the settings version '{settingsData.Version}' is not the current version '{CURRENT_SETTINGS_VERSION}'."); + this.logger.LogWarning($"Skipping settings backup because the settings version '{settingsVersion}' is not the current version '{CURRENT_SETTINGS_VERSION}'."); return; } var backupSettingsPath = GetBackupSettingsPath(CURRENT_SETTINGS_VERSION); - await this.StoreSettingsSnapshot(settingsData, backupSettingsPath); + await this.StoreSettingsSnapshot(settingsJson, backupSettingsPath); this.logger.LogInformation($"Stored the settings backup file '{backupSettingsPath}'."); } - private async Task StoreSettingsSnapshot(Data settingsData, string settingsPath) + private Task StoreSettingsSnapshot(Data settingsData, string settingsPath) => + this.StoreSettingsSnapshot(JsonSerializer.Serialize(settingsData, JSON_OPTIONS), settingsPath); + + private async Task StoreSettingsSnapshot(string settingsJson, string settingsPath) { if(!Directory.Exists(ConfigDirectory)) { @@ -336,8 +352,6 @@ public sealed class SettingsManager Directory.CreateDirectory(ConfigDirectory!); } - var settingsJson = JsonSerializer.Serialize(settingsData, JSON_OPTIONS); - // // We write the new settings next to the previous ones and replace them afterwards, so that // no crash can leave a half-written settings file behind. The temporary file has to live in @@ -349,7 +363,7 @@ public sealed class SettingsManager try { await File.WriteAllTextAsync(tempFile, settingsJson); - File.Move(tempFile, settingsPath, true); + await Task.Run(() => File.Move(tempFile, settingsPath, true)); } catch { diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md index 85a6cdc2..758568a9 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md @@ -26,6 +26,7 @@ - Improved the list of your attached files: every file now appears under the folder it came from, and each folder is named only once, no matter in which order you attached your files. - Changed how provider trust and provider confidence work together. Marking a provider as trustworthy in a configuration no longer also satisfies a required confidence level: one says who runs the provider, the other how confidential it is. Organizations raise a provider's level in their own confidence scheme instead. This applies beyond local data sources, for example, when a model reads a page from your intranet. - Fixed the abilities AI Studio assumed for many models. We checked the families against their documentation: some models gained image input, reasoning, or tool calling, others lost an ability they never had. +- Fixed the Document Analysis assistant becoming unresponsive when choosing an LLM provider. - Fixed model names that a provider writes in its own way not being recognized at all, such as the colon Ollama puts before the variant. Those models were treated as plain text models and lost every other ability. - Fixed a model resold under a plain name not getting the abilities it really has. - Fixed image and video generation models showing up among the chat models.