Skip to content

Commit

Permalink
Add workspaces & persistent chats (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
SommerEngineering authored Jul 13, 2024
1 parent 2926366 commit 59d0321
Show file tree
Hide file tree
Showing 43 changed files with 1,392 additions and 66 deletions.
26 changes: 16 additions & 10 deletions app/MindWork AI Studio/Chat/ChatThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,35 @@ namespace AIStudio.Chat;
/// <summary>
/// Data structure for a chat thread.
/// </summary>
/// <param name="name">The name of the chat thread.</param>
/// <param name="seed">The seed for the chat thread. Some providers use this to generate deterministic results.</param>
/// <param name="systemPrompt">The system prompt for the chat thread.</param>
/// <param name="blocks">The content blocks of the chat thread.</param>
public sealed class ChatThread(string name, int seed, string systemPrompt, IEnumerable<ContentBlock> blocks)
public sealed class ChatThread
{
/// <summary>
/// The unique identifier of the chat thread.
/// </summary>
public Guid ChatId { get; init; }

/// <summary>
/// The unique identifier of the workspace.
/// </summary>
public Guid WorkspaceId { get; set; }

/// <summary>
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
/// </summary>
public string Name { get; set; } = name;
public string Name { get; set; } = string.Empty;

/// <summary>
/// The seed for the chat thread. Some providers use this to generate deterministic results.
/// </summary>
public int Seed { get; set; } = seed;
public int Seed { get; init; }

/// <summary>
/// The current system prompt for the chat thread.
/// </summary>
public string SystemPrompt { get; set; } = systemPrompt;
public string SystemPrompt { get; init; } = string.Empty;

/// <summary>
/// The content blocks of the chat thread.
/// </summary>
public List<ContentBlock> Blocks { get; init; } = blocks.ToList();
public List<ContentBlock> Blocks { get; init; } = [];
}
11 changes: 4 additions & 7 deletions app/MindWork AI Studio/Chat/ContentBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@ namespace AIStudio.Chat;
/// <summary>
/// A block of content in a chat thread. Might be any type of content, e.g., text, image, voice, etc.
/// </summary>
/// <param name="time">Time when the content block was created.</param>
/// <param name="type">Type of the content block, e.g., text, image, voice, etc.</param>
/// <param name="content">The content of the block.</param>
public class ContentBlock(DateTimeOffset time, ContentType type, IContent content)
public class ContentBlock
{
/// <summary>
/// Time when the content block was created.
/// </summary>
public DateTimeOffset Time => time;
public DateTimeOffset Time { get; init; }

/// <summary>
/// Type of the content block, e.g., text, image, voice, etc.
/// </summary>
public ContentType ContentType => type;
public ContentType ContentType { get; init; } = ContentType.NONE;

/// <summary>
/// The content of the block.
/// </summary>
public IContent Content => content;
public IContent? Content { get; init; } = null;

/// <summary>
/// The role of the content block in the chat thread, e.g., user, AI, etc.
Expand Down
6 changes: 6 additions & 0 deletions app/MindWork AI Studio/Chat/ContentImage.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;

using AIStudio.Provider;
using AIStudio.Settings;

Expand All @@ -11,15 +13,19 @@ public sealed class ContentImage : IContent
#region Implementation of IContent

/// <inheritdoc />
[JsonIgnore]
public bool InitialRemoteWait { get; set; } = false;

/// <inheritdoc />
[JsonIgnore]
public bool IsStreaming { get; set; } = false;

/// <inheritdoc />
[JsonIgnore]
public Func<Task> StreamingDone { get; set; } = () => Task.CompletedTask;

/// <inheritdoc />
[JsonIgnore]
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;

/// <inheritdoc />
Expand Down
7 changes: 7 additions & 0 deletions app/MindWork AI Studio/Chat/ContentText.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;

using AIStudio.Provider;
using AIStudio.Settings;

Expand All @@ -17,14 +19,19 @@ public sealed class ContentText : IContent
#region Implementation of IContent

/// <inheritdoc />
[JsonIgnore]
public bool InitialRemoteWait { get; set; }

/// <inheritdoc />
// [JsonIgnore]
public bool IsStreaming { get; set; }

/// <inheritdoc />
[JsonIgnore]
public Func<Task> StreamingDone { get; set; } = () => Task.CompletedTask;

/// <inheritdoc />
[JsonIgnore]
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;

/// <inheritdoc />
Expand Down
8 changes: 8 additions & 0 deletions app/MindWork AI Studio/Chat/IContent.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.Serialization;

using AIStudio.Provider;
using AIStudio.Settings;

Expand All @@ -6,29 +8,35 @@ namespace AIStudio.Chat;
/// <summary>
/// The interface for any content in the chat.
/// </summary>
[JsonDerivedType(typeof(ContentText), typeDiscriminator: "text")]
[JsonDerivedType(typeof(ContentImage), typeDiscriminator: "image")]
public interface IContent
{
/// <summary>
/// Do we need to wait for the remote, i.e., the AI, to process the related request?
/// Does not indicate that the stream is finished; it only indicates that we are
/// waiting for the first response, i.e., wait for the remote to pick up the request.
/// </summary>
[JsonIgnore]
public bool InitialRemoteWait { get; set; }

/// <summary>
/// Indicates whether the content is streaming right now. False, if the content is
/// either static or the stream has finished.
/// </summary>
[JsonIgnore]
public bool IsStreaming { get; set; }

/// <summary>
/// An action that is called when the content was changed during streaming.
/// </summary>
[JsonIgnore]
public Func<Task> StreamingEvent { get; set; }

/// <summary>
/// An action that is called when the streaming is done.
/// </summary>
[JsonIgnore]
public Func<Task> StreamingDone { get; set; }

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions app/MindWork AI Studio/Components/Blocks/Changelog.Logs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public readonly record struct Log(int Build, string Display, string Filename)

public static readonly Log[] LOGS =
[
new (160, "v0.7.0, build 160 (2024-07-13 08:21 UTC)", "v0.7.0.md"),
new (159, "v0.6.3, build 159 (2024-07-03 18:26 UTC)", "v0.6.3.md"),
new (158, "v0.6.2, build 158 (2024-07-01 18:03 UTC)", "v0.6.2.md"),
new (157, "v0.6.1, build 157 (2024-06-30 19:00 UTC)", "v0.6.1.md"),
Expand Down
3 changes: 3 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/ITreeItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace AIStudio.Components.Blocks;

public interface ITreeItem;
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ protected override async Task OnInitializedAsync()
return Task.CompletedTask;
}

public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
{
return Task.FromResult(default(TResult));
}

#endregion

private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});";
Expand Down
3 changes: 3 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/TreeButton.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace AIStudio.Components.Blocks;

public readonly record struct TreeButton(WorkspaceBranch Branch, int Depth, string Text, string Icon, Func<Task> Action) : ITreeItem;
3 changes: 3 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/TreeDivider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace AIStudio.Components.Blocks;

public readonly record struct TreeDivider : ITreeItem;
22 changes: 22 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/TreeItemData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace AIStudio.Components.Blocks;

public class TreeItemData : ITreeItem
{
public WorkspaceBranch Branch { get; init; } = WorkspaceBranch.NONE;

public int Depth { get; init; }

public string Text { get; init; } = string.Empty;

public string ShortenedText => Text.Length > 30 ? this.Text[..30] + "..." : this.Text;

public string Icon { get; init; } = string.Empty;

public TreeItemType Type { get; init; }

public string Path { get; init; } = string.Empty;

public bool Expandable { get; init; } = true;

public HashSet<ITreeItem> Children { get; init; } = [];
}
9 changes: 9 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/TreeItemType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace AIStudio.Components.Blocks;

public enum TreeItemType
{
NONE,

CHAT,
WORKSPACE,
}
9 changes: 9 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/WorkspaceBranch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace AIStudio.Components.Blocks;

public enum WorkspaceBranch
{
NONE,

WORKSPACES,
TEMPORARY_CHATS,
}
88 changes: 88 additions & 0 deletions app/MindWork AI Studio/Components/Blocks/Workspaces.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<MudTreeView T="ITreeItem" Items="@this.treeItems" MultiSelection="@false" Hover="@true" ExpandOnClick="@true">
<ItemTemplate Context="item">
@switch (item)
{
case TreeDivider:
<li style="min-height: 1em;">
<MudDivider Style="margin-top: 1em; width: 90%; border-width: 3pt;"/>
</li>
break;

case TreeItemData treeItem:
@if (treeItem.Type is TreeItemType.CHAT)
{
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item" CanExpand="@treeItem.Expandable" Items="@treeItem.Children" OnClick="() => this.LoadChat(treeItem.Path, true)">
<BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">
@if (string.IsNullOrWhiteSpace(treeItem.Text))
{
@("Empty chat")
}
else
{
@treeItem.ShortenedText
}
</MudText>
<div style="justify-self: end;">

<MudTooltip Text="Move to workspace" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.MoveToInbox" Size="Size.Medium" Color="Color.Inherit" OnClick="() => this.MoveChat(treeItem.Path)"/>
</MudTooltip>

<MudTooltip Text="Rename" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" OnClick="() => this.RenameChat(treeItem.Path)"/>
</MudTooltip>

<MudTooltip Text="Delete" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="() => this.DeleteChat(treeItem.Path)"/>
</MudTooltip>
</div>
</div>
</BodyContent>
</MudTreeViewItem>
}
else if (treeItem.Type is TreeItemType.WORKSPACE)
{
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item" 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 style="justify-self: end;">
<MudTooltip Text="Rename" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Inherit" OnClick="() => this.RenameWorkspace(treeItem.Path)"/>
</MudTooltip>

<MudTooltip Text="Delete" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="() => this.DeleteWorkspace(treeItem.Path)"/>
</MudTooltip>
</div>
</div>
</BodyContent>
</MudTreeViewItem>
}
else
{
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item" 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 treeButton:
<li>
<div class="mud-treeview-item-content" style="background-color: unset;">
<div class="mud-treeview-item-arrow"></div>
<MudButton StartIcon="@treeButton.Icon" Variant="Variant.Filled" OnClick="treeButton.Action">
@treeButton.Text
</MudButton>
</div>
</li>
break;
}
</ItemTemplate>
</MudTreeView>
Loading

0 comments on commit 59d0321

Please sign in to comment.