Compare commits

...
3 Commits
Author SHA1 Message Date
Thorsten Sommer 9267ef865d Added Fireworks provider (#43) 2024-07-25 15:29:44 +02:00
Thorsten Sommer 4f5815272a 39 improve hostname handling (#42) 2024-07-24 19:27:25 +02:00
Thorsten Sommer 5250e5d2fb Migrate to MudBlazor v7.x.x (#41) 2024-07-24 15:17:45 +02:00
42 changed files with 617 additions and 195 deletions

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@ MindWork AI Studio is a desktop application available for macOS, Windows, and Li
**Key advantages:** **Key advantages:**
- **Free of charge**: The app is free to use, both for personal and commercial purposes. - **Free of charge**: The app is free to use, both for personal and commercial purposes.
- **Independence**: Users are not tied to any single provider. Instead, they can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), or [LM Studio](https://lmstudio.ai/). Support for Google Gemini, [Replicate](https://replicate.com/), and [Fireworks](https://fireworks.ai/) is planned. - **Independence**: You are not tied to any single provider. Instead, you can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), or [Fireworks](https://fireworks.ai/). Support for Google Gemini, and [Replicate](https://replicate.com/) is planned.
- **Unrestricted usage**: Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API. - **Unrestricted usage**: Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.
- **Cost-effective**: You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. - **Cost-effective**: You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.
- **Privacy**: The data entered into the app is not used for training by the providers since we are using the provider's API. - **Privacy**: The data entered into the app is not used for training by the providers since we are using the provider's API.
@@ -20,7 +20,7 @@
{ {
<MudPaper Class="pr-2 mt-3" Outlined="@true"> <MudPaper Class="pr-2 mt-3" Outlined="@true">
<MudText Typo="Typo.h6">Issues</MudText> <MudText Typo="Typo.h6">Issues</MudText>
<MudList Clickable="@true"> <MudList T="string">
@foreach (var issue in this.inputIssues) @foreach (var issue in this.inputIssues)
{ {
<MudListItem Icon="@Icons.Material.Filled.Error" IconColor="Color.Error"> <MudListItem Icon="@Icons.Material.Filled.Error" IconColor="Color.Error">
@@ -13,6 +13,7 @@ public partial class Changelog
public static readonly Log[] LOGS = public static readonly Log[] LOGS =
[ [
new (165, "v0.8.3, build 165 (2024-07-25 13:25 UTC)", "v0.8.3.md"),
new (164, "v0.8.2, build 164 (2024-07-16 18:03 UTC)", "v0.8.2.md"), new (164, "v0.8.2, build 164 (2024-07-16 18:03 UTC)", "v0.8.2.md"),
new (163, "v0.8.1, build 163 (2024-07-16 08:32 UTC)", "v0.8.1.md"), new (163, "v0.8.1, build 163 (2024-07-16 08:32 UTC)", "v0.8.1.md"),
new (162, "v0.8.0, build 162 (2024-07-14 19:39 UTC)", "v0.8.0.md"), new (162, "v0.8.0, build 162 (2024-07-14 19:39 UTC)", "v0.8.0.md"),
@@ -1,4 +1,4 @@
<MudExpansionPanel Class="border-solid border rounded-lg" IsInitiallyExpanded="@this.IsExpanded"> <MudExpansionPanel Class="border-solid border rounded-lg" Expanded="@this.IsExpanded">
<TitleContent> <TitleContent>
<div class="d-flex align-center"> <div class="d-flex align-center">
<MudIcon Icon="@this.HeaderIcon" Size="@this.IconSize" Color="@this.IconColor" class="mr-3"/> <MudIcon Icon="@this.HeaderIcon" Size="@this.IconSize" Color="@this.IconColor" class="mr-3"/>
@@ -1,7 +1,7 @@
<MudList Clickable="@this.Clickable" Class="@this.Classes"> <MudList T="string" ReadOnly="@(!this.Clickable)" Class="@this.Classes">
@foreach(var item in this.Items) @foreach(var item in this.Items)
{ {
<MudListItem Icon="@this.Icon" Style="display: flex; align-items: flex-start;"> <MudListItem T="string" Icon="@this.Icon" Style="display: flex; align-items: flex-start;">
<MudText Typo="Typo.body1" Style="text-align: justify; hyphens: auto;"><b>@item.Header:</b> @item.Text</MudText> <MudText Typo="Typo.body1" Style="text-align: justify; hyphens: auto;"><b>@item.Header:</b> @item.Text</MudText>
</MudListItem> </MudListItem>
} }
@@ -18,5 +18,5 @@ public class TreeItemData : ITreeItem
public bool Expandable { get; init; } = true; public bool Expandable { get; init; } = true;
public HashSet<ITreeItem> Children { get; init; } = []; public IReadOnlyCollection<TreeItemData<ITreeItem>> Children { get; init; } = [];
} }
@@ -1,6 +1,6 @@
<MudTreeView T="ITreeItem" Items="@this.treeItems" MultiSelection="@false" Hover="@true" ExpandOnClick="@true"> <MudTreeView T="ITreeItem" Items="@this.treeItems" SelectionMode="SelectionMode.SingleSelection" Hover="@true" ExpandOnClick="@true">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
@switch (item) @switch (item.Value)
{ {
case TreeDivider: case TreeDivider:
<li style="min-height: 1em;"> <li style="min-height: 1em;">
@@ -11,7 +11,7 @@
case TreeItemData treeItem: case TreeItemData treeItem:
@if (treeItem.Type is TreeItemType.CHAT) @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)"> <MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" CanExpand="@treeItem.Expandable" Items="@treeItem.Children" OnClick="() => this.LoadChat(treeItem.Path, true)">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;"> <MudText Style="justify-self: start;">
@@ -44,7 +44,7 @@
} }
else if (treeItem.Type is TreeItemType.WORKSPACE) else if (treeItem.Type is TreeItemType.WORKSPACE)
{ {
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item" CanExpand="@treeItem.Expandable" Items="@treeItem.Children"> <MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" CanExpand="@treeItem.Expandable" Items="@treeItem.Children">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">@treeItem.Text</MudText> <MudText Style="justify-self: start;">@treeItem.Text</MudText>
@@ -63,7 +63,7 @@
} }
else else
{ {
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item" CanExpand="@treeItem.Expandable" Items="@treeItem.Children"> <MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" CanExpand="@treeItem.Expandable" Items="@treeItem.Children">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">@treeItem.Text</MudText> <MudText Style="justify-self: start;">@treeItem.Text</MudText>
@@ -47,8 +47,8 @@ public partial class Workspaces : ComponentBase
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper), new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
} }
}; };
private readonly HashSet<ITreeItem> treeItems = new(); private readonly List<TreeItemData<ITreeItem>> treeItems = new();
#region Overrides of ComponentBase #region Overrides of ComponentBase
@@ -70,33 +70,46 @@ public partial class Workspaces : ComponentBase
private async Task LoadTreeItems() private async Task LoadTreeItems()
{ {
this.treeItems.Clear(); this.treeItems.Clear();
this.treeItems.Add(new TreeItemData this.treeItems.Add(new TreeItemData<ITreeItem>
{ {
Depth = 0,
Branch = WorkspaceBranch.WORKSPACES,
Text = "Workspaces",
Icon = Icons.Material.Filled.Folder,
Expandable = true, Expandable = true,
Path = "root", Value = new TreeItemData
Children = await this.LoadWorkspaces(), {
Depth = 0,
Branch = WorkspaceBranch.WORKSPACES,
Text = "Workspaces",
Icon = Icons.Material.Filled.Folder,
Expandable = true,
Path = "root",
Children = await this.LoadWorkspaces(),
},
}); });
this.treeItems.Add(new TreeDivider()); this.treeItems.Add(new TreeItemData<ITreeItem>
this.treeItems.Add(new TreeItemData {
Expandable = false,
Value = new TreeDivider(),
});
this.treeItems.Add(new TreeItemData<ITreeItem>
{ {
Depth = 0,
Branch = WorkspaceBranch.TEMPORARY_CHATS,
Text = "Temporary chats",
Icon = Icons.Material.Filled.Timer,
Expandable = true, Expandable = true,
Path = "temp", Value = new TreeItemData
Children = await this.LoadTemporaryChats(), {
Depth = 0,
Branch = WorkspaceBranch.TEMPORARY_CHATS,
Text = "Temporary chats",
Icon = Icons.Material.Filled.Timer,
Expandable = true,
Path = "temp",
Children = await this.LoadTemporaryChats(),
},
}); });
} }
private async Task<HashSet<ITreeItem>> LoadTemporaryChats() private async Task<IReadOnlyCollection<TreeItemData<ITreeItem>>> LoadTemporaryChats()
{ {
var tempChildren = new HashSet<ITreeItem>(); var tempChildren = new List<TreeItemData<ITreeItem>>();
// //
// Search for workspace folders in the data directory: // Search for workspace folders in the data directory:
@@ -115,15 +128,19 @@ public partial class Workspaces : ComponentBase
var chatNamePath = Path.Join(tempChatDirPath, "name"); var chatNamePath = Path.Join(tempChatDirPath, "name");
var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8);
tempChildren.Add(new TreeItemData tempChildren.Add(new TreeItemData<ITreeItem>
{ {
Type = TreeItemType.CHAT,
Depth = 1,
Branch = WorkspaceBranch.TEMPORARY_CHATS,
Text = chatName,
Icon = Icons.Material.Filled.Timer,
Expandable = false, Expandable = false,
Path = tempChatDirPath, Value = new TreeItemData
{
Type = TreeItemType.CHAT,
Depth = 1,
Branch = WorkspaceBranch.TEMPORARY_CHATS,
Text = chatName,
Icon = Icons.Material.Filled.Timer,
Expandable = false,
Path = tempChatDirPath,
},
}); });
} }
@@ -140,9 +157,9 @@ public partial class Workspaces : ComponentBase
return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8);
} }
private async Task<HashSet<ITreeItem>> LoadWorkspaces() private async Task<IReadOnlyCollection<TreeItemData<ITreeItem>>> LoadWorkspaces()
{ {
var workspaces = new HashSet<ITreeItem>(); var workspaces = new List<TreeItemData<ITreeItem>>();
// //
// Search for workspace folders in the data directory: // Search for workspace folders in the data directory:
@@ -161,26 +178,34 @@ public partial class Workspaces : ComponentBase
var workspaceNamePath = Path.Join(workspaceDirPath, "name"); var workspaceNamePath = Path.Join(workspaceDirPath, "name");
var workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); var workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8);
workspaces.Add(new TreeItemData workspaces.Add(new TreeItemData<ITreeItem>
{ {
Type = TreeItemType.WORKSPACE,
Depth = 1,
Branch = WorkspaceBranch.WORKSPACES,
Text = workspaceName,
Icon = Icons.Material.Filled.Description,
Expandable = true, Expandable = true,
Path = workspaceDirPath, Value = new TreeItemData
Children = await this.LoadWorkspaceChats(workspaceDirPath), {
Type = TreeItemType.WORKSPACE,
Depth = 1,
Branch = WorkspaceBranch.WORKSPACES,
Text = workspaceName,
Icon = Icons.Material.Filled.Description,
Expandable = true,
Path = workspaceDirPath,
Children = await this.LoadWorkspaceChats(workspaceDirPath),
},
}); });
} }
workspaces.Add(new TreeButton(WorkspaceBranch.WORKSPACES, 1, "Add workspace",Icons.Material.Filled.LibraryAdd, this.AddWorkspace)); workspaces.Add(new TreeItemData<ITreeItem>
{
Expandable = false,
Value = new TreeButton(WorkspaceBranch.WORKSPACES, 1, "Add workspace",Icons.Material.Filled.LibraryAdd, this.AddWorkspace),
});
return workspaces; return workspaces;
} }
private async Task<HashSet<ITreeItem>> LoadWorkspaceChats(string workspacePath) private async Task<IReadOnlyCollection<TreeItemData<ITreeItem>>> LoadWorkspaceChats(string workspacePath)
{ {
var workspaceChats = new HashSet<ITreeItem>(); var workspaceChats = new List<TreeItemData<ITreeItem>>();
// Enumerate the workspace directory: // Enumerate the workspace directory:
foreach (var chatPath in Directory.EnumerateDirectories(workspacePath)) foreach (var chatPath in Directory.EnumerateDirectories(workspacePath))
@@ -189,19 +214,28 @@ public partial class Workspaces : ComponentBase
var chatNamePath = Path.Join(chatPath, "name"); var chatNamePath = Path.Join(chatPath, "name");
var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8);
workspaceChats.Add(new TreeItemData workspaceChats.Add(new TreeItemData<ITreeItem>
{ {
Type = TreeItemType.CHAT,
Depth = 2,
Branch = WorkspaceBranch.WORKSPACES,
Text = chatName,
Icon = Icons.Material.Filled.Chat,
Expandable = false, Expandable = false,
Path = chatPath, Value = new TreeItemData
{
Type = TreeItemType.CHAT,
Depth = 2,
Branch = WorkspaceBranch.WORKSPACES,
Text = chatName,
Icon = Icons.Material.Filled.Chat,
Expandable = false,
Path = chatPath,
},
}); });
} }
workspaceChats.Add(new TreeButton(WorkspaceBranch.WORKSPACES, 2, "Add chat",Icons.Material.Filled.AddComment, () => this.AddChat(workspacePath))); workspaceChats.Add(new TreeItemData<ITreeItem>
{
Expandable = false,
Value = new TreeButton(WorkspaceBranch.WORKSPACES, 2, "Add chat",Icons.Material.Filled.AddComment, () => this.AddChat(workspacePath)),
});
return workspaceChats; return workspaceChats;
} }
@@ -247,7 +281,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Load Chat", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Load Chat", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return null; return null;
} }
@@ -294,7 +328,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
} }
@@ -331,7 +365,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Rename Chat", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Rename Chat", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
chat.Name = (dialogResult.Data as string)!; chat.Name = (dialogResult.Data as string)!;
@@ -356,7 +390,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Rename Workspace", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Rename Workspace", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var alteredWorkspaceName = (dialogResult.Data as string)!; var alteredWorkspaceName = (dialogResult.Data as string)!;
@@ -377,7 +411,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Add Workspace", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>("Add Workspace", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var workspaceId = Guid.NewGuid(); var workspaceId = Guid.NewGuid();
@@ -408,7 +442,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Workspace", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Workspace", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
Directory.Delete(workspacePath, true); Directory.Delete(workspacePath, true);
@@ -430,7 +464,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<WorkspaceSelectionDialog>("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<WorkspaceSelectionDialog>("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var workspaceId = dialogResult.Data is Guid id ? id : default; var workspaceId = dialogResult.Data is Guid id ? id : default;
@@ -475,7 +509,7 @@ public partial class Workspaces : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Create Chat", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Create Chat", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
} }
@@ -1,4 +1,5 @@
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -23,7 +24,12 @@ public partial class ConfigurationBase : ComponentBase
public string OptionHelp { get; set; } = string.Empty; public string OptionHelp { get; set; } = string.Empty;
[Inject] [Inject]
public SettingsManager SettingsManager { get; init; } = null!; protected SettingsManager SettingsManager { get; init; } = null!;
[Inject]
protected MessageBus MessageBus { get; init; } = null!;
protected const string MARGIN_CLASS = "mb-6"; protected const string MARGIN_CLASS = "mb-6";
protected async Task InformAboutChange() => await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
@@ -35,5 +35,6 @@ public partial class ConfigurationOption : ConfigurationBase
{ {
this.StateUpdate(updatedState); this.StateUpdate(updatedState);
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
} }
} }
@@ -30,6 +30,7 @@ public partial class ConfigurationSelect<T> : ConfigurationBase
{ {
this.SelectionUpdate(updatedValue); this.SelectionUpdate(updatedValue);
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
} }
private static string GetClass => $"{MARGIN_CLASS} rounded-lg"; private static string GetClass => $"{MARGIN_CLASS} rounded-lg";
@@ -47,4 +47,12 @@ public static class ConfigurationSelectDataFactory
yield return new("Delete temporary chats older than 180 days", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_180_DAYS); yield return new("Delete temporary chats older than 180 days", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_180_DAYS);
yield return new("Delete temporary chats older than 1 year", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_365_DAYS); yield return new("Delete temporary chats older than 1 year", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_365_DAYS);
} }
public static IEnumerable<ConfigurationSelectData<NavBehavior>> GetNavBehaviorData()
{
yield return new("Navigation expands on mouse hover", NavBehavior.EXPAND_ON_HOVER);
yield return new("Navigation never expands, but there are tooltips", NavBehavior.NEVER_EXPAND_USE_TOOLTIPS);
yield return new("Navigation never expands, no tooltips", NavBehavior.NEVER_EXPAND_NO_TOOLTIPS);
yield return new("Always expand navigation", NavBehavior.ALWAYS_EXPAND);
}
} }
@@ -1,36 +1,32 @@
@inherits LayoutComponentBase @using AIStudio.Settings
@inherits LayoutComponentBase
<MudPaper Height="calc(100vh);" Elevation="0"> <MudPaper Height="calc(100vh);" Elevation="0">
<MudLayout> <MudLayout>
@if (!this.performingUpdate) @if (!this.performingUpdate)
{ {
<MudDrawerContainer Class="mud-height-full absolute"> <MudDrawerContainer Class="mud-height-full absolute">
<MudDrawer Elevation="0" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@true" Color="Color.Default"> <MudDrawer @bind-Open="@this.navBarOpen" MiniWidth="@NAVBAR_COLLAPSED_WIDTH" Width="@NAVBAR_EXPANDED_WIDTH" Elevation="1" Fixed="@true" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@(this.SettingsManager.ConfigurationData.NavigationBehavior is NavBehavior.EXPAND_ON_HOVER)" Color="Color.Default">
<MudNavMenu> <MudNavMenu>
<MudTooltip Text="Home" Placement="Placement.Right"> @foreach (var navBarItem in NAV_ITEMS)
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink> {
</MudTooltip> if (this.SettingsManager.ConfigurationData.NavigationBehavior is NavBehavior.NEVER_EXPAND_USE_TOOLTIPS)
<MudTooltip Text="Chat" Placement="Placement.Right"> {
<MudNavLink Href="/chat" Icon="@Icons.Material.Filled.Chat">Chat</MudNavLink> <MudTooltip Text="@navBarItem.Name" Placement="Placement.Right">
</MudTooltip> <MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" IconColor="@navBarItem.IconColor">@navBarItem.Name</MudNavLink>
<MudTooltip Text="Assistants" Placement="Placement.Right"> </MudTooltip>
<MudNavLink Href="/assistants" Icon="@Icons.Material.Filled.Apps">Assistants</MudNavLink> }
</MudTooltip> else
<MudTooltip Text="Supporters" Placement="Placement.Right"> {
<MudNavLink Href="/supporters" Icon="@Icons.Material.Filled.Favorite" IconColor="Color.Error">Supporters</MudNavLink> <MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" IconColor="@navBarItem.IconColor">@navBarItem.Name</MudNavLink>
</MudTooltip> }
<MudTooltip Text="About" Placement="Placement.Right"> }
<MudNavLink Href="/about" Icon="@Icons.Material.Filled.Info">About</MudNavLink>
</MudTooltip>
<MudTooltip Text="Settings" Placement="Placement.Right">
<MudNavLink Href="/settings" Icon="@Icons.Material.Filled.Settings">Settings</MudNavLink>
</MudTooltip>
</MudNavMenu> </MudNavMenu>
</MudDrawer> </MudDrawer>
</MudDrawerContainer> </MudDrawerContainer>
} }
<MudMainContent Class="mud-height-full pt-1"> <MudMainContent Class="mud-height-full pt-1" Style="@this.PaddingLeft">
<MudContainer Fixed="@true" Class="mud-height-full" Style="margin-left: 5em; width: calc(100% - 5em);"> <MudContainer Fixed="@true" Class="mud-height-full" Style="margin-left: 5em; width: calc(100% - 5em);">
@if (!this.performingUpdate && this.IsUpdateAlertVisible) @if (!this.performingUpdate && this.IsUpdateAlertVisible)
{ {
@@ -34,12 +34,30 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
public string AdditionalHeight { get; private set; } = "0em"; public string AdditionalHeight { get; private set; } = "0em";
private string PaddingLeft => this.navBarOpen ? $"padding-left: {NAVBAR_EXPANDED_WIDTH_INT - NAVBAR_COLLAPSED_WIDTH_INT}em;" : "padding-left: 0em;";
private const int NAVBAR_COLLAPSED_WIDTH_INT = 4;
private const int NAVBAR_EXPANDED_WIDTH_INT = 10;
private static readonly string NAVBAR_COLLAPSED_WIDTH = $"{NAVBAR_COLLAPSED_WIDTH_INT}em";
private static readonly string NAVBAR_EXPANDED_WIDTH = $"{NAVBAR_EXPANDED_WIDTH_INT}em";
private bool navBarOpen;
private bool isUpdateAvailable; private bool isUpdateAvailable;
private bool performingUpdate; private bool performingUpdate;
private bool userDismissedUpdate; private bool userDismissedUpdate;
private string updateToVersion = string.Empty; private string updateToVersion = string.Empty;
private UpdateResponse? currentUpdateResponse; private UpdateResponse? currentUpdateResponse;
private static readonly IReadOnlyCollection<NavBarItem> NAV_ITEMS = new List<NavBarItem>
{
new("Home", Icons.Material.Filled.Home, Color.Default, "/", true),
new("Chat", Icons.Material.Filled.Chat, Color.Default, "/chat", false),
new("Assistants", Icons.Material.Filled.Apps, Color.Default ,"/assistants", false),
new("Supporters", Icons.Material.Filled.Favorite, Color.Error ,"/supporters", false),
new("About", Icons.Material.Filled.Info, Color.Default ,"/about", false),
new("Settings", Icons.Material.Filled.Settings, Color.Default ,"/settings", false),
};
#region Overrides of ComponentBase #region Overrides of ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -63,12 +81,16 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
// Register this component with the message bus: // Register this component with the message bus:
this.MessageBus.RegisterComponent(this); this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE ]); this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED ]);
// Set the js runtime for the update service: // Set the js runtime for the update service:
UpdateService.SetBlazorDependencies(this.JsRuntime, this.Snackbar); UpdateService.SetBlazorDependencies(this.JsRuntime, this.Snackbar);
TemporaryChatService.Initialize(); TemporaryChatService.Initialize();
// Should the navigation bar be open by default?
if(this.SettingsManager.ConfigurationData.NavigationBehavior is NavBehavior.ALWAYS_EXPAND)
this.navBarOpen = true;
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
@@ -96,6 +118,15 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
} }
break; break;
case Event.CONFIGURATION_CHANGED:
if(this.SettingsManager.ConfigurationData.NavigationBehavior is NavBehavior.ALWAYS_EXPAND)
this.navBarOpen = true;
else
this.navBarOpen = false;
this.StateHasChanged();
break;
} }
} }
@@ -105,7 +136,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
} }
#endregion #endregion
private async Task DismissUpdate() private async Task DismissUpdate()
{ {
this.userDismissedUpdate = true; this.userDismissedUpdate = true;
@@ -151,7 +182,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
var dialogReference = await this.DialogService.ShowAsync<UpdateDialog>("Update", dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER); var dialogReference = await this.DialogService.ShowAsync<UpdateDialog>("Update", dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
this.performingUpdate = true; this.performingUpdate = true;
@@ -170,7 +201,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Leave Chat Page", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Leave Chat Page", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
{ {
context.PreventNavigation(); context.PreventNavigation();
return; return;
@@ -0,0 +1,3 @@
namespace AIStudio.Components.Layout;
public record NavBarItem(string Name, string Icon, Color IconColor, string Path, bool MatchAll);
@@ -9,14 +9,14 @@
<MudText> <MudText>
The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.: The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.:
</MudText> </MudText>
<MudList Clickable="@true"> <MudList T="string">
<MudListItem Icon="@Icons.Material.Outlined.Chat" Text="@VersionApp"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Chat" Text="@VersionApp"/>
<MudListItem Icon="@Icons.Material.Outlined.Timer" Text="@BuildTime"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Timer" Text="@BuildTime"/>
<MudListItem Icon="@Icons.Material.Outlined.Build" Text="@VersionDotnetSdk"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@VersionDotnetSdk"/>
<MudListItem Icon="@Icons.Material.Outlined.Memory" Text="@VersionDotnetRuntime"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@VersionDotnetRuntime"/>
<MudListItem Icon="@Icons.Material.Outlined.Build" Text="@VersionRust"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@VersionRust"/>
<MudListItem Icon="@Icons.Material.Outlined.Widgets" Text="@MudBlazorVersion"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Widgets" Text="@MudBlazorVersion"/>
<MudListItem Icon="@Icons.Material.Outlined.Memory" Text="@TauriVersion"/> <MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@TauriVersion"/>
</MudList> </MudList>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Update" OnClick="() => this.CheckForUpdate()"> <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Update" OnClick="() => this.CheckForUpdate()">
Check for updates Check for updates
@@ -235,7 +235,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
} }
@@ -297,7 +297,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
var confirmationDialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Unsaved Changes", confirmationDialogParameters, DialogOptions.FULLSCREEN); var confirmationDialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Unsaved Changes", confirmationDialogParameters, DialogOptions.FULLSCREEN);
var confirmationDialogResult = await confirmationDialogReference.Result; var confirmationDialogResult = await confirmationDialogReference.Result;
if (confirmationDialogResult.Canceled) if (confirmationDialogResult is null || confirmationDialogResult.Canceled)
return; return;
} }
@@ -310,7 +310,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
var dialogReference = await this.DialogService.ShowAsync<WorkspaceSelectionDialog>("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<WorkspaceSelectionDialog>("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var workspaceId = dialogResult.Data is Guid id ? id : default; var workspaceId = dialogResult.Data is Guid id ? id : default;
@@ -31,7 +31,7 @@ public partial class Home : ComponentBase
private static readonly TextItem[] ITEMS_ADVANTAGES = private static readonly TextItem[] ITEMS_ADVANTAGES =
[ [
new TextItem("Free of charge", "The app is free to use, both for personal and commercial purposes."), new TextItem("Free of charge", "The app is free to use, both for personal and commercial purposes."),
new TextItem("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using llama.cpp, ollama, or LM Studio. Support for Google Gemini, Replicate, and Fireworks is planned."), new TextItem("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using llama.cpp, ollama, LM Studio, or Fireworks. Support for Google Gemini and Replicate is planned."),
new TextItem("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."), new TextItem("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."),
new TextItem("Cost-effective", "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit."), new TextItem("Cost-effective", "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit."),
new TextItem("Privacy", "The data entered into the app is not used for training by the providers since we are using the provider's API."), new TextItem("Privacy", "The data entered into the app is not used for training by the providers since we are using the provider's API."),
@@ -1,5 +1,3 @@
using AIStudio.Provider;
namespace AIStudio.Components.Pages.IconFinder; namespace AIStudio.Components.Pages.IconFinder;
public partial class AssistantIconFinder : AssistantBaseCore public partial class AssistantIconFinder : AssistantBaseCore
@@ -29,11 +29,11 @@
<MudTd> <MudTd>
@if (context.UsedProvider is not Providers.SELF_HOSTED) @if (context.UsedProvider is not Providers.SELF_HOSTED)
{ {
@context.Model @this.GetProviderModelName(context)
} }
else if (context.UsedProvider is Providers.SELF_HOSTED && context.Host is not Host.LLAMACPP) else if (context.UsedProvider is Providers.SELF_HOSTED && context.Host is not Host.LLAMACPP)
{ {
@context.Model @this.GetProviderModelName(context)
} }
else else
{ {
@@ -41,7 +41,7 @@
} }
</MudTd> </MudTd>
<MudTd Style="text-align: left;"> <MudTd Style="text-align: left;">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(context.UsedProvider is Providers.NONE or Providers.SELF_HOSTED)"> <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Class="ma-2" Href="@this.GetProviderDashboardURL(context.UsedProvider)" Target="_blank" Disabled="@(!this.HasDashboard(context.UsedProvider))">
Open Dashboard Open Dashboard
</MudButton> </MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProvider(context)"> <MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditProvider(context)">
@@ -73,5 +73,6 @@
<ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/> <ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/>
<ConfigurationSelect OptionDescription="Workspace behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior = selectedValue)" OptionHelp="Should we store your chats?"/> <ConfigurationSelect OptionDescription="Workspace behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.WorkspaceStorageBehavior = selectedValue)" OptionHelp="Should we store your chats?"/>
<ConfigurationSelect OptionDescription="Workspace maintenance" SelectedValue="@(() => this.SettingsManager.ConfigurationData.WorkspaceStorageTemporaryMaintenancePolicy)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageTemporaryMaintenancePolicyData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.WorkspaceStorageTemporaryMaintenancePolicy = selectedValue)" OptionHelp="If and when should we delete your temporary chats?"/> <ConfigurationSelect OptionDescription="Workspace maintenance" SelectedValue="@(() => this.SettingsManager.ConfigurationData.WorkspaceStorageTemporaryMaintenancePolicy)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageTemporaryMaintenancePolicyData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.WorkspaceStorageTemporaryMaintenancePolicy = selectedValue)" OptionHelp="If and when should we delete your temporary chats?"/>
<ConfigurationSelect OptionDescription="Navigation bar behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.NavigationBehavior = selectedValue)" OptionHelp="Select the desired behavior for the navigation bar."/>
</MudPaper> </MudPaper>
</InnerScrolling> </InnerScrolling>
@@ -1,6 +1,8 @@
using AIStudio.Components.CommonDialogs; using AIStudio.Components.CommonDialogs;
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions; using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
@@ -19,6 +21,9 @@ public partial class Settings : ComponentBase
[Inject] [Inject]
public IJSRuntime JsRuntime { get; init; } = null!; public IJSRuntime JsRuntime { get; init; } = null!;
[Inject]
protected MessageBus MessageBus { get; init; } = null!;
#region Provider related #region Provider related
@@ -31,14 +36,15 @@ public partial class Settings : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add Provider", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var addedProvider = (AIStudio.Settings.Provider)dialogResult.Data; var addedProvider = (AIStudio.Settings.Provider)dialogResult.Data!;
addedProvider = addedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ }; addedProvider = addedProvider with { Num = this.SettingsManager.ConfigurationData.NextProviderNum++ };
this.SettingsManager.ConfigurationData.Providers.Add(addedProvider); this.SettingsManager.ConfigurationData.Providers.Add(addedProvider);
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
private async Task EditProvider(AIStudio.Settings.Provider provider) private async Task EditProvider(AIStudio.Settings.Provider provider)
@@ -58,10 +64,10 @@ public partial class Settings : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var editedProvider = (AIStudio.Settings.Provider)dialogResult.Data; var editedProvider = (AIStudio.Settings.Provider)dialogResult.Data!;
// Set the provider number if it's not set. This is important for providers // Set the provider number if it's not set. This is important for providers
// added before we started saving the provider number. // added before we started saving the provider number.
@@ -70,6 +76,7 @@ public partial class Settings : ComponentBase
this.SettingsManager.ConfigurationData.Providers[this.SettingsManager.ConfigurationData.Providers.IndexOf(provider)] = editedProvider; this.SettingsManager.ConfigurationData.Providers[this.SettingsManager.ConfigurationData.Providers.IndexOf(provider)] = editedProvider;
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
private async Task DeleteProvider(AIStudio.Settings.Provider provider) private async Task DeleteProvider(AIStudio.Settings.Provider provider)
@@ -81,7 +88,7 @@ public partial class Settings : ComponentBase
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN); var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result; var dialogResult = await dialogReference.Result;
if (dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var providerInstance = provider.CreateProvider(); var providerInstance = provider.CreateProvider();
@@ -91,16 +98,36 @@ public partial class Settings : ComponentBase
this.SettingsManager.ConfigurationData.Providers.Remove(provider); this.SettingsManager.ConfigurationData.Providers.Remove(provider);
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
} }
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
private bool HasDashboard(Providers provider) => provider switch
{
Providers.OPEN_AI => true,
Providers.MISTRAL => true,
Providers.ANTHROPIC => true,
Providers.FIREWORKS => true,
_ => false,
};
private string GetProviderDashboardURL(Providers provider) => provider switch private string GetProviderDashboardURL(Providers provider) => provider switch
{ {
Providers.OPEN_AI => "https://platform.openai.com/usage", Providers.OPEN_AI => "https://platform.openai.com/usage",
Providers.MISTRAL => "https://console.mistral.ai/usage/", Providers.MISTRAL => "https://console.mistral.ai/usage/",
Providers.ANTHROPIC => "https://console.anthropic.com/settings/plans", Providers.ANTHROPIC => "https://console.anthropic.com/settings/plans",
Providers.FIREWORKS => "https://fireworks.ai/account/billing",
_ => string.Empty, _ => string.Empty,
}; };
private string GetProviderModelName(AIStudio.Settings.Provider provider)
{
const int MAX_LENGTH = 36;
var modelName = provider.Model.ToString();
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
}
#endregion #endregion
} }
@@ -1,4 +1,3 @@
using AIStudio.Provider;
using AIStudio.Tools; using AIStudio.Tools;
namespace AIStudio.Components.Pages.TextSummarizer; namespace AIStudio.Components.Pages.TextSummarizer;
@@ -1,4 +1,3 @@
using AIStudio.Provider;
using AIStudio.Tools; using AIStudio.Tools;
namespace AIStudio.Components.Pages.Translator; namespace AIStudio.Components.Pages.Translator;
@@ -7,4 +7,5 @@
<MudThemeProvider /> <MudThemeProvider />
<MudDialogProvider /> <MudDialogProvider />
<MudPopoverProvider />
<MudSnackbarProvider /> <MudSnackbarProvider />
@@ -45,8 +45,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.6" /> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.7" />
<PackageReference Include="MudBlazor" Version="6.20.0" /> <PackageReference Include="MudBlazor" Version="7.4.0" />
<PackageReference Include="MudBlazor.Markdown" Version="1.0.2" /> <PackageReference Include="MudBlazor.Markdown" Version="1.0.2" />
</ItemGroup> </ItemGroup>
@@ -0,0 +1,13 @@
namespace AIStudio.Provider.Fireworks;
/// <summary>
/// The Fireworks chat request model.
/// </summary>
/// <param name="Model">Which model to use for chat completion.</param>
/// <param name="Messages">The chat messages.</param>
/// <param name="Stream">Whether to stream the chat completion.</param>
public readonly record struct ChatRequest(
string Model,
IList<Message> Messages,
bool Stream
);
@@ -0,0 +1,8 @@
namespace AIStudio.Provider.Fireworks;
/// <summary>
/// Chat message model.
/// </summary>
/// <param name="Content">The text content of the message.</param>
/// <param name="Role">The role of the message.</param>
public readonly record struct Message(string Content, string Role);
@@ -0,0 +1,160 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Settings;
namespace AIStudio.Provider.Fireworks;
public class ProviderFireworks() : BaseProvider("https://api.fireworks.ai/inference/v1/"), IProvider
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};
#region Implementation of IProvider
/// <inheritdoc />
public string Id => "Fireworks.ai";
/// <inheritdoc />
public string InstanceName { get; set; } = "Fireworks.ai";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
if(!requestedSecret.Success)
yield break;
// Prepare the system prompt:
var systemPrompt = new Message
{
Role = "system",
Content = chatThread.SystemPrompt,
};
// Prepare the Fireworks HTTP chat request:
var fireworksChatRequest = JsonSerializer.Serialize(new ChatRequest
{
Model = chatModel.Id,
// Build the messages:
// - First of all the system prompt
// - Then none-empty user and AI messages
Messages = [systemPrompt, ..chatThread.Blocks.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)).Select(n => new Message
{
Role = n.Role switch
{
ChatRole.USER => "user",
ChatRole.AI => "assistant",
ChatRole.SYSTEM => "system",
_ => "user",
},
Content = n.Content switch
{
ContentText text => text.Text,
_ => string.Empty,
}
}).ToList()],
// Right now, we only support streaming completions:
Stream = true,
}, JSON_SERIALIZER_OPTIONS);
// Build the HTTP post request:
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
// Set the authorization header:
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", requestedSecret.Secret);
// Set the content:
request.Content = new StringContent(fireworksChatRequest, Encoding.UTF8, "application/json");
// Send the request with the ResponseHeadersRead option.
// This allows us to read the stream as soon as the headers are received.
// This is important because we want to stream the responses.
var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
// Open the response stream:
var fireworksStream = await response.Content.ReadAsStreamAsync(token);
// Add a stream reader to read the stream, line by line:
var streamReader = new StreamReader(fireworksStream);
// Read the stream, line by line:
while(!streamReader.EndOfStream)
{
// Check if the token is canceled:
if(token.IsCancellationRequested)
yield break;
// Read the next line:
var line = await streamReader.ReadLineAsync(token);
// Skip empty lines:
if(string.IsNullOrWhiteSpace(line))
continue;
// Skip lines that do not start with "data: ". Regard
// to the specification, we only want to read the data lines:
if(!line.StartsWith("data: ", StringComparison.InvariantCulture))
continue;
// Check if the line is the end of the stream:
if (line.StartsWith("data: [DONE]", StringComparison.InvariantCulture))
yield break;
ResponseStreamLine fireworksResponse;
try
{
// We know that the line starts with "data: ". Hence, we can
// skip the first 6 characters to get the JSON data after that.
var jsonData = line[6..];
// Deserialize the JSON data:
fireworksResponse = JsonSerializer.Deserialize<ResponseStreamLine>(jsonData, JSON_SERIALIZER_OPTIONS);
}
catch
{
// Skip invalid JSON data:
continue;
}
// Skip empty responses:
if(fireworksResponse == default || fireworksResponse.Choices.Count == 0)
continue;
// Yield the response:
yield return fireworksResponse.Choices[0].Delta.Content;
}
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
/// <inheritdoc />
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion
}
@@ -0,0 +1,24 @@
namespace AIStudio.Provider.Fireworks;
/// <summary>
/// Data model for a line in the response stream, for streaming completions.
/// </summary>
/// <param name="Id">The id of the response.</param>
/// <param name="Object">The object describing the response.</param>
/// <param name="Created">The timestamp of the response.</param>
/// <param name="Model">The model used for the response.</param>
/// <param name="Choices">The choices made by the AI.</param>
public readonly record struct ResponseStreamLine(string Id, string Object, uint Created, string Model, IList<Choice> Choices);
/// <summary>
/// Data model for a choice made by the AI.
/// </summary>
/// <param name="Index">The index of the choice.</param>
/// <param name="Delta">The delta text of the choice.</param>
public readonly record struct Choice(int Index, Delta Delta);
/// <summary>
/// The delta text of a choice.
/// </summary>
/// <param name="Content">The content of the delta text.</param>
public readonly record struct Delta(string Content);
@@ -97,7 +97,7 @@ public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"
// Read the stream, line by line: // Read the stream, line by line:
while(!streamReader.EndOfStream) while(!streamReader.EndOfStream)
{ {
// Check if the token is cancelled: // Check if the token is canceled:
if(token.IsCancellationRequested) if(token.IsCancellationRequested)
yield break; yield break;
+32 -14
View File
@@ -1,4 +1,5 @@
using AIStudio.Provider.Anthropic; using AIStudio.Provider.Anthropic;
using AIStudio.Provider.Fireworks;
using AIStudio.Provider.Mistral; using AIStudio.Provider.Mistral;
using AIStudio.Provider.OpenAI; using AIStudio.Provider.OpenAI;
using AIStudio.Provider.SelfHosted; using AIStudio.Provider.SelfHosted;
@@ -10,13 +11,15 @@ namespace AIStudio.Provider;
/// </summary> /// </summary>
public enum Providers public enum Providers
{ {
NONE, NONE = 0,
OPEN_AI, OPEN_AI = 1,
ANTHROPIC, ANTHROPIC = 2,
MISTRAL, MISTRAL = 3,
SELF_HOSTED, FIREWORKS = 5,
SELF_HOSTED = 4,
} }
/// <summary> /// <summary>
@@ -37,6 +40,8 @@ public static class ExtensionsProvider
Providers.ANTHROPIC => "Anthropic", Providers.ANTHROPIC => "Anthropic",
Providers.MISTRAL => "Mistral", Providers.MISTRAL => "Mistral",
Providers.FIREWORKS => "Fireworks.ai",
Providers.SELF_HOSTED => "Self-hosted", Providers.SELF_HOSTED => "Self-hosted",
_ => "Unknown", _ => "Unknown",
@@ -47,14 +52,27 @@ public static class ExtensionsProvider
/// </summary> /// </summary>
/// <param name="providerSettings">The provider settings.</param> /// <param name="providerSettings">The provider settings.</param>
/// <returns>The provider instance.</returns> /// <returns>The provider instance.</returns>
public static IProvider CreateProvider(this Settings.Provider providerSettings) => providerSettings.UsedProvider switch public static IProvider CreateProvider(this Settings.Provider providerSettings)
{ {
Providers.OPEN_AI => new ProviderOpenAI { InstanceName = providerSettings.InstanceName }, try
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = providerSettings.InstanceName }, {
Providers.MISTRAL => new ProviderMistral { InstanceName = providerSettings.InstanceName }, return providerSettings.UsedProvider switch
{
Providers.SELF_HOSTED => new ProviderSelfHosted(providerSettings) { InstanceName = providerSettings.InstanceName }, Providers.OPEN_AI => new ProviderOpenAI { InstanceName = providerSettings.InstanceName },
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = providerSettings.InstanceName },
_ => new NoProvider(), Providers.MISTRAL => new ProviderMistral { InstanceName = providerSettings.InstanceName },
};
Providers.FIREWORKS => new ProviderFireworks { InstanceName = providerSettings.InstanceName },
Providers.SELF_HOSTED => new ProviderSelfHosted(providerSettings) { InstanceName = providerSettings.InstanceName },
_ => new NoProvider(),
};
}
catch (Exception e)
{
Console.WriteLine($"Failed to create provider: {e.Message}");
return new NoProvider();
}
}
} }
+5
View File
@@ -51,4 +51,9 @@ public sealed class Data
/// The chat storage maintenance behavior. /// The chat storage maintenance behavior.
/// </summary> /// </summary>
public WorkspaceStorageTemporaryMaintenancePolicy WorkspaceStorageTemporaryMaintenancePolicy { get; set; } = WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_90_DAYS; public WorkspaceStorageTemporaryMaintenancePolicy WorkspaceStorageTemporaryMaintenancePolicy { get; set; } = WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_90_DAYS;
/// <summary>
/// The navigation behavior.
/// </summary>
public NavBehavior NavigationBehavior { get; set; } = NavBehavior.EXPAND_ON_HOVER;
} }
@@ -0,0 +1,9 @@
namespace AIStudio.Settings;
public enum NavBehavior
{
EXPAND_ON_HOVER,
NEVER_EXPAND_USE_TOOLTIPS,
NEVER_EXPAND_NO_TOOLTIPS,
ALWAYS_EXPAND,
}
@@ -13,7 +13,7 @@
<MudSelectItem Value="@provider">@provider</MudSelectItem> <MudSelectItem Value="@provider">@provider</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudButton Disabled="@this.IsSelfHostedOrNone" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetProviderCreationURL()" Target="_blank">Create account</MudButton> <MudButton Disabled="@(!this.ShowRegisterButton)" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetProviderCreationURL()" Target="_blank">Create account</MudButton>
</MudStack> </MudStack>
@* ReSharper disable once CSharpWarnings::CS8974 *@ @* ReSharper disable once CSharpWarnings::CS8974 *@
@@ -21,7 +21,7 @@
T="string" T="string"
@bind-Text="@this.dataAPIKey" @bind-Text="@this.dataAPIKey"
Label="API Key" Label="API Key"
Disabled="@this.IsSelfHostedOrNone" Disabled="@(!this.NeedAPIKey)"
Class="mb-3" Class="mb-3"
Adornment="Adornment.Start" Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.VpnKey" AdornmentIcon="@Icons.Material.Filled.VpnKey"
@@ -34,7 +34,7 @@
T="string" T="string"
@bind-Text="@this.DataHostname" @bind-Text="@this.DataHostname"
Label="Hostname" Label="Hostname"
Disabled="@this.IsCloudProvider" Disabled="@(!this.NeedHostname)"
Class="mb-3" Class="mb-3"
Adornment="Adornment.Start" Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns" AdornmentIcon="@Icons.Material.Filled.Dns"
@@ -43,7 +43,7 @@
UserAttributes="@SPELLCHECK_ATTRIBUTES" UserAttributes="@SPELLCHECK_ATTRIBUTES"
/> />
<MudSelect Disabled="@this.IsCloudProvider" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingHost"> <MudSelect Disabled="@(!this.NeedHost)" @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host))) @foreach (Host host in Enum.GetValues(typeof(Host)))
{ {
<MudSelectItem Value="@host">@host.Name()</MudSelectItem> <MudSelectItem Value="@host">@host.Name()</MudSelectItem>
@@ -51,13 +51,31 @@
</MudSelect> </MudSelect>
<MudStack Row="@true" AlignItems="AlignItems.Center"> <MudStack Row="@true" AlignItems="AlignItems.Center">
<MudButton Disabled="@(!this.CanLoadModels())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton> @if (this.ProvideModelManually)
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel"> {
@foreach (var model in this.availableModels) <MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.GetModelOverviewURL()" Target="_blank">Show available models</MudButton>
{ <MudTextField
<MudSelectItem Value="@model">@model</MudSelectItem> T="string"
} @bind-Text="@this.dataManuallyModel"
</MudSelect> Label="Model"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
}
else
{
<MudButton Disabled="@(!this.CanLoadModels())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">Load</MudButton>
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.ValidatingModel">
@foreach (var model in this.availableModels)
{
<MudSelectItem Value="@model">@model</MudSelectItem>
}
</MudSelect>
}
</MudStack> </MudStack>
@* ReSharper disable once CSharpWarnings::CS8974 *@ @* ReSharper disable once CSharpWarnings::CS8974 *@
@@ -79,10 +97,10 @@
{ {
<MudPaper Class="pa-2 mt-3"> <MudPaper Class="pa-2 mt-3">
<MudText Typo="Typo.h6">Issues</MudText> <MudText Typo="Typo.h6">Issues</MudText>
<MudList Clickable="@true"> <MudList T="string">
@foreach (var issue in this.dataIssues) @foreach (var issue in this.dataIssues)
{ {
<MudListItem Icon="@Icons.Material.Filled.Error" IconColor="Color.Error"> <MudListItem T="string" Icon="@Icons.Material.Filled.Error" IconColor="Color.Error">
@issue @issue
</MudListItem> </MudListItem>
} }
@@ -86,6 +86,7 @@ public partial class ProviderDialog : ComponentBase
private bool dataIsValid; private bool dataIsValid;
private string[] dataIssues = []; private string[] dataIssues = [];
private string dataAPIKey = string.Empty; private string dataAPIKey = string.Empty;
private string dataManuallyModel = string.Empty;
private string dataAPIKeyStorageIssue = string.Empty; private string dataAPIKeyStorageIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty; private string dataEditingPreviousInstanceName = string.Empty;
@@ -100,9 +101,9 @@ public partial class ProviderDialog : ComponentBase
Id = this.DataId, Id = this.DataId,
InstanceName = this.DataInstanceName, InstanceName = this.DataInstanceName,
UsedProvider = this.DataProvider, UsedProvider = this.DataProvider,
Model = this.DataModel, Model = this.DataProvider is Providers.FIREWORKS ? new Model(this.dataManuallyModel) : this.DataModel,
IsSelfHosted = this.DataProvider is Providers.SELF_HOSTED, IsSelfHosted = this.DataProvider is Providers.SELF_HOSTED,
Hostname = this.DataHostname, Hostname = this.DataHostname.EndsWith('/') ? this.DataHostname[..^1] : this.DataHostname,
Host = this.DataHost, Host = this.DataHost,
}; };
@@ -220,6 +221,14 @@ public partial class ProviderDialog : ComponentBase
return null; return null;
} }
private string? ValidateManuallyModel(string manuallyModel)
{
if (this.DataProvider is Providers.FIREWORKS && string.IsNullOrWhiteSpace(manuallyModel))
return "Please enter a model name.";
return null;
}
private string? ValidatingModel(Model model) private string? ValidatingModel(Model model)
{ {
if(this.DataProvider is Providers.SELF_HOSTED && this.DataHost == Host.LLAMACPP) if(this.DataProvider is Providers.SELF_HOSTED && this.DataHost == Host.LLAMACPP)
@@ -312,7 +321,7 @@ public partial class ProviderDialog : ComponentBase
var provider = currentProviderSettings.CreateProvider(); var provider = currentProviderSettings.CreateProvider();
if(provider is NoProvider) if(provider is NoProvider)
return; return;
var models = await provider.GetTextModels(this.JsRuntime, this.SettingsManager, this.dataAPIKey); var models = await provider.GetTextModels(this.JsRuntime, this.SettingsManager, this.dataAPIKey);
// Order descending by ID means that the newest models probably come first: // Order descending by ID means that the newest models probably come first:
@@ -354,11 +363,52 @@ public partial class ProviderDialog : ComponentBase
return true; return true;
} }
private bool IsCloudProvider => this.DataProvider is not Providers.SELF_HOSTED; private bool ShowRegisterButton => this.DataProvider switch
{
Providers.OPEN_AI => true,
Providers.MISTRAL => true,
Providers.ANTHROPIC => true,
Providers.FIREWORKS => true,
_ => false,
};
private bool NeedAPIKey => this.DataProvider switch
{
Providers.OPEN_AI => true,
Providers.MISTRAL => true,
Providers.ANTHROPIC => true,
Providers.FIREWORKS => true,
_ => false,
};
private bool NeedHostname => this.DataProvider switch
{
Providers.SELF_HOSTED => true,
_ => false,
};
private bool IsSelfHostedOrNone => this.DataProvider is Providers.SELF_HOSTED or Providers.NONE; private bool NeedHost => this.DataProvider switch
{
Providers.SELF_HOSTED => true,
_ => false,
};
private bool IsNoneProvider => this.DataProvider is Providers.NONE; private bool ProvideModelManually => this.DataProvider switch
{
Providers.FIREWORKS => true,
_ => false,
};
private string GetModelOverviewURL() => this.DataProvider switch
{
Providers.FIREWORKS => "https://fireworks.ai/models?show=Serverless",
_ => string.Empty,
};
private string GetProviderCreationURL() => this.DataProvider switch private string GetProviderCreationURL() => this.DataProvider switch
{ {
@@ -366,6 +416,10 @@ public partial class ProviderDialog : ComponentBase
Providers.MISTRAL => "https://console.mistral.ai/", Providers.MISTRAL => "https://console.mistral.ai/",
Providers.ANTHROPIC => "https://console.anthropic.com/dashboard", Providers.ANTHROPIC => "https://console.anthropic.com/dashboard",
Providers.FIREWORKS => "https://fireworks.ai/login",
_ => string.Empty, _ => string.Empty,
}; };
private bool IsNoneProvider => this.DataProvider is Providers.NONE;
} }
+1
View File
@@ -6,6 +6,7 @@ public enum Event
// Common events: // Common events:
STATE_HAS_CHANGED, STATE_HAS_CHANGED,
CONFIGURATION_CHANGED,
// Update events: // Update events:
USER_SEARCH_FOR_UPDATE, USER_SEARCH_FOR_UPDATE,
+35 -35
View File
@@ -4,9 +4,9 @@
"net8.0": { "net8.0": {
"Microsoft.Extensions.FileProviders.Embedded": { "Microsoft.Extensions.FileProviders.Embedded": {
"type": "Direct", "type": "Direct",
"requested": "[8.0.6, )", "requested": "[8.0.7, )",
"resolved": "8.0.6", "resolved": "8.0.7",
"contentHash": "hQlf5+YxiUbKdpaPBf/zdMGItnWF8ai9ToPjeZ6gnxT10V4RGDKvChl5MSdc838+2SRHLAC0cdJwG0+L7dqR0g==", "contentHash": "ABsn0T09b5lzVNbOcuRc10+kNZkO+RGtZWfzqVay0Ah+/ouhEvG7JrXc+9+7zFgoPuH4E4N6+uWfTp+pJqMeGw==",
"dependencies": { "dependencies": {
"Microsoft.Extensions.FileProviders.Abstractions": "8.0.0" "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0"
} }
@@ -19,13 +19,13 @@
}, },
"MudBlazor": { "MudBlazor": {
"type": "Direct", "type": "Direct",
"requested": "[6.20.0, )", "requested": "[7.4.0, )",
"resolved": "6.20.0", "resolved": "7.4.0",
"contentHash": "2MqW/E1OLszSqDhW06rpRTN4OpIRJU0iVzeiZLPzymckrKJH1Hg09St+O3kaEnsbXRcjcdM9iE5cSKzCgxvySQ==", "contentHash": "dm8ZD6OhyI6icE3Si0V3BJvwPZp8fEspPFq4/iQXU8/i7L39/ny2bPgQqPnQmIojppd9cR/H4RdsaBlSLe6rIw==",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Components": "8.0.3", "Microsoft.AspNetCore.Components": "8.0.6",
"Microsoft.AspNetCore.Components.Web": "8.0.3", "Microsoft.AspNetCore.Components.Web": "8.0.6",
"Microsoft.Extensions.Localization": "8.0.3" "Microsoft.Extensions.Localization": "8.0.6"
} }
}, },
"MudBlazor.Markdown": { "MudBlazor.Markdown": {
@@ -45,53 +45,53 @@
}, },
"Microsoft.AspNetCore.Authorization": { "Microsoft.AspNetCore.Authorization": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "9Nic17acgZbysUlhGc+TEd9F8jI01kC6+V31sC7/xI5v2OSWGL8NhdYaB/Iu4KnDRoQEolg6qvepGsVfeYpIYA==", "contentHash": "H1CSbD7UeSPsrJSUpvbms6SqWMa5y8ch4Rw+gyHh2uztOEb20fTP2r0AUnStn1Q9WYghikiDO5wzkgV+n6KC2Q==",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Metadata": "8.0.3", "Microsoft.AspNetCore.Metadata": "8.0.6",
"Microsoft.Extensions.Logging.Abstractions": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.1",
"Microsoft.Extensions.Options": "8.0.2" "Microsoft.Extensions.Options": "8.0.2"
} }
}, },
"Microsoft.AspNetCore.Components": { "Microsoft.AspNetCore.Components": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "q1Da8sfxG+B+BSYpc/3RKNEdzGcLbDTXkTUqekY65kXMMVCTqTAQ0Zs4csmB7FNVTFSjwaw1dGMFD0bQ+erlBw==", "contentHash": "Je2l+rd5i8gB+ZWsN5wBHOsnyAh81h6+a5vNq6rjfQhM2ZYxH+BVsmanCYa+F8BFy6rmd+ZL61SEiNaAyAOgtg==",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Authorization": "8.0.3", "Microsoft.AspNetCore.Authorization": "8.0.6",
"Microsoft.AspNetCore.Components.Analyzers": "8.0.3" "Microsoft.AspNetCore.Components.Analyzers": "8.0.6"
} }
}, },
"Microsoft.AspNetCore.Components.Analyzers": { "Microsoft.AspNetCore.Components.Analyzers": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "iERLuYM+YFI/K1jkinr1YeAkJYHUcijPiPCKgmgs2ZhJLqiIVJRT08vUtIsfhiFtGiI5MIzK0R1BZHyS3yAQng==" "contentHash": "SEL0CN1jJdJkCDwRox3kSY1ffMnahlOCDJZYrYWfQ6ftjqRuuiPtwyvI3VIeVHLcpT3VP1AXww8wzKDK3oeFxg=="
}, },
"Microsoft.AspNetCore.Components.Forms": { "Microsoft.AspNetCore.Components.Forms": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "OxY5NDmePnn6FMb+Fum57YL7LCHk3u2Wg0qSln3uZSayo+oIxYuoGnqH2dUMp1P5vOPfq17NKCIIEbxfU2dirQ==", "contentHash": "49wIE1ns3ZGDU4NPMcKCSTC716IxujUeaynsUyflM5Qmb2WlF6M6m4pMcJ6UET1Ixmn027nZjMEc8J7pU4BLBg==",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Components": "8.0.3" "Microsoft.AspNetCore.Components": "8.0.6"
} }
}, },
"Microsoft.AspNetCore.Components.Web": { "Microsoft.AspNetCore.Components.Web": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "bHWJiz/JhjptK3iYzha0Rm73chjFcbMAOD9DdDq2tn1rp4rQa/K7O/zdnZpSYAT3nI33Q0aY6ts6t0PUVu5hCA==", "contentHash": "xxYlTpcTEWsxaWUscDLN32mM32ysslLFtNQBS6wJOxaKJ9LCvusXDyBVEKL2DPbB8PZdereNvyrVr0deHkuOEw==",
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Components": "8.0.3", "Microsoft.AspNetCore.Components": "8.0.6",
"Microsoft.AspNetCore.Components.Forms": "8.0.3", "Microsoft.AspNetCore.Components.Forms": "8.0.6",
"Microsoft.Extensions.DependencyInjection": "8.0.0", "Microsoft.Extensions.DependencyInjection": "8.0.0",
"Microsoft.Extensions.Primitives": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0",
"Microsoft.JSInterop": "8.0.3", "Microsoft.JSInterop": "8.0.6",
"System.IO.Pipelines": "8.0.0" "System.IO.Pipelines": "8.0.0"
} }
}, },
"Microsoft.AspNetCore.Metadata": { "Microsoft.AspNetCore.Metadata": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "MAdmICjtSckGDutLRMydRI0pvBcGw/WoC4UC+hZ999idVW96l5iJlhyrtakgYkF5Rp0ekNVNWA+onP2gJZUkpw==" "contentHash": "3e7S/kz1MGds8zDA8SfQiutqifyVcULs5P/8Bxpes0WIkJRMGkU/l+XlMllj9KrDfCGwHW8bgjRy/zxfoILgfg=="
}, },
"Microsoft.Extensions.DependencyInjection": { "Microsoft.Extensions.DependencyInjection": {
"type": "Transitive", "type": "Transitive",
@@ -116,19 +116,19 @@
}, },
"Microsoft.Extensions.Localization": { "Microsoft.Extensions.Localization": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "EjHnIiEwNq9xl8S36hf0nj64IF3DTQ1l5PVK8Vo+QocYKDKpDPONHNJC8env0DPwod7T5oA1HYLLo2mdSTCgrQ==", "contentHash": "EXcSpQE6E80Qutej0ZShz1oRWBFocDzir/R3Gd1jAqZ5SnPpSr1Ep/tH5ha0cRXY22cWVuOb7I9rM32Ew+ju7g==",
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1",
"Microsoft.Extensions.Localization.Abstractions": "8.0.3", "Microsoft.Extensions.Localization.Abstractions": "8.0.6",
"Microsoft.Extensions.Logging.Abstractions": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.1",
"Microsoft.Extensions.Options": "8.0.2" "Microsoft.Extensions.Options": "8.0.2"
} }
}, },
"Microsoft.Extensions.Localization.Abstractions": { "Microsoft.Extensions.Localization.Abstractions": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "k/kUPm1FQBxcs9/vsM1eF4qIOg2Sovqh/+KUGHur5Mc0Y3OFGuoz9ktBX7LA0gPz53SZhW3W3oaSaMFFcjgM6Q==" "contentHash": "oQdKQ4xlb+Qa4t1gWGj58cocLdWZynP+wLExIAxozh2SrvffEzTvTGI2IbRx4mBNgg9jbbyoPE8nNOwJ1Thddg=="
}, },
"Microsoft.Extensions.Logging.Abstractions": { "Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive", "type": "Transitive",
@@ -154,8 +154,8 @@
}, },
"Microsoft.JSInterop": { "Microsoft.JSInterop": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.3", "resolved": "8.0.6",
"contentHash": "Oi21Fa7KubCzafwXb2IOdSGg24+/ylYGwrJgAYdWmgXBj04Oj/1b8vr9hrcoFKjQ6K18ryHYh35ZO/CCIEhuzg==" "contentHash": "E1djWS3d41fcd++7sGlbYhOHde5Pb0oBpOcNvUbn+1ga/yCvsjzUfbd/tDRg1qacNKS0iwKWYOIqTZxJnh99dQ=="
}, },
"System.IO.Pipelines": { "System.IO.Pipelines": {
"type": "Transitive", "type": "Transitive",
@@ -0,0 +1,6 @@
# v0.8.3, build 165 (2024-07-25 13:25 UTC)
- Added an option to configure the behavior of the navigation bar in the settings
- Added support for Fireworks.ai as provider, where you can use e.g., the llama 3.1 405b model
- Improved the handling of self-hosted provider hostnames
- Improved the configured provider table: long model names are now truncated
- Migrated UI framework from MudBlazor v6.x.x to v7.x.x
+5 -5
View File
@@ -1,9 +1,9 @@
0.8.2 0.8.3
2024-07-16 18:03:04 UTC 2024-07-25 13:25:12 UTC
164 165
8.0.107 (commit 1bdaef7265) 8.0.107 (commit 1bdaef7265)
8.0.7 (commit 2aade6beb0) 8.0.7 (commit 2aade6beb0)
1.79.0 (commit 129f3b996) 1.79.0 (commit 129f3b996)
6.20.0 7.4.0
1.6.1 1.6.1
c92ce49af2d, release 72eb50d226f, release
+1 -1
View File
@@ -2313,7 +2313,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "mindwork-ai-studio" name = "mindwork-ai-studio"
version = "0.8.2" version = "0.8.3"
dependencies = [ dependencies = [
"arboard", "arboard",
"flexi_logger", "flexi_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mindwork-ai-studio" name = "mindwork-ai-studio"
version = "0.8.2" version = "0.8.3"
edition = "2021" edition = "2021"
description = "MindWork AI Studio" description = "MindWork AI Studio"
authors = ["Thorsten Sommer"] authors = ["Thorsten Sommer"]
+1 -1
View File
@@ -6,7 +6,7 @@
}, },
"package": { "package": {
"productName": "MindWork AI Studio", "productName": "MindWork AI Studio",
"version": "0.8.2" "version": "0.8.3"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {