mirror of
				https://github.com/MindWorkAI/AI-Studio.git
				synced 2025-10-31 09:40:20 +00:00 
			
		
		
		
	Add profiles (#132)
This commit is contained in:
		
							parent
							
								
									00f45f8998
								
							
						
					
					
						commit
						d7c124926b
					
				| @ -93,6 +93,11 @@ | ||||
|             <MudButton Variant="Variant.Filled" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()"> | ||||
|                 Reset | ||||
|             </MudButton> | ||||
|              | ||||
|             @if (this.AllowProfiles) | ||||
|             { | ||||
|                 <ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.currentProfile"/> | ||||
|             } | ||||
|         </MudStack> | ||||
|     </FooterContent> | ||||
| </InnerScrolling> | ||||
| @ -55,6 +55,8 @@ public abstract partial class AssistantBase : ComponentBase | ||||
|     private protected virtual RenderFragment? Body => null; | ||||
| 
 | ||||
|     protected virtual bool ShowResult => true; | ||||
| 
 | ||||
|     protected virtual bool AllowProfiles => true; | ||||
|      | ||||
|     protected virtual bool ShowDedicatedProgress => false; | ||||
| 
 | ||||
| @ -72,6 +74,7 @@ public abstract partial class AssistantBase : ComponentBase | ||||
|     private ContentBlock? resultingContentBlock; | ||||
|     private string[] inputIssues = []; | ||||
|     private bool isProcessing; | ||||
|     private Profile currentProfile = Profile.NO_PROFILE; | ||||
|      | ||||
|     #region Overrides of ComponentBase | ||||
| 
 | ||||
| @ -79,6 +82,7 @@ public abstract partial class AssistantBase : ComponentBase | ||||
|     { | ||||
|         this.MightPreselectValues(); | ||||
|         this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component); | ||||
|         this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component); | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
| 
 | ||||
| @ -118,7 +122,12 @@ public abstract partial class AssistantBase : ComponentBase | ||||
|             ChatId = Guid.NewGuid(), | ||||
|             Name = string.Empty, | ||||
|             Seed = this.RNG.Next(), | ||||
|             SystemPrompt = this.SystemPrompt, | ||||
|             SystemPrompt = !this.AllowProfiles ? this.SystemPrompt : | ||||
|                 $"""
 | ||||
|                 {this.SystemPrompt} | ||||
|                  | ||||
|                 {this.currentProfile.ToSystemPrompt()} | ||||
|                 """,
 | ||||
|             Blocks = [], | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @ -22,6 +22,8 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore | ||||
|         Your response includes only the corrected text. Do not explain your changes. If no changes are needed, | ||||
|         you return the text unchanged. | ||||
|         """;
 | ||||
|      | ||||
|     protected override bool AllowProfiles => false; | ||||
| 
 | ||||
|     protected override bool ShowResult => false; | ||||
|      | ||||
|  | ||||
| @ -24,6 +24,8 @@ public partial class AssistantIconFinder : AssistantBaseCore | ||||
|         related to the keyword "buildings" might be the best match. Provide your keywords in a Markdown list without | ||||
|         quotation marks. | ||||
|         """;
 | ||||
|      | ||||
|     protected override bool AllowProfiles => false; | ||||
| 
 | ||||
|     protected override IReadOnlyList<IButtonData> FooterButtons => []; | ||||
|      | ||||
|  | ||||
| @ -24,6 +24,8 @@ public partial class AssistantRewriteImprove : AssistantBaseCore | ||||
|         You follow the rules according to {this.SystemPromptLanguage()} in all your changes. | ||||
|         """;
 | ||||
|      | ||||
|     protected override bool AllowProfiles => false; | ||||
|      | ||||
|     protected override bool ShowResult => false; | ||||
| 
 | ||||
|     protected override bool ShowDedicatedProgress => true; | ||||
|  | ||||
| @ -47,6 +47,8 @@ public partial class AssistantSynonyms : AssistantBaseCore | ||||
|         the {this.SystemPromptLanguage()} language. | ||||
|         """;
 | ||||
|      | ||||
|     protected override bool AllowProfiles => false; | ||||
|      | ||||
|     protected override IReadOnlyList<IButtonData> FooterButtons => []; | ||||
|      | ||||
|     protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with | ||||
|  | ||||
| @ -25,6 +25,8 @@ public partial class AssistantTextSummarizer : AssistantBaseCore | ||||
|         a summary with the requested complexity. In any case, do not add any information. | ||||
|         """;
 | ||||
|      | ||||
|     protected override bool AllowProfiles => false; | ||||
|      | ||||
|     protected override IReadOnlyList<IButtonData> FooterButtons => []; | ||||
|      | ||||
|     protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with | ||||
|  | ||||
| @ -21,6 +21,8 @@ public partial class AssistantTranslation : AssistantBaseCore | ||||
|         language requires, e.g., shorter sentences, you should split the text into shorter sentences. | ||||
|         """;
 | ||||
|      | ||||
|     protected override bool AllowProfiles => false; | ||||
|      | ||||
|     protected override IReadOnlyList<IButtonData> FooterButtons => []; | ||||
|      | ||||
|     protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with | ||||
|  | ||||
							
								
								
									
										16
									
								
								app/MindWork AI Studio/Components/MudJustifiedText.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/MindWork AI Studio/Components/MudJustifiedText.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public class MudJustifiedText : MudText | ||||
| { | ||||
|     #region Overrides of ComponentBase | ||||
| 
 | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|         this.Align = Align.Justify; | ||||
|         this.Style = "hyphens: auto; word-break: auto-phrase;"; | ||||
|          | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| } | ||||
| @ -2,7 +2,7 @@ | ||||
|     @foreach(var item in this.Items) | ||||
|     { | ||||
|         <MudListItem T="string" Icon="@this.Icon" Style="display: flex; align-items: flex-start;"> | ||||
|             <MudText Typo="Typo.body1" Style="text-align: justify; hyphens: auto;"><b>@item.Header:</b> @item.Text</MudText> | ||||
|             <MudText Typo="Typo.body1" Align="Align.Justify" Style="hyphens: auto; word-break: auto-phrase;"><b>@item.Header:</b> @item.Text</MudText> | ||||
|         </MudListItem> | ||||
|     } | ||||
| </MudList> | ||||
							
								
								
									
										10
									
								
								app/MindWork AI Studio/Components/ProfileSelection.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/MindWork AI Studio/Components/ProfileSelection.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <MudTooltip Text="You can switch between your profiles here"> | ||||
|     <MudMenu StartIcon="@Icons.Material.Filled.Person4" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.CurrentProfile.Name" Variant="Variant.Filled" Color="Color.Default" Class="@this.MarginClass"> | ||||
|         @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles()) | ||||
|         { | ||||
|             <MudMenuItem OnClick="() => this.SelectionChanged(profile)"> | ||||
|                 @profile.Name | ||||
|             </MudMenuItem> | ||||
|         } | ||||
|     </MudMenu> | ||||
| </MudTooltip> | ||||
							
								
								
									
										28
									
								
								app/MindWork AI Studio/Components/ProfileSelection.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/MindWork AI Studio/Components/ProfileSelection.razor.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| using AIStudio.Settings; | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Components; | ||||
| 
 | ||||
| public partial class ProfileSelection : ComponentBase | ||||
| { | ||||
|     [Parameter] | ||||
|     public Profile CurrentProfile { get; set; } = Profile.NO_PROFILE; | ||||
|      | ||||
|     [Parameter] | ||||
|     public EventCallback<Profile> CurrentProfileChanged { get; set; } | ||||
| 
 | ||||
|     [Parameter] | ||||
|     public string MarginLeft { get; set; } = "ml-3"; | ||||
|      | ||||
|     [Inject] | ||||
|     private SettingsManager SettingsManager { get; init; } = null!; | ||||
|      | ||||
|     private string MarginClass => $"{this.MarginLeft}"; | ||||
|      | ||||
|     private async Task SelectionChanged(Profile profile) | ||||
|     { | ||||
|         this.CurrentProfile = profile; | ||||
|         await this.CurrentProfileChanged.InvokeAsync(profile); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										93
									
								
								app/MindWork AI Studio/Dialogs/ProfileDialog.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								app/MindWork AI Studio/Dialogs/ProfileDialog.razor
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| <MudDialog> | ||||
|     <DialogContent> | ||||
|         <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|             Store personal data about yourself in various profiles so that the AIs know your personal context. | ||||
|             This saves you from having to explain your context each time, for example, in every chat. When you | ||||
|             have different roles, you can create a profile for each role. | ||||
|         </MudJustifiedText> | ||||
| 
 | ||||
|         <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|             Are you a project manager in a research facility? You might want to create a profile for your project | ||||
|             management activities, one for your scientific work, and a profile for when you need to write program | ||||
|             code. In these profiles, you can record how much experience you have or which methods you like or | ||||
|             dislike using. Later, you can choose when and where you want to use each profile. | ||||
|         </MudJustifiedText> | ||||
| 
 | ||||
|         <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|             The name of the profile is mandatory. Each profile must have a unique name. Whether you provide | ||||
|             information about yourself or only fill out the actions is up to you. Only one of these pieces | ||||
|             is required. | ||||
|         </MudJustifiedText> | ||||
|         <MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues"> | ||||
|             @* ReSharper disable once CSharpWarnings::CS8974 *@ | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.DataName" | ||||
|                 Label="Profile Name" | ||||
|                 Class="mb-3" | ||||
|                 Immediate="@true" | ||||
|                 MaxLength="40" | ||||
|                 Counter="40" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.Badge" | ||||
|                 AdornmentColor="Color.Info" | ||||
|                 Validation="@this.ValidateName" | ||||
|                 Variant="Variant.Outlined" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|             /> | ||||
| 
 | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.DataNeedToKnow" | ||||
|                 Validation="@this.ValidateNeedToKnow" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.ListAlt" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 Immediate="@true" | ||||
|                 Label="What should the AI know about you?" | ||||
|                 Variant="Variant.Outlined" | ||||
|                 Lines="6" | ||||
|                 AutoGrow="@true" | ||||
|                 MaxLines="12" | ||||
|                 MaxLength="444" | ||||
|                 Counter="444" | ||||
|                 Class="mb-3" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|                 HelperText="Tell the AI something about yourself. What is your profession? How experienced are you in this profession? Which technologies do you like?" | ||||
|             /> | ||||
| 
 | ||||
|             <MudTextField | ||||
|                 T="string" | ||||
|                 @bind-Text="@this.DataActions" | ||||
|                 Validation="@this.ValidateActions" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.ListAlt" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 Immediate="@true" | ||||
|                 Label="What should the AI do for you?" | ||||
|                 Variant="Variant.Outlined" | ||||
|                 Lines="6" | ||||
|                 AutoGrow="@true" | ||||
|                 MaxLines="12" | ||||
|                 MaxLength="256" | ||||
|                 Counter="256" | ||||
|                 Class="mb-3" | ||||
|                 UserAttributes="@SPELLCHECK_ATTRIBUTES" | ||||
|                 HelperText="Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally." | ||||
|             /> | ||||
| 
 | ||||
|         </MudForm> | ||||
|         <Issues IssuesData="@this.dataIssues"/> | ||||
|     </DialogContent> | ||||
|     <DialogActions> | ||||
|         <MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton> | ||||
|         <MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary"> | ||||
|             @if(this.IsEditing) | ||||
|             { | ||||
|                 @:Update | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 @:Add | ||||
|             } | ||||
|         </MudButton> | ||||
|     </DialogActions> | ||||
| </MudDialog> | ||||
							
								
								
									
										168
									
								
								app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | ||||
| using AIStudio.Settings; | ||||
| 
 | ||||
| using Microsoft.AspNetCore.Components; | ||||
| 
 | ||||
| namespace AIStudio.Dialogs; | ||||
| 
 | ||||
| public partial class ProfileDialog : ComponentBase | ||||
| { | ||||
|     [CascadingParameter] | ||||
|     private MudDialogInstance MudDialog { get; set; } = null!; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The profile's number in the list. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public uint DataNum { get; set; } | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The profile's ID. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataId { get; set; } = Guid.NewGuid().ToString(); | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The profile name chosen by the user. | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataName { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// What should the LLM know about you? | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataNeedToKnow { get; set; } = string.Empty; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// What actions should the LLM take? | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public string DataActions { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Should the dialog be in editing mode? | ||||
|     /// </summary> | ||||
|     [Parameter] | ||||
|     public bool IsEditing { get; init; } | ||||
|      | ||||
|     [Inject] | ||||
|     private SettingsManager SettingsManager { get; init; } = null!; | ||||
|      | ||||
|     [Inject] | ||||
|     private ILogger<ProviderDialog> Logger { get; init; } = null!; | ||||
|      | ||||
|     private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new(); | ||||
|      | ||||
|     /// <summary> | ||||
|     /// The list of used profile names. We need this to check for uniqueness. | ||||
|     /// </summary> | ||||
|     private List<string> UsedNames { get; set; } = []; | ||||
|      | ||||
|     private bool dataIsValid; | ||||
|     private string[] dataIssues = []; | ||||
|     private string dataEditingPreviousName = string.Empty; | ||||
|      | ||||
|     // We get the form reference from Blazor code to validate it manually: | ||||
|     private MudForm form = null!; | ||||
| 
 | ||||
|     private Profile CreateProfileSettings() => new() | ||||
|     { | ||||
|         Num = this.DataNum, | ||||
|         Id = this.DataId, | ||||
|          | ||||
|         Name = this.DataName, | ||||
|         NeedToKnow = this.DataNeedToKnow, | ||||
|         Actions = this.DataActions, | ||||
|     }; | ||||
| 
 | ||||
|     #region Overrides of ComponentBase | ||||
| 
 | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|         // Configure the spellchecking for the instance name input: | ||||
|         this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES); | ||||
|          | ||||
|         // Load the used instance names: | ||||
|         this.UsedNames = this.SettingsManager.ConfigurationData.Profiles.Select(x => x.Name.ToLowerInvariant()).ToList(); | ||||
|          | ||||
|         // When editing, we need to load the data: | ||||
|         if(this.IsEditing) | ||||
|         { | ||||
|             this.dataEditingPreviousName = this.DataName.ToLowerInvariant(); | ||||
|         } | ||||
|          | ||||
|         await base.OnInitializedAsync(); | ||||
|     } | ||||
| 
 | ||||
|     protected override async Task OnAfterRenderAsync(bool firstRender) | ||||
|     { | ||||
|         // Reset the validation when not editing and on the first render. | ||||
|         // We don't want to show validation errors when the user opens the dialog. | ||||
|         if(!this.IsEditing && firstRender) | ||||
|             this.form.ResetValidation(); | ||||
|          | ||||
|         await base.OnAfterRenderAsync(firstRender); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     private async Task Store() | ||||
|     { | ||||
|         await this.form.Validate(); | ||||
|          | ||||
|         // When the data is not valid, we don't store it: | ||||
|         if (!this.dataIsValid) | ||||
|             return; | ||||
|          | ||||
|         // Use the data model to store the profile. | ||||
|         // We just return this data to the parent component: | ||||
|         var addedProfileSettings = this.CreateProfileSettings(); | ||||
|          | ||||
|         if(this.IsEditing) | ||||
|             this.Logger.LogInformation($"Edited profile '{addedProfileSettings.Name}'."); | ||||
|         else | ||||
|             this.Logger.LogInformation($"Created profile '{addedProfileSettings.Name}'."); | ||||
|          | ||||
|         this.MudDialog.Close(DialogResult.Ok(addedProfileSettings)); | ||||
|     } | ||||
|      | ||||
|     private string? ValidateNeedToKnow(string text) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(this.DataNeedToKnow) && string.IsNullOrWhiteSpace(this.DataActions)) | ||||
|             return "Please enter what the LLM should know about you and/or what actions it should take."; | ||||
|          | ||||
|         if(text.Length > 444) | ||||
|             return "The text must not exceed 444 characters."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private string? ValidateActions(string text) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(this.DataNeedToKnow) && string.IsNullOrWhiteSpace(this.DataActions)) | ||||
|             return "Please enter what the LLM should know about you and/or what actions it should take."; | ||||
|          | ||||
|         if(text.Length > 256) | ||||
|             return "The text must not exceed 256 characters."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private string? ValidateName(string name) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(name)) | ||||
|             return "Please enter a profile name."; | ||||
|          | ||||
|         if (name.Length > 40) | ||||
|             return "The profile name must not exceed 40 characters."; | ||||
|          | ||||
|         // The instance name must be unique: | ||||
|         var lowerName = name.ToLowerInvariant(); | ||||
|         if (lowerName != this.dataEditingPreviousName && this.UsedNames.Contains(lowerName)) | ||||
|             return "The profile name must be unique; the chosen name is already in use."; | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private void Cancel() => this.MudDialog.Cancel(); | ||||
| } | ||||
| @ -83,6 +83,9 @@ | ||||
|                 @bind-Text="@this.DataInstanceName" | ||||
|                 Label="Instance Name" | ||||
|                 Class="mb-3" | ||||
|                 MaxLength="40" | ||||
|                 Counter="40" | ||||
|                 Immediate="@true" | ||||
|                 Adornment="Adornment.Start" | ||||
|                 AdornmentIcon="@Icons.Material.Filled.Lightbulb" | ||||
|                 AdornmentColor="Color.Info" | ||||
|  | ||||
| @ -1,5 +1,3 @@ | ||||
| using System.Text.RegularExpressions; | ||||
| 
 | ||||
| using AIStudio.Provider; | ||||
| using AIStudio.Settings; | ||||
| 
 | ||||
| @ -128,6 +126,10 @@ public partial class ProviderDialog : ComponentBase | ||||
|         { | ||||
|             this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant(); | ||||
|              | ||||
|             // When using Fireworks, we must copy the model name: | ||||
|             if (this.DataProvider is Providers.FIREWORKS) | ||||
|                 this.dataManuallyModel = this.DataModel.Id; | ||||
|              | ||||
|             // | ||||
|             // We cannot load the API key for self-hosted providers: | ||||
|             // | ||||
| @ -245,40 +247,14 @@ public partial class ProviderDialog : ComponentBase | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     [GeneratedRegex(@"^[a-zA-Z0-9\-_. ]+$")]
 | ||||
|     private static partial Regex InstanceNameRegex(); | ||||
|      | ||||
|     private static readonly string[] RESERVED_NAMES = ["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"]; | ||||
|      | ||||
| 
 | ||||
|     private string? ValidatingInstanceName(string instanceName) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(instanceName)) | ||||
|             return "Please enter an instance name."; | ||||
|          | ||||
|         if (instanceName.StartsWith(' ') || instanceName.StartsWith('.')) | ||||
|             return "The instance name must not start with a space or a dot."; | ||||
|          | ||||
|         if (instanceName.EndsWith(' ') || instanceName.EndsWith('.')) | ||||
|             return "The instance name must not end with a space or a dot."; | ||||
|          | ||||
|         if (instanceName.StartsWith('-') || instanceName.StartsWith('_')) | ||||
|             return "The instance name must not start with a hyphen or an underscore."; | ||||
|          | ||||
|         if (instanceName.Length > 255) | ||||
|             return "The instance name must not exceed 255 characters."; | ||||
|          | ||||
|         if (!InstanceNameRegex().IsMatch(instanceName)) | ||||
|             return "The instance name must only contain letters, numbers, spaces, hyphens, underscores, and dots."; | ||||
|          | ||||
|         if (instanceName.Contains("  ")) | ||||
|             return "The instance name must not contain consecutive spaces."; | ||||
|          | ||||
|         if (RESERVED_NAMES.Contains(instanceName.ToUpperInvariant())) | ||||
|             return "This name is reserved and cannot be used."; | ||||
|          | ||||
|         if (instanceName.Any(c => Path.GetInvalidFileNameChars().Contains(c))) | ||||
|             return "The instance name contains invalid characters."; | ||||
|         if (instanceName.Length > 40) | ||||
|             return "The instance name must not exceed 40 characters."; | ||||
|          | ||||
|         // The instance name must be unique: | ||||
|         var lowerInstanceName = instanceName.ToLowerInvariant(); | ||||
|  | ||||
| @ -69,6 +69,8 @@ | ||||
|                     <MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeSaved)" OnClick="() => this.MoveChatToWorkspace()"/> | ||||
|                 </MudTooltip> | ||||
|             } | ||||
| 
 | ||||
|             <ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" /> | ||||
|         </MudToolBar> | ||||
|     </FooterContent> | ||||
| </InnerScrolling> | ||||
|  | ||||
| @ -35,6 +35,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable | ||||
|     private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new(); | ||||
|      | ||||
|     private AIStudio.Settings.Provider providerSettings; | ||||
|     private Profile currentProfile = Profile.NO_PROFILE; | ||||
|     private ChatThread? chatThread; | ||||
|     private bool hasUnsavedChanges; | ||||
|     private bool isStreaming; | ||||
| @ -61,6 +62,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable | ||||
|         this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); | ||||
| 
 | ||||
|         this.providerSettings = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT); | ||||
|         this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT); | ||||
|         var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<ChatThread>(Event.SEND_TO_CHAT).FirstOrDefault(); | ||||
|         if (deferredContent is not null) | ||||
|         { | ||||
| @ -118,6 +120,22 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable | ||||
|     private bool CanThreadBeSaved => this.chatThread is not null && this.chatThread.Blocks.Count > 0; | ||||
| 
 | ||||
|     private string TooltipAddChatToWorkspace => $"Start new chat in workspace \"{this.currentWorkspaceName}\""; | ||||
| 
 | ||||
|     private void ProfileWasChanged(Profile profile) | ||||
|     { | ||||
|         this.currentProfile = profile; | ||||
|         if(this.chatThread is null) | ||||
|             return; | ||||
| 
 | ||||
|         this.chatThread = this.chatThread with | ||||
|         { | ||||
|             SystemPrompt = $"""
 | ||||
|                             {SystemPrompts.DEFAULT} | ||||
| 
 | ||||
|                             {this.currentProfile.ToSystemPrompt()} | ||||
|                             """
 | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     private async Task SendMessage() | ||||
|     { | ||||
| @ -135,7 +153,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable | ||||
|                 ChatId = Guid.NewGuid(), | ||||
|                 Name = threadName, | ||||
|                 Seed = this.RNG.Next(), | ||||
|                 SystemPrompt = SystemPrompts.DEFAULT, | ||||
|                 SystemPrompt = $"""
 | ||||
|                                 {SystemPrompts.DEFAULT} | ||||
| 
 | ||||
|                                 {this.currentProfile.ToSystemPrompt()} | ||||
|                                 """,
 | ||||
|                 Blocks = [], | ||||
|             }; | ||||
|         } | ||||
| @ -320,7 +342,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable | ||||
|                 ChatId = Guid.NewGuid(), | ||||
|                 Name = string.Empty, | ||||
|                 Seed = this.RNG.Next(), | ||||
|                 SystemPrompt = "You are a helpful assistant!", | ||||
|                 SystemPrompt = $"""
 | ||||
|                                 {SystemPrompts.DEFAULT} | ||||
| 
 | ||||
|                                 {this.currentProfile.ToSystemPrompt()} | ||||
|                                 """,
 | ||||
|                 Blocks = [], | ||||
|             }; | ||||
|         } | ||||
|  | ||||
| @ -11,6 +11,12 @@ | ||||
|     <MudExpansionPanels Class="mb-3" MultiExpansion="@false"> | ||||
|         <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="Configure Providers"> | ||||
|             <MudText Typo="Typo.h4" Class="mb-3">Configured Providers</MudText> | ||||
|             <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|                 What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. | ||||
|                 You can configure as many providers as you want. This way, you can use the appropriate model for each | ||||
|                 task. As an LLM provider, you can also choose local providers. However, to use this app, you must | ||||
|                 configure at least one provider. | ||||
|             </MudJustifiedText> | ||||
|             <MudTable Items="@this.SettingsManager.ConfigurationData.Providers" Class="border-dashed border rounded-lg"> | ||||
|                 <ColGroup> | ||||
|                     <col style="width: 3em;"/> | ||||
| @ -68,12 +74,62 @@ | ||||
|             </MudButton> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
|         <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Person4" HeaderText="Configure Profiles"> | ||||
|             <MudText Typo="Typo.h4" Class="mb-3">Your Profiles</MudText> | ||||
|             <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|                 Store personal data about yourself in various profiles so that the AIs know your personal context. | ||||
|                 This saves you from having to explain your context each time, for example, in every chat. When you | ||||
|                 have different roles, you can create a profile for each role. | ||||
|             </MudJustifiedText> | ||||
| 
 | ||||
|             <MudJustifiedText Typo="Typo.body1" Class="mb-3"> | ||||
|                 Are you a project manager in a research facility? You might want to create a profile for your project | ||||
|                 management activities, one for your scientific work, and a profile for when you need to write program | ||||
|                 code. In these profiles, you can record how much experience you have or which methods you like or | ||||
|                 dislike using. Later, you can choose when and where you want to use each profile. | ||||
|             </MudJustifiedText> | ||||
|             <MudTable Items="@this.SettingsManager.ConfigurationData.Profiles" Class="border-dashed border rounded-lg"> | ||||
|                 <ColGroup> | ||||
|                     <col style="width: 3em;"/> | ||||
|                     <col/> | ||||
|                     <col style="width: 40em;"/> | ||||
|                 </ColGroup> | ||||
|                 <HeaderContent> | ||||
|                     <MudTh>#</MudTh> | ||||
|                     <MudTh>Profile Name</MudTh> | ||||
|                     <MudTh Style="text-align: left;">Actions</MudTh> | ||||
|                 </HeaderContent> | ||||
|                 <RowTemplate> | ||||
|                     <MudTd>@context.Num</MudTd> | ||||
|                     <MudTd>@context.Name</MudTd> | ||||
|                     <MudTd Style="text-align: left;"> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProfile(context)"> | ||||
|                             Edit | ||||
|                         </MudButton> | ||||
|                         <MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteProfile(context)"> | ||||
|                             Delete | ||||
|                         </MudButton> | ||||
|                     </MudTd> | ||||
|                 </RowTemplate> | ||||
|             </MudTable> | ||||
| 
 | ||||
|             @if(this.SettingsManager.ConfigurationData.Profiles.Count == 0) | ||||
|             { | ||||
|                 <MudText Typo="Typo.h6" Class="mt-3">No profiles configured yet.</MudText> | ||||
|             } | ||||
| 
 | ||||
|             <MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddProfile"> | ||||
|                 Add Profile | ||||
|             </MudButton> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
|         <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options"> | ||||
|             <ConfigurationOption OptionDescription="Save energy?" LabelOn="Energy saving is enabled" LabelOff="Energy saving is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."/> | ||||
|             <ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." /> | ||||
|             <ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/> | ||||
|             <ConfigurationSelect OptionDescription="Navigation bar behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="Select the desired behavior for the navigation bar."/> | ||||
|             <ConfigurationProviderSelection Data="@this.availableProviders" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.")"/> | ||||
|             <ConfigurationSelect OptionDescription="Preselect one of your profiles?" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence."/> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
|         <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Chat" HeaderText="Chat Options"> | ||||
| @ -82,6 +138,7 @@ | ||||
|             <MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg"> | ||||
|                 <ConfigurationOption OptionDescription="Preselect chat options?" LabelOn="Chat options are preselected" LabelOff="No chat options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider."/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for chats?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -125,6 +182,7 @@ | ||||
|                     <ConfigurationText OptionDescription="Preselect another programming language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" Icon="@Icons.Material.Filled.Code" Text="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Coding.PreselectedOtherProgrammingLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Coding.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Coding.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -167,13 +225,13 @@ | ||||
|                 <ConfigurationOption OptionDescription="Preselect whether participants needs to arrive and depart" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" LabelOn="Participants need to arrive and depart" LabelOff="Participants do not need to arrive and depart" State="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectArriveAndDepart)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Agenda.PreselectArriveAndDepart = updatedState)" /> | ||||
|                 <ConfigurationSlider T="int" OptionDescription="Preselect the approx. lunch time" Min="30" Max="120" Step="5" Unit="minutes" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Value="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectLunchTime)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectLunchTime = updatedValue)" /> | ||||
|                 <ConfigurationSlider T="int" OptionDescription="Preselect the approx. break time" Min="10" Max="60" Step="5" Unit="minutes" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Value="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectBreakTime)" ValueUpdate="@(updatedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectBreakTime = updatedValue)" /> | ||||
| 
 | ||||
|                 <ConfigurationSelect OptionDescription="Preselect the agenda language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesTranslationData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which agenda language should be preselected?"/> | ||||
|                 @if (this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage is CommonLanguages.OTHER) | ||||
|                 { | ||||
|                     <ConfigurationText OptionDescription="Preselect another agenda language" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.Agenda.PreselectedOtherLanguage = updatedText)"/> | ||||
|                 } | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.Agenda.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Agenda.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|          | ||||
| @ -215,6 +273,7 @@ | ||||
|                 } | ||||
|                 <ConfigurationSelect OptionDescription="Preselect a writing style" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle)" Data="@ConfigurationSelectDataFactory.GetWritingStyles4EMailData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle = selectedValue)" OptionHelp="Which writing style should be preselected?"/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.EMail.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.EMail.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
| 
 | ||||
| @ -225,6 +284,7 @@ | ||||
|                 <ConfigurationOption OptionDescription="Preselect the web content reader?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions || this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" LabelOn="Web content reader is preselected" LabelOff="Web content reader is not preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader = updatedState)" OptionHelp="When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often."/> | ||||
|                 <ConfigurationOption OptionDescription="Preselect the content cleaner agent?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions || this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" LabelOn="Content cleaner agent is preselected" LabelOff="Content cleaner agent is not preselected" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent = updatedState)" OptionHelp="When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it."/> | ||||
|                 <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProvider = selectedValue)"/> | ||||
|                 <ConfigurationSelect OptionDescription="Preselect one of your profiles?" Disabled="@(() => !this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.LegalCheck.PreselectedProfile = selectedValue)" OptionHelp="Would you like to preselect one of your profiles?"/> | ||||
|             </MudPaper> | ||||
|         </ExpansionPanel> | ||||
|      | ||||
|  | ||||
| @ -160,6 +160,73 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable | ||||
|             this.availableProviders.Add(new (provider.InstanceName, provider.Id)); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
| 
 | ||||
|     #region Profile related | ||||
| 
 | ||||
|     private async Task AddProfile() | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters<ProfileDialog> | ||||
|         { | ||||
|             { x => x.IsEditing, false }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ProfileDialog>("Add Profile", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
|          | ||||
|         var addedProfile = (Profile)dialogResult.Data!; | ||||
|         addedProfile = addedProfile with { Num = this.SettingsManager.ConfigurationData.NextProfileNum++ }; | ||||
|          | ||||
|         this.SettingsManager.ConfigurationData.Profiles.Add(addedProfile); | ||||
|          | ||||
|         await this.SettingsManager.StoreSettings(); | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
|      | ||||
|     private async Task EditProfile(Profile profile) | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters<ProfileDialog> | ||||
|         { | ||||
|             { x => x.DataNum, profile.Num }, | ||||
|             { x => x.DataId, profile.Id }, | ||||
|             { x => x.DataName, profile.Name }, | ||||
|             { x => x.DataNeedToKnow, profile.NeedToKnow }, | ||||
|             { x => x.DataActions, profile.Actions }, | ||||
|             { x => x.IsEditing, true }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ProfileDialog>("Edit Profile", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
|          | ||||
|         var editedProfile = (Profile)dialogResult.Data!; | ||||
|         this.SettingsManager.ConfigurationData.Profiles[this.SettingsManager.ConfigurationData.Profiles.IndexOf(profile)] = editedProfile; | ||||
|          | ||||
|         await this.SettingsManager.StoreSettings(); | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
| 
 | ||||
|     private async Task DeleteProfile(Profile profile) | ||||
|     { | ||||
|         var dialogParameters = new DialogParameters | ||||
|         { | ||||
|             { "Message", $"Are you sure you want to delete the profile '{profile.Name}'?" }, | ||||
|         }; | ||||
|          | ||||
|         var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Profile", dialogParameters, DialogOptions.FULLSCREEN); | ||||
|         var dialogResult = await dialogReference.Result; | ||||
|         if (dialogResult is null || dialogResult.Canceled) | ||||
|             return; | ||||
|          | ||||
|         this.SettingsManager.ConfigurationData.Profiles.Remove(profile); | ||||
|         await this.SettingsManager.StoreSettings(); | ||||
|          | ||||
|         await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); | ||||
|     } | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     #region Implementation of IMessageBusReceiver | ||||
|  | ||||
| @ -130,4 +130,10 @@ public static class ConfigurationSelectDataFactory | ||||
|         foreach (var voice in Enum.GetValues<SentenceStructure>()) | ||||
|             yield return new(voice.Name(), voice); | ||||
|     } | ||||
|      | ||||
|     public static IEnumerable<ConfigurationSelectData<string>> GetProfilesData(IEnumerable<Profile> profiles) | ||||
|     { | ||||
|         foreach (var profile in profiles.GetAllProfiles()) | ||||
|             yield return new(profile.Name, profile.Id); | ||||
|     } | ||||
| } | ||||
| @ -15,12 +15,22 @@ public sealed class Data | ||||
|     /// List of configured providers. | ||||
|     /// </summary> | ||||
|     public List<Provider> Providers { get; init; } = []; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// List of configured profiles. | ||||
|     /// </summary> | ||||
|     public List<Profile> Profiles { get; init; } = []; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The next provider number to use. | ||||
|     /// </summary> | ||||
|     public uint NextProviderNum { get; set; } = 1; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// The next profile number to use. | ||||
|     /// </summary> | ||||
|     public uint NextProfileNum { get; set; } = 1; | ||||
| 
 | ||||
|     public DataApp App { get; init; } = new(); | ||||
| 
 | ||||
|     public DataChat Chat { get; init; } = new(); | ||||
|  | ||||
| @ -55,4 +55,9 @@ public sealed class DataAgenda | ||||
|     /// Preselect a agenda provider? | ||||
|     /// </summary> | ||||
|     public string PreselectedProvider { get; set; } = string.Empty; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Preselect a profile? | ||||
|     /// </summary> | ||||
|     public string PreselectedProfile { get; set; } = string.Empty; | ||||
| } | ||||
| @ -27,4 +27,9 @@ public sealed class DataApp | ||||
|     /// Should we preselect a provider for the entire app? | ||||
|     /// </summary> | ||||
|     public string PreselectedProvider { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Should we preselect a profile for the entire app? | ||||
|     /// </summary> | ||||
|     public string PreselectedProfile { get; set; } = string.Empty; | ||||
| } | ||||
| @ -16,6 +16,11 @@ public sealed class DataChat | ||||
|     /// Should we preselect a provider for the chat? | ||||
|     /// </summary> | ||||
|     public string PreselectedProvider { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Preselect a profile? | ||||
|     /// </summary> | ||||
|     public string PreselectedProfile { get; set; } = string.Empty; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Should we show the latest message after loading? When false, we show the first (aka oldest) message. | ||||
|  | ||||
| @ -28,4 +28,9 @@ public sealed class DataCoding | ||||
|     /// Which coding provider should be preselected? | ||||
|     /// </summary> | ||||
|     public string PreselectedProvider { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Preselect a profile? | ||||
|     /// </summary> | ||||
|     public string PreselectedProfile { get; set; } = string.Empty; | ||||
| } | ||||
| @ -28,6 +28,11 @@ public sealed class DataEMail | ||||
|     /// Preselect a provider? | ||||
|     /// </summary> | ||||
|     public string PreselectedProvider { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Preselect a profile? | ||||
|     /// </summary> | ||||
|     public string PreselectedProfile { get; set; } = string.Empty; | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Preselect a greeting phrase? | ||||
|  | ||||
| @ -26,4 +26,9 @@ public class DataLegalCheck | ||||
|     /// The preselected translator provider. | ||||
|     /// </summary> | ||||
|     public string PreselectedProvider { get; set; } = string.Empty; | ||||
|      | ||||
|     /// <summary> | ||||
|     /// Preselect a profile? | ||||
|     /// </summary> | ||||
|     public string PreselectedProfile { get; set; } = string.Empty; | ||||
| } | ||||
							
								
								
									
										59
									
								
								app/MindWork AI Studio/Settings/Profile.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/MindWork AI Studio/Settings/Profile.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| namespace AIStudio.Settings; | ||||
| 
 | ||||
| public readonly record struct Profile(uint Num, string Id, string Name, string NeedToKnow, string Actions) | ||||
| { | ||||
|     public static readonly Profile NO_PROFILE = new() | ||||
|     { | ||||
|         Name = "Use no profile", | ||||
|         NeedToKnow = string.Empty, | ||||
|         Actions = string.Empty, | ||||
|         Id = Guid.Empty.ToString(), | ||||
|         Num = uint.MaxValue, | ||||
|     }; | ||||
|      | ||||
|     #region Overrides of ValueType | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Returns a string that represents the profile in a human-readable format. | ||||
|     /// </summary> | ||||
|     /// <returns>A string that represents the profile in a human-readable format.</returns> | ||||
|     public override string ToString() => this.Name; | ||||
| 
 | ||||
|     #endregion | ||||
|      | ||||
|     public string ToSystemPrompt() | ||||
|     { | ||||
|         if(this.Num == uint.MaxValue) | ||||
|             return string.Empty; | ||||
|          | ||||
|         var needToKnow =   | ||||
|             $"""
 | ||||
|              What should you know about the user? | ||||
| 
 | ||||
|              ``` | ||||
|              {this.NeedToKnow} | ||||
|              ``` | ||||
|              """;
 | ||||
|          | ||||
|         var actions =  | ||||
|             $"""
 | ||||
|              The user wants you to consider the following things. | ||||
| 
 | ||||
|              ``` | ||||
|              {this.Actions} | ||||
|              ``` | ||||
|              """;
 | ||||
| 
 | ||||
|         if (string.IsNullOrWhiteSpace(this.NeedToKnow)) | ||||
|             return actions; | ||||
| 
 | ||||
|         if (string.IsNullOrWhiteSpace(this.Actions)) | ||||
|             return needToKnow; | ||||
|          | ||||
|         return $"""
 | ||||
|                 {needToKnow} | ||||
| 
 | ||||
|                 {actions} | ||||
|                 """;
 | ||||
|     } | ||||
| } | ||||
| @ -138,4 +138,24 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger) | ||||
| 
 | ||||
|         return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider); | ||||
|     } | ||||
| 
 | ||||
|     public Profile GetPreselectedProfile(Tools.Components component) | ||||
|     { | ||||
|         var preselection = component switch | ||||
|         { | ||||
|             Tools.Components.CHAT => this.ConfigurationData.Chat.PreselectOptions ? this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.ConfigurationData.Chat.PreselectedProfile) : default, | ||||
|             Tools.Components.AGENDA_ASSISTANT => this.ConfigurationData.Agenda.PreselectOptions ? this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.ConfigurationData.Agenda.PreselectedProfile) : default, | ||||
|             Tools.Components.CODING_ASSISTANT => this.ConfigurationData.Coding.PreselectOptions ? this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.ConfigurationData.Coding.PreselectedProfile) : default, | ||||
|             Tools.Components.EMAIL_ASSISTANT => this.ConfigurationData.EMail.PreselectOptions ? this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.ConfigurationData.EMail.PreselectedProfile) : default, | ||||
|             Tools.Components.LEGAL_CHECK_ASSISTANT => this.ConfigurationData.LegalCheck.PreselectOptions ? this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.ConfigurationData.LegalCheck.PreselectedProfile) : default, | ||||
| 
 | ||||
|             _ => default, | ||||
|         }; | ||||
|          | ||||
|         if (preselection != default) | ||||
|             return preselection; | ||||
|          | ||||
|         preselection = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProfile); | ||||
|         return preselection != default ? preselection : Profile.NO_PROFILE; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								app/MindWork AI Studio/Tools/ProfileExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/MindWork AI Studio/Tools/ProfileExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| using AIStudio.Settings; | ||||
| 
 | ||||
| namespace AIStudio.Tools; | ||||
| 
 | ||||
| public static class ProfileExtensions | ||||
| { | ||||
|     public static IEnumerable<Profile> GetAllProfiles(this IEnumerable<Profile> profiles) | ||||
|     { | ||||
|         yield return Profile.NO_PROFILE; | ||||
|         foreach (var profile in profiles) | ||||
|             yield return profile; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								app/MindWork AI Studio/wwwroot/changelog/v0.9.7.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/MindWork AI Studio/wwwroot/changelog/v0.9.7.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| # v0.9.7, build 182 (2024-09-09 xx:xx UTC) | ||||
| - Added the possibility to define multiple profiles in the settings. Use profiles to share some information about you with the AI. | ||||
| - Added profiles to the chat interface. You can now select a profile for each chat or even change the profile during a chat. | ||||
| - Added profiles to some assistants. It makes no sense to have profiles for, e.g., translation, etc. | ||||
| - Added the possibility to preselect any of your profiles as the default profile for the entire app or configure individual profiles for assistants and chats.  | ||||
| - Added an introductory description to the provider settings. | ||||
| - Added an indicator for the current and maximal length of the provider instance name. | ||||
| - Fixed the bug that the model name for Fireworks was not loaded when editing the provider settings. | ||||
| - Improved hyphenation for continuous text so that the rules of the respective language are taken into account where possible. | ||||
| - Improved the rules for provider names: unnecessary restrictions from earlier versions have been removed. You can now use emojis in your provider names when you like. | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user