diff --git a/app/MindWork AI Studio/Pages/About.razor b/app/MindWork AI Studio/Pages/About.razor index 32c8b5df..d4a37762 100644 --- a/app/MindWork AI Studio/Pages/About.razor +++ b/app/MindWork AI Studio/Pages/About.razor @@ -3,6 +3,22 @@
About MindWork AI Studio + + +

Pandoc Verfügbarkeit prüfen

+ + @(isChecking ? "Überprüfe..." : "Pandoc überprüfen") + +

@statusMessage

+
+ + +

Pandoc Installation

+ + Install Pandoc + +
+ diff --git a/app/MindWork AI Studio/Pages/About.razor.cs b/app/MindWork AI Studio/Pages/About.razor.cs index 51727506..94e5d612 100644 --- a/app/MindWork AI Studio/Pages/About.razor.cs +++ b/app/MindWork AI Studio/Pages/About.razor.cs @@ -174,4 +174,30 @@ public partial class About : ComponentBase { await this.MessageBus.SendMessage(this, Event.USER_SEARCH_FOR_UPDATE); } + + // TODO: DELETE FOR DEBUGGING ONLY + private bool isChecking; + private string statusMessage = string.Empty; + private async Task CheckPandoc() + { + this.isChecking = true; + this.statusMessage = "Überprüfe die Verfügbarkeit von Pandoc..."; + this.StateHasChanged(); // Aktualisiere die UI + var isPandocAvailable = await Pandoc.IsPandocAvailableAsync(); + if (isPandocAvailable) + { + this.statusMessage = "Pandoc ist verfügbar und erfüllt die Mindestversion."; + } + else + { + this.statusMessage = "Pandoc ist nicht verfügbar oder die installierte Version ist zu niedrig."; + } + this.isChecking = false; + this.StateHasChanged(); // Aktualisiere die UI + } + + private async Task InstallPandoc() + { + var installPandoc = Pandoc.InstallPandocAsync(this.RustService); + } } diff --git a/app/MindWork AI Studio/Tools/Error.cs b/app/MindWork AI Studio/Tools/Error.cs index a3ba6c61..77f8d7c2 100644 --- a/app/MindWork AI Studio/Tools/Error.cs +++ b/app/MindWork AI Studio/Tools/Error.cs @@ -13,4 +13,19 @@ public readonly record struct Error(string Icon, string Message) config.VisibleStateDuration = 14_000; }); } +} + +public readonly record struct Success(string Icon, string Message) +{ + public void Show(ISnackbar snackbar) + { + var icon = this.Icon; + snackbar.Add(this.Message, Severity.Success, config => + { + config.Icon = icon; + config.IconSize = Size.Large; + config.HideTransitionDuration = 600; + config.VisibleStateDuration = 10_000; + }); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs index 213adf29..76a5051b 100644 --- a/app/MindWork AI Studio/Tools/Event.cs +++ b/app/MindWork AI Studio/Tools/Event.cs @@ -11,6 +11,7 @@ public enum Event STARTUP_PLUGIN_SYSTEM, PLUGINS_RELOADED, SHOW_ERROR, + SHOW_SUCCESS, // Update events: USER_SEARCH_FOR_UPDATE, diff --git a/app/MindWork AI Studio/Tools/MessageBus.cs b/app/MindWork AI Studio/Tools/MessageBus.cs index 06a2dfd8..3d48a7bb 100644 --- a/app/MindWork AI Studio/Tools/MessageBus.cs +++ b/app/MindWork AI Studio/Tools/MessageBus.cs @@ -67,6 +67,8 @@ public sealed class MessageBus } public Task SendError(Error error) => this.SendMessage(null, Event.SHOW_ERROR, error); + + public Task SendSuccess(Success success) => this.SendMessage(null, Event.SHOW_SUCCESS, success); public void DeferMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default) { diff --git a/app/MindWork AI Studio/Tools/Pandoc.cs b/app/MindWork AI Studio/Tools/Pandoc.cs new file mode 100644 index 00000000..73cdc6ad --- /dev/null +++ b/app/MindWork AI Studio/Tools/Pandoc.cs @@ -0,0 +1,136 @@ +using System.Diagnostics; +using System.IO.Compression; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using AIStudio.Components; +using AIStudio.Tools.Services; + +namespace AIStudio.Tools; + +public static partial class Pandoc +{ + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PluginFactory"); + private static readonly string DOWNLOAD_URL = "https://github.com/jgm/pandoc/releases/download/3.6.4/pandoc-3.6.4-windows-x86_64.zip"; + private static readonly Version MINIMUM_REQUIRED_VERSION = new Version(3, 6); + + /// + /// Checks if pandoc is available on the system and can be started as a process + /// + /// True, if pandoc is available and the minimum required version is met, else False. + public static async Task IsPandocAvailableAsync() + { + try + { + var startInfo = new ProcessStartInfo + { + FileName = GetPandocExecutableName(), + Arguments = "--version", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + using var process = Process.Start(startInfo); + if (process == null) + { + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Help, "The pandoc process could not be started.")); + LOG.LogInformation("The pandoc process was not started, it was null"); + return false; + } + + var output = await process.StandardOutput.ReadToEndAsync(); + await process.WaitForExitAsync(); + if (process.ExitCode != 0) + { + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Error, $"The pandoc process exited unexpectedly.")); + LOG.LogError("The pandoc process was exited with code {ProcessExitCode}", process.ExitCode); + return false; + } + + var versionMatch = PandocRegex().Match(output); + if (!versionMatch.Success) + { + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Terminal, $"pandoc --version returned an invalid format.")); + LOG.LogError("pandoc --version returned an invalid format:\n {Output}", output); + return false; + } + var versions = versionMatch.Groups[1].Value.Split('.'); + var major = int.Parse(versions[0]); + var minor = int.Parse(versions[1]); + var installedVersion = new Version(major, minor); + + if (installedVersion >= MINIMUM_REQUIRED_VERSION) + { + await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, $"Pandoc {installedVersion.ToString()} is installed.")); + return true; + } + + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Build, $"Pandoc {installedVersion.ToString()} is installed, but it doesn't match the required version ({MINIMUM_REQUIRED_VERSION.ToString()}).")); + LOG.LogInformation("Pandoc {Installed} is installed, but it does not match the required version ({Requirement})", installedVersion.ToString(), MINIMUM_REQUIRED_VERSION.ToString()); + return false; + + } + catch (Exception e) + { + await MessageBus.INSTANCE.SendError(new (@Icons.Material.Filled.AppsOutage, "Pandoc is not installed.")); + LOG.LogError("Pandoc is not installed and threw an exception:\n {Message}", e.Message); + return false; + } + } + + public static async Task InstallPandocAsync(RustService rustService) + { + var dataDir = await rustService.GetDataDirectory(); + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Help, $"{dataDir}")); + var installDir = Path.Join(dataDir, "pandoc"); + + try + { + if (!Directory.Exists(installDir)) + Directory.CreateDirectory(installDir); + + using var client = new HttpClient(); + var response = await client.GetAsync(DOWNLOAD_URL); + if (response.IsSuccessStatusCode) + { + var fileBytes = await response.Content.ReadAsByteArrayAsync(); + var tempZipPath = Path.Join(Path.GetTempPath(), "pandoc.zip"); + await File.WriteAllBytesAsync(tempZipPath, fileBytes); + ZipFile.ExtractToDirectory(tempZipPath, installDir); + File.Delete(tempZipPath); + + var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine); + var pandocDir = Path.Join(currentPath, "pandoc-3.6.4"); + if (currentPath != null && !currentPath.Contains(pandocDir)) + { + Environment.SetEnvironmentVariable( + "PATH", + $"{currentPath};{pandocDir}", + EnvironmentVariableTarget.Machine); + Console.WriteLine("Pandoc-Verzeichnis zum PATH hinzugefügt."); + } + else + { + Console.WriteLine("Pandoc-Verzeichnis ist bereits im PATH."); + } + + await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, $"Pandoc {MINIMUM_REQUIRED_VERSION.ToString()} was installed successfully.")); + } + else + { + Console.WriteLine("Fehler beim Herunterladen von Pandoc."); + } + } + catch (Exception ex) + { + Console.WriteLine($"Fehler: {ex.Message}"); + } + } + + /// + /// Returns the name of the pandoc executable based on the running operating system + /// + private static string GetPandocExecutableName() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "pandoc.exe" : "pandoc"; + + [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+)")] + private static partial Regex PandocRegex(); +} \ No newline at end of file