diff --git a/app/MindWork AI Studio/Agents/AgentBase.cs b/app/MindWork AI Studio/Agents/AgentBase.cs index 0507d8c7..8028a864 100644 --- a/app/MindWork AI Studio/Agents/AgentBase.cs +++ b/app/MindWork AI Studio/Agents/AgentBase.cs @@ -2,24 +2,18 @@ using AIStudio.Chat; using AIStudio.Provider; using AIStudio.Settings; -using RustService = AIStudio.Tools.RustService; - // ReSharper disable MemberCanBePrivate.Global namespace AIStudio.Agents; -public abstract class AgentBase(ILogger logger, RustService rustService, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent +public abstract class AgentBase(ILogger logger, SettingsManager settingsManager, ThreadSafeRandom rng) : IAgent { protected SettingsManager SettingsManager { get; init; } = settingsManager; - protected IJSRuntime JsRuntime { get; init; } = jsRuntime; - protected ThreadSafeRandom RNG { get; init; } = rng; protected ILogger Logger { get; init; } = logger; - protected RustService RustService { get; init; } = rustService; - /// /// Represents the type or category of this agent. /// @@ -109,6 +103,6 @@ public abstract class AgentBase(ILogger logger, RustService rustServi // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger, this.RustService), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread); + await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), this.SettingsManager, providerSettings.Model, thread); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Agents/AgentTextContentCleaner.cs b/app/MindWork AI Studio/Agents/AgentTextContentCleaner.cs index 6a27b731..f98b80eb 100644 --- a/app/MindWork AI Studio/Agents/AgentTextContentCleaner.cs +++ b/app/MindWork AI Studio/Agents/AgentTextContentCleaner.cs @@ -1,11 +1,9 @@ using AIStudio.Chat; using AIStudio.Settings; -using RustService = AIStudio.Tools.RustService; - namespace AIStudio.Agents; -public sealed class AgentTextContentCleaner(ILogger logger, RustService rustService, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(logger, rustService, settingsManager, jsRuntime, rng) +public sealed class AgentTextContentCleaner(ILogger logger, SettingsManager settingsManager, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, rng) { private static readonly ContentBlock EMPTY_BLOCK = new() { diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index bee2aa07..a12376b3 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -155,7 +155,7 @@ public abstract partial class AssistantBase : ComponentBase // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger, this.RustService), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread); + await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.SettingsManager, this.providerSettings.Model, this.chatThread); this.isProcessing = false; this.StateHasChanged(); diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index 1b6144f2..756d3a61 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -42,9 +42,6 @@ public partial class ContentBlockComponent : ComponentBase [Inject] private RustService RustService { get; init; } = null!; - [Inject] - private IJSRuntime JsRuntime { get; init; } = null!; - [Inject] private ISnackbar Snackbar { get; init; } = null!; diff --git a/app/MindWork AI Studio/Chat/ContentImage.cs b/app/MindWork AI Studio/Chat/ContentImage.cs index 9afe4476..314ba93c 100644 --- a/app/MindWork AI Studio/Chat/ContentImage.cs +++ b/app/MindWork AI Studio/Chat/ContentImage.cs @@ -29,7 +29,7 @@ public sealed class ContentImage : IContent public Func StreamingEvent { get; set; } = () => Task.CompletedTask; /// - public Task CreateFromProviderAsync(IProvider provider, IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default) + public Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default) { throw new NotImplementedException(); } diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs index 0354c756..bbf65065 100644 --- a/app/MindWork AI Studio/Chat/ContentText.cs +++ b/app/MindWork AI Studio/Chat/ContentText.cs @@ -35,18 +35,18 @@ public sealed class ContentText : IContent public Func StreamingEvent { get; set; } = () => Task.CompletedTask; /// - public async Task CreateFromProviderAsync(IProvider provider, IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread? chatThread, CancellationToken token = default) + public async Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread? chatThread, CancellationToken token = default) { if(chatThread is null) return; - // Store the last time we got a response. We use this later, + // Store the last time we got a response. We use this ater // to determine whether we should notify the UI about the // new content or not. Depends on the energy saving mode // the user chose. var last = DateTimeOffset.Now; - // Start another thread by using a task, to uncouple + // Start another thread by using a task to uncouple // the UI thread from the AI processing: await Task.Run(async () => { @@ -54,7 +54,7 @@ public sealed class ContentText : IContent this.InitialRemoteWait = true; // Iterate over the responses from the AI: - await foreach (var deltaText in provider.StreamChatCompletion(jsRuntime, settings, chatModel, chatThread, token)) + await foreach (var deltaText in provider.StreamChatCompletion(chatModel, chatThread, token)) { // When the user cancels the request, we stop the loop: if (token.IsCancellationRequested) @@ -89,7 +89,7 @@ public sealed class ContentText : IContent } // Stop the waiting animation (in case the loop - // was stopped or no content was received): + // was stopped, or no content was received): this.InitialRemoteWait = false; this.IsStreaming = false; }, token); diff --git a/app/MindWork AI Studio/Chat/IContent.cs b/app/MindWork AI Studio/Chat/IContent.cs index 8f6bc0ad..1feea520 100644 --- a/app/MindWork AI Studio/Chat/IContent.cs +++ b/app/MindWork AI Studio/Chat/IContent.cs @@ -42,5 +42,5 @@ public interface IContent /// /// Uses the provider to create the content. /// - public Task CreateFromProviderAsync(IProvider provider, IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default); + public Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 3a64b92e..c6fd8dc8 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -1,7 +1,5 @@ -@using AIStudio.Components @using AIStudio.Provider @using AIStudio.Provider.SelfHosted -@using MudBlazor diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs index 0e1a00eb..02d29d22 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs @@ -75,9 +75,6 @@ public partial class ProviderDialog : ComponentBase [Inject] private SettingsManager SettingsManager { get; init; } = null!; - [Inject] - private IJSRuntime JsRuntime { get; init; } = null!; - [Inject] private ILogger Logger { get; init; } = null!; @@ -142,7 +139,7 @@ public partial class ProviderDialog : ComponentBase } var loadedProviderSettings = this.CreateProviderSettings(); - var provider = loadedProviderSettings.CreateProvider(this.Logger, this.RustService); + var provider = loadedProviderSettings.CreateProvider(this.Logger); if(provider is NoProvider) { await base.OnInitializedAsync(); @@ -196,7 +193,7 @@ public partial class ProviderDialog : ComponentBase if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED) { // We need to instantiate the provider to store the API key: - var provider = addedProviderSettings.CreateProvider(this.Logger, this.RustService); + var provider = addedProviderSettings.CreateProvider(this.Logger); // Store the API key in the OS secure storage: var storeResponse = await this.RustService.SetAPIKey(provider, this.dataAPIKey); @@ -327,11 +324,11 @@ public partial class ProviderDialog : ComponentBase private async Task ReloadModels() { var currentProviderSettings = this.CreateProviderSettings(); - var provider = currentProviderSettings.CreateProvider(this.Logger, this.RustService); + var provider = currentProviderSettings.CreateProvider(this.Logger); if(provider is NoProvider) return; - var models = await provider.GetTextModels(this.JsRuntime, this.SettingsManager, this.dataAPIKey); + var models = await provider.GetTextModels(this.dataAPIKey); // Order descending by ID means that the newest models probably come first: var orderedModels = models.OrderByDescending(n => n.Id); diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index 6ac863cc..e277c79c 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using DialogOptions = AIStudio.Dialogs.DialogOptions; -using RustService = AIStudio.Tools.RustService; namespace AIStudio.Pages; @@ -20,9 +19,6 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable { [Inject] private SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - public IJSRuntime JsRuntime { get; init; } = null!; [Inject] private ThreadSafeRandom RNG { get; init; } = null!; @@ -32,10 +28,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable [Inject] private ILogger Logger { get; init; } = null!; - - [Inject] - private RustService RustService { get; init; } = null!; - + private InnerScrolling scrollingArea = null!; private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom; @@ -195,7 +188,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger, this.RustService), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread); + await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.SettingsManager, this.providerSettings.Model, this.chatThread); // Save the chat: if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) diff --git a/app/MindWork AI Studio/Pages/Settings.razor.cs b/app/MindWork AI Studio/Pages/Settings.razor.cs index 6678ed17..daa216b4 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor.cs +++ b/app/MindWork AI Studio/Pages/Settings.razor.cs @@ -19,9 +19,6 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable [Inject] private IDialogService DialogService { get; init; } = null!; - [Inject] - private IJSRuntime JsRuntime { get; init; } = null!; - [Inject] private MessageBus MessageBus { get; init; } = null!; @@ -117,8 +114,8 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable if (dialogResult is null || dialogResult.Canceled) return; - var providerInstance = provider.CreateProvider(this.Logger, this.RustService); - var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance); + var providerInstance = provider.CreateProvider(this.Logger); + var deleteSecretResponse = await this.RustService.DeleteAPIKey(providerInstance); if(deleteSecretResponse.Success) { this.SettingsManager.ConfigurationData.Providers.Remove(provider); diff --git a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs index 0f561f19..88dc493c 100644 --- a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs +++ b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs @@ -4,7 +4,6 @@ using System.Text.Json; using AIStudio.Chat; using AIStudio.Provider.OpenAI; -using AIStudio.Settings; namespace AIStudio.Provider.Anthropic; @@ -22,7 +21,7 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap public string InstanceName { get; set; } = "Anthropic"; /// - public async IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) { // Get the API key: var requestedSecret = await RUST_SERVICE.GetAPIKey(this); @@ -137,14 +136,14 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public async IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamImageCompletion(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 /// - public Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { return Task.FromResult(new[] { @@ -157,7 +156,7 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) { return Task.FromResult(Enumerable.Empty()); } diff --git a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs index 65ae2528..a48582be 100644 --- a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs +++ b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs @@ -4,7 +4,6 @@ using System.Text; using System.Text.Json; using AIStudio.Chat; -using AIStudio.Settings; namespace AIStudio.Provider.Fireworks; @@ -24,7 +23,7 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew public string InstanceName { get; set; } = "Fireworks.ai"; /// - public async IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) { // Get the API key: var requestedSecret = await RUST_SERVICE.GetAPIKey(this); @@ -139,20 +138,20 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public async IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamImageCompletion(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 /// - public Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { return Task.FromResult(Enumerable.Empty()); } /// - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) { return Task.FromResult(Enumerable.Empty()); } diff --git a/app/MindWork AI Studio/Provider/IProvider.cs b/app/MindWork AI Studio/Provider/IProvider.cs index 776d981b..ef3214bb 100644 --- a/app/MindWork AI Studio/Provider/IProvider.cs +++ b/app/MindWork AI Studio/Provider/IProvider.cs @@ -1,5 +1,4 @@ using AIStudio.Chat; -using AIStudio.Settings; namespace AIStudio.Provider; @@ -22,44 +21,36 @@ public interface IProvider /// /// Starts a chat completion stream. /// - /// The JS runtime to access the Rust code. - /// The settings manager to access the API key. /// The model to use for chat completion. /// The chat thread to continue. /// The cancellation token. /// The chat completion stream. - public IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, CancellationToken token = default); + public IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, CancellationToken token = default); /// /// Starts an image completion stream. /// - /// The JS runtime to access the Rust code. - /// The settings manager to access the API key. /// The model to use for image completion. /// The positive prompt. /// The negative prompt. /// The reference image URL. /// The cancellation token. /// The image completion stream. - public IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default); + public IAsyncEnumerable StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default); /// /// Load all possible text models that can be used with this provider. /// - /// The JS runtime to access the Rust code. - /// The settings manager to access the API key. /// The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used. /// The cancellation token. /// The list of text models. - public Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default); + public Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default); /// /// Load all possible image models that can be used with this provider. /// - /// The JS runtime to access the Rust code. - /// The settings manager to access the API key. /// The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used. /// The cancellation token. /// The list of image models. - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default); + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs index 95c96059..86d94e85 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs @@ -5,7 +5,6 @@ using System.Text.Json; using AIStudio.Chat; using AIStudio.Provider.OpenAI; -using AIStudio.Settings; namespace AIStudio.Provider.Mistral; @@ -23,7 +22,7 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. public string InstanceName { get; set; } = "Mistral"; /// - public async IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) { // Get the API key: var requestedSecret = await RUST_SERVICE.GetAPIKey(this); @@ -141,14 +140,14 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public async IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamImageCompletion(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 /// - public async Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { var secretKey = apiKeyProvisional switch { @@ -179,7 +178,7 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) { return Task.FromResult(Enumerable.Empty()); } diff --git a/app/MindWork AI Studio/Provider/NoProvider.cs b/app/MindWork AI Studio/Provider/NoProvider.cs index b5b9b2fe..f6a6079b 100644 --- a/app/MindWork AI Studio/Provider/NoProvider.cs +++ b/app/MindWork AI Studio/Provider/NoProvider.cs @@ -1,7 +1,6 @@ using System.Runtime.CompilerServices; using AIStudio.Chat; -using AIStudio.Settings; namespace AIStudio.Provider; @@ -13,17 +12,17 @@ public class NoProvider : IProvider public string InstanceName { get; set; } = "None"; - public Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult>([]); + public Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult>([]); - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult>([]); + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult>([]); - public async IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default) { await Task.FromResult(0); yield break; } - public async IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) { await Task.FromResult(0); yield break; diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index fe1f19ba..70b80f2e 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -4,7 +4,6 @@ using System.Text; using System.Text.Json; using AIStudio.Chat; -using AIStudio.Settings; namespace AIStudio.Provider.OpenAI; @@ -27,7 +26,7 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o public string InstanceName { get; set; } = "OpenAI"; /// - public async IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) { // Get the API key: var requestedSecret = await RUST_SERVICE.GetAPIKey(this); @@ -145,20 +144,20 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public async IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamImageCompletion(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 /// - public Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { return this.LoadModels("gpt-", token, apiKeyProvisional); } /// - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) { return this.LoadModels("dall-e-", token, apiKeyProvisional); } diff --git a/app/MindWork AI Studio/Provider/ProvidersExtensions.cs b/app/MindWork AI Studio/Provider/ProvidersExtensions.cs index f5aea99b..806c3bb8 100644 --- a/app/MindWork AI Studio/Provider/ProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/ProvidersExtensions.cs @@ -4,8 +4,6 @@ using AIStudio.Provider.Mistral; using AIStudio.Provider.OpenAI; using AIStudio.Provider.SelfHosted; -using RustService = AIStudio.Tools.RustService; - namespace AIStudio.Provider; public static class ProvidersExtensions @@ -35,9 +33,8 @@ public static class ProvidersExtensions /// /// The provider settings. /// The logger to use. - /// The Rust instance to use. /// The provider instance. - public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger, RustService rustService) + public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger) { try { diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index e80611cc..2f979543 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -4,7 +4,6 @@ using System.Text.Json; using AIStudio.Chat; using AIStudio.Provider.OpenAI; -using AIStudio.Settings; namespace AIStudio.Provider.SelfHosted; @@ -21,7 +20,8 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide public string InstanceName { get; set; } = "Self-hosted"; - public async IAsyncEnumerable StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) + /// + public async IAsyncEnumerable StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default) { // Prepare the system prompt: var systemPrompt = new Message @@ -129,14 +129,14 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public async IAsyncEnumerable StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + public async IAsyncEnumerable StreamImageCompletion(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 - public async Task> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { try { @@ -169,7 +169,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// - public Task> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) + public Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) { return Task.FromResult(Enumerable.Empty()); } diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 871119fe..5f647460 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; -using AIStudio.Provider; using AIStudio.Settings.DataModel; // ReSharper disable NotAccessedPositionalProperty.Local @@ -39,28 +38,6 @@ public sealed class SettingsManager(ILogger logger) public Data ConfigurationData { get; private set; } = new(); private bool IsSetUp => !string.IsNullOrWhiteSpace(ConfigDirectory) && !string.IsNullOrWhiteSpace(DataDirectory); - - #region API Key Handling - - private readonly record struct DeleteSecretRequest(string Destination, string UserName); - - /// - /// Data structure for deleting a secret response. - /// - /// True, when the secret was successfully deleted or not found. - /// The issue, when the secret could not be deleted. - /// True, when the entry was found and deleted. - public readonly record struct DeleteSecretResponse(bool Success, string Issue, bool WasEntryFound); - - /// - /// Tries to delete the API key for the given provider. - /// - /// The JS runtime to access the Rust code. - /// The provider to delete the API key for. - /// The delete secret response. - public async Task DeleteAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync("window.__TAURI__.invoke", "delete_secret", new DeleteSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName)); - - #endregion /// /// Loads the settings from the file system. diff --git a/app/MindWork AI Studio/Tools/Rust/DeleteSecretResponse.cs b/app/MindWork AI Studio/Tools/Rust/DeleteSecretResponse.cs new file mode 100644 index 00000000..634dc012 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/DeleteSecretResponse.cs @@ -0,0 +1,9 @@ +namespace AIStudio.Tools.Rust; + +/// +/// Data structure for deleting a secret response. +/// +/// True, when the secret was successfully deleted or not found. +/// The issue, when the secret could not be deleted. +/// True, when the entry was found and deleted. +public readonly record struct DeleteSecretResponse(bool Success, string Issue, bool WasEntryFound); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs b/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs deleted file mode 100644 index ddccc322..00000000 --- a/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace AIStudio.Tools.Rust; - -public readonly record struct GetSecretRequest( - string Destination, - string UserName -); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/SelectSecretRequest.cs b/app/MindWork AI Studio/Tools/Rust/SelectSecretRequest.cs new file mode 100644 index 00000000..d1596709 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/SelectSecretRequest.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools.Rust; + +public readonly record struct SelectSecretRequest(string Destination, string UserName); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/RustService.cs b/app/MindWork AI Studio/Tools/RustService.cs index 570db290..28c6a2b8 100644 --- a/app/MindWork AI Studio/Tools/RustService.cs +++ b/app/MindWork AI Studio/Tools/RustService.cs @@ -170,7 +170,7 @@ public sealed class RustService(string apiPort) : IDisposable /// The requested secret. public async Task GetAPIKey(IProvider provider) { - var secretRequest = new GetSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName); + var secretRequest = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName); var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions); if (!result.IsSuccessStatusCode) { @@ -208,7 +208,28 @@ public sealed class RustService(string apiPort) : IDisposable return state; } - + + /// + /// Tries to delete the API key for the given provider. + /// + /// The provider to delete the API key for. + /// The delete secret response. + public async Task DeleteAPIKey(IProvider provider) + { + var request = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName); + var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions); + if (!result.IsSuccessStatusCode) + { + this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'"); + return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = "Failed to delete the API key due to an API issue."}; + } + + var state = await result.Content.ReadFromJsonAsync(); + if (!state.Success) + this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}': '{state.Issue}'"); + + return state; + } #region IDisposable diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 1472ba9b..85b33e24 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -194,7 +194,10 @@ async fn main() { // tauri::async_runtime::spawn(async move { _ = rocket::custom(figment) - .mount("/", routes![dotnet_port, dotnet_ready, set_clipboard, check_for_update, install_update, get_secret, store_secret]) + .mount("/", routes![ + dotnet_port, dotnet_ready, set_clipboard, check_for_update, install_update, + get_secret, store_secret, delete_secret + ]) .ignite().await.unwrap() .launch().await.unwrap(); }); @@ -318,9 +321,6 @@ async fn main() { Ok(()) }) .plugin(tauri_plugin_window_state::Builder::default().build()) - .invoke_handler(tauri::generate_handler![ - delete_secret - ]) .build(tauri::generate_context!()) .expect("Error while running Tauri application"); @@ -849,38 +849,39 @@ struct RequestedSecret { issue: String, } -#[tauri::command] -fn delete_secret(destination: String, user_name: String) -> DeleteSecretResponse { - let service = format!("mindwork-ai-studio::{}", destination); - let entry = Entry::new(service.as_str(), user_name.as_str()).unwrap(); +#[post("/secrets/delete", data = "")] +fn delete_secret(request: Json) -> Json { + let user_name = request.user_name.as_str(); + let service = format!("mindwork-ai-studio::{}", request.destination); + let entry = Entry::new(service.as_str(), user_name).unwrap(); let result = entry.delete_credential(); match result { Ok(_) => { warn!(Source = "Secret Store"; "Secret for {service} and user {user_name} was deleted successfully."); - DeleteSecretResponse { + Json(DeleteSecretResponse { success: true, was_entry_found: true, issue: String::from(""), - } + }) }, Err(NoEntry) => { warn!(Source = "Secret Store"; "No secret for {service} and user {user_name} was found."); - DeleteSecretResponse { + Json(DeleteSecretResponse { success: true, was_entry_found: false, issue: String::from(""), - } + }) } Err(e) => { error!(Source = "Secret Store"; "Failed to delete secret for {service} and user {user_name}: {e}."); - DeleteSecretResponse { + Json(DeleteSecretResponse { success: false, was_entry_found: false, issue: e.to_string(), - } + }) }, } }