mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-05 23:49:40 +00:00
Added an option for mandatory terms of use dialogs (#725)
Build and Release / Determine run mode (push) Has been cancelled
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,deb,updater, appimage,deb) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,deb,updater, appimage,deb) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
Build and Release / Determine run mode (push) Has been cancelled
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,deb,updater, appimage,deb) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,updater, dmg) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,deb,updater, appimage,deb) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
This commit is contained in:
1 parent
09258c7548
commit
8b11e2317b
29 files changed
+836
-21
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using Markdig;
|
||||
using System.Text;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
@@ -26,4 +27,123 @@ public static class Markdown
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
public static string RemoveSharedIndentation(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return string.Empty;
|
||||
|
||||
return RemoveSharedIndentation(value.AsSpan());
|
||||
}
|
||||
|
||||
private static string RemoveSharedIndentation(ReadOnlySpan<char> value)
|
||||
{
|
||||
var firstContentLineStart = -1;
|
||||
var lastContentLineStart = -1;
|
||||
var lastContentLineEnd = -1;
|
||||
var commonIndentation = int.MaxValue;
|
||||
var position = 0;
|
||||
|
||||
while (TryGetNextLine(value, position, out var lineStart, out var currentLineEnd, out var nextPosition))
|
||||
{
|
||||
var lineContent = value[lineStart..currentLineEnd];
|
||||
if (IsWhiteSpace(lineContent))
|
||||
{
|
||||
position = nextPosition;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstContentLineStart < 0)
|
||||
firstContentLineStart = lineStart;
|
||||
|
||||
lastContentLineStart = lineStart;
|
||||
lastContentLineEnd = currentLineEnd;
|
||||
commonIndentation = Math.Min(commonIndentation, CountIndentation(lineContent));
|
||||
position = nextPosition;
|
||||
}
|
||||
|
||||
if (firstContentLineStart < 0)
|
||||
return string.Empty;
|
||||
|
||||
if (commonIndentation == int.MaxValue)
|
||||
commonIndentation = 0;
|
||||
|
||||
var builder = new StringBuilder(lastContentLineEnd - firstContentLineStart);
|
||||
var shouldAppendLineBreak = false;
|
||||
position = firstContentLineStart;
|
||||
|
||||
while (TryGetNextLine(value, position, out var lineStart, out var lineEnd, out var nextPosition))
|
||||
{
|
||||
var lineContent = value[lineStart..lineEnd];
|
||||
|
||||
if (shouldAppendLineBreak)
|
||||
builder.Append('\n');
|
||||
|
||||
if (IsWhiteSpace(lineContent))
|
||||
shouldAppendLineBreak = true;
|
||||
else if (lineContent.Length > commonIndentation)
|
||||
{
|
||||
builder.Append(lineContent[commonIndentation..]);
|
||||
shouldAppendLineBreak = true;
|
||||
}
|
||||
else
|
||||
shouldAppendLineBreak = true;
|
||||
|
||||
if (lineStart == lastContentLineStart)
|
||||
break;
|
||||
|
||||
position = nextPosition;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static bool IsWhiteSpace(ReadOnlySpan<char> value)
|
||||
{
|
||||
foreach (var character in value)
|
||||
{
|
||||
if (!char.IsWhiteSpace(character))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int CountIndentation(ReadOnlySpan<char> value)
|
||||
{
|
||||
var indentation = 0;
|
||||
while (indentation < value.Length && char.IsWhiteSpace(value[indentation]))
|
||||
indentation++;
|
||||
|
||||
return indentation;
|
||||
}
|
||||
|
||||
private static bool TryGetNextLine(ReadOnlySpan<char> value, int position, out int lineStart, out int lineEnd, out int nextPosition)
|
||||
{
|
||||
if (position > value.Length)
|
||||
{
|
||||
lineStart = 0;
|
||||
lineEnd = 0;
|
||||
nextPosition = position;
|
||||
return false;
|
||||
}
|
||||
|
||||
lineStart = position;
|
||||
for (var i = position; i < value.Length; i++)
|
||||
{
|
||||
if (value[i] != '\n')
|
||||
continue;
|
||||
|
||||
lineEnd = i > lineStart && value[i - 1] == '\r'
|
||||
? i - 1
|
||||
: i;
|
||||
|
||||
nextPosition = i + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
lineEnd = value.Length;
|
||||
nextPosition = value.Length + 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ public static partial class Pandoc
|
||||
/// </summary>
|
||||
private static bool HAS_LOGGED_AVAILABILITY_CHECK_ONCE;
|
||||
|
||||
private static readonly HttpClient WEB_CLIENT = new();
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a Pandoc process by using the Pandoc process builder.
|
||||
/// </summary>
|
||||
@@ -181,21 +183,18 @@ public static partial class Pandoc
|
||||
// Download the latest Pandoc archive from GitHub:
|
||||
//
|
||||
var uri = await GenerateArchiveUriAsync();
|
||||
using (var client = new HttpClient())
|
||||
var response = await WEB_CLIENT.GetAsync(uri);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var response = await client.GetAsync(uri);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, TB("Pandoc was not installed successfully, because the archive was not found.")));
|
||||
LOG.LogError("Pandoc was not installed successfully, because the archive was not found (status code {0}): url='{1}', message='{2}'", response.StatusCode, uri, response.RequestMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Download the archive to the temporary file:
|
||||
await using var tempFileStream = File.Create(pandocTempDownloadFile);
|
||||
await response.Content.CopyToAsync(tempFileStream);
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, TB("Pandoc was not installed successfully, because the archive was not found.")));
|
||||
LOG.LogError("Pandoc was not installed successfully, because the archive was not found (status code {0}): url='{1}', message='{2}'", response.StatusCode, uri, response.RequestMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Download the archive to the temporary file:
|
||||
await using var tempFileStream = File.Create(pandocTempDownloadFile);
|
||||
await response.Content.CopyToAsync(tempFileStream);
|
||||
|
||||
if (uri.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ZipFile.ExtractToDirectory(pandocTempDownloadFile, installDir);
|
||||
@@ -245,9 +244,7 @@ public static partial class Pandoc
|
||||
/// <remarks>Version numbers can have the following formats: x.x, x.x.x or x.x.x.x</remarks>
|
||||
/// <returns>Latest Pandoc version number</returns>
|
||||
public static async Task<string> FetchLatestVersionAsync() {
|
||||
using var client = new HttpClient();
|
||||
var response = await client.GetAsync(LATEST_URL);
|
||||
|
||||
var response = await WEB_CLIENT.GetAsync(LATEST_URL);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
LOG.LogError("Code {StatusCode}: Could not fetch Pandoc's latest page: {Response}", response.StatusCode, response.RequestMessage);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
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 List<PluginConfigurationObject> configObjects = [];
|
||||
private List<DataMandatoryInfo> mandatoryInfos = [];
|
||||
|
||||
/// <summary>
|
||||
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
|
||||
/// </summary>
|
||||
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>
|
||||
/// True/false when explicitly configured in the plugin, otherwise null.
|
||||
/// </summary>
|
||||
@@ -91,6 +98,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
private bool TryProcessConfiguration(bool dryRun, out string message)
|
||||
{
|
||||
this.configObjects.Clear();
|
||||
this.mandatoryInfos.Clear();
|
||||
|
||||
// Ensure that the main CONFIG table exists and is a valid Lua table:
|
||||
if (!this.State.Environment["CONFIG"].TryRead<LuaTable>(out var mainTable))
|
||||
@@ -150,6 +158,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
|
||||
// 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);
|
||||
|
||||
// Handle configured mandatory infos:
|
||||
this.TryReadMandatoryInfos(mainTable);
|
||||
|
||||
// Config: preselected provider?
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,10 @@ public static partial class PluginFactory
|
||||
// Check document analysis policies:
|
||||
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check left-over mandatory info acceptances:
|
||||
if (SETTINGS_MANAGER.ConfigurationData.MandatoryInformation.RemoveLeftOverAcceptances(GetMandatoryInfos()))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Check for a preselected provider:
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.PreselectedProvider, AVAILABLE_PLUGINS))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
@@ -127,4 +128,12 @@ public static partial class PluginFactory
|
||||
|
||||
HOT_RELOAD_WATCHER.Dispose();
|
||||
}
|
||||
|
||||
public static IReadOnlyList<DataMandatoryInfo> GetMandatoryInfos()
|
||||
{
|
||||
return RUNNING_PLUGINS
|
||||
.OfType<PluginConfiguration>()
|
||||
.SelectMany(plugin => plugin.MandatoryInfos)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -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 AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
public sealed partial class RustService
|
||||
@@ -117,4 +119,35 @@ public sealed partial class RustService
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user