Implemented basic store and load operation for chats

This commit is contained in:
tsommer committed 2024-07-10 19:27:49 +02:00
1 parent d0858a1707
commit c76a5e4dbf
5 files changed
+202 -38

No files matched your search

@@ -29,9 +29,27 @@
</MudPaper>
<MudPaper Class="mt-1" Outlined="@true">
<MudToolBar WrapContent="true">
<MudTooltip Text="Your workspaces" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.SnippetFolder" OnClick="() => this.ToggleWorkspaces()" Disabled="@(this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.DISABLE_WORKSPACES)"/>
@if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
<MudTooltip Text="Your workspaces" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.SnippetFolder" OnClick="() => this.ToggleWorkspaces()"/>
</MudTooltip>
}
@if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY)
{
<MudTooltip Text="Save chat" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="() => this.SaveThread()" Disabled="@(!this.CanThreadBeSaved)"/>
</MudTooltip>
}
<MudTooltip Text="Move chat to workspace" Placement="Placement.Bottom">
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Disabled="@(!this.CanThreadBeSaved)"/>
</MudTooltip>
<MudIconButton Icon="@Icons.Material.Filled.BugReport" OnClick="() => this.StateHasChanged()"/>
</MudToolBar>
</MudPaper>
</FooterContent>
@@ -49,7 +67,7 @@
</MudStack>
</MudDrawerHeader>
<MudDrawerContainer Class="ml-6">
<Workspaces/>
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread"/>
</MudDrawerContainer>
</MudDrawer>
}
@@ -1,4 +1,5 @@
using AIStudio.Chat;
using AIStudio.Components.Blocks;
using AIStudio.Provider;
using AIStudio.Settings;
@@ -28,6 +29,7 @@ public partial class Chat : ComponentBase
private bool isStreaming;
private string userInput = string.Empty;
private bool workspacesVisible;
private Workspaces? workspaces = null;
// Unfortunately, we need the input field reference to clear it after sending a message.
// This is necessary because we have to handle the key events ourselves. Otherwise,
@@ -41,11 +43,6 @@ public partial class Chat : ComponentBase
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
// For now, we just create a new chat thread.
// Later we want the chats to be persisted
// across page loads and organize them in
// a chat history & workspaces.
this.chatThread = new("Thread 1", this.RNG.Next(), "You are a helpful assistant!", []);
await base.OnInitializedAsync();
}
@@ -57,25 +54,44 @@ public partial class Chat : ComponentBase
private string InputLabel => this.IsProviderSelected ? $"Your Prompt (use selected instance '{this.selectedProvider.InstanceName}', provider '{this.selectedProvider.UsedProvider.ToName()}')" : "Select a provider first";
private bool CanThreadBeSaved => this.IsProviderSelected && this.chatThread is not null && this.chatThread.Blocks.Count > 0;
private async Task SendMessage()
{
if (!this.IsProviderSelected)
return;
// Create a new chat thread if necessary:
var threadName = this.ExtractThreadName(this.userInput);
this.chatThread ??= new()
{
WorkspaceId = Guid.Empty,
ChatId = Guid.NewGuid(),
Name = threadName,
Seed = this.RNG.Next(),
SystemPrompt = "You are a helpful assistant!",
Blocks = [],
};
//
// Add the user message to the thread:
//
var time = DateTimeOffset.Now;
this.chatThread?.Blocks.Add(new ContentBlock(time, ContentType.TEXT, new ContentText
this.chatThread?.Blocks.Add(new ContentBlock
{
// Text content properties:
Text = this.userInput,
})
{
// Content block properties:
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.USER,
Content = new ContentText
{
Text = this.userInput,
},
});
// Save the chat:
if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
await this.SaveThread();
//
// Add the AI response to the thread:
//
@@ -86,9 +102,12 @@ public partial class Chat : ComponentBase
InitialRemoteWait = true,
};
this.chatThread?.Blocks.Add(new ContentBlock(time, ContentType.TEXT, aiText)
this.chatThread?.Blocks.Add(new ContentBlock
{
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.AI,
Content = aiText,
});
// Clear the input field:
@@ -104,6 +123,10 @@ public partial class Chat : ComponentBase
// content to be streamed.
await aiText.CreateFromProviderAsync(this.selectedProvider.UsedProvider.CreateProvider(this.selectedProvider.InstanceName, this.selectedProvider.Hostname), this.JsRuntime, this.SettingsManager, this.selectedProvider.Model, this.chatThread);
// Save the chat:
if (this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
await this.SaveThread();
// Disable the stream state:
this.isStreaming = false;
this.StateHasChanged();
@@ -138,4 +161,31 @@ public partial class Chat : ComponentBase
{
this.workspacesVisible = !this.workspacesVisible;
}
private async Task SaveThread()
{
if(this.workspaces is null)
return;
if(this.chatThread is null)
return;
if (!this.CanThreadBeSaved)
return;
await this.workspaces.StoreChat(this.chatThread);
}
private string ExtractThreadName(string firstUserInput)
{
// We select the first 10 words of the user input:
var words = firstUserInput.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var threadName = string.Join(' ', words.Take(10));
// If the thread name is empty, we use a default name:
if (string.IsNullOrWhiteSpace(threadName))
threadName = "Thread";
return threadName;
}
}