mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-10 04:13:48 +00:00
Refactored logging into a unified logging
This commit is contained in:
24 files changed
+416
-224
No files matched your search
@@ -7,13 +7,15 @@ using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
||||
public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
||||
{
|
||||
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
||||
|
||||
protected IJSRuntime JsRuntime { get; init; } = jsRuntime;
|
||||
|
||||
protected ThreadSafeRandom RNG { get; init; } = rng;
|
||||
|
||||
protected ILogger<AgentBase> Logger { get; init; } = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type or category of this agent.
|
||||
@@ -104,6 +106,6 @@ public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRu
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using AIStudio.Tools;
|
||||
|
||||
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, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, jsRuntime, rng)
|
||||
{
|
||||
private static readonly ContentBlock EMPTY_BLOCK = new()
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace AIStudio.Assistants;
|
||||
public abstract partial class AssistantBase : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; set; } = null!;
|
||||
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected IJSRuntime JsRuntime { get; init; } = null!;
|
||||
@@ -27,6 +27,9 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
[Inject]
|
||||
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 RESULT_DIV_ID = "assistantResult";
|
||||
|
||||
@@ -151,7 +154,7 @@ public abstract partial class AssistantBase : ComponentBase
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
this.isProcessing = false;
|
||||
this.StateHasChanged();
|
||||
|
||||
@@ -24,6 +24,9 @@ public partial class Workspaces : ComponentBase
|
||||
[Inject]
|
||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<Workspaces> Logger { get; init; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public ChatThread? CurrentChatThread { get; set; }
|
||||
|
||||
@@ -309,7 +312,7 @@ public partial class Workspaces : ComponentBase
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
this.Logger.LogError($"Failed to load chat from '{chatPath}': {e.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace AIStudio.Pages;
|
||||
public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; set; } = null!;
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IJSRuntime JsRuntime { get; init; } = null!;
|
||||
@@ -28,7 +28,10 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
private ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
[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!;
|
||||
|
||||
@@ -189,7 +192,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
// Save the chat:
|
||||
if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
|
||||
@@ -14,16 +14,19 @@ namespace AIStudio.Pages;
|
||||
public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Inject]
|
||||
public SettingsManager SettingsManager { get; init; } = null!;
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; init; } = null!;
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IJSRuntime JsRuntime { get; init; } = null!;
|
||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected MessageBus MessageBus { get; init; } = null!;
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<Settings> Logger { get; init; } = null!;
|
||||
|
||||
private readonly List<ConfigurationSelectData<string>> availableProviders = new();
|
||||
|
||||
@@ -111,7 +114,7 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var providerInstance = provider.CreateProvider();
|
||||
var providerInstance = provider.CreateProvider(this.Logger);
|
||||
var deleteSecretResponse = await this.SettingsManager.DeleteAPIKey(this.JsRuntime, providerInstance);
|
||||
if(deleteSecretResponse.Success)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@ using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
using MudBlazor.Services;
|
||||
|
||||
#if !DEBUG
|
||||
@@ -35,6 +37,18 @@ if(string.IsNullOrWhiteSpace(secretKey))
|
||||
}
|
||||
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
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;
|
||||
@@ -101,5 +115,8 @@ app.MapRazorComponents<App>()
|
||||
|
||||
var serverTask = app.RunAsync();
|
||||
|
||||
var rustLogger = app.Services.GetRequiredService<ILogger<Rust>>();
|
||||
rust.SetLogger(rustLogger);
|
||||
|
||||
await rust.AppIsReady();
|
||||
await serverTask;
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Settings;
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -9,13 +9,21 @@ public abstract class BaseProvider
|
||||
/// The HTTP client to use for all requests.
|
||||
/// </summary>
|
||||
protected readonly HttpClient httpClient = new();
|
||||
|
||||
/// <summary>
|
||||
/// The logger to use.
|
||||
/// </summary>
|
||||
protected readonly ILogger logger;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the base provider.
|
||||
/// </summary>
|
||||
/// <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:
|
||||
this.httpClient.BaseAddress = new(url);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Settings;
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using AIStudio.Settings;
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace AIStudio.Provider.OpenAI;
|
||||
/// <summary>
|
||||
/// The OpenAI provider.
|
||||
/// </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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,59 +14,4 @@ public enum Providers
|
||||
FIREWORKS = 5,
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using AIStudio.Settings;
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -162,7 +162,7 @@ public sealed class ProviderSelfHosted(Settings.Provider provider) : BaseProvide
|
||||
}
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +71,13 @@ public partial class ProviderDialog : ComponentBase
|
||||
public bool IsEditing { get; init; }
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; set; } = null!;
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IJSRuntime JsRuntime { get; set; } = null!;
|
||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
@@ -133,7 +136,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
}
|
||||
|
||||
var loadedProviderSettings = this.CreateProviderSettings();
|
||||
var provider = loadedProviderSettings.CreateProvider();
|
||||
var provider = loadedProviderSettings.CreateProvider(this.Logger);
|
||||
if(provider is NoProvider)
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
@@ -187,7 +190,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
if (addedProviderSettings.UsedProvider != Providers.SELF_HOSTED)
|
||||
{
|
||||
// We need to instantiate the provider to store the API key:
|
||||
var provider = addedProviderSettings.CreateProvider();
|
||||
var provider = addedProviderSettings.CreateProvider(this.Logger);
|
||||
|
||||
// Store the API key in the OS secure storage:
|
||||
var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey);
|
||||
@@ -318,7 +321,7 @@ public partial class ProviderDialog : ComponentBase
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
var currentProviderSettings = this.CreateProviderSettings();
|
||||
var provider = currentProviderSettings.CreateProvider();
|
||||
var provider = currentProviderSettings.CreateProvider(this.Logger);
|
||||
if(provider is NoProvider)
|
||||
return;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace AIStudio.Settings;
|
||||
/// <summary>
|
||||
/// The settings manager.
|
||||
/// </summary>
|
||||
public sealed class SettingsManager
|
||||
public sealed class SettingsManager(ILogger<SettingsManager> logger)
|
||||
{
|
||||
private const string SETTINGS_FILENAME = "settings.json";
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class SettingsManager
|
||||
WriteIndented = true,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
|
||||
private ILogger<SettingsManager> logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// The directory where the configuration files are stored.
|
||||
@@ -102,12 +104,18 @@ public sealed class SettingsManager
|
||||
public async Task LoadSettings()
|
||||
{
|
||||
if(!this.IsSetUp)
|
||||
{
|
||||
this.logger.LogWarning("Cannot load settings, because the configuration is not set up yet.");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
||||
if(!File.Exists(settingsPath))
|
||||
{
|
||||
this.logger.LogWarning("Cannot load settings, because the settings file does not exist.");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// We read the `"Version": "V3"` line to determine the version of the settings file:
|
||||
await foreach (var line in File.ReadLinesAsync(settingsPath))
|
||||
{
|
||||
@@ -123,16 +131,16 @@ public sealed class SettingsManager
|
||||
Enum.TryParse(settingsVersionText, out Version settingsVersion);
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -142,14 +150,22 @@ public sealed class SettingsManager
|
||||
public async Task StoreSettings()
|
||||
{
|
||||
if(!this.IsSetUp)
|
||||
{
|
||||
this.logger.LogWarning("Cannot store settings, because the configuration is not set up yet.");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
||||
if(!Directory.Exists(ConfigDirectory))
|
||||
{
|
||||
this.logger.LogInformation("Creating the configuration directory.");
|
||||
Directory.CreateDirectory(ConfigDirectory!);
|
||||
|
||||
}
|
||||
|
||||
var settingsJson = JsonSerializer.Serialize(this.ConfigurationData, JSON_OPTIONS);
|
||||
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";
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace AIStudio.Settings;
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -17,41 +17,41 @@ public static class SettingsMigrations
|
||||
var configV1 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
||||
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();
|
||||
}
|
||||
|
||||
configV1 = MigrateV1ToV2(configV1);
|
||||
configV1 = MigrateV2ToV3(configV1);
|
||||
return MigrateV3ToV4(configV1);
|
||||
configV1 = MigrateV1ToV2(logger, configV1);
|
||||
configV1 = MigrateV2ToV3(logger, configV1);
|
||||
return MigrateV3ToV4(logger, configV1);
|
||||
|
||||
case Version.V2:
|
||||
var configV2 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
||||
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();
|
||||
}
|
||||
|
||||
configV2 = MigrateV2ToV3(configV2);
|
||||
return MigrateV3ToV4(configV2);
|
||||
configV2 = MigrateV2ToV3(logger, configV2);
|
||||
return MigrateV3ToV4(logger, configV2);
|
||||
|
||||
case Version.V3:
|
||||
var configV3 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
|
||||
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 MigrateV3ToV4(configV3);
|
||||
return MigrateV3ToV4(logger, configV3);
|
||||
|
||||
default:
|
||||
Console.WriteLine("No configuration migration needed.");
|
||||
logger.LogInformation("No configuration migration is needed.");
|
||||
var configV4 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -59,14 +59,14 @@ public static class SettingsMigrations
|
||||
}
|
||||
}
|
||||
|
||||
private static DataV1V3 MigrateV1ToV2(DataV1V3 previousData)
|
||||
private static DataV1V3 MigrateV1ToV2(ILogger<SettingsManager> logger, DataV1V3 previousData)
|
||||
{
|
||||
//
|
||||
// Summary:
|
||||
// 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()
|
||||
{
|
||||
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:
|
||||
// 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()
|
||||
{
|
||||
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:
|
||||
// We grouped the settings into different categories.
|
||||
//
|
||||
|
||||
Console.WriteLine("Migrating from v3 to v4...");
|
||||
logger.LogInformation("Migrating from v3 to v4...");
|
||||
return new()
|
||||
{
|
||||
Version = Version.V4,
|
||||
|
||||
@@ -9,6 +9,13 @@ public sealed class Rust(string apiPort) : IDisposable
|
||||
{
|
||||
BaseAddress = new Uri($"http://127.0.0.1:{apiPort}"),
|
||||
};
|
||||
|
||||
private ILogger<Rust>? logger;
|
||||
|
||||
public void SetLogger(ILogger<Rust> logService)
|
||||
{
|
||||
this.logger = logService;
|
||||
}
|
||||
|
||||
public async Task<int> GetAppPort()
|
||||
{
|
||||
@@ -36,14 +43,14 @@ public sealed class Rust(string apiPort) : IDisposable
|
||||
var response = await initialHttp.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($" Try {tris}/{MAX_TRIES}");
|
||||
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}'");
|
||||
Console.WriteLine($"Received app port from Rust runtime: '{appPort}'");
|
||||
return appPort;
|
||||
}
|
||||
|
||||
@@ -54,11 +61,11 @@ public sealed class Rust(string apiPort) : IDisposable
|
||||
public async Task AppIsReady()
|
||||
{
|
||||
const string URL = "/system/dotnet/ready";
|
||||
Console.WriteLine($"Notifying Rust runtime that the app is ready.");
|
||||
this.logger!.LogInformation("Notifying Rust runtime that the app is ready.");
|
||||
var response = await this.http.PostAsync(URL, new StringContent(string.Empty));
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"Failed to notify Rust runtime that the app is ready: '{response.StatusCode}'");
|
||||
this.logger!.LogError($"Failed to notify Rust runtime that the app is ready: '{response.StatusCode}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class MarkdownClipboardService(Rust rust, IJSRuntime jsRuntime, IS
|
||||
private Rust Rust { get; } = rust;
|
||||
|
||||
/// <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>
|
||||
/// <param name="text">The Markdown text to copy.</param>
|
||||
public async ValueTask CopyToClipboardAsync(string text) => await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, text);
|
||||
|
||||
@@ -3,11 +3,13 @@ using AIStudio.Settings.DataModel;
|
||||
|
||||
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 bool IS_INITIALIZED;
|
||||
|
||||
private readonly ILogger<TemporaryChatService> logger = logger;
|
||||
|
||||
#region Overrides of BackgroundService
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
@@ -15,10 +17,12 @@ public class TemporaryChatService(SettingsManager settingsManager) : BackgroundS
|
||||
while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
|
||||
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
|
||||
|
||||
this.logger.LogInformation("The temporary chat maintenance service was initialized.");
|
||||
|
||||
await settingsManager.LoadSettings();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -34,10 +38,14 @@ public class TemporaryChatService(SettingsManager settingsManager) : BackgroundS
|
||||
|
||||
private Task StartMaintenance()
|
||||
{
|
||||
this.logger.LogInformation("Starting maintenance of temporary chat storage.");
|
||||
var temporaryDirectories = Path.Join(SettingsManager.DataDirectory, "tempChats");
|
||||
if(!Directory.Exists(temporaryDirectories))
|
||||
{
|
||||
this.logger.LogWarning("Temporary chat storage directory does not exist. End maintenance.");
|
||||
return Task.CompletedTask;
|
||||
|
||||
}
|
||||
|
||||
foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories))
|
||||
{
|
||||
var chatPath = Path.Join(tempChatDirPath, "thread.json");
|
||||
@@ -59,9 +67,13 @@ public class TemporaryChatService(SettingsManager settingsManager) : BackgroundS
|
||||
};
|
||||
|
||||
if(deleteChat)
|
||||
{
|
||||
this.logger.LogInformation($"Deleting temporary chat storage directory '{tempChatDirPath}' due to maintenance policy.");
|
||||
Directory.Delete(tempChatDirPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.LogInformation("Finished maintenance of temporary chat storage.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user