mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-02-05 12:29:07 +00:00
Improve ipc (#102)
This commit is contained in:
parent
47f5756e5a
commit
fc64c0b505
10
.github/workflows/build-and-release.yml
vendored
10
.github/workflows/build-and-release.yml
vendored
@ -489,7 +489,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
id: linux_arm_cache
|
id: linux_arm_cache
|
||||||
with:
|
with:
|
||||||
path: $RUNNER_TEMP/linux_arm_qemu_cache.img
|
path: ${{ runner.temp }}/linux_arm_qemu_cache.img
|
||||||
|
|
||||||
# When the entire key matches, Rust might just create the bundles using the current .NET build:
|
# When the entire key matches, Rust might just create the bundles using the current .NET build:
|
||||||
key: target-linux-arm64-rust-${{ env.RUST_VERSION }}-dependencies-${{ env.CARGO_LOCK_HASH }}
|
key: target-linux-arm64-rust-${{ env.RUST_VERSION }}-dependencies-${{ env.CARGO_LOCK_HASH }}
|
||||||
@ -544,7 +544,7 @@ jobs:
|
|||||||
- name: Add the built runner image to the cache
|
- name: Add the built runner image to the cache
|
||||||
if: ${{ steps.linux_arm_cache.outputs.cache-hit != 'true' && env.SKIP != 'true' }}
|
if: ${{ steps.linux_arm_cache.outputs.cache-hit != 'true' && env.SKIP != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
mv ${{ steps.build-linux-arm-runner.outputs.image }} $RUNNER_TEMP/linux_arm_qemu_cache.img
|
mv ${{ steps.build-linux-arm-runner.outputs.image }} ${{ runner.temp }}/linux_arm_qemu_cache.img
|
||||||
|
|
||||||
- name: Build Tauri project
|
- name: Build Tauri project
|
||||||
if: ${{ env.SKIP != 'true' }}
|
if: ${{ env.SKIP != 'true' }}
|
||||||
@ -552,7 +552,7 @@ jobs:
|
|||||||
id: build-linux-arm
|
id: build-linux-arm
|
||||||
|
|
||||||
with:
|
with:
|
||||||
base_image: file://$RUNNER_TEMP/linux_arm_qemu_cache.img
|
base_image: file://${{ runner.temp }}/linux_arm_qemu_cache.img
|
||||||
cpu: cortex-a53
|
cpu: cortex-a53
|
||||||
optimize_image: false
|
optimize_image: false
|
||||||
copy_artifact_path: runtime
|
copy_artifact_path: runtime
|
||||||
@ -845,8 +845,8 @@ jobs:
|
|||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: false
|
prerelease: true
|
||||||
draft: false
|
draft: true
|
||||||
make_latest: true
|
make_latest: true
|
||||||
body: ${{ env.CHANGELOG }}
|
body: ${{ env.CHANGELOG }}
|
||||||
name: "Release ${{ env.FORMATTED_VERSION }}"
|
name: "Release ${{ env.FORMATTED_VERSION }}"
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
// ReSharper disable MemberCanBePrivate.Global
|
// ReSharper disable MemberCanBePrivate.Global
|
||||||
|
|
||||||
namespace AIStudio.Agents;
|
namespace AIStudio.Agents;
|
||||||
|
|
||||||
public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager settingsManager, ThreadSafeRandom rng) : IAgent
|
||||||
{
|
{
|
||||||
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
||||||
|
|
||||||
protected IJSRuntime JsRuntime { get; init; } = jsRuntime;
|
|
||||||
|
|
||||||
protected ThreadSafeRandom RNG { get; init; } = rng;
|
protected ThreadSafeRandom RNG { get; init; } = rng;
|
||||||
|
|
||||||
|
protected ILogger<AgentBase> Logger { get; init; } = logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the type or category of this agent.
|
/// Represents the type or category of this agent.
|
||||||
@ -104,6 +103,6 @@ public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRu
|
|||||||
// Use the selected provider to get the AI response.
|
// Use the selected provider to get the AI response.
|
||||||
// By awaiting this line, we wait for the entire
|
// By awaiting this line, we wait for the entire
|
||||||
// content to be streamed.
|
// content to be streamed.
|
||||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), this.SettingsManager, providerSettings.Model, thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,9 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Agents;
|
namespace AIStudio.Agents;
|
||||||
|
|
||||||
public sealed class AgentTextContentCleaner(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(settingsManager, jsRuntime, rng)
|
public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, SettingsManager settingsManager, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, rng)
|
||||||
{
|
{
|
||||||
private static readonly ContentBlock EMPTY_BLOCK = new()
|
private static readonly ContentBlock EMPTY_BLOCK = new()
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.Agenda;
|
namespace AIStudio.Assistants.Agenda;
|
||||||
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
using RustService = AIStudio.Tools.RustService;
|
||||||
|
|
||||||
namespace AIStudio.Assistants;
|
namespace AIStudio.Assistants;
|
||||||
|
|
||||||
public abstract partial class AssistantBase : ComponentBase
|
public abstract partial class AssistantBase : ComponentBase
|
||||||
{
|
{
|
||||||
[Inject]
|
[Inject]
|
||||||
protected SettingsManager SettingsManager { get; set; } = null!;
|
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected IJSRuntime JsRuntime { get; init; } = null!;
|
protected IJSRuntime JsRuntime { get; init; } = null!;
|
||||||
@ -22,11 +23,14 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
protected ISnackbar Snackbar { get; init; } = null!;
|
protected ISnackbar Snackbar { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected Rust Rust { get; init; } = null!;
|
protected RustService RustService { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected NavigationManager NavigationManager { get; init; } = null!;
|
protected NavigationManager NavigationManager { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
protected ILogger<AssistantBase> Logger { get; init; } = null!;
|
||||||
|
|
||||||
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
|
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
|
||||||
internal const string RESULT_DIV_ID = "assistantResult";
|
internal const string RESULT_DIV_ID = "assistantResult";
|
||||||
|
|
||||||
@ -151,7 +155,7 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
// Use the selected provider to get the AI response.
|
// Use the selected provider to get the AI response.
|
||||||
// By awaiting this line, we wait for the entire
|
// By awaiting this line, we wait for the entire
|
||||||
// content to be streamed.
|
// content to be streamed.
|
||||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), 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.isProcessing = false;
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
@ -162,7 +166,7 @@ public abstract partial class AssistantBase : ComponentBase
|
|||||||
|
|
||||||
protected async Task CopyToClipboard()
|
protected async Task CopyToClipboard()
|
||||||
{
|
{
|
||||||
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, this.Result2Copy());
|
await this.RustService.CopyText2Clipboard(this.Snackbar, this.Result2Copy());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? GetButtonIcon(string icon)
|
private static string? GetButtonIcon(string icon)
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.Coding;
|
namespace AIStudio.Assistants.Coding;
|
||||||
|
|
||||||
public partial class AssistantCoding : AssistantBaseCore
|
public partial class AssistantCoding : AssistantBaseCore
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.EMail;
|
namespace AIStudio.Assistants.EMail;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.GrammarSpelling;
|
namespace AIStudio.Assistants.GrammarSpelling;
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.IconFinder;
|
namespace AIStudio.Assistants.IconFinder;
|
||||||
|
|
||||||
public partial class AssistantIconFinder : AssistantBaseCore
|
public partial class AssistantIconFinder : AssistantBaseCore
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.LegalCheck;
|
namespace AIStudio.Assistants.LegalCheck;
|
||||||
|
|
||||||
public partial class AssistantLegalCheck : AssistantBaseCore
|
public partial class AssistantLegalCheck : AssistantBaseCore
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.RewriteImprove;
|
namespace AIStudio.Assistants.RewriteImprove;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.TextSummarizer;
|
namespace AIStudio.Assistants.TextSummarizer;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.Translation;
|
namespace AIStudio.Assistants.Translation;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
using RustService = AIStudio.Tools.RustService;
|
||||||
|
|
||||||
namespace AIStudio.Chat;
|
namespace AIStudio.Chat;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -40,10 +40,7 @@ public partial class ContentBlockComponent : ComponentBase
|
|||||||
public string Class { get; set; } = string.Empty;
|
public string Class { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private Rust Rust { get; init; } = null!;
|
private RustService RustService { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
|
||||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ISnackbar Snackbar { get; init; } = null!;
|
private ISnackbar Snackbar { get; init; } = null!;
|
||||||
@ -100,7 +97,7 @@ public partial class ContentBlockComponent : ComponentBase
|
|||||||
{
|
{
|
||||||
case ContentType.TEXT:
|
case ContentType.TEXT:
|
||||||
var textContent = (ContentText) this.Content;
|
var textContent = (ContentText) this.Content;
|
||||||
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, textContent.Text);
|
await this.RustService.CopyText2Clipboard(this.Snackbar, textContent.Text);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -29,7 +29,7 @@ public sealed class ContentImage : IContent
|
|||||||
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
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();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -35,18 +35,18 @@ public sealed class ContentText : IContent
|
|||||||
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
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)
|
if(chatThread is null)
|
||||||
return;
|
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
|
// to determine whether we should notify the UI about the
|
||||||
// new content or not. Depends on the energy saving mode
|
// new content or not. Depends on the energy saving mode
|
||||||
// the user chose.
|
// the user chose.
|
||||||
var last = DateTimeOffset.Now;
|
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:
|
// the UI thread from the AI processing:
|
||||||
await Task.Run(async () =>
|
await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@ -54,7 +54,7 @@ public sealed class ContentText : IContent
|
|||||||
this.InitialRemoteWait = true;
|
this.InitialRemoteWait = true;
|
||||||
|
|
||||||
// Iterate over the responses from the AI:
|
// 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:
|
// When the user cancels the request, we stop the loop:
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
@ -89,7 +89,7 @@ public sealed class ContentText : IContent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop the waiting animation (in case the loop
|
// 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.InitialRemoteWait = false;
|
||||||
this.IsStreaming = false;
|
this.IsStreaming = false;
|
||||||
}, token);
|
}, token);
|
||||||
|
@ -42,5 +42,5 @@ public interface IContent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses the provider to create the content.
|
/// Uses the provider to create the content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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);
|
||||||
}
|
}
|
@ -13,6 +13,7 @@ public partial class Changelog
|
|||||||
|
|
||||||
public static readonly Log[] LOGS =
|
public static readonly Log[] LOGS =
|
||||||
[
|
[
|
||||||
|
new (175, "v0.9.0, build 175 (2024-09-01 18:04 UTC)", "v0.9.0.md"),
|
||||||
new (174, "v0.8.12, build 174 (2024-08-24 08:30 UTC)", "v0.8.12.md"),
|
new (174, "v0.8.12, build 174 (2024-08-24 08:30 UTC)", "v0.8.12.md"),
|
||||||
new (173, "v0.8.11, build 173 (2024-08-21 07:03 UTC)", "v0.8.11.md"),
|
new (173, "v0.8.11, build 173 (2024-08-21 07:03 UTC)", "v0.8.11.md"),
|
||||||
new (172, "v0.8.10, build 172 (2024-08-18 19:44 UTC)", "v0.8.10.md"),
|
new (172, "v0.8.10, build 172 (2024-08-18 19:44 UTC)", "v0.8.10.md"),
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Layout;
|
using AIStudio.Layout;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace AIStudio.Components;
|
namespace AIStudio.Components;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace AIStudio.Components;
|
namespace AIStudio.Components;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using AIStudio.Agents;
|
using AIStudio.Agents;
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using System.Text.Json.Serialization;
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Dialogs;
|
using AIStudio.Dialogs;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
@ -24,6 +23,9 @@ public partial class Workspaces : ComponentBase
|
|||||||
[Inject]
|
[Inject]
|
||||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
private ThreadSafeRandom RNG { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ILogger<Workspaces> Logger { get; init; } = null!;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public ChatThread? CurrentChatThread { get; set; }
|
public ChatThread? CurrentChatThread { get; set; }
|
||||||
|
|
||||||
@ -309,7 +311,7 @@ public partial class Workspaces : ComponentBase
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e);
|
this.Logger.LogError($"Failed to load chat from '{chatPath}': {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
@using AIStudio.Components
|
|
||||||
@using AIStudio.Provider
|
@using AIStudio.Provider
|
||||||
@using AIStudio.Provider.SelfHosted
|
@using AIStudio.Provider.SelfHosted
|
||||||
@using MudBlazor
|
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<DialogContent>
|
<DialogContent>
|
@ -1,12 +1,14 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
|
using AIStudio.Settings;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||||
|
using RustService = AIStudio.Tools.RustService;
|
||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Dialogs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The provider settings dialog.
|
/// The provider settings dialog.
|
||||||
@ -71,10 +73,13 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
public bool IsEditing { get; init; }
|
public bool IsEditing { get; init; }
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private SettingsManager SettingsManager { get; set; } = null!;
|
private SettingsManager SettingsManager { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private IJSRuntime JsRuntime { get; set; } = null!;
|
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private RustService RustService { get; init; } = null!;
|
||||||
|
|
||||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||||
|
|
||||||
@ -94,8 +99,9 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
private MudForm form = null!;
|
private MudForm form = null!;
|
||||||
|
|
||||||
private readonly List<Model> availableModels = new();
|
private readonly List<Model> availableModels = new();
|
||||||
|
private readonly Encryption encryption = Program.ENCRYPTION;
|
||||||
private Provider CreateProviderSettings() => new()
|
|
||||||
|
private Settings.Provider CreateProviderSettings() => new()
|
||||||
{
|
{
|
||||||
Num = this.DataNum,
|
Num = this.DataNum,
|
||||||
Id = this.DataId,
|
Id = this.DataId,
|
||||||
@ -133,7 +139,7 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
var loadedProviderSettings = this.CreateProviderSettings();
|
var loadedProviderSettings = this.CreateProviderSettings();
|
||||||
var provider = loadedProviderSettings.CreateProvider();
|
var provider = loadedProviderSettings.CreateProvider(this.Logger);
|
||||||
if(provider is NoProvider)
|
if(provider is NoProvider)
|
||||||
{
|
{
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
@ -141,10 +147,10 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the API key:
|
// Load the API key:
|
||||||
var requestedSecret = await this.SettingsManager.GetAPIKey(this.JsRuntime, provider);
|
var requestedSecret = await this.RustService.GetAPIKey(provider);
|
||||||
if(requestedSecret.Success)
|
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:
|
// Now, we try to load the list of available models:
|
||||||
await this.ReloadModels();
|
await this.ReloadModels();
|
||||||
@ -187,10 +193,10 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED)
|
if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED)
|
||||||
{
|
{
|
||||||
// We need to instantiate the provider to store the API key:
|
// We need to instantiate the provider to store the API key:
|
||||||
var provider = addedProviderSettings.CreateProvider();
|
var provider = addedProviderSettings.CreateProvider(this.Logger);
|
||||||
|
|
||||||
// Store the API key in the OS secure storage:
|
// Store the API key in the OS secure storage:
|
||||||
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
var storeResponse = await this.RustService.SetAPIKey(provider, this.dataAPIKey);
|
||||||
if (!storeResponse.Success)
|
if (!storeResponse.Success)
|
||||||
{
|
{
|
||||||
this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again.";
|
this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again.";
|
||||||
@ -318,11 +324,11 @@ public partial class ProviderDialog : ComponentBase
|
|||||||
private async Task ReloadModels()
|
private async Task ReloadModels()
|
||||||
{
|
{
|
||||||
var currentProviderSettings = this.CreateProviderSettings();
|
var currentProviderSettings = this.CreateProviderSettings();
|
||||||
var provider = currentProviderSettings.CreateProvider();
|
var provider = currentProviderSettings.CreateProvider(this.Logger);
|
||||||
if(provider is NoProvider)
|
if(provider is NoProvider)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var models = await provider.GetTextModels(this.JsRuntime, this.SettingsManager, this.dataAPIKey);
|
var models = await provider.GetTextModels(this.dataAPIKey);
|
||||||
|
|
||||||
// Order descending by ID means that the newest models probably come first:
|
// Order descending by ID means that the newest models probably come first:
|
||||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
var orderedModels = models.OrderByDescending(n => n.Id);
|
@ -1,6 +1,6 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
using AIStudio.Tools;
|
using AIStudio.Tools.Rust;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// Global using directives
|
// Global using directives
|
||||||
|
|
||||||
|
global using AIStudio.Tools;
|
||||||
|
|
||||||
global using Microsoft.JSInterop;
|
global using Microsoft.JSInterop;
|
||||||
|
|
||||||
global using MudBlazor;
|
global using MudBlazor;
|
@ -1,21 +1,19 @@
|
|||||||
using AIStudio.Dialogs;
|
using AIStudio.Dialogs;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Settings.DataModel;
|
using AIStudio.Settings.DataModel;
|
||||||
using AIStudio.Tools;
|
using AIStudio.Tools.Rust;
|
||||||
using AIStudio.Tools.Services;
|
using AIStudio.Tools.Services;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Routing;
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
|
||||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||||
|
using RustService = AIStudio.Tools.RustService;
|
||||||
|
|
||||||
namespace AIStudio.Layout;
|
namespace AIStudio.Layout;
|
||||||
|
|
||||||
public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDisposable
|
public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDisposable
|
||||||
{
|
{
|
||||||
[Inject]
|
|
||||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private SettingsManager SettingsManager { get; init; } = null!;
|
private SettingsManager SettingsManager { get; init; } = null!;
|
||||||
|
|
||||||
@ -26,13 +24,16 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
|||||||
private IDialogService DialogService { get; init; } = null!;
|
private IDialogService DialogService { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private Rust Rust { get; init; } = null!;
|
private RustService RustService { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ISnackbar Snackbar { get; init; } = null!;
|
private ISnackbar Snackbar { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private NavigationManager NavigationManager { get; init; } = null!;
|
private NavigationManager NavigationManager { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ILogger<MainLayout> Logger { get; init; } = null!;
|
||||||
|
|
||||||
public string AdditionalHeight { get; private set; } = "0em";
|
public string AdditionalHeight { get; private set; } = "0em";
|
||||||
|
|
||||||
@ -70,12 +71,15 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
|||||||
// We use the Tauri API (Rust) to get the data and config directories
|
// We use the Tauri API (Rust) to get the data and config directories
|
||||||
// for this app.
|
// for this app.
|
||||||
//
|
//
|
||||||
var dataDir = await this.JsRuntime.InvokeAsync<string>("window.__TAURI__.path.appLocalDataDir");
|
var dataDir = await this.RustService.GetDataDirectory();
|
||||||
var configDir = await this.JsRuntime.InvokeAsync<string>("window.__TAURI__.path.appConfigDir");
|
var configDir = await this.RustService.GetConfigDirectory();
|
||||||
|
|
||||||
|
this.Logger.LogInformation($"The data directory is: '{dataDir}'");
|
||||||
|
this.Logger.LogInformation($"The config directory is: '{configDir}'");
|
||||||
|
|
||||||
// Store the directories in the settings manager:
|
// Store the directories in the settings manager:
|
||||||
SettingsManager.ConfigDirectory = configDir;
|
SettingsManager.ConfigDirectory = configDir;
|
||||||
SettingsManager.DataDirectory = Path.Join(dataDir, "data");
|
SettingsManager.DataDirectory = dataDir;
|
||||||
Directory.CreateDirectory(SettingsManager.DataDirectory);
|
Directory.CreateDirectory(SettingsManager.DataDirectory);
|
||||||
|
|
||||||
// Ensure that all settings are loaded:
|
// Ensure that all settings are loaded:
|
||||||
@ -85,8 +89,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
|||||||
this.MessageBus.RegisterComponent(this);
|
this.MessageBus.RegisterComponent(this);
|
||||||
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED ]);
|
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED ]);
|
||||||
|
|
||||||
// Set the js runtime for the update service:
|
// Set the snackbar for the update service:
|
||||||
UpdateService.SetBlazorDependencies(this.JsRuntime, this.Snackbar);
|
UpdateService.SetBlazorDependencies(this.Snackbar);
|
||||||
TemporaryChatService.Initialize();
|
TemporaryChatService.Initialize();
|
||||||
|
|
||||||
// Should the navigation bar be open by default?
|
// Should the navigation bar be open by default?
|
||||||
@ -189,7 +193,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
|
|||||||
|
|
||||||
this.performingUpdate = true;
|
this.performingUpdate = true;
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
await this.Rust.InstallUpdate(this.JsRuntime);
|
await this.RustService.InstallUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask OnLocationChanging(LocationChangingContext context)
|
private async ValueTask OnLocationChanging(LocationChangingContext context)
|
||||||
|
@ -39,13 +39,18 @@
|
|||||||
<ThirdPartyComponent Name=".NET" Developer="Microsoft & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/dotnet/runtime/blob/main/LICENSE.TXT" RepositoryUrl="https://github.com/dotnet" UseCase="The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK."/>
|
<ThirdPartyComponent Name=".NET" Developer="Microsoft & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/dotnet/runtime/blob/main/LICENSE.TXT" RepositoryUrl="https://github.com/dotnet" UseCase="The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK."/>
|
||||||
<ThirdPartyComponent Name="MudBlazor" Developer="Jonny Larsson, Meinrad Recheis & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MudBlazor/MudBlazor/blob/dev/LICENSE" RepositoryUrl="https://github.com/MudBlazor/MudBlazor/" UseCase="Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor."/>
|
<ThirdPartyComponent Name="MudBlazor" Developer="Jonny Larsson, Meinrad Recheis & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MudBlazor/MudBlazor/blob/dev/LICENSE" RepositoryUrl="https://github.com/MudBlazor/MudBlazor/" UseCase="Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor."/>
|
||||||
<ThirdPartyComponent Name="MudBlazor.Markdown" Developer="My Nihongo & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MyNihongo/MudBlazor.Markdown/blob/main/LICENSE" RepositoryUrl="https://github.com/MyNihongo/MudBlazor.Markdown" UseCase="This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read."/>
|
<ThirdPartyComponent Name="MudBlazor.Markdown" Developer="My Nihongo & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/MyNihongo/MudBlazor.Markdown/blob/main/LICENSE" RepositoryUrl="https://github.com/MyNihongo/MudBlazor.Markdown" UseCase="This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read."/>
|
||||||
<ThirdPartyComponent Name="Rust" Developer="Graydon Hoare, Rust Foundation & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/rust-lang/rust?tab=readme-ov-file#license" RepositoryUrl="https://github.com/rust-lang/rust" UseCase="The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software."/>
|
<ThirdPartyComponent Name="Rust" Developer="Graydon Hoare, Rust Foundation, Rust developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/rust/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/rust" UseCase="The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software."/>
|
||||||
<ThirdPartyComponent Name="Tauri" Developer="Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/tauri-apps/tauri?tab=readme-ov-file#licenses" RepositoryUrl="https://github.com/tauri-apps/tauri" UseCase="Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"/>
|
<ThirdPartyComponent Name="Tauri" Developer="Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tauri-apps/tauri/blob/dev/LICENSE_MIT" RepositoryUrl="https://github.com/tauri-apps/tauri" UseCase="Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"/>
|
||||||
<ThirdPartyComponent Name="serde" Developer="Erick Tryzelaar, David Tolnay & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/serde-rs/serde?tab=readme-ov-file#license" RepositoryUrl="https://github.com/serde-rs/serde" UseCase="Now we have multiple systems, some developed in .NET and others in Rust. The data format JSON is responsible for translating data between both worlds (called data serialization and deserialization). Serde takes on this task in the Rust world. The counterpart in the .NET world is an integral part of .NET and is located in System.Text.Json."/>
|
<ThirdPartyComponent Name="Rocket" Developer="Sergio Benitez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rwf2/Rocket/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rwf2/Rocket" UseCase="We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust."/>
|
||||||
<ThirdPartyComponent Name="keyring" Developer="Walther Chen, Daniel Brotsky & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/hwchen/keyring-rs?tab=readme-ov-file#license" RepositoryUrl="https://github.com/hwchen/keyring-rs" UseCase="In order to use any LLM, each user must store their so-called token for each LLM provider. This token must be kept secure, similar to a password. The safest way to do this is offered by operating systems like macOS, Windows, and Linux: They have mechanisms to store such data, if available, on special security hardware. Since this is currently not possible in .NET, we use this Rust library."/>
|
<ThirdPartyComponent Name="serde" Developer="Erick Tryzelaar, David Tolnay & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/serde-rs/serde/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/serde-rs/serde" UseCase="Now we have multiple systems, some developed in .NET and others in Rust. The data format JSON is responsible for translating data between both worlds (called data serialization and deserialization). Serde takes on this task in the Rust world. The counterpart in the .NET world is an integral part of .NET and is located in System.Text.Json."/>
|
||||||
<ThirdPartyComponent Name="arboard" Developer="Artur Kovacs, Avi Weinstock, 1Password & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/1Password/arboard" RepositoryUrl="https://github.com/1Password/arboard" UseCase="To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system."/>
|
<ThirdPartyComponent Name="keyring" Developer="Walther Chen, Daniel Brotsky & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/hwchen/keyring-rs/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/hwchen/keyring-rs" UseCase="In order to use any LLM, each user must store their so-called token for each LLM provider. This token must be kept secure, similar to a password. The safest way to do this is offered by operating systems like macOS, Windows, and Linux: They have mechanisms to store such data, if available, on special security hardware. Since this is currently not possible in .NET, we use this Rust library."/>
|
||||||
|
<ThirdPartyComponent Name="arboard" Developer="Artur Kovacs, Avi Weinstock, 1Password & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/arboard/blob/master/LICENSE-MIT.txt" RepositoryUrl="https://github.com/1Password/arboard" UseCase="To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system."/>
|
||||||
<ThirdPartyComponent Name="tokio" Developer="Alex Crichton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/tokio/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/tokio" UseCase="Code in the Rust language can be specified as synchronous or asynchronous. Unlike .NET and the C# language, Rust cannot execute asynchronous code by itself. Rust requires support in the form of an executor for this. Tokio is one such executor."/>
|
<ThirdPartyComponent Name="tokio" Developer="Alex Crichton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/tokio/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/tokio" UseCase="Code in the Rust language can be specified as synchronous or asynchronous. Unlike .NET and the C# language, Rust cannot execute asynchronous code by itself. Rust requires support in the form of an executor for this. Tokio is one such executor."/>
|
||||||
<ThirdPartyComponent Name="flexi_logger" Developer="emabee & Open Source Community" LicenseName="MIT & Apache-2.0" LicenseUrl="https://github.com/emabee/flexi_logger" RepositoryUrl="https://github.com/emabee/flexi_logger" UseCase="This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible."/>
|
<ThirdPartyComponent Name="flexi_logger" Developer="emabee & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/emabee/flexi_logger/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/emabee/flexi_logger" UseCase="This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible."/>
|
||||||
|
<ThirdPartyComponent Name="rand" Developer="Rust developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-random/rand/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-random/rand" UseCase="We must generate random numbers, e.g., for securing the interprocess communication between the user interface and the runtime. The rand library is great for this purpose."/>
|
||||||
|
<ThirdPartyComponent Name="base64" Developer="Marshall Pierce, Alice Maz & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/marshallpierce/rust-base64/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/marshallpierce/rust-base64" UseCase="For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose."/>
|
||||||
|
<ThirdPartyComponent Name="Rust Crypto" Developer="Artyom Pavlov, Tony Arcieri, Brian Warner, Arthur Gautier, Vlad Filippov, Friedel Ziegelmayer, Nicolas Stalder & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/RustCrypto/traits/blob/master/cipher/LICENSE-MIT" RepositoryUrl="https://github.com/RustCrypto" UseCase="When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project."/>
|
||||||
|
<ThirdPartyComponent Name="rcgen" Developer="RustTLS developers, est31 & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rustls/rcgen/blob/main/LICENSE" RepositoryUrl="https://github.com/rustls/rcgen" UseCase="For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose."/>
|
||||||
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant."/>
|
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant."/>
|
||||||
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."/>
|
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."/>
|
||||||
<ThirdPartyComponent Name="wikEd diff" Developer="Cacycle & Open Source Community" LicenseName="None (public domain)" LicenseUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff#License" RepositoryUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff" UseCase="This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant."/>
|
<ThirdPartyComponent Name="wikEd diff" Developer="Cacycle & Open Source Community" LicenseName="None (public domain)" LicenseUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff#License" RepositoryUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff" UseCase="This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant."/>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace AIStudio.Pages;
|
namespace AIStudio.Pages;
|
||||||
|
@ -4,7 +4,6 @@ using AIStudio.Dialogs;
|
|||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Settings.DataModel;
|
using AIStudio.Settings.DataModel;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
@ -19,17 +18,17 @@ namespace AIStudio.Pages;
|
|||||||
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||||
{
|
{
|
||||||
[Inject]
|
[Inject]
|
||||||
private SettingsManager SettingsManager { get; set; } = null!;
|
private SettingsManager SettingsManager { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
|
||||||
public IJSRuntime JsRuntime { get; init; } = null!;
|
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
private ThreadSafeRandom RNG { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public IDialogService DialogService { get; set; } = null!;
|
private IDialogService DialogService { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private ILogger<Chat> Logger { get; init; } = null!;
|
||||||
|
|
||||||
private InnerScrolling scrollingArea = null!;
|
private InnerScrolling scrollingArea = null!;
|
||||||
|
|
||||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
|
||||||
@ -189,7 +188,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
|||||||
// Use the selected provider to get the AI response.
|
// Use the selected provider to get the AI response.
|
||||||
// By awaiting this line, we wait for the entire
|
// By awaiting this line, we wait for the entire
|
||||||
// content to be streamed.
|
// content to be streamed.
|
||||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), 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:
|
// Save the chat:
|
||||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using AIStudio.Dialogs;
|
using AIStudio.Dialogs;
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||||
|
using RustService = AIStudio.Tools.RustService;
|
||||||
|
|
||||||
// ReSharper disable ClassNeverInstantiated.Global
|
// ReSharper disable ClassNeverInstantiated.Global
|
||||||
|
|
||||||
@ -14,16 +14,19 @@ namespace AIStudio.Pages;
|
|||||||
public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||||
{
|
{
|
||||||
[Inject]
|
[Inject]
|
||||||
public SettingsManager SettingsManager { get; init; } = null!;
|
private SettingsManager SettingsManager { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public IDialogService DialogService { get; init; } = null!;
|
private IDialogService DialogService { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
public IJSRuntime JsRuntime { get; init; } = null!;
|
private MessageBus MessageBus { get; init; } = null!;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected MessageBus MessageBus { get; init; } = null!;
|
private ILogger<Settings> Logger { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private RustService RustService { get; init; } = null!;
|
||||||
|
|
||||||
private readonly List<ConfigurationSelectData<string>> availableProviders = new();
|
private readonly List<ConfigurationSelectData<string>> availableProviders = new();
|
||||||
|
|
||||||
@ -111,8 +114,8 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
|||||||
if (dialogResult is null || dialogResult.Canceled)
|
if (dialogResult is null || dialogResult.Canceled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var providerInstance = provider.CreateProvider();
|
var providerInstance = provider.CreateProvider(this.Logger);
|
||||||
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
var deleteSecretResponse = await this.RustService.DeleteAPIKey(providerInstance);
|
||||||
if(deleteSecretResponse.Success)
|
if(deleteSecretResponse.Success)
|
||||||
{
|
{
|
||||||
this.SettingsManager.ConfigurationData.Providers.Remove(provider);
|
this.SettingsManager.ConfigurationData.Providers.Remove(provider);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using AIStudio;
|
|
||||||
using AIStudio.Agents;
|
using AIStudio.Agents;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
using AIStudio.Tools;
|
|
||||||
using AIStudio.Tools.Services;
|
using AIStudio.Tools.Services;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.Extensions.Logging.Console;
|
||||||
|
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@ -11,73 +12,168 @@ using System.Reflection;
|
|||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var port = args.Length > 0 ? args[0] : "5000";
|
namespace AIStudio;
|
||||||
var builder = WebApplication.CreateBuilder();
|
|
||||||
builder.Services.AddMudServices(config =>
|
|
||||||
{
|
|
||||||
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomLeft;
|
|
||||||
config.SnackbarConfiguration.PreventDuplicates = false;
|
|
||||||
config.SnackbarConfiguration.NewestOnTop = false;
|
|
||||||
config.SnackbarConfiguration.ShowCloseIcon = true;
|
|
||||||
config.SnackbarConfiguration.VisibleStateDuration = 6_000; //milliseconds aka 6 seconds
|
|
||||||
config.SnackbarConfiguration.HideTransitionDuration = 500;
|
|
||||||
config.SnackbarConfiguration.ShowTransitionDuration = 500;
|
|
||||||
config.SnackbarConfiguration.SnackbarVariant = Variant.Outlined;
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddMudMarkdownServices();
|
internal sealed class Program
|
||||||
builder.Services.AddSingleton(MessageBus.INSTANCE);
|
{
|
||||||
builder.Services.AddSingleton<Rust>();
|
public static RustService RUST_SERVICE = null!;
|
||||||
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();
|
public static Encryption ENCRYPTION = null!;
|
||||||
builder.Services.AddSingleton<SettingsManager>();
|
public static string API_TOKEN = null!;
|
||||||
builder.Services.AddSingleton<ThreadSafeRandom>();
|
|
||||||
builder.Services.AddTransient<HTMLParser>();
|
public static async Task Main(string[] args)
|
||||||
builder.Services.AddTransient<AgentTextContentCleaner>();
|
|
||||||
builder.Services.AddHostedService<UpdateService>();
|
|
||||||
builder.Services.AddHostedService<TemporaryChatService>();
|
|
||||||
builder.Services.AddRazorComponents()
|
|
||||||
.AddInteractiveServerComponents()
|
|
||||||
.AddHubOptions(options =>
|
|
||||||
{
|
{
|
||||||
options.MaximumReceiveMessageSize = null;
|
if(args.Length == 0)
|
||||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(1_200);
|
{
|
||||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
Console.WriteLine("Error: Please provide the port of the runtime API.");
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
builder.Services.AddSingleton(new HttpClient
|
// Read the secret key for the IPC from the AI_STUDIO_SECRET_KEY environment variable:
|
||||||
{
|
var secretPasswordEncoded = Environment.GetEnvironmentVariable("AI_STUDIO_SECRET_PASSWORD");
|
||||||
BaseAddress = new Uri($"http://localhost:{port}")
|
if(string.IsNullOrWhiteSpace(secretPasswordEncoded))
|
||||||
});
|
{
|
||||||
|
Console.WriteLine("Error: The AI_STUDIO_SECRET_PASSWORD environment variable is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
builder.WebHost.UseUrls($"http://localhost:{port}");
|
var secretPassword = Convert.FromBase64String(secretPasswordEncoded);
|
||||||
|
var secretKeySaltEncoded = Environment.GetEnvironmentVariable("AI_STUDIO_SECRET_KEY_SALT");
|
||||||
|
if(string.IsNullOrWhiteSpace(secretKeySaltEncoded))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: The AI_STUDIO_SECRET_KEY_SALT environment variable is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretKeySalt = Convert.FromBase64String(secretKeySaltEncoded);
|
||||||
|
|
||||||
|
var certificateFingerprint = Environment.GetEnvironmentVariable("AI_STUDIO_CERTIFICATE_FINGERPRINT");
|
||||||
|
if(string.IsNullOrWhiteSpace(certificateFingerprint))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: The AI_STUDIO_CERTIFICATE_FINGERPRINT environment variable is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiToken = Environment.GetEnvironmentVariable("AI_STUDIO_API_TOKEN");
|
||||||
|
if(string.IsNullOrWhiteSpace(apiToken))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: The AI_STUDIO_API_TOKEN environment variable is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
API_TOKEN = apiToken;
|
||||||
|
|
||||||
|
var rustApiPort = args[0];
|
||||||
|
using var rust = new RustService(rustApiPort, certificateFingerprint);
|
||||||
|
var appPort = await rust.GetAppPort();
|
||||||
|
if(appPort == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Failed to get the app port from Rust.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder();
|
||||||
|
|
||||||
|
builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
|
||||||
|
{
|
||||||
|
kestrelServerOptions.ConfigureEndpointDefaults(listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.SetMinimumLevel(LogLevel.Debug);
|
||||||
|
builder.Logging.AddFilter("Microsoft", LogLevel.Information);
|
||||||
|
builder.Logging.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("Microsoft.AspNetCore.Routing.EndpointMiddleware", LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("Microsoft.AspNetCore.StaticFiles", LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("MudBlazor", LogLevel.Information);
|
||||||
|
builder.Logging.AddConsole(options =>
|
||||||
|
{
|
||||||
|
options.FormatterName = TerminalLogger.FORMATTER_NAME;
|
||||||
|
}).AddConsoleFormatter<TerminalLogger, ConsoleFormatterOptions>();
|
||||||
|
|
||||||
|
builder.Services.AddMudServices(config =>
|
||||||
|
{
|
||||||
|
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomLeft;
|
||||||
|
config.SnackbarConfiguration.PreventDuplicates = false;
|
||||||
|
config.SnackbarConfiguration.NewestOnTop = false;
|
||||||
|
config.SnackbarConfiguration.ShowCloseIcon = true;
|
||||||
|
config.SnackbarConfiguration.VisibleStateDuration = 6_000; //milliseconds aka 6 seconds
|
||||||
|
config.SnackbarConfiguration.HideTransitionDuration = 500;
|
||||||
|
config.SnackbarConfiguration.ShowTransitionDuration = 500;
|
||||||
|
config.SnackbarConfiguration.SnackbarVariant = Variant.Outlined;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddMudMarkdownServices();
|
||||||
|
builder.Services.AddSingleton(MessageBus.INSTANCE);
|
||||||
|
builder.Services.AddSingleton(rust);
|
||||||
|
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();
|
||||||
|
builder.Services.AddSingleton<SettingsManager>();
|
||||||
|
builder.Services.AddSingleton<ThreadSafeRandom>();
|
||||||
|
builder.Services.AddTransient<HTMLParser>();
|
||||||
|
builder.Services.AddTransient<AgentTextContentCleaner>();
|
||||||
|
builder.Services.AddHostedService<UpdateService>();
|
||||||
|
builder.Services.AddHostedService<TemporaryChatService>();
|
||||||
|
builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents()
|
||||||
|
.AddHubOptions(options =>
|
||||||
|
{
|
||||||
|
options.MaximumReceiveMessageSize = null;
|
||||||
|
options.ClientTimeoutInterval = TimeSpan.FromSeconds(1_200);
|
||||||
|
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri($"http://localhost:{appPort}")
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.WebHost.UseUrls($"http://localhost:{appPort}");
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
builder.WebHost.UseWebRoot("wwwroot");
|
||||||
|
builder.WebHost.UseStaticWebAssets();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Execute the builder to get the app:
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Initialize the encryption service:
|
||||||
|
var encryptionLogger = app.Services.GetRequiredService<ILogger<Encryption>>();
|
||||||
|
var encryption = new Encryption(encryptionLogger, secretPassword, secretKeySalt);
|
||||||
|
var encryptionInitializer = encryption.Initialize();
|
||||||
|
|
||||||
|
// Set the logger for the Rust service:
|
||||||
|
var rustLogger = app.Services.GetRequiredService<ILogger<RustService>>();
|
||||||
|
rust.SetLogger(rustLogger);
|
||||||
|
rust.SetEncryptor(encryption);
|
||||||
|
|
||||||
|
RUST_SERVICE = rust;
|
||||||
|
ENCRYPTION = encryption;
|
||||||
|
|
||||||
|
app.Use(Redirect.HandlerContentAsync);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
builder.WebHost.UseWebRoot("wwwroot");
|
app.UseStaticFiles();
|
||||||
builder.WebHost.UseStaticWebAssets();
|
app.UseDeveloperExceptionPage();
|
||||||
#endif
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
app.Use(Redirect.HandlerContentAsync);
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
app.UseStaticFiles();
|
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
#else
|
#else
|
||||||
|
var fileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "wwwroot");
|
||||||
var fileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "wwwroot");
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
{
|
||||||
{
|
FileProvider = fileProvider,
|
||||||
FileProvider = fileProvider,
|
RequestPath = string.Empty,
|
||||||
RequestPath = string.Empty,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
var serverTask = app.RunAsync();
|
var serverTask = app.RunAsync();
|
||||||
|
|
||||||
Console.WriteLine("RUST/TAURI SERVER STARTED");
|
await encryptionInitializer;
|
||||||
await serverTask;
|
await rust.AppIsReady();
|
||||||
|
await serverTask;
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,10 @@ using System.Text.Json;
|
|||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Provider.OpenAI;
|
using AIStudio.Provider.OpenAI;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider.Anthropic;
|
namespace AIStudio.Provider.Anthropic;
|
||||||
|
|
||||||
public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.com/v1/"), IProvider
|
public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger), IProvider
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||||
{
|
{
|
||||||
@ -22,10 +21,10 @@ public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.co
|
|||||||
public string InstanceName { get; set; } = "Anthropic";
|
public string InstanceName { get; set; } = "Anthropic";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Get the API key:
|
// Get the API key:
|
||||||
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
|
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||||
if(!requestedSecret.Success)
|
if(!requestedSecret.Success)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
@ -64,7 +63,7 @@ public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.co
|
|||||||
var request = new HttpRequestMessage(HttpMethod.Post, "messages");
|
var request = new HttpRequestMessage(HttpMethod.Post, "messages");
|
||||||
|
|
||||||
// Set the authorization header:
|
// 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:
|
// Set the Anthropic version:
|
||||||
request.Headers.Add("anthropic-version", "2023-06-01");
|
request.Headers.Add("anthropic-version", "2023-06-01");
|
||||||
@ -137,14 +136,14 @@ public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.co
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <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)
|
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new[]
|
return Task.FromResult(new[]
|
||||||
{
|
{
|
||||||
@ -157,7 +156,7 @@ public sealed class ProviderAnthropic() : BaseProvider("https://api.anthropic.co
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(Enumerable.Empty<Model>());
|
return Task.FromResult(Enumerable.Empty<Model>());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using RustService = AIStudio.Tools.RustService;
|
||||||
|
|
||||||
namespace AIStudio.Provider;
|
namespace AIStudio.Provider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -9,13 +11,31 @@ public abstract class BaseProvider
|
|||||||
/// The HTTP client to use for all requests.
|
/// The HTTP client to use for all requests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly HttpClient httpClient = new();
|
protected readonly HttpClient httpClient = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The logger to use.
|
||||||
|
/// </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>
|
/// <summary>
|
||||||
/// Constructor for the base provider.
|
/// Constructor for the base provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url">The base URL for the provider.</param>
|
/// <param name="url">The base URL for the provider.</param>
|
||||||
protected BaseProvider(string url)
|
/// <param name="loggerService">The logger service to use.</param>
|
||||||
|
protected BaseProvider(string url, ILogger loggerService)
|
||||||
{
|
{
|
||||||
|
this.logger = loggerService;
|
||||||
|
|
||||||
// Set the base URL:
|
// Set the base URL:
|
||||||
this.httpClient.BaseAddress = new(url);
|
this.httpClient.BaseAddress = new(url);
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,10 @@ using System.Text;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider.Fireworks;
|
namespace AIStudio.Provider.Fireworks;
|
||||||
|
|
||||||
public class ProviderFireworks() : BaseProvider("https://api.fireworks.ai/inference/v1/"), IProvider
|
public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger), IProvider
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||||
{
|
{
|
||||||
@ -24,10 +23,10 @@ public class ProviderFireworks() : BaseProvider("https://api.fireworks.ai/infere
|
|||||||
public string InstanceName { get; set; } = "Fireworks.ai";
|
public string InstanceName { get; set; } = "Fireworks.ai";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Get the API key:
|
// Get the API key:
|
||||||
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
|
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||||
if(!requestedSecret.Success)
|
if(!requestedSecret.Success)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ public class ProviderFireworks() : BaseProvider("https://api.fireworks.ai/infere
|
|||||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||||
|
|
||||||
// Set the authorization header:
|
// 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:
|
// Set the content:
|
||||||
request.Content = new StringContent(fireworksChatRequest, Encoding.UTF8, "application/json");
|
request.Content = new StringContent(fireworksChatRequest, Encoding.UTF8, "application/json");
|
||||||
@ -139,20 +138,20 @@ public class ProviderFireworks() : BaseProvider("https://api.fireworks.ai/infere
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <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)
|
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(Enumerable.Empty<Model>());
|
return Task.FromResult(Enumerable.Empty<Model>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(Enumerable.Empty<Model>());
|
return Task.FromResult(Enumerable.Empty<Model>());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider;
|
namespace AIStudio.Provider;
|
||||||
|
|
||||||
@ -22,44 +21,36 @@ public interface IProvider
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts a chat completion stream.
|
/// Starts a chat completion stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
|
||||||
/// <param name="settings">The settings manager to access the API key.</param>
|
|
||||||
/// <param name="chatModel">The model to use for chat completion.</param>
|
/// <param name="chatModel">The model to use for chat completion.</param>
|
||||||
/// <param name="chatThread">The chat thread to continue.</param>
|
/// <param name="chatThread">The chat thread to continue.</param>
|
||||||
/// <param name="token">The cancellation token.</param>
|
/// <param name="token">The cancellation token.</param>
|
||||||
/// <returns>The chat completion stream.</returns>
|
/// <returns>The chat completion stream.</returns>
|
||||||
public IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, CancellationToken token = default);
|
public IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts an image completion stream.
|
/// Starts an image completion stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
|
||||||
/// <param name="settings">The settings manager to access the API key.</param>
|
|
||||||
/// <param name="imageModel">The model to use for image completion.</param>
|
/// <param name="imageModel">The model to use for image completion.</param>
|
||||||
/// <param name="promptPositive">The positive prompt.</param>
|
/// <param name="promptPositive">The positive prompt.</param>
|
||||||
/// <param name="promptNegative">The negative prompt.</param>
|
/// <param name="promptNegative">The negative prompt.</param>
|
||||||
/// <param name="referenceImageURL">The reference image URL.</param>
|
/// <param name="referenceImageURL">The reference image URL.</param>
|
||||||
/// <param name="token">The cancellation token.</param>
|
/// <param name="token">The cancellation token.</param>
|
||||||
/// <returns>The image completion stream.</returns>
|
/// <returns>The image completion stream.</returns>
|
||||||
public IAsyncEnumerable<ImageURL> StreamImageCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
|
public IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load all possible text models that can be used with this provider.
|
/// Load all possible text models that can be used with this provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
|
||||||
/// <param name="settings">The settings manager to access the API key.</param>
|
|
||||||
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
|
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
|
||||||
/// <param name="token">The cancellation token.</param>
|
/// <param name="token">The cancellation token.</param>
|
||||||
/// <returns>The list of text models.</returns>
|
/// <returns>The list of text models.</returns>
|
||||||
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default);
|
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load all possible image models that can be used with this provider.
|
/// Load all possible image models that can be used with this provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
|
||||||
/// <param name="settings">The settings manager to access the API key.</param>
|
|
||||||
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
|
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
|
||||||
/// <param name="token">The cancellation token.</param>
|
/// <param name="token">The cancellation token.</param>
|
||||||
/// <returns>The list of image models.</returns>
|
/// <returns>The list of image models.</returns>
|
||||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default);
|
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||||
}
|
}
|
@ -5,11 +5,10 @@ using System.Text.Json;
|
|||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Provider.OpenAI;
|
using AIStudio.Provider.OpenAI;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider.Mistral;
|
namespace AIStudio.Provider.Mistral;
|
||||||
|
|
||||||
public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/"), IProvider
|
public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger), IProvider
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||||
{
|
{
|
||||||
@ -23,10 +22,10 @@ public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/
|
|||||||
public string InstanceName { get; set; } = "Mistral";
|
public string InstanceName { get; set; } = "Mistral";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Get the API key:
|
// Get the API key:
|
||||||
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
|
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||||
if(!requestedSecret.Success)
|
if(!requestedSecret.Success)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
@ -75,7 +74,7 @@ public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/
|
|||||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||||
|
|
||||||
// Set the authorization header:
|
// 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:
|
// Set the content:
|
||||||
request.Content = new StringContent(mistralChatRequest, Encoding.UTF8, "application/json");
|
request.Content = new StringContent(mistralChatRequest, Encoding.UTF8, "application/json");
|
||||||
@ -141,21 +140,21 @@ public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <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)
|
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var secretKey = apiKeyProvisional switch
|
var secretKey = apiKeyProvisional switch
|
||||||
{
|
{
|
||||||
not null => apiKeyProvisional,
|
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,
|
_ => null,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -179,7 +178,7 @@ public sealed class ProviderMistral() : BaseProvider("https://api.mistral.ai/v1/
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Provider.Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider;
|
namespace AIStudio.Provider;
|
||||||
|
|
||||||
@ -13,17 +12,17 @@ public class NoProvider : IProvider
|
|||||||
|
|
||||||
public string InstanceName { get; set; } = "None";
|
public string InstanceName { get; set; } = "None";
|
||||||
|
|
||||||
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||||
|
|
||||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
await Task.FromResult(0);
|
await Task.FromResult(0);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
await Task.FromResult(0);
|
await Task.FromResult(0);
|
||||||
yield break;
|
yield break;
|
||||||
|
@ -4,14 +4,13 @@ using System.Text;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider.OpenAI;
|
namespace AIStudio.Provider.OpenAI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The OpenAI provider.
|
/// The OpenAI provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"), IProvider
|
public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger), IProvider
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||||
{
|
{
|
||||||
@ -27,10 +26,10 @@ public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"
|
|||||||
public string InstanceName { get; set; } = "OpenAI";
|
public string InstanceName { get; set; } = "OpenAI";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Get the API key:
|
// Get the API key:
|
||||||
var requestedSecret = await settings.GetAPIKey(jsRuntime, this);
|
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||||
if(!requestedSecret.Success)
|
if(!requestedSecret.Success)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
@ -79,7 +78,7 @@ public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"
|
|||||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||||
|
|
||||||
// Set the authorization header:
|
// 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:
|
// Set the content:
|
||||||
request.Content = new StringContent(openAIChatRequest, Encoding.UTF8, "application/json");
|
request.Content = new StringContent(openAIChatRequest, Encoding.UTF8, "application/json");
|
||||||
@ -145,34 +144,34 @@ public sealed class ProviderOpenAI() : BaseProvider("https://api.openai.com/v1/"
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <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)
|
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return this.LoadModels(jsRuntime, settings, "gpt-", token, apiKeyProvisional);
|
return this.LoadModels("gpt-", token, apiKeyProvisional);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return this.LoadModels(jsRuntime, settings, "dall-e-", token, apiKeyProvisional);
|
return this.LoadModels("dall-e-", token, apiKeyProvisional);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
var secretKey = apiKeyProvisional switch
|
||||||
{
|
{
|
||||||
not null => apiKeyProvisional,
|
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,
|
_ => null,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
using AIStudio.Provider.Anthropic;
|
|
||||||
using AIStudio.Provider.Fireworks;
|
|
||||||
using AIStudio.Provider.Mistral;
|
|
||||||
using AIStudio.Provider.OpenAI;
|
|
||||||
using AIStudio.Provider.SelfHosted;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider;
|
namespace AIStudio.Provider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -20,59 +14,4 @@ public enum Providers
|
|||||||
FIREWORKS = 5,
|
FIREWORKS = 5,
|
||||||
|
|
||||||
SELF_HOSTED = 4,
|
SELF_HOSTED = 4,
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for the provider enum.
|
|
||||||
/// </summary>
|
|
||||||
public static class ExtensionsProvider
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the human-readable name of the provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="provider">The provider.</param>
|
|
||||||
/// <returns>The human-readable name of the provider.</returns>
|
|
||||||
public static string ToName(this Providers provider) => provider switch
|
|
||||||
{
|
|
||||||
Providers.NONE => "No provider selected",
|
|
||||||
|
|
||||||
Providers.OPEN_AI => "OpenAI",
|
|
||||||
Providers.ANTHROPIC => "Anthropic",
|
|
||||||
Providers.MISTRAL => "Mistral",
|
|
||||||
|
|
||||||
Providers.FIREWORKS => "Fireworks.ai",
|
|
||||||
|
|
||||||
Providers.SELF_HOSTED => "Self-hosted",
|
|
||||||
|
|
||||||
_ => "Unknown",
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new provider instance based on the provider value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="providerSettings">The provider settings.</param>
|
|
||||||
/// <returns>The provider instance.</returns>
|
|
||||||
public static IProvider CreateProvider(this Settings.Provider providerSettings)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return providerSettings.UsedProvider switch
|
|
||||||
{
|
|
||||||
Providers.OPEN_AI => new ProviderOpenAI { InstanceName = providerSettings.InstanceName },
|
|
||||||
Providers.ANTHROPIC => new ProviderAnthropic { InstanceName = providerSettings.InstanceName },
|
|
||||||
Providers.MISTRAL => new ProviderMistral { InstanceName = providerSettings.InstanceName },
|
|
||||||
|
|
||||||
Providers.FIREWORKS => new ProviderFireworks { InstanceName = providerSettings.InstanceName },
|
|
||||||
|
|
||||||
Providers.SELF_HOSTED => new ProviderSelfHosted(providerSettings) { InstanceName = providerSettings.InstanceName },
|
|
||||||
|
|
||||||
_ => new NoProvider(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Failed to create provider: {e.Message}");
|
|
||||||
return new NoProvider();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
60
app/MindWork AI Studio/Provider/ProvidersExtensions.cs
Normal file
60
app/MindWork AI Studio/Provider/ProvidersExtensions.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using AIStudio.Provider.Anthropic;
|
||||||
|
using AIStudio.Provider.Fireworks;
|
||||||
|
using AIStudio.Provider.Mistral;
|
||||||
|
using AIStudio.Provider.OpenAI;
|
||||||
|
using AIStudio.Provider.SelfHosted;
|
||||||
|
|
||||||
|
namespace AIStudio.Provider;
|
||||||
|
|
||||||
|
public static class ProvidersExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the human-readable name of the provider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">The provider.</param>
|
||||||
|
/// <returns>The human-readable name of the provider.</returns>
|
||||||
|
public static string ToName(this Providers provider) => provider switch
|
||||||
|
{
|
||||||
|
Providers.NONE => "No provider selected",
|
||||||
|
|
||||||
|
Providers.OPEN_AI => "OpenAI",
|
||||||
|
Providers.ANTHROPIC => "Anthropic",
|
||||||
|
Providers.MISTRAL => "Mistral",
|
||||||
|
|
||||||
|
Providers.FIREWORKS => "Fireworks.ai",
|
||||||
|
|
||||||
|
Providers.SELF_HOSTED => "Self-hosted",
|
||||||
|
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new provider instance based on the provider value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="providerSettings">The provider settings.</param>
|
||||||
|
/// <param name="logger">The logger to use.</param>
|
||||||
|
/// <returns>The provider instance.</returns>
|
||||||
|
public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return providerSettings.UsedProvider switch
|
||||||
|
{
|
||||||
|
Providers.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = providerSettings.InstanceName },
|
||||||
|
Providers.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = providerSettings.InstanceName },
|
||||||
|
Providers.MISTRAL => new ProviderMistral(logger) { InstanceName = providerSettings.InstanceName },
|
||||||
|
|
||||||
|
Providers.FIREWORKS => new ProviderFireworks(logger) { InstanceName = providerSettings.InstanceName },
|
||||||
|
|
||||||
|
Providers.SELF_HOSTED => new ProviderSelfHosted(logger, providerSettings) { InstanceName = providerSettings.InstanceName },
|
||||||
|
|
||||||
|
_ => new NoProvider(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError($"Failed to create provider: {e.Message}");
|
||||||
|
return new NoProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,10 @@ using System.Text.Json;
|
|||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
using AIStudio.Provider.OpenAI;
|
using AIStudio.Provider.OpenAI;
|
||||||
using AIStudio.Settings;
|
|
||||||
|
|
||||||
namespace AIStudio.Provider.SelfHosted;
|
namespace AIStudio.Provider.SelfHosted;
|
||||||
|
|
||||||
public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvider($"{provider.Hostname}{provider.Host.BaseURL()}"), IProvider
|
public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provider) : BaseProvider($"{provider.Hostname}{provider.Host.BaseURL()}", logger), IProvider
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
|
||||||
{
|
{
|
||||||
@ -21,7 +20,8 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
|
|||||||
|
|
||||||
public string InstanceName { get; set; } = "Self-hosted";
|
public string InstanceName { get; set; } = "Self-hosted";
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> StreamChatCompletion(IJSRuntime jsRuntime, SettingsManager settings, Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
/// <inheritdoc />
|
||||||
|
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Prepare the system prompt:
|
// Prepare the system prompt:
|
||||||
var systemPrompt = new Message
|
var systemPrompt = new Message
|
||||||
@ -129,14 +129,14 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
|
|||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <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)
|
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
|
|
||||||
|
|
||||||
public async Task<IEnumerable<Provider.Model>> GetTextModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -162,14 +162,14 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Failed to load text models from self-hosted provider: {e.Message}");
|
this.logger.LogError($"Failed to load text models from self-hosted provider: {e.Message}");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<Provider.Model>> GetImageModels(IJSRuntime jsRuntime, SettingsManager settings, string? apiKeyProvisional = null, CancellationToken token = default)
|
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using AIStudio.Assistants.RewriteImprove;
|
|||||||
using AIStudio.Assistants.TextSummarizer;
|
using AIStudio.Assistants.TextSummarizer;
|
||||||
using AIStudio.Assistants.EMail;
|
using AIStudio.Assistants.EMail;
|
||||||
using AIStudio.Settings.DataModel;
|
using AIStudio.Settings.DataModel;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
using WritingStylesRewrite = AIStudio.Assistants.RewriteImprove.WritingStyles;
|
using WritingStylesRewrite = AIStudio.Assistants.RewriteImprove.WritingStyles;
|
||||||
using WritingStylesEMail = AIStudio.Assistants.EMail.WritingStyles;
|
using WritingStylesEMail = AIStudio.Assistants.EMail.WritingStyles;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Assistants.Agenda;
|
using AIStudio.Assistants.Agenda;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel;
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Assistants.EMail;
|
using AIStudio.Assistants.EMail;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel;
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel;
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
public sealed class DataGrammarSpelling
|
public sealed class DataGrammarSpelling
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Assistants.RewriteImprove;
|
using AIStudio.Assistants.RewriteImprove;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel;
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using AIStudio.Assistants.TextSummarizer;
|
using AIStudio.Assistants.TextSummarizer;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel;
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel;
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
public sealed class DataTranslation
|
public sealed class DataTranslation
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using AIStudio.Assistants.Coding;
|
using AIStudio.Assistants.Coding;
|
||||||
using AIStudio.Assistants.IconFinder;
|
using AIStudio.Assistants.IconFinder;
|
||||||
using AIStudio.Assistants.TextSummarizer;
|
using AIStudio.Assistants.TextSummarizer;
|
||||||
using AIStudio.Tools;
|
|
||||||
|
|
||||||
namespace AIStudio.Settings.DataModel.PreviousModels;
|
namespace AIStudio.Settings.DataModel.PreviousModels;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
using AIStudio.Provider;
|
|
||||||
using AIStudio.Settings.DataModel;
|
using AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
// ReSharper disable NotAccessedPositionalProperty.Local
|
// ReSharper disable NotAccessedPositionalProperty.Local
|
||||||
@ -11,7 +10,7 @@ namespace AIStudio.Settings;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The settings manager.
|
/// The settings manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SettingsManager
|
public sealed class SettingsManager(ILogger<SettingsManager> logger)
|
||||||
{
|
{
|
||||||
private const string SETTINGS_FILENAME = "settings.json";
|
private const string SETTINGS_FILENAME = "settings.json";
|
||||||
|
|
||||||
@ -20,6 +19,8 @@ public sealed class SettingsManager
|
|||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
Converters = { new JsonStringEnumConverter() },
|
Converters = { new JsonStringEnumConverter() },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private ILogger<SettingsManager> logger = logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The directory where the configuration files are stored.
|
/// The directory where the configuration files are stored.
|
||||||
@ -37,64 +38,6 @@ public sealed class SettingsManager
|
|||||||
public Data ConfigurationData { get; private set; } = new();
|
public Data ConfigurationData { get; private set; } = new();
|
||||||
|
|
||||||
private bool IsSetUp => !string.IsNullOrWhiteSpace(ConfigDirectory) && !string.IsNullOrWhiteSpace(DataDirectory);
|
private bool IsSetUp => !string.IsNullOrWhiteSpace(ConfigDirectory) && !string.IsNullOrWhiteSpace(DataDirectory);
|
||||||
|
|
||||||
#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>
|
|
||||||
/// Data structure for storing a secret response.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Success">True, when the secret was successfully stored.</param>
|
|
||||||
/// <param name="Issue">The issue, when the secret could not be stored.</param>
|
|
||||||
public readonly record struct StoreSecretResponse(bool Success, string Issue);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to store 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 store the API key for.</param>
|
|
||||||
/// <param name="key">The API key to store.</param>
|
|
||||||
/// <returns>The store secret response.</returns>
|
|
||||||
public async Task<StoreSecretResponse> SetAPIKey(IJSRuntime jsRuntime, IProvider provider, string key) => await jsRuntime.InvokeAsync<StoreSecretResponse>("window.__TAURI__.invoke", "store_secret", new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, key));
|
|
||||||
|
|
||||||
private readonly record struct DeleteSecretRequest(string Destination, string UserName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Data structure for deleting a secret response.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Success">True, when the secret was successfully deleted or not found.</param>
|
|
||||||
/// <param name="Issue">The issue, when the secret could not be deleted.</param>
|
|
||||||
/// <param name="WasEntryFound">True, when the entry was found and deleted.</param>
|
|
||||||
public readonly record struct DeleteSecretResponse(bool Success, string Issue, bool WasEntryFound);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to delete 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 delete the API key for.</param>
|
|
||||||
/// <returns>The delete secret response.</returns>
|
|
||||||
public async Task<DeleteSecretResponse> DeleteAPIKey(IJSRuntime jsRuntime, IProvider provider) => await jsRuntime.InvokeAsync<DeleteSecretResponse>("window.__TAURI__.invoke", "delete_secret", new DeleteSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the settings from the file system.
|
/// Loads the settings from the file system.
|
||||||
@ -102,12 +45,18 @@ public sealed class SettingsManager
|
|||||||
public async Task LoadSettings()
|
public async Task LoadSettings()
|
||||||
{
|
{
|
||||||
if(!this.IsSetUp)
|
if(!this.IsSetUp)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Cannot load settings, because the configuration is not set up yet.");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
||||||
if(!File.Exists(settingsPath))
|
if(!File.Exists(settingsPath))
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Cannot load settings, because the settings file does not exist.");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We read the `"Version": "V3"` line to determine the version of the settings file:
|
// We read the `"Version": "V3"` line to determine the version of the settings file:
|
||||||
await foreach (var line in File.ReadLinesAsync(settingsPath))
|
await foreach (var line in File.ReadLinesAsync(settingsPath))
|
||||||
{
|
{
|
||||||
@ -123,16 +72,16 @@ public sealed class SettingsManager
|
|||||||
Enum.TryParse(settingsVersionText, out Version settingsVersion);
|
Enum.TryParse(settingsVersionText, out Version settingsVersion);
|
||||||
if(settingsVersion is Version.UNKNOWN)
|
if(settingsVersion is Version.UNKNOWN)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: Unknown version of the settings file.");
|
this.logger.LogError("Unknown version of the settings file found.");
|
||||||
this.ConfigurationData = new();
|
this.ConfigurationData = new();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ConfigurationData = SettingsMigrations.Migrate(settingsVersion, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
|
this.ConfigurationData = SettingsMigrations.Migrate(this.logger, settingsVersion, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine("Error: Failed to read the version of the settings file.");
|
this.logger.LogError("Failed to read the version of the settings file.");
|
||||||
this.ConfigurationData = new();
|
this.ConfigurationData = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,14 +91,22 @@ public sealed class SettingsManager
|
|||||||
public async Task StoreSettings()
|
public async Task StoreSettings()
|
||||||
{
|
{
|
||||||
if(!this.IsSetUp)
|
if(!this.IsSetUp)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Cannot store settings, because the configuration is not set up yet.");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
||||||
if(!Directory.Exists(ConfigDirectory))
|
if(!Directory.Exists(ConfigDirectory))
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Creating the configuration directory.");
|
||||||
Directory.CreateDirectory(ConfigDirectory!);
|
Directory.CreateDirectory(ConfigDirectory!);
|
||||||
|
}
|
||||||
|
|
||||||
var settingsJson = JsonSerializer.Serialize(this.ConfigurationData, JSON_OPTIONS);
|
var settingsJson = JsonSerializer.Serialize(this.ConfigurationData, JSON_OPTIONS);
|
||||||
await File.WriteAllTextAsync(settingsPath, settingsJson);
|
await File.WriteAllTextAsync(settingsPath, settingsJson);
|
||||||
|
|
||||||
|
this.logger.LogInformation("Stored the settings to the file system.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InjectSpellchecking(Dictionary<string, object?> attributes) => attributes["spellcheck"] = this.ConfigurationData.App.EnableSpellchecking ? "true" : "false";
|
public void InjectSpellchecking(Dictionary<string, object?> attributes) => attributes["spellcheck"] = this.ConfigurationData.App.EnableSpellchecking ? "true" : "false";
|
||||||
|
@ -9,7 +9,7 @@ namespace AIStudio.Settings;
|
|||||||
|
|
||||||
public static class SettingsMigrations
|
public static class SettingsMigrations
|
||||||
{
|
{
|
||||||
public static Data Migrate(Version previousVersion, string configData, JsonSerializerOptions jsonOptions)
|
public static Data Migrate(ILogger<SettingsManager> logger, Version previousVersion, string configData, JsonSerializerOptions jsonOptions)
|
||||||
{
|
{
|
||||||
switch (previousVersion)
|
switch (previousVersion)
|
||||||
{
|
{
|
||||||
@ -17,41 +17,41 @@ public static class SettingsMigrations
|
|||||||
var configV1 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
var configV1 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
||||||
if (configV1 is null)
|
if (configV1 is null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: failed to parse the configuration. Using default values.");
|
logger.LogError("Failed to parse the v1 configuration. Using default values.");
|
||||||
return new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
configV1 = MigrateV1ToV2(configV1);
|
configV1 = MigrateV1ToV2(logger, configV1);
|
||||||
configV1 = MigrateV2ToV3(configV1);
|
configV1 = MigrateV2ToV3(logger, configV1);
|
||||||
return MigrateV3ToV4(configV1);
|
return MigrateV3ToV4(logger, configV1);
|
||||||
|
|
||||||
case Version.V2:
|
case Version.V2:
|
||||||
var configV2 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
var configV2 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
||||||
if (configV2 is null)
|
if (configV2 is null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: failed to parse the configuration. Using default values.");
|
logger.LogError("Failed to parse the v2 configuration. Using default values.");
|
||||||
return new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
configV2 = MigrateV2ToV3(configV2);
|
configV2 = MigrateV2ToV3(logger, configV2);
|
||||||
return MigrateV3ToV4(configV2);
|
return MigrateV3ToV4(logger, configV2);
|
||||||
|
|
||||||
case Version.V3:
|
case Version.V3:
|
||||||
var configV3 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
var configV3 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
||||||
if (configV3 is null)
|
if (configV3 is null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: failed to parse the configuration. Using default values.");
|
logger.LogError("Failed to parse the v3 configuration. Using default values.");
|
||||||
return new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
return MigrateV3ToV4(configV3);
|
return MigrateV3ToV4(logger, configV3);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Console.WriteLine("No configuration migration needed.");
|
logger.LogInformation("No configuration migration is needed.");
|
||||||
var configV4 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
|
var configV4 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
|
||||||
if (configV4 is null)
|
if (configV4 is null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: failed to parse the configuration. Using default values.");
|
logger.LogError("Failed to parse the v4 configuration. Using default values.");
|
||||||
return new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ public static class SettingsMigrations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataV1V3 MigrateV1ToV2(DataV1V3 previousData)
|
private static DataV1V3 MigrateV1ToV2(ILogger<SettingsManager> logger, DataV1V3 previousData)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// In v1 we had no self-hosted providers. Thus, we had no hostnames.
|
// In v1 we had no self-hosted providers. Thus, we had no hostnames.
|
||||||
//
|
//
|
||||||
|
|
||||||
Console.WriteLine("Migrating from v1 to v2...");
|
logger.LogInformation("Migrating from v1 to v2...");
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Version = Version.V2,
|
Version = Version.V2,
|
||||||
@ -81,14 +81,14 @@ public static class SettingsMigrations
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataV1V3 MigrateV2ToV3(DataV1V3 previousData)
|
private static DataV1V3 MigrateV2ToV3(ILogger<SettingsManager> logger, DataV1V3 previousData)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// In v2, self-hosted providers had no host (LM Studio, llama.cpp, ollama, etc.)
|
// In v2, self-hosted providers had no host (LM Studio, llama.cpp, ollama, etc.)
|
||||||
//
|
//
|
||||||
|
|
||||||
Console.WriteLine("Migrating from v2 to v3...");
|
logger.LogInformation("Migrating from v2 to v3...");
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Version = Version.V3,
|
Version = Version.V3,
|
||||||
@ -110,14 +110,14 @@ public static class SettingsMigrations
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Data MigrateV3ToV4(DataV1V3 previousConfig)
|
private static Data MigrateV3ToV4(ILogger<SettingsManager> logger, DataV1V3 previousConfig)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// We grouped the settings into different categories.
|
// We grouped the settings into different categories.
|
||||||
//
|
//
|
||||||
|
|
||||||
Console.WriteLine("Migrating from v3 to v4...");
|
logger.LogInformation("Migrating from v3 to v4...");
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Version = Version.V4,
|
Version = Version.V4,
|
||||||
|
13
app/MindWork AI Studio/Tools/EncryptedText.cs
Normal file
13
app/MindWork AI Studio/Tools/EncryptedText.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Security;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(EncryptedTextJsonConverter))]
|
||||||
|
public readonly record struct EncryptedText(string EncryptedData)
|
||||||
|
{
|
||||||
|
public EncryptedText() : this(string.Empty)
|
||||||
|
{
|
||||||
|
throw new SecurityException("Please provide the encrypted data.");
|
||||||
|
}
|
||||||
|
}
|
8
app/MindWork AI Studio/Tools/EncryptedTextExtensions.cs
Normal file
8
app/MindWork AI Studio/Tools/EncryptedTextExtensions.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public static class EncryptedTextExtensions
|
||||||
|
{
|
||||||
|
public static async Task<EncryptedText> Encrypt(this string data, Encryption encryption) => await encryption.Encrypt(data);
|
||||||
|
|
||||||
|
public static async Task<string> Decrypt(this EncryptedText encryptedText, Encryption encryption) => await encryption.Decrypt(encryptedText);
|
||||||
|
}
|
27
app/MindWork AI Studio/Tools/EncryptedTextJsonConverter.cs
Normal file
27
app/MindWork AI Studio/Tools/EncryptedTextJsonConverter.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public sealed class EncryptedTextJsonConverter : JsonConverter<EncryptedText>
|
||||||
|
{
|
||||||
|
#region Overrides of JsonConverter<EncryptedText>
|
||||||
|
|
||||||
|
public override EncryptedText Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (reader.TokenType is JsonTokenType.String)
|
||||||
|
{
|
||||||
|
var value = reader.GetString()!;
|
||||||
|
return new EncryptedText(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new JsonException($"Unexpected token type when parsing EncryptedText. Expected {JsonTokenType.String}, but got {reader.TokenType}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, EncryptedText value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.EncryptedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
142
app/MindWork AI Studio/Tools/Encryption.cs
Normal file
142
app/MindWork AI Studio/Tools/Encryption.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public sealed class Encryption(ILogger<Encryption> logger, byte[] secretPassword, byte[] secretKeySalt)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The number of iterations to derive the key and IV from the password. For a password manager
|
||||||
|
/// where the user has to enter their primary password, 100 iterations would be too few and
|
||||||
|
/// insecure. Here, the use case is different: We generate a 512-byte long and cryptographically
|
||||||
|
/// secure password at every start. This password already contains enough entropy. In our case,
|
||||||
|
/// we need key and IV primarily because AES, with the algorithms we chose, requires a fixed key
|
||||||
|
/// length, and our password is too long.
|
||||||
|
/// </summary>
|
||||||
|
private const int ITERATIONS = 100;
|
||||||
|
|
||||||
|
private readonly byte[] key = new byte[32];
|
||||||
|
private readonly byte[] iv = new byte[16];
|
||||||
|
|
||||||
|
public async Task Initialize()
|
||||||
|
{
|
||||||
|
logger.LogInformation("Initializing encryption service...");
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
if (secretPassword.Length != 512)
|
||||||
|
{
|
||||||
|
logger.LogError($"The secret password must be 512 bytes long. It was {secretPassword.Length} bytes long.");
|
||||||
|
throw new CryptographicException("The secret password must be 512 bytes long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(secretKeySalt.Length != 16)
|
||||||
|
{
|
||||||
|
logger.LogError($"The salt data must be 16 bytes long. It was {secretKeySalt.Length} bytes long.");
|
||||||
|
throw new CryptographicException("The salt data must be 16 bytes long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive key and iv vector: the operations take several seconds. Thus, using a task:
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
using var keyVectorObj = new Rfc2898DeriveBytes(secretPassword, secretKeySalt, ITERATIONS, HashAlgorithmName.SHA512);
|
||||||
|
var keyBytes = keyVectorObj.GetBytes(32); // the max valid key length = 256 bit = 32 bytes
|
||||||
|
var ivBytes = keyVectorObj.GetBytes(16); // the only valid block size = 128 bit = 16 bytes
|
||||||
|
|
||||||
|
Array.Copy(keyBytes, this.key, this.key.Length);
|
||||||
|
Array.Copy(ivBytes, this.iv, this.iv.Length);
|
||||||
|
});
|
||||||
|
|
||||||
|
var initDuration = stopwatch.Elapsed;
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
logger.LogInformation($"Encryption service initialized in {initDuration.TotalMilliseconds} milliseconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EncryptedText> Encrypt(string data)
|
||||||
|
{
|
||||||
|
// Create AES encryption:
|
||||||
|
using var aes = Aes.Create();
|
||||||
|
aes.Padding = PaddingMode.PKCS7;
|
||||||
|
aes.Key = this.key;
|
||||||
|
aes.IV = this.iv;
|
||||||
|
aes.Mode = CipherMode.CBC;
|
||||||
|
|
||||||
|
using var encryption = aes.CreateEncryptor();
|
||||||
|
|
||||||
|
// Copy the given string data into a memory stream:
|
||||||
|
await using var plainDataStream = new MemoryStream(Encoding.UTF8.GetBytes(data));
|
||||||
|
|
||||||
|
// A memory stream for the final, encrypted data:
|
||||||
|
await using var encryptedAndEncodedData = new MemoryStream();
|
||||||
|
|
||||||
|
// A base64 stream for the encoding:
|
||||||
|
await using var base64Stream = new CryptoStream(encryptedAndEncodedData, new ToBase64Transform(), CryptoStreamMode.Write);
|
||||||
|
|
||||||
|
// Write the salt into the base64 stream:
|
||||||
|
await base64Stream.WriteAsync(secretKeySalt);
|
||||||
|
|
||||||
|
// Create the encryption stream:
|
||||||
|
await using var cryptoStream = new CryptoStream(base64Stream, encryption, CryptoStreamMode.Write);
|
||||||
|
|
||||||
|
// Write the payload into the encryption stream:
|
||||||
|
await plainDataStream.CopyToAsync(cryptoStream);
|
||||||
|
|
||||||
|
// Flush the final block. Please note that it is not enough to call the regular flush method.
|
||||||
|
await cryptoStream.FlushFinalBlockAsync();
|
||||||
|
|
||||||
|
// Convert the base64 encoded data back into a string. Uses GetBuffer due to the advantage that
|
||||||
|
// it does not create another copy of the data. ToArray would create another copy of the data.
|
||||||
|
return new EncryptedText(Encoding.ASCII.GetString(encryptedAndEncodedData.GetBuffer()[..(int)encryptedAndEncodedData.Length]));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.EncryptedData));
|
||||||
|
|
||||||
|
// Wrap around the base64 decoder stream:
|
||||||
|
await using var base64Stream = new CryptoStream(encodedEncryptedStream, new FromBase64Transform(), CryptoStreamMode.Read);
|
||||||
|
|
||||||
|
// A buffer for the salt's bytes:
|
||||||
|
var readSaltBytes = new byte[16]; // 16 bytes = Guid
|
||||||
|
|
||||||
|
// Read the salt's bytes out of the stream:
|
||||||
|
var readBytes = 0;
|
||||||
|
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
|
||||||
|
while(readBytes < readSaltBytes.Length && !cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
readBytes += await base64Stream.ReadAsync(readSaltBytes, readBytes, readSaltBytes.Length - readBytes, cts.Token);
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(60), cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the salt bytes:
|
||||||
|
if(!readSaltBytes.SequenceEqual(secretKeySalt))
|
||||||
|
{
|
||||||
|
logger.LogError("The salt bytes do not match. The data is corrupted or tampered.");
|
||||||
|
throw new CryptographicException("The salt bytes do not match. The data is corrupted or tampered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create AES decryption:
|
||||||
|
using var aes = Aes.Create();
|
||||||
|
aes.Padding = PaddingMode.PKCS7;
|
||||||
|
aes.Key = this.key;
|
||||||
|
aes.IV = this.iv;
|
||||||
|
|
||||||
|
using var decryption = aes.CreateDecryptor();
|
||||||
|
|
||||||
|
// A memory stream for the final, decrypted data:
|
||||||
|
await using var decryptedData = new MemoryStream();
|
||||||
|
|
||||||
|
// The crypto stream:
|
||||||
|
await using var cryptoStream = new CryptoStream(base64Stream, decryption, CryptoStreamMode.Read);
|
||||||
|
|
||||||
|
// Reads all remaining data through the decrypt stream. Note that this operation
|
||||||
|
// starts at the current position, i.e., after the salt bytes:
|
||||||
|
await cryptoStream.CopyToAsync(decryptedData);
|
||||||
|
|
||||||
|
// Convert the decrypted data back into a string. Uses GetBuffer due to the advantage that
|
||||||
|
// it does not create another copy of the data. ToArray would create another copy of the data.
|
||||||
|
return Encoding.UTF8.GetString(decryptedData.GetBuffer()[..(int)decryptedData.Length]);
|
||||||
|
}
|
||||||
|
}
|
18
app/MindWork AI Studio/Tools/HttpRequestHeadersExtensions.cs
Normal file
18
app/MindWork AI Studio/Tools/HttpRequestHeadersExtensions.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public static class HttpRequestHeadersExtensions
|
||||||
|
{
|
||||||
|
private static readonly string API_TOKEN;
|
||||||
|
|
||||||
|
static HttpRequestHeadersExtensions()
|
||||||
|
{
|
||||||
|
API_TOKEN = Program.API_TOKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddApiToken(this HttpRequestHeaders headers)
|
||||||
|
{
|
||||||
|
headers.Add("token", API_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
namespace AIStudio.Tools;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calling Rust functions.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class Rust
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to copy the given text to the clipboard.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
|
||||||
/// <param name="snackbar">The snackbar to show the result.</param>
|
|
||||||
/// <param name="text">The text to copy to the clipboard.</param>
|
|
||||||
public async Task CopyText2Clipboard(IJSRuntime jsRuntime, ISnackbar snackbar, string text)
|
|
||||||
{
|
|
||||||
var response = await jsRuntime.InvokeAsync<SetClipboardResponse>("window.__TAURI__.invoke", "set_clipboard", new SetClipboardText(text));
|
|
||||||
var msg = response.Success switch
|
|
||||||
{
|
|
||||||
true => "Successfully copied text to clipboard!",
|
|
||||||
false => $"Failed to copy text to clipboard: {response.Issue}",
|
|
||||||
};
|
|
||||||
|
|
||||||
var severity = response.Success switch
|
|
||||||
{
|
|
||||||
true => Severity.Success,
|
|
||||||
false => Severity.Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
snackbar.Add(msg, severity, config =>
|
|
||||||
{
|
|
||||||
config.Icon = Icons.Material.Filled.ContentCopy;
|
|
||||||
config.IconSize = Size.Large;
|
|
||||||
config.IconColor = severity switch
|
|
||||||
{
|
|
||||||
Severity.Success => Color.Success,
|
|
||||||
Severity.Error => Color.Error,
|
|
||||||
|
|
||||||
_ => Color.Default,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<UpdateResponse> CheckForUpdate(IJSRuntime jsRuntime)
|
|
||||||
{
|
|
||||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(16));
|
|
||||||
return await jsRuntime.InvokeAsync<UpdateResponse>("window.__TAURI__.invoke", cts.Token, "check_for_update");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InstallUpdate(IJSRuntime jsRuntime)
|
|
||||||
{
|
|
||||||
var cts = new CancellationTokenSource();
|
|
||||||
await jsRuntime.InvokeVoidAsync("window.__TAURI__.invoke", cts.Token, "install_update");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data structure for deleting a secret response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Success">True, when the secret was successfully deleted or not found.</param>
|
||||||
|
/// <param name="Issue">The issue, when the secret could not be deleted.</param>
|
||||||
|
/// <param name="WasEntryFound">True, when the entry was found and deleted.</param>
|
||||||
|
public readonly record struct DeleteSecretResponse(bool Success, string Issue, bool WasEntryFound);
|
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);
|
3
app/MindWork AI Studio/Tools/Rust/SelectSecretRequest.cs
Normal file
3
app/MindWork AI Studio/Tools/Rust/SelectSecretRequest.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
|
public readonly record struct SelectSecretRequest(string Destination, string UserName);
|
@ -1,4 +1,4 @@
|
|||||||
namespace AIStudio.Tools;
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The response from the set clipboard operation.
|
/// The response from the set clipboard operation.
|
3
app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs
Normal file
3
app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
|
public readonly record struct StoreSecretRequest(string Destination, string UserName, EncryptedText Secret);
|
8
app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs
Normal file
8
app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data structure for storing a secret response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Success">True, when the secret was successfully stored.</param>
|
||||||
|
/// <param name="Issue">The issue, when the secret could not be stored.</param>
|
||||||
|
public readonly record struct StoreSecretResponse(bool Success, string Issue);
|
@ -1,6 +1,4 @@
|
|||||||
using System.Text.Json.Serialization;
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
namespace AIStudio.Tools;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The response of the update check.
|
/// The response of the update check.
|
||||||
@ -9,8 +7,8 @@ namespace AIStudio.Tools;
|
|||||||
/// <param name="NewVersion">The new version, when available.</param>
|
/// <param name="NewVersion">The new version, when available.</param>
|
||||||
/// <param name="Changelog">The changelog of the new version, when available.</param>
|
/// <param name="Changelog">The changelog of the new version, when available.</param>
|
||||||
public readonly record struct UpdateResponse(
|
public readonly record struct UpdateResponse(
|
||||||
[property:JsonPropertyName("update_is_available")] bool UpdateIsAvailable,
|
bool UpdateIsAvailable,
|
||||||
[property:JsonPropertyName("error")] bool Error,
|
bool Error,
|
||||||
[property:JsonPropertyName("new_version")] string NewVersion,
|
string NewVersion,
|
||||||
string Changelog
|
string Changelog
|
||||||
);
|
);
|
331
app/MindWork AI Studio/Tools/RustService.cs
Normal file
331
app/MindWork AI Studio/Tools/RustService.cs
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using AIStudio.Provider;
|
||||||
|
using AIStudio.Tools.Rust;
|
||||||
|
|
||||||
|
// ReSharper disable NotAccessedPositionalProperty.Local
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calling Rust functions.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RustService : IDisposable
|
||||||
|
{
|
||||||
|
private readonly HttpClient http;
|
||||||
|
|
||||||
|
private readonly JsonSerializerOptions jsonRustSerializerOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
|
};
|
||||||
|
|
||||||
|
private ILogger<RustService>? logger;
|
||||||
|
private Encryption? encryptor;
|
||||||
|
|
||||||
|
private readonly string apiPort;
|
||||||
|
private readonly string certificateFingerprint;
|
||||||
|
|
||||||
|
public RustService(string apiPort, string certificateFingerprint)
|
||||||
|
{
|
||||||
|
this.apiPort = apiPort;
|
||||||
|
this.certificateFingerprint = certificateFingerprint;
|
||||||
|
var certificateValidationHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (_, certificate, _, _) =>
|
||||||
|
{
|
||||||
|
if(certificate is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var currentCertificateFingerprint = certificate.GetCertHashString(HashAlgorithmName.SHA256);
|
||||||
|
return currentCertificateFingerprint == certificateFingerprint;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http = new HttpClient(certificateValidationHandler)
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri($"https://127.0.0.1:{apiPort}"),
|
||||||
|
DefaultRequestVersion = Version.Parse("2.0"),
|
||||||
|
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.DefaultRequestHeaders.AddApiToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLogger(ILogger<RustService> logService)
|
||||||
|
{
|
||||||
|
this.logger = logService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEncryptor(Encryption encryptionService)
|
||||||
|
{
|
||||||
|
this.encryptor = encryptionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetAppPort()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Trying to get app port from Rust runtime...");
|
||||||
|
|
||||||
|
//
|
||||||
|
// Note I: In the production environment, the Rust runtime is already running
|
||||||
|
// and listening on the given port. In the development environment, the IDE
|
||||||
|
// starts the Rust runtime in parallel with the .NET runtime. Since the
|
||||||
|
// Rust runtime needs some time to start, we have to wait for it to be ready.
|
||||||
|
//
|
||||||
|
const int MAX_TRIES = 160;
|
||||||
|
var tris = 0;
|
||||||
|
var wait4Try = TimeSpan.FromMilliseconds(250);
|
||||||
|
var url = new Uri($"https://127.0.0.1:{this.apiPort}/system/dotnet/port");
|
||||||
|
while (tris++ < MAX_TRIES)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Note II: We use a new HttpClient instance for each try to avoid
|
||||||
|
// .NET is caching the result. When we use the same HttpClient
|
||||||
|
// instance, we would always get the same result (403 forbidden),
|
||||||
|
// without even trying to connect to the Rust server.
|
||||||
|
//
|
||||||
|
|
||||||
|
using var initialHttp = new HttpClient(new HttpClientHandler
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Note III: We have to create also a new HttpClientHandler instance
|
||||||
|
// for each try to avoid .NET is caching the result. This is necessary
|
||||||
|
// because it gets disposed when the HttpClient instance gets disposed.
|
||||||
|
//
|
||||||
|
ServerCertificateCustomValidationCallback = (_, certificate, _, _) =>
|
||||||
|
{
|
||||||
|
if(certificate is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var currentCertificateFingerprint = certificate.GetCertHashString(HashAlgorithmName.SHA256);
|
||||||
|
return currentCertificateFingerprint == this.certificateFingerprint;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initialHttp.DefaultRequestVersion = Version.Parse("2.0");
|
||||||
|
initialHttp.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
|
||||||
|
initialHttp.DefaultRequestHeaders.AddApiToken();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await initialHttp.GetAsync(url);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Try {tris}/{MAX_TRIES} to get the app port from Rust runtime");
|
||||||
|
await Task.Delay(wait4Try);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var appPortContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var appPort = int.Parse(appPortContent);
|
||||||
|
Console.WriteLine($"Received app port from Rust runtime: '{appPort}'");
|
||||||
|
return appPort;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: Was not able to get the app port from Rust runtime: '{e.Message}'");
|
||||||
|
Console.WriteLine(e.InnerException);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Failed to receive the app port from Rust runtime.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AppIsReady()
|
||||||
|
{
|
||||||
|
const string URL = "/system/dotnet/ready";
|
||||||
|
this.logger!.LogInformation("Notifying Rust runtime that the app is ready.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await this.http.GetAsync(URL);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
this.logger!.LogError($"Failed to notify Rust runtime that the app is ready: '{response.StatusCode}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.logger!.LogError(e, "Failed to notify the Rust runtime that the app is ready.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetConfigDirectory()
|
||||||
|
{
|
||||||
|
var response = await this.http.GetAsync("/system/directories/config");
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
this.logger!.LogError($"Failed to get the config directory from Rust: '{response.StatusCode}'");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetDataDirectory()
|
||||||
|
{
|
||||||
|
var response = await this.http.GetAsync("/system/directories/data");
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
this.logger!.LogError($"Failed to get the data directory from Rust: '{response.StatusCode}'");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to copy the given text to the clipboard.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="snackbar">The snackbar to show the result.</param>
|
||||||
|
/// <param name="text">The text to copy to the clipboard.</param>
|
||||||
|
public async Task CopyText2Clipboard(ISnackbar snackbar, string text)
|
||||||
|
{
|
||||||
|
var message = "Successfully copied the text to your clipboard";
|
||||||
|
var iconColor = Color.Error;
|
||||||
|
var severity = Severity.Error;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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}'");
|
||||||
|
message = "Failed to copy the text to your clipboard.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = await response.Content.ReadFromJsonAsync<SetClipboardResponse>();
|
||||||
|
if (!state.Success)
|
||||||
|
{
|
||||||
|
this.logger!.LogError("Failed to copy the text to the clipboard.");
|
||||||
|
message = "Failed to copy the text to your clipboard.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
iconColor = Color.Success;
|
||||||
|
severity = Severity.Success;
|
||||||
|
this.logger!.LogDebug("Successfully copied the text to the clipboard.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
snackbar.Add(message, severity, config =>
|
||||||
|
{
|
||||||
|
config.Icon = Icons.Material.Filled.ContentCopy;
|
||||||
|
config.IconSize = Size.Large;
|
||||||
|
config.IconColor = iconColor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UpdateResponse> CheckForUpdate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(16));
|
||||||
|
return await this.http.GetFromJsonAsync<UpdateResponse>("/updates/check", cts.Token);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.logger!.LogError(e, "Failed to check for an update.");
|
||||||
|
return new UpdateResponse
|
||||||
|
{
|
||||||
|
Error = true,
|
||||||
|
UpdateIsAvailable = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InstallUpdate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
await this.http.GetAsync("/updates/install", cts.Token);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName);
|
||||||
|
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to store the API key for the given provider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">The provider to store the API key for.</param>
|
||||||
|
/// <param name="key">The API key to store.</param>
|
||||||
|
/// <returns>The store secret response.</returns>
|
||||||
|
public async Task<StoreSecretResponse> SetAPIKey(IProvider provider, string key)
|
||||||
|
{
|
||||||
|
var encryptedKey = await this.encryptor!.Encrypt(key);
|
||||||
|
var request = new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, encryptedKey);
|
||||||
|
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
|
||||||
|
if (!result.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
|
||||||
|
return new StoreSecretResponse(false, "Failed to get the API key due to an API issue.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>();
|
||||||
|
if (!state.Success)
|
||||||
|
this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}': '{state.Issue}'");
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to delete the API key for the given provider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">The provider to delete the API key for.</param>
|
||||||
|
/// <returns>The delete secret response.</returns>
|
||||||
|
public async Task<DeleteSecretResponse> 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<DeleteSecretResponse>();
|
||||||
|
if (!state.Success)
|
||||||
|
this.logger!.LogError($"Failed to delete the API key for provider '{provider.Id}': '{state.Issue}'");
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.http.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
@ -6,17 +6,15 @@ namespace AIStudio.Tools.Services;
|
|||||||
/// Wire up the clipboard service to copy Markdown to the clipboard.
|
/// Wire up the clipboard service to copy Markdown to the clipboard.
|
||||||
/// We use our own Rust-based clipboard service for this.
|
/// We use our own Rust-based clipboard service for this.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MarkdownClipboardService(Rust rust, IJSRuntime jsRuntime, ISnackbar snackbar) : IMudMarkdownClipboardService
|
public sealed class MarkdownClipboardService(RustService rust, ISnackbar snackbar) : IMudMarkdownClipboardService
|
||||||
{
|
{
|
||||||
private IJSRuntime JsRuntime { get; } = jsRuntime;
|
|
||||||
|
|
||||||
private ISnackbar Snackbar { get; } = snackbar;
|
private ISnackbar Snackbar { get; } = snackbar;
|
||||||
|
|
||||||
private Rust Rust { get; } = rust;
|
private RustService Rust { get; } = rust;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets called when the user wants to copy the markdown to the clipboard.
|
/// Gets called when the user wants to copy the Markdown to the clipboard.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">The Markdown text to copy.</param>
|
/// <param name="text">The Markdown text to copy.</param>
|
||||||
public async ValueTask CopyToClipboardAsync(string text) => await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, text);
|
public async ValueTask CopyToClipboardAsync(string text) => await this.Rust.CopyText2Clipboard(this.Snackbar, text);
|
||||||
}
|
}
|
@ -3,11 +3,13 @@ using AIStudio.Settings.DataModel;
|
|||||||
|
|
||||||
namespace AIStudio.Tools.Services;
|
namespace AIStudio.Tools.Services;
|
||||||
|
|
||||||
public class TemporaryChatService(SettingsManager settingsManager) : BackgroundService
|
public class TemporaryChatService(ILogger<TemporaryChatService> logger, SettingsManager settingsManager) : BackgroundService
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromDays(1);
|
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromDays(1);
|
||||||
private static bool IS_INITIALIZED;
|
private static bool IS_INITIALIZED;
|
||||||
|
|
||||||
|
private readonly ILogger<TemporaryChatService> logger = logger;
|
||||||
|
|
||||||
#region Overrides of BackgroundService
|
#region Overrides of BackgroundService
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
@ -15,10 +17,12 @@ public class TemporaryChatService(SettingsManager settingsManager) : BackgroundS
|
|||||||
while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
|
while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
|
||||||
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
|
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
|
||||||
|
|
||||||
|
this.logger.LogInformation("The temporary chat maintenance service was initialized.");
|
||||||
|
|
||||||
await settingsManager.LoadSettings();
|
await settingsManager.LoadSettings();
|
||||||
if(settingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy is WorkspaceStorageTemporaryMaintenancePolicy.NO_AUTOMATIC_MAINTENANCE)
|
if(settingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy is WorkspaceStorageTemporaryMaintenancePolicy.NO_AUTOMATIC_MAINTENANCE)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Automatic maintenance of temporary chat storage is disabled. Exiting maintenance service.");
|
this.logger.LogWarning("Automatic maintenance of temporary chat storage is disabled. Exiting maintenance service.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,10 +38,14 @@ public class TemporaryChatService(SettingsManager settingsManager) : BackgroundS
|
|||||||
|
|
||||||
private Task StartMaintenance()
|
private Task StartMaintenance()
|
||||||
{
|
{
|
||||||
|
this.logger.LogInformation("Starting maintenance of temporary chat storage.");
|
||||||
var temporaryDirectories = Path.Join(SettingsManager.DataDirectory, "tempChats");
|
var temporaryDirectories = Path.Join(SettingsManager.DataDirectory, "tempChats");
|
||||||
if(!Directory.Exists(temporaryDirectories))
|
if(!Directory.Exists(temporaryDirectories))
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Temporary chat storage directory does not exist. End maintenance.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories))
|
foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories))
|
||||||
{
|
{
|
||||||
var chatPath = Path.Join(tempChatDirPath, "thread.json");
|
var chatPath = Path.Join(tempChatDirPath, "thread.json");
|
||||||
@ -59,9 +67,13 @@ public class TemporaryChatService(SettingsManager settingsManager) : BackgroundS
|
|||||||
};
|
};
|
||||||
|
|
||||||
if(deleteChat)
|
if(deleteChat)
|
||||||
|
{
|
||||||
|
this.logger.LogInformation($"Deleting temporary chat storage directory '{tempChatDirPath}' due to maintenance policy.");
|
||||||
Directory.Delete(tempChatDirPath, true);
|
Directory.Delete(tempChatDirPath, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.LogInformation("Finished maintenance of temporary chat storage.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,20 +7,16 @@ namespace AIStudio.Tools.Services;
|
|||||||
|
|
||||||
public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||||
{
|
{
|
||||||
// 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_INITIALIZED;
|
private static bool IS_INITIALIZED;
|
||||||
private static ISnackbar? SNACKBAR;
|
private static ISnackbar? SNACKBAR;
|
||||||
|
|
||||||
private readonly SettingsManager settingsManager;
|
private readonly SettingsManager settingsManager;
|
||||||
private readonly MessageBus messageBus;
|
private readonly MessageBus messageBus;
|
||||||
private readonly Rust rust;
|
private readonly RustService rust;
|
||||||
|
|
||||||
private TimeSpan updateInterval;
|
private TimeSpan updateInterval;
|
||||||
|
|
||||||
public UpdateService(MessageBus messageBus, SettingsManager settingsManager, Rust rust)
|
public UpdateService(MessageBus messageBus, SettingsManager settingsManager, RustService rust)
|
||||||
{
|
{
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.messageBus = messageBus;
|
this.messageBus = messageBus;
|
||||||
@ -96,7 +92,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
|||||||
if(!IS_INITIALIZED)
|
if(!IS_INITIALIZED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var response = await this.rust.CheckForUpdate(JS_RUNTIME!);
|
var response = await this.rust.CheckForUpdate();
|
||||||
if (response.UpdateIsAvailable)
|
if (response.UpdateIsAvailable)
|
||||||
{
|
{
|
||||||
await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
|
await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
|
||||||
@ -115,10 +111,9 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetBlazorDependencies(IJSRuntime jsRuntime, ISnackbar snackbar)
|
public static void SetBlazorDependencies(ISnackbar snackbar)
|
||||||
{
|
{
|
||||||
SNACKBAR = snackbar;
|
SNACKBAR = snackbar;
|
||||||
JS_RUNTIME = jsRuntime;
|
|
||||||
IS_INITIALIZED = true;
|
IS_INITIALIZED = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
|
23
app/MindWork AI Studio/Tools/TerminalLogger.cs
Normal file
23
app/MindWork AI Studio/Tools/TerminalLogger.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging.Console;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools;
|
||||||
|
|
||||||
|
public sealed class TerminalLogger() : ConsoleFormatter(FORMATTER_NAME)
|
||||||
|
{
|
||||||
|
public const string FORMATTER_NAME = "AI Studio Terminal Logger";
|
||||||
|
|
||||||
|
public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter)
|
||||||
|
{
|
||||||
|
var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
|
||||||
|
var timestamp = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||||
|
var logLevel = logEntry.LogLevel.ToString();
|
||||||
|
var category = logEntry.Category;
|
||||||
|
|
||||||
|
textWriter.Write($"=> {timestamp} [{logLevel}] {category}: {message}");
|
||||||
|
if (logEntry.Exception is not null)
|
||||||
|
textWriter.Write($" Exception was = {logEntry.Exception}");
|
||||||
|
|
||||||
|
textWriter.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +0,0 @@
|
|||||||
# v0.8.13, build 175
|
|
||||||
- Upgraded `keyring` dependency to v3.2.0.
|
|
4
app/MindWork AI Studio/wwwroot/changelog/v0.9.0.md
Normal file
4
app/MindWork AI Studio/wwwroot/changelog/v0.9.0.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# v0.9.0, build 175 (2024-09-01 18:04 UTC)
|
||||||
|
- Upgraded `keyring` dependency to v3.2.0.
|
||||||
|
- Refactored the interprocess communication (IPC) to use a new runtime API for better performance, stability, extensibility, maintainability, and security. This is the foundation for upcoming features and improvements, such as performant RAG and scripting support.
|
||||||
|
- Refactored the logging system. Now, the logging of all systems is unified.
|
@ -1,9 +1,9 @@
|
|||||||
0.8.12
|
0.9.0
|
||||||
2024-08-24 08:30:01 UTC
|
2024-09-01 18:04:01 UTC
|
||||||
174
|
175
|
||||||
8.0.108 (commit 665a05cea7)
|
8.0.108 (commit 665a05cea7)
|
||||||
8.0.8 (commit 08338fcaa5)
|
8.0.8 (commit 08338fcaa5)
|
||||||
1.80.1 (commit 3f5fd8dd4)
|
1.80.1 (commit 3f5fd8dd4)
|
||||||
7.6.0
|
7.6.0
|
||||||
1.7.1
|
1.7.1
|
||||||
2d82fcc5e16, release
|
a1446103d70, release
|
||||||
|
603
runtime/Cargo.lock
generated
603
runtime/Cargo.lock
generated
@ -17,6 +17,17 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@ -80,6 +91,39 @@ dependencies = [
|
|||||||
"x11rb",
|
"x11rb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-stream"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream-impl",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-stream-impl"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atk"
|
name = "atk"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
@ -104,6 +148,21 @@ dependencies = [
|
|||||||
"system-deps 6.2.2",
|
"system-deps 6.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@ -149,6 +208,12 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "binascii"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@ -185,6 +250,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block2"
|
name = "block2"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -292,6 +366,15 @@ dependencies = [
|
|||||||
"toml 0.7.8",
|
"toml 0.7.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cbc"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.6"
|
version = "1.1.6"
|
||||||
@ -353,6 +436,16 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "5.4.0"
|
version = "5.4.0"
|
||||||
@ -414,6 +507,17 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -648,6 +752,39 @@ dependencies = [
|
|||||||
"syn 2.0.72",
|
"syn 2.0.72",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "devise"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d"
|
||||||
|
dependencies = [
|
||||||
|
"devise_codegen",
|
||||||
|
"devise_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "devise_codegen"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867"
|
||||||
|
dependencies = [
|
||||||
|
"devise_core",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "devise_core"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"proc-macro2-diagnostics",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@ -656,6 +793,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -706,6 +844,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
|
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "2.4.3"
|
version = "2.4.3"
|
||||||
@ -782,6 +926,20 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "figment"
|
||||||
|
version = "0.10.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
|
||||||
|
dependencies = [
|
||||||
|
"atomic 0.6.0",
|
||||||
|
"pear",
|
||||||
|
"serde",
|
||||||
|
"toml 0.8.16",
|
||||||
|
"uncased",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.23"
|
version = "0.2.23"
|
||||||
@ -806,9 +964,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flexi_logger"
|
name = "flexi_logger"
|
||||||
version = "0.28.5"
|
version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cca927478b3747ba47f98af6ba0ac0daea4f12d12f55e9104071b3dc00276310"
|
checksum = "a250587a211932896a131f214a4f64c047b826ce072d2018764e5ff5141df8fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"glob",
|
"glob",
|
||||||
@ -885,6 +1043,20 @@ dependencies = [
|
|||||||
"new_debug_unreachable",
|
"new_debug_unreachable",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
@ -892,6 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -946,6 +1119,7 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
@ -1350,12 +1524,27 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@ -1488,23 +1677,6 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-rustls"
|
|
||||||
version = "0.27.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
|
|
||||||
dependencies = [
|
|
||||||
"futures-util",
|
|
||||||
"http 1.1.0",
|
|
||||||
"hyper 1.4.1",
|
|
||||||
"hyper-util",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-tls"
|
name = "hyper-tls"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1675,6 +1847,22 @@ dependencies = [
|
|||||||
"cfb",
|
"cfb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inlinable_string"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"block-padding",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@ -1690,6 +1878,17 @@ version = "2.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.4.0",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
@ -1931,16 +2130,27 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mindwork-ai-studio"
|
name = "mindwork-ai-studio"
|
||||||
version = "0.8.12"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes",
|
||||||
"arboard",
|
"arboard",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"cbc",
|
||||||
|
"cipher",
|
||||||
"flexi_logger",
|
"flexi_logger",
|
||||||
|
"hmac",
|
||||||
"keyring",
|
"keyring",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"reqwest 0.12.5",
|
"pbkdf2",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"rand_chacha 0.3.1",
|
||||||
|
"rcgen",
|
||||||
|
"reqwest 0.12.4",
|
||||||
|
"rocket",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
@ -1969,12 +2179,31 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.3.9",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multer"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"httparse",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"spin",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@ -2130,6 +2359,16 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.3.9",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_enum"
|
name = "num_enum"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
@ -2438,6 +2677,49 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pbkdf2"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
"hmac",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pear"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467"
|
||||||
|
dependencies = [
|
||||||
|
"inlinable_string",
|
||||||
|
"pear_codegen",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pear_codegen"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"proc-macro2-diagnostics",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -2709,6 +2991,19 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2-diagnostics"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
"version_check",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.32.0"
|
version = "0.32.0"
|
||||||
@ -2814,6 +3109,19 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
|
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rcgen"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779"
|
||||||
|
dependencies = [
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"time",
|
||||||
|
"yasna",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -2843,6 +3151,26 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931"
|
||||||
|
dependencies = [
|
||||||
|
"ref-cast-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast-impl"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.5"
|
version = "1.10.5"
|
||||||
@ -2915,7 +3243,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 0.1.2",
|
"sync_wrapper",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
@ -2931,9 +3259,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.5"
|
version = "0.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
|
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -2945,7 +3273,6 @@ dependencies = [
|
|||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"hyper-rustls",
|
|
||||||
"hyper-tls 0.6.0",
|
"hyper-tls 0.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
@ -2960,7 +3287,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.1",
|
"sync_wrapper",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
@ -3011,6 +3338,91 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rocket"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream",
|
||||||
|
"async-trait",
|
||||||
|
"atomic 0.5.3",
|
||||||
|
"binascii",
|
||||||
|
"bytes",
|
||||||
|
"either",
|
||||||
|
"figment",
|
||||||
|
"futures",
|
||||||
|
"indexmap 2.2.6",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"multer",
|
||||||
|
"num_cpus",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"ref-cast",
|
||||||
|
"rocket_codegen",
|
||||||
|
"rocket_http",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"state 0.6.0",
|
||||||
|
"tempfile",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
|
"ubyte",
|
||||||
|
"version_check",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rocket_codegen"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
|
||||||
|
dependencies = [
|
||||||
|
"devise",
|
||||||
|
"glob",
|
||||||
|
"indexmap 2.2.6",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rocket_http",
|
||||||
|
"syn 2.0.72",
|
||||||
|
"unicode-xid",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rocket_http"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9"
|
||||||
|
dependencies = [
|
||||||
|
"cookie",
|
||||||
|
"either",
|
||||||
|
"futures",
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.30",
|
||||||
|
"indexmap 2.2.6",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"pear",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"ref-cast",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pemfile 1.0.4",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"stable-pattern",
|
||||||
|
"state 0.6.0",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"uncased",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
@ -3041,15 +3453,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.12"
|
version = "0.21.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
|
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"log",
|
||||||
"rustls-pki-types",
|
"ring",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"subtle",
|
"sct",
|
||||||
"zeroize",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3079,12 +3490,11 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.102.6"
|
version = "0.101.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
|
||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3130,6 +3540,16 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sct"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
@ -3338,6 +3758,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -3409,6 +3838,15 @@ version = "0.9.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable-pattern"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -3424,6 +3862,15 @@ dependencies = [
|
|||||||
"loom",
|
"loom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "state"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8"
|
||||||
|
dependencies = [
|
||||||
|
"loom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@ -3458,9 +3905,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
@ -3490,12 +3937,6 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sync_wrapper"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -3659,7 +4100,7 @@ dependencies = [
|
|||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
"shared_child",
|
"shared_child",
|
||||||
"state",
|
"state 0.5.3",
|
||||||
"tar",
|
"tar",
|
||||||
"tauri-macros",
|
"tauri-macros",
|
||||||
"tauri-runtime",
|
"tauri-runtime",
|
||||||
@ -3958,10 +4399,23 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.72",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -3974,12 +4428,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-stream"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4164,6 +4628,25 @@ version = "1.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ubyte"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uncased"
|
||||||
|
version = "0.9.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
@ -4191,6 +4674,12 @@ version = "1.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -4993,10 +5482,22 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "yansi"
|
||||||
version = "1.8.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
dependencies = [
|
||||||
|
"is-terminal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yasna"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||||
|
dependencies = [
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mindwork-ai-studio"
|
name = "mindwork-ai-studio"
|
||||||
version = "0.8.12"
|
version = "0.9.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "MindWork AI Studio"
|
description = "MindWork AI Studio"
|
||||||
authors = ["Thorsten Sommer"]
|
authors = ["Thorsten Sommer"]
|
||||||
@ -9,22 +9,31 @@ authors = ["Thorsten Sommer"]
|
|||||||
tauri-build = { version = "1.5", features = [] }
|
tauri-build = { version = "1.5", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "1.7.1", features = [ "updater", "http-all", "shell-sidecar", "path-all", "shell-open"] }
|
tauri = { version = "1.7.1", features = [ "updater", "shell-sidecar", "shell-open"] }
|
||||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
keyring = { version = "3.2.0", features = ["apple-native", "windows-native", "sync-secret-service"] }
|
keyring = { version = "3.2", features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||||
arboard = "3.4.0"
|
arboard = "3.4.0"
|
||||||
tokio = "1.39"
|
tokio = { version = "1.39", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
flexi_logger = "0.28"
|
flexi_logger = "0.29"
|
||||||
log = "0.4"
|
log = { version = "0.4", features = ["kv"] }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
rocket = { version = "0.5", features = ["json", "tls"] }
|
||||||
|
rand = "0.8"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
cipher = { version = "0.4.4", features = ["std"] }
|
||||||
|
aes = "0.8.4"
|
||||||
|
cbc = "0.1.2"
|
||||||
|
pbkdf2 = "0.12.2"
|
||||||
|
hmac = "0.12.1"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
rcgen = { version = "0.13.1", features = ["pem"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
# See issue https://github.com/tauri-apps/tauri/issues/4470
|
# See issue https://github.com/tauri-apps/tauri/issues/4470
|
||||||
reqwest = { version = "0.12", features = ["native-tls-vendored"] }
|
reqwest = { version = "0.12", features = ["native-tls-vendored"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
|
||||||
# DO NOT REMOVE!!
|
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
1020
runtime/src/main.rs
1020
runtime/src/main.rs
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"devPath": "http://localhost:5000",
|
"devPath": "ui/",
|
||||||
"distDir": "ui/",
|
"distDir": "ui/",
|
||||||
"withGlobalTauri": true
|
"withGlobalTauri": false
|
||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "MindWork AI Studio",
|
"productName": "MindWork AI Studio",
|
||||||
"version": "0.8.12"
|
"version": "0.9.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@ -22,16 +22,6 @@
|
|||||||
"args": true
|
"args": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"all": true
|
|
||||||
},
|
|
||||||
"http" : {
|
|
||||||
"all": true,
|
|
||||||
"request": true,
|
|
||||||
"scope": [
|
|
||||||
"http://localhost"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"windows": [
|
"windows": [
|
||||||
@ -43,16 +33,6 @@
|
|||||||
"height": 1080
|
"height": 1080
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
|
||||||
"csp": null,
|
|
||||||
"dangerousRemoteDomainIpcAccess": [
|
|
||||||
{
|
|
||||||
"domain": "localhost",
|
|
||||||
"windows": ["main"],
|
|
||||||
"enableTauriAPI": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
|
Loading…
Reference in New Issue
Block a user