mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-14 12:33:48 +00:00
Fixed an issue with switching between chat threads while multiple chats are running (#779)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
3e6e3bdcbd
commit
d05ff26e62
9 files changed
+246
-80
No files matched your search
@@ -37,7 +37,7 @@
|
||||
<MudTextField
|
||||
T="string"
|
||||
@ref="@this.inputField"
|
||||
@bind-Text="@this.userInput"
|
||||
@bind-Text="@this.UserInput"
|
||||
Variant="Variant.Outlined"
|
||||
AutoGrow="@true"
|
||||
Lines="3"
|
||||
@@ -96,28 +96,28 @@
|
||||
@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">
|
||||
<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>
|
||||
}
|
||||
|
||||
<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;"/>
|
||||
|
||||
<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 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 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 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 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>
|
||||
|
||||
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
||||
@@ -137,7 +137,7 @@
|
||||
@if (this.IsCurrentChatStreaming)
|
||||
{
|
||||
<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>
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
[Parameter]
|
||||
public Workspaces? Workspaces { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ChatComposerState ComposerState { get; set; } = new();
|
||||
|
||||
[Inject]
|
||||
private ILogger<ChatComponent> Logger { get; set; } = null!;
|
||||
@@ -62,7 +65,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
private bool mustScrollToBottomAfterRender;
|
||||
private InnerScrolling scrollingArea = null!;
|
||||
private byte scrollRenderCountdown;
|
||||
private string userInput = string.Empty;
|
||||
private bool mustStoreChat;
|
||||
private bool mustLoadChat;
|
||||
private LoadChat loadChat;
|
||||
@@ -70,20 +72,36 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
private string currentWorkspaceName = string.Empty;
|
||||
private Guid currentWorkspaceId = Guid.Empty;
|
||||
private Guid currentChatThreadId = Guid.Empty;
|
||||
private Guid loadedParameterChatId = Guid.Empty;
|
||||
private Guid loadedParameterWorkspaceId = Guid.Empty;
|
||||
private Guid foregroundChatId = Guid.Empty;
|
||||
private int workspaceHeaderSyncVersion;
|
||||
private HashSet<FileAttachment> chatDocumentPaths = [];
|
||||
|
||||
// Unfortunately, we need the input field reference to blur the focus away. Without
|
||||
// this, we cannot clear the input field.
|
||||
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
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Apply the filters for the message bus:
|
||||
this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED, Event.CHAT_GENERATION_CHANGED ]);
|
||||
this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED, Event.CHAT_GENERATION_CHANGED ]);
|
||||
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
@@ -94,15 +112,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
// Get the preselected chat template:
|
||||
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();
|
||||
if (!string.IsNullOrWhiteSpace(deferredInput))
|
||||
this.userInput = deferredInput;
|
||||
|
||||
// Apply template's file attachments, if any:
|
||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
||||
this.ComposerState.SetUserInput(deferredInput);
|
||||
|
||||
//
|
||||
// Check for deferred messages of the kind 'SEND_TO_CHAT',
|
||||
@@ -120,6 +135,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.ChatThread.IncludeDateTime = true;
|
||||
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.ChatId}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
|
||||
// We know already that the chat thread is not null,
|
||||
@@ -246,6 +262,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
if(this.ChatThread is not null)
|
||||
{
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread!.ChatId}' with title '{this.ChatThread.Name}' ({this.ChatThread.Blocks.Count} messages) was loaded successfully.");
|
||||
|
||||
@@ -276,13 +293,35 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
await this.ApplyLoadedChatParameterAsync();
|
||||
await this.SyncForegroundChatAsync();
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task ApplyLoadedChatParameterAsync()
|
||||
{
|
||||
var chatId = this.ChatThread?.ChatId ?? Guid.Empty;
|
||||
var workspaceId = this.ChatThread?.WorkspaceId ?? Guid.Empty;
|
||||
|
||||
if (this.loadedParameterChatId == chatId && this.loadedParameterWorkspaceId == workspaceId)
|
||||
{
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadedParameterChatId = chatId;
|
||||
this.loadedParameterWorkspaceId = workspaceId;
|
||||
await this.LoadedChatChanged(notifyParent: false);
|
||||
}
|
||||
|
||||
private void MarkCurrentChatAsLoadedParameter()
|
||||
{
|
||||
this.loadedParameterChatId = this.ChatThread?.ChatId ?? Guid.Empty;
|
||||
this.loadedParameterWorkspaceId = this.ChatThread?.WorkspaceId ?? Guid.Empty;
|
||||
}
|
||||
|
||||
private async Task SyncWorkspaceHeaderWithChatThreadAsync()
|
||||
{
|
||||
var syncVersion = Interlocked.Increment(ref this.workspaceHeaderSyncVersion);
|
||||
@@ -424,12 +463,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
this.currentChatTemplate = chatTemplate;
|
||||
if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt))
|
||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
||||
this.ComposerState.SetSystemInput(this.currentChatTemplate.PredefinedUserPrompt);
|
||||
|
||||
// Apply template's file attachments (replaces existing):
|
||||
this.chatDocumentPaths.Clear();
|
||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
||||
this.ComposerState.ReplaceFileAttachments(this.currentChatTemplate.FileAttachments);
|
||||
|
||||
if(this.ChatThread is null)
|
||||
return;
|
||||
@@ -489,6 +526,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.dataSourceSelectionComponent.Hide();
|
||||
|
||||
this.hasUnsavedChanges = true;
|
||||
this.ComposerState.MarkUserDraft();
|
||||
var key = keyEvent.Code.ToLowerInvariant();
|
||||
|
||||
// Was the enter key (either enter or numpad enter) pressed?
|
||||
@@ -520,7 +558,16 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
if(this.dataSourceSelectionComponent?.IsVisible ?? false)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -548,17 +595,18 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
WorkspaceId = this.currentWorkspaceId,
|
||||
ChatId = Guid.NewGuid(),
|
||||
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(),
|
||||
};
|
||||
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the thread name if it is empty:
|
||||
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:
|
||||
this.ChatThread.SelectedProvider = this.Provider.Id;
|
||||
@@ -575,14 +623,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
IContent? lastUserPrompt;
|
||||
if (!reuseLastUserPrompt)
|
||||
{
|
||||
var normalizedAttachments = this.chatDocumentPaths
|
||||
var normalizedAttachments = this.ComposerState.FileAttachments
|
||||
.Select(attachment => attachment.Normalize())
|
||||
.Where(attachment => attachment.IsValid)
|
||||
.ToList();
|
||||
|
||||
lastUserPrompt = new ContentText
|
||||
{
|
||||
Text = this.userInput,
|
||||
Text = this.ComposerState.UserInput,
|
||||
FileAttachments = normalizedAttachments,
|
||||
};
|
||||
|
||||
@@ -629,8 +677,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// Clear the input field:
|
||||
await this.inputField.FocusAsync();
|
||||
|
||||
this.userInput = string.Empty;
|
||||
this.chatDocumentPaths.Clear();
|
||||
this.ComposerState.Clear();
|
||||
|
||||
await this.inputField.BlurAsync();
|
||||
|
||||
@@ -724,7 +771,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// Reset our state:
|
||||
//
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
this.ComposerState.Clear();
|
||||
|
||||
//
|
||||
// Reset the LLM provider considering the user's settings:
|
||||
@@ -781,18 +828,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
};
|
||||
}
|
||||
|
||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
||||
|
||||
// Apply template's file attachments:
|
||||
this.chatDocumentPaths.Clear();
|
||||
foreach (var attachment in this.currentChatTemplate.FileAttachments)
|
||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
||||
this.ComposerState.ApplyTemplate(this.currentChatTemplate);
|
||||
|
||||
// Now, we have to reset the data source options as well:
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
|
||||
// Notify the parent component about the change:
|
||||
await this.SyncForegroundChatAsync();
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
}
|
||||
|
||||
@@ -834,26 +877,33 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await WorkspaceBehaviour.DeleteChatAsync(this.DialogService, this.ChatThread!.WorkspaceId, this.ChatThread.ChatId, askForConfirmation: false);
|
||||
|
||||
this.ChatThread!.WorkspaceId = workspaceId;
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.SaveThread();
|
||||
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
}
|
||||
|
||||
private async Task LoadedChatChanged()
|
||||
private async Task LoadedChatChanged(bool notifyParent = true)
|
||||
{
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
this.ComposerState.Clear();
|
||||
|
||||
if (this.ChatThread is not null)
|
||||
{
|
||||
this.ChatThread = this.AIJobService.TryGetLiveChatThread(this.ChatThread.ChatId) ?? this.ChatThread;
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
this.loadedParameterChatId = this.ChatThread.ChatId;
|
||||
this.loadedParameterWorkspaceId = this.ChatThread.WorkspaceId;
|
||||
if (notifyParent)
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
|
||||
await this.SyncWorkspaceHeaderWithChatThreadAsync();
|
||||
await this.SyncForegroundChatAsync();
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions, this.ChatThread.AISelectedDataSources);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.loadedParameterChatId = Guid.Empty;
|
||||
this.loadedParameterWorkspaceId = Guid.Empty;
|
||||
this.ClearWorkspaceHeaderState();
|
||||
await this.SyncForegroundChatAsync();
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
@@ -872,10 +922,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
private async Task ResetState()
|
||||
{
|
||||
this.hasUnsavedChanges = false;
|
||||
this.userInput = string.Empty;
|
||||
this.ComposerState.Clear();
|
||||
this.ClearWorkspaceHeaderState();
|
||||
|
||||
this.ChatThread = null;
|
||||
this.MarkCurrentChatAsLoadedParameter();
|
||||
await this.SyncForegroundChatAsync();
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
@@ -974,11 +1025,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
private void RestoreComposerFromTextBlock(ContentText textBlock)
|
||||
{
|
||||
this.userInput = textBlock.Text;
|
||||
this.chatDocumentPaths.Clear();
|
||||
|
||||
foreach (var attachment in textBlock.FileAttachments)
|
||||
this.chatDocumentPaths.Add(attachment.Normalize());
|
||||
this.ComposerState.RestoreFromTextBlock(textBlock);
|
||||
}
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
@@ -1000,10 +1047,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
await this.SaveThread();
|
||||
break;
|
||||
|
||||
case Event.WORKSPACE_LOADED_CHAT_CHANGED:
|
||||
await this.LoadedChatChanged();
|
||||
break;
|
||||
|
||||
case Event.AI_JOB_CHANGED:
|
||||
case Event.AI_JOB_FINISHED:
|
||||
case Event.CHAT_GENERATION_CHANGED:
|
||||
@@ -1030,7 +1073,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
if (this.IsCurrentChatStreaming)
|
||||
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));
|
||||
@@ -1049,6 +1092,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
}
|
||||
|
||||
await this.AIJobService.SetForegroundAsync(AIJobKind.CHAT_GENERATION, this.foregroundChatId, false);
|
||||
this.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -236,12 +236,13 @@ public partial class Workspaces : MSGComponentBase
|
||||
private string GetChatTreeIcon(Guid chatId, string defaultIcon)
|
||||
{
|
||||
var snapshot = this.AIJobService.TryGetChatSnapshot(chatId);
|
||||
return snapshot?.Status switch
|
||||
if (snapshot is null || !snapshot.IsActive)
|
||||
return defaultIcon;
|
||||
|
||||
return snapshot.Status switch
|
||||
{
|
||||
AIJobStatus.WAITING_FOR_REMOTE => Icons.Material.Filled.HourglassTop,
|
||||
AIJobStatus.RUNNING => Icons.Material.Filled.ChangeCircle,
|
||||
AIJobStatus.CANCELED => Icons.Material.Filled.Cancel,
|
||||
AIJobStatus.FAILED => Icons.Material.Filled.Error,
|
||||
_ => defaultIcon,
|
||||
};
|
||||
}
|
||||
@@ -390,7 +391,6 @@ public partial class Workspaces : MSGComponentBase
|
||||
{
|
||||
this.CurrentChatThread = chat;
|
||||
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.WORKSPACE_LOADED_CHAT_CHANGED);
|
||||
}
|
||||
|
||||
return chat;
|
||||
@@ -439,7 +439,6 @@ public partial class Workspaces : MSGComponentBase
|
||||
{
|
||||
this.CurrentChatThread = null;
|
||||
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.WORKSPACE_LOADED_CHAT_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +472,6 @@ public partial class Workspaces : MSGComponentBase
|
||||
{
|
||||
this.CurrentChatThread.Name = chat.Name;
|
||||
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.WORKSPACE_LOADED_CHAT_CHANGED);
|
||||
}
|
||||
|
||||
await WorkspaceBehaviour.StoreChatAsync(chat);
|
||||
@@ -596,7 +594,6 @@ public partial class Workspaces : MSGComponentBase
|
||||
{
|
||||
this.CurrentChatThread = chat;
|
||||
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.WORKSPACE_LOADED_CHAT_CHANGED);
|
||||
}
|
||||
|
||||
await WorkspaceBehaviour.StoreChatAsync(chat);
|
||||
|
||||
Reference in new issue
Block a user