mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 21:59:48 +00:00
Migrated the get secret calls from Tauri JS to the runtime API
This commit is contained in:
parent
273376ad97
commit
97854e7eaa
@ -1,13 +1,14 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
||||
public abstract class AgentBase(ILogger<AgentBase> logger, RustService rustService, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
||||
{
|
||||
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
||||
|
||||
@ -17,6 +18,8 @@ public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager setti
|
||||
|
||||
protected ILogger<AgentBase> Logger { get; init; } = logger;
|
||||
|
||||
protected RustService RustService { get; init; } = rustService;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type or category of this agent.
|
||||
/// </summary>
|
||||
@ -106,6 +109,6 @@ public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager setti
|
||||
// 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.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger, this.RustService), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, jsRuntime, rng)
|
||||
public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, RustService rustService, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(logger, rustService, settingsManager, jsRuntime, rng)
|
||||
{
|
||||
private static readonly ContentBlock EMPTY_BLOCK = new()
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.Agenda;
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Assistants;
|
||||
|
||||
public abstract partial class AssistantBase : ComponentBase
|
||||
@ -22,7 +23,7 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected Rust Rust { get; init; } = null!;
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; init; } = null!;
|
||||
@ -154,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.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger, this.RustService), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
this.isProcessing = false;
|
||||
this.StateHasChanged();
|
||||
@ -165,7 +166,7 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
|
||||
protected async Task CopyToClipboard()
|
||||
{
|
||||
await this.Rust.CopyText2Clipboard(this.Snackbar, this.Result2Copy());
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, this.Result2Copy());
|
||||
}
|
||||
|
||||
private static string? GetButtonIcon(string icon)
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public partial class AssistantCoding : AssistantBaseCore
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.EMail;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.GrammarSpelling;
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.IconFinder;
|
||||
|
||||
public partial class AssistantIconFinder : AssistantBaseCore
|
||||
|
@ -1,5 +1,3 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.LegalCheck;
|
||||
|
||||
public partial class AssistantLegalCheck : AssistantBaseCore
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.TextSummarizer;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Assistants.Translation;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
/// <summary>
|
||||
@ -40,7 +40,7 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
public string Class { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private Rust Rust { get; init; } = null!;
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
||||
@ -100,7 +100,7 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
{
|
||||
case ContentType.TEXT:
|
||||
var textContent = (ContentText) this.Content;
|
||||
await this.Rust.CopyText2Clipboard(this.Snackbar, textContent.Text);
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, textContent.Text);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Layout;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
@ -1,5 +1,3 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using AIStudio.Agents;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System.Text.Json.Serialization;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Reflection;
|
||||
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Global using directives
|
||||
|
||||
global using AIStudio.Tools;
|
||||
|
||||
global using Microsoft.JSInterop;
|
||||
|
||||
global using MudBlazor;
|
@ -1,13 +1,14 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Layout;
|
||||
|
||||
@ -26,7 +27,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private Rust Rust { get; init; } = null!;
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
@ -189,7 +190,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
||||
|
||||
this.performingUpdate = true;
|
||||
this.StateHasChanged();
|
||||
await this.Rust.InstallUpdate(this.JsRuntime);
|
||||
await this.RustService.InstallUpdate();
|
||||
}
|
||||
|
||||
private async ValueTask OnLocationChanging(LocationChangingContext context)
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System.Reflection;
|
||||
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Pages;
|
||||
|
@ -4,12 +4,12 @@ using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Pages;
|
||||
|
||||
@ -33,6 +33,9 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
[Inject]
|
||||
private ILogger<Chat> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
private InnerScrolling scrollingArea = null!;
|
||||
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
||||
@ -192,7 +195,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.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger, this.RustService), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
// Save the chat:
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
|
@ -1,11 +1,11 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
|
||||
@ -28,6 +28,9 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
[Inject]
|
||||
private ILogger<Settings> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
private readonly List<ConfigurationSelectData<string>> availableProviders = new();
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
@ -114,7 +117,7 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var providerInstance = provider.CreateProvider(this.Logger);
|
||||
var providerInstance = provider.CreateProvider(this.Logger, this.RustService);
|
||||
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
||||
if(deleteSecretResponse.Success)
|
||||
{
|
||||
|
@ -1,7 +1,5 @@
|
||||
using AIStudio;
|
||||
using AIStudio.Agents;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
@ -13,6 +11,15 @@ using System.Reflection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
#endif
|
||||
|
||||
namespace AIStudio;
|
||||
|
||||
internal sealed class Program
|
||||
{
|
||||
public static RustService RUST_SERVICE = null!;
|
||||
public static Encryption ENCRYPTION = null!;
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
if(args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Please provide the port of the runtime API.");
|
||||
@ -20,7 +27,7 @@ if(args.Length == 0)
|
||||
}
|
||||
|
||||
var rustApiPort = args[0];
|
||||
using var rust = new Rust(rustApiPort);
|
||||
using var rust = new RustService(rustApiPort);
|
||||
var appPort = await rust.GetAppPort();
|
||||
if(appPort == 0)
|
||||
{
|
||||
@ -111,10 +118,13 @@ var encryption = new Encryption(encryptionLogger, secretPassword, secretKeySalt)
|
||||
var encryptionInitializer = encryption.Initialize();
|
||||
|
||||
// Set the logger for the Rust service:
|
||||
var rustLogger = app.Services.GetRequiredService<ILogger<Rust>>();
|
||||
var rustLogger = app.Services.GetRequiredService<ILogger<RustService>>();
|
||||
rust.SetLogger(rustLogger);
|
||||
rust.SetEncryptor(encryption);
|
||||
|
||||
RUST_SERVICE = rust;
|
||||
ENCRYPTION = encryption;
|
||||
|
||||
app.Use(Redirect.HandlerContentAsync);
|
||||
|
||||
#if DEBUG
|
||||
@ -140,3 +150,5 @@ var serverTask = app.RunAsync();
|
||||
await encryptionInitializer;
|
||||
await rust.AppIsReady();
|
||||
await serverTask;
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
|
||||
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);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@ -64,7 +64,7 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "messages");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Add("x-api-key", requestedSecret.Secret);
|
||||
request.Headers.Add("x-api-key", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
|
||||
// Set the Anthropic version:
|
||||
request.Headers.Add("anthropic-version", "2023-06-01");
|
||||
|
@ -1,3 +1,5 @@
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
@ -15,6 +17,16 @@ public abstract class BaseProvider
|
||||
/// </summary>
|
||||
protected readonly ILogger logger;
|
||||
|
||||
static BaseProvider()
|
||||
{
|
||||
RUST_SERVICE = Program.RUST_SERVICE;
|
||||
ENCRYPTION = Program.ENCRYPTION;
|
||||
}
|
||||
|
||||
protected static readonly RustService RUST_SERVICE;
|
||||
|
||||
protected static readonly Encryption ENCRYPTION;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the base provider.
|
||||
/// </summary>
|
||||
|
@ -27,7 +27,7 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
|
||||
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);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@ -73,7 +73,7 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", requestedSecret.Secret);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
|
||||
// Set the content:
|
||||
request.Content = new StringContent(fireworksChatRequest, Encoding.UTF8, "application/json");
|
||||
|
@ -26,7 +26,7 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
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);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@ -75,7 +75,7 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", requestedSecret.Secret);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
|
||||
// Set the content:
|
||||
request.Content = new StringContent(mistralChatRequest, Encoding.UTF8, "application/json");
|
||||
@ -153,9 +153,9 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await settings.GetAPIKey(jsRuntime, this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
{
|
||||
{ Success: true } result => result.Secret,
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
@ -30,7 +30,7 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
|
||||
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);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@ -79,7 +79,7 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", requestedSecret.Secret);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
|
||||
// Set the content:
|
||||
request.Content = new StringContent(openAIChatRequest, Encoding.UTF8, "application/json");
|
||||
@ -154,25 +154,25 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(jsRuntime, settings, "gpt-", token, apiKeyProvisional);
|
||||
return this.LoadModels("gpt-", token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(jsRuntime, settings, "dall-e-", token, apiKeyProvisional);
|
||||
return this.LoadModels("dall-e-", token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<IEnumerable<Model>> LoadModels(IJSRuntime jsRuntime, SettingsManager settings, string prefix, CancellationToken token, string? apiKeyProvisional = null)
|
||||
private async Task<IEnumerable<Model>> LoadModels(string prefix, CancellationToken token, string? apiKeyProvisional = null)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await settings.GetAPIKey(jsRuntime, this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
{
|
||||
{ Success: true } result => result.Secret,
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
@ -4,6 +4,8 @@ using AIStudio.Provider.Mistral;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Provider.SelfHosted;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
public static class ProvidersExtensions
|
||||
@ -33,8 +35,9 @@ public static class ProvidersExtensions
|
||||
/// </summary>
|
||||
/// <param name="providerSettings">The provider settings.</param>
|
||||
/// <param name="logger">The logger to use.</param>
|
||||
/// <param name="rustService">The Rust instance to use.</param>
|
||||
/// <returns>The provider instance.</returns>
|
||||
public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger)
|
||||
public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger, RustService rustService)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using AIStudio.Assistants.RewriteImprove;
|
||||
using AIStudio.Assistants.TextSummarizer;
|
||||
using AIStudio.Assistants.EMail;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using WritingStylesRewrite = AIStudio.Assistants.RewriteImprove.WritingStyles;
|
||||
using WritingStylesEMail = AIStudio.Assistants.EMail.WritingStyles;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Assistants.Agenda;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Assistants.EMail;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public sealed class DataGrammarSpelling
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Assistants.RewriteImprove;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using AIStudio.Assistants.TextSummarizer;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public sealed class DataTranslation
|
||||
|
@ -1,7 +1,6 @@
|
||||
using AIStudio.Assistants.Coding;
|
||||
using AIStudio.Assistants.IconFinder;
|
||||
using AIStudio.Assistants.TextSummarizer;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Settings.DataModel.PreviousModels;
|
||||
|
||||
|
@ -5,6 +5,7 @@ using AIStudio.Provider;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
@ -79,6 +80,12 @@ public partial class ProviderDialog : ComponentBase
|
||||
[Inject]
|
||||
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private Encryption Encryption { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
@ -136,7 +143,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
}
|
||||
|
||||
var loadedProviderSettings = this.CreateProviderSettings();
|
||||
var provider = loadedProviderSettings.CreateProvider(this.Logger);
|
||||
var provider = loadedProviderSettings.CreateProvider(this.Logger, this.RustService);
|
||||
if(provider is NoProvider)
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
@ -144,10 +151,10 @@ public partial class ProviderDialog : ComponentBase
|
||||
}
|
||||
|
||||
// Load the API key:
|
||||
var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider);
|
||||
var requestedSecret = await this.RustService.GetAPIKey(provider);
|
||||
if(requestedSecret.Success)
|
||||
{
|
||||
this.dataAPIKey = requestedSecret.Secret;
|
||||
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.Encryption);
|
||||
|
||||
// Now, we try to load the list of available models:
|
||||
await this.ReloadModels();
|
||||
@ -190,7 +197,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);
|
||||
var provider = addedProviderSettings.CreateProvider(this.Logger, this.RustService);
|
||||
|
||||
// Store the API key in the OS secure storage:
|
||||
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
||||
@ -321,7 +328,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
var currentProviderSettings = this.CreateProviderSettings();
|
||||
var provider = currentProviderSettings.CreateProvider(this.Logger);
|
||||
var provider = currentProviderSettings.CreateProvider(this.Logger, this.RustService);
|
||||
if(provider is NoProvider)
|
||||
return;
|
||||
|
||||
|
@ -42,24 +42,6 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger)
|
||||
|
||||
#region API Key Handling
|
||||
|
||||
private readonly record struct GetSecretRequest(string Destination, string UserName);
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for any requested secret.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully retrieved.</param>
|
||||
/// <param name="Secret">The secret, e.g., API key.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be retrieved.</param>
|
||||
public readonly record struct RequestedSecret(bool Success, string Secret, string Issue);
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
||||
/// <param name="provider">The provider to get the API key for.</param>
|
||||
/// <returns>The requested secret.</returns>
|
||||
public async Task<RequestedSecret> GetAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync<RequestedSecret>("window.__TAURI__.invoke", "get_secret", new GetSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName));
|
||||
|
||||
private readonly record struct StoreSecretRequest(string Destination, string UserName, string Secret);
|
||||
|
||||
/// <summary>
|
||||
|
@ -93,7 +93,7 @@ public sealed class Encryption(ILogger<Encryption> logger, byte[] secretPassword
|
||||
public async Task<string> Decrypt(EncryptedText encryptedData)
|
||||
{
|
||||
// Build a memory stream to access the given base64 encoded data:
|
||||
await using var encodedEncryptedStream = new MemoryStream(Encoding.ASCII.GetBytes(encryptedData));
|
||||
await using var encodedEncryptedStream = new MemoryStream(Encoding.ASCII.GetBytes(encryptedData.EncryptedData));
|
||||
|
||||
// Wrap around the base64 decoder stream:
|
||||
await using var base64Stream = new CryptoStream(encodedEncryptedStream, new FromBase64Transform(), CryptoStreamMode.Read);
|
||||
@ -102,11 +102,12 @@ public sealed class Encryption(ILogger<Encryption> logger, byte[] secretPassword
|
||||
var readSaltBytes = new byte[16]; // 16 bytes = Guid
|
||||
|
||||
// Read the salt's bytes out of the stream:
|
||||
var readBytes = await base64Stream.ReadAsync(readSaltBytes);
|
||||
if(readBytes != 16)
|
||||
var readBytes = 0;
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
|
||||
while(readBytes < readSaltBytes.Length && !cts.Token.IsCancellationRequested)
|
||||
{
|
||||
logger.LogError($"Read {readBytes} bytes instead of 16 bytes for the salt.");
|
||||
throw new CryptographicException("Failed to read the salt bytes.");
|
||||
readBytes += await base64Stream.ReadAsync(readSaltBytes, readBytes, readSaltBytes.Length - readBytes, cts.Token);
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(60), cts.Token);
|
||||
}
|
||||
|
||||
// Check the salt bytes:
|
||||
|
7
app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs
Normal file
7
app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
public readonly record struct GetSecretRequest(
|
||||
string Destination,
|
||||
[property:JsonPropertyName("user_name")] string UserName);
|
9
app/MindWork AI Studio/Tools/Rust/RequestedSecret.cs
Normal file
9
app/MindWork AI Studio/Tools/Rust/RequestedSecret.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for any requested secret.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully retrieved.</param>
|
||||
/// <param name="Secret">The secret, e.g., API key.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be retrieved.</param>
|
||||
public readonly record struct RequestedSecret(bool Success, EncryptedText Secret, string Issue);
|
@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Tools;
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// The response from the set clipboard operation.
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// The response of the update check.
|
@ -1,19 +1,24 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
// ReSharper disable NotAccessedPositionalProperty.Local
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Calling Rust functions.
|
||||
/// </summary>
|
||||
public sealed class Rust(string apiPort) : IDisposable
|
||||
public sealed class RustService(string apiPort) : IDisposable
|
||||
{
|
||||
private readonly HttpClient http = new()
|
||||
{
|
||||
BaseAddress = new Uri($"http://127.0.0.1:{apiPort}"),
|
||||
};
|
||||
|
||||
private ILogger<Rust>? logger;
|
||||
private ILogger<RustService>? logger;
|
||||
private Encryption? encryptor;
|
||||
|
||||
public void SetLogger(ILogger<Rust> logService)
|
||||
public void SetLogger(ILogger<RustService> logService)
|
||||
{
|
||||
this.logger = logService;
|
||||
}
|
||||
@ -87,7 +92,8 @@ public sealed class Rust(string apiPort) : IDisposable
|
||||
var severity = Severity.Error;
|
||||
try
|
||||
{
|
||||
var response = await this.http.PostAsync("/clipboard/set", new StringContent(await text.Encrypt(this.encryptor!)));
|
||||
var encryptedText = await text.Encrypt(this.encryptor!);
|
||||
var response = await this.http.PostAsync("/clipboard/set", new StringContent(encryptedText.EncryptedData));
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to copy the text to the clipboard due to an network error: '{response.StatusCode}'");
|
||||
@ -136,7 +142,7 @@ public sealed class Rust(string apiPort) : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InstallUpdate(IJSRuntime jsRuntime)
|
||||
public async Task InstallUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -150,6 +156,28 @@ public sealed class Rust(string apiPort) : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider to get the API key for.</param>
|
||||
/// <returns>The requested secret.</returns>
|
||||
public async Task<RequestedSecret> GetAPIKey(IProvider provider)
|
||||
{
|
||||
var secretRequest = new GetSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
|
||||
return new RequestedSecret(false, new EncryptedText(string.Empty), "Failed to get the API key due to an API issue.");
|
||||
}
|
||||
|
||||
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>();
|
||||
if (!secret.Success)
|
||||
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}': '{secret.Issue}'");
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
@ -6,11 +6,11 @@ namespace AIStudio.Tools.Services;
|
||||
/// Wire up the clipboard service to copy Markdown to the clipboard.
|
||||
/// We use our own Rust-based clipboard service for this.
|
||||
/// </summary>
|
||||
public sealed class MarkdownClipboardService(Rust rust, ISnackbar snackbar) : IMudMarkdownClipboardService
|
||||
public sealed class MarkdownClipboardService(RustService rust, ISnackbar snackbar) : IMudMarkdownClipboardService
|
||||
{
|
||||
private ISnackbar Snackbar { get; } = snackbar;
|
||||
|
||||
private Rust Rust { get; } = rust;
|
||||
private RustService Rust { get; } = rust;
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the user wants to copy the Markdown to the clipboard.
|
||||
|
@ -12,11 +12,11 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
|
||||
private readonly SettingsManager settingsManager;
|
||||
private readonly MessageBus messageBus;
|
||||
private readonly Rust rust;
|
||||
private readonly RustService rust;
|
||||
|
||||
private TimeSpan updateInterval;
|
||||
|
||||
public UpdateService(MessageBus messageBus, SettingsManager settingsManager, Rust rust)
|
||||
public UpdateService(MessageBus messageBus, SettingsManager settingsManager, RustService rust)
|
||||
{
|
||||
this.settingsManager = settingsManager;
|
||||
this.messageBus = messageBus;
|
||||
|
@ -1,7 +0,0 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Model for setting clipboard text.
|
||||
/// </summary>
|
||||
/// <param name="Text">The text to set to the clipboard.</param>
|
||||
public record SetClipboardText(string Text);
|
@ -194,7 +194,7 @@ 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])
|
||||
.mount("/", routes![dotnet_port, dotnet_ready, set_clipboard, check_for_update, install_update, get_secret])
|
||||
.ignite().await.unwrap()
|
||||
.launch().await.unwrap();
|
||||
});
|
||||
@ -319,7 +319,7 @@ async fn main() {
|
||||
})
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
store_secret, get_secret, delete_secret
|
||||
store_secret, delete_secret
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("Error while running Tauri application");
|
||||
@ -776,36 +776,57 @@ struct StoreSecretResponse {
|
||||
issue: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_secret(destination: String, user_name: String) -> RequestedSecret {
|
||||
let service = format!("mindwork-ai-studio::{}", destination);
|
||||
let entry = Entry::new(service.as_str(), user_name.as_str()).unwrap();
|
||||
#[post("/secrets/get", data = "<request>")]
|
||||
fn get_secret(request: Json<RequestSecret>) -> Json<RequestedSecret> {
|
||||
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 secret = entry.get_password();
|
||||
match secret {
|
||||
Ok(s) => {
|
||||
info!(Source = "Secret Store"; "Secret for {service} and user {user_name} was retrieved successfully.");
|
||||
RequestedSecret {
|
||||
info!(Source = "Secret Store"; "Secret for '{service}' and user '{user_name}' was retrieved successfully.");
|
||||
|
||||
// Encrypt the secret:
|
||||
let encrypted_secret = match ENCRYPTION.encrypt(s.as_str()) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
error!(Source = "Secret Store"; "Failed to encrypt the secret: {e}.");
|
||||
return Json(RequestedSecret {
|
||||
success: false,
|
||||
secret: EncryptedText::new(String::from("")),
|
||||
issue: format!("Failed to encrypt the secret: {e}"),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Json(RequestedSecret {
|
||||
success: true,
|
||||
secret: s,
|
||||
secret: encrypted_secret,
|
||||
issue: String::from(""),
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
error!(Source = "Secret Store"; "Failed to retrieve secret for {service} and user {user_name}: {e}.");
|
||||
RequestedSecret {
|
||||
error!(Source = "Secret Store"; "Failed to retrieve secret for '{service}' and user '{user_name}': {e}.");
|
||||
Json(RequestedSecret {
|
||||
success: false,
|
||||
secret: String::from(""),
|
||||
issue: e.to_string(),
|
||||
}
|
||||
secret: EncryptedText::new(String::from("")),
|
||||
issue: format!("Failed to retrieve secret for '{service}' and user '{user_name}': {e}"),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RequestSecret {
|
||||
destination: String,
|
||||
user_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RequestedSecret {
|
||||
success: bool,
|
||||
secret: String,
|
||||
secret: EncryptedText,
|
||||
issue: String,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user