Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
698fc6a
Let chat templates carry a preselected profile and provider
Oct 21, 2026
f50020f
Start chats with the profile and provider of their chat template
Oct 21, 2026
9b2e430
Let users pick the profile and provider in the chat template dialog
Oct 21, 2026
4ee0558
Document profile and provider preselection of chat templates
Oct 21, 2026
a9774e8
Let chat templates name a workspace for their chats
Oct 21, 2026
f38770f
Start chats in the workspace of their chat template
Oct 21, 2026
6833a98
Let users pick the workspace in the chat template dialog
Oct 21, 2026
8b16266
Document the workspace of chat templates
Oct 21, 2026
c547b82
Start with the default profile when a template's profile is gone
Oct 21, 2026
7a25f0a
Add translations for the chat template preselections
Oct 21, 2026
e064f34
Create template workspaces lazily and stop carrying over preselections
Oct 21, 2026
0f72c20
I18n
Oct 21, 2026
25a63d0
Lock the workspace of a chat template while workspaces are off
Oct 22, 2026
a6fca33
Leave out the data sources of launchers while RAG is off
Oct 22, 2026
ade7d09
Localization
Oct 22, 2026
9be6d7a
Merge branch 'main' into chat-templates-choose-profile-provider-works…
SommerEngineering Oct 22, 2026
7a65efc
Improved how chats find the workspace of their chat template
SommerEngineering Oct 22, 2026
5f9e3da
Fixed races while a new chat waits for the workspace of its chat temp…
SommerEngineering Oct 22, 2026
fd162db
Fixed canceling a change of the chat template keeping the new template
SommerEngineering Oct 22, 2026
ffce51c
Improved checking the provider and profile of chat templates in one p…
SommerEngineering Oct 22, 2026
668b6dc
Improved the workspace fields of chat templates and chat launchers by…
SommerEngineering Oct 22, 2026
3c05c21
Improved the documentation of chat template preselections
SommerEngineering Oct 22, 2026
38327a2
Improved the changelog of chat template preselections
SommerEngineering Oct 22, 2026
4b89866
Improved the German texts of chat template preselections
SommerEngineering Oct 22, 2026
6d170df
Improved the results of resolving chat launcher selections by using n…
SommerEngineering Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ startup.env
libpdfium.dylib
libpdfium.so
libpdfium.dll
pdfium.dll

# Ignore qdrant database:
qdrant-aarch64-apple-darwin
Expand Down
96 changes: 96 additions & 0 deletions app/MindWork AI Studio/Assistants/I18N/allTexts.lua

Large diffs are not rendered by default.

214 changes: 182 additions & 32 deletions app/MindWork AI Studio/Components/ChatComponent.razor.cs

Large diffs are not rendered by default.

60 changes: 37 additions & 23 deletions app/MindWork AI Studio/Components/DirectChatLauncherForm.razor
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase

@if (this.availableWorkspaces.Count > 0)
{
<MudSelect T="string" Value="@this.WorkspaceName" ValueChanged="@this.SelectExistingWorkspace" Strict="@false" Label="@T("Existing workspace (Optional)")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Folder">
@foreach (var workspace in this.availableWorkspaces)
{
<MudSelectItem T="string" Value="@workspace.Name">@workspace.Name</MudSelectItem>
}
</MudSelect>
}

<MudTextField T="string" Text="@this.WorkspaceName" TextChanged="@this.SetWorkspaceName" AdornmentIcon="@(this.OpensTemporaryChat ? Icons.Material.Filled.Timer : Icons.Material.Filled.CreateNewFolder)" Adornment="Adornment.Start" IconSize="Size.Small" Label="@T("Workspace name (Optional)")" HelperText="@T("Choose an existing workspace or enter a name that should be created when the launcher is opened.")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-1" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<WorkspaceNameField WorkspaceName="@this.WorkspaceName" WorkspaceNameChanged="@this.SetWorkspaceName" SelectLabel="@T("Existing workspace (Optional)")" Label="@T("Workspace name (Optional)")" HelperText="@T("Choose an existing workspace or enter a name that should be created when the launcher is opened.")" AdornmentIcon="@(this.OpensTemporaryChat ? Icons.Material.Filled.Timer : Icons.Material.Filled.CreateNewFolder)" AdornmentIconSize="Size.Small" TextFieldClass="mb-1"/>

@* The tile behaves differently with and without a workspace, so the form says which of the two is
chosen right now instead of only explaining that both are possible. *@
Expand All @@ -19,6 +10,12 @@
? T("Without a workspace, the tile opens a disappearing chat: it belongs to no workspace and is deleted according to your workspace maintenance settings.")
: T("The tile opens its chat in this workspace and creates the workspace when it does not exist yet."))
</MudText>
@if (this.OpensTemporaryChat && this.SettingsManager.GetChatTemplateWorkspaceName(this.SelectedChatTemplate) is { } templateWorkspaceName)
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@string.Format(T("The chosen chat template keeps its chats in the workspace '{0}', so the tile opens its chat there instead. A workspace entered here wins over the one of the template."), templateWorkspaceName)
</MudText>
}
<MudSelect T="string" Value="@this.ProviderId" ValueChanged="@this.SetProviderId" Label="@T("Chat provider")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.SmartToy">
<MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem>
@foreach (var provider in this.SettingsManager.GetConfidentProviders(Components.CHAT))
Expand All @@ -28,6 +25,12 @@
</MudSelectItem>
}
</MudSelect>
@if (this.SelectedChatTemplate.ProviderId is not null)
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template preselects a provider of its own, and it wins over a selection made here as long as that provider can be used for chats.")
</MudText>
}
<MudSelect T="string" Value="@this.ProfileId" ValueChanged="@this.SetProfileId" Label="@T("Chat profile")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person">
<MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem>
<MudSelectItem T="string" Value="@Guid.Empty.ToString()">@T("Use no profile")</MudSelectItem>
Expand All @@ -36,6 +39,12 @@
<MudSelectItem T="string" Value="@profile.Id">@profile.GetSafeName()</MudSelectItem>
}
</MudSelect>
@if (this.SelectedChatTemplate is { AllowProfileUsage: true, ProfileId: not null })
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template preselects a profile of its own, and it wins over a selection made here as long as that profile exists.")
</MudText>
}
<MudSelect T="string" Value="@this.ChatTemplateId" ValueChanged="@this.SetChatTemplateId" Label="@T("Chat template")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Article">
<MudSelectItem T="string" Value="@string.Empty">@T("Use chat default")</MudSelectItem>
<MudSelectItem T="string" Value="@Guid.Empty.ToString()">@T("Use no chat template")</MudSelectItem>
Expand All @@ -44,20 +53,25 @@
<MudSelectItem T="string" Value="@chatTemplate.Id">@chatTemplate.GetSafeName()</MudSelectItem>
}
</MudSelect>
<MudSelect T="string" Label="@T("Data sources (Optional)")" MultiSelection="@true" SelectedValues="@this.DataSourceIds" SelectedValuesChanged="@this.SetDataSourceIds" MultiSelectionTextFunc="@this.GetSelectedDataSourceText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@AppIcons.DATABASE">
@foreach (var dataSource in this.SettingsManager.ConfigurationData.DataSources)
@* Without the RAG preview, the chat uses no data sources, so there is nothing to choose here. A
selection made before stays with the launcher and counts once RAG is switched on again: *@
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
<MudSelect T="string" Label="@T("Data sources (Optional)")" MultiSelection="@true" SelectedValues="@this.DataSourceIds" SelectedValuesChanged="@this.SetDataSourceIds" MultiSelectionTextFunc="@this.GetSelectedDataSourceText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="mb-3 rounded-lg" Adornment="Adornment.Start" AdornmentIcon="@AppIcons.DATABASE">
@foreach (var dataSource in this.SettingsManager.ConfigurationData.DataSources)
{
<MudSelectItem T="string" Value="@dataSource.Id">@dataSource.Name</MudSelectItem>
}
</MudSelect>

@* A chat template wins over what is chosen here, so the form says so before somebody picks
something which would never take effect: *@
@if (this.SelectedChatTemplate.DataSourceOptions is not null)
{
<MudSelectItem T="string" Value="@dataSource.Id">@dataSource.Name</MudSelectItem>
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template brings data sources of its own, and those win over a selection made here. Only a template can also leave the choice of sources to the AI, which is why it decides this on its own.")
</MudText>
}
</MudSelect>

@* A chat template wins over what is chosen here, so the form says so before somebody picks
something which would never take effect: *@
@if (this.SelectedChatTemplate.DataSourceOptions is not null)
{
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@T("The chosen chat template brings data sources of its own, and those win over a selection made here. Only a template can also leave the choice of sources to the AI, which is why it decides this on its own.")
</MudText>
}

<ToolSelectionField Component="Components.CHAT" SelectedToolIds="@this.ToolIds" SelectedToolIdsChanged="@this.SetToolIds" Label="@T("Tools (Optional)")" Help="@T("These tools are preselected when the chat opens. Users can change the selection in the chat, and every tool has to meet the confidence requirements of the provider in use.")"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,37 +96,6 @@ public partial class DirectChatLauncherForm : MSGComponentBase
? this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT)
: this.SettingsManager.GetChatTemplateById(this.ChatTemplateId);

private IReadOnlyList<WorkspaceTreeWorkspace> availableWorkspaces = [];

private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();

#region Overrides of MSGComponentBase

protected override async Task OnInitializedAsync()
{
// Configure the spellchecking for the workspace name input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);

await base.OnInitializedAsync();

var workspaceSnapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
this.availableWorkspaces = workspaceSnapshot.Workspaces;
}

#endregion

//
// Picking an existing workspace fills the name field. Clearing the select must not wipe a name
// the user typed, though, so an empty selection is ignored:
//
private async Task SelectExistingWorkspace(string workspaceName)
{
if (string.IsNullOrWhiteSpace(workspaceName))
return;

await this.SetWorkspaceName(workspaceName);
}

private async Task SetWorkspaceName(string workspaceName)
{
this.WorkspaceName = workspaceName;
Expand Down
13 changes: 13 additions & 0 deletions app/MindWork AI Studio/Components/WorkspaceNameField.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@inherits MSGComponentBase

@if (this.availableWorkspaces.Count > 0 && !this.ReadOnly)
{
<MudSelect T="string" Value="@this.WorkspaceName" ValueChanged="@this.SelectExistingWorkspace" Strict="@false" Label="@this.SelectLabel" Variant="Variant.Outlined" Margin="Margin.Dense" Class="@this.SelectClass" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Folder">
@foreach (var workspace in this.availableWorkspaces)
{
<MudSelectItem T="string" Value="@workspace.Name">@workspace.Name</MudSelectItem>
}
</MudSelect>
}

<MudTextField T="string" Text="@this.WorkspaceName" TextChanged="@this.SetWorkspaceName" Label="@this.Label" HelperText="@this.HelperText" Validation="@this.Validation" Immediate="@this.Immediate" ReadOnly="@this.ReadOnly" Variant="Variant.Outlined" Margin="Margin.Dense" Class="@this.TextFieldClass" Adornment="Adornment.Start" AdornmentIcon="@this.AdornmentIcon" IconSize="@this.AdornmentIconSize" UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
107 changes: 107 additions & 0 deletions app/MindWork AI Studio/Components/WorkspaceNameField.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using Microsoft.AspNetCore.Components;

namespace AIStudio.Components;

/// <summary>
/// Names a workspace by its name: an existing one to pick from, or a new name to type.
/// </summary>
/// <remarks>
/// Assistant chat launchers and chat templates both name their workspace this way, because a
/// workspace has an ID of its own on every computer, and both create it when it does not exist
/// yet. The field offers the existing workspaces only as a shortcut; the name it hands out is
/// whatever the text field says. All texts come from the form around it, which knows what the
/// name is used for.
/// </remarks>
public partial class WorkspaceNameField : MSGComponentBase
{
/// <summary>
/// The name of the workspace. An empty name is up to the form around the field, e.g., a
/// disappearing chat for an assistant chat launcher.
/// </summary>
[Parameter]
public string WorkspaceName { get; set; } = string.Empty;

[Parameter]
public EventCallback<string> WorkspaceNameChanged { get; set; }

/// <summary>
/// The label of the selection of existing workspaces.
/// </summary>
[Parameter]
public string SelectLabel { get; set; } = string.Empty;

/// <summary>
/// The label of the text field for the name.
/// </summary>
[Parameter]
public string Label { get; set; } = string.Empty;

[Parameter]
public string HelperText { get; set; } = string.Empty;

[Parameter]
public string AdornmentIcon { get; set; } = Icons.Material.Filled.CreateNewFolder;

[Parameter]
public Size AdornmentIconSize { get; set; } = Size.Medium;

/// <summary>
/// The validation of the name, or null for none.
/// </summary>
[Parameter]
public Func<string, string?>? Validation { get; set; }

/// <summary>
/// Whether every keystroke counts as a change, e.g., so that a validation follows the typing.
/// </summary>
[Parameter]
public bool Immediate { get; set; }

/// <summary>
/// Shows the name without letting the user change it. The selection of existing workspaces
/// is left out then, since picking from it would change the name.
/// </summary>
[Parameter]
public bool ReadOnly { get; set; }

[Parameter]
public string SelectClass { get; set; } = "mb-3 rounded-lg";

[Parameter]
public string TextFieldClass { get; set; } = "mb-3";

private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();

private IReadOnlyList<WorkspaceTreeWorkspace> availableWorkspaces = [];

#region Overrides of MSGComponentBase

protected override async Task OnInitializedAsync()
{
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
await base.OnInitializedAsync();

var workspaceSnapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
this.availableWorkspaces = workspaceSnapshot.Workspaces;
}

#endregion

//
// Picking an existing workspace fills the name field. Clearing the select must not wipe a name
// the user typed, though, so an empty selection is ignored:
//
private async Task SelectExistingWorkspace(string workspaceName)
{
if (string.IsNullOrWhiteSpace(workspaceName))
return;

await this.SetWorkspaceName(workspaceName);
}

private async Task SetWorkspaceName(string workspaceName)
{
this.WorkspaceName = workspaceName;
await this.WorkspaceNameChanged.InvokeAsync(workspaceName);
}
}
Loading
Loading