mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-20 21:17:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cc1d37db8 | ||
|
|
b29e181ecd | ||
|
|
9b1649b48a | ||
|
|
15e8fbc6ca |
No files matched your search
@@ -13,6 +13,8 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
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"),
|
||||
new (156, "v0.6.0, build 156 (2024-06-30 12:49 UTC)", "v0.6.0.md"),
|
||||
new (155, "v0.5.2, build 155 (2024-06-25 18:07 UTC)", "v0.5.2.md"),
|
||||
new (154, "v0.5.1, build 154 (2024-06-25 15:35 UTC)", "v0.5.1.md"),
|
||||
|
||||
@@ -20,10 +20,13 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; init; } = null!;
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public Rust Rust { get; init; } = null!;
|
||||
private Rust Rust { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
public string AdditionalHeight { get; private set; } = "0em";
|
||||
|
||||
@@ -56,7 +59,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
|
||||
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE ]);
|
||||
|
||||
// Set the js runtime for the update service:
|
||||
UpdateService.SetJsRuntime(this.JsRuntime);
|
||||
UpdateService.SetBlazorDependencies(this.JsRuntime, this.Snackbar);
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider.Anthropic;
|
||||
|
||||
/// <summary>
|
||||
/// The Anthropic chat request model.
|
||||
/// </summary>
|
||||
/// <param name="Model">Which model to use for chat completion.</param>
|
||||
/// <param name="Messages">The chat messages.</param>
|
||||
/// <param name="MaxTokens">The maximum number of tokens to generate.</param>
|
||||
/// <param name="Stream">Whether to stream the chat completion.</param>
|
||||
/// <param name="System">The system prompt for the chat completion.</param>
|
||||
public readonly record struct ChatRequest(
|
||||
string Model,
|
||||
IList<Message> Messages,
|
||||
int MaxTokens,
|
||||
bool Stream,
|
||||
string System
|
||||
);
|
||||
@@ -0,0 +1,166 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Provider.Anthropic;
|
||||
|
||||
public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.com/v1/"), IProvider
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
};
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
public string Id => "Anthropic";
|
||||
|
||||
public string InstanceName { get; set; } = "Anthropic";
|
||||
|
||||
/// <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 Anthropic HTTP chat request:
|
||||
var chatRequest = JsonSerializer.Serialize(new ChatRequest
|
||||
{
|
||||
Model = chatModel.Id,
|
||||
|
||||
// Build the messages:
|
||||
Messages = [..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",
|
||||
|
||||
_ => "user",
|
||||
},
|
||||
|
||||
Content = n.Content switch
|
||||
{
|
||||
ContentText text => text.Text,
|
||||
_ => string.Empty,
|
||||
}
|
||||
}).ToList()],
|
||||
|
||||
System = chatThread.SystemPrompt,
|
||||
MaxTokens = 4_096,
|
||||
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
}, JSON_SERIALIZER_OPTIONS);
|
||||
|
||||
// Build the HTTP post request:
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "messages");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Add("x-api-key", requestedSecret.Secret);
|
||||
|
||||
// Set the Anthropic version:
|
||||
request.Headers.Add("anthropic-version", "2023-06-01");
|
||||
|
||||
// Set the content:
|
||||
request.Content = new StringContent(chatRequest, 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 stream = await response.Content.ReadAsStreamAsync(token);
|
||||
|
||||
// Add a stream reader to read the stream, line by line:
|
||||
var streamReader = new StreamReader(stream);
|
||||
|
||||
// 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;
|
||||
|
||||
// Check for the end of the stream:
|
||||
if(line.StartsWith("event: message_stop", StringComparison.InvariantCulture))
|
||||
yield break;
|
||||
|
||||
// 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;
|
||||
|
||||
// Ignore any type except "content_block_delta":
|
||||
if(!line.Contains("\"content_block_delta\"", StringComparison.InvariantCulture))
|
||||
continue;
|
||||
|
||||
ResponseStreamLine anthropicResponse;
|
||||
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:
|
||||
anthropicResponse = JsonSerializer.Deserialize<ResponseStreamLine>(jsonData, JSON_SERIALIZER_OPTIONS);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip invalid JSON data:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty responses:
|
||||
if(anthropicResponse == default || string.IsNullOrWhiteSpace(anthropicResponse.Delta.Text))
|
||||
continue;
|
||||
|
||||
// Yield the response:
|
||||
yield return anthropicResponse.Delta.Text;
|
||||
}
|
||||
}
|
||||
|
||||
#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(new[]
|
||||
{
|
||||
new Model("claude-3-5-sonnet-20240620"),
|
||||
new Model("claude-3-opus-20240229"),
|
||||
new Model("claude-3-sonnet-20240229"),
|
||||
new Model("claude-3-haiku-20240307"),
|
||||
}.AsEnumerable());
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// ReSharper disable NotAccessedPositionalProperty.Global
|
||||
namespace AIStudio.Provider.Anthropic;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a response stream line.
|
||||
/// </summary>
|
||||
/// <param name="Type">The type of the response line.</param>
|
||||
/// <param name="Index">The index of the response line.</param>
|
||||
/// <param name="Delta">The delta of the response line.</param>
|
||||
public readonly record struct ResponseStreamLine(string Type, int Index, Delta Delta);
|
||||
|
||||
/// <summary>
|
||||
/// The delta object of a response line.
|
||||
/// </summary>
|
||||
/// <param name="Type">The type of the delta.</param>
|
||||
/// <param name="Text">The text of the delta.</param>
|
||||
public readonly record struct Delta(string Type, string Text);
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AIStudio.Provider.Mistral;
|
||||
|
||||
/// <summary>
|
||||
/// The OpenAI 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>
|
||||
/// <param name="RandomSeed">The seed for the chat completion.</param>
|
||||
/// <param name="SafePrompt">Whether to inject a safety prompt before all conversations.</param>
|
||||
public readonly record struct ChatRequest(
|
||||
string Model,
|
||||
IList<RegularMessage> Messages,
|
||||
bool Stream,
|
||||
int RandomSeed,
|
||||
bool SafePrompt = false
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace AIStudio.Provider.Mistral;
|
||||
|
||||
public readonly record struct ModelsResponse(string Object, Model[] Data);
|
||||
|
||||
public readonly record struct Model(string Id, string Object, int Created, string OwnedBy);
|
||||
@@ -0,0 +1,188 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Provider.Mistral;
|
||||
|
||||
public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/"), IProvider
|
||||
{
|
||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
};
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
public string Id => "Mistral";
|
||||
|
||||
public string InstanceName { get; set; } = "Mistral";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.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 RegularMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.SystemPrompt,
|
||||
};
|
||||
|
||||
// Prepare the Mistral HTTP chat request:
|
||||
var mistralChatRequest = 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 RegularMessage
|
||||
{
|
||||
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()],
|
||||
|
||||
RandomSeed = chatThread.Seed,
|
||||
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
SafePrompt = false,
|
||||
}, 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(mistralChatRequest, 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 mistralStream = await response.Content.ReadAsStreamAsync(token);
|
||||
|
||||
// Add a stream reader to read the stream, line by line:
|
||||
var streamReader = new StreamReader(mistralStream);
|
||||
|
||||
// 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 mistralResponse;
|
||||
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:
|
||||
mistralResponse = JsonSerializer.Deserialize<ResponseStreamLine>(jsonData, JSON_SERIALIZER_OPTIONS);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip invalid JSON data:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty responses:
|
||||
if(mistralResponse == default || mistralResponse.Choices.Count == 0)
|
||||
continue;
|
||||
|
||||
// Yield the response:
|
||||
yield return mistralResponse.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, Provider.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 async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await settings.GetAPIKey(jsRuntime, this) switch
|
||||
{
|
||||
{ Success: true } result => result.Secret,
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
||||
if (secretKey is null)
|
||||
return [];
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "models");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
|
||||
|
||||
var response = await this.httpClient.SendAsync(request, token);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
return [];
|
||||
|
||||
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
|
||||
return modelResponse.Data.Where(n =>
|
||||
!n.Id.StartsWith("code", StringComparison.InvariantCulture) &&
|
||||
!n.Id.Contains("embed", StringComparison.InvariantCulture))
|
||||
.Select(n => new Provider.Model(n.Id));
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Provider.Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Provider.Mistral;
|
||||
|
||||
/// <summary>
|
||||
/// Regulat 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 RegularMessage(string Content, string Role);
|
||||
File renamed without changes.
@@ -1,3 +1,5 @@
|
||||
using AIStudio.Provider.Anthropic;
|
||||
using AIStudio.Provider.Mistral;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
@@ -9,6 +11,8 @@ public enum Providers
|
||||
{
|
||||
NONE,
|
||||
OPEN_AI,
|
||||
ANTHROPIC,
|
||||
MISTRAL,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -24,6 +28,8 @@ public static class ExtensionsProvider
|
||||
public static string ToName(this Providers provider) => provider switch
|
||||
{
|
||||
Providers.OPEN_AI => "OpenAI",
|
||||
Providers.ANTHROPIC => "Anthropic",
|
||||
Providers.MISTRAL => "Mistral",
|
||||
|
||||
Providers.NONE => "No provider selected",
|
||||
_ => "Unknown",
|
||||
@@ -38,6 +44,8 @@ public static class ExtensionsProvider
|
||||
public static IProvider CreateProvider(this Providers provider, string instanceName) => provider switch
|
||||
{
|
||||
Providers.OPEN_AI => new ProviderOpenAI { InstanceName = instanceName },
|
||||
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = instanceName },
|
||||
Providers.MISTRAL => new ProviderMistral { InstanceName = instanceName },
|
||||
|
||||
_ => new NoProvider(),
|
||||
};
|
||||
|
||||
@@ -6,11 +6,12 @@ namespace AIStudio.Tools;
|
||||
|
||||
public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
{
|
||||
// We cannot inject IJSRuntime into our service. This is due to the fact that
|
||||
// the service is not a Blaozor component. We need to pass the IJSRuntime from
|
||||
// We cannot inject IJSRuntime into our service. This is because
|
||||
// the service is not a Blazor component. We need to pass the IJSRuntime from
|
||||
// the MainLayout component to the service.
|
||||
private static IJSRuntime? JS_RUNTIME;
|
||||
private static bool IS_INITALIZED;
|
||||
private static bool IS_INITIALIZED;
|
||||
private static ISnackbar? SNACKBAR;
|
||||
|
||||
private readonly SettingsManager settingsManager;
|
||||
private readonly TimeSpan updateInterval;
|
||||
@@ -22,7 +23,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
this.settingsManager = settingsManager;
|
||||
this.messageBus = messageBus;
|
||||
this.rust = rust;
|
||||
|
||||
|
||||
this.messageBus.RegisterComponent(this);
|
||||
this.ApplyFilters([], [ Event.USER_SEARCH_FOR_UPDATE ]);
|
||||
|
||||
@@ -43,7 +44,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested && !IS_INITALIZED)
|
||||
while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
|
||||
}
|
||||
@@ -68,7 +69,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.USER_SEARCH_FOR_UPDATE:
|
||||
await this.CheckForUpdate();
|
||||
await this.CheckForUpdate(notifyUserWhenNoUpdate: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -85,9 +86,9 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task CheckForUpdate()
|
||||
private async Task CheckForUpdate(bool notifyUserWhenNoUpdate = false)
|
||||
{
|
||||
if(!IS_INITALIZED)
|
||||
if(!IS_INITIALIZED)
|
||||
return;
|
||||
|
||||
var response = await this.rust.CheckForUpdate(JS_RUNTIME!);
|
||||
@@ -95,11 +96,24 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
{
|
||||
await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (notifyUserWhenNoUpdate)
|
||||
{
|
||||
SNACKBAR!.Add("No update found.", Severity.Normal, config =>
|
||||
{
|
||||
config.Icon = Icons.Material.Filled.Update;
|
||||
config.IconSize = Size.Large;
|
||||
config.IconColor = Color.Primary;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetJsRuntime(IJSRuntime jsRuntime)
|
||||
public static void SetBlazorDependencies(IJSRuntime jsRuntime, ISnackbar snackbar)
|
||||
{
|
||||
SNACKBAR = snackbar;
|
||||
JS_RUNTIME = jsRuntime;
|
||||
IS_INITALIZED = true;
|
||||
IS_INITIALIZED = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# v0.6.1, build 157 (2024-06-30 19:00 UTC)
|
||||
- Added support for the Anthropic provider
|
||||
@@ -0,0 +1,2 @@
|
||||
# v0.6.2, build 158 (2024-07-01 18:03 UTC)
|
||||
- Added support for the Mistral provider
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
0.6.0
|
||||
2024-06-30 12:49:38 UTC
|
||||
156
|
||||
0.6.2
|
||||
2024-07-01 18:08:01 UTC
|
||||
158
|
||||
8.0.206 (commit bb12410699)
|
||||
8.0.6 (commit 3b8b000a0e)
|
||||
1.79.0 (commit 129f3b996)
|
||||
6.20.0
|
||||
1.6.1
|
||||
dab121a7217, release
|
||||
c86a9e32c12, release
|
||||
Generated
+1
-1
@@ -2313,7 +2313,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.6.0"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"flexi_logger",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.6.0"
|
||||
version = "0.6.2"
|
||||
edition = "2021"
|
||||
description = "MindWork AI Studio"
|
||||
authors = ["Thorsten Sommer"]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "MindWork AI Studio",
|
||||
"version": "0.6.0"
|
||||
"version": "0.6.2"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
Reference in new issue
Block a user