Skip to content

Commit 8b11e23

Browse files
Added an option for mandatory terms of use dialogs (#725)
1 parent 09258c7 commit 8b11e23

29 files changed

Lines changed: 836 additions & 21 deletions

app/MindWork AI Studio/Assistants/I18N/allTexts.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,6 +2098,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T527187983"] = "C
20982098
-- Install Pandoc
20992099
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Install Pandoc"
21002100

2101+
-- Version
2102+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T1573770551"] = "Version"
2103+
2104+
-- A new version of the terms is available. Please review it again.
2105+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T1711766303"] = "A new version of the terms is available. Please review it again."
2106+
2107+
-- This mandatory info has not been accepted yet.
2108+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T1870532312"] = "This mandatory info has not been accepted yet."
2109+
2110+
-- Accepted version
2111+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T203086476"] = "Accepted version"
2112+
2113+
-- Last accepted version
2114+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T3407978086"] = "Last accepted version"
2115+
2116+
-- Accepted at (UTC)
2117+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T3511160492"] = "Accepted at (UTC)"
2118+
2119+
-- Please review this text again. The content was changed.
2120+
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T941885055"] = "Please review this text again. The content was changed."
2121+
21012122
-- Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications.
21022123
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1057189794"] = "Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications."
21032124

@@ -5695,6 +5716,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1137744461"] = "ID mismatch: the
56955716
-- This is a private AI Studio installation. It runs without an enterprise configuration.
56965717
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1209549230"] = "This is a private AI Studio installation. It runs without an enterprise configuration."
56975718

5719+
-- Unknown configuration plugin
5720+
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1290340974"] = "Unknown configuration plugin"
5721+
56985722
-- This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.
56995723
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1388816916"] = "This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat."
57005724

@@ -5725,6 +5749,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1629800076"] = "Building on .NET
57255749
-- AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files.
57265750
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1630237140"] = "AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files."
57275751

5752+
-- Consent:
5753+
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T171952677"] = "Consent:"
5754+
57285755
-- This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant.
57295756
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1772678682"] = "This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant."
57305757

@@ -5944,6 +5971,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T788846912"] = "Copies the config
59445971
-- installed by AI Studio
59455972
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T833849470"] = "installed by AI Studio"
59465973

5974+
-- Provided by configuration plugin: {0}
5975+
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T836298648"] = "Provided by configuration plugin: {0}"
5976+
59475977
-- We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate.
59485978
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T855925638"] = "We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate."
59495979

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@inherits MSGComponentBase
2+
3+
<MudStack Spacing="2">
4+
<MudText Typo="Typo.body2">
5+
@T("Version"): @this.Info.VersionText
6+
</MudText>
7+
8+
@if (this.ShowAcceptanceMetadata)
9+
{
10+
@if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.MISSING)
11+
{
12+
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
13+
@T("This mandatory info has not been accepted yet.")
14+
</MudAlert>
15+
}
16+
else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.VERSION_CHANGED)
17+
{
18+
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
19+
@T("A new version of the terms is available. Please review it again.")
20+
<br />
21+
@T("Last accepted version"): @this.Acceptance!.AcceptedVersion
22+
<br />
23+
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
24+
</MudAlert>
25+
}
26+
else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.CONTENT_CHANGED)
27+
{
28+
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
29+
@T("Please review this text again. The content was changed.")
30+
<br />
31+
@T("Last accepted version"): @this.Acceptance!.AcceptedVersion
32+
<br />
33+
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
34+
</MudAlert>
35+
}
36+
else
37+
{
38+
<MudAlert Severity="Severity.Success" Variant="Variant.Outlined" Dense="@true">
39+
@T("Accepted version"): @this.Acceptance!.AcceptedVersion
40+
<br />
41+
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
42+
</MudAlert>
43+
}
44+
}
45+
46+
<MudJustifiedMarkdown Value="@this.Info.Markdown" />
47+
</MudStack>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using AIStudio.Settings.DataModel;
2+
3+
using Microsoft.AspNetCore.Components;
4+
5+
namespace AIStudio.Components;
6+
7+
public partial class MandatoryInfoDisplay
8+
{
9+
private enum MandatoryInfoAcceptanceStatus
10+
{
11+
MISSING,
12+
VERSION_CHANGED,
13+
CONTENT_CHANGED,
14+
ACCEPTED,
15+
}
16+
17+
[Parameter]
18+
public DataMandatoryInfo Info { get; set; } = new();
19+
20+
[Parameter]
21+
public DataMandatoryInfoAcceptance? Acceptance { get; set; }
22+
23+
[Parameter]
24+
public bool ShowAcceptanceMetadata { get; set; }
25+
26+
private MandatoryInfoAcceptanceStatus AcceptanceStatus
27+
{
28+
get
29+
{
30+
if (this.Acceptance is null)
31+
return MandatoryInfoAcceptanceStatus.MISSING;
32+
33+
if (!string.Equals(this.Acceptance.AcceptedVersion, this.Info.VersionText, StringComparison.Ordinal))
34+
return MandatoryInfoAcceptanceStatus.VERSION_CHANGED;
35+
36+
if (!string.Equals(this.Acceptance.AcceptedHash, this.Info.AcceptanceHash, StringComparison.Ordinal))
37+
return MandatoryInfoAcceptanceStatus.CONTENT_CHANGED;
38+
39+
return MandatoryInfoAcceptanceStatus.ACCEPTED;
40+
}
41+
}
42+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="justified-markdown">
2+
<MudMarkdown Value="@this.Value" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE" />
3+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Components;
2+
3+
namespace AIStudio.Components;
4+
5+
public partial class MudJustifiedMarkdown
6+
{
7+
[Parameter]
8+
public string Value { get; set; } = string.Empty;
9+
}

app/MindWork AI Studio/Dialogs/DialogOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ public static class DialogOptions
1414
CloseOnEscapeKey = true,
1515
FullWidth = true, MaxWidth = MaxWidth.Medium,
1616
};
17+
18+
public static readonly MudBlazor.DialogOptions BLOCKING_FULLSCREEN = new()
19+
{
20+
BackdropClick = false,
21+
CloseOnEscapeKey = false,
22+
FullWidth = true, MaxWidth = MaxWidth.Medium,
23+
};
1724
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@inherits MSGComponentBase
2+
3+
<MudDialog>
4+
<DialogContent>
5+
<div class="pt-6" style="max-height: calc(100vh - 11rem); overflow-y: auto; overflow-x: hidden; padding-right: 0.5rem;">
6+
<MandatoryInfoDisplay Info="@this.Info" Acceptance="@this.Acceptance" ShowAcceptanceMetadata="@true" />
7+
</div>
8+
</DialogContent>
9+
<DialogActions>
10+
<MudStack Row="true" Justify="Justify.SpaceBetween" Class="pa-4" Style="width: 100%;">
11+
<MudButton OnClick="@this.Reject"
12+
Variant="Variant.Filled"
13+
Color="Color.Error"
14+
Size="Size.Large">
15+
@this.Info.RejectButtonText
16+
</MudButton>
17+
<MudButton OnClick="@this.Accept"
18+
Variant="Variant.Filled"
19+
Color="Color.Success"
20+
Size="Size.Large">
21+
@this.Info.AcceptButtonText
22+
</MudButton>
23+
</MudStack>
24+
</DialogActions>
25+
</MudDialog>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using AIStudio.Components;
2+
using AIStudio.Settings.DataModel;
3+
4+
using Microsoft.AspNetCore.Components;
5+
6+
namespace AIStudio.Dialogs;
7+
8+
public partial class MandatoryInfoDialog : MSGComponentBase
9+
{
10+
[CascadingParameter]
11+
private IMudDialogInstance MudDialog { get; set; } = null!;
12+
13+
[Parameter]
14+
public DataMandatoryInfo Info { get; set; } = new();
15+
16+
[Parameter]
17+
public DataMandatoryInfoAcceptance? Acceptance { get; set; }
18+
19+
private void Accept() => this.MudDialog.Close(DialogResult.Ok(true));
20+
21+
private void Reject() => this.MudDialog.Close(DialogResult.Ok(false));
22+
}

app/MindWork AI Studio/Layout/MainLayout.razor.cs

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
5353
private UpdateResponse? currentUpdateResponse;
5454
private MudThemeProvider themeProvider = null!;
5555
private bool useDarkMode;
56+
private bool startupCompleted;
57+
private readonly SemaphoreSlim mandatoryInfoDialogSemaphore = new(1, 1);
5658

5759
private IReadOnlyCollection<NavBarItem> navItems = [];
5860

@@ -91,8 +93,8 @@ protected override async Task OnInitializedAsync()
9193
this.MessageBus.ApplyFilters(this, [],
9294
[
9395
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
94-
Event.SHOW_ERROR, Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM,
95-
Event.PLUGINS_RELOADED, Event.INSTALL_UPDATE,
96+
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
97+
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED,
9698
]);
9799

98100
// Set the snackbar for the update service:
@@ -174,6 +176,8 @@ await this.InvokeAsync(async () =>
174176
await this.UpdateThemeConfiguration();
175177
this.LoadNavItems();
176178
this.StateHasChanged();
179+
if (this.startupCompleted)
180+
_ = this.EnsureMandatoryInfosAcceptedAsync();
177181
break;
178182

179183
case Event.COLOR_THEME_CHANGED:
@@ -261,6 +265,13 @@ await this.InvokeAsync(async () =>
261265
this.LoadNavItems();
262266

263267
await this.InvokeAsync(this.StateHasChanged);
268+
if (this.startupCompleted)
269+
_ = this.EnsureMandatoryInfosAcceptedAsync();
270+
break;
271+
272+
case Event.STARTUP_COMPLETED:
273+
this.startupCompleted = true;
274+
_ = this.EnsureMandatoryInfosAcceptedAsync();
264275
break;
265276
}
266277
});
@@ -368,12 +379,90 @@ private async Task UpdateThemeConfiguration()
368379
await this.MessageBus.SendMessage<bool>(this, Event.COLOR_THEME_CHANGED);
369380
this.StateHasChanged();
370381
}
382+
383+
private async Task EnsureMandatoryInfosAcceptedAsync()
384+
{
385+
if (!await this.mandatoryInfoDialogSemaphore.WaitAsync(0))
386+
return;
387+
388+
try
389+
{
390+
while (true)
391+
{
392+
var pendingInfos = this.GetPendingMandatoryInfos().ToList();
393+
if (pendingInfos.Count == 0)
394+
return;
395+
396+
foreach (var info in pendingInfos)
397+
{
398+
var wasAccepted = await this.ShowMandatoryInfoDialog(info);
399+
if (!wasAccepted)
400+
{
401+
await this.RustService.ExitApplication();
402+
return;
403+
}
404+
405+
await this.StoreMandatoryInfoAcceptance(info);
406+
}
407+
}
408+
}
409+
finally
410+
{
411+
this.mandatoryInfoDialogSemaphore.Release();
412+
}
413+
}
414+
415+
private IEnumerable<DataMandatoryInfo> GetPendingMandatoryInfos()
416+
{
417+
return PluginFactory.GetMandatoryInfos()
418+
.Where(info =>
419+
{
420+
var acceptance = this.SettingsManager.ConfigurationData.MandatoryInformation.FindAcceptance(info.Id);
421+
return acceptance is null || !string.Equals(acceptance.AcceptedHash, info.AcceptanceHash, StringComparison.Ordinal);
422+
});
423+
}
424+
425+
private async Task<bool> ShowMandatoryInfoDialog(DataMandatoryInfo info)
426+
{
427+
var acceptance = this.SettingsManager.ConfigurationData.MandatoryInformation.FindAcceptance(info.Id);
428+
var dialogParameters = new DialogParameters<MandatoryInfoDialog>
429+
{
430+
{ x => x.Info, info },
431+
{ x => x.Acceptance, acceptance },
432+
};
433+
434+
var dialogReference = await this.DialogService.ShowAsync<MandatoryInfoDialog>(info.Title, dialogParameters, DialogOptions.BLOCKING_FULLSCREEN);
435+
var dialogResult = await dialogReference.Result;
436+
return dialogResult is { Canceled: false, Data: true };
437+
}
438+
439+
private async Task StoreMandatoryInfoAcceptance(DataMandatoryInfo info)
440+
{
441+
var acceptances = this.SettingsManager.ConfigurationData.MandatoryInformation.Acceptances;
442+
var acceptance = new DataMandatoryInfoAcceptance
443+
{
444+
InfoId = info.Id,
445+
AcceptedVersion = info.VersionText,
446+
AcceptedHash = info.AcceptanceHash,
447+
AcceptedAtUtc = DateTimeOffset.UtcNow,
448+
EnterpriseConfigurationPluginId = info.EnterpriseConfigurationPluginId,
449+
};
450+
451+
var existingIndex = acceptances.FindIndex(item => item.InfoId == info.Id);
452+
if (existingIndex >= 0)
453+
acceptances[existingIndex] = acceptance;
454+
else
455+
acceptances.Add(acceptance);
456+
457+
await this.SettingsManager.StoreSettings();
458+
}
371459

372460
#region Implementation of IDisposable
373461

374462
public void Dispose()
375463
{
376464
this.MessageBus.Unregister(this);
465+
this.mandatoryInfoDialogSemaphore.Dispose();
377466
}
378467

379468
#endregion

app/MindWork AI Studio/Pages/Information.razor

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@
222222
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.EventNote" HeaderText="@T("Changelog")">
223223
<Changelog/>
224224
</ExpansionPanel>
225+
226+
@foreach (var mandatoryInfoPanel in this.mandatoryInfoPanels)
227+
{
228+
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Gavel" HeaderText="@mandatoryInfoPanel.HeaderText">
229+
<MudText Typo="Typo.body2" Class="mb-3">
230+
@string.Format(T("Provided by configuration plugin: {0}"), mandatoryInfoPanel.PluginName)
231+
</MudText>
232+
<MandatoryInfoDisplay Info="@mandatoryInfoPanel.Info"
233+
Acceptance="@mandatoryInfoPanel.Acceptance"
234+
ShowAcceptanceMetadata="@true"/>
235+
</ExpansionPanel>
236+
}
225237

226238
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Book" HeaderText="@T("Logbook")">
227239
<MudText Typo="Typo.h4">

0 commit comments

Comments
 (0)