mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-12 03:53:48 +00:00
Merge branch 'main' into custom-tokenizer
This commit is contained in:
commit
e3cb7e9734
165 files changed
+4739
-1738
No files matched your search
@@ -13,6 +13,7 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (235, "v26.4.1, build 235 (2026-04-17 17:25 UTC)", "v26.4.1.md"),
|
||||
new (234, "v26.2.2, build 234 (2026-02-22 14:16 UTC)", "v26.2.2.md"),
|
||||
new (233, "v26.2.1, build 233 (2026-02-01 19:16 UTC)", "v26.2.1.md"),
|
||||
new (232, "v26.1.2, build 232 (2026-01-25 14:05 UTC)", "v26.1.2.md"),
|
||||
|
||||
@@ -70,6 +70,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
private string currentWorkspaceName = string.Empty;
|
||||
private Guid currentWorkspaceId = Guid.Empty;
|
||||
private Guid currentChatThreadId = Guid.Empty;
|
||||
private int workspaceHeaderSyncVersion;
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private HashSet<FileAttachment> chatDocumentPaths = [];
|
||||
private string tokenCount = "0";
|
||||
@@ -97,6 +98,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
|
||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
||||
|
||||
var deferredInput = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_CHAT_INPUT).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(deferredInput))
|
||||
this.userInput = deferredInput;
|
||||
|
||||
// Apply template's file attachments, if any:
|
||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
||||
@@ -213,12 +218,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// workspace name is loaded:
|
||||
//
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.currentChatThreadId = this.ChatThread.ChatId;
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
}
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
|
||||
// Select the correct provider:
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
@@ -235,10 +235,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await this.Workspaces.StoreChatAsync(this.ChatThread);
|
||||
else
|
||||
await WorkspaceBehaviour.StoreChatAsync(this.ChatThread);
|
||||
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
}
|
||||
|
||||
if (firstRender && this.mustLoadChat)
|
||||
@@ -251,9 +249,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread!.ChatId}' with title '{this.ChatThread.Name}' ({this.ChatThread.Blocks.Count} messages) was loaded successfully.");
|
||||
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
}
|
||||
else
|
||||
@@ -288,40 +285,59 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private async Task SyncWorkspaceHeaderWithChatThreadAsync()
|
||||
{
|
||||
if (this.ChatThread is null)
|
||||
var syncVersion = Interlocked.Increment(ref this.workspaceHeaderSyncVersion);
|
||||
var currentChatThread = this.ChatThread;
|
||||
if (currentChatThread is null)
|
||||
{
|
||||
if (this.currentChatThreadId != Guid.Empty || this.currentWorkspaceId != Guid.Empty || !string.IsNullOrWhiteSpace(this.currentWorkspaceName))
|
||||
{
|
||||
this.currentChatThreadId = Guid.Empty;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
}
|
||||
|
||||
this.ClearWorkspaceHeaderState();
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard: If ChatThread ID and WorkspaceId haven't changed, skip entirely.
|
||||
// Using ID-based comparison instead of name-based to correctly handle
|
||||
// temporary chats where the workspace name is always empty.
|
||||
if (this.currentChatThreadId == this.ChatThread.ChatId
|
||||
&& this.currentWorkspaceId == this.ChatThread.WorkspaceId)
|
||||
if (this.currentChatThreadId == currentChatThread.ChatId
|
||||
&& this.currentWorkspaceId == currentChatThread.WorkspaceId)
|
||||
return;
|
||||
|
||||
this.currentChatThreadId = this.ChatThread.ChatId;
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
var loadedWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(this.ChatThread.WorkspaceId);
|
||||
var chatThreadId = currentChatThread.ChatId;
|
||||
var workspaceId = currentChatThread.WorkspaceId;
|
||||
var loadedWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(workspaceId);
|
||||
|
||||
// Only notify the parent when the name actually changed to prevent
|
||||
// an infinite render loop: WorkspaceName → UpdateWorkspaceName →
|
||||
// StateHasChanged → re-render → OnParametersSetAsync → WorkspaceName → ...
|
||||
if (this.currentWorkspaceName != loadedWorkspaceName)
|
||||
{
|
||||
this.currentWorkspaceName = loadedWorkspaceName;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
}
|
||||
// A newer sync request was started while awaiting IO. Ignore stale results.
|
||||
if (syncVersion != this.workspaceHeaderSyncVersion)
|
||||
return;
|
||||
|
||||
// The active chat changed while loading the workspace name.
|
||||
if (this.ChatThread is null
|
||||
|| this.ChatThread.ChatId != chatThreadId
|
||||
|| this.ChatThread.WorkspaceId != workspaceId)
|
||||
return;
|
||||
|
||||
this.currentChatThreadId = chatThreadId;
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
this.PublishWorkspaceNameIfChanged(loadedWorkspaceName);
|
||||
}
|
||||
|
||||
|
||||
private void ClearWorkspaceHeaderState()
|
||||
{
|
||||
this.currentChatThreadId = Guid.Empty;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.PublishWorkspaceNameIfChanged(string.Empty);
|
||||
}
|
||||
|
||||
private void PublishWorkspaceNameIfChanged(string workspaceName)
|
||||
{
|
||||
// Only notify the parent when the name actually changed to prevent
|
||||
// an infinite render loop: WorkspaceName -> UpdateWorkspaceName ->
|
||||
// StateHasChanged -> re-render -> OnParametersSetAsync -> WorkspaceName -> ...
|
||||
if (this.currentWorkspaceName == workspaceName)
|
||||
return;
|
||||
|
||||
this.currentWorkspaceName = workspaceName;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
}
|
||||
|
||||
private bool IsProviderSelected => this.Provider.UsedLLMProvider != LLMProviders.NONE;
|
||||
|
||||
private string ProviderPlaceholder => this.IsProviderSelected ? T("Type your input here...") : T("Select a provider first");
|
||||
@@ -747,10 +763,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// to reset the chat thread:
|
||||
//
|
||||
this.ChatThread = null;
|
||||
this.currentChatThreadId = Guid.Empty;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.ClearWorkspaceHeaderState();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -826,10 +839,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
this.ChatThread!.WorkspaceId = workspaceId;
|
||||
await this.SaveThread();
|
||||
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
}
|
||||
|
||||
private async Task LoadedChatChanged()
|
||||
@@ -840,18 +851,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.currentWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
this.currentWorkspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(this.ChatThread.WorkspaceId);
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.currentChatThreadId = this.ChatThread.ChatId;
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions, this.ChatThread.AISelectedDataSources);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentChatThreadId = Guid.Empty;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.ClearWorkspaceHeaderState();
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
}
|
||||
|
||||
@@ -870,11 +875,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.isStreaming = false;
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
this.currentChatThreadId = Guid.Empty;
|
||||
this.currentWorkspaceId = Guid.Empty;
|
||||
|
||||
this.currentWorkspaceName = string.Empty;
|
||||
this.WorkspaceName(this.currentWorkspaceName);
|
||||
this.ClearWorkspaceHeaderState();
|
||||
|
||||
this.ChatThread = null;
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
@@ -887,22 +888,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
var chatProfile = this.ChatThread?.SelectedProfile;
|
||||
var chatChatTemplate = this.ChatThread?.SelectedChatTemplate;
|
||||
|
||||
switch (this.SettingsManager.ConfigurationData.Chat.LoadingProviderBehavior)
|
||||
{
|
||||
default:
|
||||
case LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE:
|
||||
this.Provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT, chatProvider);
|
||||
break;
|
||||
|
||||
case LoadingChatProviderBehavior.ALWAYS_USE_DEFAULT_CHAT_PROVIDER:
|
||||
this.Provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
break;
|
||||
|
||||
case LoadingChatProviderBehavior.ALWAYS_USE_LATEST_CHAT_PROVIDER:
|
||||
if(this.Provider == AIStudio.Settings.Provider.NONE)
|
||||
this.Provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
break;
|
||||
}
|
||||
this.Provider = this.SettingsManager.GetChatProviderForLoadedChat(chatProvider);
|
||||
|
||||
await this.ProviderChanged.InvokeAsync(this.Provider);
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.body2">
|
||||
@T("Version"): @this.Info.VersionText
|
||||
</MudText>
|
||||
|
||||
@if (this.ShowAcceptanceMetadata)
|
||||
{
|
||||
@if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.MISSING)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
|
||||
@T("This mandatory info has not been accepted yet.")
|
||||
</MudAlert>
|
||||
}
|
||||
else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.VERSION_CHANGED)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
|
||||
@T("A new version of the terms is available. Please review it again.")
|
||||
<br />
|
||||
@T("Last accepted version"): @this.Acceptance!.AcceptedVersion
|
||||
<br />
|
||||
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
|
||||
</MudAlert>
|
||||
}
|
||||
else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.CONTENT_CHANGED)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
|
||||
@T("Please review this text again. The content was changed.")
|
||||
<br />
|
||||
@T("Last accepted version"): @this.Acceptance!.AcceptedVersion
|
||||
<br />
|
||||
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Variant="Variant.Outlined" Dense="@true">
|
||||
@T("Accepted version"): @this.Acceptance!.AcceptedVersion
|
||||
<br />
|
||||
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
|
||||
</MudAlert>
|
||||
}
|
||||
}
|
||||
|
||||
<MudJustifiedMarkdown Value="@this.Info.Markdown" />
|
||||
</MudStack>
|
||||
@@ -0,0 +1,42 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class MandatoryInfoDisplay
|
||||
{
|
||||
private enum MandatoryInfoAcceptanceStatus
|
||||
{
|
||||
MISSING,
|
||||
VERSION_CHANGED,
|
||||
CONTENT_CHANGED,
|
||||
ACCEPTED,
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public DataMandatoryInfo Info { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public DataMandatoryInfoAcceptance? Acceptance { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool ShowAcceptanceMetadata { get; set; }
|
||||
|
||||
private MandatoryInfoAcceptanceStatus AcceptanceStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Acceptance is null)
|
||||
return MandatoryInfoAcceptanceStatus.MISSING;
|
||||
|
||||
if (!string.Equals(this.Acceptance.AcceptedVersion, this.Info.VersionText, StringComparison.Ordinal))
|
||||
return MandatoryInfoAcceptanceStatus.VERSION_CHANGED;
|
||||
|
||||
if (!string.Equals(this.Acceptance.AcceptedHash, this.Info.AcceptanceHash, StringComparison.Ordinal))
|
||||
return MandatoryInfoAcceptanceStatus.CONTENT_CHANGED;
|
||||
|
||||
return MandatoryInfoAcceptanceStatus.ACCEPTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="justified-markdown">
|
||||
<MudMarkdown Value="@this.Value" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE" />
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class MudJustifiedMarkdown
|
||||
{
|
||||
[Parameter]
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -6,7 +6,11 @@
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
@T("This Agent audits newly installed or updated external Plugin-Assistant for security risks before they are activated and stores the latest audit card until the plugin manifest changes.")
|
||||
</MudText>
|
||||
<ConfigurationOption OptionDescription="@T("Require a security audit before activating external Assistants?")" LabelOn="@T("External Assistants must be audited before activation")" LabelOff="@T("External Assistant can be activated without an audit")" State="@(() => this.SettingsManager.ConfigurationData.AssistantPluginAudit.RequireAuditBeforeActivation)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.AssistantPluginAudit.RequireAuditBeforeActivation = updatedState)" />
|
||||
<MudField Label="@T("Require a security audit before activating external Assistants?")" Variant="Variant.Outlined" Underline="false" Class="mb-6" InnerPadding="false">
|
||||
<MudSwitch T="bool" Value="@this.SettingsManager.ConfigurationData.AssistantPluginAudit.RequireAuditBeforeActivation" ValueChanged="@this.RequireAuditBeforeActivationChanged" Color="Color.Primary">
|
||||
@(this.SettingsManager.ConfigurationData.AssistantPluginAudit.RequireAuditBeforeActivation ? T("External Assistants must be audited before activation") : T("External Assistant can be activated without an audit"))
|
||||
</MudSwitch>
|
||||
</MudField>
|
||||
<ConfigurationProviderSelection Data="@this.AvailableLLMProvidersFunc()" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AssistantPluginAudit.PreselectedAgentProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.AssistantPluginAudit.PreselectedAgentProvider = selectedValue)" HelpText="@(() => T("Optionally choose a dedicated provider for assistant plugin audits. When left empty, AI Studio falls back to the app-wide default provider."))" />
|
||||
<ConfigurationSelect OptionDescription="@T("Minimum required audit level")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.AssistantPluginAudit.MinimumLevel)" Data="@ConfigurationSelectDataFactory.GetAssistantAuditLevelsData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.AssistantPluginAudit.MinimumLevel = selectedValue)" OptionHelp="@T("External Assistants rated below this audit level are treated as insufficiently reviewed.")" />
|
||||
<ConfigurationOption OptionDescription="@T("Block activation below the minimum Audit-Level?")" LabelOn="@T("Activation is blocked below the minimum Audit-Level")" LabelOff="@T("Users may still activate plugins below the minimum Audit-Level")" State="@(() => this.SettingsManager.ConfigurationData.AssistantPluginAudit.BlockActivationBelowMinimum)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.AssistantPluginAudit.BlockActivationBelowMinimum = updatedState)"
|
||||
|
||||
+35
-1
@@ -1,3 +1,37 @@
|
||||
using AIStudio.Dialogs;
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelAgentAssistantAudit : SettingsPanelBase;
|
||||
public partial class SettingsPanelAgentAssistantAudit : SettingsPanelBase
|
||||
{
|
||||
private async Task RequireAuditBeforeActivationChanged(bool updatedState)
|
||||
{
|
||||
if (!updatedState)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{
|
||||
x => x.Message,
|
||||
this.T("Disabling this setting turns off assistant plugin security audits. External assistants may then be activated and used even without a valid audit or after plugin changes. Do you really want to disable this protection?")
|
||||
},
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(
|
||||
this.T("Disable Assistant Audit Protection"),
|
||||
dialogParameters,
|
||||
DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
{
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.SettingsManager.ConfigurationData.AssistantPluginAudit.RequireAuditBeforeActivation = updatedState;
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.SendMessage<bool>(Event.CONFIGURATION_CHANGED);
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user