mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-05-03 09:39:47 +00:00
Implemented basic store and load operation for chats
This commit is contained in:
parent
d0858a1707
commit
c76a5e4dbf
@ -10,6 +10,8 @@ public class TreeItemData<T> : ITreeItem<T>
|
|||||||
|
|
||||||
public string Icon { get; init; } = string.Empty;
|
public string Icon { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsChat { get; init; }
|
||||||
|
|
||||||
public T? Value { get; init; }
|
public T? Value { get; init; }
|
||||||
|
|
||||||
public bool Expandable { get; init; } = true;
|
public bool Expandable { get; init; } = true;
|
||||||
|
@ -9,21 +9,30 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TreeItemData<string> treeItem:
|
case TreeItemData<string> treeItem:
|
||||||
<MudTreeViewItem T="ITreeItem<string>" Icon="@treeItem.Icon" Value="@item" LoadingIconColor="@Color.Info" CanExpand="@treeItem.Expandable" Items="@treeItem.Children">
|
@if (treeItem.IsChat)
|
||||||
<BodyContent>
|
{
|
||||||
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
|
<MudTreeViewItem T="ITreeItem<string>" Icon="@treeItem.Icon" Value="@item" LoadingIconColor="@Color.Info" CanExpand="@treeItem.Expandable" Items="@treeItem.Children" OnClick="() => this.LoadChat(treeItem.Value)">
|
||||||
<MudText Style="justify-self: start;">@treeItem.Text</MudText>
|
<BodyContent>
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
|
||||||
@if (treeItem.Value is not "root" and not "temp")
|
<MudText Style="justify-self: start;">@treeItem.Text</MudText>
|
||||||
{
|
|
||||||
<div style="justify-self: end;">
|
<div style="justify-self: end;">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit"/>
|
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit"/>
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit"/>
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit"/>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</div>
|
</BodyContent>
|
||||||
</BodyContent>
|
</MudTreeViewItem>
|
||||||
</MudTreeViewItem>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTreeViewItem T="ITreeItem<string>" Icon="@treeItem.Icon" Value="@item" LoadingIconColor="@Color.Info" CanExpand="@treeItem.Expandable" Items="@treeItem.Children">
|
||||||
|
<BodyContent>
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
|
||||||
|
<MudText Style="justify-self: start;">@treeItem.Text</MudText>
|
||||||
|
</div>
|
||||||
|
</BodyContent>
|
||||||
|
</MudTreeViewItem>
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TreeButton<string> treeButton:
|
case TreeButton<string> treeButton:
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
using AIStudio.Chat;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
@ -13,6 +17,22 @@ public partial class Workspaces : ComponentBase
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public ChatThread? CurrentChatThread { get; set; }
|
public ChatThread? CurrentChatThread { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<ChatThread> CurrentChatThreadChanged { get; set; }
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
AllowTrailingCommas = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
|
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
Converters =
|
||||||
|
{
|
||||||
|
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private readonly HashSet<ITreeItem<string>> initialTreeItems = new();
|
private readonly HashSet<ITreeItem<string>> initialTreeItems = new();
|
||||||
|
|
||||||
#region Overrides of ComponentBase
|
#region Overrides of ComponentBase
|
||||||
@ -52,7 +72,7 @@ public partial class Workspaces : ComponentBase
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private Task<HashSet<ITreeItem<string>>> LoadServerData(ITreeItem<string>? parent)
|
private async Task<HashSet<ITreeItem<string>>> LoadServerData(ITreeItem<string>? parent)
|
||||||
{
|
{
|
||||||
switch (parent)
|
switch (parent)
|
||||||
{
|
{
|
||||||
@ -77,11 +97,16 @@ public partial class Workspaces : ComponentBase
|
|||||||
// Enumerate the workspace directories:
|
// Enumerate the workspace directories:
|
||||||
foreach (var workspaceDirPath in Directory.EnumerateDirectories(workspaceDirectories))
|
foreach (var workspaceDirPath in Directory.EnumerateDirectories(workspaceDirectories))
|
||||||
{
|
{
|
||||||
|
// Read the `name` file:
|
||||||
|
var workspaceNamePath = Path.Join(workspaceDirPath, "name");
|
||||||
|
var workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8);
|
||||||
|
|
||||||
workspaceChildren.Add(new TreeItemData<string>
|
workspaceChildren.Add(new TreeItemData<string>
|
||||||
{
|
{
|
||||||
|
IsChat = false,
|
||||||
Depth = item.Depth + 1,
|
Depth = item.Depth + 1,
|
||||||
Branch = WorkspaceBranch.WORKSPACES,
|
Branch = WorkspaceBranch.WORKSPACES,
|
||||||
Text = Path.GetFileName(workspaceDirPath),
|
Text = workspaceName,
|
||||||
Icon = Icons.Material.Filled.Description,
|
Icon = Icons.Material.Filled.Description,
|
||||||
Expandable = true,
|
Expandable = true,
|
||||||
Value = workspaceDirPath,
|
Value = workspaceDirPath,
|
||||||
@ -100,17 +125,22 @@ public partial class Workspaces : ComponentBase
|
|||||||
// Get the workspace directory:
|
// Get the workspace directory:
|
||||||
var workspaceDirPath = item.Value;
|
var workspaceDirPath = item.Value;
|
||||||
|
|
||||||
if(workspaceDirPath is null)
|
if (workspaceDirPath is null)
|
||||||
return Task.FromResult(new HashSet<ITreeItem<string>>());
|
return [];
|
||||||
|
|
||||||
// Enumerate the workspace directory:
|
// Enumerate the workspace directory:
|
||||||
foreach (var chatPath in Directory.EnumerateDirectories(workspaceDirPath))
|
foreach (var chatPath in Directory.EnumerateDirectories(workspaceDirPath))
|
||||||
{
|
{
|
||||||
|
// Read the `name` file:
|
||||||
|
var chatNamePath = Path.Join(chatPath, "name");
|
||||||
|
var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8);
|
||||||
|
|
||||||
workspaceChildren.Add(new TreeItemData<string>
|
workspaceChildren.Add(new TreeItemData<string>
|
||||||
{
|
{
|
||||||
|
IsChat = true,
|
||||||
Depth = item.Depth + 1,
|
Depth = item.Depth + 1,
|
||||||
Branch = WorkspaceBranch.WORKSPACES,
|
Branch = WorkspaceBranch.WORKSPACES,
|
||||||
Text = Path.GetFileNameWithoutExtension(chatPath),
|
Text = chatName,
|
||||||
Icon = Icons.Material.Filled.Chat,
|
Icon = Icons.Material.Filled.Chat,
|
||||||
Expandable = false,
|
Expandable = false,
|
||||||
Value = chatPath,
|
Value = chatPath,
|
||||||
@ -120,7 +150,7 @@ public partial class Workspaces : ComponentBase
|
|||||||
workspaceChildren.Add(new TreeButton<string>(WorkspaceBranch.WORKSPACES, item.Depth + 1, "Add chat",Icons.Material.Filled.Add));
|
workspaceChildren.Add(new TreeButton<string>(WorkspaceBranch.WORKSPACES, item.Depth + 1, "Add chat",Icons.Material.Filled.Add));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(workspaceChildren);
|
return workspaceChildren;
|
||||||
|
|
||||||
case WorkspaceBranch.TEMPORARY_CHATS:
|
case WorkspaceBranch.TEMPORARY_CHATS:
|
||||||
var tempChildren = new HashSet<ITreeItem<string>>();
|
var tempChildren = new HashSet<ITreeItem<string>>();
|
||||||
@ -138,24 +168,79 @@ public partial class Workspaces : ComponentBase
|
|||||||
// Enumerate the workspace directories:
|
// Enumerate the workspace directories:
|
||||||
foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories))
|
foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories))
|
||||||
{
|
{
|
||||||
|
// Read the `name` file:
|
||||||
|
var chatNamePath = Path.Join(tempChatDirPath, "name");
|
||||||
|
var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8);
|
||||||
|
|
||||||
tempChildren.Add(new TreeItemData<string>
|
tempChildren.Add(new TreeItemData<string>
|
||||||
{
|
{
|
||||||
|
IsChat = true,
|
||||||
Depth = item.Depth + 1,
|
Depth = item.Depth + 1,
|
||||||
Branch = WorkspaceBranch.TEMPORARY_CHATS,
|
Branch = WorkspaceBranch.TEMPORARY_CHATS,
|
||||||
Text = Path.GetFileName(tempChatDirPath),
|
Text = chatName,
|
||||||
Icon = Icons.Material.Filled.Timer,
|
Icon = Icons.Material.Filled.Timer,
|
||||||
Expandable = false,
|
Expandable = false,
|
||||||
Value = tempChatDirPath,
|
Value = tempChatDirPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(tempChildren);
|
return tempChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(new HashSet<ITreeItem<string>>());
|
return [];
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Task.FromResult(new HashSet<ITreeItem<string>>());
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StoreChat(ChatThread thread)
|
||||||
|
{
|
||||||
|
string chatDirectory;
|
||||||
|
if (thread.WorkspaceId == Guid.Empty)
|
||||||
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", thread.ChatId.ToString());
|
||||||
|
else
|
||||||
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", thread.WorkspaceId.ToString(), thread.ChatId.ToString());
|
||||||
|
|
||||||
|
// Ensure the directory exists:
|
||||||
|
Directory.CreateDirectory(chatDirectory);
|
||||||
|
|
||||||
|
// Save the chat name:
|
||||||
|
var chatNamePath = Path.Join(chatDirectory, "name");
|
||||||
|
await File.WriteAllTextAsync(chatNamePath, thread.Name);
|
||||||
|
|
||||||
|
// Save the thread as thread.json:
|
||||||
|
var chatPath = Path.Join(chatDirectory, "thread.json");
|
||||||
|
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(thread, JSON_OPTIONS), Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadChat(string? chatPath)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(chatPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: chat path is empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Directory.Exists(chatPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: chat not found: '{chatPath}'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
||||||
|
this.CurrentChatThread = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
||||||
|
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
|
||||||
|
|
||||||
|
Console.WriteLine($"Loaded chat: {this.CurrentChatThread?.Name}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
Console.WriteLine(e.StackTrace);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,9 +29,27 @@
|
|||||||
</MudPaper>
|
</MudPaper>
|
||||||
<MudPaper Class="mt-1" Outlined="@true">
|
<MudPaper Class="mt-1" Outlined="@true">
|
||||||
<MudToolBar WrapContent="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>
|
</MudTooltip>
|
||||||
|
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.BugReport" OnClick="() => this.StateHasChanged()"/>
|
||||||
|
|
||||||
</MudToolBar>
|
</MudToolBar>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</FooterContent>
|
</FooterContent>
|
||||||
@ -49,7 +67,7 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
</MudDrawerHeader>
|
</MudDrawerHeader>
|
||||||
<MudDrawerContainer Class="ml-6">
|
<MudDrawerContainer Class="ml-6">
|
||||||
<Workspaces/>
|
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread"/>
|
||||||
</MudDrawerContainer>
|
</MudDrawerContainer>
|
||||||
</MudDrawer>
|
</MudDrawer>
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
|
using AIStudio.Components.Blocks;
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ public partial class Chat : ComponentBase
|
|||||||
private bool isStreaming;
|
private bool isStreaming;
|
||||||
private string userInput = string.Empty;
|
private string userInput = string.Empty;
|
||||||
private bool workspacesVisible;
|
private bool workspacesVisible;
|
||||||
|
private Workspaces? workspaces = null;
|
||||||
|
|
||||||
// Unfortunately, we need the input field reference to clear it after sending a message.
|
// 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,
|
// 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:
|
// Configure the spellchecking for the user input:
|
||||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
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();
|
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 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()
|
private async Task SendMessage()
|
||||||
{
|
{
|
||||||
if (!this.IsProviderSelected)
|
if (!this.IsProviderSelected)
|
||||||
return;
|
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:
|
// Add the user message to the thread:
|
||||||
//
|
//
|
||||||
var time = DateTimeOffset.Now;
|
var time = DateTimeOffset.Now;
|
||||||
this.chatThread?.Blocks.Add(new ContentBlock(time, ContentType.TEXT, new ContentText
|
this.chatThread?.Blocks.Add(new ContentBlock
|
||||||
{
|
{
|
||||||
// Text content properties:
|
Time = time,
|
||||||
Text = this.userInput,
|
ContentType = ContentType.TEXT,
|
||||||
})
|
|
||||||
{
|
|
||||||
// Content block properties:
|
|
||||||
Role = ChatRole.USER,
|
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:
|
// Add the AI response to the thread:
|
||||||
//
|
//
|
||||||
@ -86,9 +102,12 @@ public partial class Chat : ComponentBase
|
|||||||
InitialRemoteWait = true,
|
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,
|
Role = ChatRole.AI,
|
||||||
|
Content = aiText,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear the input field:
|
// Clear the input field:
|
||||||
@ -104,6 +123,10 @@ public partial class Chat : ComponentBase
|
|||||||
// content to be streamed.
|
// 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);
|
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:
|
// Disable the stream state:
|
||||||
this.isStreaming = false;
|
this.isStreaming = false;
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
@ -138,4 +161,31 @@ public partial class Chat : ComponentBase
|
|||||||
{
|
{
|
||||||
this.workspacesVisible = !this.workspacesVisible;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user