diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor b/app/MindWork AI Studio/Assistants/AssistantBase.razor index 9555278..ab8a01f 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor @@ -48,17 +48,20 @@ @if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO)) { - - @foreach (var assistant in Enum.GetValues().OrderBy(n => n.Name().Length)) - { - if (assistant is Components.NONE || this.Component == assistant) - continue; + @if (this.ShowSendTo) + { + + @foreach (var assistant in Enum.GetValues().OrderBy(n => n.Name().Length)) + { + if (assistant is Components.NONE || this.Component == assistant) + continue; - - @assistant.Name() - - } - + + @assistant.Name() + + } + + } } @foreach (var button in this.FooterButtons) @@ -95,13 +98,19 @@ } } - - Copy result - - - - Reset - + @if (this.ShowCopyResult) + { + + Copy result + + } + + @if (this.ShowReset) + { + + Reset + + } @if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence) { diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index 8135850..1a7b404 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -74,6 +74,12 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver protected virtual bool ShowProfileSelection => true; protected virtual bool ShowDedicatedProgress => false; + + protected virtual bool ShowSendTo => true; + + protected virtual bool ShowCopyResult => true; + + protected virtual bool ShowReset => true; protected virtual ChatThread ConvertToChatThread => this.chatThread ?? new(); @@ -173,14 +179,36 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver Blocks = [], }; } + + protected Guid CreateChatThread(Guid workspaceId, string name) + { + var chatId = Guid.NewGuid(); + this.chatThread = new() + { + WorkspaceId = workspaceId, + ChatId = chatId, + Name = name, + Seed = this.RNG.Next(), + SystemPrompt = !this.AllowProfiles ? this.SystemPrompt : + $""" + {this.SystemPrompt} + + {this.currentProfile.ToSystemPrompt()} + """, + Blocks = [], + }; + + return chatId; + } - protected DateTimeOffset AddUserRequest(string request) + protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false) { var time = DateTimeOffset.Now; this.chatThread!.Blocks.Add(new ContentBlock { Time = time, ContentType = ContentType.TEXT, + HideFromUser = hideContentFromUser, Role = ChatRole.USER, Content = new ContentText { @@ -237,9 +265,9 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver return icon; } - private Task SendToAssistant(Tools.Components destination, SendToButton sendToButton) + protected Task SendToAssistant(Tools.Components destination, SendToButton sendToButton) { - var contentToSend = sendToButton.UseResultingContentBlockData switch + var contentToSend = sendToButton == default ? string.Empty : sendToButton.UseResultingContentBlockData switch { false => sendToButton.GetText(), true => this.resultingContentBlock?.Content switch diff --git a/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor b/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor new file mode 100644 index 0000000..f5acf48 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor @@ -0,0 +1,14 @@ +@attribute [Route(Routes.ASSISTANT_BIAS)] +@inherits AssistantBaseCore + + + Links: + + + Wikipedia list of cognitive biases + Extended bias poster + Blog post of Buster Benson: "Cognitive bias cheat sheet" + + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs b/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs new file mode 100644 index 0000000..a7a750c --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BiasDay/BiasOfTheDayAssistant.razor.cs @@ -0,0 +1,164 @@ +using System.Text; + +using AIStudio.Components; +using AIStudio.Settings.DataModel; + +namespace AIStudio.Assistants.BiasDay; + +public partial class BiasOfTheDayAssistant : AssistantBaseCore +{ + public override Tools.Components Component => Tools.Components.BIAS_DAY_ASSISTANT; + + protected override string Title => "Bias of the Day"; + + protected override string Description => + """ + Learn about a different cognitive bias every day. You can also ask the LLM your questions. The idea behind + "Bias of the Day" is based on work by Buster Benson, John Manoogian III, and Brian Rene Morrissette. Buster + Benson grouped the biases, and the original texts come from Wikipedia. Brian Rene Morrissette condensed them + into a shorter version. Finally, John Manoogian III created the original poster based on Benson's work and + Morrissette's texts. Thorsten Sommer compared all texts for integration into AI Studio with the current Wikipedia + versions, updated them, and added source references. The idea of learning about one bias each day based on John's + poster comes from Drew Nelson. + """; + + protected override string SystemPrompt => $""" + You are a friendly, helpful expert on cognitive bias. You studied psychology and + have a lot of experience. You explain a bias every day. Today's bias belongs to + the category: "{this.biasOfTheDay.Category.ToName()}". We have the following + thoughts on this category: + + {this.biasOfTheDay.Category.GetThoughts()} + + Today's bias is: + {this.biasOfTheDay.Description} + {this.SystemPromptSources()} + Important: you use the following language: {this.SystemPromptLanguage()}. Please + ask the user a personal question at the end to encourage them to think about + this bias. + """; + + protected override IReadOnlyList FooterButtons => []; + + protected override string SubmitText => "Show me the bias of the day"; + + protected override Func SubmitAction => this.TellBias; + + protected override bool ShowSendTo => false; + + protected override bool ShowCopyResult => false; + + protected override bool ShowReset => false; + + protected override void ResetFrom() + { + if (!this.MightPreselectValues()) + { + this.selectedTargetLanguage = CommonLanguages.AS_IS; + this.customTargetLanguage = string.Empty; + } + } + + protected override bool MightPreselectValues() + { + if (this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions) + { + this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedTargetLanguage; + this.customTargetLanguage = this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedOtherLanguage; + return true; + } + + return false; + } + + private Bias biasOfTheDay = BiasCatalog.NONE; + private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS; + private string customTargetLanguage = string.Empty; + + private string? ValidateTargetLanguage(CommonLanguages language) + { + if(language is CommonLanguages.AS_IS) + return "Please select a target language for the bias."; + + return null; + } + + private string? ValidateCustomLanguage(string language) + { + if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language)) + return "Please provide a custom language."; + + return null; + } + + private string SystemPromptSources() + { + var sb = new StringBuilder(); + if (this.biasOfTheDay.Links.Count > 0) + { + sb.AppendLine(); + sb.AppendLine("Please share the following sources with the user as a Markdown list:"); + foreach (var link in this.biasOfTheDay.Links) + sb.AppendLine($"- {link}"); + + sb.AppendLine(); + } + + return sb.ToString(); + } + + private string SystemPromptLanguage() + { + if(this.selectedTargetLanguage is CommonLanguages.OTHER) + return this.customTargetLanguage; + + return this.selectedTargetLanguage.Name(); + } + + private async Task TellBias() + { + bool useDrawnBias = false; + if(this.SettingsManager.ConfigurationData.BiasOfTheDay.RestrictOneBiasPerDay) + { + if(this.SettingsManager.ConfigurationData.BiasOfTheDay.DateLastBiasDrawn == DateOnly.FromDateTime(DateTime.Now)) + { + var biasChat = new LoadChat + { + WorkspaceId = Workspaces.WORKSPACE_ID_BIAS, + ChatId = this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayChatId, + }; + + if (Workspaces.IsChatExisting(biasChat)) + { + MessageBus.INSTANCE.DeferMessage(this, Event.LOAD_CHAT, biasChat); + this.NavigationManager.NavigateTo(Routes.CHAT); + return; + } + else + useDrawnBias = true; + } + } + + await this.form!.Validate(); + if (!this.inputIsValid) + return; + + this.biasOfTheDay = useDrawnBias ? + BiasCatalog.ALL_BIAS[this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayId] : + BiasCatalog.GetRandomBias(this.SettingsManager.ConfigurationData.BiasOfTheDay.UsedBias); + + var chatId = this.CreateChatThread(Workspaces.WORKSPACE_ID_BIAS, this.biasOfTheDay.Name); + this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayId = this.biasOfTheDay.Id; + this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayChatId = chatId; + this.SettingsManager.ConfigurationData.BiasOfTheDay.DateLastBiasDrawn = DateOnly.FromDateTime(DateTime.Now); + await this.SettingsManager.StoreSettings(); + var time = this.AddUserRequest( + """ + Please tell me about the bias of the day. + """, true); + + // Start the AI response without waiting for it to finish: + _ = this.AddAIResponseAsync(time); + await this.SendToAssistant(Tools.Components.CHAT, default); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Chat/ContentBlock.cs b/app/MindWork AI Studio/Chat/ContentBlock.cs index 05a6bd1..632d98d 100644 --- a/app/MindWork AI Studio/Chat/ContentBlock.cs +++ b/app/MindWork AI Studio/Chat/ContentBlock.cs @@ -18,10 +18,15 @@ public class ContentBlock /// /// The content of the block. /// - public IContent? Content { get; init; } = null; + public IContent? Content { get; init; } /// /// The role of the content block in the chat thread, e.g., user, AI, etc. /// public ChatRole Role { get; init; } = ChatRole.NONE; + + /// + /// Should the content block be hidden from the user? + /// + public bool HideFromUser { get; set; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index 756d3a6..2bef5f9 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -64,7 +64,7 @@ public partial class ContentBlockComponent : ComponentBase private async Task AfterStreaming() { // Might be called from a different thread, so we need to invoke the UI thread: - await this.InvokeAsync(() => + await this.InvokeAsync(async () => { // // Issue we try to solve: When the content changes during streaming, @@ -83,6 +83,9 @@ public partial class ContentBlockComponent : ComponentBase // Let Blazor update the UI, i.e., to see the render tree diff: this.StateHasChanged(); + + // Inform the chat that the streaming is done: + await MessageBus.INSTANCE.SendMessage(this, Event.CHAT_STREAMING_DONE); }); } diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index edf0d28..fcb845e 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -37,6 +37,7 @@ public partial class Workspaces : ComponentBase private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom; + public static readonly Guid WORKSPACE_ID_BIAS = Guid.Parse("82050a4e-ee92-43d7-8ee5-ab512f847e02"); private static readonly JsonSerializerOptions JSON_OPTIONS = new() { WriteIndented = true, @@ -63,6 +64,7 @@ public partial class Workspaces : ComponentBase // - When assigning the tree items to the MudTreeViewItem component, we must set the Value property to the value of the item // + await this.EnsureBiasWorkspace(); await this.LoadTreeItems(); await base.OnInitializedAsync(); } @@ -250,7 +252,7 @@ public partial class Workspaces : ComponentBase return result; } - public async Task StoreChat(ChatThread chat) + public async Task StoreChat(ChatThread chat, bool reloadTreeItems = true) { string chatDirectory; if (chat.WorkspaceId == Guid.Empty) @@ -270,9 +272,29 @@ public partial class Workspaces : ComponentBase await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8); // Reload the tree items: - await this.LoadTreeItems(); + if(reloadTreeItems) + await this.LoadTreeItems(); + this.StateHasChanged(); } + + public async Task LoadChat(LoadChat loadChat) + { + var chatPath = loadChat.WorkspaceId == Guid.Empty + ? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString()) + : Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString()); + + await this.LoadChat(chatPath, switchToChat: true); + } + + public static bool IsChatExisting(LoadChat loadChat) + { + var chatPath = loadChat.WorkspaceId == Guid.Empty + ? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString()) + : Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString()); + + return Directory.Exists(chatPath); + } private async Task LoadChat(string? chatPath, bool switchToChat) { @@ -434,6 +456,18 @@ public partial class Workspaces : ComponentBase await this.LoadTreeItems(); } + + private async Task EnsureBiasWorkspace() + { + var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", WORKSPACE_ID_BIAS.ToString()); + + if(Path.Exists(workspacePath)) + return; + + Directory.CreateDirectory(workspacePath); + var workspaceNamePath = Path.Join(workspacePath, "name"); + await File.WriteAllTextAsync(workspaceNamePath, "Bias of the Day", Encoding.UTF8); + } private async Task DeleteWorkspace(string? workspacePath) { diff --git a/app/MindWork AI Studio/Pages/Assistants.razor b/app/MindWork AI Studio/Pages/Assistants.razor index 2368878..565469a 100644 --- a/app/MindWork AI Studio/Pages/Assistants.razor +++ b/app/MindWork AI Studio/Pages/Assistants.razor @@ -29,6 +29,13 @@ + + Learning + + + + + Software Engineering diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor index 5e993db..8162231 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor +++ b/app/MindWork AI Studio/Pages/Chat.razor @@ -22,7 +22,10 @@ { foreach (var block in this.chatThread.Blocks.OrderBy(n => n.Time)) { - + @if (!block.HideFromUser) + { + + } } } diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index c5d3180..909b3cb 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -45,7 +45,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable private bool workspacesVisible; private Workspaces? workspaces; private bool mustScrollToBottomAfterRender; + private bool mustStoreChat; + private bool mustLoadChat; + private LoadChat loadChat; private byte scrollRenderCountdown; + private bool autoSaveEnabled; // Unfortunately, we need the input field reference to blur the focus away. Without // this, we cannot clear the input field. @@ -55,7 +59,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable protected override async Task OnInitializedAsync() { - this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE ]); + this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE ]); // Configure the spellchecking for the user input: this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); @@ -68,14 +72,23 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable this.chatThread = deferredContent; if (this.chatThread is not null) { - var firstUserBlock = this.chatThread.Blocks.FirstOrDefault(x => x.Role == ChatRole.USER); - if (firstUserBlock is not null) + if (string.IsNullOrWhiteSpace(this.chatThread.Name)) { - this.chatThread.Name = firstUserBlock.Content switch + var firstUserBlock = this.chatThread.Blocks.FirstOrDefault(x => x.Role == ChatRole.USER); + if (firstUserBlock is not null) { - ContentText textBlock => this.ExtractThreadName(textBlock.Text), - _ => "Thread" - }; + this.chatThread.Name = firstUserBlock.Content switch + { + ContentText textBlock => this.ExtractThreadName(textBlock.Text), + _ => "Thread" + }; + } + } + + if(this.chatThread.WorkspaceId != Guid.Empty) + { + this.autoSaveEnabled = true; + this.mustStoreChat = true; } } @@ -86,12 +99,33 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable this.StateHasChanged(); } } + + var deferredLoading = MessageBus.INSTANCE.CheckDeferredMessages(Event.LOAD_CHAT).FirstOrDefault(); + if (deferredLoading != default) + { + this.loadChat = deferredLoading; + this.mustLoadChat = true; + } await base.OnInitializedAsync(); } protected override async Task OnAfterRenderAsync(bool firstRender) { + if (firstRender && this.workspaces is not null && this.chatThread is not null && this.mustStoreChat) + { + this.mustStoreChat = false; + await this.workspaces.StoreChat(this.chatThread, false); + this.currentWorkspaceId = this.chatThread.WorkspaceId; + this.currentWorkspaceName = await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId); + } + + if (firstRender && this.workspaces is not null && this.mustLoadChat) + { + this.mustLoadChat = false; + await this.workspaces.LoadChat(this.loadChat); + } + if(this.mustScrollToBottomAfterRender) { if (--this.scrollRenderCountdown == 0) @@ -455,16 +489,19 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable #region Overrides of MSGComponentBase - public override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + public override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { case Event.RESET_CHAT_STATE: this.ResetState(); break; + + case Event.CHAT_STREAMING_DONE: + if(this.autoSaveEnabled) + await this.SaveThread(); + break; } - - return Task.CompletedTask; } public override Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor index df5ed94..f37b277 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor +++ b/app/MindWork AI Studio/Pages/Settings.razor @@ -3,6 +3,7 @@ @using AIStudio.Assistants.TextSummarizer @using AIStudio.Provider @using AIStudio.Settings +@using AIStudio.Settings.DataModel @using Host = AIStudio.Provider.SelfHosted.Host Settings @@ -400,6 +401,32 @@ + + + + + + + + You have learned about @this.SettingsManager.ConfigurationData.BiasOfTheDay.UsedBias.Count out of @BiasCatalog.ALL_BIAS.Count biases. + + + Reset + + + + + + + @if (this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedTargetLanguage is CommonLanguages.OTHER) + { + + } + + + + + diff --git a/app/MindWork AI Studio/Pages/Settings.razor.cs b/app/MindWork AI Studio/Pages/Settings.razor.cs index 9ff7beb..75197a4 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor.cs +++ b/app/MindWork AI Studio/Pages/Settings.razor.cs @@ -249,6 +249,29 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); } + #endregion + + #region Bias-of-the-day related + + private async Task ResetBiasOfTheDayHistory() + { + var dialogParameters = new DialogParameters + { + { "Message", "Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again." }, + }; + + var dialogReference = await this.DialogService.ShowAsync("Reset your bias-of-the-day statistics", dialogParameters, DialogOptions.FULLSCREEN); + var dialogResult = await dialogReference.Result; + if (dialogResult is null || dialogResult.Canceled) + return; + + this.SettingsManager.ConfigurationData.BiasOfTheDay.UsedBias.Clear(); + this.SettingsManager.ConfigurationData.BiasOfTheDay.DateLastBiasDrawn = DateOnly.MinValue; + await this.SettingsManager.StoreSettings(); + + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + #endregion #region Implementation of IMessageBusReceiver diff --git a/app/MindWork AI Studio/Routes.razor.cs b/app/MindWork AI Studio/Routes.razor.cs index 7b3e24e..03ef143 100644 --- a/app/MindWork AI Studio/Routes.razor.cs +++ b/app/MindWork AI Studio/Routes.razor.cs @@ -22,5 +22,6 @@ public sealed partial class Routes public const string ASSISTANT_SYNONYMS = "/assistant/synonyms"; public const string ASSISTANT_MY_TASKS = "/assistant/my-tasks"; public const string ASSISTANT_JOB_POSTING = "/assistant/job-posting"; + public const string ASSISTANT_BIAS = "/assistant/bias-of-the-day"; // ReSharper restore InconsistentNaming } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/BiasCatalog.cs b/app/MindWork AI Studio/Settings/DataModel/BiasCatalog.cs index 16f46cb..53e36c9 100644 --- a/app/MindWork AI Studio/Settings/DataModel/BiasCatalog.cs +++ b/app/MindWork AI Studio/Settings/DataModel/BiasCatalog.cs @@ -22,6 +22,16 @@ namespace AIStudio.Settings.DataModel; /// public static class BiasCatalog { + public static readonly Bias NONE = new() + { + Id = Guid.Empty, + Category = BiasCategory.NONE, + Name = "None", + Description = "No bias selected.", + Related = [], + Links = [], + }; + #region WHAT_SHOULD_WE_REMEMBER private static readonly Bias MISATTRIBUTION_OF_MEMORY = new() @@ -5909,4 +5919,23 @@ public static class BiasCatalog { IMPLICIT_STEREOTYPES.Id, IMPLICIT_STEREOTYPES }, { IMPLICIT_ASSOCIATIONS.Id, IMPLICIT_ASSOCIATIONS }, }; + + public static Bias GetRandomBias(IList usedBias) + { + if(usedBias.Count >= ALL_BIAS.Count) + usedBias.Clear(); + + int randomBiasIndex; + lock (RANDOM) + { + randomBiasIndex = RANDOM.Next(0, ALL_BIAS.Count); + while(usedBias.Contains(randomBiasIndex)) + randomBiasIndex = RANDOM.Next(0, ALL_BIAS.Count); + } + + usedBias.Add(randomBiasIndex); + return ALL_BIAS.Values.ElementAt(randomBiasIndex); + } + + private static readonly Random RANDOM = new(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs index 720d0e0..59ceaae 100644 --- a/app/MindWork AI Studio/Settings/DataModel/Data.cs +++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs @@ -67,4 +67,6 @@ public sealed class Data public DataMyTasks MyTasks { get; init; } = new(); public DataJobPostings JobPostings { get; init; } = new(); + + public DataBiasOfTheDay BiasOfTheDay { get; init; } = new(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataBiasOfTheDay.cs b/app/MindWork AI Studio/Settings/DataModel/DataBiasOfTheDay.cs new file mode 100644 index 0000000..47e03bc --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/DataBiasOfTheDay.cs @@ -0,0 +1,61 @@ +using AIStudio.Provider; + +namespace AIStudio.Settings.DataModel; + +public sealed class DataBiasOfTheDay +{ + /// + /// A list of bias IDs that have been used. + /// + public List UsedBias { get; set; } = new(); + + /// + /// When was the last bias drawn? + /// + public DateOnly DateLastBiasDrawn { get; set; } = DateOnly.MinValue; + + /// + /// Which bias is the bias of the day? This isn't the bias id, but rather the chat id in the bias workspace. + /// + public Guid BiasOfTheDayChatId { get; set; } = Guid.Empty; + + /// + /// Which bias is the bias of the day? + /// + public Guid BiasOfTheDayId { get; set; } = Guid.Empty; + + /// + /// Restrict to one bias per day? + /// + public bool RestrictOneBiasPerDay { get; set; } = true; + + /// + /// Preselect any rewrite options? + /// + public bool PreselectOptions { get; set; } + + /// + /// Preselect the language? + /// + public CommonLanguages PreselectedTargetLanguage { get; set; } + + /// + /// Preselect any other language? + /// + public string PreselectedOtherLanguage { get; set; } = string.Empty; + + /// + /// The minimum confidence level required for a provider to be considered. + /// + public ConfidenceLevel MinimumProviderConfidence { get; set; } = ConfidenceLevel.NONE; + + /// + /// Preselect a profile? + /// + public string PreselectedProfile { get; set; } = string.Empty; + + /// + /// Preselect a provider? + /// + public string PreselectedProvider { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Components.cs b/app/MindWork AI Studio/Tools/Components.cs index ccebcd0..30dee58 100644 --- a/app/MindWork AI Studio/Tools/Components.cs +++ b/app/MindWork AI Studio/Tools/Components.cs @@ -16,6 +16,7 @@ public enum Components SYNONYMS_ASSISTANT, MY_TASKS_ASSISTANT, JOB_POSTING_ASSISTANT, + BIAS_DAY_ASSISTANT, CHAT, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index f9f9e8e..1aa92a1 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -59,6 +59,7 @@ public static class ComponentsExtensions Components.SYNONYMS_ASSISTANT => settingsManager.ConfigurationData.Synonyms.PreselectOptions ? settingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence : default, Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence : default, Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence : default, + Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence : default, _ => default, }; @@ -77,6 +78,7 @@ public static class ComponentsExtensions Components.SYNONYMS_ASSISTANT => settingsManager.ConfigurationData.Synonyms.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Synonyms.PreselectedProvider) : default, Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.MyTasks.PreselectedProvider) : default, Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.JobPostings.PreselectedProvider) : default, + Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider) : default, Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProvider) : default, @@ -90,6 +92,7 @@ public static class ComponentsExtensions Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.EMail.PreselectedProfile) : default, Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.LegalCheck.PreselectedProfile) : default, Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.MyTasks.PreselectedProfile) : default, + Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile) : default, Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProfile) : default, diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs index 691f8b3..2dde28a 100644 --- a/app/MindWork AI Studio/Tools/Event.cs +++ b/app/MindWork AI Studio/Tools/Event.cs @@ -16,6 +16,8 @@ public enum Event // Chat events: HAS_CHAT_UNSAVED_CHANGES, RESET_CHAT_STATE, + LOAD_CHAT, + CHAT_STREAMING_DONE, // Send events: SEND_TO_GRAMMAR_SPELLING_ASSISTANT, diff --git a/app/MindWork AI Studio/Tools/LoadChat.cs b/app/MindWork AI Studio/Tools/LoadChat.cs new file mode 100644 index 0000000..6b9072e --- /dev/null +++ b/app/MindWork AI Studio/Tools/LoadChat.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools; + +public readonly record struct LoadChat(Guid WorkspaceId, Guid ChatId); \ No newline at end of file