diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor b/app/MindWork AI Studio/Assistants/AssistantBase.razor
index 9d7673e..ba18f75 100644
--- a/app/MindWork AI Studio/Assistants/AssistantBase.razor
+++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor
@@ -93,6 +93,11 @@
Reset
+
+ @if (this.AllowProfiles)
+ {
+
+ }
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
index 9608b1c..c63659f 100644
--- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
+++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
@@ -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 = [],
};
}
diff --git a/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs b/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs
index f0dd044..21227df 100644
--- a/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs
+++ b/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs
@@ -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;
diff --git a/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs b/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs
index f23f710..e239c8f 100644
--- a/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs
+++ b/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs
@@ -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 FooterButtons => [];
diff --git a/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs b/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs
index a7ada74..6d8aef6 100644
--- a/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs
+++ b/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs
@@ -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;
diff --git a/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs b/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs
index 032a50a..c238aac 100644
--- a/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs
+++ b/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs
@@ -47,6 +47,8 @@ public partial class AssistantSynonyms : AssistantBaseCore
the {this.SystemPromptLanguage()} language.
""";
+ protected override bool AllowProfiles => false;
+
protected override IReadOnlyList FooterButtons => [];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
diff --git a/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs b/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs
index c8461e2..20c0300 100644
--- a/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs
+++ b/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs
@@ -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 FooterButtons => [];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
diff --git a/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs b/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs
index 8485b16..29cc0ef 100644
--- a/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs
+++ b/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs
@@ -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 FooterButtons => [];
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
diff --git a/app/MindWork AI Studio/Components/MudJustifiedText.cs b/app/MindWork AI Studio/Components/MudJustifiedText.cs
new file mode 100644
index 0000000..887308e
--- /dev/null
+++ b/app/MindWork AI Studio/Components/MudJustifiedText.cs
@@ -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
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/MudTextList.razor b/app/MindWork AI Studio/Components/MudTextList.razor
index bb70247..f0ec629 100644
--- a/app/MindWork AI Studio/Components/MudTextList.razor
+++ b/app/MindWork AI Studio/Components/MudTextList.razor
@@ -2,7 +2,7 @@
@foreach(var item in this.Items)
{
- @item.Header: @item.Text
+ @item.Header: @item.Text
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/ProfileSelection.razor b/app/MindWork AI Studio/Components/ProfileSelection.razor
new file mode 100644
index 0000000..a832ec6
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ProfileSelection.razor
@@ -0,0 +1,10 @@
+
+
+ @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
+ {
+
+ @profile.Name
+
+ }
+
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/ProfileSelection.razor.cs b/app/MindWork AI Studio/Components/ProfileSelection.razor.cs
new file mode 100644
index 0000000..55f2fa9
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ProfileSelection.razor.cs
@@ -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 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);
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor
new file mode 100644
index 0000000..fb44439
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor
@@ -0,0 +1,93 @@
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+ @* ReSharper disable once CSharpWarnings::CS8974 *@
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+ @if(this.IsEditing)
+ {
+ @:Update
+ }
+ else
+ {
+ @:Add
+ }
+
+
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
new file mode 100644
index 0000000..7391c1d
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
@@ -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!;
+
+ ///
+ /// The profile's number in the list.
+ ///
+ [Parameter]
+ public uint DataNum { get; set; }
+
+ ///
+ /// The profile's ID.
+ ///
+ [Parameter]
+ public string DataId { get; set; } = Guid.NewGuid().ToString();
+
+ ///
+ /// The profile name chosen by the user.
+ ///
+ [Parameter]
+ public string DataName { get; set; } = string.Empty;
+
+ ///
+ /// What should the LLM know about you?
+ ///
+ [Parameter]
+ public string DataNeedToKnow { get; set; } = string.Empty;
+
+ ///
+ /// What actions should the LLM take?
+ ///
+ [Parameter]
+ public string DataActions { get; set; } = string.Empty;
+
+ ///
+ /// Should the dialog be in editing mode?
+ ///
+ [Parameter]
+ public bool IsEditing { get; init; }
+
+ [Inject]
+ private SettingsManager SettingsManager { get; init; } = null!;
+
+ [Inject]
+ private ILogger Logger { get; init; } = null!;
+
+ private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new();
+
+ ///
+ /// The list of used profile names. We need this to check for uniqueness.
+ ///
+ private List 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();
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
index c6fd8dc..82cb514 100644
--- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
@@ -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"
diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
index 02d29d2..0d55ade 100644
--- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
@@ -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();
diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor
index f87bdde..3d5d3b7 100644
--- a/app/MindWork AI Studio/Pages/Chat.razor
+++ b/app/MindWork AI Studio/Pages/Chat.razor
@@ -69,6 +69,8 @@
}
+
+
diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs
index e3ff30e..e94a9ca 100644
--- a/app/MindWork AI Studio/Pages/Chat.razor.cs
+++ b/app/MindWork AI Studio/Pages/Chat.razor.cs
@@ -35,6 +35,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
private static readonly Dictionary 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(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 = [],
};
}
diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor
index 02586ce..32f979c 100644
--- a/app/MindWork AI Studio/Pages/Settings.razor
+++ b/app/MindWork AI Studio/Pages/Settings.razor
@@ -11,6 +11,12 @@
Configured Providers
+
+ 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.
+
@@ -68,12 +74,62 @@
+
+ Your Profiles
+
+ 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.
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+ #
+ Profile Name
+ Actions
+
+
+ @context.Num
+ @context.Name
+
+
+ Edit
+
+
+ Delete
+
+
+
+
+
+ @if(this.SettingsManager.ConfigurationData.Profiles.Count == 0)
+ {
+ No profiles configured yet.
+ }
+
+
+ Add Profile
+
+
+
+
@@ -82,6 +138,7 @@
+
@@ -125,6 +182,7 @@
}
+
@@ -167,13 +225,13 @@
-
@if (this.SettingsManager.ConfigurationData.Agenda.PreselectedTargetLanguage is CommonLanguages.OTHER)
{
}
+
@@ -215,6 +273,7 @@
}
+
@@ -225,6 +284,7 @@
+
diff --git a/app/MindWork AI Studio/Pages/Settings.razor.cs b/app/MindWork AI Studio/Pages/Settings.razor.cs
index daa216b..5c09f98 100644
--- a/app/MindWork AI Studio/Pages/Settings.razor.cs
+++ b/app/MindWork AI Studio/Pages/Settings.razor.cs
@@ -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
+ {
+ { x => x.IsEditing, false },
+ };
+
+ var dialogReference = await this.DialogService.ShowAsync("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(this, Event.CONFIGURATION_CHANGED);
+ }
+
+ private async Task EditProfile(Profile profile)
+ {
+ var dialogParameters = new DialogParameters
+ {
+ { 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("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(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("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(this, Event.CONFIGURATION_CHANGED);
+ }
+
#endregion
#region Implementation of IMessageBusReceiver
diff --git a/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs b/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs
index 2266b6a..c8dfd39 100644
--- a/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs
+++ b/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs
@@ -130,4 +130,10 @@ public static class ConfigurationSelectDataFactory
foreach (var voice in Enum.GetValues())
yield return new(voice.Name(), voice);
}
+
+ public static IEnumerable> GetProfilesData(IEnumerable profiles)
+ {
+ foreach (var profile in profiles.GetAllProfiles())
+ yield return new(profile.Name, profile.Id);
+ }
}
\ 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 586c763..f1dc886 100644
--- a/app/MindWork AI Studio/Settings/DataModel/Data.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs
@@ -15,12 +15,22 @@ public sealed class Data
/// List of configured providers.
///
public List Providers { get; init; } = [];
+
+ ///
+ /// List of configured profiles.
+ ///
+ public List Profiles { get; init; } = [];
///
/// The next provider number to use.
///
public uint NextProviderNum { get; set; } = 1;
+ ///
+ /// The next profile number to use.
+ ///
+ public uint NextProfileNum { get; set; } = 1;
+
public DataApp App { get; init; } = new();
public DataChat Chat { get; init; } = new();
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataAgenda.cs b/app/MindWork AI Studio/Settings/DataModel/DataAgenda.cs
index 46ef668..3960274 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataAgenda.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataAgenda.cs
@@ -55,4 +55,9 @@ public sealed class DataAgenda
/// Preselect a agenda provider?
///
public string PreselectedProvider { get; set; } = string.Empty;
+
+ ///
+ /// Preselect a profile?
+ ///
+ public string PreselectedProfile { get; set; } = string.Empty;
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
index 55b9e93..ce1f475 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
@@ -27,4 +27,9 @@ public sealed class DataApp
/// Should we preselect a provider for the entire app?
///
public string PreselectedProvider { get; set; } = string.Empty;
+
+ ///
+ /// Should we preselect a profile for the entire app?
+ ///
+ public string PreselectedProfile { get; set; } = string.Empty;
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs
index eca8569..f68865f 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs
@@ -16,6 +16,11 @@ public sealed class DataChat
/// Should we preselect a provider for the chat?
///
public string PreselectedProvider { get; set; } = string.Empty;
+
+ ///
+ /// Preselect a profile?
+ ///
+ public string PreselectedProfile { get; set; } = string.Empty;
///
/// Should we show the latest message after loading? When false, we show the first (aka oldest) message.
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataCoding.cs b/app/MindWork AI Studio/Settings/DataModel/DataCoding.cs
index 7608f61..f83c616 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataCoding.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataCoding.cs
@@ -28,4 +28,9 @@ public sealed class DataCoding
/// Which coding provider should be preselected?
///
public string PreselectedProvider { get; set; } = string.Empty;
+
+ ///
+ /// Preselect a profile?
+ ///
+ public string PreselectedProfile { get; set; } = string.Empty;
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataEMail.cs b/app/MindWork AI Studio/Settings/DataModel/DataEMail.cs
index ab659fc..a913c7f 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataEMail.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataEMail.cs
@@ -28,6 +28,11 @@ public sealed class DataEMail
/// Preselect a provider?
///
public string PreselectedProvider { get; set; } = string.Empty;
+
+ ///
+ /// Preselect a profile?
+ ///
+ public string PreselectedProfile { get; set; } = string.Empty;
///
/// Preselect a greeting phrase?
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataLegalCheck.cs b/app/MindWork AI Studio/Settings/DataModel/DataLegalCheck.cs
index 80dc53f..c72b157 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataLegalCheck.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataLegalCheck.cs
@@ -26,4 +26,9 @@ public class DataLegalCheck
/// The preselected translator provider.
///
public string PreselectedProvider { get; set; } = string.Empty;
+
+ ///
+ /// Preselect a profile?
+ ///
+ public string PreselectedProfile { get; set; } = string.Empty;
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/Profile.cs b/app/MindWork AI Studio/Settings/Profile.cs
new file mode 100644
index 0000000..b7a7da6
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/Profile.cs
@@ -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
+
+ ///
+ /// Returns a string that represents the profile in a human-readable format.
+ ///
+ /// A string that represents the profile in a human-readable format.
+ 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}
+ """;
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs
index 36441bc..428c518 100644
--- a/app/MindWork AI Studio/Settings/SettingsManager.cs
+++ b/app/MindWork AI Studio/Settings/SettingsManager.cs
@@ -138,4 +138,24 @@ public sealed class SettingsManager(ILogger 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;
+ }
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ProfileExtensions.cs b/app/MindWork AI Studio/Tools/ProfileExtensions.cs
new file mode 100644
index 0000000..53cb256
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ProfileExtensions.cs
@@ -0,0 +1,13 @@
+using AIStudio.Settings;
+
+namespace AIStudio.Tools;
+
+public static class ProfileExtensions
+{
+ public static IEnumerable GetAllProfiles(this IEnumerable profiles)
+ {
+ yield return Profile.NO_PROFILE;
+ foreach (var profile in profiles)
+ yield return profile;
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.7.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.7.md
new file mode 100644
index 0000000..1427636
--- /dev/null
+++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.7.md
@@ -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.
\ No newline at end of file