Skip to content

UI for Pandoc integration #393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
266fdb9
WIP: implemented a static function to determine if pandoc is installe…
Apr 8, 2025
e578796
Included expressive Error Messages if pandoc is not installed or the …
Apr 8, 2025
62a4549
Removed patch level from version requirements and included more expre…
Apr 8, 2025
ca98c55
Added a success event to the message bus for better ux
Apr 10, 2025
f7771d9
added temporary buttons for debugging purposes TODO: Delete
Apr 10, 2025
8a890d2
WIP: included function to download pandoc' latest zip into our data dir
Apr 10, 2025
844e305
added warning messages to the message bus
Apr 14, 2025
b60d23a
Added functions to parse the current latest version from gh latest pa…
Apr 14, 2025
331c6ca
WIP: Change pandocs download function to account for different cpu ar…
Apr 14, 2025
09c9834
WIP: Included a pandoc dialog to check for the availability and to in…
Apr 15, 2025
08c1a54
WIP: Included expressive dialog for automatic and manual installation
Apr 15, 2025
d9fb0de
Introduced a flexible code block that enables to show code examples i…
nilskruthoff May 19, 2025
8031e1b
WIP: Extending dialog content to be very descriptive and beginner fri…
nilskruthoff May 19, 2025
49dc848
WIP: finished manual installation guide
May 26, 2025
cecf777
WIP: Adjusted styling for user experience and added installation with…
May 26, 2025
012b28d
Finished dialog and download of installers and archives
nilskruthoff May 26, 2025
81b276b
Finished dialog; installation and availability check logic
nilskruthoff May 27, 2025
b3108fd
finished pandoc logic; removed warnings
nilskruthoff May 27, 2025
647bc00
removed debugging from about page
nilskruthoff May 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/MindWork AI Studio/Components/CodeBlock.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

@if (!this.IsInline)
{
@if (this.ParentTabs is null)
{
<MudPaper Class="code-block no-elevation" Style="@this.BlockPadding()">
<pre><code>@this.ChildContent</code></pre>
</MudPaper>
}
}
else
{
<span class="inline-code-block"><kbd>@this.ChildContent</kbd></span>
}

39 changes: 39 additions & 0 deletions app/MindWork AI Studio/Components/CodeBlock.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Components;
using MudBlazor.Utilities;

namespace AIStudio.Components;

public partial class CodeBlock : ComponentBase
{
[Parameter]
public RenderFragment? ChildContent { get; set; }

[Parameter]
public string? Title { get; set; } = string.Empty;

[Parameter]
public bool IsInline { get; set; } = false;

[CascadingParameter]
public CodeTabs? ParentTabs { get; set; }

protected override void OnInitialized()
{
if (this.ParentTabs is not null && this.Title is not null)
{
RenderFragment blockSelf = builder =>
{
builder.OpenComponent<CodeBlock>(0);
builder.AddAttribute(1, "Title", this.Title);
builder.AddAttribute(2, "ChildContent", this.ChildContent);
builder.CloseComponent();
};
this.ParentTabs.RegisterBlock(this.Title, blockSelf);
}
}

private string BlockPadding()
{
return this.ParentTabs is null ? "padding: 16px !important;" : "padding: 8px !important";
}
}
11 changes: 11 additions & 0 deletions app/MindWork AI Studio/Components/CodeTabs.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<MudTabs @bind-ActivePanelIndex="selectedIndex" PanelClass="code-block" MinimumTabWidth="30px" Class="mt-2">
@foreach (var block in blocks)
{
<MudTabPanel Text="@block.Title">
@block.Fragment
</MudTabPanel>
}
</MudTabs>
<CascadingValue Value="this">
@this.ChildContent
</CascadingValue>
28 changes: 28 additions & 0 deletions app/MindWork AI Studio/Components/CodeTabs.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Components;

namespace AIStudio.Components;

public partial class CodeTabs : ComponentBase
{
[Parameter]
public RenderFragment? ChildContent { get; set; }

private List<CodeTabItem> blocks = new();
private int selectedIndex = 0;

internal void RegisterBlock(string title, RenderFragment fragment)
{
this.blocks.Add(new CodeTabItem
{
Title = title,
Fragment = fragment,
});
this.StateHasChanged();
}

private class CodeTabItem
{
public string Title { get; init; } = string.Empty;
public RenderFragment Fragment { get; init; } = null!;
}
}
154 changes: 154 additions & 0 deletions app/MindWork AI Studio/Dialogs/PandocDialog.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<MudDialog>
<TitleContent>
Install Pandoc
</TitleContent>
<DialogContent>
@if (this.showInstallPage)
{
<div class="mb-4">
<MudText Class="mb-2">
AI Studio relies on the <strong>free and open-sourced</strong> third-party app <strong>Pandoc</strong> to process and retrieve data from local
Office files (ex. <strong>Word</strong>) and later other text formats like LaTeX.
</MudText>
<MudText>
Unfortunately Pandoc's GPL license is not compatible with AI Studios licences, nonetheless software under GPL is generally free to use and
free of charge as well.
Therefore you have to accept Pandoc's GPL license before we can download and install Pandoc for free
automatically for you <strong>(recommended)</strong>.
However you can download it yourself manually with the instructions below.
</MudText>
<MudExpansionPanels>
<MudExpansionPanel Text="GNU General Public License v2 (GPL)" MaxHeight="300" ExpandedChanged="OnExpandedChanged">
@if (this.isLoading)
{
<MudSkeleton />
<MudSkeleton Animation="Animation.Wave" />
<MudSkeleton />
}
else if (!string.IsNullOrEmpty(this.licenseText))
{
<MudJustifiedText>@this.licenseText</MudJustifiedText>
}
</MudExpansionPanel>
</MudExpansionPanels>
</div>
<MudExpansionPanels Class="mb-3" MultiExpansion="@false" Outlined="false" Elevation="0">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.AutoFixHigh" HeaderText="Automatic installation" IsExpanded="true">
<MudText Typo="Typo.caption">
Pandoc is distributed under the
<MudLink Typo="Typo.caption" Href="https://github.com/jgm/pandoc/blob/main/COPYRIGHT" Target="_blank">GNU General Public License v2 (GPL)</MudLink>.
By clicking "Accept GPL and Install", you agree to the terms of the GPL license <br/> and Pandoc
will be installed automatically for you. Software under GPL is <strong>free of charge</strong> and free to use.<br/>
</MudText>
<MudButton OnClick="InstallPandocAsync" Color="Color.Primary" Class="mt-4" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.InstallDesktop">
Accept GPL and install for free
</MudButton>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Build" HeaderText="Manual installation">
<MudText Class="mb-2">
If you prefer to install Pandoc yourself, please follow one of these two guides. Installers are only available for Windows and Mac.
</MudText>
<MudExpansionPanels Outlined="false" Elevation="0">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.AppRegistration" HeaderText="Download with installer" IsExpanded="true">
<MudList T="string">
<MudListItem T="string" Class="mb-2">
Accept the terms of the GPL license and download the latest installer with the download button below.
Eventually you need to allow the download of the installer in the download window.
<CodeTabs>
<CodeBlock Title="Windows">pandoc-@(PANDOC_VERSION)-windows-x86_64.msi</CodeBlock>
<CodeBlock Title="Mac OS x86">pandoc-@(PANDOC_VERSION)-x86_64-macOS.pkg</CodeBlock>
<CodeBlock Title="Mac OS ARM">pandoc-@(PANDOC_VERSION)-arm64-macOS.pkg</CodeBlock>
</CodeTabs>
</MudListItem>
<MudListItem T="string">
Execute the installer and follow the instructions.
</MudListItem>
</MudList>
<MudText Class="mb-3" Typo="Typo.caption">
Pandoc is distributed under the <MudLink Typo="Typo.caption" Href="https://github.com/jgm/pandoc/blob/main/COPYRIGHT" Target="_blank">GNU General Public License v2 (GPL)</MudLink>.
By clicking "Accept GPL and download installer", you agree to the terms of the GPL license. Software under GPL is <strong>free of charge</strong> and free to use.<br/>
</MudText>
<MudButton OnClick="@this.GetInstaller" Color="Color.Secondary" Class="mt-4" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Downloading">
Accept GPL and download installer
</MudButton>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Outlined.Archive" HeaderText="Download with archive">
<MudList T="string">
<MudListItem T="string" Class="mb-2">
Accept the terms of the GPL license and download the latest archive with the download button below.
</MudListItem>
<MudListItem T="string" Class="mb-2">
Extract the archive to a folder of your choice.
<CodeTabs>
<CodeBlock Title="Windows">C:\Users\%USERNAME%\pandoc</CodeBlock>
<CodeBlock Title="Mac OS">/usr/local/bin/pandoc</CodeBlock>
<CodeBlock Title="Linux">/usr/local/bin/pandoc</CodeBlock>
</CodeTabs>
</MudListItem>
<MudListItem T="string" Class="mb-2">
Open the folder and copy the full path to the <CodeBlock IsInline="@true">pandoc.exe</CodeBlock> file into your
clipboard.
<CodeTabs>
<CodeBlock Title="Windows">C:\Users\%USERNAME%\pandoc\pandoc-@(PANDOC_VERSION)</CodeBlock>
<CodeBlock Title="Mac OS">/usr/local/bin/pandoc/pandoc-@(PANDOC_VERSION)</CodeBlock>
<CodeBlock Title="Linux">/usr/local/bin/pandoc/pandoc-@(PANDOC_VERSION)</CodeBlock>
</CodeTabs>
</MudListItem>
<MudListItem T="string">
Add the copied path to your systems environment variables and check the installation
by typing <br/><CodeBlock IsInline="@true">pandoc --version</CodeBlock>
into your command line interface.
<CodeTabs>
<CodeBlock Title="Windows">> pandoc.exe --version<br/>> pandoc.exe @(PANDOC_VERSION)</CodeBlock>
<CodeBlock Title="Mac OS">> pandoc --version<br/>> pandoc.exe @(PANDOC_VERSION)</CodeBlock>
<CodeBlock Title="Linux">> pandoc --version<br/>> pandoc.exe @(PANDOC_VERSION)</CodeBlock>
</CodeTabs>
</MudListItem>
</MudList>
<MudText Class="mb-3" Typo="Typo.caption">
Pandoc is distributed under the <MudLink Typo="Typo.caption" Href="https://github.com/jgm/pandoc/blob/main/COPYRIGHT" Target="_blank">GNU General Public License v2 (GPL)</MudLink>.
By clicking "Accept GPL and archive", you agree to the terms of the GPL license. Software under GPL is <strong>free of charge</strong> and free to use.<br/>
</MudText>
<MudButton OnClick="@this.GetArchive" Color="Color.Secondary" Class="mt-4" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Downloading">
Accept GPL and download archive
</MudButton>
</ExpansionPanel>
</MudExpansionPanels>
</ExpansionPanel>
</MudExpansionPanels>
<div class="mt-2">
<MudButton OnClick="@this.RejectLicense" Variant="Variant.Text" Color="Color.Default">Reject GPL licence</MudButton>
</div>
}
else
{
<MudItem Class="px-8 py-2" Style="height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
@if (showSkeleton)
{
<MudSkeleton SkeletonType="SkeletonType.Circle" Animation="Animation.Pulse" Class="mb-4"
Style="width: 4em; height: 4em;"/>
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Animation="Animation.Pulse" Width="230px"
Height="35px"/>
}
else if (isPandocAvailable)
{
<MudIcon Class="mb-2" Style="width: 2.5em; height: 2.5em;" Icon="@Icons.Material.Filled.Check"
Color="Color.Success"/>
<MudText Typo="Typo.subtitle1" Align="Align.Center">
Pandoc ist auf Ihrem System verfügbar
</MudText>
}
else
{
<MudIcon Class="mb-2" Style="width: 3.5em; height: 3.5em;" Icon="@Icons.Material.Filled.Error" Color="Color.Error"/>
<MudText Class="mb-6" Typo="Typo.subtitle1" Align="Align.Center">
Pandoc ist auf Ihrem System nicht verfügbar
</MudText>
<MudButton Color="Color.Primary" OnClick="@this.ProceedToInstallation" Variant="Variant.Filled">
Proceed to installation
</MudButton>
}
</MudItem>
}
</DialogContent>
</MudDialog>
136 changes: 136 additions & 0 deletions app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;

namespace AIStudio.Dialogs;

public partial class PandocDialog : ComponentBase
{
[Inject]
private HttpClient HttpClient { get; set; } = null!;

[Inject]
private RustService RustService { get; init; } = null!;

[Inject]
protected IJSRuntime JsRuntime { get; init; } = null!;

[Inject]
private IDialogService DialogService { get; init; } = null!;

[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;

private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PandocDialog");
private static readonly string LICENCE_URI = "https://raw.githubusercontent.com/jgm/pandoc/master/COPYRIGHT";
private static string PANDOC_VERSION = "1.0.0";

private bool isPandocAvailable;
private bool showSkeleton;
private bool showInstallPage;
private string? licenseText;
private bool isLoading;

#region Overrides of ComponentBase

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
this.showSkeleton = true;
await this.CheckPandocAvailabilityAsync();
PANDOC_VERSION = await Pandoc.FetchLatestVersionAsync();
}

#endregion

private void Cancel() => this.MudDialog.Cancel();

private async Task CheckPandocAvailabilityAsync()
{
this.isPandocAvailable = await Pandoc.CheckAvailabilityAsync(this.RustService);
this.showSkeleton = false;
await this.InvokeAsync(this.StateHasChanged);
}

private async Task InstallPandocAsync()
{
await Pandoc.InstallAsync(this.RustService);
this.MudDialog.Close(DialogResult.Ok(true));
await this.DialogService.ShowAsync<PandocDialog>("pandoc dialog");
}

private void ProceedToInstallation() => this.showInstallPage = true;

private async Task GetInstaller()
{
var uri = await Pandoc.GenerateInstallerUriAsync();
var filename = this.FilenameFromUri(uri);
await this.JsRuntime.InvokeVoidAsync("triggerDownload", uri, filename);
}

private async Task GetArchive()
{
var uri = await Pandoc.GenerateUriAsync();
var filename = this.FilenameFromUri(uri);
await this.JsRuntime.InvokeVoidAsync("triggerDownload", uri, filename);
}

private async Task RejectLicense()
{
var message = "Pandoc is open-source and free of charge, but if you reject Pandoc's license, it can not be installed and some of AIStudios data retrieval features will be disabled (e.g. using Office files like Word)." +
"This decision can be revoked at any time. Are you sure you want to reject the license?";

var dialogParameters = new DialogParameters
{
{ "Message", message },
};

var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Reject Pandoc's licence", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
dialogReference.Close();
else
this.Cancel();
}

private string FilenameFromUri(string uri)
{
var index = uri.LastIndexOf('/');
return uri[(index + 1)..];
}

private async Task OnExpandedChanged(bool isExpanded)
{
if (isExpanded)
{
this.isLoading = true;
try
{
await Task.Delay(600);

this.licenseText = await this.LoadLicenseTextAsync();
}
catch (Exception ex)
{
this.licenseText = "Error loading license text, please consider following the links to read the GPL.";
LOG.LogError("Error loading GPL license text:\n{ErrorMessage}", ex.Message);
}
finally
{
this.isLoading = false;
}
}
else
{
await Task.Delay(350);
this.licenseText = string.Empty;
}
}

private async Task<string> LoadLicenseTextAsync()
{
var response = await this.HttpClient.GetAsync(LICENCE_URI);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return content;
}
}
Loading