mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-06-27 17:16:28 +00:00
Initial version
This commit is contained in:
parent
6155442039
commit
4d09f5ad43
@ -2002,6 +2002,24 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T527187983"] = "C
|
|||||||
-- Install Pandoc
|
-- Install Pandoc
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Install Pandoc"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Install Pandoc"
|
||||||
|
|
||||||
|
-- Version
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T1573770551"] = "Version"
|
||||||
|
|
||||||
|
-- This mandatory info has not been accepted yet.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T1870532312"] = "This mandatory info has not been accepted yet."
|
||||||
|
|
||||||
|
-- Accepted version
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T203086476"] = "Accepted version"
|
||||||
|
|
||||||
|
-- The current version has not been accepted yet.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T2158667957"] = "The current version has not been accepted yet."
|
||||||
|
|
||||||
|
-- Last accepted version
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T3407978086"] = "Last accepted version"
|
||||||
|
|
||||||
|
-- Accepted at (UTC)
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANDATORYINFODISPLAY::T3511160492"] = "Accepted at (UTC)"
|
||||||
|
|
||||||
-- 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.
|
-- 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.
|
||||||
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."
|
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."
|
||||||
|
|
||||||
@ -4696,6 +4714,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T13933
|
|||||||
-- Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize.
|
-- Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1528169602"] = "Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1528169602"] = "Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize."
|
||||||
|
|
||||||
|
-- Slide Planner Assistant options are preselected
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1549358578"] = "Slide Planner Assistant options are preselected"
|
||||||
|
|
||||||
|
-- No Slide Planner Assistant options are preselected
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1694374279"] = "No Slide Planner Assistant options are preselected"
|
||||||
|
|
||||||
-- Choose whether the assistant should use the app default profile, no profile, or a specific profile.
|
-- Choose whether the assistant should use the app default profile, no profile, or a specific profile.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1766361623"] = "Choose whether the assistant should use the app default profile, no profile, or a specific profile."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1766361623"] = "Choose whether the assistant should use the app default profile, no profile, or a specific profile."
|
||||||
|
|
||||||
@ -4705,9 +4729,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T20146
|
|||||||
-- Which audience organizational level should be preselected?
|
-- Which audience organizational level should be preselected?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T216511105"] = "Which audience organizational level should be preselected?"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T216511105"] = "Which audience organizational level should be preselected?"
|
||||||
|
|
||||||
-- Preselect Slide Planner Assistant options?
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T227645894"] = "Preselect Slide Planner Assistant options?"
|
|
||||||
|
|
||||||
-- Preselect a profile
|
-- Preselect a profile
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2322771068"] = "Preselect a profile"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2322771068"] = "Preselect a profile"
|
||||||
|
|
||||||
@ -4724,26 +4745,23 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T25714
|
|||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2645589441"] = "Preselect the audience age group"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2645589441"] = "Preselect the audience age group"
|
||||||
|
|
||||||
-- Assistant: Slide Planner Assistant Options
|
-- Assistant: Slide Planner Assistant Options
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3215549988"] = "Assistant: Slide Planner Assistant Options"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3226042276"] = "Assistant: Slide Planner Assistant Options"
|
||||||
|
|
||||||
-- Which audience expertise should be preselected?
|
-- Which audience expertise should be preselected?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3228597992"] = "Which audience expertise should be preselected?"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3228597992"] = "Which audience expertise should be preselected?"
|
||||||
|
|
||||||
|
-- Preselect Slide Planner Assistant options?
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T339924858"] = "Preselect Slide Planner Assistant options?"
|
||||||
|
|
||||||
-- Close
|
-- Close
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3448155331"] = "Close"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3448155331"] = "Close"
|
||||||
|
|
||||||
-- Preselect important aspects
|
-- Preselect important aspects
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3705987833"] = "Preselect important aspects"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3705987833"] = "Preselect important aspects"
|
||||||
|
|
||||||
-- No Slide Planner Assistant options are preselected
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T4214398691"] = "No Slide Planner Assistant options are preselected"
|
|
||||||
|
|
||||||
-- Preselect the audience profile
|
-- Preselect the audience profile
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T861397972"] = "Preselect the audience profile"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T861397972"] = "Preselect the audience profile"
|
||||||
|
|
||||||
-- Slide Planner Assistant options are preselected
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T93124146"] = "Slide Planner Assistant options are preselected"
|
|
||||||
|
|
||||||
-- Which audience age group should be preselected?
|
-- Which audience age group should be preselected?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T956845877"] = "Which audience age group should be preselected?"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T956845877"] = "Which audience age group should be preselected?"
|
||||||
|
|
||||||
@ -5404,6 +5422,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1137744461"] = "ID mismatch: the
|
|||||||
-- This is a private AI Studio installation. It runs without an enterprise configuration.
|
-- This is a private AI Studio installation. It runs without an enterprise configuration.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1209549230"] = "This is a private AI Studio installation. It runs without an enterprise configuration."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1209549230"] = "This is a private AI Studio installation. It runs without an enterprise configuration."
|
||||||
|
|
||||||
|
-- Unknown configuration plugin
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1290340974"] = "Unknown configuration plugin"
|
||||||
|
|
||||||
-- This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.
|
-- This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.
|
||||||
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."
|
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."
|
||||||
|
|
||||||
@ -5653,6 +5674,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T788846912"] = "Copies the config
|
|||||||
-- installed by AI Studio
|
-- installed by AI Studio
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T833849470"] = "installed by AI Studio"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T833849470"] = "installed by AI Studio"
|
||||||
|
|
||||||
|
-- Provided by configuration plugin: {0}
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T836298648"] = "Provided by configuration plugin: {0}"
|
||||||
|
|
||||||
-- 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.
|
-- 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.
|
||||||
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."
|
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."
|
||||||
|
|
||||||
|
|||||||
42
app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor
Normal file
42
app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
@inherits MSGComponentBase
|
||||||
|
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
@if (this.ShowTitle)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.h4">@this.Info.Title</MudText>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
@T("Version"): @this.Info.VersionText
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (this.ShowAcceptanceMetadata)
|
||||||
|
{
|
||||||
|
@if (this.Acceptance is null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
|
||||||
|
@T("This mandatory info has not been accepted yet.")
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else if (!string.Equals(this.Acceptance.AcceptedVersion, this.Info.VersionText, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
|
||||||
|
@T("The current version has not been accepted yet.")
|
||||||
|
<br />
|
||||||
|
@T("Last accepted version"): @this.Acceptance.AcceptedVersion
|
||||||
|
<br />
|
||||||
|
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Success" Variant="Variant.Outlined" Dense="@true">
|
||||||
|
@T("Accepted version"): @this.Acceptance.AcceptedVersion
|
||||||
|
<br />
|
||||||
|
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudMarkdown Value="@this.Info.Markdown" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE" />
|
||||||
|
</MudStack>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
using AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
|
public partial class MandatoryInfoDisplay
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public DataMandatoryInfo Info { get; set; } = new();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public DataMandatoryInfoAcceptance? Acceptance { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ShowTitle { get; set; } = true;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ShowAcceptanceMetadata { get; set; }
|
||||||
|
}
|
||||||
@ -14,4 +14,10 @@ public static class DialogOptions
|
|||||||
CloseOnEscapeKey = true,
|
CloseOnEscapeKey = true,
|
||||||
FullWidth = true, MaxWidth = MaxWidth.Medium,
|
FullWidth = true, MaxWidth = MaxWidth.Medium,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static readonly MudBlazor.DialogOptions BLOCKING_FULLSCREEN = new()
|
||||||
|
{
|
||||||
|
BackdropClick = false,
|
||||||
|
CloseOnEscapeKey = false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
25
app/MindWork AI Studio/Dialogs/MandatoryInfoDialog.razor
Normal file
25
app/MindWork AI Studio/Dialogs/MandatoryInfoDialog.razor
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@inherits MSGComponentBase
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<div class="pt-6" style="height: calc(100vh - 11rem); overflow-y: auto; padding-right: 0.5rem;">
|
||||||
|
<MandatoryInfoDisplay Info="@this.Info" />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" Class="pa-4" Style="width: 100%;">
|
||||||
|
<MudButton OnClick="@this.Reject"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Error"
|
||||||
|
Size="Size.Large">
|
||||||
|
@this.Info.RejectButtonText
|
||||||
|
</MudButton>
|
||||||
|
<MudButton OnClick="@this.Accept"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Success"
|
||||||
|
Size="Size.Large">
|
||||||
|
@this.Info.AcceptButtonText
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
19
app/MindWork AI Studio/Dialogs/MandatoryInfoDialog.razor.cs
Normal file
19
app/MindWork AI Studio/Dialogs/MandatoryInfoDialog.razor.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using AIStudio.Components;
|
||||||
|
using AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace AIStudio.Dialogs;
|
||||||
|
|
||||||
|
public partial class MandatoryInfoDialog : MSGComponentBase
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public DataMandatoryInfo Info { get; set; } = new();
|
||||||
|
|
||||||
|
private void Accept() => this.MudDialog.Close(DialogResult.Ok(true));
|
||||||
|
|
||||||
|
private void Reject() => this.MudDialog.Close(DialogResult.Ok(false));
|
||||||
|
}
|
||||||
@ -53,6 +53,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
private UpdateResponse? currentUpdateResponse;
|
private UpdateResponse? currentUpdateResponse;
|
||||||
private MudThemeProvider themeProvider = null!;
|
private MudThemeProvider themeProvider = null!;
|
||||||
private bool useDarkMode;
|
private bool useDarkMode;
|
||||||
|
private bool startupCompleted;
|
||||||
|
private readonly SemaphoreSlim mandatoryInfoDialogSemaphore = new(1, 1);
|
||||||
|
|
||||||
private IReadOnlyCollection<NavBarItem> navItems = [];
|
private IReadOnlyCollection<NavBarItem> navItems = [];
|
||||||
|
|
||||||
@ -91,8 +93,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
this.MessageBus.ApplyFilters(this, [],
|
this.MessageBus.ApplyFilters(this, [],
|
||||||
[
|
[
|
||||||
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
|
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
|
||||||
Event.SHOW_ERROR, Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM,
|
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
|
||||||
Event.PLUGINS_RELOADED, Event.INSTALL_UPDATE,
|
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Set the snackbar for the update service:
|
// Set the snackbar for the update service:
|
||||||
@ -174,6 +176,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
await this.UpdateThemeConfiguration();
|
await this.UpdateThemeConfiguration();
|
||||||
this.LoadNavItems();
|
this.LoadNavItems();
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
|
if (this.startupCompleted)
|
||||||
|
_ = this.EnsureMandatoryInfosAcceptedAsync();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Event.COLOR_THEME_CHANGED:
|
case Event.COLOR_THEME_CHANGED:
|
||||||
@ -261,6 +265,13 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
this.LoadNavItems();
|
this.LoadNavItems();
|
||||||
|
|
||||||
await this.InvokeAsync(this.StateHasChanged);
|
await this.InvokeAsync(this.StateHasChanged);
|
||||||
|
if (this.startupCompleted)
|
||||||
|
_ = this.EnsureMandatoryInfosAcceptedAsync();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Event.STARTUP_COMPLETED:
|
||||||
|
this.startupCompleted = true;
|
||||||
|
_ = this.EnsureMandatoryInfosAcceptedAsync();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -369,11 +380,86 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EnsureMandatoryInfosAcceptedAsync()
|
||||||
|
{
|
||||||
|
if (!await this.mandatoryInfoDialogSemaphore.WaitAsync(0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var pendingInfos = this.GetPendingMandatoryInfos().ToList();
|
||||||
|
if (pendingInfos.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var info in pendingInfos)
|
||||||
|
{
|
||||||
|
var wasAccepted = await this.ShowMandatoryInfoDialog(info);
|
||||||
|
if (!wasAccepted)
|
||||||
|
{
|
||||||
|
await this.RustService.ExitApplication();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.StoreMandatoryInfoAcceptance(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.mandatoryInfoDialogSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<DataMandatoryInfo> GetPendingMandatoryInfos()
|
||||||
|
{
|
||||||
|
return PluginFactory.GetMandatoryInfos()
|
||||||
|
.Where(info =>
|
||||||
|
{
|
||||||
|
var acceptance = this.SettingsManager.ConfigurationData.MandatoryInformation.FindAcceptance(info.Id);
|
||||||
|
return acceptance is null || acceptance.AcceptedVersion != info.VersionText;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> ShowMandatoryInfoDialog(DataMandatoryInfo info)
|
||||||
|
{
|
||||||
|
var dialogParameters = new DialogParameters<MandatoryInfoDialog>
|
||||||
|
{
|
||||||
|
{ x => x.Info, info },
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialogReference = await this.DialogService.ShowAsync<MandatoryInfoDialog>(null, dialogParameters, DialogOptions.BLOCKING_FULLSCREEN);
|
||||||
|
var dialogResult = await dialogReference.Result;
|
||||||
|
return dialogResult is { Canceled: false, Data: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StoreMandatoryInfoAcceptance(DataMandatoryInfo info)
|
||||||
|
{
|
||||||
|
var acceptances = this.SettingsManager.ConfigurationData.MandatoryInformation.Acceptances;
|
||||||
|
var acceptance = new DataMandatoryInfoAcceptance
|
||||||
|
{
|
||||||
|
InfoId = info.Id,
|
||||||
|
AcceptedVersion = info.VersionText,
|
||||||
|
AcceptedAtUtc = DateTimeOffset.UtcNow,
|
||||||
|
EnterpriseConfigurationPluginId = info.EnterpriseConfigurationPluginId,
|
||||||
|
};
|
||||||
|
|
||||||
|
var existingIndex = acceptances.FindIndex(item => item.InfoId == info.Id);
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
acceptances[existingIndex] = acceptance;
|
||||||
|
else
|
||||||
|
acceptances.Add(acceptance);
|
||||||
|
|
||||||
|
await this.SettingsManager.StoreSettings();
|
||||||
|
}
|
||||||
|
|
||||||
#region Implementation of IDisposable
|
#region Implementation of IDisposable
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.MessageBus.Unregister(this);
|
this.MessageBus.Unregister(this);
|
||||||
|
this.mandatoryInfoDialogSemaphore.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -223,6 +223,19 @@
|
|||||||
<Changelog/>
|
<Changelog/>
|
||||||
</ExpansionPanel>
|
</ExpansionPanel>
|
||||||
|
|
||||||
|
@foreach (var mandatoryInfoPanel in this.mandatoryInfoPanels)
|
||||||
|
{
|
||||||
|
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Gavel" HeaderText="@mandatoryInfoPanel.HeaderText">
|
||||||
|
<MudText Typo="Typo.body2" Class="mb-3">
|
||||||
|
@string.Format(T("Provided by configuration plugin: {0}"), mandatoryInfoPanel.PluginName)
|
||||||
|
</MudText>
|
||||||
|
<MandatoryInfoDisplay Info="@mandatoryInfoPanel.Info"
|
||||||
|
Acceptance="@mandatoryInfoPanel.Acceptance"
|
||||||
|
ShowTitle="@false"
|
||||||
|
ShowAcceptanceMetadata="@true"/>
|
||||||
|
</ExpansionPanel>
|
||||||
|
}
|
||||||
|
|
||||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Book" HeaderText="@T("Logbook")">
|
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Book" HeaderText="@T("Logbook")">
|
||||||
<MudText Typo="Typo.h4">
|
<MudText Typo="Typo.h4">
|
||||||
@T("Explanation")
|
@T("Explanation")
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using System.Reflection;
|
|||||||
|
|
||||||
using AIStudio.Components;
|
using AIStudio.Components;
|
||||||
using AIStudio.Dialogs;
|
using AIStudio.Dialogs;
|
||||||
|
using AIStudio.Settings.DataModel;
|
||||||
using AIStudio.Tools.Databases;
|
using AIStudio.Tools.Databases;
|
||||||
using AIStudio.Tools.Metadata;
|
using AIStudio.Tools.Metadata;
|
||||||
using AIStudio.Tools.PluginSystem;
|
using AIStudio.Tools.PluginSystem;
|
||||||
@ -78,8 +79,12 @@ public partial class Information : MSGComponentBase
|
|||||||
|
|
||||||
private List<EnterpriseEnvironment> enterpriseEnvironments = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.ToList();
|
private List<EnterpriseEnvironment> enterpriseEnvironments = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.ToList();
|
||||||
|
|
||||||
|
private List<MandatoryInfoPanelData> mandatoryInfoPanels = [];
|
||||||
|
|
||||||
private sealed record DatabaseDisplayInfo(string Label, string Value);
|
private sealed record DatabaseDisplayInfo(string Label, string Value);
|
||||||
|
|
||||||
|
private sealed record MandatoryInfoPanelData(string HeaderText, string PluginName, DataMandatoryInfo Info, DataMandatoryInfoAcceptance? Acceptance);
|
||||||
|
|
||||||
private readonly List<DatabaseDisplayInfo> databaseDisplayInfo = new();
|
private readonly List<DatabaseDisplayInfo> databaseDisplayInfo = new();
|
||||||
|
|
||||||
private bool HasAnyActiveEnvironment => this.enterpriseEnvironments.Any(e => e.IsActive);
|
private bool HasAnyActiveEnvironment => this.enterpriseEnvironments.Any(e => e.IsActive);
|
||||||
@ -117,7 +122,7 @@ public partial class Information : MSGComponentBase
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
this.ApplyFilters([], [ Event.ENTERPRISE_ENVIRONMENTS_CHANGED ]);
|
this.ApplyFilters([], [ Event.ENTERPRISE_ENVIRONMENTS_CHANGED, Event.CONFIGURATION_CHANGED ]);
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
|
|
||||||
this.RefreshEnterpriseConfigurationState();
|
this.RefreshEnterpriseConfigurationState();
|
||||||
@ -145,6 +150,7 @@ public partial class Information : MSGComponentBase
|
|||||||
{
|
{
|
||||||
case Event.PLUGINS_RELOADED:
|
case Event.PLUGINS_RELOADED:
|
||||||
case Event.ENTERPRISE_ENVIRONMENTS_CHANGED:
|
case Event.ENTERPRISE_ENVIRONMENTS_CHANGED:
|
||||||
|
case Event.CONFIGURATION_CHANGED:
|
||||||
this.RefreshEnterpriseConfigurationState();
|
this.RefreshEnterpriseConfigurationState();
|
||||||
await this.InvokeAsync(this.StateHasChanged);
|
await this.InvokeAsync(this.StateHasChanged);
|
||||||
break;
|
break;
|
||||||
@ -163,6 +169,16 @@ public partial class Information : MSGComponentBase
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
this.enterpriseEnvironments = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.ToList();
|
this.enterpriseEnvironments = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.ToList();
|
||||||
|
this.mandatoryInfoPanels = PluginFactory.GetMandatoryInfos()
|
||||||
|
.Select(info =>
|
||||||
|
{
|
||||||
|
var plugin = this.configPlugins.FirstOrDefault(item => item.Id == info.EnterpriseConfigurationPluginId);
|
||||||
|
var pluginName = plugin?.Name ?? T("Unknown configuration plugin");
|
||||||
|
var acceptance = this.SettingsManager.ConfigurationData.MandatoryInformation.FindAcceptance(info.Id);
|
||||||
|
var headerText = $"{pluginName}: {info.Title}";
|
||||||
|
return new MandatoryInfoPanelData(headerText, pluginName, info, acceptance);
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeterminePandocVersion()
|
private async Task DeterminePandocVersion()
|
||||||
|
|||||||
@ -266,6 +266,30 @@ CONFIG["CHAT_TEMPLATES"] = {}
|
|||||||
-- Document analysis policies for this configuration:
|
-- Document analysis policies for this configuration:
|
||||||
CONFIG["DOCUMENT_ANALYSIS_POLICIES"] = {}
|
CONFIG["DOCUMENT_ANALYSIS_POLICIES"] = {}
|
||||||
|
|
||||||
|
-- Mandatory infos that users must explicitly accept before using AI Studio:
|
||||||
|
CONFIG["MANDATORY_INFOS"] = {}
|
||||||
|
|
||||||
|
-- An example mandatory info:
|
||||||
|
-- CONFIG["MANDATORY_INFOS"][#CONFIG["MANDATORY_INFOS"]+1] = {
|
||||||
|
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||||
|
-- ["Title"] = "AI Usage Requirements",
|
||||||
|
-- ["Version"] = "1",
|
||||||
|
-- ["Markdown"] = [===[
|
||||||
|
-- ## Usage Requirements
|
||||||
|
--
|
||||||
|
-- Before using this AI offering, please ensure that:
|
||||||
|
--
|
||||||
|
-- - you have completed the required internal training,
|
||||||
|
-- - generated output is clearly labeled where necessary,
|
||||||
|
-- - results are reviewed by a human before reuse,
|
||||||
|
-- - all internal policies and applicable law are followed.
|
||||||
|
--
|
||||||
|
-- Further information is available in the [internal wiki](https://example.org/wiki).
|
||||||
|
-- ]===],
|
||||||
|
-- ["AcceptButtonText"] = "Yes, I comply with these requirements",
|
||||||
|
-- ["RejectButtonText"] = "Stop. I do not agree to these requirements"
|
||||||
|
-- }
|
||||||
|
|
||||||
-- An example document analysis policy:
|
-- An example document analysis policy:
|
||||||
-- CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
|
-- CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
|
||||||
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||||
|
|||||||
@ -107,6 +107,8 @@ public sealed class Data
|
|||||||
|
|
||||||
public DataDocumentAnalysis DocumentAnalysis { get; init; } = new();
|
public DataDocumentAnalysis DocumentAnalysis { get; init; } = new();
|
||||||
|
|
||||||
|
public DataMandatoryInformation MandatoryInformation { get; init; } = new();
|
||||||
|
|
||||||
public DataTextSummarizer TextSummarizer { get; init; } = new();
|
public DataTextSummarizer TextSummarizer { get; init; } = new();
|
||||||
|
|
||||||
public DataTextContentCleaner TextContentCleaner { get; init; } = new();
|
public DataTextContentCleaner TextContentCleaner { get; init; } = new();
|
||||||
|
|||||||
@ -0,0 +1,96 @@
|
|||||||
|
using Lua;
|
||||||
|
|
||||||
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
public sealed record DataMandatoryInfo
|
||||||
|
{
|
||||||
|
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger<DataMandatoryInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The stable ID of the mandatory info.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the enterprise configuration plugin that provides this info.
|
||||||
|
/// </summary>
|
||||||
|
public Guid EnterpriseConfigurationPluginId { get; private init; } = Guid.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The title shown to the user.
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The configured version string. When it changes, the user must accept the text again.
|
||||||
|
/// </summary>
|
||||||
|
public string VersionText { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Markdown content shown to the user.
|
||||||
|
/// </summary>
|
||||||
|
public string Markdown { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The label of the acceptance button.
|
||||||
|
/// </summary>
|
||||||
|
public string AcceptButtonText { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The label of the reject button.
|
||||||
|
/// </summary>
|
||||||
|
public string RejectButtonText { get; private init; } = string.Empty;
|
||||||
|
|
||||||
|
public static bool TryParseConfiguration(int idx, LuaTable table, Guid configPluginId, out DataMandatoryInfo mandatoryInfo)
|
||||||
|
{
|
||||||
|
mandatoryInfo = new DataMandatoryInfo();
|
||||||
|
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The configured mandatory info {InfoIndex} does not contain a valid ID. The ID must be a valid GUID.", idx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("Title", out var titleValue) || !titleValue.TryRead<string>(out var title) || string.IsNullOrWhiteSpace(title))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The configured mandatory info {InfoIndex} does not contain a valid Title field.", idx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("Version", out var versionValue) || !versionValue.TryRead<string>(out var versionText) || string.IsNullOrWhiteSpace(versionText))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The configured mandatory info {InfoIndex} does not contain a valid Version field.", idx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("Markdown", out var markdownValue) || !markdownValue.TryRead<string>(out var markdown) || string.IsNullOrWhiteSpace(markdown))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The configured mandatory info {InfoIndex} does not contain a valid Markdown field.", idx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("AcceptButtonText", out var acceptButtonValue) || !acceptButtonValue.TryRead<string>(out var acceptButtonText) || string.IsNullOrWhiteSpace(acceptButtonText))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The configured mandatory info {InfoIndex} does not contain a valid AcceptButtonText field.", idx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("RejectButtonText", out var rejectButtonValue) || !rejectButtonValue.TryRead<string>(out var rejectButtonText) || string.IsNullOrWhiteSpace(rejectButtonText))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The configured mandatory info {InfoIndex} does not contain a valid RejectButtonText field.", idx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mandatoryInfo = new DataMandatoryInfo
|
||||||
|
{
|
||||||
|
Id = id.ToString(),
|
||||||
|
Title = title,
|
||||||
|
VersionText = versionText,
|
||||||
|
Markdown = markdown,
|
||||||
|
AcceptButtonText = acceptButtonText,
|
||||||
|
RejectButtonText = rejectButtonText,
|
||||||
|
EnterpriseConfigurationPluginId = configPluginId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
public sealed record DataMandatoryInfoAcceptance
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the mandatory info that was accepted.
|
||||||
|
/// </summary>
|
||||||
|
public string InfoId { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The accepted version string.
|
||||||
|
/// </summary>
|
||||||
|
public string AcceptedVersion { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The UTC time of the acceptance.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset AcceptedAtUtc { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The plugin that provided the accepted info at the time of acceptance.
|
||||||
|
/// </summary>
|
||||||
|
public Guid EnterpriseConfigurationPluginId { get; init; } = Guid.Empty;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
namespace AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
public sealed class DataMandatoryInformation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Persisted user acceptances for configured mandatory infos.
|
||||||
|
/// </summary>
|
||||||
|
public List<DataMandatoryInfoAcceptance> Acceptances { get; set; } = [];
|
||||||
|
|
||||||
|
public DataMandatoryInfoAcceptance? FindAcceptance(string infoId)
|
||||||
|
{
|
||||||
|
return this.Acceptances.LastOrDefault(acceptance => string.Equals(acceptance.InfoId, infoId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveLeftOverAcceptances(IEnumerable<DataMandatoryInfo> mandatoryInfos)
|
||||||
|
{
|
||||||
|
var validInfoIds = mandatoryInfos
|
||||||
|
.Select(info => info.Id)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var removedCount = this.Acceptances.RemoveAll(acceptance => !validInfoIds.Contains(acceptance.InfoId));
|
||||||
|
return removedCount > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
|
using AIStudio.Settings.DataModel;
|
||||||
using AIStudio.Tools.Services;
|
using AIStudio.Tools.Services;
|
||||||
|
|
||||||
using Lua;
|
using Lua;
|
||||||
@ -12,12 +13,18 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginConfiguration));
|
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginConfiguration));
|
||||||
|
|
||||||
private List<PluginConfigurationObject> configObjects = [];
|
private List<PluginConfigurationObject> configObjects = [];
|
||||||
|
private List<DataMandatoryInfo> mandatoryInfos = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
|
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<PluginConfigurationObject> ConfigObjects => this.configObjects;
|
public IEnumerable<PluginConfigurationObject> ConfigObjects => this.configObjects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of mandatory infos provided by this configuration plugin.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<DataMandatoryInfo> MandatoryInfos => this.mandatoryInfos;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True/false when explicitly configured in the plugin, otherwise null.
|
/// True/false when explicitly configured in the plugin, otherwise null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -91,6 +98,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
private bool TryProcessConfiguration(bool dryRun, out string message)
|
private bool TryProcessConfiguration(bool dryRun, out string message)
|
||||||
{
|
{
|
||||||
this.configObjects.Clear();
|
this.configObjects.Clear();
|
||||||
|
this.mandatoryInfos.Clear();
|
||||||
|
|
||||||
// Ensure that the main CONFIG table exists and is a valid Lua table:
|
// Ensure that the main CONFIG table exists and is a valid Lua table:
|
||||||
if (!this.state.Environment["CONFIG"].TryRead<LuaTable>(out var mainTable))
|
if (!this.state.Environment["CONFIG"].TryRead<LuaTable>(out var mainTable))
|
||||||
@ -151,6 +159,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
// Handle configured document analysis policies:
|
// Handle configured document analysis policies:
|
||||||
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, x => x.NextDocumentAnalysisPolicyNum, mainTable, this.Id, ref this.configObjects, dryRun);
|
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, x => x.NextDocumentAnalysisPolicyNum, mainTable, this.Id, ref this.configObjects, dryRun);
|
||||||
|
|
||||||
|
// Handle configured mandatory infos:
|
||||||
|
this.TryReadMandatoryInfos(mainTable);
|
||||||
|
|
||||||
// Config: preselected provider?
|
// Config: preselected provider?
|
||||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
|
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
|
||||||
|
|
||||||
@ -163,4 +174,25 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
message = string.Empty;
|
message = string.Empty;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TryReadMandatoryInfos(LuaTable mainTable)
|
||||||
|
{
|
||||||
|
if (!mainTable.TryGetValue("MANDATORY_INFOS", out var mandatoryInfosValue) || !mandatoryInfosValue.TryRead<LuaTable>(out var mandatoryInfosTable))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 1; i <= mandatoryInfosTable.ArrayLength; i++)
|
||||||
|
{
|
||||||
|
var luaMandatoryInfoValue = mandatoryInfosTable[i];
|
||||||
|
if (!luaMandatoryInfoValue.TryRead<LuaTable>(out var luaMandatoryInfoTable))
|
||||||
|
{
|
||||||
|
LOG.LogWarning("The table 'MANDATORY_INFOS' entry at index {Index} is not a valid table (config plugin id: {ConfigPluginId}).", i, this.Id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DataMandatoryInfo.TryParseConfiguration(i, luaMandatoryInfoTable, this.Id, out var mandatoryInfo))
|
||||||
|
this.mandatoryInfos.Add(mandatoryInfo);
|
||||||
|
else
|
||||||
|
LOG.LogWarning("The table 'MANDATORY_INFOS' entry at index {Index} does not contain a valid mandatory info (config plugin id: {ConfigPluginId}).", i, this.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -187,6 +187,10 @@ public static partial class PluginFactory
|
|||||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
|
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
|
||||||
wasConfigurationChanged = true;
|
wasConfigurationChanged = true;
|
||||||
|
|
||||||
|
// Check left-over mandatory info acceptances:
|
||||||
|
if (SETTINGS_MANAGER.ConfigurationData.MandatoryInformation.RemoveLeftOverAcceptances(GetMandatoryInfos()))
|
||||||
|
wasConfigurationChanged = true;
|
||||||
|
|
||||||
// Check for a preselected provider:
|
// Check for a preselected provider:
|
||||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.PreselectedProvider, AVAILABLE_PLUGINS))
|
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.PreselectedProvider, AVAILABLE_PLUGINS))
|
||||||
wasConfigurationChanged = true;
|
wasConfigurationChanged = true;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
|
using AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
namespace AIStudio.Tools.PluginSystem;
|
namespace AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
@ -127,4 +128,12 @@ public static partial class PluginFactory
|
|||||||
|
|
||||||
HOT_RELOAD_WATCHER.Dispose();
|
HOT_RELOAD_WATCHER.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<DataMandatoryInfo> GetMandatoryInfos()
|
||||||
|
{
|
||||||
|
return RUNNING_PLUGINS
|
||||||
|
.OfType<PluginConfiguration>()
|
||||||
|
.SelectMany(plugin => plugin.MandatoryInfos)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
3
app/MindWork AI Studio/Tools/Rust/AppExitResponse.cs
Normal file
3
app/MindWork AI Studio/Tools/Rust/AppExitResponse.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace AIStudio.Tools.Rust;
|
||||||
|
|
||||||
|
public sealed record AppExitResponse(bool Success, string ErrorMessage);
|
||||||
@ -1,5 +1,7 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using AIStudio.Tools.Rust;
|
||||||
|
|
||||||
namespace AIStudio.Tools.Services;
|
namespace AIStudio.Tools.Services;
|
||||||
|
|
||||||
public sealed partial class RustService
|
public sealed partial class RustService
|
||||||
@ -117,4 +119,35 @@ public sealed partial class RustService
|
|||||||
|
|
||||||
return await response.Content.ReadAsStringAsync();
|
return await response.Content.ReadAsStringAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests the Rust runtime to exit the entire desktop application.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> ExitApplication()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await this.http.PostAsync("/app/exit", null);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
this.logger?.LogError("Failed to exit the app due to network error: {StatusCode}.", response.StatusCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AppExitResponse>(this.jsonRustSerializerOptions);
|
||||||
|
if (result is null || !result.Success)
|
||||||
|
{
|
||||||
|
this.logger?.LogError("Failed to exit the app: {Error}", result?.ErrorMessage ?? "Unknown error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger?.LogInformation("Exit request sent to Rust runtime.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger?.LogError(ex, "Exception while requesting application exit.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -727,6 +727,13 @@ pub struct ShortcutResponse {
|
|||||||
error_message: String,
|
error_message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response for application exit requests.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AppExitResponse {
|
||||||
|
success: bool,
|
||||||
|
error_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal helper function to register a shortcut with its callback.
|
/// Internal helper function to register a shortcut with its callback.
|
||||||
/// This is used by both `register_shortcut` and `resume_shortcuts` to
|
/// This is used by both `register_shortcut` and `resume_shortcuts` to
|
||||||
/// avoid code duplication.
|
/// avoid code duplication.
|
||||||
@ -755,6 +762,34 @@ fn register_shortcut_with_callback(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Requests a controlled shutdown of the entire desktop application.
|
||||||
|
#[post("/app/exit")]
|
||||||
|
pub fn exit_app(_token: APIToken) -> Json<AppExitResponse> {
|
||||||
|
let main_window_lock = MAIN_WINDOW.lock().unwrap();
|
||||||
|
let main_window = match main_window_lock.as_ref() {
|
||||||
|
Some(window) => window,
|
||||||
|
None => {
|
||||||
|
error!(Source = "Tauri"; "Cannot exit app: main window not available.");
|
||||||
|
return Json(AppExitResponse {
|
||||||
|
success: false,
|
||||||
|
error_message: "Main window not available".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let app_handle = main_window.app_handle();
|
||||||
|
info!(Source = "Tauri"; "Controlled app exit was requested by the UI.");
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
time::sleep(Duration::from_millis(50)).await;
|
||||||
|
app_handle.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
Json(AppExitResponse {
|
||||||
|
success: true,
|
||||||
|
error_message: String::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Registers or updates a global shortcut. If the shortcut string is empty,
|
/// Registers or updates a global shortcut. If the shortcut string is empty,
|
||||||
/// the existing shortcut for that name will be unregistered.
|
/// the existing shortcut for that name will be unregistered.
|
||||||
#[post("/shortcuts/register", data = "<payload>")]
|
#[post("/shortcuts/register", data = "<payload>")]
|
||||||
|
|||||||
@ -76,6 +76,7 @@ pub fn start_runtime_api() {
|
|||||||
crate::app_window::select_file,
|
crate::app_window::select_file,
|
||||||
crate::app_window::select_files,
|
crate::app_window::select_files,
|
||||||
crate::app_window::save_file,
|
crate::app_window::save_file,
|
||||||
|
crate::app_window::exit_app,
|
||||||
crate::secret::get_secret,
|
crate::secret::get_secret,
|
||||||
crate::secret::store_secret,
|
crate::secret::store_secret,
|
||||||
crate::secret::delete_secret,
|
crate::secret::delete_secret,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user