mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-24 20:52:11 +00:00
Fixed the chat user prompt being cleared when toggling the workspace view
This commit is contained in:
parent
7bf1c4ae25
commit
9a947579cc
@ -37,7 +37,7 @@
|
|||||||
<MudTextField
|
<MudTextField
|
||||||
T="string"
|
T="string"
|
||||||
@ref="@this.inputField"
|
@ref="@this.inputField"
|
||||||
@bind-Text="@this.userInput"
|
@bind-Text="@this.UserInput"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
AutoGrow="@true"
|
AutoGrow="@true"
|
||||||
Lines="3"
|
Lines="3"
|
||||||
@ -96,28 +96,28 @@
|
|||||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
|
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
|
||||||
{
|
{
|
||||||
<MudTooltip Text="@T("Move the chat to a workspace, or to another if it is already in one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Move the chat to a workspace, or to another if it is already in one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeSaved || this.IsCurrentChatStreaming)" OnClick="@(() => this.MoveChatToWorkspace())"/>
|
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeSaved || this.IsCurrentChatStreaming)" OnClick="@this.MoveChatToWorkspace"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
|
|
||||||
<AttachDocuments Name="File Attachments" Layer="@DropLayers.PAGES" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true" Provider="@this.Provider"/>
|
<AttachDocuments Name="File Attachments" Layer="@DropLayers.PAGES" DocumentPaths="@this.ComposerState.FileAttachments" DocumentPathsChanged="@this.ComposerAttachmentsChanged" CatchAllDocuments="true" UseSmallForm="true" Provider="@this.Provider"/>
|
||||||
|
|
||||||
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
||||||
|
|
||||||
<MudTooltip Text="@T("Bold")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Bold")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.FormatBold" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_BOLD)" Disabled="@this.IsInputForbidden()"/>
|
<MudIconButton Icon="@Icons.Material.Filled.FormatBold" OnClick="@(() => this.ApplyMarkdownFormat(MARKDOWN_BOLD))" Disabled="@this.IsInputForbidden()"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
<MudTooltip Text="@T("Italic")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Italic")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.FormatItalic" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_ITALIC)" Disabled="@this.IsInputForbidden()"/>
|
<MudIconButton Icon="@Icons.Material.Filled.FormatItalic" OnClick="@(() => this.ApplyMarkdownFormat(MARKDOWN_ITALIC))" Disabled="@this.IsInputForbidden()"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
<MudTooltip Text="@T("Heading")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Heading")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.TextFields" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_HEADING)" Disabled="@this.IsInputForbidden()"/>
|
<MudIconButton Icon="@Icons.Material.Filled.TextFields" OnClick="@(() => this.ApplyMarkdownFormat(MARKDOWN_HEADING))" Disabled="@this.IsInputForbidden()"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
<MudTooltip Text="@T("Bulleted List")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Bulleted List")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.FormatListBulleted" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_BULLET_LIST)" Disabled="@this.IsInputForbidden()"/>
|
<MudIconButton Icon="@Icons.Material.Filled.FormatListBulleted" OnClick="@(() => this.ApplyMarkdownFormat(MARKDOWN_BULLET_LIST))" Disabled="@this.IsInputForbidden()"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
<MudTooltip Text="@T("Code")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Code")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Code" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_CODE)" Disabled="@this.IsInputForbidden()"/>
|
<MudIconButton Icon="@Icons.Material.Filled.Code" OnClick="@(() => this.ApplyMarkdownFormat(MARKDOWN_CODE))" Disabled="@this.IsInputForbidden()"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
|
||||||
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
||||||
@ -137,7 +137,7 @@
|
|||||||
@if (this.IsCurrentChatStreaming)
|
@if (this.IsCurrentChatStreaming)
|
||||||
{
|
{
|
||||||
<MudTooltip Text="@T("Stop generation")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
<MudTooltip Text="@T("Stop generation")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Stop" Color="Color.Error" OnClick="@(() => this.CancelStreaming())"/>
|
<MudIconButton Icon="@Icons.Material.Filled.Stop" Color="Color.Error" OnClick="@this.CancelStreaming"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Workspaces? Workspaces { get; set; }
|
public Workspaces? Workspaces { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public ChatComposerState ComposerState { get; set; } = new();
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ILogger<ChatComponent> Logger { get; set; } = null!;
|
private ILogger<ChatComponent> Logger { get; set; } = null!;
|
||||||
@ -62,7 +65,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
private bool mustScrollToBottomAfterRender;
|
private bool mustScrollToBottomAfterRender;
|
||||||
private InnerScrolling scrollingArea = null!;
|
private InnerScrolling scrollingArea = null!;
|
||||||
private byte scrollRenderCountdown;
|
private byte scrollRenderCountdown;
|
||||||
private string userInput = string.Empty;
|
|
||||||
private bool mustStoreChat;
|
private bool mustStoreChat;
|
||||||
private bool mustLoadChat;
|
private bool mustLoadChat;
|
||||||
private LoadChat loadChat;
|
private LoadChat loadChat;
|
||||||
@ -74,12 +76,26 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
private Guid loadedParameterWorkspaceId = Guid.Empty;
|
private Guid loadedParameterWorkspaceId = Guid.Empty;
|
||||||
private Guid foregroundChatId = Guid.Empty;
|
private Guid foregroundChatId = Guid.Empty;
|
||||||
private int workspaceHeaderSyncVersion;
|
private int workspaceHeaderSyncVersion;
|
||||||
private HashSet<FileAttachment> chatDocumentPaths = [];
|
|
||||||
|
|
||||||
// Unfortunately, we need the input field reference to blur the focus away. Without
|
// Unfortunately, we need the input field reference to blur the focus away. Without
|
||||||
// this, we cannot clear the input field.
|
// this, we cannot clear the input field.
|
||||||
private MudTextField<string> inputField = null!;
|
private MudTextField<string> inputField = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the user's input in the chat interface.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property serves as a bridge between the chat component and the
|
||||||
|
/// underlying composer state, allowing user input to be dynamically updated
|
||||||
|
/// and managed. The setter also triggers state changes within the composer
|
||||||
|
/// to track whether the user has drafted any input.
|
||||||
|
/// </remarks>
|
||||||
|
private string UserInput
|
||||||
|
{
|
||||||
|
get => this.ComposerState.UserInput;
|
||||||
|
set => this.ComposerState.SetUserInput(value);
|
||||||
|
}
|
||||||
|
|
||||||
#region Overrides of ComponentBase
|
#region Overrides of ComponentBase
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@ -96,15 +112,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
// Get the preselected chat template:
|
// Get the preselected chat template:
|
||||||
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
|
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
|
||||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
if (!this.ComposerState.HasUserDraft && !this.ComposerState.HasComposerContent)
|
||||||
|
this.ComposerState.ApplyTemplate(this.currentChatTemplate);
|
||||||
|
|
||||||
var deferredInput = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_CHAT_INPUT).FirstOrDefault();
|
var deferredInput = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_CHAT_INPUT).FirstOrDefault();
|
||||||
if (!string.IsNullOrWhiteSpace(deferredInput))
|
if (!string.IsNullOrWhiteSpace(deferredInput))
|
||||||
this.userInput = deferredInput;
|
this.ComposerState.SetUserInput(deferredInput);
|
||||||
|
|
||||||
// Apply template's file attachments, if any:
|
|
||||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
|
||||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Check for deferred messages of the kind 'SEND_TO_CHAT',
|
// Check for deferred messages of the kind 'SEND_TO_CHAT',
|
||||||
@ -450,12 +463,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
this.currentChatTemplate = chatTemplate;
|
this.currentChatTemplate = chatTemplate;
|
||||||
if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt))
|
if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt))
|
||||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
this.ComposerState.SetSystemInput(this.currentChatTemplate.PredefinedUserPrompt);
|
||||||
|
|
||||||
// Apply template's file attachments (replaces existing):
|
// Apply template's file attachments (replaces existing):
|
||||||
this.chatDocumentPaths.Clear();
|
this.ComposerState.ReplaceFileAttachments(this.currentChatTemplate.FileAttachments);
|
||||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
|
||||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
|
||||||
|
|
||||||
if(this.ChatThread is null)
|
if(this.ChatThread is null)
|
||||||
return;
|
return;
|
||||||
@ -515,6 +526,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
this.dataSourceSelectionComponent.Hide();
|
this.dataSourceSelectionComponent.Hide();
|
||||||
|
|
||||||
this.hasUnsavedChanges = true;
|
this.hasUnsavedChanges = true;
|
||||||
|
this.ComposerState.MarkUserDraft();
|
||||||
var key = keyEvent.Code.ToLowerInvariant();
|
var key = keyEvent.Code.ToLowerInvariant();
|
||||||
|
|
||||||
// Was the enter key (either enter or numpad enter) pressed?
|
// Was the enter key (either enter or numpad enter) pressed?
|
||||||
@ -546,7 +558,16 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
if(this.dataSourceSelectionComponent?.IsVisible ?? false)
|
if(this.dataSourceSelectionComponent?.IsVisible ?? false)
|
||||||
this.dataSourceSelectionComponent.Hide();
|
this.dataSourceSelectionComponent.Hide();
|
||||||
|
|
||||||
this.userInput = await this.JsRuntime.InvokeAsync<string>("formatChatInputMarkdown", CHAT_INPUT_ID, formatType);
|
this.ComposerState.SetUserInput(await this.JsRuntime.InvokeAsync<string>("formatChatInputMarkdown", CHAT_INPUT_ID, formatType));
|
||||||
|
this.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposerAttachmentsChanged(HashSet<FileAttachment> attachments)
|
||||||
|
{
|
||||||
|
if (!ReferenceEquals(this.ComposerState.FileAttachments, attachments))
|
||||||
|
this.ComposerState.ReplaceFileAttachments(attachments);
|
||||||
|
|
||||||
|
this.ComposerState.MarkUserDraft();
|
||||||
this.hasUnsavedChanges = true;
|
this.hasUnsavedChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,7 +595,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
WorkspaceId = this.currentWorkspaceId,
|
WorkspaceId = this.currentWorkspaceId,
|
||||||
ChatId = Guid.NewGuid(),
|
ChatId = Guid.NewGuid(),
|
||||||
DataSourceOptions = this.earlyDataSourceOptions,
|
DataSourceOptions = this.earlyDataSourceOptions,
|
||||||
Name = this.ExtractThreadName(this.userInput),
|
Name = this.ExtractThreadName(this.ComposerState.UserInput),
|
||||||
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -585,7 +606,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
// Set the thread name if it is empty:
|
// Set the thread name if it is empty:
|
||||||
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
|
if (string.IsNullOrWhiteSpace(this.ChatThread.Name))
|
||||||
this.ChatThread.Name = this.ExtractThreadName(this.userInput);
|
this.ChatThread.Name = this.ExtractThreadName(this.ComposerState.UserInput);
|
||||||
|
|
||||||
// Update provider, profile and chat template:
|
// Update provider, profile and chat template:
|
||||||
this.ChatThread.SelectedProvider = this.Provider.Id;
|
this.ChatThread.SelectedProvider = this.Provider.Id;
|
||||||
@ -602,14 +623,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
IContent? lastUserPrompt;
|
IContent? lastUserPrompt;
|
||||||
if (!reuseLastUserPrompt)
|
if (!reuseLastUserPrompt)
|
||||||
{
|
{
|
||||||
var normalizedAttachments = this.chatDocumentPaths
|
var normalizedAttachments = this.ComposerState.FileAttachments
|
||||||
.Select(attachment => attachment.Normalize())
|
.Select(attachment => attachment.Normalize())
|
||||||
.Where(attachment => attachment.IsValid)
|
.Where(attachment => attachment.IsValid)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
lastUserPrompt = new ContentText
|
lastUserPrompt = new ContentText
|
||||||
{
|
{
|
||||||
Text = this.userInput,
|
Text = this.ComposerState.UserInput,
|
||||||
FileAttachments = normalizedAttachments,
|
FileAttachments = normalizedAttachments,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -656,8 +677,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
// Clear the input field:
|
// Clear the input field:
|
||||||
await this.inputField.FocusAsync();
|
await this.inputField.FocusAsync();
|
||||||
|
|
||||||
this.userInput = string.Empty;
|
this.ComposerState.Clear();
|
||||||
this.chatDocumentPaths.Clear();
|
|
||||||
|
|
||||||
await this.inputField.BlurAsync();
|
await this.inputField.BlurAsync();
|
||||||
|
|
||||||
@ -751,7 +771,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
// Reset our state:
|
// Reset our state:
|
||||||
//
|
//
|
||||||
this.hasUnsavedChanges = false;
|
this.hasUnsavedChanges = false;
|
||||||
this.userInput = string.Empty;
|
this.ComposerState.Clear();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Reset the LLM provider considering the user's settings:
|
// Reset the LLM provider considering the user's settings:
|
||||||
@ -808,12 +828,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
this.ComposerState.ApplyTemplate(this.currentChatTemplate);
|
||||||
|
|
||||||
// Apply template's file attachments:
|
|
||||||
this.chatDocumentPaths.Clear();
|
|
||||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
|
||||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
|
||||||
|
|
||||||
// Now, we have to reset the data source options as well:
|
// Now, we have to reset the data source options as well:
|
||||||
this.ApplyStandardDataSourceOptions();
|
this.ApplyStandardDataSourceOptions();
|
||||||
@ -871,7 +886,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
private async Task LoadedChatChanged(bool notifyParent = true)
|
private async Task LoadedChatChanged(bool notifyParent = true)
|
||||||
{
|
{
|
||||||
this.hasUnsavedChanges = false;
|
this.hasUnsavedChanges = false;
|
||||||
this.userInput = string.Empty;
|
this.ComposerState.Clear();
|
||||||
|
|
||||||
if (this.ChatThread is not null)
|
if (this.ChatThread is not null)
|
||||||
{
|
{
|
||||||
@ -907,7 +922,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
private async Task ResetState()
|
private async Task ResetState()
|
||||||
{
|
{
|
||||||
this.hasUnsavedChanges = false;
|
this.hasUnsavedChanges = false;
|
||||||
this.userInput = string.Empty;
|
this.ComposerState.Clear();
|
||||||
this.ClearWorkspaceHeaderState();
|
this.ClearWorkspaceHeaderState();
|
||||||
|
|
||||||
this.ChatThread = null;
|
this.ChatThread = null;
|
||||||
@ -1010,11 +1025,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
private void RestoreComposerFromTextBlock(ContentText textBlock)
|
private void RestoreComposerFromTextBlock(ContentText textBlock)
|
||||||
{
|
{
|
||||||
this.userInput = textBlock.Text;
|
this.ComposerState.RestoreFromTextBlock(textBlock);
|
||||||
this.chatDocumentPaths.Clear();
|
|
||||||
|
|
||||||
foreach (var attachment in textBlock.FileAttachments)
|
|
||||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Overrides of MSGComponentBase
|
#region Overrides of MSGComponentBase
|
||||||
@ -1062,7 +1073,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
if (this.IsCurrentChatStreaming)
|
if (this.IsCurrentChatStreaming)
|
||||||
return Task.FromResult((TResult?) (object) false);
|
return Task.FromResult((TResult?) (object) false);
|
||||||
|
|
||||||
return Task.FromResult((TResult?)(object)this.hasUnsavedChanges);
|
return Task.FromResult((TResult?)(object)(this.hasUnsavedChanges || this.ComposerState.HasVisibleUserDraft));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(default(TResult));
|
return Task.FromResult(default(TResult));
|
||||||
|
|||||||
65
app/MindWork AI Studio/Components/ChatComposerState.cs
Normal file
65
app/MindWork AI Studio/Components/ChatComposerState.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using AIStudio.Chat;
|
||||||
|
using AIStudio.Settings;
|
||||||
|
|
||||||
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
|
public sealed class ChatComposerState
|
||||||
|
{
|
||||||
|
public string UserInput { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public HashSet<FileAttachment> FileAttachments { get; } = [];
|
||||||
|
|
||||||
|
public bool HasUserDraft { get; private set; }
|
||||||
|
|
||||||
|
public bool HasComposerContent => !string.IsNullOrWhiteSpace(this.UserInput) || this.FileAttachments.Count > 0;
|
||||||
|
|
||||||
|
public bool HasVisibleUserDraft => this.HasUserDraft && (!string.IsNullOrWhiteSpace(this.UserInput) || this.FileAttachments.Count > 0);
|
||||||
|
|
||||||
|
public void ApplyTemplate(ChatTemplate chatTemplate)
|
||||||
|
{
|
||||||
|
this.UserInput = chatTemplate.PredefinedUserPrompt;
|
||||||
|
this.FileAttachments.Clear();
|
||||||
|
foreach (var attachment in chatTemplate.FileAttachments)
|
||||||
|
this.FileAttachments.Add(attachment.Normalize());
|
||||||
|
|
||||||
|
this.HasUserDraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserInput(string? userInput)
|
||||||
|
{
|
||||||
|
this.UserInput = userInput ?? string.Empty;
|
||||||
|
this.HasUserDraft = !string.IsNullOrWhiteSpace(userInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSystemInput(string? userInput)
|
||||||
|
{
|
||||||
|
this.UserInput = userInput ?? string.Empty;
|
||||||
|
this.HasUserDraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkUserDraft()
|
||||||
|
{
|
||||||
|
this.HasUserDraft = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplaceFileAttachments(IEnumerable<FileAttachment> fileAttachments)
|
||||||
|
{
|
||||||
|
this.FileAttachments.Clear();
|
||||||
|
foreach (var attachment in fileAttachments)
|
||||||
|
this.FileAttachments.Add(attachment.Normalize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
this.UserInput = string.Empty;
|
||||||
|
this.FileAttachments.Clear();
|
||||||
|
this.HasUserDraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestoreFromTextBlock(ContentText textBlock)
|
||||||
|
{
|
||||||
|
this.UserInput = textBlock.Text;
|
||||||
|
this.ReplaceFileAttachments(textBlock.FileAttachments);
|
||||||
|
this.HasUserDraft = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -89,6 +89,7 @@
|
|||||||
<ChatComponent
|
<ChatComponent
|
||||||
@bind-ChatThread="@this.chatThread"
|
@bind-ChatThread="@this.chatThread"
|
||||||
@bind-Provider="@this.providerSettings"
|
@bind-Provider="@this.providerSettings"
|
||||||
|
ComposerState="@this.composerState"
|
||||||
Workspaces="@this.workspaces"
|
Workspaces="@this.workspaces"
|
||||||
WorkspaceName="name => this.UpdateWorkspaceName(name)"/>
|
WorkspaceName="name => this.UpdateWorkspaceName(name)"/>
|
||||||
</EndContent>
|
</EndContent>
|
||||||
@ -115,6 +116,7 @@
|
|||||||
<ChatComponent
|
<ChatComponent
|
||||||
@bind-ChatThread="@this.chatThread"
|
@bind-ChatThread="@this.chatThread"
|
||||||
@bind-Provider="@this.providerSettings"
|
@bind-Provider="@this.providerSettings"
|
||||||
|
ComposerState="@this.composerState"
|
||||||
Workspaces="@this.workspaces"
|
Workspaces="@this.workspaces"
|
||||||
WorkspaceName="name => this.UpdateWorkspaceName(name)"/>
|
WorkspaceName="name => this.UpdateWorkspaceName(name)"/>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@ -125,6 +127,7 @@
|
|||||||
<ChatComponent
|
<ChatComponent
|
||||||
@bind-ChatThread="@this.chatThread"
|
@bind-ChatThread="@this.chatThread"
|
||||||
@bind-Provider="@this.providerSettings"
|
@bind-Provider="@this.providerSettings"
|
||||||
|
ComposerState="@this.composerState"
|
||||||
Workspaces="@this.workspaces"
|
Workspaces="@this.workspaces"
|
||||||
WorkspaceName="name => this.UpdateWorkspaceName(name)"/>
|
WorkspaceName="name => this.UpdateWorkspaceName(name)"/>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ public partial class Chat : MSGComponentBase
|
|||||||
private string currentWorkspaceName = string.Empty;
|
private string currentWorkspaceName = string.Empty;
|
||||||
private Workspaces? workspaces;
|
private Workspaces? workspaces;
|
||||||
private double splitterPosition = 30;
|
private double splitterPosition = 30;
|
||||||
|
private readonly ChatComposerState composerState = new();
|
||||||
|
|
||||||
private readonly Timer splitterSaveTimer = new(TimeSpan.FromSeconds(1.6));
|
private readonly Timer splitterSaveTimer = new(TimeSpan.FromSeconds(1.6));
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
- Fixed an issue where an AI response in chat could be interrupted when you interacted with workspaces, such as opening, closing, or resizing the workspace panel.
|
- Fixed an issue where an AI response in chat could be interrupted when you interacted with workspaces, such as opening, closing, or resizing the workspace panel.
|
||||||
- Fixed error messages for provider requests so missing OpenAI API credits and too many requests are shown clearly in chats, assistants, transcription, and model loading.
|
- Fixed error messages for provider requests so missing OpenAI API credits and too many requests are shown clearly in chats, assistants, transcription, and model loading.
|
||||||
- Fixed missing translations for file type names in file selection dialogs.
|
- Fixed missing translations for file type names in file selection dialogs.
|
||||||
|
- Fixed the chat user prompt being cleared when toggling the workspace view.
|
||||||
- Upgraded the native secret storage integration to `keyring-core`, keeping API keys in the secure credential store provided by the operating system.
|
- Upgraded the native secret storage integration to `keyring-core`, keeping API keys in the secure credential store provided by the operating system.
|
||||||
- Upgraded Rust to v1.95.0.
|
- Upgraded Rust to v1.95.0.
|
||||||
- Upgraded .NET to v9.0.16.
|
- Upgraded .NET to v9.0.16.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user