Compare commits

..
15 Commits
Author SHA1 Message Date
Thorsten Sommer c23f0f49f9 Prepared release v0.9.42 (#447) 2025-05-04 15:06:45 +02:00
Thorsten Sommer 7a08baf99b Added more text content to the I18N system (#446) 2025-05-04 14:59:30 +02:00
Thorsten Sommer cfc2103b43 Improved I18N system (#445) 2025-05-03 21:47:43 +02:00
Thorsten Sommer f8dbc186fb Improved chat name generation to remove line breaks, tabs, etc. (#444) 2025-05-03 16:25:51 +02:00
Thorsten Sommer 6041b42510 Refactored I18N key handling in build script (#443) 2025-05-03 16:17:23 +02:00
Thorsten Sommer 10dc03f33b Added file type filtering to file selection dialogs (#442) 2025-05-03 15:43:12 +02:00
Thorsten Sommer 437ea2c243 Refactored metadata initialization (#441) 2025-05-03 14:26:13 +02:00
Thorsten Sommer b0d4615506 Refactored PDFium initialization (#440) 2025-05-03 12:20:22 +02:00
Thorsten Sommer c4d9af18cd Add component to read PDF files into assistants (#439)
Build and Release / Read metadata (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) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (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) (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) (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) (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) (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
2025-05-02 23:09:50 +02:00
Thorsten Sommer 9996b810e2 Added "Changelog" writing style to assist in creating release notes (#438)
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
2025-05-02 12:11:23 +02:00
Thorsten Sommer 53ec4c2162 Improved app settings to apply language changes immediately (#437)
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
2025-05-02 11:50:14 +02:00
Thorsten Sommer 38facf3e04 Improved internal plugin maintenance (#436)
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) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
2025-05-02 11:27:21 +02:00
Thorsten Sommer 68404199ba Improved model selection for OpenAI by excluding unsupported models (#435)
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
2025-05-01 13:35:27 +02:00
Thorsten Sommer 08b8f42c25 Fixed localization assistant file loading issue in the release app (#434) 2025-05-01 13:23:57 +02:00
Thorsten Sommer 3c41fe2771 Fixed Markdown rendering issue and upgrade dependencies (#433)
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
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) (push) Blocked by required conditions
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) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
2025-05-01 12:54:04 +02:00
98 changed files with 4366 additions and 598 deletions

No files matched your search

+127
View File
@@ -167,6 +167,12 @@ jobs:
sed -i '' "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt
fi
# Next line is the necessary PDFium version.
# The format is '137.0.7123.0'. What we need
# is the '7123' part:
pdfium_version=$(sed -n '11p' metadata.txt)
pdfium_version=$(echo $pdfium_version | cut -d'.' -f3)
# Write the metadata to the environment:
echo "APP_VERSION=${app_version}" >> $GITHUB_ENV
echo "FORMATTED_APP_VERSION=${formatted_app_version}" >> $GITHUB_ENV
@@ -178,6 +184,7 @@ jobs:
echo "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $GITHUB_ENV
echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV
echo "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $GITHUB_ENV
echo "PDFIUM_VERSION=${pdfium_version}" >> $GITHUB_ENV
# Log the metadata:
echo "App version: '${formatted_app_version}'"
@@ -189,6 +196,7 @@ jobs:
echo "MudBlazor version: '${mud_blazor_version}'"
echo "Tauri version: '${tauri_version}'"
echo "Architecture: '${{ matrix.dotnet_runtime }}'"
echo "PDFium version: '${pdfium_version}'"
- name: Read and format metadata (Windows)
if: matrix.platform == 'windows-latest'
@@ -227,6 +235,12 @@ jobs:
# Write the changed metadata back to the file:
Set-Content -Path metadata.txt -Value $metadata
# Next line is the necessary PDFium version.
# The format is '137.0.7123.0'. What we need
# is the '7123' part:
$pdfium_version = $metadata[10]
$pdfium_version = $pdfium_version.Split('.')[2]
# Write the metadata to the environment:
Write-Output "APP_VERSION=${app_version}" >> $env:GITHUB_ENV
Write-Output "FORMATTED_APP_VERSION=${formatted_app_version}" >> $env:GITHUB_ENV
@@ -237,6 +251,7 @@ jobs:
Write-Output "RUST_VERSION=${rust_version}" >> $env:GITHUB_ENV
Write-Output "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $env:GITHUB_ENV
Write-Output "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $env:GITHUB_ENV
Write-Output "PDFIUM_VERSION=${pdfium_version}" >> $env:GITHUB_ENV
# Log the metadata:
Write-Output "App version: '${formatted_app_version}'"
@@ -248,6 +263,7 @@ jobs:
Write-Output "MudBlazor version: '${mud_blazor_version}'"
Write-Output "Tauri version: '${tauri_version}'"
Write-Output "Architecture: '${{ matrix.dotnet_runtime }}'"
Write-Output "PDFium version: '${pdfium_version}'"
- name: Setup .NET
uses: actions/setup-dotnet@v4
@@ -255,6 +271,117 @@ jobs:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
cache: true
cache-dependency-path: 'app/MindWork AI Studio/packages.lock.json'
- name: Deploy PDFium (Unix)
if: matrix.platform != 'windows-latest'
env:
PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }}
DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }}
run: |
set -e
# Target directory:
TLIB_DIR="runtime/resources/libraries"
mkdir -p "$TLIB_DIR"
case "${DOTNET_RUNTIME}" in
linux-x64)
PDFIUM_FILE="linux-x64.tgz"
LIB_SOURCE="lib/libpdfium.so"
LIB_TARGET="libpdfium.so"
;;
linux-arm64)
PDFIUM_FILE="linux-arm64.tgz"
LIB_SOURCE="lib/libpdfium.so"
LIB_TARGET="libpdfium.so"
;;
osx-x64)
PDFIUM_FILE="mac-x64.tgz"
LIB_SOURCE="lib/libpdfium.dylib"
LIB_TARGET="libpdfium.dylib"
;;
osx-arm64)
PDFIUM_FILE="mac-arm64.tgz"
LIB_SOURCE="lib/libpdfium.dylib"
LIB_TARGET="libpdfium.dylib"
;;
*)
echo "Unknown platform: ${DOTNET_RUNTIME}"
exit 1
;;
esac
PDFIUM_URL="https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F${PDFIUM_VERSION}/pdfium-${PDFIUM_FILE}"
echo "Download PDFium $PDFIUM_URL ..."
TMP=$(mktemp -d)
ARCHIVE="${TMP}/pdfium.tgz"
curl -fsSL -o "$ARCHIVE" "$PDFIUM_URL"
echo "Extracting PDFium ..."
tar xzf "$ARCHIVE" -C "$TMP"
SRC="${TMP}/${LIB_SOURCE}"
if [ ! -f "$SRC" ]; then
echo "Was not able to find PDFium source: $SRC"
exit 1
fi
echo "Copy PDFium from ${LIB_TARGET} to ${TLIB_DIR}/"
cp -f "$SRC" "$TLIB_DIR/$LIB_TARGET"
echo "Cleaning up ..."
rm -fr "$TMP"
- name: Install PDFium (Windows)
if: matrix.platform == 'windows-latest'
env:
PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }}
DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }}
run: |
$TLIB_DIR = "runtime\resources\libraries"
New-Item -ItemType Directory -Force -Path $TLIB_DIR | Out-Null
switch ($env:DOTNET_RUNTIME) {
"win-x64" {
$PDFIUM_FILE = "win-x64.tgz"
$LIB_SOURCE = "bin\pdfium.dll"
$LIB_TARGET = "pdfium.dll"
}
"win-arm64" {
$PDFIUM_FILE = "win-arm64.tgz"
$LIB_SOURCE = "bin\pdfium.dll"
$LIB_TARGET = "pdfium.dll"
}
default {
Write-Error "Unknown platform: $($env:DOTNET_RUNTIME)"
exit 1
}
}
$PDFIUM_URL = "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F$($env:PDFIUM_VERSION)/pdfium-$PDFIUM_FILE"
Write-Host "Download $PDFIUM_URL ..."
$TMP = New-TemporaryFile | Split-Path
$ARCHIVE = Join-Path $TMP "pdfium.tgz"
Invoke-WebRequest -Uri $PDFIUM_URL -OutFile $ARCHIVE
Write-Host "Extracting PDFium ..."
tar -xzf $ARCHIVE -C $TMP
$SRC = Join-Path $TMP $LIB_SOURCE
if (!(Test-Path $SRC)) {
Write-Error "Cannot find PDFium source: $SRC"
exit 1
}
$DEST = Join-Path $TLIB_DIR $LIB_TARGET
Copy-Item -Path $SRC -Destination $DEST -Force
Write-Host "Cleaning up ..."
Remove-Item $ARCHIVE -Force
Remove-Item $TMP -Recurse -Force
- name: Build .NET project
run: |
+5
View File
@@ -1,3 +1,8 @@
# Ignore pdfium library:
libpdfium.dylib
libpdfium.so
libpdfium.dll
# User-specific files
*.rsuser
*.suo
+5
View File
@@ -17,5 +17,10 @@ public sealed class CheckRidsCommand
{
Console.WriteLine($"- {rid}");
}
Console.WriteLine();
Console.WriteLine("The RID for the current OS and CPU is:");
var currentRid = Environment.GetCurrentRid();
Console.WriteLine($"- {currentRid}");
}
}
+37 -28
View File
@@ -10,6 +10,29 @@ namespace Build.Commands;
public sealed partial class CollectI18NKeysCommand
{
private const string START_TAG1 = """
T("
""";
private const string START_TAG2 = """
TB("
""";
private const string START_TAG3 = """
T(@"
""";
private const string END_TAG = """
")
""";
private static readonly (string Tag, int Length)[] START_TAGS =
[
(START_TAG1, START_TAG1.Length),
(START_TAG2, START_TAG2.Length),
(START_TAG3, START_TAG3.Length)
];
[Command("collect-i18n", Description = "Collect I18N keys")]
public async Task CollectI18NKeys()
{
@@ -133,36 +156,22 @@ public sealed partial class CollectI18NKeysCommand
private List<string> FindAllTextTags(ReadOnlySpan<char> fileContent)
{
const string START_TAG1 = """
T("
""";
const string START_TAG2 = """
TB("
""";
const string END_TAG = """
")
""";
(int Index, int Len) FindNextStart(ReadOnlySpan<char> content)
{
var startIdx1 = content.IndexOf(START_TAG1);
var startIdx2 = content.IndexOf(START_TAG2);
if (startIdx1 == -1 && startIdx2 == -1)
return (-1, 0);
if (startIdx1 == -1)
return (startIdx2, START_TAG2.Length);
if (startIdx2 == -1)
return (startIdx1, START_TAG1.Length);
if (startIdx1 < startIdx2)
return (startIdx1, START_TAG1.Length);
return (startIdx2, START_TAG2.Length);
var bestIndex = -1;
var bestLength = 0;
foreach (var (tag, length) in START_TAGS)
{
var index = content.IndexOf(tag);
if (index != -1 && (bestIndex == -1 || index < bestIndex))
{
bestIndex = index;
bestLength = length;
}
}
return (bestIndex, bestLength);
}
var matches = new List<string>();
+3
View File
@@ -0,0 +1,3 @@
namespace Build.Commands;
public record Library(string Path, string Filename);
+107
View File
@@ -0,0 +1,107 @@
using System.Formats.Tar;
using System.IO.Compression;
using SharedTools;
namespace Build.Commands;
public static class Pdfium
{
public static async Task InstallAsync(RID rid, string version)
{
Console.Write($"- Installing Pdfium {version} for {rid.ToUserFriendlyName()} ...");
var cwd = Environment.GetRustRuntimeDirectory();
var pdfiumTmpDownloadPath = Path.GetTempFileName();
var pdfiumTmpExtractPath = Directory.CreateTempSubdirectory();
var pdfiumUrl = GetPdfiumDownloadUrl(rid, version);
//
// Download the file:
//
Console.Write(" downloading ...");
using (var client = new HttpClient())
{
var response = await client.GetAsync(pdfiumUrl);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($" failed to download Pdfium {version} for {rid.ToUserFriendlyName()}");
return;
}
await using var fileStream = File.Create(pdfiumTmpDownloadPath);
await response.Content.CopyToAsync(fileStream);
}
//
// Extract the downloaded file:
//
Console.Write(" extracting ...");
await using(var tgzStream = File.Open(pdfiumTmpDownloadPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
await using var uncompressedStream = new GZipStream(tgzStream, CompressionMode.Decompress);
await TarFile.ExtractToDirectoryAsync(uncompressedStream, pdfiumTmpExtractPath.FullName, true);
}
//
// Copy the library to the target directory:
//
Console.Write(" deploying ...");
var library = GetLibraryPath(rid);
if (string.IsNullOrEmpty(library.Path))
{
Console.WriteLine($" failed to find the library path for {rid.ToUserFriendlyName()}");
return;
}
var pdfiumLibSourcePath = Path.Join(pdfiumTmpExtractPath.FullName, library.Path);
var pdfiumLibTargetPath = Path.Join(cwd, "resources", "libraries", library.Filename);
if (!File.Exists(pdfiumLibSourcePath))
{
Console.WriteLine($" failed to find the library file '{pdfiumLibSourcePath}'");
return;
}
Directory.CreateDirectory(Path.Join(cwd, "resources", "libraries"));
if (File.Exists(pdfiumLibTargetPath))
File.Delete(pdfiumLibTargetPath);
File.Copy(pdfiumLibSourcePath, pdfiumLibTargetPath);
//
// Cleanup:
//
Console.Write(" cleaning up ...");
File.Delete(pdfiumTmpDownloadPath);
Directory.Delete(pdfiumTmpExtractPath.FullName, true);
Console.WriteLine(" done.");
}
private static Library GetLibraryPath(RID rid) => rid switch
{
RID.LINUX_ARM64 or RID.LINUX_X64 => new(Path.Join("lib", "libpdfium.so"), "libpdfium.so"),
RID.OSX_ARM64 or RID.OSX_X64 => new(Path.Join("lib", "libpdfium.dylib"), "libpdfium.dylib"),
RID.WIN_ARM64 or RID.WIN_X64 => new(Path.Join("bin", "pdfium.dll"), "pdfium.dll"),
_ => new(string.Empty, string.Empty),
};
private static string GetPdfiumDownloadUrl(RID rid, string version)
{
var baseUrl = $"https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F{version}/pdfium-";
return rid switch
{
RID.LINUX_ARM64 => $"{baseUrl}linux-arm64.tgz",
RID.LINUX_X64 => $"{baseUrl}linux-x64.tgz",
RID.OSX_ARM64 => $"{baseUrl}mac-arm64.tgz",
RID.OSX_X64 => $"{baseUrl}mac-x64.tgz",
RID.WIN_ARM64 => $"{baseUrl}win-arm64.zip",
RID.WIN_X64 => $"{baseUrl}win-x64.zip",
_ => string.Empty,
};
}
}
+99 -90
View File
@@ -105,105 +105,102 @@ public sealed partial class UpdateMetadataCommands
// Build the .NET project:
//
var pathApp = Environment.GetAIStudioDirectory();
var rids = Environment.GetRidsForCurrentOS();
foreach (var rid in rids)
{
Console.WriteLine("==============================");
await this.UpdateArchitecture(rid);
Console.Write($"- Start .NET build for '{rid.AsMicrosoftRid()}' ...");
await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.AsMicrosoftRid()}");
var dotnetBuildOutput = await this.ReadCommandOutput(pathApp, "dotnet", $"publish --configuration release --runtime {rid.AsMicrosoftRid()} --disable-build-servers --force");
var dotnetBuildOutputLines = dotnetBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
var foundIssue = false;
foreach (var buildOutputLine in dotnetBuildOutputLines)
{
if(buildOutputLine.Contains(" error ") || buildOutputLine.Contains("#warning"))
{
if(!foundIssue)
{
foundIssue = true;
Console.WriteLine();
Console.WriteLine("- Build has issues:");
}
Console.Write(" - ");
Console.WriteLine(buildOutputLine);
}
}
if(foundIssue)
Console.WriteLine();
else
{
Console.WriteLine(" completed successfully.");
}
//
// Prepare the .NET artifact to be used by Tauri as sidecar:
//
var os = Environment.GetOS();
var tauriSidecarArtifactName = rid switch
{
RID.WIN_X64 => "mindworkAIStudioServer-x86_64-pc-windows-msvc.exe",
RID.WIN_ARM64 => "mindworkAIStudioServer-aarch64-pc-windows-msvc.exe",
RID.LINUX_X64 => "mindworkAIStudioServer-x86_64-unknown-linux-gnu",
RID.LINUX_ARM64 => "mindworkAIStudioServer-aarch64-unknown-linux-gnu",
RID.OSX_ARM64 => "mindworkAIStudioServer-aarch64-apple-darwin",
RID.OSX_X64 => "mindworkAIStudioServer-x86_64-apple-darwin",
_ => string.Empty,
};
if (string.IsNullOrWhiteSpace(tauriSidecarArtifactName))
{
Console.WriteLine($"- Error: Unsupported rid '{rid.AsMicrosoftRid()}'.");
return;
}
var rid = Environment.GetCurrentRid();
var dotnetArtifactPath = Path.Combine(pathApp, "bin", "dist");
if(!Directory.Exists(dotnetArtifactPath))
Directory.CreateDirectory(dotnetArtifactPath);
var dotnetArtifactFilename = os switch
Console.WriteLine("==============================");
await this.UpdateArchitecture(rid);
var pdfiumVersion = await this.ReadPdfiumVersion();
await Pdfium.InstallAsync(rid, pdfiumVersion);
Console.Write($"- Start .NET build for {rid.ToUserFriendlyName()} ...");
await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.AsMicrosoftRid()}");
var dotnetBuildOutput = await this.ReadCommandOutput(pathApp, "dotnet", $"publish --configuration release --runtime {rid.AsMicrosoftRid()} --disable-build-servers --force");
var dotnetBuildOutputLines = dotnetBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
var foundIssue = false;
foreach (var buildOutputLine in dotnetBuildOutputLines)
{
if(buildOutputLine.Contains(" error ") || buildOutputLine.Contains("#warning"))
{
"windows" => "mindworkAIStudio.exe",
_ => "mindworkAIStudio",
};
var dotnetPublishedPath = Path.Combine(pathApp, "bin", "release", Environment.DOTNET_VERSION, rid.AsMicrosoftRid(), "publish", dotnetArtifactFilename);
var finalDestination = Path.Combine(dotnetArtifactPath, tauriSidecarArtifactName);
if(File.Exists(dotnetPublishedPath))
Console.WriteLine("- Published .NET artifact found.");
else
{
Console.WriteLine($"- Error: Published .NET artifact not found: '{dotnetPublishedPath}'.");
return;
}
if(!foundIssue)
{
foundIssue = true;
Console.WriteLine();
Console.WriteLine("- Build has issues:");
}
Console.Write($"- Move the .NET artifact to the Tauri sidecar destination ...");
try
{
File.Move(dotnetPublishedPath, finalDestination, true);
Console.WriteLine(" done.");
Console.Write(" - ");
Console.WriteLine(buildOutputLine);
}
catch (Exception e)
{
Console.WriteLine(" failed.");
Console.WriteLine($" - Error: {e.Message}");
}
}
if(foundIssue)
Console.WriteLine();
else
{
Console.WriteLine(" completed successfully.");
}
//
// Prepare the .NET artifact to be used by Tauri as sidecar:
//
var os = Environment.GetOS();
var tauriSidecarArtifactName = rid switch
{
RID.WIN_X64 => "mindworkAIStudioServer-x86_64-pc-windows-msvc.exe",
RID.WIN_ARM64 => "mindworkAIStudioServer-aarch64-pc-windows-msvc.exe",
RID.LINUX_X64 => "mindworkAIStudioServer-x86_64-unknown-linux-gnu",
RID.LINUX_ARM64 => "mindworkAIStudioServer-aarch64-unknown-linux-gnu",
RID.OSX_ARM64 => "mindworkAIStudioServer-aarch64-apple-darwin",
RID.OSX_X64 => "mindworkAIStudioServer-x86_64-apple-darwin",
_ => string.Empty,
};
if (string.IsNullOrWhiteSpace(tauriSidecarArtifactName))
{
Console.WriteLine($"- Error: Unsupported rid '{rid.AsMicrosoftRid()}'.");
return;
}
var dotnetArtifactPath = Path.Combine(pathApp, "bin", "dist");
if(!Directory.Exists(dotnetArtifactPath))
Directory.CreateDirectory(dotnetArtifactPath);
var dotnetArtifactFilename = os switch
{
"windows" => "mindworkAIStudio.exe",
_ => "mindworkAIStudio",
};
var dotnetPublishedPath = Path.Combine(pathApp, "bin", "release", Environment.DOTNET_VERSION, rid.AsMicrosoftRid(), "publish", dotnetArtifactFilename);
var finalDestination = Path.Combine(dotnetArtifactPath, tauriSidecarArtifactName);
if(File.Exists(dotnetPublishedPath))
Console.WriteLine("- Published .NET artifact found.");
else
{
Console.WriteLine($"- Error: Published .NET artifact not found: '{dotnetPublishedPath}'.");
return;
}
Console.Write($"- Move the .NET artifact to the Tauri sidecar destination ...");
try
{
File.Move(dotnetPublishedPath, finalDestination, true);
Console.WriteLine(" done.");
}
catch (Exception e)
{
Console.WriteLine(" failed.");
Console.WriteLine($" - Error: {e.Message}");
}
//
// Build the Rust project / runtime:
//
Console.WriteLine("==============================");
Console.WriteLine("- Start building the Rust runtime ...");
var pathRuntime = Environment.GetRustRuntimeDirectory();
@@ -314,6 +311,18 @@ public sealed partial class UpdateMetadataCommands
await File.WriteAllTextAsync(changelogCodePath, changelogCode, Environment.UTF8_NO_BOM);
Console.WriteLine(" done.");
}
private async Task<string> ReadPdfiumVersion()
{
const int PDFIUM_VERSION_INDEX = 10;
var pathMetadata = Environment.GetMetadataPath();
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
var currentPdfiumVersion = lines[PDFIUM_VERSION_INDEX].Trim();
var shortVersion = currentPdfiumVersion.Split('.')[2];
return shortVersion;
}
private async Task UpdateArchitecture(RID rid)
{
@@ -321,7 +330,7 @@ public sealed partial class UpdateMetadataCommands
var pathMetadata = Environment.GetMetadataPath();
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
Console.Write("- Updating architecture ...");
Console.Write($"- Updating architecture to {rid.ToUserFriendlyName()} ...");
lines[ARCHITECTURE_INDEX] = rid.AsMicrosoftRid();
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
+1 -1
View File
@@ -17,7 +17,7 @@ public sealed class UpdateWebAssetsCommand
Console.WriteLine("=========================");
Console.Write("- Updating web assets ...");
var rid = Environment.GetRidsForCurrentOS().First();
var rid = Environment.GetCurrentRid();
var cwd = Environment.GetAIStudioDirectory();
var contentPath = Path.Join(cwd, "bin", "release", Environment.DOTNET_VERSION, rid.AsMicrosoftRid(), "publish", "wwwroot", "_content");
var isMudBlazorDirectoryPresent = Directory.Exists(Path.Join(contentPath, "MudBlazor"));
+34
View File
@@ -76,4 +76,38 @@ public static class Environment
Console.WriteLine($"Error: Unsupported OS '{RuntimeInformation.OSDescription}'");
return [];
}
public static RID GetCurrentRid()
{
var arch = RuntimeInformation.ProcessArchitecture;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return arch switch
{
Architecture.X64 => RID.WIN_X64,
Architecture.Arm64 => RID.WIN_ARM64,
_ => RID.NONE,
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return arch switch
{
Architecture.X64 => RID.OSX_X64,
Architecture.Arm64 => RID.OSX_ARM64,
_ => RID.NONE,
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return arch switch
{
Architecture.X64 => RID.LINUX_X64,
Architecture.Arm64 => RID.LINUX_ARM64,
_ => RID.NONE,
};
Console.WriteLine($"Error: Unsupported OS '{RuntimeInformation.OSDescription}'");
return RID.NONE;
}
}
+1
View File
@@ -9,6 +9,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PDF/@EntryIndexedValue">PDF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RAG/@EntryIndexedValue">RAG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RID/@EntryIndexedValue">RID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TB/@EntryIndexedValue">TB</s:String>
@@ -2,14 +2,16 @@ namespace AIStudio.Assistants.EMail;
public static class WritingStylesExtensions
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(WritingStylesExtensions).Namespace, nameof(WritingStylesExtensions));
public static string Name(this WritingStyles style) => style switch
{
WritingStyles.ACADEMIC => "Academic",
WritingStyles.PERSONAL => "Personal",
WritingStyles.BUSINESS_FORMAL => "Business formal",
WritingStyles.BUSINESS_INFORMAL => "Business informal",
WritingStyles.ACADEMIC => TB("Academic"),
WritingStyles.PERSONAL => TB("Personal"),
WritingStyles.BUSINESS_FORMAL => TB("Business formal"),
WritingStyles.BUSINESS_INFORMAL => TB("Business informal"),
_ => "Not specified",
_ => TB("Not specified"),
};
public static string Prompt(this WritingStyles style) => style switch
@@ -182,7 +182,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
var filePath = Path.Join(Environment.CurrentDirectory, "Assistants", "I18N");
var resourceFileProvider = new PhysicalFileProvider(filePath);
#else
var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Assistants.I18N");
var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Assistants/I18N");
#endif
var file = resourceFileProvider.GetFileInfo("allTexts.lua");
File diff suppressed because it is too large. Load diff
@@ -1,4 +1,5 @@
@attribute [Route(Routes.ASSISTANT_LEGAL_CHECK)]
@using AIStudio.Settings.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogLegalCheck>
@if (!this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)
@@ -6,6 +7,11 @@
<ReadWebContent @bind-Content="@this.inputLegalDocument" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions && this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions && this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent)"/>
}
@if (PreviewFeatures.PRE_READ_PDF_2025.IsEnabled(this.SettingsManager))
{
<ReadPDFContent @bind-PDFContent="@this.inputLegalDocument"/>
}
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputLegalDocument" Validation="@this.ValidatingLegalDocument" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="@T("Legal document")" Variant="Variant.Outlined" Lines="12" AutoGrow="@true" MaxLines="24" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputQuestions" Validation="@this.ValidatingQuestions" AdornmentIcon="@Icons.Material.Filled.QuestionAnswer" Adornment="Adornment.Start" Label="@T("Your questions")" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
@@ -2,12 +2,14 @@ namespace AIStudio.Assistants.RewriteImprove;
public static class SentenceStructureExtensions
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(SentenceStructureExtensions).Namespace, nameof(SentenceStructureExtensions));
public static string Name(this SentenceStructure sentenceStructure) => sentenceStructure switch
{
SentenceStructure.ACTIVE => "Active voice",
SentenceStructure.PASSIVE => "Passive voice",
SentenceStructure.ACTIVE => TB("Active voice"),
SentenceStructure.PASSIVE => TB("Passive voice"),
_ => "Not Specified",
_ => TB("Not Specified"),
};
public static string Prompt(this SentenceStructure sentenceStructure) => sentenceStructure switch
@@ -13,4 +13,5 @@ public enum WritingStyles
MARKETING,
ACADEMIC,
LEGAL,
CHANGELOG,
}
@@ -2,19 +2,22 @@ namespace AIStudio.Assistants.RewriteImprove;
public static class WritingStylesExtensions
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(WritingStylesExtensions).Namespace, nameof(WritingStylesExtensions));
public static string Name(this WritingStyles style) => style switch
{
WritingStyles.EVERYDAY => "Everyday (personal texts, social media)",
WritingStyles.BUSINESS => "Business (business emails, reports, presentations)",
WritingStyles.SCIENTIFIC => "Scientific (scientific papers, research reports)",
WritingStyles.JOURNALISTIC => "Journalistic (magazines, newspapers, news)",
WritingStyles.LITERARY => "Literary (fiction, poetry)",
WritingStyles.TECHNICAL => "Technical (manuals, documentation)",
WritingStyles.MARKETING => "Marketing (advertisements, sales texts)",
WritingStyles.ACADEMIC => "Academic (essays, seminar papers)",
WritingStyles.LEGAL => "Legal (legal texts, contracts)",
WritingStyles.EVERYDAY => TB("Everyday (personal texts, social media)"),
WritingStyles.BUSINESS => TB("Business (business emails, reports, presentations)"),
WritingStyles.SCIENTIFIC => TB("Scientific (scientific papers, research reports)"),
WritingStyles.JOURNALISTIC => TB("Journalistic (magazines, newspapers, news)"),
WritingStyles.LITERARY => TB("Literary (fiction, poetry)"),
WritingStyles.TECHNICAL => TB("Technical (manuals, documentation)"),
WritingStyles.MARKETING => TB("Marketing (advertisements, sales texts)"),
WritingStyles.ACADEMIC => TB("Academic (essays, seminar papers)"),
WritingStyles.LEGAL => TB("Legal (legal texts, contracts)"),
WritingStyles.CHANGELOG => TB("Changelog (release notes, version history)"),
_ => "Not specified",
_ => TB("Not specified"),
};
public static string Prompt(this WritingStyles style) => style switch
@@ -28,6 +31,7 @@ public static class WritingStylesExtensions
WritingStyles.MARKETING => "Use a marketing style like for advertisements, sales texts, and promotional content. Most important is persuasiveness and engagement.",
WritingStyles.ACADEMIC => "Use a academic style like for essays, seminar papers, and academic writing. Most important is clarity and objectivity.",
WritingStyles.LEGAL => "Use a legal style like for legal texts, contracts, and official documents. Most important is precision and legal correctness. Use formal legal language.",
WritingStyles.CHANGELOG => "Use a changelog style like for release notes, version history, and software updates. Most important is clarity and conciseness. The changelog is structured as a Markdown list. Most list items start with one of the following verbs: Added, Changed, Deprecated, Removed, Fixed, Refactored, Improved, or Upgraded -- these verbs should also translated to the target language. Also, changelogs use past tense.",
_ => "Keep the style of the text as it is.",
};
@@ -1,4 +1,5 @@
@attribute [Route(Routes.ASSISTANT_SUMMARIZER)]
@using AIStudio.Settings.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogTextSummarizer>
@if (!this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)
@@ -6,6 +7,11 @@
<ReadWebContent @bind-Content="@this.inputText" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions && this.SettingsManager.ConfigurationData.TextSummarizer.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions && this.SettingsManager.ConfigurationData.TextSummarizer.PreselectContentCleanerAgent)"/>
}
@if (PreviewFeatures.PRE_READ_PDF_2025.IsEnabled(this.SettingsManager))
{
<ReadPDFContent @bind-PDFContent="@this.inputText"/>
}
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="@T("Your input")" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.Name())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" @bind-OtherInput="@this.customTargetLanguage" OtherValue="CommonLanguages.OTHER" LabelOther="@T("Custom target language")" ValidateOther="@this.ValidateCustomLanguage" />
<EnumSelection T="Complexity" NameFunc="@(complexity => complexity.Name())" @bind-Value="@this.selectedComplexity" Icon="@Icons.Material.Filled.Layers" Label="@T("Target complexity")" AllowOther="@true" @bind-OtherInput="@this.expertInField" OtherValue="Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS" LabelOther="@T("Your expertise")" ValidateOther="@this.ValidateExpertInField" />
@@ -2,18 +2,20 @@ namespace AIStudio.Assistants.TextSummarizer;
public static class ComplexityExtensions
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(ComplexityExtensions).Namespace, nameof(ComplexityExtensions));
public static string Name(this Complexity complexity) => complexity switch
{
Complexity.NO_CHANGE => "No change in complexity",
Complexity.NO_CHANGE => TB("No change in complexity"),
Complexity.SIMPLE_LANGUAGE => "Simple language, e.g., for children",
Complexity.TEEN_LANGUAGE => "Teen language, e.g., for teenagers",
Complexity.EVERYDAY_LANGUAGE => "Everyday language, e.g., for adults",
Complexity.POPULAR_SCIENCE_LANGUAGE => "Popular science language, e.g., for people interested in science",
Complexity.SCIENTIFIC_LANGUAGE_FIELD_EXPERTS => "Scientific language for experts in this field",
Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS => "Scientific language for experts from other fields (interdisciplinary)",
Complexity.SIMPLE_LANGUAGE => TB("Simple language, e.g., for children"),
Complexity.TEEN_LANGUAGE => TB("Teen language, e.g., for teenagers"),
Complexity.EVERYDAY_LANGUAGE => TB("Everyday language, e.g., for adults"),
Complexity.POPULAR_SCIENCE_LANGUAGE => TB("Popular science language, e.g., for people interested in science"),
Complexity.SCIENTIFIC_LANGUAGE_FIELD_EXPERTS => TB("Scientific language for experts in this field"),
Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS => TB("Scientific language for experts from other fields (interdisciplinary)"),
_ => "No change in complexity",
_ => TB("No change in complexity"),
};
public static string Prompt(this Complexity complexity, string expertInField) => complexity switch
@@ -1,4 +1,5 @@
@attribute [Route(Routes.ASSISTANT_TRANSLATION)]
@using AIStudio.Settings.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogTranslation>
@if (!this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)
@@ -6,6 +7,11 @@
<ReadWebContent @bind-Content="@this.inputText" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.Translation.PreselectOptions && this.SettingsManager.ConfigurationData.Translation.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.Translation.PreselectOptions && this.SettingsManager.ConfigurationData.Translation.PreselectContentCleanerAgent)"/>
}
@if (PreviewFeatures.PRE_READ_PDF_2025.IsEnabled(this.SettingsManager))
{
<ReadPDFContent @bind-PDFContent="@this.inputText"/>
}
<MudTextSwitch Label="@T("Live translation")" @bind-Value="@this.liveTranslation" LabelOn="@T("Live translation")" LabelOff="@T("No live translation")"/>
@if (this.liveTranslation)
{
@@ -13,6 +13,7 @@ public partial class Changelog
public static readonly Log[] LOGS =
[
new (217, "v0.9.42, build 217 (2025-05-04 13:03 UTC)", "v0.9.42.md"),
new (216, "v0.9.41, build 216 (2025-04-27 14:18 UTC)", "v0.9.41.md"),
new (215, "v0.9.40, build 215 (2025-04-20 13:30 UTC)", "v0.9.40.md"),
new (214, "v0.9.39, build 214 (2025-04-07 17:39 UTC)", "v0.9.39.md"),
@@ -270,7 +270,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private bool CanThreadBeSaved => this.ChatThread is not null && this.ChatThread.Blocks.Count > 0;
private string TooltipAddChatToWorkspace => string.Format(T(@"Start new chat in workspace ""{0}"""), this.currentWorkspaceName);
private string TooltipAddChatToWorkspace => string.Format(T("""
Start new chat in workspace "{0}"
"""), this.currentWorkspaceName);
private string UserInputStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.Provider.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager) : string.Empty;
@@ -291,6 +293,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// We select the first 10 words of the user input:
var words = firstUserInput.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var threadName = string.Join(' ', words.Take(10));
threadName = threadName.Trim();
// Remove all line breaks:
threadName = threadName.Replace("\r", string.Empty);
threadName = threadName.Replace("\n", " ");
threadName = threadName.Replace("\t", " ");
// If the thread name is empty, we use a default name:
if (string.IsNullOrWhiteSpace(threadName))
@@ -0,0 +1,4 @@
@inherits MSGComponentBase
<MudButton StartIcon="@Icons.Material.Filled.Description" OnClick="async () => await this.SelectFile()" Variant="Variant.Filled" Class="mb-3">
@T("Use PDF content as input")
</MudButton>
@@ -0,0 +1,31 @@
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class ReadPDFContent : MSGComponentBase
{
[Parameter]
public string PDFContent { get; set; } = string.Empty;
[Parameter]
public EventCallback<string> PDFContentChanged { get; set; }
[Inject]
private RustService RustService { get; init; } = null!;
private async Task SelectFile()
{
var pdfFile = await this.RustService.SelectFile(T("Select PDF file"), FileTypeFilter.PDF);
if (pdfFile.UserCancelled)
return;
if(!File.Exists(pdfFile.SelectedFilePath))
return;
var pdfText = await this.RustService.GetPDFText(pdfFile.SelectedFilePath);
await this.PDFContentChanged.InvokeAsync(pdfText);
}
}
@@ -1,5 +1,5 @@
@inherits MSGComponentBase
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<MudPaper Class="pa-3 mb-3 border-dashed border rounded-lg">
<MudTextSwitch Label="@T("Read content from web?")" Disabled="@this.AgentIsRunning" @bind-Value="@this.showWebContentReader" LabelOn="@T("Show web content options")" LabelOff="@T("Hide web content options")" />
@if (this.showWebContentReader)
{
@@ -1,4 +1,5 @@
using AIStudio.Settings;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
@@ -22,6 +23,9 @@ public partial class SelectFile : ComponentBase
[Parameter]
public string FileDialogTitle { get; set; } = "Select File";
[Parameter]
public FileTypeFilter? Filter { get; set; }
[Parameter]
public Func<string, string?> Validation { get; set; } = _ => null;
@@ -55,7 +59,7 @@ public partial class SelectFile : ComponentBase
private async Task OpenFileDialog()
{
var response = await this.RustService.SelectFile(this.FileDialogTitle, string.IsNullOrWhiteSpace(this.File) ? null : this.File);
var response = await this.RustService.SelectFile(this.FileDialogTitle, this.Filter, string.IsNullOrWhiteSpace(this.File) ? null : this.File);
this.Logger.LogInformation($"The user selected the file '{response.SelectedFilePath}'.");
if (!response.UserCancelled)
@@ -6,11 +6,11 @@
@if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
{
<ConfigurationSelect OptionDescription="@T("Language behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguageBehavior)" Data="@ConfigurationSelectDataFactory.GetLangBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.LanguageBehavior = selectedValue)" OptionHelp="@T("Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?")"/>
<ConfigurationSelect OptionDescription="@T("Language behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguageBehavior)" Data="@ConfigurationSelectDataFactory.GetLangBehaviorData()" SelectionUpdate="@(selectedValue => _ = this.UpdateLangBehaviour(selectedValue))" OptionHelp="@T("Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?")"/>
@if (this.SettingsManager.ConfigurationData.App.LanguageBehavior is LangBehavior.MANUAL)
{
<ConfigurationSelect OptionDescription="@T("Language")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.LanguagePluginId = selectedValue)" OptionHelp="@T("Select the language for the app.")"/>
<ConfigurationSelect OptionDescription="@T("Language")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => _ = this.UpdateManuallySelectedLanguage(selectedValue))" OptionHelp="@T("Select the language for the app.")"/>
}
}
@@ -9,4 +9,16 @@ public partial class SettingsPanelApp : SettingsPanelBase
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures);
}
private async Task UpdateLangBehaviour(LangBehavior behavior)
{
this.SettingsManager.ConfigurationData.App.LanguageBehavior = behavior;
await this.MessageBus.SendMessage<bool>(this, Event.PLUGINS_RELOADED);
}
private async Task UpdateManuallySelectedLanguage(Guid pluginId)
{
this.SettingsManager.ConfigurationData.App.LanguagePluginId = pluginId;
await this.MessageBus.SendMessage<bool>(this, Event.PLUGINS_RELOADED);
}
}
@@ -79,7 +79,7 @@
@* ReSharper restore Asp.Entity *@
}
<MudField FullWidth="true" Label="Model selection" Variant="Variant.Outlined" Class="mb-3">
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
{
@@ -206,6 +206,10 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
break;
case Event.PLUGINS_RELOADED:
this.Lang = await this.SettingsManager.GetActiveLanguagePlugin();
I18N.Init(this.Lang);
this.LoadNavItems();
await this.InvokeAsync(this.StateHasChanged);
break;
}
@@ -47,11 +47,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.0.0" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.1.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.4" />
<PackageReference Include="MudBlazor" Version="8.5.1" />
<PackageReference Include="MudBlazor.Markdown" Version="8.5.1" />
<PackageReference Include="MudBlazor" Version="8.6.0" />
<PackageReference Include="MudBlazor.Markdown" Version="8.5.1.1" />
<PackageReference Include="ReverseMarkdown" Version="4.6.0" />
<PackageReference Include="LuaCSharp" Version="0.4.2" />
</ItemGroup>
@@ -80,6 +80,7 @@
<MetaTauriVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 7 ])</MetaTauriVersion>
<MetaAppCommitHash>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 8 ])</MetaAppCommitHash>
<MetaArchitecture>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 9 ])</MetaArchitecture>
<MetaPdfiumVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 10 ])</MetaPdfiumVersion>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
@@ -104,6 +105,9 @@
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataArchitecture">
<_Parameter1>$(MetaArchitecture)</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataLibraries">
<_Parameter1>$(MetaPdfiumVersion)</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Target>
+2
View File
@@ -18,6 +18,7 @@
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@VersionDotnetSdk"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@VersionDotnetRuntime"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@VersionRust"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.DocumentScanner" Text="@VersionPdfium"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Widgets" Text="@MudBlazorVersion"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@TauriVersion"/>
<MudListItem T="string" Icon="@Icons.Material.Outlined.Translate" Text="@this.OSLanguage"/>
@@ -111,6 +112,7 @@
<ThirdPartyComponent Name="rcgen" Developer="RustTLS developers, est31 & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rustls/rcgen/blob/main/LICENSE" RepositoryUrl="https://github.com/rustls/rcgen" UseCase="@T("For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose.")"/>
<ThirdPartyComponent Name="file-format" Developer="Mickaël Malécot & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mmalecot/file-format/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/mmalecot/file-format" UseCase="@T("This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file.")"/>
<ThirdPartyComponent Name="calamine" Developer="Johann Tuffe, Joel Natividad, Eric Jolibois, Dmitriy & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tafia/calamine/blob/master/LICENSE-MIT.md" RepositoryUrl="https://github.com/tafia/calamine" UseCase="@T("This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.")"/>
<ThirdPartyComponent Name="PDFium" Developer="Lei Zhang, Tom Sepez, Dan Sinclair, and Foxit, Google, Chromium, Collabora, Ada, DocsCorp, Dropbox, Microsoft, and PSPDFKit Teams & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://pdfium.googlesource.com/pdfium/+/refs/heads/main/LICENSE" RepositoryUrl="https://pdfium.googlesource.com/pdfium" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
<ThirdPartyComponent Name="pdfium-render" Developer="Alastair Carey, Dorian Rudolph & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/ajrcarey/pdfium-render/blob/master/LICENSE.md" RepositoryUrl="https://github.com/ajrcarey/pdfium-render" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
<ThirdPartyComponent Name="sys-locale" Developer="1Password Team, ComplexSpaces & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/sys-locale/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/1Password/sys-locale" UseCase="@T("This library is used to determine the language of the operating system. This is necessary to set the language of the user interface.")"/>
<ThirdPartyComponent Name="Lua-CSharp" Developer="Yusuke Nakada & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nuskey8/Lua-CSharp/blob/main/LICENSE" RepositoryUrl="https://github.com/nuskey8/Lua-CSharp" UseCase="@T("We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.")" />
+4 -1
View File
@@ -21,7 +21,8 @@ public partial class About : MSGComponentBase
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
private static readonly MetaDataArchitecture META_DATA_ARCH = ASSEMBLY.GetCustomAttribute<MetaDataArchitecture>()!;
private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute<MetaDataArchitectureAttribute>()!;
private static readonly MetaDataLibrariesAttribute META_DATA_LIBRARIES = ASSEMBLY.GetCustomAttribute<MetaDataLibrariesAttribute>()!;
private string osLanguage = string.Empty;
@@ -40,6 +41,8 @@ public partial class About : MSGComponentBase
private string VersionDotnetSdk => $"{T("Used .NET SDK")}: v{META_DATA.DotnetSdkVersion}";
private string BuildTime => $"{T("Build time")}: {META_DATA.BuildTime}";
private string VersionPdfium => $"{T("Used PDFium version")}: v{META_DATA_LIBRARIES.PdfiumVersion}";
private GetLogPathsResponse logPaths;
+1
View File
@@ -111,6 +111,7 @@ internal sealed class Program
config.SnackbarConfiguration.SnackbarVariant = Variant.Outlined;
});
builder.Services.AddMemoryCache(); // Needed for the Markdown library
builder.Services.AddMudMarkdownServices();
builder.Services.AddSingleton(new MudTheme());
builder.Services.AddSingleton(MessageBus.INSTANCE);
@@ -142,7 +142,6 @@ public sealed class ProviderAlibabaCloud(ILogger logger) : BaseProvider("https:/
#endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null)
{
var secretKey = apiKeyProvisional switch
+13 -10
View File
@@ -4,6 +4,7 @@ using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
namespace AIStudio.Provider;
@@ -13,6 +14,8 @@ namespace AIStudio.Provider;
/// </summary>
public abstract class BaseProvider : IProvider, ISecretId
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(BaseProvider).Namespace, nameof(BaseProvider));
/// <summary>
/// The HTTP client to use it for all requests.
/// </summary>
@@ -119,7 +122,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if (nextResponse.StatusCode is HttpStatusCode.Forbidden)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Block, $"Tried to communicate with the LLM provider '{this.InstanceName}'. You might not be able to use this provider from your location. The provider message is: '{nextResponse.ReasonPhrase}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Block, string.Format(TB("Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}').");
errorMessage = nextResponse.ReasonPhrase;
break;
@@ -127,7 +130,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if(nextResponse.StatusCode is HttpStatusCode.BadRequest)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The required message format might be changed. The provider message is: '{nextResponse.ReasonPhrase}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}').");
errorMessage = nextResponse.ReasonPhrase;
break;
@@ -135,7 +138,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if(nextResponse.StatusCode is HttpStatusCode.NotFound)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. Something was not found. The provider message is: '{nextResponse.ReasonPhrase}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}').");
errorMessage = nextResponse.ReasonPhrase;
break;
@@ -143,7 +146,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if(nextResponse.StatusCode is HttpStatusCode.Unauthorized)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Key, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The API key might be invalid. The provider message is: '{nextResponse.ReasonPhrase}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Key, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}').");
errorMessage = nextResponse.ReasonPhrase;
break;
@@ -151,7 +154,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if(nextResponse.StatusCode is HttpStatusCode.InternalServerError)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The server might be down or having issues. The provider message is: '{nextResponse.ReasonPhrase}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}').");
errorMessage = nextResponse.ReasonPhrase;
break;
@@ -159,7 +162,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if(nextResponse.StatusCode is HttpStatusCode.ServiceUnavailable)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. The provider is overloaded. The message is: '{nextResponse.ReasonPhrase}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
this.logger.LogError($"Failed request with status code {nextResponse.StatusCode} (message = '{nextResponse.ReasonPhrase}').");
errorMessage = nextResponse.ReasonPhrase;
break;
@@ -176,7 +179,7 @@ public abstract class BaseProvider : IProvider, ISecretId
if(retry >= MAX_RETRIES || !string.IsNullOrWhiteSpace(errorMessage))
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, $"Tried to communicate with the LLM provider '{this.InstanceName}'. Even after {MAX_RETRIES} retries, there were some problems with the request. The provider message is: '{errorMessage}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'"), this.InstanceName, MAX_RETRIES, errorMessage)));
return new HttpRateLimitedStreamResult(false, true, errorMessage ?? $"Failed after {MAX_RETRIES} retries; no provider message available", response);
}
@@ -204,7 +207,7 @@ public abstract class BaseProvider : IProvider, ISecretId
}
catch(Exception e)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, $"Tried to communicate with the LLM provider '{this.InstanceName}'. There were some problems with the request. The provider message is: '{e.Message}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, string.Format(TB("Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'"), this.InstanceName, e.Message)));
this.logger.LogError($"Failed to stream chat completion from {providerName} '{this.InstanceName}': {e.Message}");
}
@@ -221,7 +224,7 @@ public abstract class BaseProvider : IProvider, ISecretId
}
catch (Exception e)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, $"Tried to stream the LLM provider '{this.InstanceName}' answer. There were some problems with the stream. The message is: '{e.Message}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, string.Format(TB("Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"), this.InstanceName, e.Message)));
this.logger.LogWarning($"Failed to read the end-of-stream state from {providerName} '{this.InstanceName}': {e.Message}");
break;
}
@@ -242,7 +245,7 @@ public abstract class BaseProvider : IProvider, ISecretId
}
catch (Exception e)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, $"Tried to stream the LLM provider '{this.InstanceName}' answer. Was not able to read the stream. The message is: '{e.Message}'"));
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, string.Format(TB("Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"), this.InstanceName, e.Message)));
this.logger.LogError($"Failed to read the stream from {providerName} '{this.InstanceName}': {e.Message}");
break;
}
+10 -7
View File
@@ -1,9 +1,12 @@
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Provider;
public sealed record Confidence
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(Confidence).Namespace, nameof(Confidence));
public ConfidenceLevel Level { get; private init; } = ConfidenceLevel.UNKNOWN;
public string Region { get; private init; } = string.Empty;
@@ -29,42 +32,42 @@ public sealed record Confidence
public static readonly Confidence NONE = new()
{
Level = ConfidenceLevel.NONE,
Description = "No provider selected. Please select a provider to get see its confidence level.",
Description = TB("No provider selected. Please select a provider to get see its confidence level."),
};
public static readonly Confidence USA_HUB = new()
{
Level = ConfidenceLevel.UNKNOWN,
Description = "The provider operates its service from the USA and is subject to **U.S. jurisdiction**. In case of suspicion, authorities in the USA can access your data. Please inform yourself about the use of your data. We do not know if your data is safe.",
Description = TB("The provider operates its service from the USA and is subject to **U.S. jurisdiction**. In case of suspicion, authorities in the USA can access your data. Please inform yourself about the use of your data. We do not know if your data is safe."),
};
public static readonly Confidence UNKNOWN = new()
{
Level = ConfidenceLevel.UNKNOWN,
Description = "The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.",
Description = TB("The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe."),
};
public static readonly Confidence USA_NO_TRAINING = new()
{
Level = ConfidenceLevel.MODERATE,
Description = "The provider operates its service from the USA and is subject to **US jurisdiction**. In case of suspicion, authorities in the USA can access your data. However, **your data is not used for training** purposes.",
Description = TB("The provider operates its service from the USA and is subject to **US jurisdiction**. In case of suspicion, authorities in the USA can access your data. However, **your data is not used for training** purposes."),
};
public static readonly Confidence CHINA_NO_TRAINING = new()
{
Level = ConfidenceLevel.MODERATE,
Description = "The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes.",
Description = TB("The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes."),
};
public static readonly Confidence GDPR_NO_TRAINING = new()
{
Level = ConfidenceLevel.MEDIUM,
Description = "The provider is located in the EU and is subject to the **GDPR** (General Data Protection Regulation). Additionally, the provider states that **your data is not used for training**.",
Description = TB("The provider is located in the EU and is subject to the **GDPR** (General Data Protection Regulation). Additionally, the provider states that **your data is not used for training**."),
};
public static readonly Confidence SELF_HOSTED = new()
{
Level = ConfidenceLevel.HIGH,
Description = "You or your organization operate the LLM locally or within your trusted network. In terms of data processing and security, this is the best possible way.",
Description = TB("You or your organization operate the LLM locally or within your trusted network. In terms of data processing and security, this is the best possible way."),
};
}
@@ -1,21 +1,24 @@
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Provider;
public static class ConfidenceLevelExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfidenceLevelExtensions).Namespace, nameof(ConfidenceLevelExtensions));
public static string GetName(this ConfidenceLevel level) => level switch
{
ConfidenceLevel.NONE => "No provider selected",
ConfidenceLevel.NONE => TB("No provider selected"),
ConfidenceLevel.UNTRUSTED => "Untrusted",
ConfidenceLevel.VERY_LOW => "Very Low",
ConfidenceLevel.LOW => "Low",
ConfidenceLevel.MODERATE => "Moderate",
ConfidenceLevel.MEDIUM => "Medium",
ConfidenceLevel.HIGH => "High",
ConfidenceLevel.UNTRUSTED => TB("Untrusted"),
ConfidenceLevel.VERY_LOW => TB("Very Low"),
ConfidenceLevel.LOW => TB("Low"),
ConfidenceLevel.MODERATE => TB("Moderate"),
ConfidenceLevel.MEDIUM => TB("Medium"),
ConfidenceLevel.HIGH => TB("High"),
_ => "Unknown confidence level",
_ => TB("Unknown confidence level"),
};
public static string GetColor(this ConfidenceLevel level, SettingsManager settingsManager) => (level, settingsManager.IsDarkMode) switch
@@ -12,6 +12,7 @@ using AIStudio.Provider.OpenAI;
using AIStudio.Provider.SelfHosted;
using AIStudio.Provider.X;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using Host = AIStudio.Provider.SelfHosted.Host;
@@ -19,6 +20,8 @@ namespace AIStudio.Provider;
public static class LLMProvidersExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(LLMProvidersExtensions).Namespace, nameof(LLMProvidersExtensions));
/// <summary>
/// Returns the human-readable name of the provider.
/// </summary>
@@ -26,7 +29,7 @@ public static class LLMProvidersExtensions
/// <returns>The human-readable name of the provider.</returns>
public static string ToName(this LLMProviders llmProvider) => llmProvider switch
{
LLMProviders.NONE => "No provider selected",
LLMProviders.NONE => TB("No provider selected"),
LLMProviders.OPEN_AI => "OpenAI",
LLMProviders.ANTHROPIC => "Anthropic",
@@ -40,12 +43,12 @@ public static class LLMProvidersExtensions
LLMProviders.FIREWORKS => "Fireworks.ai",
LLMProviders.HUGGINGFACE => "Hugging Face",
LLMProviders.SELF_HOSTED => "Self-hosted",
LLMProviders.SELF_HOSTED => TB("Self-hosted"),
LLMProviders.HELMHOLTZ => "Helmholtz Blablador",
LLMProviders.GWDG => "GWDG SAIA",
_ => "Unknown",
_ => TB("Unknown"),
};
/// <summary>
@@ -127,7 +127,8 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
!model.Id.Contains("realtime", StringComparison.OrdinalIgnoreCase) &&
!model.Id.Contains("audio", StringComparison.OrdinalIgnoreCase) &&
!model.Id.Contains("tts", StringComparison.OrdinalIgnoreCase) &&
!model.Id.Contains("transcribe", StringComparison.OrdinalIgnoreCase));
!model.Id.Contains("transcribe", StringComparison.OrdinalIgnoreCase) &&
!model.Id.Contains("o1-pro", StringComparison.OrdinalIgnoreCase));
}
/// <inheritdoc />
@@ -26,6 +26,8 @@ public readonly record struct ConfigurationSelectData<T>(string Name, T Value);
/// </summary>
public static class ConfigurationSelectDataFactory
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSelectDataFactory).Namespace, nameof(ConfigurationSelectDataFactory));
public static IEnumerable<ConfigurationSelectData<LangBehavior>> GetLangBehaviorData()
{
foreach (var behavior in Enum.GetValues<LangBehavior>())
@@ -43,65 +45,65 @@ public static class ConfigurationSelectDataFactory
public static IEnumerable<ConfigurationSelectData<LoadingChatProviderBehavior>> GetLoadingChatProviderBehavior()
{
yield return new("When possible, use the LLM provider which was used for each chat in the first place", LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE);
yield return new("Use the latest LLM provider, which was used before; use the default chat provider initially", LoadingChatProviderBehavior.ALWAYS_USE_LATEST_CHAT_PROVIDER);
yield return new("Always use the default chat provider when loading chats", LoadingChatProviderBehavior.ALWAYS_USE_DEFAULT_CHAT_PROVIDER);
yield return new(TB("When possible, use the LLM provider which was used for each chat in the first place"), LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE);
yield return new(TB("Use the latest LLM provider, which was used before; use the default chat provider initially"), LoadingChatProviderBehavior.ALWAYS_USE_LATEST_CHAT_PROVIDER);
yield return new(TB("Always use the default chat provider when loading chats"), LoadingChatProviderBehavior.ALWAYS_USE_DEFAULT_CHAT_PROVIDER);
}
public static IEnumerable<ConfigurationSelectData<AddChatProviderBehavior>> GetAddChatProviderBehavior()
{
yield return new("Use the latest LLM provider, which was used before; use the default chat provider initially", AddChatProviderBehavior.ADDED_CHATS_USE_LATEST_PROVIDER);
yield return new("Always use the default chat provider for new chats", AddChatProviderBehavior.ADDED_CHATS_USE_DEFAULT_PROVIDER);
yield return new(TB("Use the latest LLM provider, which was used before; use the default chat provider initially"), AddChatProviderBehavior.ADDED_CHATS_USE_LATEST_PROVIDER);
yield return new(TB("Always use the default chat provider for new chats"), AddChatProviderBehavior.ADDED_CHATS_USE_DEFAULT_PROVIDER);
}
public static IEnumerable<ConfigurationSelectData<SendBehavior>> GetSendBehaviorData()
{
yield return new("No key is sending the input", SendBehavior.NO_KEY_IS_SENDING);
yield return new("Modifier key + enter is sending the input", SendBehavior.MODIFER_ENTER_IS_SENDING);
yield return new("Enter is sending the input", SendBehavior.ENTER_IS_SENDING);
yield return new(TB("No key is sending the input"), SendBehavior.NO_KEY_IS_SENDING);
yield return new(TB("Modifier key + enter is sending the input"), SendBehavior.MODIFER_ENTER_IS_SENDING);
yield return new(TB("Enter is sending the input"), SendBehavior.ENTER_IS_SENDING);
}
public static IEnumerable<ConfigurationSelectData<UpdateBehavior>> GetUpdateBehaviorData()
{
yield return new("No automatic update checks", UpdateBehavior.NO_CHECK);
yield return new("Once at startup", UpdateBehavior.ONCE_STARTUP);
yield return new("Check every hour", UpdateBehavior.HOURLY);
yield return new("Check every day", UpdateBehavior.DAILY);
yield return new ("Check every week", UpdateBehavior.WEEKLY);
yield return new(TB("No automatic update checks"), UpdateBehavior.NO_CHECK);
yield return new(TB("Once at startup"), UpdateBehavior.ONCE_STARTUP);
yield return new(TB("Check every hour"), UpdateBehavior.HOURLY);
yield return new(TB("Check every day"), UpdateBehavior.DAILY);
yield return new (TB("Check every week"), UpdateBehavior.WEEKLY);
}
public static IEnumerable<ConfigurationSelectData<WorkspaceStorageBehavior>> GetWorkspaceStorageBehaviorData()
{
yield return new("Disable workspaces", WorkspaceStorageBehavior.DISABLE_WORKSPACES);
yield return new("Store chats automatically", WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY);
yield return new("Store chats manually", WorkspaceStorageBehavior.STORE_CHATS_MANUALLY);
yield return new(TB("Disable workspaces"), WorkspaceStorageBehavior.DISABLE_WORKSPACES);
yield return new(TB("Store chats automatically"), WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY);
yield return new(TB("Store chats manually"), WorkspaceStorageBehavior.STORE_CHATS_MANUALLY);
}
public static IEnumerable<ConfigurationSelectData<WorkspaceStorageTemporaryMaintenancePolicy>> GetWorkspaceStorageTemporaryMaintenancePolicyData()
{
yield return new("No automatic maintenance for temporary chats", WorkspaceStorageTemporaryMaintenancePolicy.NO_AUTOMATIC_MAINTENANCE);
yield return new("Delete temporary chats older than 7 days", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_7_DAYS);
yield return new("Delete temporary chats older than 30 days", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_30_DAYS);
yield return new("Delete temporary chats older than 90 days", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_90_DAYS);
yield return new("Delete temporary chats older than 180 days", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_180_DAYS);
yield return new("Delete temporary chats older than 1 year", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_365_DAYS);
yield return new(TB("No automatic maintenance for disappearing chats; old chats will never be deleted"), WorkspaceStorageTemporaryMaintenancePolicy.NO_AUTOMATIC_MAINTENANCE);
yield return new(TB("Delete disappearing chats older than 7 days"), WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_7_DAYS);
yield return new(TB("Delete disappearing chats older than 30 days"), WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_30_DAYS);
yield return new(TB("Delete disappearing chats older than 90 days"), WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_90_DAYS);
yield return new(TB("Delete disappearing chats older than 180 days"), WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_180_DAYS);
yield return new(TB("Delete disappearing chats older than 1 year"), WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_365_DAYS);
}
public static IEnumerable<ConfigurationSelectData<WorkspaceDisplayBehavior>> GetWorkspaceDisplayBehaviorData()
{
yield return new("Toggle the overlay: the chat uses all the space, workspaces are temporarily shown", WorkspaceDisplayBehavior.TOGGLE_OVERLAY);
yield return new("Toggle the sidebar: show the workspaces next to the chat when desired", WorkspaceDisplayBehavior.TOGGLE_SIDEBAR);
yield return new("Sidebar is always visible: show the workspaces next to the chat all the time", WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE);
yield return new(TB("Toggle the overlay: the chat uses all the space, workspaces are temporarily shown"), WorkspaceDisplayBehavior.TOGGLE_OVERLAY);
yield return new(TB("Toggle the sidebar: show the workspaces next to the chat when desired"), WorkspaceDisplayBehavior.TOGGLE_SIDEBAR);
yield return new(TB("Sidebar is always visible: show the workspaces next to the chat all the time"), WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE);
}
public static IEnumerable<ConfigurationSelectData<PreviewVisibility>> GetPreviewVisibility()
{
yield return new("All preview features are hidden", PreviewVisibility.NONE);
yield return new("Also show features ready for release; these should be stable", PreviewVisibility.RELEASE_CANDIDATE);
yield return new("Also show features in beta: these are almost ready for release; expect some bugs", PreviewVisibility.BETA);
yield return new("Also show features in alpha: these are in development; expect bugs and missing features", PreviewVisibility.ALPHA);
yield return new("Show also prototype features: these are works in progress; expect bugs and missing features", PreviewVisibility.PROTOTYPE);
yield return new("Show also experimental features: these are experimental; expect bugs, missing features, many changes", PreviewVisibility.EXPERIMENTAL);
yield return new(TB("All preview features are hidden"), PreviewVisibility.NONE);
yield return new(TB("Also show features ready for release; these should be stable"), PreviewVisibility.RELEASE_CANDIDATE);
yield return new(TB("Also show features in beta: these are almost ready for release; expect some bugs"), PreviewVisibility.BETA);
yield return new(TB("Also show features in alpha: these are in development; expect bugs and missing features"), PreviewVisibility.ALPHA);
yield return new(TB("Show also prototype features: these are works in progress; expect bugs and missing features"), PreviewVisibility.PROTOTYPE);
yield return new(TB("Show also experimental features: these are experimental; expect bugs, missing features, many changes"), PreviewVisibility.EXPERIMENTAL);
}
public static IEnumerable<ConfigurationSelectData<PreviewFeatures>> GetPreviewFeaturesData(SettingsManager settingsManager)
@@ -118,10 +120,10 @@ public static class ConfigurationSelectDataFactory
public static IEnumerable<ConfigurationSelectData<NavBehavior>> GetNavBehaviorData()
{
yield return new("Navigation expands on mouse hover", NavBehavior.EXPAND_ON_HOVER);
yield return new("Navigation never expands, but there are tooltips", NavBehavior.NEVER_EXPAND_USE_TOOLTIPS);
yield return new("Navigation never expands, no tooltips", NavBehavior.NEVER_EXPAND_NO_TOOLTIPS);
yield return new("Always expand navigation", NavBehavior.ALWAYS_EXPAND);
yield return new(TB("Navigation expands on mouse hover"), NavBehavior.EXPAND_ON_HOVER);
yield return new(TB("Navigation never expands, but there are tooltips"), NavBehavior.NEVER_EXPAND_USE_TOOLTIPS);
yield return new(TB("Navigation never expands, no tooltips"), NavBehavior.NEVER_EXPAND_NO_TOOLTIPS);
yield return new(TB("Always expand navigation"), NavBehavior.ALWAYS_EXPAND);
}
public static IEnumerable<ConfigurationSelectData<IconSources>> GetIconSourcesData()
@@ -140,7 +142,7 @@ public static class ConfigurationSelectDataFactory
{
foreach (var language in Enum.GetValues<CommonLanguages>())
if(language is CommonLanguages.AS_IS)
yield return new("Not yet specified", language);
yield return new(TB("Not yet specified"), language);
else
yield return new(language.Name(), language);
}
@@ -149,7 +151,7 @@ public static class ConfigurationSelectDataFactory
{
foreach (var language in Enum.GetValues<CommonLanguages>())
if(language is CommonLanguages.AS_IS)
yield return new("Do not specify the language", language);
yield return new(TB("Do not specify the language"), language);
else
yield return new(language.Name(), language);
}
@@ -211,7 +213,7 @@ public static class ConfigurationSelectDataFactory
foreach (var level in Enum.GetValues<ConfidenceLevel>())
{
if(level is ConfidenceLevel.NONE)
yield return new("No minimum confidence level chosen", level);
yield return new(TB("No minimum confidence level chosen"), level);
if(level < minimumLevel)
continue;
@@ -1,25 +1,29 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
public static class DataSourceSecurityExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceSecurityExtensions).Namespace, nameof(DataSourceSecurityExtensions));
public static string ToSelectionText(this DataSourceSecurity security) => security switch
{
DataSourceSecurity.NOT_SPECIFIED => "Please select a security policy",
DataSourceSecurity.NOT_SPECIFIED => TB("Please select a security policy"),
DataSourceSecurity.ALLOW_ANY => "This data source can be used with any LLM provider. Your data may be sent to a cloud-based provider.",
DataSourceSecurity.SELF_HOSTED => "This data source can only be used with a self-hosted LLM provider. Your data will not be sent to any cloud-based provider.",
DataSourceSecurity.ALLOW_ANY => TB("This data source can be used with any LLM provider. Your data may be sent to a cloud-based provider."),
DataSourceSecurity.SELF_HOSTED => TB("This data source can only be used with a self-hosted LLM provider. Your data will not be sent to any cloud-based provider."),
_ => "Unknown security policy"
_ => TB("Unknown security policy")
};
public static string ToInfoText(this DataSourceSecurity security) => security switch
{
DataSourceSecurity.NOT_SPECIFIED => "The security of the data source is not specified yet. You cannot use this data source until you specify a security policy.",
DataSourceSecurity.NOT_SPECIFIED => TB("The security of the data source is not specified yet. You cannot use this data source until you specify a security policy."),
DataSourceSecurity.ALLOW_ANY => "This data source can be used with any LLM provider. Your data may be sent to a cloud-based provider.",
DataSourceSecurity.SELF_HOSTED => "This data source can only be used with a self-hosted LLM provider. Your data will not be sent to any cloud-based provider.",
DataSourceSecurity.ALLOW_ANY => TB("This data source can be used with any LLM provider. Your data may be sent to a cloud-based provider."),
DataSourceSecurity.SELF_HOSTED => TB("This data source can only be used with a self-hosted LLM provider. Your data will not be sent to any cloud-based provider."),
_ => "Unknown security policy"
_ => TB("Unknown security policy")
};
public static TextColor GetColor(this DataSourceSecurity security) => security switch
@@ -1,3 +1,5 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
/// <summary>
@@ -5,6 +7,8 @@ namespace AIStudio.Settings.DataModel;
/// </summary>
public static class DataSourceTypeExtension
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceTypeExtension).Namespace, nameof(DataSourceTypeExtension));
/// <summary>
/// Get the display name of the data source type.
/// </summary>
@@ -12,10 +16,10 @@ public static class DataSourceTypeExtension
/// <returns>The display name of the data source type.</returns>
public static string GetDisplayName(this DataSourceType type) => type switch
{
DataSourceType.LOCAL_FILE => "Local File",
DataSourceType.LOCAL_DIRECTORY => "Local Directory",
DataSourceType.ERI_V1 => "External ERI Server (v1)",
DataSourceType.LOCAL_FILE => TB("Local File"),
DataSourceType.LOCAL_DIRECTORY => TB("Local Directory"),
DataSourceType.ERI_V1 => TB("External ERI Server (v1)"),
_ => "None",
_ => TB("None"),
};
}
@@ -0,0 +1,16 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
public static class LangBehaviorExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(LangBehaviorExtensions).Namespace, nameof(LangBehaviorExtensions));
public static string Name(this LangBehavior langBehavior) => langBehavior switch
{
LangBehavior.AUTO => TB("Choose the language automatically, based on your system language."),
LangBehavior.MANUAL => TB("Choose the language manually."),
_ => TB("Unknown option")
};
}
@@ -1,12 +0,0 @@
namespace AIStudio.Settings.DataModel;
public static class LangBehaviorExtensions
{
public static string Name(this LangBehavior langBehavior) => langBehavior switch
{
LangBehavior.AUTO => "Choose the language automatically, based on your system language.",
LangBehavior.MANUAL => "Choose the language manually.",
_ => "Unknown option"
};
}
@@ -10,4 +10,5 @@ public enum PreviewFeatures
PRE_RAG_2024,
PRE_PLUGINS_2025,
PRE_READ_PDF_2025,
}
@@ -1,14 +1,20 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
public static class PreviewFeaturesExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PreviewFeaturesExtensions).Namespace, nameof(PreviewFeaturesExtensions));
public static string GetPreviewDescription(this PreviewFeatures feature) => feature switch
{
PreviewFeatures.PRE_WRITER_MODE_2024 => "Writer Mode: Experiments about how to write long texts using AI",
PreviewFeatures.PRE_RAG_2024 => "RAG: Preview of our RAG implementation where you can refer your files or integrate enterprise data within your company",
PreviewFeatures.PRE_PLUGINS_2025 => "Plugins: Preview of our plugin system where you can extend the functionality of the app",
PreviewFeatures.PRE_WRITER_MODE_2024 => TB("Writer Mode: Experiments about how to write long texts using AI"),
PreviewFeatures.PRE_RAG_2024 => TB("RAG: Preview of our RAG implementation where you can refer your files or integrate enterprise data within your company"),
_ => "Unknown preview feature"
PreviewFeatures.PRE_PLUGINS_2025 => TB("Plugins: Preview of our plugin system where you can extend the functionality of the app"),
PreviewFeatures.PRE_READ_PDF_2025 => TB("Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files"),
_ => TB("Unknown preview feature")
};
public static bool IsEnabled(this PreviewFeatures feature, SettingsManager settingsManager) => settingsManager.ConfigurationData.App.EnabledPreviewFeatures.Contains(feature);
@@ -19,6 +19,7 @@ public static class PreviewVisibilityExtensions
if (visibility >= PreviewVisibility.PROTOTYPE)
{
features.Add(PreviewFeatures.PRE_READ_PDF_2025);
features.Add(PreviewFeatures.PRE_RAG_2024);
}
@@ -1,12 +1,16 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
public static class SendToChatDataSourceBehaviorExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SendToChatDataSourceBehaviorExtensions).Namespace, nameof(SendToChatDataSourceBehaviorExtensions));
public static string Description(this SendToChatDataSourceBehavior behavior) => behavior switch
{
SendToChatDataSourceBehavior.NO_DATA_SOURCES => "Use no data sources, when sending an assistant result to a chat",
SendToChatDataSourceBehavior.APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS => "Apply standard chat data source options, when sending an assistant result to a chat",
SendToChatDataSourceBehavior.NO_DATA_SOURCES => TB("Use no data sources, when sending an assistant result to a chat"),
SendToChatDataSourceBehavior.APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS => TB("Apply standard chat data source options, when sending an assistant result to a chat"),
_ => "Unknown behavior",
_ => TB("Unknown behavior"),
};
}
@@ -1,13 +1,17 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings.DataModel;
public static class ThemesExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ThemesExtensions).Namespace, nameof(ThemesExtensions));
public static string GetName(this Themes theme) => theme switch
{
Themes.SYSTEM => "Synchronized with the operating system settings",
Themes.LIGHT => "Always use light theme",
Themes.DARK => "Always use dark theme",
Themes.SYSTEM => TB("Synchronized with the operating system settings"),
Themes.LIGHT => TB("Always use light theme"),
Themes.DARK => TB("Always use dark theme"),
_ => "Unknown setting",
_ => TB("Unknown setting"),
};
}
+5 -1
View File
@@ -1,10 +1,14 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Settings;
public readonly record struct Profile(uint Num, string Id, string Name, string NeedToKnow, string Actions)
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(Profile).Namespace, nameof(Profile));
public static readonly Profile NO_PROFILE = new()
{
Name = "Use no profile",
Name = TB("Use no profile"),
NeedToKnow = string.Empty,
Actions = string.Empty,
Id = Guid.Empty.ToString(),
@@ -13,7 +13,7 @@ namespace AIStudio.Settings;
/// <summary>
/// The settings manager.
/// </summary>
public sealed class SettingsManager(ILogger<SettingsManager> logger, RustService rustService)
public sealed class SettingsManager
{
private const string SETTINGS_FILENAME = "settings.json";
@@ -23,9 +23,19 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger, RustService
Converters = { new TolerantEnumConverter() },
};
private readonly ILogger<SettingsManager> logger = logger;
private readonly RustService rustService = rustService;
private readonly ILogger<SettingsManager> logger;
private readonly RustService rustService;
/// <summary>
/// The settings manager.
/// </summary>
public SettingsManager(ILogger<SettingsManager> logger, RustService rustService)
{
this.logger = logger;
this.rustService = rustService;
this.logger.LogInformation("Settings manager created.");
}
/// <summary>
/// The directory where the configuration files are stored.
/// </summary>
@@ -1,16 +1,19 @@
using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools;
public static class AuthMethodsV1Extensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AuthMethodsV1Extensions).Namespace, nameof(AuthMethodsV1Extensions));
public static string DisplayName(this AuthMethod authMethod) => authMethod switch
{
AuthMethod.NONE => "None",
AuthMethod.USERNAME_PASSWORD => "Username & Password",
AuthMethod.KERBEROS => "SSO (Kerberos)",
AuthMethod.TOKEN => "Access Token",
AuthMethod.NONE => TB("None"),
AuthMethod.USERNAME_PASSWORD => TB("Username & Password"),
AuthMethod.KERBEROS => TB("SSO (Kerberos)"),
AuthMethod.TOKEN => TB("Access Token"),
_ => "Unknown authentication method",
_ => TB("Unknown authentication method"),
};
}
@@ -1,23 +1,27 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools;
public static class CommonLanguageExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(CommonLanguageExtensions).Namespace, nameof(CommonLanguageExtensions));
public static string Name(this CommonLanguages language) => language switch
{
CommonLanguages.AS_IS => "Do not change the language",
CommonLanguages.AS_IS => TB("Do not change the language"),
CommonLanguages.EN_US => "English (US)",
CommonLanguages.EN_GB => "English (UK)",
CommonLanguages.ZH_CN => "Chinese (Simplified)",
CommonLanguages.HI_IN => "Hindi (India)",
CommonLanguages.ES_ES => "Spanish (Spain)",
CommonLanguages.FR_FR => "French (France)",
CommonLanguages.DE_DE => "German (Germany)",
CommonLanguages.DE_AT => "German (Austria)",
CommonLanguages.DE_CH => "German (Switzerland)",
CommonLanguages.JA_JP => "Japanese (Japan)",
CommonLanguages.EN_US => TB("English (US)"),
CommonLanguages.EN_GB => TB("English (UK)"),
CommonLanguages.ZH_CN => TB("Chinese (Simplified)"),
CommonLanguages.HI_IN => TB("Hindi (India)"),
CommonLanguages.ES_ES => TB("Spanish (Spain)"),
CommonLanguages.FR_FR => TB("French (France)"),
CommonLanguages.DE_DE => TB("German (Germany)"),
CommonLanguages.DE_AT => TB("German (Austria)"),
CommonLanguages.DE_CH => TB("German (Switzerland)"),
CommonLanguages.JA_JP => TB("Japanese (Japan)"),
_ => "Other",
_ => TB("Other"),
};
public static string ToIETFTag(this CommonLanguages language) => language switch
@@ -56,7 +60,7 @@ public static class CommonLanguageExtensions
public static string NameSelecting(this CommonLanguages language)
{
if(language is CommonLanguages.AS_IS)
return "Please select the target language";
return TB("Please select the target language");
return language.Name();
}
@@ -64,7 +68,7 @@ public static class CommonLanguageExtensions
public static string NameSelectingOptional(this CommonLanguages language)
{
if(language is CommonLanguages.AS_IS)
return "Do not specify the language";
return TB("Do not specify the language");
return language.Name();
}
@@ -2,11 +2,14 @@ using System.Diagnostics.CodeAnalysis;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools;
public static class ComponentsExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ComponentsExtensions).Namespace, nameof(ComponentsExtensions));
public static bool AllowSendTo(this Components component) => component switch
{
Components.NONE => false,
@@ -26,22 +29,22 @@ public static class ComponentsExtensions
public static string Name(this Components component) => component switch
{
Components.GRAMMAR_SPELLING_ASSISTANT => "Grammar & Spelling Assistant",
Components.TEXT_SUMMARIZER_ASSISTANT => "Text Summarizer Assistant",
Components.ICON_FINDER_ASSISTANT => "Icon Finder Assistant",
Components.TRANSLATION_ASSISTANT => "Translation Assistant",
Components.REWRITE_ASSISTANT => "Rewrite Assistant",
Components.AGENDA_ASSISTANT => "Agenda Assistant",
Components.CODING_ASSISTANT => "Coding Assistant",
Components.EMAIL_ASSISTANT => "E-Mail Assistant",
Components.LEGAL_CHECK_ASSISTANT => "Legal Check Assistant",
Components.SYNONYMS_ASSISTANT => "Synonym Assistant",
Components.MY_TASKS_ASSISTANT => "My Tasks Assistant",
Components.JOB_POSTING_ASSISTANT => "Job Posting Assistant",
Components.ERI_ASSISTANT => "ERI Server",
Components.I18N_ASSISTANT => "Localization Assistant",
Components.GRAMMAR_SPELLING_ASSISTANT => TB("Grammar & Spelling Assistant"),
Components.TEXT_SUMMARIZER_ASSISTANT => TB("Text Summarizer Assistant"),
Components.ICON_FINDER_ASSISTANT => TB("Icon Finder Assistant"),
Components.TRANSLATION_ASSISTANT => TB("Translation Assistant"),
Components.REWRITE_ASSISTANT => TB("Rewrite Assistant"),
Components.AGENDA_ASSISTANT => TB("Agenda Assistant"),
Components.CODING_ASSISTANT => TB("Coding Assistant"),
Components.EMAIL_ASSISTANT => TB("E-Mail Assistant"),
Components.LEGAL_CHECK_ASSISTANT => TB("Legal Check Assistant"),
Components.SYNONYMS_ASSISTANT => TB("Synonym Assistant"),
Components.MY_TASKS_ASSISTANT => TB("My Tasks Assistant"),
Components.JOB_POSTING_ASSISTANT => TB("Job Posting Assistant"),
Components.ERI_ASSISTANT => TB("ERI Server"),
Components.I18N_ASSISTANT => TB("Localization Assistant"),
Components.CHAT => "New Chat",
Components.CHAT => TB("New Chat"),
_ => Enum.GetName(typeof(Components), component)!,
};
@@ -1,18 +1,22 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools;
public static class ConfidenceSchemesExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfidenceSchemesExtensions).Namespace, nameof(ConfidenceSchemesExtensions));
public static string GetListDescription(this ConfidenceSchemes scheme) => scheme switch
{
ConfidenceSchemes.TRUST_ALL => "Trust all LLM providers",
ConfidenceSchemes.TRUST_USA_EUROPE => "Trust LLM providers from the USA and Europe",
ConfidenceSchemes.TRUST_USA => "Trust LLM providers from the USA",
ConfidenceSchemes.TRUST_EUROPE => "Trust LLM providers from Europe",
ConfidenceSchemes.TRUST_ASIA => "Trust LLM providers from Asia",
ConfidenceSchemes.LOCAL_TRUST_ONLY => "Trust only local LLM providers",
ConfidenceSchemes.TRUST_ALL => TB("Trust all LLM providers"),
ConfidenceSchemes.TRUST_USA_EUROPE => TB("Trust LLM providers from the USA and Europe"),
ConfidenceSchemes.TRUST_USA => TB("Trust LLM providers from the USA"),
ConfidenceSchemes.TRUST_EUROPE => TB("Trust LLM providers from Europe"),
ConfidenceSchemes.TRUST_ASIA => TB("Trust LLM providers from Asia"),
ConfidenceSchemes.LOCAL_TRUST_ONLY => TB("Trust only local LLM providers"),
ConfidenceSchemes.CUSTOM => "Configure your own confidence scheme",
ConfidenceSchemes.CUSTOM => TB("Configure your own confidence scheme"),
_ => "Unknown confidence scheme"
_ => TB("Unknown confidence scheme"),
};
}
@@ -1,13 +1,17 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.ERIClient.DataModel;
public static class ProviderTypeExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderTypeExtensions).Namespace, nameof(ProviderTypeExtensions));
public static string Explain(this ProviderType providerType) => providerType switch
{
ProviderType.NONE => "The related data is not allowed to be sent to any LLM provider. This means that this data source cannot be used at the moment.",
ProviderType.ANY => "The related data can be sent to any provider, regardless of where it is hosted (cloud or self-hosted).",
ProviderType.SELF_HOSTED => "The related data can be sent to a provider that is hosted by the same organization, either on-premises or locally. Cloud-based providers are not allowed.",
ProviderType.NONE => TB("The related data is not allowed to be sent to any LLM provider. This means that this data source cannot be used at the moment."),
ProviderType.ANY => TB("The related data can be sent to any provider, regardless of where it is hosted (cloud or self-hosted)."),
ProviderType.SELF_HOSTED => TB("The related data can be sent to a provider that is hosted by the same organization, either on-premises or locally. Cloud-based providers are not allowed."),
_ => "Unknown configuration. This data source cannot be used at the moment.",
_ => TB("Unknown configuration. This data source cannot be used at the moment."),
};
}
@@ -3,12 +3,15 @@ using System.Text.Json;
using AIStudio.Settings;
using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
namespace AIStudio.Tools.ERIClient;
public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource), IERIClient
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ERIClientV1).Namespace, nameof(ERIClientV1));
#region Implementation of IERIClient
public async Task<APIResponse<List<AuthScheme>>> GetAuthMethodsAsync(CancellationToken cancellationToken = default)
@@ -21,7 +24,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the authentication methods: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
Message = string.Format(TB("Failed to retrieve the authentication methods: there was an issue communicating with the ERI server. Code: {0}, Reason: {1}"), response.StatusCode, response.ReasonPhrase)
};
}
@@ -31,7 +34,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the authentication methods: the ERI server did not return a valid response."
Message = TB("Failed to retrieve the authentication methods: the ERI server did not return a valid response.")
};
}
@@ -46,7 +49,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the authentication methods: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to retrieve the authentication methods: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -54,7 +57,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the authentication methods due to an exception: {e.Message}"
Message = string.Format(TB("Failed to retrieve the authentication methods due to an exception: {0}"), e.Message)
};
}
}
@@ -76,7 +79,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server. Code: {noneAuthResponse.StatusCode}, Reason: {noneAuthResponse.ReasonPhrase}"
Message = string.Format(TB("Failed to authenticate with the ERI server. Code: {0}, Reason: {1}"), noneAuthResponse.StatusCode, noneAuthResponse.ReasonPhrase)
};
}
@@ -86,7 +89,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to authenticate with the ERI server: the response was invalid."
Message = TB("Failed to authenticate with the ERI server: the response was invalid.")
};
}
@@ -108,7 +111,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the password."
Message = TB("Failed to retrieve the password.")
};
}
@@ -130,7 +133,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server. Code: {usernamePasswordAuthResponse.StatusCode}, Reason: {usernamePasswordAuthResponse.ReasonPhrase}"
Message = string.Format(TB("Failed to authenticate with the ERI server. Code: {0}, Reason: {1}"), usernamePasswordAuthResponse.StatusCode, usernamePasswordAuthResponse.ReasonPhrase)
};
}
@@ -140,7 +143,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to authenticate with the server: the response was invalid."
Message = TB("Failed to authenticate with the server: the response was invalid.")
};
}
@@ -162,7 +165,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the access token."
Message = TB("Failed to retrieve the access token.")
};
}
@@ -181,7 +184,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server. Code: {tokenAuthResponse.StatusCode}, Reason: {tokenAuthResponse.ReasonPhrase}"
Message = string.Format(TB("Failed to authenticate with the ERI server. Code: {0}, Reason: {1}"), tokenAuthResponse.StatusCode, tokenAuthResponse.ReasonPhrase)
};
}
@@ -191,7 +194,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to authenticate with the ERI server: the response was invalid."
Message = TB("Failed to authenticate with the ERI server: the response was invalid.")
};
}
@@ -208,7 +211,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "The authentication method is not supported yet."
Message = TB("The authentication method is not supported yet.")
};
}
}
@@ -217,7 +220,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to authenticate with the ERI server: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to authenticate with the ERI server: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -225,7 +228,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server due to an exception: {e.Message}"
Message = string.Format(TB("Failed to authenticate with the ERI server due to an exception: {0}"), e.Message)
};
}
}
@@ -243,7 +246,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the data source information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
Message = string.Format(TB("Failed to retrieve the data source information: there was an issue communicating with the ERI server. Code: {0}, Reason: {1}"), response.StatusCode, response.ReasonPhrase)
};
}
@@ -253,7 +256,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the data source information: the ERI server did not return a valid response."
Message = TB("Failed to retrieve the data source information: the ERI server did not return a valid response.")
};
}
@@ -268,7 +271,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the data source information: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to retrieve the data source information: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -276,7 +279,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the data source information due to an exception: {e.Message}"
Message = string.Format(TB("Failed to retrieve the data source information due to an exception: {0}"), e.Message)
};
}
}
@@ -294,7 +297,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the embedding information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
Message = string.Format(TB("Failed to retrieve the embedding information: there was an issue communicating with the ERI server. Code: {0}, Reason: {1}"), response.StatusCode, response.ReasonPhrase)
};
}
@@ -304,7 +307,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the embedding information: the ERI server did not return a valid response."
Message = TB("Failed to retrieve the embedding information: the ERI server did not return a valid response.")
};
}
@@ -319,7 +322,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the embedding information: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to retrieve the embedding information: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -327,7 +330,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the embedding information due to an exception: {e.Message}"
Message = string.Format(TB("Failed to retrieve the embedding information due to an exception: {0}"), e.Message)
};
}
}
@@ -345,7 +348,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the retrieval information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
Message = string.Format(TB("Failed to retrieve the retrieval information: there was an issue communicating with the ERI server. Code: {0}, Reason: {1}"), response.StatusCode, response.ReasonPhrase)
};
}
@@ -355,7 +358,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the retrieval information: the ERI server did not return a valid response."
Message = TB("Failed to retrieve the retrieval information: the ERI server did not return a valid response.")
};
}
@@ -370,7 +373,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the retrieval information: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to retrieve the retrieval information: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -378,7 +381,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the retrieval information due to an exception: {e.Message}"
Message = string.Format(TB("Failed to retrieve the retrieval information due to an exception: {0}"), e.Message)
};
}
}
@@ -399,7 +402,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to execute the retrieval request: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
Message = string.Format(TB("Failed to execute the retrieval request: there was an issue communicating with the ERI server. Code: {0}, Reason: {1}"), response.StatusCode, response.ReasonPhrase)
};
}
@@ -409,7 +412,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to execute the retrieval request: the ERI server did not return a valid response."
Message = TB("Failed to execute the retrieval request: the ERI server did not return a valid response.")
};
}
@@ -424,7 +427,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to execute the retrieval request: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to execute the retrieval request: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -432,7 +435,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to execute the retrieval request due to an exception: {e.Message}"
Message = string.Format(TB("Failed to execute the retrieval request due to an exception: {0}"), e.Message)
};
}
}
@@ -450,7 +453,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the security requirements: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
Message = string.Format(TB("Failed to retrieve the security requirements: there was an issue communicating with the ERI server. Code: {0}, Reason: {1}"), response.StatusCode, response.ReasonPhrase)
};
}
@@ -460,7 +463,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the security requirements: the ERI server did not return a valid response."
Message = TB("Failed to retrieve the security requirements: the ERI server did not return a valid response.")
};
}
@@ -475,7 +478,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = "Failed to retrieve the security requirements: the request was canceled either by the user or due to a timeout."
Message = TB("Failed to retrieve the security requirements: the request was canceled either by the user or due to a timeout.")
};
}
catch (Exception e)
@@ -483,7 +486,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
return new()
{
Successful = false,
Message = $"Failed to retrieve the security requirements due to an exception: {e.Message}"
Message = string.Format(TB("Failed to retrieve the security requirements due to an exception: {0}"), e.Message)
};
}
}
@@ -2,7 +2,7 @@
namespace AIStudio.Tools.Metadata;
[AttributeUsage(AttributeTargets.Assembly)]
public class MetaDataArchitecture(string architecture) : Attribute
public class MetaDataArchitectureAttribute(string architecture) : Attribute
{
public string Architecture => architecture;
}
@@ -0,0 +1,6 @@
namespace AIStudio.Tools.Metadata;
public class MetaDataLibrariesAttribute(string pdfiumVersion) : Attribute
{
public string PdfiumVersion => pdfiumVersion;
}
@@ -0,0 +1,36 @@
namespace AIStudio.Tools.PluginSystem;
public class I18N : ILang
{
public static readonly I18N I = new();
private static readonly ILogger<I18N> LOG = Program.LOGGER_FACTORY.CreateLogger<I18N>();
private ILanguagePlugin? language = PluginFactory.BaseLanguage;
private I18N()
{
}
public static void Init(ILanguagePlugin language) => I.language = language;
#region Implementation of ILang
public string T(string fallbackEN)
{
LOG.LogWarning("Using I18N.I.T without namespace and type is probably wrong, because the I18N key collection process of the build system will not find those keys.");
if(this.language is not null)
return this.GetText(this.language, fallbackEN);
return fallbackEN;
}
public string T(string fallbackEN, string? typeNamespace, string? typeName)
{
if(this.language is not null)
return this.GetText(this.language, fallbackEN, typeNamespace, typeName);
return fallbackEN;
}
#endregion
}
@@ -12,8 +12,8 @@ public static class ILangExtensions
typeName ??= type.Name;
typeNamespace ??= type.Namespace!;
// When the type's name ends with `1 or 2`, etc. (i.e., generic classes), remove it:
if(typeName.Contains("`"))
// When the type's name ends with `1 or `2, etc. (i.e., generic classes), remove it:
if(typeName.Contains('`'))
typeName = typeName[..typeName.IndexOf('`')];
var ns = $"{typeNamespace}::{typeName}".ToUpperInvariant().Replace(".", "::");
@@ -8,6 +8,8 @@ namespace AIStudio.Tools.PluginSystem;
/// </summary>
public abstract partial class PluginBase : IPluginMetadata
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginBase).Namespace, nameof(PluginBase));
private readonly IReadOnlyCollection<string> baseIssues;
protected readonly LuaState state;
@@ -155,21 +157,21 @@ public abstract partial class PluginBase : IPluginMetadata
{
if (!this.state.Environment["ID"].TryRead<string>(out var idText))
{
message = "The field ID does not exist or is not a valid string.";
message = TB("The field ID does not exist or is not a valid string.");
id = Guid.Empty;
return false;
}
if (!Guid.TryParse(idText, out id))
{
message = "The field ID is not a valid GUID / UUID. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX).";
message = TB("The field ID is not a valid GUID / UUID. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX).");
id = Guid.Empty;
return false;
}
if(id == Guid.Empty)
{
message = "The field ID is empty. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX).";
message = TB("The field ID is empty. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX).");
return false;
}
@@ -187,14 +189,14 @@ public abstract partial class PluginBase : IPluginMetadata
{
if (!this.state.Environment["NAME"].TryRead(out name))
{
message = "The field NAME does not exist or is not a valid string.";
message = TB("The field NAME does not exist or is not a valid string.");
name = string.Empty;
return false;
}
if(string.IsNullOrWhiteSpace(name))
{
message = "The field NAME is empty. The name must be a non-empty string.";
message = TB("The field NAME is empty. The name must be a non-empty string.");
return false;
}
@@ -212,14 +214,14 @@ public abstract partial class PluginBase : IPluginMetadata
{
if (!this.state.Environment["DESCRIPTION"].TryRead(out description))
{
message = "The field DESCRIPTION does not exist or is not a valid string.";
message = TB("The field DESCRIPTION does not exist or is not a valid string.");
description = string.Empty;
return false;
}
if(string.IsNullOrWhiteSpace(description))
{
message = "The field DESCRIPTION is empty. The description must be a non-empty string.";
message = TB("The field DESCRIPTION is empty. The description must be a non-empty string.");
return false;
}
@@ -237,21 +239,21 @@ public abstract partial class PluginBase : IPluginMetadata
{
if (!this.state.Environment["VERSION"].TryRead<string>(out var versionText))
{
message = "The field VERSION does not exist or is not a valid string.";
message = TB("The field VERSION does not exist or is not a valid string.");
version = PluginVersion.NONE;
return false;
}
if (!PluginVersion.TryParse(versionText, out version))
{
message = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).";
message = TB("The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).");
version = PluginVersion.NONE;
return false;
}
if(version == PluginVersion.NONE)
{
message = "The field VERSION is empty. The version number must be formatted as string in the major.minor.patch format (X.X.X).";
message = TB("The field VERSION is empty. The version number must be formatted as string in the major.minor.patch format (X.X.X).");
return false;
}
@@ -270,7 +272,7 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["AUTHORS"].TryRead<LuaTable>(out var authorsTable))
{
authors = [];
message = "The table AUTHORS does not exist or is using an invalid syntax.";
message = TB("The table AUTHORS does not exist or is using an invalid syntax.");
return false;
}
@@ -282,7 +284,7 @@ public abstract partial class PluginBase : IPluginMetadata
authors = authorList.ToArray();
if(authorList.Count == 0)
{
message = "The table AUTHORS is empty. At least one author must be specified.";
message = TB("The table AUTHORS is empty. At least one author must be specified.");
return false;
}
@@ -301,13 +303,13 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["SUPPORT_CONTACT"].TryRead(out contact))
{
contact = string.Empty;
message = "The field SUPPORT_CONTACT does not exist or is not a valid string.";
message = TB("The field SUPPORT_CONTACT does not exist or is not a valid string.");
return false;
}
if(string.IsNullOrWhiteSpace(contact))
{
message = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string.";
message = TB("The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string.");
return false;
}
@@ -326,14 +328,14 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["SOURCE_URL"].TryRead(out url))
{
url = string.Empty;
message = "The field SOURCE_URL does not exist or is not a valid string.";
message = TB("The field SOURCE_URL does not exist or is not a valid string.");
return false;
}
if (!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
{
url = string.Empty;
message = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'.";
message = TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'.");
return false;
}
@@ -352,7 +354,7 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["CATEGORIES"].TryRead<LuaTable>(out var categoriesTable))
{
categories = [];
message = "The table CATEGORIES does not exist or is using an invalid syntax.";
message = TB("The table CATEGORIES does not exist or is using an invalid syntax.");
return false;
}
@@ -365,7 +367,7 @@ public abstract partial class PluginBase : IPluginMetadata
categories = categoryList.ToArray();
if(categoryList.Count == 0)
{
message = $"The table CATEGORIES is empty. At least one category is necessary. Valid categories are: {CommonTools.GetAllEnumValues(PluginCategory.NONE)}.";
message = string.Format(TB("The table CATEGORIES is empty. At least one category is necessary. Valid categories are: {0}."), CommonTools.GetAllEnumValues(PluginCategory.NONE));
return false;
}
@@ -384,7 +386,7 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["TARGET_GROUPS"].TryRead<LuaTable>(out var targetGroupsTable))
{
targetGroups = [];
message = "The table TARGET_GROUPS does not exist or is using an invalid syntax.";
message = TB("The table TARGET_GROUPS does not exist or is using an invalid syntax.");
return false;
}
@@ -397,7 +399,7 @@ public abstract partial class PluginBase : IPluginMetadata
targetGroups = targetGroupList.ToArray();
if(targetGroups.Length == 0)
{
message = "The table TARGET_GROUPS is empty or is not a valid table of strings. Valid target groups are: {CommonTools.GetAllEnumValues(PluginTargetGroup.NONE)}.";
message = string.Format(TB("The table TARGET_GROUPS is empty or is not a valid table of strings. Valid target groups are: {0}."), CommonTools.GetAllEnumValues(PluginTargetGroup.NONE));
return false;
}
@@ -416,7 +418,7 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["IS_MAINTAINED"].TryRead(out isMaintained))
{
isMaintained = false;
message = "The field IS_MAINTAINED does not exist or is not a valid boolean.";
message = TB("The field IS_MAINTAINED does not exist or is not a valid boolean.");
return false;
}
@@ -435,7 +437,7 @@ public abstract partial class PluginBase : IPluginMetadata
if (!this.state.Environment["DEPRECATION_MESSAGE"].TryRead(out deprecationMessage))
{
deprecationMessage = string.Empty;
message = "The field DEPRECATION_MESSAGE does not exist, is not a valid string. This message is optional: use an empty string to indicate that the plugin is not deprecated.";
message = TB("The field DEPRECATION_MESSAGE does not exist, is not a valid string. This message is optional: use an empty string to indicate that the plugin is not deprecated.");
return false;
}
@@ -453,7 +455,7 @@ public abstract partial class PluginBase : IPluginMetadata
{
if (!this.state.Environment["UI_TEXT_CONTENT"].TryRead<LuaTable>(out var textTable))
{
message = "The UI_TEXT_CONTENT table does not exist or is not a valid table.";
message = TB("The UI_TEXT_CONTENT table does not exist or is not a valid table.");
pluginContent = [];
return false;
}
@@ -2,37 +2,39 @@ namespace AIStudio.Tools.PluginSystem;
public static class PluginCategoryExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginCategoryExtensions).Namespace, nameof(PluginCategoryExtensions));
public static string GetName(this PluginCategory type) => type switch
{
PluginCategory.NONE => "None",
PluginCategory.CORE => "AI Studio Core",
PluginCategory.NONE => TB("None"),
PluginCategory.CORE => TB("AI Studio Core"),
PluginCategory.BUSINESS => "Business",
PluginCategory.INDUSTRY => "Industry",
PluginCategory.UTILITY => "Utility",
PluginCategory.SOFTWARE_DEVELOPMENT => "Software Development",
PluginCategory.GAMING => "Gaming",
PluginCategory.EDUCATION => "Education",
PluginCategory.ENTERTAINMENT => "Entertainment",
PluginCategory.SOCIAL => "Social",
PluginCategory.SHOPPING => "Shopping",
PluginCategory.TRAVEL => "Travel",
PluginCategory.HEALTH => "Health",
PluginCategory.FITNESS => "Fitness",
PluginCategory.FOOD => "Food",
PluginCategory.PARTY => "Party",
PluginCategory.SPORTS => "Sports",
PluginCategory.NEWS => "News",
PluginCategory.WEATHER => "Weather",
PluginCategory.MUSIC => "Music",
PluginCategory.POLITICAL => "Political",
PluginCategory.SCIENCE => "Science",
PluginCategory.TECHNOLOGY => "Technology",
PluginCategory.ART => "Art",
PluginCategory.FICTION => "Fiction",
PluginCategory.WRITING => "Writing",
PluginCategory.CONTENT_CREATION => "Content Creation",
PluginCategory.BUSINESS => TB("Business"),
PluginCategory.INDUSTRY => TB("Industry"),
PluginCategory.UTILITY => TB("Utility"),
PluginCategory.SOFTWARE_DEVELOPMENT => TB("Software Development"),
PluginCategory.GAMING => TB("Gaming"),
PluginCategory.EDUCATION => TB("Education"),
PluginCategory.ENTERTAINMENT => TB("Entertainment"),
PluginCategory.SOCIAL => TB("Social"),
PluginCategory.SHOPPING => TB("Shopping"),
PluginCategory.TRAVEL => TB("Travel"),
PluginCategory.HEALTH => TB("Health"),
PluginCategory.FITNESS => TB("Fitness"),
PluginCategory.FOOD => TB("Food"),
PluginCategory.PARTY => TB("Party"),
PluginCategory.SPORTS => TB("Sports"),
PluginCategory.NEWS => TB("News"),
PluginCategory.WEATHER => TB("Weather"),
PluginCategory.MUSIC => TB("Music"),
PluginCategory.POLITICAL => TB("Political"),
PluginCategory.SCIENCE => TB("Science"),
PluginCategory.TECHNOLOGY => TB("Technology"),
PluginCategory.ART => TB("Art"),
PluginCategory.FICTION => TB("Fiction"),
PluginCategory.WRITING => TB("Writing"),
PluginCategory.CONTENT_CREATION => TB("Content Creation"),
_ => "Unknown plugin category",
_ => TB("Unknown plugin category"),
};
}
@@ -16,7 +16,27 @@ public static partial class PluginFactory
return;
}
// A plugin update might remove some resources. Even worse, a plugin
// might have changed its name, etc. Thus, we delete the internal
// plugin directories before copying the new resources:
LOG.LogInformation("Try to delete the internal plugins directory for maintenance.");
if (Directory.Exists(INTERNAL_PLUGINS_ROOT))
{
try
{
Directory.Delete(INTERNAL_PLUGINS_ROOT, true);
LOG.LogInformation("Successfully deleted the internal plugins directory for maintenance.");
}
catch (Exception e)
{
LOG.LogError($"Could not delete the internal plugins directory for maintenance: {INTERNAL_PLUGINS_ROOT}. Error: {e}");
}
}
LOG.LogInformation("Start ensuring internal plugins.");
if(!Directory.Exists(INTERNAL_PLUGINS_ROOT))
Directory.CreateDirectory(INTERNAL_PLUGINS_ROOT);
foreach (var plugin in Enum.GetValues<InternalPlugin>())
{
LOG.LogInformation($"Ensure plugin: {plugin}");
@@ -4,6 +4,7 @@ namespace AIStudio.Tools.PluginSystem;
public sealed class PluginLanguage : PluginBase, ILanguagePlugin
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginLanguage).Namespace, nameof(PluginLanguage));
private static readonly ILogger<PluginLanguage> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginLanguage>();
private readonly Dictionary<string, string> content = [];
@@ -53,28 +54,28 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
{
if (!this.state.Environment["IETF_TAG"].TryRead(out readLangCultureTag))
{
message = "The field IETF_TAG does not exist or is not a valid string.";
message = TB("The field IETF_TAG does not exist or is not a valid string.");
readLangCultureTag = string.Empty;
return false;
}
if (string.IsNullOrWhiteSpace(readLangCultureTag))
{
message = "The field IETF_TAG is empty. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
message = TB("The field IETF_TAG is empty. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.");
readLangCultureTag = string.Empty;
return false;
}
if (readLangCultureTag.Length != 5)
{
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
message = TB("The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.");
readLangCultureTag = string.Empty;
return false;
}
if (readLangCultureTag[2] != '-')
{
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
message = TB("The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.");
readLangCultureTag = string.Empty;
return false;
}
@@ -83,7 +84,7 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
for (var i = 0; i < 2; i++)
if (!char.IsLower(readLangCultureTag[i]))
{
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
message = TB("The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.");
readLangCultureTag = string.Empty;
return false;
}
@@ -92,7 +93,7 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
for (var i = 3; i < 5; i++)
if (!char.IsUpper(readLangCultureTag[i]))
{
message = "The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.";
message = TB("The field IETF_TAG is not a valid IETF tag. Use a valid IETF tag like 'en-US'. The first part is the language, the second part is the country code.");
readLangCultureTag = string.Empty;
return false;
}
@@ -105,14 +106,14 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
{
if (!this.state.Environment["LANG_NAME"].TryRead(out readLangName))
{
message = "The field LANG_NAME does not exist or is not a valid string.";
message = TB("The field LANG_NAME does not exist or is not a valid string.");
readLangName = string.Empty;
return false;
}
if (string.IsNullOrWhiteSpace(readLangName))
{
message = "The field LANG_NAME is empty. Use a valid language name.";
message = TB("The field LANG_NAME is empty. Use a valid language name.");
readLangName = string.Empty;
return false;
}
@@ -2,24 +2,26 @@ namespace AIStudio.Tools.PluginSystem;
public static class PluginTargetGroupExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginTargetGroupExtensions).Namespace, nameof(PluginTargetGroupExtensions));
public static string Name(this PluginTargetGroup group) => group switch
{
PluginTargetGroup.NONE => "No target group",
PluginTargetGroup.NONE => TB("No target group"),
PluginTargetGroup.EVERYONE => "Everyone",
PluginTargetGroup.CHILDREN => "Children",
PluginTargetGroup.TEENAGERS => "Teenagers",
PluginTargetGroup.STUDENTS => "Students",
PluginTargetGroup.ADULTS => "Adults",
PluginTargetGroup.EVERYONE => TB("Everyone"),
PluginTargetGroup.CHILDREN => TB("Children"),
PluginTargetGroup.TEENAGERS => TB("Teenagers"),
PluginTargetGroup.STUDENTS => TB("Students"),
PluginTargetGroup.ADULTS => TB("Adults"),
PluginTargetGroup.INDUSTRIAL_WORKERS => "Industrial workers",
PluginTargetGroup.OFFICE_WORKERS => "Office workers",
PluginTargetGroup.BUSINESS_PROFESSIONALS => "Business professionals",
PluginTargetGroup.SOFTWARE_DEVELOPERS => "Software developers",
PluginTargetGroup.SCIENTISTS => "Scientists",
PluginTargetGroup.TEACHERS => "Teachers",
PluginTargetGroup.ARTISTS => "Artists",
PluginTargetGroup.INDUSTRIAL_WORKERS => TB("Industrial workers"),
PluginTargetGroup.OFFICE_WORKERS => TB("Office workers"),
PluginTargetGroup.BUSINESS_PROFESSIONALS => TB("Business professionals"),
PluginTargetGroup.SOFTWARE_DEVELOPERS => TB("Software developers"),
PluginTargetGroup.SCIENTISTS => TB("Scientists"),
PluginTargetGroup.TEACHERS => TB("Teachers"),
PluginTargetGroup.ARTISTS => TB("Artists"),
_ => "Unknown target group",
_ => TB("Unknown target group"),
};
}
@@ -2,14 +2,16 @@ namespace AIStudio.Tools.PluginSystem;
public static class PluginTypeExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginTypeExtensions).Namespace, nameof(PluginTypeExtensions));
public static string GetName(this PluginType type) => type switch
{
PluginType.LANGUAGE => "Language plugin",
PluginType.ASSISTANT => "Assistant plugin",
PluginType.CONFIGURATION => "Configuration plugin",
PluginType.THEME => "Theme plugin",
PluginType.LANGUAGE => TB("Language plugin"),
PluginType.ASSISTANT => TB("Assistant plugin"),
PluginType.CONFIGURATION => TB("Configuration plugin"),
PluginType.THEME => TB("Theme plugin"),
_ => "Unknown plugin type",
_ => TB("Unknown plugin type"),
};
public static string GetDirectory(this PluginType type) => type switch
@@ -4,21 +4,24 @@ using AIStudio.Agents;
using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.RAG.AugmentationProcesses;
public sealed class AugmentationOne : IAugmentationProcess
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AugmentationOne).Namespace, nameof(AugmentationOne));
#region Implementation of IAugmentationProcess
/// <inheritdoc />
public string TechnicalName => "AugmentationOne";
/// <inheritdoc />
public string UIName => "Standard augmentation process";
public string UIName => TB("Standard augmentation process");
/// <inheritdoc />
public string Description => "This is the standard augmentation process, which uses all retrieval contexts to augment the chat thread.";
public string Description => TB("This is the standard augmentation process, which uses all retrieval contexts to augment the chat thread.");
/// <inheritdoc />
public async Task<ChatThread> ProcessAsync(IProvider provider, IContent lastPrompt, ChatThread chatThread, IReadOnlyList<IRetrievalContext> retrievalContexts, CancellationToken token = default)
@@ -3,21 +3,24 @@ using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.RAG.DataSourceSelectionProcesses;
public class AgenticSrcSelWithDynHeur : IDataSourceSelectionProcess
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AgenticSrcSelWithDynHeur).Namespace, nameof(AgenticSrcSelWithDynHeur));
#region Implementation of IDataSourceSelectionProcess
/// <inheritdoc />
public string TechnicalName => "AgenticSrcSelWithDynHeur";
/// <inheritdoc />
public string UIName => "Automatic AI data source selection with heuristik source reduction";
public string UIName => TB("Automatic AI data source selection with heuristik source reduction");
/// <inheritdoc />
public string Description => "Automatically selects the appropriate data sources based on the last prompt. Applies a heuristic reduction at the end to reduce the number of data sources.";
public string Description => TB("Automatically selects the appropriate data sources based on the last prompt. Applies a heuristic reduction at the end to reduce the number of data sources.");
/// <inheritdoc />
public async Task<DataSelectionResult> SelectDataSourcesAsync(IProvider provider, IContent lastPrompt, ChatThread chatThread, AllowedSelectedDataSources dataSources, CancellationToken token = default)
@@ -2,6 +2,7 @@ using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.RAG.AugmentationProcesses;
using AIStudio.Tools.RAG.DataSourceSelectionProcesses;
using AIStudio.Tools.Services;
@@ -10,16 +11,18 @@ namespace AIStudio.Tools.RAG.RAGProcesses;
public sealed class AISrcSelWithRetCtxVal : IRagProcess
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AISrcSelWithRetCtxVal).Namespace, nameof(AISrcSelWithRetCtxVal));
#region Implementation of IRagProcess
/// <inheritdoc />
public string TechnicalName => "AISrcSelWithRetCtxVal";
/// <inheritdoc />
public string UIName => "AI source selection with AI retrieval context validation";
public string UIName => TB("AI source selection with AI retrieval context validation");
/// <inheritdoc />
public string Description => "This RAG process filters data sources, automatically selects appropriate sources, optionally allows manual source selection, retrieves data, and automatically validates the retrieval context.";
public string Description => TB("This RAG process filters data sources, automatically selects appropriate sources, optionally allows manual source selection, retrieves data, and automatically validates the retrieval context.");
/// <inheritdoc />
public async Task<ChatThread> ProcessAsync(IProvider provider, IContent lastPrompt, ChatThread chatThread, CancellationToken token = default)
@@ -0,0 +1,23 @@
// ReSharper disable NotAccessedPositionalProperty.Global
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.Rust;
/// <summary>
/// Represents a file type filter for file selection dialogs.
/// </summary>
/// <param name="FilterName">The name of the filter.</param>
/// <param name="FilterExtensions">The file extensions associated with the filter.</param>
public readonly record struct FileTypeFilter(string FilterName, string[] FilterExtensions)
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileTypeFilter).Namespace, nameof(FileTypeFilter));
public static FileTypeFilter PDF => new(TB("PDF Files"), ["pdf"]);
public static FileTypeFilter Text => new(TB("Text Files"), ["txt", "md"]);
public static FileTypeFilter AllOffice => new(TB("All Office Files"), ["docx", "xlsx", "pptx", "doc", "xls", "ppt", "pdf"]);
public static FileTypeFilter AllImages => new(TB("All Image Files"), ["jpg", "jpeg", "png", "gif", "bmp", "tiff"]);
}
@@ -0,0 +1,10 @@
namespace AIStudio.Tools.Rust;
public sealed class SelectFileOptions
{
public required string Title { get; init; }
public PreviousFile? PreviousFile { get; init; }
public FileTypeFilter? Filter { get; init; }
}
@@ -1,9 +1,12 @@
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
private static string TB_APIKeys(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, $"{nameof(RustService)}.APIKeys");
/// <summary>
/// Try to get the API key for the given secret ID.
/// </summary>
@@ -12,13 +15,15 @@ public sealed partial class RustService
/// <returns>The requested secret.</returns>
public async Task<RequestedSecret> GetAPIKey(ISecretId secretId, bool isTrying = false)
{
static string TB(string fallbackEN) => TB_APIKeys(fallbackEN);
var secretRequest = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
if(!isTrying)
this.logger!.LogError($"Failed to get the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new RequestedSecret(false, new EncryptedText(string.Empty), "Failed to get the API key due to an API issue.");
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the API key due to an API issue."));
}
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
@@ -36,13 +41,15 @@ public sealed partial class RustService
/// <returns>The store secret response.</returns>
public async Task<StoreSecretResponse> SetAPIKey(ISecretId secretId, string key)
{
static string TB(string fallbackEN) => TB_APIKeys(fallbackEN);
var encryptedKey = await this.encryptor!.Encrypt(key);
var request = new StoreSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, encryptedKey);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to store the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new StoreSecretResponse(false, "Failed to get the API key due to an API issue.");
return new StoreSecretResponse(false, TB("Failed to get the API key due to an API issue."));
}
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
@@ -59,12 +66,14 @@ public sealed partial class RustService
/// <returns>The delete secret response.</returns>
public async Task<DeleteSecretResponse> DeleteAPIKey(ISecretId secretId)
{
static string TB(string fallbackEN) => TB_APIKeys(fallbackEN);
var request = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, false);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to delete the API key for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = "Failed to delete the API key due to an API issue."};
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the API key due to an API issue.")};
}
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
@@ -1,9 +1,12 @@
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
private static string TB_Clipboard(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, $"{nameof(RustService)}.Clipboard");
/// <summary>
/// Tries to copy the given text to the clipboard.
/// </summary>
@@ -11,7 +14,9 @@ public sealed partial class RustService
/// <param name="text">The text to copy to the clipboard.</param>
public async Task CopyText2Clipboard(ISnackbar snackbar, string text)
{
var message = "Successfully copied the text to your clipboard";
static string TB(string fallbackEN) => TB_Clipboard(fallbackEN);
var message = TB("Successfully copied the text to your clipboard");
var iconColor = Color.Error;
var severity = Severity.Error;
try
@@ -21,7 +26,7 @@ public sealed partial class RustService
if (!response.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to copy the text to the clipboard due to an network error: '{response.StatusCode}'");
message = "Failed to copy the text to your clipboard.";
message = TB("Failed to copy the text to your clipboard.");
return;
}
@@ -29,7 +34,7 @@ public sealed partial class RustService
if (!state.Success)
{
this.logger!.LogError("Failed to copy the text to the clipboard.");
message = "Failed to copy the text to your clipboard.";
message = TB("Failed to copy the text to your clipboard.");
return;
}
@@ -17,10 +17,16 @@ public sealed partial class RustService
return await result.Content.ReadFromJsonAsync<DirectorySelectionResponse>(this.jsonRustSerializerOptions);
}
public async Task<FileSelectionResponse> SelectFile(string title, string? initialFile = null)
public async Task<FileSelectionResponse> SelectFile(string title, FileTypeFilter? filter = null, string? initialFile = null)
{
PreviousFile? previousFile = initialFile is null ? null : new (initialFile);
var result = await this.http.PostAsJsonAsync($"/select/file?title={title}", previousFile, this.jsonRustSerializerOptions);
var payload = new SelectFileOptions
{
Title = title,
PreviousFile = initialFile is null ? null : new (initialFile),
Filter = filter
};
var result = await this.http.PostAsJsonAsync("/select/file", payload, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to select a file: '{result.StatusCode}'");
@@ -0,0 +1,16 @@
namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
public async Task<string> GetPDFText(string filePath)
{
var response = await this.http.GetAsync($"/retrieval/fs/read/pdf?file_path={filePath}");
if (!response.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to read the PDF file due to an network error: '{response.StatusCode}'");
return string.Empty;
}
return await response.Content.ReadAsStringAsync();
}
}
@@ -1,9 +1,12 @@
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
private static string TB_Secrets(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, $"{nameof(RustService)}.Secrets");
/// <summary>
/// Try to get the secret data for the given secret ID.
/// </summary>
@@ -12,13 +15,15 @@ public sealed partial class RustService
/// <returns>The requested secret.</returns>
public async Task<RequestedSecret> GetSecret(ISecretId secretId, bool isTrying = false)
{
static string TB(string fallbackEN) => TB_Secrets(fallbackEN);
var secretRequest = new SelectSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
if(!isTrying)
this.logger!.LogError($"Failed to get the secret data for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new RequestedSecret(false, new EncryptedText(string.Empty), "Failed to get the secret data due to an API issue.");
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the secret data due to an API issue."));
}
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
@@ -36,13 +41,15 @@ public sealed partial class RustService
/// <returns>The store secret response.</returns>
public async Task<StoreSecretResponse> SetSecret(ISecretId secretId, string secretData)
{
static string TB(string fallbackEN) => TB_Secrets(fallbackEN);
var encryptedSecret = await this.encryptor!.Encrypt(secretData);
var request = new StoreSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, encryptedSecret);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to store the secret data for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new StoreSecretResponse(false, "Failed to get the secret data due to an API issue.");
return new StoreSecretResponse(false, TB("Failed to get the secret data due to an API issue."));
}
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
@@ -59,12 +66,14 @@ public sealed partial class RustService
/// <returns>The delete secret response.</returns>
public async Task<DeleteSecretResponse> DeleteSecret(ISecretId secretId)
{
static string TB(string fallbackEN) => TB_Secrets(fallbackEN);
var request = new SelectSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, false);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to delete the secret data for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = "Failed to delete the secret data due to an API issue."};
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the secret data due to an API issue.")};
}
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
@@ -1,5 +1,6 @@
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components;
@@ -7,6 +8,8 @@ namespace AIStudio.Tools.Services;
public sealed class UpdateService : BackgroundService, IMessageBusReceiver
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(UpdateService).Namespace, nameof(UpdateService));
private static bool IS_INITIALIZED;
private static ISnackbar? SNACKBAR;
@@ -118,7 +121,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
{
if (notifyUserWhenNoUpdate)
{
SNACKBAR!.Add("No update found.", Severity.Normal, config =>
SNACKBAR!.Add(TB("No update found."), Severity.Normal, config =>
{
config.Icon = Icons.Material.Filled.Update;
config.IconSize = Size.Large;
@@ -1,10 +1,13 @@
using AIStudio.Settings.DataModel;
using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.Validation;
public sealed class DataSourceValidation
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceValidation).Namespace, nameof(DataSourceValidation));
public Func<string> GetSecretStorageIssue { get; init; } = () => string.Empty;
public Func<string> GetPreviousDataSourceName { get; init; } = () => string.Empty;
@@ -26,13 +29,13 @@ public sealed class DataSourceValidation
public string? ValidatingHostname(string hostname)
{
if(string.IsNullOrWhiteSpace(hostname))
return "Please enter a hostname, e.g., http://localhost";
return TB("Please enter a hostname, e.g., http://localhost");
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
return "The hostname must start with either http:// or https://";
return TB("The hostname must start with either http:// or https://");
if(!Uri.TryCreate(hostname, UriKind.Absolute, out _))
return "The hostname is not a valid HTTP(S) URL.";
return TB("The hostname is not a valid HTTP(S) URL.");
return null;
}
@@ -40,7 +43,7 @@ public sealed class DataSourceValidation
public string? ValidatePort(int port)
{
if(port is < 1 or > 65535)
return "The port must be between 1 and 65535.";
return TB("The port must be between 1 and 65535.");
return null;
}
@@ -48,14 +51,14 @@ public sealed class DataSourceValidation
public string? ValidateSecurityPolicy(DataSourceSecurity securityPolicy)
{
if(securityPolicy is DataSourceSecurity.NOT_SPECIFIED)
return "Please select your security policy.";
return TB("Please select your security policy.");
var dataSourceSecurity = this.GetSecurityRequirements();
if (dataSourceSecurity is null)
return null;
if(dataSourceSecurity.Value.AllowedProviderType is ProviderType.SELF_HOSTED && securityPolicy is not DataSourceSecurity.SELF_HOSTED)
return "This data source can only be used with a self-hosted LLM provider. Please change the security policy.";
return TB("This data source can only be used with a self-hosted LLM provider. Please change the security policy.");
return null;
}
@@ -66,7 +69,7 @@ public sealed class DataSourceValidation
return null;
if(string.IsNullOrWhiteSpace(username))
return "The username must not be empty.";
return TB("The username must not be empty.");
return null;
}
@@ -84,10 +87,10 @@ public sealed class DataSourceValidation
if (string.IsNullOrWhiteSpace(secret))
return authMethod switch
{
AuthMethod.TOKEN => "Please enter your secure access token.",
AuthMethod.USERNAME_PASSWORD => "Please enter your password.",
AuthMethod.TOKEN => TB("Please enter your secure access token."),
AuthMethod.USERNAME_PASSWORD => TB("Please enter your password."),
_ => "Please enter the secret necessary for authentication."
_ => TB("Please enter the secret necessary for authentication.")
};
return null;
@@ -96,7 +99,7 @@ public sealed class DataSourceValidation
public string? ValidateRetrievalProcess(RetrievalInfo retrievalInfo)
{
if(retrievalInfo == default)
return "Please select one retrieval process.";
return TB("Please select one retrieval process.");
return null;
}
@@ -104,14 +107,14 @@ public sealed class DataSourceValidation
public string? ValidatingName(string dataSourceName)
{
if(string.IsNullOrWhiteSpace(dataSourceName))
return "The name must not be empty.";
return TB("The name must not be empty.");
if (dataSourceName.Length > 40)
return "The name must not exceed 40 characters.";
return TB("The name must not exceed 40 characters.");
var lowerName = dataSourceName.ToLowerInvariant();
if(lowerName != this.GetPreviousDataSourceName() && this.GetUsedDataSourceNames().Contains(lowerName))
return "The name is already used by another data source. Please choose a different name.";
return TB("The name is already used by another data source. Please choose a different name.");
return null;
}
@@ -119,10 +122,10 @@ public sealed class DataSourceValidation
public string? ValidatePath(string path)
{
if(string.IsNullOrWhiteSpace(path))
return "The path must not be empty. Please select a directory.";
return TB("The path must not be empty. Please select a directory.");
if(!Directory.Exists(path))
return "The path does not exist. Please select a valid directory.";
return TB("The path does not exist. Please select a valid directory.");
return null;
}
@@ -130,10 +133,10 @@ public sealed class DataSourceValidation
public string? ValidateFilePath(string filePath)
{
if(string.IsNullOrWhiteSpace(filePath))
return "The file path must not be empty. Please select a file.";
return TB("The file path must not be empty. Please select a file.");
if(!File.Exists(filePath))
return "The file does not exist. Please select a valid file.";
return TB("The file does not exist. Please select a valid file.");
return null;
}
@@ -141,7 +144,7 @@ public sealed class DataSourceValidation
public string? ValidateEmbeddingId(string embeddingId)
{
if(string.IsNullOrWhiteSpace(embeddingId))
return "Please select an embedding provider.";
return TB("Please select an embedding provider.");
return null;
}
@@ -149,7 +152,7 @@ public sealed class DataSourceValidation
public string? ValidateUserAcknowledgedCloudEmbedding(bool value)
{
if(this.GetSelectedCloudEmbedding() && !value)
return "Please acknowledge that you are aware of the cloud embedding implications.";
return TB("Please acknowledge that you are aware of the cloud embedding implications.");
return null;
}
@@ -157,10 +160,10 @@ public sealed class DataSourceValidation
public string? ValidateTestedConnection()
{
if(!this.GetTestedConnection())
return "Please test the connection before saving.";
return TB("Please test the connection before saving.");
if(!this.GetTestedConnectionResult())
return "The connection test failed. Please check the connection settings.";
return TB("The connection test failed. Please check the connection settings.");
return null;
}
@@ -168,7 +171,7 @@ public sealed class DataSourceValidation
public string? ValidateAuthMethod(AuthMethod authMethod)
{
if(!this.GetAvailableAuthMethods().Contains(authMethod))
return "Please select one valid authentication method.";
return TB("Please select one valid authentication method.");
return null;
}
@@ -1,11 +1,15 @@
using AIStudio.Provider;
using AIStudio.Provider.HuggingFace;
using AIStudio.Tools.PluginSystem;
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Tools.Validation;
public sealed class ProviderValidation
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderValidation).Namespace, nameof(ProviderValidation));
public Func<LLMProviders> GetProvider { get; init; } = () => LLMProviders.NONE;
public Func<string> GetAPIKeyStorageIssue { get; init; } = () => string.Empty;
@@ -22,13 +26,13 @@ public sealed class ProviderValidation
return null;
if(string.IsNullOrWhiteSpace(hostname))
return "Please enter a hostname, e.g., http://localhost:1234";
return TB("Please enter a hostname, e.g., http://localhost:1234");
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
return "The hostname must start with either http:// or https://";
return TB("The hostname must start with either http:// or https://");
if(!Uri.TryCreate(hostname, UriKind.Absolute, out _))
return "The hostname is not a valid HTTP(S) URL.";
return TB("The hostname is not a valid HTTP(S) URL.");
return null;
}
@@ -43,7 +47,7 @@ public sealed class ProviderValidation
return apiKeyStorageIssue;
if(string.IsNullOrWhiteSpace(apiKey))
return "Please enter an API key.";
return TB("Please enter an API key.");
return null;
}
@@ -51,15 +55,15 @@ public sealed class ProviderValidation
public string? ValidatingInstanceName(string instanceName)
{
if (string.IsNullOrWhiteSpace(instanceName))
return "Please enter an instance name.";
return TB("Please enter an instance name.");
if (instanceName.Length > 40)
return "The instance name must not exceed 40 characters.";
return TB("The instance name must not exceed 40 characters.");
// The instance name must be unique:
var lowerInstanceName = instanceName.ToLowerInvariant();
if (lowerInstanceName != this.GetPreviousInstanceName() && this.GetUsedInstanceNames().Contains(lowerInstanceName))
return "The instance name must be unique; the chosen name is already in use.";
return TB("The instance name must be unique; the chosen name is already in use.");
return null;
}
@@ -70,7 +74,7 @@ public sealed class ProviderValidation
return null;
if (model == default)
return "Please select a model.";
return TB("Please select a model.");
return null;
}
@@ -78,7 +82,7 @@ public sealed class ProviderValidation
public string? ValidatingProvider(LLMProviders llmProvider)
{
if (llmProvider == LLMProviders.NONE)
return "Please select a provider.";
return TB("Please select a provider.");
return null;
}
@@ -89,7 +93,7 @@ public sealed class ProviderValidation
return null;
if (host == Host.NONE)
return "Please select a host.";
return TB("Please select a host.");
return null;
}
@@ -100,7 +104,7 @@ public sealed class ProviderValidation
return null;
if (inferenceProvider is HFInferenceProvider.NONE)
return "Please select an Hugging Face inference provider.";
return TB("Please select an Hugging Face inference provider.");
return null;
}
@@ -5,11 +5,14 @@ using System.Text.Json.Serialization;
using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools;
public static class WorkspaceBehaviour
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
public static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
WriteIndented = true,
@@ -97,13 +100,13 @@ public static class WorkspaceBehaviour
{
"Message", (chat.WorkspaceId == Guid.Empty) switch
{
true => $"Are you sure you want to delete the temporary chat '{chat.Name}'?",
false => $"Are you sure you want to delete the chat '{chat.Name}' in the workspace '{workspaceName}'?",
true => TB($"Are you sure you want to delete the temporary chat '{chat.Name}'?"),
false => TB($"Are you sure you want to delete the chat '{chat.Name}' in the workspace '{workspaceName}'?"),
}
},
};
var dialogReference = await dialogService.ShowAsync<ConfirmDialog>("Delete Chat", dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
var dialogReference = await dialogService.ShowAsync<ConfirmDialog>(TB("Delete Chat"), dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
+39 -39
View File
@@ -4,14 +4,14 @@
"net9.0": {
"CodeBeam.MudBlazor.Extensions": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "MNxReFDmME1OLhHsD7lrfQZ9cMu5X60PIvidyFueq0ddDKHlagHS6CuYBZRQ62rHqmocdHWOLyJ73t2Im0NnWw==",
"requested": "[8.1.0, )",
"resolved": "8.1.0",
"contentHash": "6f6K8Qr+cjk419URr8+b9nkSr0y300n/DaB0tJdYVRtDsWwoVCvgPwf9kENdDg1HIiYPL524+Q5a9QraL6hAvw==",
"dependencies": {
"BuildBundlerMinifier": "3.2.449",
"CsvHelper": "33.0.1",
"Microsoft.AspNetCore.Components": "9.0.1",
"Microsoft.AspNetCore.Components.Web": "9.0.1",
"Microsoft.AspNetCore.Components": "9.0.4",
"Microsoft.AspNetCore.Components.Web": "9.0.4",
"MudBlazor": "8.0.0",
"ZXing.Net": "0.16.9"
}
@@ -45,9 +45,9 @@
},
"MudBlazor": {
"type": "Direct",
"requested": "[8.5.1, )",
"resolved": "8.5.1",
"contentHash": "xzGhtY60e1fvvyI7rJSYYV0UEkvY8niKuTaqby19cb8rELd29EpPU439RVL8KjVSMy473BEcpgYWBRAoDPGl8A==",
"requested": "[8.6.0, )",
"resolved": "8.6.0",
"contentHash": "vJROmWdTjiBmyiZBSbD8RcYVFfLbNMEQpKoCOMfY2628KbOLGVcnH1RBziHGyJH9pqc99UoCQS81XLm8X+49ug==",
"dependencies": {
"Microsoft.AspNetCore.Components": "9.0.1",
"Microsoft.AspNetCore.Components.Web": "9.0.1",
@@ -56,9 +56,9 @@
},
"MudBlazor.Markdown": {
"type": "Direct",
"requested": "[8.5.1, )",
"resolved": "8.5.1",
"contentHash": "UrNr948Nn70CuDoTWaN/HgtaIKO4oKfj7W6Mw8Ei9OG7MCzCwkhUYJLe/CRTKCkt3wINquB/2AAN7ezgEfmbww==",
"requested": "[8.5.1.1, )",
"resolved": "8.5.1.1",
"contentHash": "3FQaHlpLV54W77aVl58JCH2HX0p2QcqGNamtxyVzMKeMa92x+MYm4DFcqqhfbEvNEWTRZ6q22ZCx/NG0UX422Q==",
"dependencies": {
"Markdig": "0.41.0",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
@@ -91,52 +91,52 @@
},
"Microsoft.AspNetCore.Authorization": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "WgLlLBlMczb2+QLNG6sM95OUZ0EBztz60k/N75tjIgpyu0SdpIfYytAmX/7JJAjRTZF0c/CrWaQV+SH9FuGsrA==",
"resolved": "9.0.4",
"contentHash": "QIV3Jk/zb5wsBNkxj4eK4Qoja6f0hO2pUhWrG8NOmgnclA7zbQvzLLKaGnva7/PtglS8VS5HusZfrnnO8FTpSA==",
"dependencies": {
"Microsoft.AspNetCore.Metadata": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Microsoft.Extensions.Options": "9.0.1"
"Microsoft.AspNetCore.Metadata": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.AspNetCore.Components": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "6pwfbQKNtvPkbF4tCGiAKGyt6BVpu58xAXz7u2YXcUKTNmNxrymbG1mEyMc0EPzVdnquDDqTyfXM3mC1EJycxQ==",
"resolved": "9.0.4",
"contentHash": "imeT4r1PLXqI3kzf7b06Tzj5H4od8o/u4ZE5JgyrlvwABSZlBQzWN8AgT/lPLqs2VJVxNwcAolx+vxLMl9Dn+g==",
"dependencies": {
"Microsoft.AspNetCore.Authorization": "9.0.1",
"Microsoft.AspNetCore.Components.Analyzers": "9.0.1"
"Microsoft.AspNetCore.Authorization": "9.0.4",
"Microsoft.AspNetCore.Components.Analyzers": "9.0.4"
}
},
"Microsoft.AspNetCore.Components.Analyzers": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "I8Rs4LXT5UQxM5Nin2+Oj8aSY2heszSZ3EyTLgt3mxmfiRPrVO7D8NNSsf1voI2Gb0qFJceof/J5c9E+nfNuHw=="
"resolved": "9.0.4",
"contentHash": "fPrKrrfT5o0dJZ4AgbGsXBl4YNTlSrRcpYMgLTsrmYo7aOkZrdWPmsxVsFRyelfmCW9qHH7ZZK/4cTC8HuS7BA=="
},
"Microsoft.AspNetCore.Components.Forms": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "KyULVU32bLz74LWDwPEwNUEllTehzWJuM7YAsz80rMKEzvR0K8cRjRzO0fnN/nfydMeLRRlbI0xj8wnEAymLVw==",
"resolved": "9.0.4",
"contentHash": "FvoNIyz5URP2QonItKbDBzCcdmSXHWfyh0lUAYkNittlLmFAqaIF4TE55h3/SYLI8ISsbAVd44Cax86HBhLu8w==",
"dependencies": {
"Microsoft.AspNetCore.Components": "9.0.1"
"Microsoft.AspNetCore.Components": "9.0.4"
}
},
"Microsoft.AspNetCore.Components.Web": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "LI0vjYEd9MaDZPDQxPCn4gGYDkEC5U9rp1nWZo7rPozJxgTG2zU3WERujxTi2LeAC2ZzdXlOVCrUyPQ55LZV2A==",
"resolved": "9.0.4",
"contentHash": "X02T/wGvT7vCgIXG0R4ZWQUx5/qMaKuWJklBL3eaa8b1NJHyPlox6IpI4AcmMy7r1hM8GwaxKfm34dv5b7n5VA==",
"dependencies": {
"Microsoft.AspNetCore.Components": "9.0.1",
"Microsoft.AspNetCore.Components.Forms": "9.0.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.Primitives": "9.0.1",
"Microsoft.JSInterop": "9.0.1"
"Microsoft.AspNetCore.Components": "9.0.4",
"Microsoft.AspNetCore.Components.Forms": "9.0.4",
"Microsoft.Extensions.DependencyInjection": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4",
"Microsoft.JSInterop": "9.0.4"
}
},
"Microsoft.AspNetCore.Metadata": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "EZnHifamF7IFEIyjAKMtJM3I/94OIe72i3P09v5oL0twmsmfQwal6Ni3m8lbB5mge3jWFhMozeW+rUdRSqnXRQ=="
"resolved": "9.0.4",
"contentHash": "3AhnSEW/Zw0E++itsen8YvqT7UoyF7zYD9OZPQmeGpT7YQP4W9GOiwOljfJEC8+4pP8kqgpeRN/p4LkmKLIUlQ=="
},
"Microsoft.Extensions.Caching.Abstractions": {
"type": "Transitive",
@@ -160,10 +160,10 @@
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "qZI42ASAe3hr2zMSA6UjM92pO1LeDq5DcwkgSowXXPY8I56M76pEKrnmsKKbxagAf39AJxkH2DY4sb72ixyOrg==",
"resolved": "9.0.4",
"contentHash": "f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
@@ -219,8 +219,8 @@
},
"Microsoft.JSInterop": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "/xBwIfb0YoC2Muv6EsHjxpqZw2aKv94+i0g0FWZvqvGv3DeAy+8wipAuECVvKYEs2EIclRD41bjajHLoD6mTtw=="
"resolved": "9.0.4",
"contentHash": "iuzwm79XoRYTRhf7cOHgMgGAE8ZsAcCGp2VsIDAV05qRzceKSE1S7tQp+8GTzG7+IZpdPpvfaJUwSD/pYIog5A=="
},
"ZXing.Net": {
"type": "Transitive",
@@ -1 +1,12 @@
# v0.9.42, build 217 (2025-05-xx xx:xx UTC)
# v0.9.42, build 217 (2025-05-04 13:03 UTC)
- Added the writing style "Changelog" to the rewrite & improve text assistant. This helps to create changelogs for your projects.
- Added an option to load PDF files directly into the translation, text summarization, and legal check assistants as a preview prototype for testing before release.
- Added more text content to the localization system.
- Improved the model selection for OpenAI by removing all `o1-pro` models. These models cannot be used right now, since OpenAI introduced a new API, which is not yet supported by MindWork AI Studio.
- Improved the internal plugin maintenance so that removed resources are now removed from the file system.
- Improved the app settings to apply the chosen language immediately.
- Improved chat name generation to prevent the inclusion of line breaks, tabs, and other special characters.
- Fixed an issue where empty lines in source code were being ignored by the Markdown renderer. Thanks My Nihongo for fixing this bug in the `MudBlazor.Markdown` repository.
- Fixed the localization assistant not being able to load the localization file when used in the release app.
- Fixed the localization of the navigation component.
- Upgraded Rust & .NET dependencies.
@@ -0,0 +1 @@
# v0.9.43, build 218 (2025-05-xx xx:xx UTC)
+7 -6
View File
@@ -1,10 +1,11 @@
0.9.41
2025-04-27 14:18:10 UTC
216
0.9.42
2025-05-04 13:03:03 UTC
217
9.0.105 (commit 35890ecb87)
9.0.4 (commit f57e6dc747)
1.86.0 (commit 05f9846f8)
8.5.1
8.6.0
1.8.1
47b6a896851, release
osx-arm64
7a08baf99bb, release
osx-arm64
137.0.7123.0
+25 -11
View File
@@ -188,6 +188,12 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "atoi_simd"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4790f9e8961209112beb783d85449b508673cf4a6a419c8449b210743ac4dbe9"
[[package]]
name = "atomic"
version = "0.5.3"
@@ -430,15 +436,17 @@ dependencies = [
[[package]]
name = "calamine"
version = "0.26.1"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138646b9af2c5d7f1804ea4bf93afc597737d2bd4f7341d67c48b03316976eb1"
checksum = "6d80f81ba5c68206b9027e62346d49dc26fb32ffc4fe6ef7022a8ae21d348ccb"
dependencies = [
"atoi_simd",
"byteorder",
"codepage",
"encoding_rs",
"fast-float2",
"log",
"quick-xml 0.31.0",
"quick-xml 0.37.5",
"serde",
"zip 2.5.0",
]
@@ -1064,6 +1072,12 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "fast-float2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55"
[[package]]
name = "fastrand"
version = "2.1.0"
@@ -2610,7 +2624,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mindwork-ai-studio"
version = "0.9.41"
version = "0.9.42"
dependencies = [
"aes",
"arboard",
@@ -3200,9 +3214,9 @@ dependencies = [
[[package]]
name = "pdfium-render"
version = "0.8.30"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2773a939ec2c736640f5f2e62a325c2e1a997d694961c50f17cadfb4c8682e84"
checksum = "a473e1f4c2630f7240b127eb9efebd1d9e55053015b624bfe9dbeae0299947e1"
dependencies = [
"bitflags 2.6.0",
"bytemuck",
@@ -3563,20 +3577,20 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.31.0"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
dependencies = [
"encoding_rs",
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.32.0"
version = "0.37.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
dependencies = [
"encoding_rs",
"memchr",
]
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mindwork-ai-studio"
version = "0.9.41"
version = "0.9.42"
edition = "2021"
description = "MindWork AI Studio"
authors = ["Thorsten Sommer"]
@@ -34,8 +34,8 @@ hmac = "0.12.1"
sha2 = "0.10.8"
rcgen = { version = "0.13.2", features = ["pem"] }
file-format = "0.26.0"
calamine = "0.26.1"
pdfium-render = "0.8.30"
calamine = "0.27.0"
pdfium-render = "0.8.31"
sys-locale = "0.3.2"
# Fixes security vulnerability downstream, where the upstream is not fixed yet:
+55 -16
View File
@@ -7,13 +7,14 @@ use rocket::serde::json::Json;
use rocket::serde::Serialize;
use serde::Deserialize;
use tauri::updater::UpdateResponse;
use tauri::{Manager, Window};
use tauri::{Manager, PathResolver, Window};
use tauri::api::dialog::blocking::FileDialogBuilder;
use tokio::time;
use crate::api_token::APIToken;
use crate::dotnet::stop_dotnet_server;
use crate::environment::{is_prod, CONFIG_DIRECTORY, DATA_DIRECTORY};
use crate::log::switch_to_file_logging;
use crate::pdfium::PDFIUM_LIB_PATH;
/// The Tauri main window.
static MAIN_WINDOW: Lazy<Mutex<Option<Window>>> = Lazy::new(|| Mutex::new(None));
@@ -38,7 +39,7 @@ pub fn start_tauri() {
info!(Source = "Bootloader Tauri"; "Reconfigure the file logger to use the app data directory {data_path:?}");
switch_to_file_logging(data_path).map_err(|e| error!("Failed to switch logging to file: {e}")).unwrap();
set_pdfium_path(app.path_resolver());
Ok(())
})
.plugin(tauri_plugin_window_state::Builder::default().build())
@@ -266,6 +267,19 @@ pub struct PreviousDirectory {
path: String,
}
#[derive(Clone, Deserialize)]
pub struct FileTypeFilter {
filter_name: String,
filter_extensions: Vec<String>,
}
#[derive(Clone, Deserialize)]
pub struct SelectFileOptions {
title: String,
previous_file: Option<PreviousFile>,
filter: Option<FileTypeFilter>,
}
#[derive(Serialize)]
pub struct DirectorySelectionResponse {
user_cancelled: bool,
@@ -273,24 +287,36 @@ pub struct DirectorySelectionResponse {
}
/// Let the user select a file.
#[post("/select/file?<title>", data = "<previous_file>")]
pub fn select_file(_token: APIToken, title: &str, previous_file: Option<Json<PreviousFile>>) -> Json<FileSelectionResponse> {
let file_path = match previous_file {
Some(previous) => {
let previous_path = previous.file_path.as_str();
FileDialogBuilder::new()
.set_title(title)
.set_directory(previous_path)
.pick_file()
#[post("/select/file", data = "<payload>")]
pub fn select_file(_token: APIToken, payload: Json<SelectFileOptions>) -> Json<FileSelectionResponse> {
// Create a new file dialog builder:
let file_dialog = FileDialogBuilder::new();
// Set the title of the file dialog:
let file_dialog = file_dialog.set_title(&payload.title);
// Set the file type filter if provided:
let file_dialog = match &payload.filter {
Some(filter) => {
file_dialog.add_filter(&filter.filter_name, &filter.filter_extensions.iter().map(|s| s.as_str()).collect::<Vec<&str>>())
},
None => {
FileDialogBuilder::new()
.set_title(title)
.pick_file()
},
None => file_dialog,
};
// Set the previous file path if provided:
let file_dialog = match &payload.previous_file {
Some(previous) => {
let previous_path = previous.file_path.as_str();
file_dialog.set_directory(previous_path)
},
None => file_dialog,
};
// Show the file dialog and get the selected file path:
let file_path = file_dialog.pick_file();
match file_path {
Some(path) => {
info!("User selected file: {path:?}");
@@ -319,4 +345,17 @@ pub struct PreviousFile {
pub struct FileSelectionResponse {
user_cancelled: bool,
selected_file_path: String,
}
fn set_pdfium_path(path_resolver: PathResolver) {
let pdfium_relative_source_path = String::from("resources/libraries/");
let pdfium_source_path = path_resolver.resolve_resource(pdfium_relative_source_path);
if pdfium_source_path.is_none() {
error!(Source = "Bootloader Tauri"; "Failed to set the PDFium library path.");
return;
}
let pdfium_source_path = pdfium_source_path.unwrap();
let pdfium_source_path = pdfium_source_path.to_str().unwrap().to_string();
*PDFIUM_LIB_PATH.lock().unwrap() = Some(pdfium_source_path.clone());
}
+36 -10
View File
@@ -15,6 +15,8 @@ use rocket::response::stream::{EventStream, Event};
use rocket::tokio::select;
use rocket::serde::Serialize;
use rocket::get;
use crate::api_token::APIToken;
use crate::pdfium::PdfiumInit;
#[derive(Debug, Serialize)]
pub struct Chunk {
@@ -39,7 +41,7 @@ type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>
type ChunkStream = Pin<Box<dyn Stream<Item = Result<Chunk>> + Send>>;
#[get("/retrieval/fs/extract?<path>")]
pub async fn extract_data(path: String, mut end: Shutdown) -> EventStream![] {
pub async fn extract_data(_token: APIToken, path: String, mut end: Shutdown) -> EventStream![] {
EventStream! {
let stream_result = stream_data(&path).await;
match stream_result {
@@ -78,7 +80,7 @@ async fn stream_data(file_path: &str) -> Result<ChunkStream> {
FileFormat::from_file(&file_path_clone)
}).await??;
let ext = file_path.split('.').last().unwrap_or("");
let ext = file_path.split('.').next_back().unwrap_or("");
let stream = match ext {
DOCX | ODT => {
let from = if ext == DOCX { "docx" } else { "odt" };
@@ -91,7 +93,7 @@ async fn stream_data(file_path: &str) -> Result<ChunkStream> {
_ => match fmt.kind() {
Kind::Document => match fmt {
FileFormat::PortableDocumentFormat => read_pdf(file_path).await?,
FileFormat::PortableDocumentFormat => stream_pdf(file_path).await?,
FileFormat::MicrosoftWordDocument => {
convert_with_pandoc(file_path, "docx", TO_MARKDOWN).await?
}
@@ -133,7 +135,7 @@ async fn stream_text_file(file_path: &str) -> Result<ChunkStream> {
let mut line_number = 0;
let stream = stream! {
while let Ok(Some(line)) = lines.next_line().await { // Korrektur hier
while let Ok(Some(line)) = lines.next_line().await {
line_number += 1;
yield Ok(Chunk {
content: line,
@@ -145,23 +147,47 @@ async fn stream_text_file(file_path: &str) -> Result<ChunkStream> {
Ok(Box::pin(stream))
}
async fn read_pdf(file_path: &str) -> Result<ChunkStream> {
#[get("/retrieval/fs/read/pdf?<file_path>")]
pub fn read_pdf(_token: APIToken, file_path: String) -> String {
let pdfium = Pdfium::ai_studio_init();
let doc = match pdfium.load_pdf_from_file(&file_path, None) {
Ok(document) => document,
Err(e) => return e.to_string(),
};
let mut pdf_content = String::new();
for page in doc.pages().iter() {
let content = match page.text().map(|text_content| text_content.all()) {
Ok(content) => content,
Err(_) => {
continue
}
};
pdf_content.push_str(&content);
pdf_content.push_str("\n\n");
}
pdf_content
}
async fn stream_pdf(file_path: &str) -> Result<ChunkStream> {
let path = file_path.to_owned();
let (tx, rx) = mpsc::channel(10);
tokio::task::spawn_blocking(move || {
let pdfium = Pdfium::default();
let pdfium = Pdfium::ai_studio_init();
let doc = match pdfium.load_pdf_from_file(&path, None) {
Ok(d) => d,
Ok(document) => document,
Err(e) => {
let _ = tx.blocking_send(Err(e.into()));
return;
}
};
for (i, page) in doc.pages().iter().enumerate() {
for (num_page, page) in doc.pages().iter().enumerate() {
let content = match page.text().map(|t| t.all()) {
Ok(c) => c,
Ok(text_content) => text_content,
Err(e) => {
let _ = tx.blocking_send(Err(e.into()));
continue;
@@ -170,7 +196,7 @@ async fn read_pdf(file_path: &str) -> Result<ChunkStream> {
if tx.blocking_send(Ok(Chunk {
content,
metadata: Metadata::Pdf { page_number: i + 1 },
metadata: Metadata::Pdf { page_number: num_page + 1 },
})).is_err() {
break;
}
+3 -1
View File
@@ -9,4 +9,6 @@ pub mod secret;
pub mod clipboard;
pub mod runtime_api;
pub mod certificate;
pub mod file_data;
pub mod file_data;
pub mod metadata;
pub mod pdfium;
+14 -19
View File
@@ -10,22 +10,12 @@ use mindwork_ai_studio::certificate::{generate_certificate};
use mindwork_ai_studio::dotnet::start_dotnet_server;
use mindwork_ai_studio::environment::is_dev;
use mindwork_ai_studio::log::init_logging;
use mindwork_ai_studio::metadata::MetaData;
use mindwork_ai_studio::runtime_api::start_runtime_api;
#[tokio::main]
async fn main() {
let metadata = include_str!("../../metadata.txt");
let mut metadata_lines = metadata.lines();
let app_version = metadata_lines.next().unwrap();
let build_time = metadata_lines.next().unwrap();
let build_number = metadata_lines.next().unwrap();
let dotnet_sdk_version = metadata_lines.next().unwrap();
let dotnet_version = metadata_lines.next().unwrap();
let rust_version = metadata_lines.next().unwrap();
let mud_blazor_version = metadata_lines.next().unwrap();
let tauri_version = metadata_lines.next().unwrap();
let app_commit_hash = metadata_lines.next().unwrap();
let architecture = metadata_lines.next().unwrap();
let metadata = MetaData::init_from_string(include_str!("../../metadata.txt"));
init_logging();
info!("Starting MindWork AI Studio:");
@@ -33,13 +23,18 @@ async fn main() {
let working_directory = std::env::current_dir().unwrap();
info!(".. The working directory is: '{working_directory:?}'");
info!(".. Version: v{app_version} (commit {app_commit_hash}, build {build_number}, {architecture})");
info!(".. Build time: {build_time}");
info!(".. .NET SDK: v{dotnet_sdk_version}");
info!(".. .NET: v{dotnet_version}");
info!(".. Rust: v{rust_version}");
info!(".. MudBlazor: v{mud_blazor_version}");
info!(".. Tauri: v{tauri_version}");
info!(".. Version: v{app_version} (commit {hash}, build {build_num}, {architecture})",
app_version = metadata.app_version,
hash = metadata.app_commit_hash,
build_num = metadata.build_number,
architecture = metadata.architecture);
info!(".. Build time: {build_time}", build_time = metadata.build_time);
info!(".. .NET SDK: v{sdk_version}", sdk_version = metadata.dotnet_sdk_version);
info!(".. .NET: v{dotnet_version}", dotnet_version = metadata.dotnet_version);
info!(".. Rust: v{rust_version}", rust_version = metadata.rust_version);
info!(".. MudBlazor: v{mud_blazor_version}", mud_blazor_version = metadata.mud_blazor_version);
info!(".. Tauri: v{tauri_version}", tauri_version = metadata.tauri_version);
info!(".. PDFium: v{pdfium_version}", pdfium_version = metadata.pdfium_version);
if is_dev() {
warn!("Running in development mode.");
+60
View File
@@ -0,0 +1,60 @@
use once_cell::sync::Lazy;
use std::sync::Mutex;
pub static META_DATA: Lazy<Mutex<Option<MetaData>>> = Lazy::new(|| Mutex::new(None));
#[derive(Clone)]
pub struct MetaData {
pub app_version: String,
pub build_time: String,
pub build_number: String,
pub dotnet_sdk_version: String,
pub dotnet_version: String,
pub rust_version: String,
pub mud_blazor_version: String,
pub tauri_version: String,
pub app_commit_hash: String,
pub architecture: String,
pub pdfium_version: String,
}
impl MetaData {
/// Creates a new instance of `MetaData` from the given metadata string.
pub fn init_from_string(metadata: &str) -> Self {
// When the metadata is already initialized, return the existing instance:
if let Some(metadata) = META_DATA.lock().unwrap().as_ref() {
return metadata.clone();
}
let mut metadata_lines = metadata.lines();
let app_version = metadata_lines.next().unwrap();
let build_time = metadata_lines.next().unwrap();
let build_number = metadata_lines.next().unwrap();
let dotnet_sdk_version = metadata_lines.next().unwrap();
let dotnet_version = metadata_lines.next().unwrap();
let rust_version = metadata_lines.next().unwrap();
let mud_blazor_version = metadata_lines.next().unwrap();
let tauri_version = metadata_lines.next().unwrap();
let app_commit_hash = metadata_lines.next().unwrap();
let architecture = metadata_lines.next().unwrap();
let pdfium_version = metadata_lines.next().unwrap();
let metadata = MetaData {
architecture: architecture.to_string(),
app_commit_hash: app_commit_hash.to_string(),
app_version: app_version.to_string(),
build_number: build_number.to_string(),
build_time: build_time.to_string(),
dotnet_sdk_version: dotnet_sdk_version.to_string(),
dotnet_version: dotnet_version.to_string(),
mud_blazor_version: mud_blazor_version.to_string(),
rust_version: rust_version.to_string(),
tauri_version: tauri_version.to_string(),
pdfium_version: pdfium_version.to_string(),
};
*META_DATA.lock().unwrap() = Some(metadata.clone());
metadata
}
}
+26
View File
@@ -0,0 +1,26 @@
use std::sync::Mutex;
use once_cell::sync::Lazy;
use pdfium_render::prelude::Pdfium;
pub static PDFIUM_LIB_PATH: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
pub trait PdfiumInit {
fn ai_studio_init() -> Pdfium;
}
impl PdfiumInit for Pdfium {
/// Initializes the PDFium library for AI Studio.
fn ai_studio_init() -> Pdfium {
let lib_path = PDFIUM_LIB_PATH.lock().unwrap();
if let Some(path) = lib_path.as_ref() {
return Pdfium::new(
Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path(path))
.or_else(|_| Pdfium::bind_to_system_library())
.unwrap(),
);
}
Pdfium::new(Pdfium::bind_to_system_library().unwrap())
}
}
+1
View File
@@ -79,6 +79,7 @@ pub fn start_runtime_api() {
crate::environment::get_config_directory,
crate::environment::read_user_language,
crate::file_data::extract_data,
crate::file_data::read_pdf,
crate::log::get_log_paths,
])
.ignite().await.unwrap()
+7 -1
View File
@@ -6,7 +6,7 @@
},
"package": {
"productName": "MindWork AI Studio",
"version": "0.9.41"
"version": "0.9.42"
},
"tauri": {
"allowlist": {
@@ -29,6 +29,9 @@
"scope": [
"http://localhost"
]
},
"fs": {
"scope": ["$RESOURCE/resources/*"]
}
},
"windows": [
@@ -57,6 +60,9 @@
"externalBin": [
"../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer"
],
"resources": [
"resources/*"
],
"macOS": {
"exceptionDomain": "localhost"
},