mirror of
				https://github.com/MindWorkAI/AI-Studio.git
				synced 2025-11-04 01:20:21 +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 bool IsChat { get; init; }
 | 
			
		||||
 | 
			
		||||
    public T? Value { get; init; }
 | 
			
		||||
 | 
			
		||||
    public bool Expandable { get; init; } = true;
 | 
			
		||||
 | 
			
		||||
@ -9,21 +9,30 @@
 | 
			
		||||
                break;
 | 
			
		||||
                
 | 
			
		||||
            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)
 | 
			
		||||
                {
 | 
			
		||||
                    <MudTreeViewItem T="ITreeItem<string>" Icon="@treeItem.Icon" Value="@item" LoadingIconColor="@Color.Info" CanExpand="@treeItem.Expandable" Items="@treeItem.Children" OnClick="() => this.LoadChat(treeItem.Value)">
 | 
			
		||||
                        <BodyContent>
 | 
			
		||||
                            <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
 | 
			
		||||
                                <MudText Style="justify-self: start;">@treeItem.Text</MudText>
 | 
			
		||||
                        
 | 
			
		||||
                            @if (treeItem.Value is not "root" and not "temp")
 | 
			
		||||
                            {
 | 
			
		||||
                                <div style="justify-self: end;">
 | 
			
		||||
                                    <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit"/>
 | 
			
		||||
                                    <MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit"/>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            }
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </BodyContent>
 | 
			
		||||
                    </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;
 | 
			
		||||
                
 | 
			
		||||
            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 Microsoft.AspNetCore.Components;
 | 
			
		||||
@ -13,6 +17,22 @@ public partial class Workspaces : ComponentBase
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    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();
 | 
			
		||||
    
 | 
			
		||||
    #region Overrides of ComponentBase
 | 
			
		||||
@ -52,7 +72,7 @@ public partial class Workspaces : ComponentBase
 | 
			
		||||
 | 
			
		||||
    #endregion
 | 
			
		||||
 | 
			
		||||
    private Task<HashSet<ITreeItem<string>>> LoadServerData(ITreeItem<string>? parent)
 | 
			
		||||
    private async Task<HashSet<ITreeItem<string>>> LoadServerData(ITreeItem<string>? parent)
 | 
			
		||||
    {
 | 
			
		||||
        switch (parent)
 | 
			
		||||
        {
 | 
			
		||||
@ -77,11 +97,16 @@ public partial class Workspaces : ComponentBase
 | 
			
		||||
                            // Enumerate the workspace directories:
 | 
			
		||||
                            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>
 | 
			
		||||
                                {
 | 
			
		||||
                                    IsChat = false,
 | 
			
		||||
                                    Depth = item.Depth + 1,
 | 
			
		||||
                                    Branch = WorkspaceBranch.WORKSPACES,
 | 
			
		||||
                                    Text = Path.GetFileName(workspaceDirPath),
 | 
			
		||||
                                    Text = workspaceName,
 | 
			
		||||
                                    Icon = Icons.Material.Filled.Description,
 | 
			
		||||
                                    Expandable = true,
 | 
			
		||||
                                    Value = workspaceDirPath,
 | 
			
		||||
@ -100,17 +125,22 @@ public partial class Workspaces : ComponentBase
 | 
			
		||||
                            // Get the workspace directory:
 | 
			
		||||
                            var workspaceDirPath = item.Value;
 | 
			
		||||
 | 
			
		||||
                            if(workspaceDirPath is null)
 | 
			
		||||
                                return Task.FromResult(new HashSet<ITreeItem<string>>());
 | 
			
		||||
                            if (workspaceDirPath is null)
 | 
			
		||||
                                return [];
 | 
			
		||||
                            
 | 
			
		||||
                            // Enumerate the workspace directory:
 | 
			
		||||
                            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>
 | 
			
		||||
                                {
 | 
			
		||||
                                    IsChat = true,
 | 
			
		||||
                                    Depth = item.Depth + 1,
 | 
			
		||||
                                    Branch = WorkspaceBranch.WORKSPACES,
 | 
			
		||||
                                    Text = Path.GetFileNameWithoutExtension(chatPath),
 | 
			
		||||
                                    Text = chatName,
 | 
			
		||||
                                    Icon = Icons.Material.Filled.Chat,
 | 
			
		||||
                                    Expandable = false,
 | 
			
		||||
                                    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));
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        return Task.FromResult(workspaceChildren);
 | 
			
		||||
                        return workspaceChildren;
 | 
			
		||||
                    
 | 
			
		||||
                    case WorkspaceBranch.TEMPORARY_CHATS:
 | 
			
		||||
                        var tempChildren = new HashSet<ITreeItem<string>>();
 | 
			
		||||
@ -138,24 +168,79 @@ public partial class Workspaces : ComponentBase
 | 
			
		||||
                        // Enumerate the workspace directories:
 | 
			
		||||
                        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>
 | 
			
		||||
                            {
 | 
			
		||||
                                IsChat = true,
 | 
			
		||||
                                Depth = item.Depth + 1,
 | 
			
		||||
                                Branch = WorkspaceBranch.TEMPORARY_CHATS,
 | 
			
		||||
                                Text = Path.GetFileName(tempChatDirPath),
 | 
			
		||||
                                Text = chatName,
 | 
			
		||||
                                Icon = Icons.Material.Filled.Timer,
 | 
			
		||||
                                Expandable = false,
 | 
			
		||||
                                Value = tempChatDirPath,
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        return Task.FromResult(tempChildren);
 | 
			
		||||
                        return tempChildren;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Task.FromResult(new HashSet<ITreeItem<string>>());
 | 
			
		||||
                return [];
 | 
			
		||||
            
 | 
			
		||||
            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 Class="mt-1" Outlined="@true">
 | 
			
		||||
            <MudToolBar WrapContent="true">
 | 
			
		||||
                
 | 
			
		||||
                @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()" Disabled="@(this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior is WorkspaceStorageBehavior.DISABLE_WORKSPACES)"/>
 | 
			
		||||
                        <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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user