Make the workspace optional when building a chat launcher

This commit is contained in:
Thorsten Sommer 2026-09-14 09:20:55 +02:00
parent 3548b1d44d
commit d25bdb35b7
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
6 changed files with 27 additions and 27 deletions

View File

@ -23,7 +23,7 @@
</MudField>
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
@(this.createChatLauncher
? T("The direct chat launcher tile has no input form of its own. It opens a new chat right away, in the workspace you name below and with the provider, profile, chat template, and data sources you select there.")
? T("The direct chat launcher tile has no input form of its own. It opens a new chat right away, with the provider, profile, chat template, and data sources you select below. Name a workspace for that chat, or leave the workspace empty to open a disappearing chat.")
: T("The assistant asks users for input through a form and builds its own prompt from it."))
</MudJustifiedText>
@if (this.createChatLauncher)
@ -39,8 +39,7 @@
@bind-ProfileId="@this.launcherProfileId"
@bind-ChatTemplateId="@this.launcherChatTemplateId"
@bind-DataSourceIds="@this.launcherDataSourceIds"
@bind-ToolIds="@this.launcherToolIds"
ValidateWorkspaceName="@this.ValidateLauncherWorkspaceName"/>
@bind-ToolIds="@this.launcherToolIds"/>
</MudPaper>
}

View File

@ -375,14 +375,6 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
return null;
}
private string? ValidateLauncherWorkspaceName(string workspaceName)
{
if (this.createChatLauncher && string.IsNullOrWhiteSpace(workspaceName))
return T("Please select or enter a workspace name for the chat launcher.");
return null;
}
private async Task GenerateAssistantSpec()
{
await this.Form!.Validate();
@ -589,7 +581,8 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
// The description stays required for both kinds of assistant. Users who only want a tile
// usually flip the switch before typing anything, so the Builder offers a starting point they
// can edit or replace. The workspace is picked after that, hence the suggestion is refreshed
// whenever the workspace changes:
// whenever the workspace changes — including when it is cleared again, which turns the tile
// into one that opens a disappearing chat:
//
private void SuggestLauncherDescription()
{
@ -597,8 +590,9 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
return;
var suggestion = T("Create a tile that opens a preconfigured chat directly, without an input form of its own.");
if (!string.IsNullOrWhiteSpace(this.launcherWorkspaceName))
suggestion = $"{suggestion} {string.Format(T("Workspace: {0}"), this.launcherWorkspaceName.Trim())}";
suggestion = string.IsNullOrWhiteSpace(this.launcherWorkspaceName)
? $"{suggestion} {T("The chat belongs to no workspace and disappears again.")}"
: $"{suggestion} {string.Format(T("Workspace: {0}"), this.launcherWorkspaceName.Trim())}";
this.assistantDescription = suggestion;
this.descriptionSuggestion = suggestion;

View File

@ -10,7 +10,15 @@
</MudSelect>
}
<MudTextField T="string" Text="@this.WorkspaceName" TextChanged="@this.SetWorkspaceName" Validation="@this.ValidateWorkspaceName" AdornmentIcon="@Icons.Material.Filled.CreateNewFolder" Adornment="Adornment.Start" IconSize="Size.Small" Label="@T("Workspace name")" 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-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<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"/>
@* 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. *@
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
@(this.OpensTemporaryChat
? 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>
<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))

View File

@ -3,8 +3,9 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// The selection a direct chat launcher needs: the workspace its chat is created in, and the
/// provider, profile, chat template, and data sources that chat starts with.
/// The selection a direct chat launcher needs: the workspace its chat is created in — or no
/// workspace, for a disappearing chat — and the provider, profile, chat template, and data sources
/// that chat starts with.
/// </summary>
/// <remarks>
/// The Assistant Builder uses this form to describe a launcher it is about to generate, while the
@ -15,7 +16,8 @@ public partial class DirectChatLauncherForm : MSGComponentBase
{
/// <summary>
/// The name of the workspace the launcher opens its chat in. The workspace is created when it
/// does not exist yet, hence this is a free-text field and not a workspace ID.
/// does not exist yet, hence this is a free-text field and not a workspace ID. An empty name is
/// a choice of its own: the launcher then opens a disappearing chat.
/// </summary>
[Parameter]
public string WorkspaceName { get; set; } = string.Empty;
@ -75,11 +77,9 @@ public partial class DirectChatLauncherForm : MSGComponentBase
public EventCallback<HashSet<string>> ToolIdsChanged { get; set; }
/// <summary>
/// Validates the workspace name. The hosts differ here: the Builder requires a name only while
/// its launcher switch is on, whereas the settings dialog always requires one.
/// Whether the launcher currently describes a chat without a workspace.
/// </summary>
[Parameter]
public Func<string, string?>? ValidateWorkspaceName { get; set; }
private bool OpensTemporaryChat => string.IsNullOrWhiteSpace(this.WorkspaceName);
private IReadOnlyList<WorkspaceTreeWorkspace> availableWorkspaces = [];

View File

@ -32,8 +32,7 @@
@bind-ProfileId="@this.profileId"
@bind-ChatTemplateId="@this.chatTemplateId"
@bind-DataSourceIds="@this.dataSourceIds"
@bind-ToolIds="@this.toolIds"
ValidateWorkspaceName="@this.ValidateWorkspaceName"/>
@bind-ToolIds="@this.toolIds"/>
</MudPaper>
</MudForm>

View File

@ -152,7 +152,9 @@ public partial class DirectChatLauncherSettingsDialog : MSGComponentBase
//
// An empty selection means "use the chat defaults" and is left out of the plugin, whereas
// the empty GUID explicitly selects no profile or no chat template:
// the empty GUID explicitly selects no profile or no chat template. Clearing the workspace
// name is a change of its own: the tile then opens a disappearing chat, and the writer
// switches the launch behavior accordingly.
//
return new(
this.workspaceName.Trim(),
@ -248,8 +250,6 @@ public partial class DirectChatLauncherSettingsDialog : MSGComponentBase
private string? ValidateDescription(string value) => string.IsNullOrWhiteSpace(value) ? T("Please provide a description for this tile.") : null;
private string? ValidateWorkspaceName(string value) => string.IsNullOrWhiteSpace(value) ? T("Please select or enter a workspace name for this tile.") : null;
private void Cancel() => this.MudDialog.Cancel();
private static Guid? ParseOptionalGuid(string value) => Guid.TryParse(value, out var parsed) ? parsed : null;