mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-13 18:27:21 +00:00
Added the choice between formal and informal address to the whole app (#1200)
This commit is contained in:
1 parent
382db4b8d0
commit
c9a376f742
342 files changed
+14378
-1893
No files matched your search
@@ -49,6 +49,8 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AIJobService).Namespace, nameof(AIJobService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(AIJobService).Namespace, nameof(AIJobService));
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, AIJobState> jobs = new();
|
||||
private readonly ConcurrentDictionary<Guid, Guid> activeChatJobsByChatId = new();
|
||||
|
||||
@@ -260,7 +262,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
|
||||
if (settingsManager.IsProviderProhibited(request.ProviderSettings.UsedLLMProvider))
|
||||
{
|
||||
logger.LogError("The provider of chat '{ChatId}' is prohibited by the organization. Skipping the AI process.", chatThread.ChatId);
|
||||
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, TB("The use of this provider has been prohibited by your organization. Please select a different provider."));
|
||||
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, TA("The use of this provider has been prohibited by your organization. Please select a different provider."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -301,7 +303,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
|
||||
// The answer is about to be created without the data the user expected it to use.
|
||||
// Without this message, that answer is indistinguishable from one that did use it:
|
||||
//
|
||||
await MessageBus.INSTANCE.SendWarning(new(AppIcons.DATA_SOURCE, TB("Your data sources could not be used. This answer was created without them.")));
|
||||
await MessageBus.INSTANCE.SendWarning(new(AppIcons.DATA_SOURCE, TA("Your data sources could not be used. This answer was created without them.")));
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
@@ -601,7 +603,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
|
||||
return true;
|
||||
|
||||
var message = string.Format(
|
||||
TB("The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."),
|
||||
TA("The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."),
|
||||
chatModel.Id,
|
||||
provider.InstanceName,
|
||||
provider.Provider);
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace AIStudio.Tools;
|
||||
public static class AssistantCategoryExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantCategoryExtensions).Namespace, nameof(AssistantCategoryExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(AssistantCategoryExtensions).Namespace, nameof(AssistantCategoryExtensions));
|
||||
|
||||
extension(AssistantCategory category)
|
||||
{
|
||||
@@ -28,7 +30,7 @@ public static class AssistantCategoryExtensions
|
||||
get
|
||||
{
|
||||
if(category is AssistantCategory.AS_IS)
|
||||
return TB("Please select the assistant category");
|
||||
return TA("Please select the assistant category");
|
||||
|
||||
return category.Name;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace AIStudio.Tools;
|
||||
public static class CommonLanguageExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(CommonLanguageExtensions).Namespace, nameof(CommonLanguageExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(CommonLanguageExtensions).Namespace, nameof(CommonLanguageExtensions));
|
||||
|
||||
extension(CommonLanguages language)
|
||||
{
|
||||
@@ -74,7 +76,7 @@ public static class CommonLanguageExtensions
|
||||
get
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return TB("Please select the target language");
|
||||
return TA("Please select the target language");
|
||||
|
||||
return language.Name;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace AIStudio.Tools;
|
||||
public static class ConfidenceSchemesExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfidenceSchemesExtensions).Namespace, nameof(ConfidenceSchemesExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ConfidenceSchemesExtensions).Namespace, nameof(ConfidenceSchemesExtensions));
|
||||
|
||||
extension(ConfidenceSchemes scheme)
|
||||
{
|
||||
@@ -17,7 +19,7 @@ public static class ConfidenceSchemesExtensions
|
||||
ConfidenceSchemes.TRUST_ASIA => TB("Trust LLM providers from Asia"),
|
||||
ConfidenceSchemes.LOCAL_TRUST_ONLY => TB("Trust only local LLM providers"),
|
||||
|
||||
ConfidenceSchemes.CUSTOM => TB("Configure your own confidence scheme"),
|
||||
ConfidenceSchemes.CUSTOM => TA("Configure your own confidence scheme"),
|
||||
|
||||
_ => TB("Unknown confidence scheme"),
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ public static class DataBackupReader
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataBackupReader).Namespace, nameof(DataBackupReader));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataBackupReader).Namespace, nameof(DataBackupReader));
|
||||
|
||||
/// <summary>
|
||||
/// Reads the manifest of a backup, and checks that this version of AI Studio can use the backup.
|
||||
/// </summary>
|
||||
@@ -43,7 +45,7 @@ public static class DataBackupReader
|
||||
return new(null, TB("The content list of this backup could not be read."));
|
||||
|
||||
if (manifest.FormatVersion > DataBackupFormat.FORMAT_VERSION)
|
||||
return new(null, TB("This backup was made by a newer version of AI Studio. Please update AI Studio first."));
|
||||
return new(null, TA("This backup was made by a newer version of AI Studio. Please update AI Studio first."));
|
||||
|
||||
return new(manifest, string.Empty);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public static class DataRestorePreparer
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataRestorePreparer).Namespace, nameof(DataRestorePreparer));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataRestorePreparer).Namespace, nameof(DataRestorePreparer));
|
||||
|
||||
/// <summary>
|
||||
/// A file of the backup to unpack, and where it goes.
|
||||
/// </summary>
|
||||
@@ -137,7 +139,7 @@ public static class DataRestorePreparer
|
||||
var settingsAreNewer = !Enum.TryParse<SettingsVersion>(manifest.SettingsVersion, out var settingsVersion) || settingsVersion > target.SettingsVersion;
|
||||
var appIsNewer = Version.TryParse(manifest.AppVersion, out var backupAppVersion) && Version.TryParse(target.AppVersion, out var appVersion) && backupAppVersion > appVersion;
|
||||
return settingsAreNewer || appIsNewer
|
||||
? TB("This backup was made by a newer version of AI Studio. Please update AI Studio first.")
|
||||
? TA("This backup was made by a newer version of AI Studio. Please update AI Studio first.")
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public static class DataSourceReindexWarning
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceReindexWarning).Namespace, nameof(DataSourceReindexWarning));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataSourceReindexWarning).Namespace, nameof(DataSourceReindexWarning));
|
||||
|
||||
/// <summary>
|
||||
/// Asks before an edited embedding provider is saved.
|
||||
/// </summary>
|
||||
@@ -124,16 +126,16 @@ public static class DataSourceReindexWarning
|
||||
var body = new StringBuilder();
|
||||
body.AppendLineLF(TB("AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."));
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
|
||||
body.AppendLineLF(TA("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
|
||||
|
||||
if (usesCloudEmbedding)
|
||||
{
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("Your embedding provider runs in the cloud, so preparing files again costs money."));
|
||||
body.AppendLineLF(TA("Your embedding provider runs in the cloud, so preparing files again costs money."));
|
||||
}
|
||||
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("Do you want to apply this change anyway?"));
|
||||
body.AppendLineLF(TA("Do you want to apply this change anyway?"));
|
||||
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
@@ -177,7 +179,7 @@ public static class DataSourceReindexWarning
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(FormatDataSourceNames(affected));
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."));
|
||||
body.AppendLineLF(TA("They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."));
|
||||
|
||||
return body.ToString();
|
||||
}
|
||||
@@ -226,16 +228,16 @@ public static class DataSourceReindexWarning
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(FormatDataSourceNames(affected));
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("Everything prepared for them is thrown away, and every one of their documents goes to your embedding provider once more. With a large data source, this takes a while."));
|
||||
body.AppendLineLF(TA("Everything prepared for them is thrown away, and every one of their documents goes to your embedding provider once more. With a large data source, this takes a while."));
|
||||
|
||||
if (usesCloudEmbedding)
|
||||
{
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("Your embedding provider runs in the cloud, so preparing everything again costs money."));
|
||||
body.AppendLineLF(TA("Your embedding provider runs in the cloud, so preparing everything again costs money."));
|
||||
}
|
||||
|
||||
body.AppendLineLF();
|
||||
body.AppendLineLF(TB("Do you want to apply this change anyway?"));
|
||||
body.AppendLineLF(TA("Do you want to apply this change anyway?"));
|
||||
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
|
||||
@@ -16,6 +16,8 @@ public static class DataSourceRepair
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceRepair).Namespace, nameof(DataSourceRepair));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataSourceRepair).Namespace, nameof(DataSourceRepair));
|
||||
|
||||
/// <summary>
|
||||
/// Asks the user, and rebuilds the index of the data source when they agree.
|
||||
/// </summary>
|
||||
@@ -29,13 +31,13 @@ public static class DataSourceRepair
|
||||
// A repair discards the index, which waits for the end of a pause:
|
||||
if (embeddingService.IsIndexingPaused)
|
||||
{
|
||||
await dialogService.ShowMessageBoxAsync(TB("Repair Data Source"), TB("AI Studio is backing up or restoring the indexes of your data sources right now. Please try again once that is done."), TB("Close"));
|
||||
await dialogService.ShowMessageBoxAsync(TB("Repair Data Source"), TA("AI Studio is backing up or restoring the indexes of your data sources right now. Please try again once that is done."), TB("Close"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, string.Format(TB("The index of the data source '{0}' cannot be read anymore. Repairing it means building the index from scratch: everything indexed so far is thrown away, and every document of this data source is sent to your embedding provider once more. With a cloud provider, this costs money, and with a large data source it takes a while. Do you want to repair this data source now?"), dataSourceName) },
|
||||
{ x => x.Message, string.Format(TA("The index of the data source '{0}' cannot be read anymore. Repairing it means building the index from scratch: everything indexed so far is thrown away, and every document of this data source is sent to your embedding provider once more. With a cloud provider, this costs money, and with a large data source it takes a while. Do you want to repair this data source now?"), dataSourceName) },
|
||||
};
|
||||
|
||||
var dialogReference = await dialogService.ShowAsync<ConfirmDialog>(TB("Repair Data Source"), dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace AIStudio.Tools;
|
||||
/// </summary>
|
||||
public static class DroppedFile
|
||||
{
|
||||
private static string TB(string fallbackEN) => PluginSystem.I18N.I.T(fallbackEN, typeof(DroppedFile).Namespace, nameof(DroppedFile));
|
||||
private static string TA(string fallbackEN) => PluginSystem.I18N.I.TA(fallbackEN, typeof(DroppedFile).Namespace, nameof(DroppedFile));
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first dropped path which leads to a file of an allowed type.
|
||||
@@ -31,12 +31,12 @@ public static class DroppedFile
|
||||
// Naming the actual mistake beats a generic "that did not work". Dropping a folder onto a
|
||||
// file picker is the likeliest of them:
|
||||
if (paths.Any(Directory.Exists))
|
||||
return TB("Please drop a file, not a folder.");
|
||||
return TA("Please drop a file, not a folder.");
|
||||
|
||||
// The file exists, so the file type filter is what turned it down:
|
||||
if (paths.Any(File.Exists))
|
||||
return TB("Please drop a file with a supported file type.");
|
||||
return TA("Please drop a file with a supported file type.");
|
||||
|
||||
return TB("The dropped file could not be accessed. Please choose it with the file chooser instead.");
|
||||
return TA("The dropped file could not be accessed. Please choose it with the file chooser instead.");
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Tools;
|
||||
/// </summary>
|
||||
public static class DroppedFolder
|
||||
{
|
||||
private static string TB(string fallbackEN) => PluginSystem.I18N.I.T(fallbackEN, typeof(DroppedFolder).Namespace, nameof(DroppedFolder));
|
||||
private static string TA(string fallbackEN) => PluginSystem.I18N.I.TA(fallbackEN, typeof(DroppedFolder).Namespace, nameof(DroppedFolder));
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first dropped path which leads to a folder.
|
||||
@@ -29,8 +29,8 @@ public static class DroppedFolder
|
||||
// Naming the actual mistake beats a generic "that did not work". Dropping a file onto a
|
||||
// folder picker is the likeliest of them:
|
||||
if (paths.Any(File.Exists))
|
||||
return TB("Please drop a folder, not a file.");
|
||||
return TA("Please drop a folder, not a file.");
|
||||
|
||||
return TB("The dropped folder could not be accessed. Please choose it with the folder chooser instead.");
|
||||
return TA("The dropped folder could not be accessed. Please choose it with the folder chooser instead.");
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,11 @@ public enum Event
|
||||
/// </summary>
|
||||
COLOR_THEME_CHANGED,
|
||||
|
||||
/// <summary>
|
||||
/// Notifies receivers that the form of address changed, so that the texts of TA() show in the new form.
|
||||
/// </summary>
|
||||
ADDRESS_FORM_CHANGED,
|
||||
|
||||
/// <summary>
|
||||
/// Requests startup initialization of the plugin system.
|
||||
/// </summary>
|
||||
|
||||
@@ -115,6 +115,8 @@ internal static class FileExclusions
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileExclusions).Namespace, nameof(FileExclusions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(FileExclusions).Namespace, nameof(FileExclusions));
|
||||
|
||||
/// <summary>
|
||||
/// Splits a list into its entries. Empty lines and empty entries are allowed and dropped.
|
||||
/// </summary>
|
||||
@@ -160,12 +162,12 @@ internal static class FileExclusions
|
||||
private static string? DescribeValidationIssue(ValidationIssue issue) => issue switch
|
||||
{
|
||||
ValidationIssue.NONE => null,
|
||||
ValidationIssue.SELECTOR => TB("Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/."),
|
||||
ValidationIssue.PATH => TB("Please write excluded folders as a name without a path, ending with a slash, e.g., node_modules/ or bin/. Such an entry excludes every folder of that name."),
|
||||
ValidationIssue.RECURSIVE_WILDCARD => TB("Please use only single asterisks as wildcards, e.g., *.min.js or cmake-build-*/."),
|
||||
ValidationIssue.SELECTOR => TA("Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/."),
|
||||
ValidationIssue.PATH => TA("Please write excluded folders as a name without a path, ending with a slash, e.g., node_modules/ or bin/. Such an entry excludes every folder of that name."),
|
||||
ValidationIssue.RECURSIVE_WILDCARD => TA("Please use only single asterisks as wildcards, e.g., *.min.js or cmake-build-*/."),
|
||||
ValidationIssue.INVALID_CHARACTER => TB("One of the excluded files or folders contains an invalid character."),
|
||||
ValidationIssue.FILE_IN_FOLDER_LIST => TB("Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'."),
|
||||
ValidationIssue.FOLDER_IN_FILE_LIST => TB("Enter folder patterns ending with / in 'Folders'. File patterns must not end with /."),
|
||||
ValidationIssue.FILE_IN_FOLDER_LIST => TA("Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'."),
|
||||
ValidationIssue.FOLDER_IN_FILE_LIST => TA("Enter folder patterns ending with / in 'Folders'. File patterns must not end with /."),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(issue)),
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ internal static class FileExtractionResultExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileExtractionResultExtensions).Namespace, nameof(FileExtractionResultExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(FileExtractionResultExtensions).Namespace, nameof(FileExtractionResultExtensions));
|
||||
|
||||
/// <param name="result">The extraction result.</param>
|
||||
extension(FileExtractionResult result)
|
||||
{
|
||||
@@ -41,7 +43,7 @@ internal static class FileExtractionResultExtensions
|
||||
/// <param name="fileName">The name of the file, as shown to the user.</param>
|
||||
/// <returns>The localized message.</returns>
|
||||
internal string ToExtensionMismatchUserMessage(string fileName) => string.Format(
|
||||
TB("The file '{0}' is actually a {1} and was read as such. Please correct its file extension."),
|
||||
TA("The file '{0}' is actually a {1} and was read as such. Please correct its file extension."),
|
||||
fileName,
|
||||
result.DetectedFormat);
|
||||
|
||||
@@ -78,7 +80,7 @@ internal static class FileExtractionResultExtensions
|
||||
{
|
||||
FileExtractionErrorCode.FILE_NOT_FOUND => TB("The file '{0}' does not exist anymore and was not sent."),
|
||||
FileExtractionErrorCode.FILE_NOT_READABLE => TB("The file '{0}' could not be read and was not sent. When the file is stored on a network drive, the drive might be unavailable, or another program might be blocking the file."),
|
||||
FileExtractionErrorCode.FILE_LOCKED => TB("The file '{0}' is currently open in another program, which is why it was not sent. Please close the file and try again. When the file is stored on a shared network drive, a colleague might have it open."),
|
||||
FileExtractionErrorCode.FILE_LOCKED => TA("The file '{0}' is currently open in another program, which is why it was not sent. Please close the file and try again. When the file is stored on a shared network drive, a colleague might have it open."),
|
||||
FileExtractionErrorCode.TIMEOUT => TB("Reading the file '{0}' took too long and was stopped, so the file was not sent. When the file is stored on a network drive, the connection might be slow or interrupted."),
|
||||
FileExtractionErrorCode.NOT_A_VALID_PDF => TB("The file '{0}' is not a readable PDF and was not sent. It might be damaged or transferred incompletely."),
|
||||
FileExtractionErrorCode.NOT_A_VALID_SPREADSHEET => TB("The file '{0}' is not a readable spreadsheet and was not sent. It might be damaged or transferred incompletely."),
|
||||
|
||||
@@ -49,6 +49,8 @@ internal static class FileNamePatterns
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileNamePatterns).Namespace, nameof(FileNamePatterns));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(FileNamePatterns).Namespace, nameof(FileNamePatterns));
|
||||
|
||||
/// <summary>
|
||||
/// Splits the list, ignoring empty entries, including a trailing semicolon.
|
||||
/// </summary>
|
||||
@@ -125,10 +127,10 @@ internal static class FileNamePatterns
|
||||
public static string? Validate(string? patterns) => GetValidationIssue(patterns) switch
|
||||
{
|
||||
ValidationIssue.NONE => null,
|
||||
ValidationIssue.MISSING => TB("Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon."),
|
||||
ValidationIssue.UNKNOWN_SELECTOR => TB("One of the file patterns is an unknown selector. Please use :documents: or :text:, or a file name pattern such as *.txt."),
|
||||
ValidationIssue.RECURSIVE_WILDCARD => TB("Please use only single asterisks as wildcards, e.g., *.txt or report-*.md."),
|
||||
ValidationIssue.PATH => TB("Please use file name patterns without folder paths, e.g., *.txt or report-*.md."),
|
||||
ValidationIssue.MISSING => TA("Please provide at least one file pattern, e.g., :documents: or *.txt. Separate multiple patterns with a semicolon."),
|
||||
ValidationIssue.UNKNOWN_SELECTOR => TA("One of the file patterns is an unknown selector. Please use :documents: or :text:, or a file name pattern such as *.txt."),
|
||||
ValidationIssue.RECURSIVE_WILDCARD => TA("Please use only single asterisks as wildcards, e.g., *.txt or report-*.md."),
|
||||
ValidationIssue.PATH => TA("Please use file name patterns without folder paths, e.g., *.txt or report-*.md."),
|
||||
ValidationIssue.INVALID_CHARACTER => TB("One of the file patterns contains an invalid character."),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(patterns)),
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@ public static class MailboxConnectionFailureExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(MailboxConnectionFailureExtensions).Namespace, nameof(MailboxConnectionFailureExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(MailboxConnectionFailureExtensions).Namespace, nameof(MailboxConnectionFailureExtensions));
|
||||
|
||||
extension(MailboxConnectionFailure failure)
|
||||
{
|
||||
/// <summary>
|
||||
@@ -13,12 +15,12 @@ public static class MailboxConnectionFailureExtensions
|
||||
/// </summary>
|
||||
public string Description => failure switch
|
||||
{
|
||||
MailboxConnectionFailure.AUTHENTICATION_FAILED => TB("The server rejected the username or the password. Some providers require an app password instead of your usual password."),
|
||||
MailboxConnectionFailure.NETWORK_UNAVAILABLE => TB("The server could not be reached. Please check the host and the port, and whether you need a VPN connection."),
|
||||
MailboxConnectionFailure.TLS_FAILED => TB("No encrypted connection to the server could be established. When your organization uses a certificate authority of its own, enable the additional root certificates in the app settings, select the bundle with its root certificate, and add the host of the server to the allowed hosts. Your IT department can also configure this for you."),
|
||||
MailboxConnectionFailure.SERVER_ERROR => TB("The server reported an error. Please try again later."),
|
||||
MailboxConnectionFailure.AUTHENTICATION_FAILED => TA("The server rejected the username or the password. Some providers require an app password instead of your usual password."),
|
||||
MailboxConnectionFailure.NETWORK_UNAVAILABLE => TA("The server could not be reached. Please check the host and the port, and whether you need a VPN connection."),
|
||||
MailboxConnectionFailure.TLS_FAILED => TA("No encrypted connection to the server could be established. When your organization uses a certificate authority of its own, enable the additional root certificates in the app settings, select the bundle with its root certificate, and add the host of the server to the allowed hosts. Your IT department can also configure this for you."),
|
||||
MailboxConnectionFailure.SERVER_ERROR => TA("The server reported an error. Please try again later."),
|
||||
MailboxConnectionFailure.INVALID_SETTINGS => TB("The settings of this mailbox are incomplete, or they were made by a newer version of AI Studio."),
|
||||
MailboxConnectionFailure.SERVER_NOT_ALLOWED => TB("Your organization allows mailboxes only on its own mail servers, and this server is none of them. AI Studio does not connect to it, and the AI does not read this mailbox."),
|
||||
MailboxConnectionFailure.SERVER_NOT_ALLOWED => TA("Your organization allows mailboxes only on its own mail servers, and this server is none of them. AI Studio does not connect to it, and the AI does not read this mailbox."),
|
||||
|
||||
_ => TB("The connection to the server failed for an unknown reason."),
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace AIStudio.Tools.Mail;
|
||||
|
||||
public static class MailboxProviderTemplateExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(MailboxProviderTemplateExtensions).Namespace, nameof(MailboxProviderTemplateExtensions));
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(MailboxProviderTemplateExtensions).Namespace, nameof(MailboxProviderTemplateExtensions));
|
||||
|
||||
extension(MailboxUsernameFormat format)
|
||||
{
|
||||
@@ -13,11 +13,11 @@ public static class MailboxProviderTemplateExtensions
|
||||
/// </summary>
|
||||
public string Description => format switch
|
||||
{
|
||||
MailboxUsernameFormat.EMAIL_ADDRESS => TB("Your full e-mail address."),
|
||||
MailboxUsernameFormat.ADDRESS_NAME_PART => TB("The part of your e-mail address before the @ sign. When that does not work, try your full e-mail address."),
|
||||
MailboxUsernameFormat.DOMAIN_ACCOUNT => string.Format(TB("Your account in the directory of your organization, either as {0} or as {1}. Your IT department knows which form your server expects."), @"DOMAIN\username", "username@domain"),
|
||||
MailboxUsernameFormat.EMAIL_ADDRESS => TA("Your full e-mail address."),
|
||||
MailboxUsernameFormat.ADDRESS_NAME_PART => TA("The part of your e-mail address before the @ sign. When that does not work, try your full e-mail address."),
|
||||
MailboxUsernameFormat.DOMAIN_ACCOUNT => string.Format(TA("Your account in the directory of your organization, either as {0} or as {1}. Your IT department knows which form your server expects."), @"DOMAIN\username", "username@domain"),
|
||||
|
||||
_ => TB("The username your provider gave you."),
|
||||
_ => TA("The username your provider gave you."),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,16 +29,16 @@ public static class MailboxProviderTemplateExtensions
|
||||
public IEnumerable<string> GetDescriptions()
|
||||
{
|
||||
if (requirements.HasFlag(MailboxProviderRequirements.IMAP_ACTIVATION))
|
||||
yield return TB("Enable the IMAP access in the settings of your webmail first. The provider may switch it off again after a longer time without use.");
|
||||
yield return TA("Enable the IMAP access in the settings of your webmail first. The provider may switch it off again after a longer time without use.");
|
||||
|
||||
if (requirements.HasFlag(MailboxProviderRequirements.ADMIN_ACTIVATION))
|
||||
yield return TB("Your IT department has to enable IMAP for the server and for your mailbox first.");
|
||||
yield return TA("Your IT department has to enable IMAP for the server and for your mailbox first.");
|
||||
|
||||
if (requirements.HasFlag(MailboxProviderRequirements.APP_PASSWORD))
|
||||
yield return TB("This provider requires an app password, which you create in the security settings of your account. Your usual password does not work here.");
|
||||
yield return TA("This provider requires an app password, which you create in the security settings of your account. Your usual password does not work here.");
|
||||
|
||||
if (requirements.HasFlag(MailboxProviderRequirements.APP_PASSWORD_WITH_TWO_FACTOR))
|
||||
yield return TB("When two-factor authentication is enabled for your account, this provider requires an app password, which you create in the security settings of your account.");
|
||||
yield return TA("When two-factor authentication is enabled for your account, this provider requires an app password, which you create in the security settings of your account.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-10
@@ -8,6 +8,8 @@ public static class PluginAssistantSecurityResolver
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginAssistantSecurityResolver).Namespace, nameof(PluginAssistantSecurityResolver));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(PluginAssistantSecurityResolver).Namespace, nameof(PluginAssistantSecurityResolver));
|
||||
|
||||
private static string NormalizeHash(string hash) => string.IsNullOrWhiteSpace(hash) ? string.Empty : hash.Trim().ToUpperInvariant();
|
||||
|
||||
private static string GetAvailabilityLabel(bool requiresAudit, bool hasAudit, bool hasHashMismatch, bool isBlocked, bool canOverride)
|
||||
@@ -88,8 +90,8 @@ public static class PluginAssistantSecurityResolver
|
||||
if (enterpriseApproval is not null)
|
||||
{
|
||||
var enterpriseHeadline = string.IsNullOrWhiteSpace(enterpriseApproval.DisplayName)
|
||||
? TB("This assistant was approved by your organization.")
|
||||
: string.Format(TB("This assistant was approved by your organization as '{0}'."), enterpriseApproval.DisplayName);
|
||||
? TA("This assistant was approved by your organization.")
|
||||
: string.Format(TA("This assistant was approved by your organization as '{0}'."), enterpriseApproval.DisplayName);
|
||||
|
||||
return new PluginAssistantSecurityState
|
||||
{
|
||||
@@ -115,7 +117,7 @@ public static class PluginAssistantSecurityResolver
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
StatusLabel = TB("Unlocked"),
|
||||
SourceLabel = TB("Approved by your organization"),
|
||||
SourceLabel = TA("Approved by your organization"),
|
||||
SourceColor = Color.Success,
|
||||
SourceIcon = AppIcons.ORGANIZATION,
|
||||
BadgeIcon = AppIcons.ORGANIZATION,
|
||||
@@ -169,8 +171,8 @@ public static class PluginAssistantSecurityResolver
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
Headline = requiresAudit ? TB("This assistant is currently locked.") : TB("This assistant currently has no stored audit."),
|
||||
Description = requiresAudit
|
||||
? TB("No security audit exists yet, and your current security settings require one before this assistant plugin may be enabled or used.")
|
||||
: TB("No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used."),
|
||||
? TA("No security audit exists yet, and your current security settings require one before this assistant plugin may be enabled or used.")
|
||||
: TA("No security audit exists yet. Your current security settings do not require an audit before this assistant plugin may be used."),
|
||||
StatusColor = GetAvailabilityColor(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
StatusIcon = GetAvailabilityIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
ActionLabel = TB("Start Security Check"),
|
||||
@@ -252,12 +254,12 @@ public static class PluginAssistantSecurityResolver
|
||||
? TB("This assistant is currently locked.")
|
||||
: isEnforcementDisabled
|
||||
? TB("This assistant can still be used because audit enforcement is disabled.")
|
||||
: TB("This assistant can still be used because your settings allow it."),
|
||||
: TA("This assistant can still be used because your settings allow it."),
|
||||
Description = isBlockedByMinimum
|
||||
? string.Format(TB("The current audit result '{0}' is below your required minimum level '{1}'. Your security settings therefore block this assistant plugin."), auditLevel.Name, auditSettings.MinimumLevel.Name)
|
||||
? string.Format(TA("The current audit result '{0}' is below your required minimum level '{1}'. Your security settings therefore block this assistant plugin."), auditLevel.Name, auditSettings.MinimumLevel.Name)
|
||||
: isEnforcementDisabled
|
||||
? string.Format(TB("The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used."), auditLevel.Name, auditSettings.MinimumLevel.Name)
|
||||
: string.Format(TB("The current audit result is '{0}', which is below your required minimum level '{1}'. Your settings still allow manual activation, but the assistant keeps this security status and should be reviewed carefully."), auditLevel.Name, auditSettings.MinimumLevel.Name),
|
||||
? string.Format(TA("The current audit result is '{0}', which is below your required minimum level '{1}'. Audit enforcement is currently disabled, so this assistant plugin can still be enabled or used."), auditLevel.Name, auditSettings.MinimumLevel.Name)
|
||||
: string.Format(TA("The current audit result is '{0}', which is below your required minimum level '{1}'. Your settings still allow manual activation, but the assistant keeps this security status and should be reviewed carefully."), auditLevel.Name, auditSettings.MinimumLevel.Name),
|
||||
StatusColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
StatusIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
ActionLabel = TB("Open Security Check"),
|
||||
@@ -294,7 +296,7 @@ public static class PluginAssistantSecurityResolver
|
||||
SourceIcon = AppIcons.SECURITY_AUDIT_PASSED,
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
Headline = TB("This assistant is currently unlocked."),
|
||||
Description = string.Format(TB("The stored audit matches the current plugin code and meets your required minimum level '{0}'."), auditSettings.MinimumLevel.Name),
|
||||
Description = string.Format(TA("The stored audit matches the current plugin code and meets your required minimum level '{0}'."), auditSettings.MinimumLevel.Name),
|
||||
StatusColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
StatusIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
ActionLabel = TB("Open Security Check"),
|
||||
|
||||
@@ -29,6 +29,8 @@ public static class ConfigurationSnippetImport
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSnippetImport).Namespace, nameof(ConfigurationSnippetImport));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ConfigurationSnippetImport).Namespace, nameof(ConfigurationSnippetImport));
|
||||
|
||||
/// <summary>
|
||||
/// Reads a pasted configuration snippet and makes its configuration object one of the user's own.
|
||||
/// </summary>
|
||||
@@ -66,7 +68,7 @@ public static class ConfigurationSnippetImport
|
||||
case DataSourceERI_V1 dataSource:
|
||||
if (dataSource.AuthMethod is AuthMethod.KERBEROS)
|
||||
{
|
||||
issue = TB("Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself.");
|
||||
issue = TA("Data sources which sign in with Kerberos cannot be imported yet. Please add them yourself.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -99,7 +101,7 @@ public static class ConfigurationSnippetImport
|
||||
case ChatTemplate template:
|
||||
var localPaths = attachmentPaths.Where(IsLocalFile).ToList();
|
||||
if (localPaths.Count > 0)
|
||||
hints.Add(string.Format(TB("The chat template attaches these files from this computer to every chat started with it: {0}. Please check that you want to share them."), string.Join(", ", localPaths)));
|
||||
hints.Add(string.Format(TA("The chat template attaches these files from this computer to every chat started with it: {0}. Please check that you want to share them."), string.Join(", ", localPaths)));
|
||||
|
||||
result = new(MakeLocal(template) with { FileAttachments = localPaths.Select(FileAttachment.FromPath).ToList() }, string.Empty, hints, attachmentPaths.Except(localPaths).ToList());
|
||||
return true;
|
||||
@@ -152,15 +154,15 @@ public static class ConfigurationSnippetImport
|
||||
{
|
||||
var templateProvider = settingsManager.GetProviderById(template.ProviderId);
|
||||
if (templateProvider == Settings.Provider.NONE)
|
||||
hints.Add(string.Format(TB("The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead."), template.ProviderId));
|
||||
hints.Add(string.Format(TA("The chat template preselects a provider which does not exist on this computer: {0}. Chats started with it use your usual provider instead."), template.ProviderId));
|
||||
else if (settingsManager.GetChatTemplateProvider(template.ProviderId) == Settings.Provider.NONE)
|
||||
hints.Add(string.Format(TB("The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead."), templateProvider.InstanceName));
|
||||
hints.Add(string.Format(TA("The chat template preselects the provider '{0}', which does not meet the confidence level required for chats. Chats started with it use your usual provider instead."), templateProvider.InstanceName));
|
||||
}
|
||||
|
||||
if (template.ProfileId is not null && !template.AllowProfileUsage)
|
||||
hints.Add(TB("The chat template preselects a profile but does not allow profiles. Chats started with it ignore that profile."));
|
||||
else if (template.ProfileId is not null && !template.StartsWithoutProfile() && settingsManager.GetProfileById(template.ProfileId) == Profile.NO_PROFILE)
|
||||
hints.Add(string.Format(TB("The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead."), template.ProfileId));
|
||||
hints.Add(string.Format(TA("The chat template preselects a profile which does not exist on this computer: {0}. Chats started with it use your usual profile instead."), template.ProfileId));
|
||||
|
||||
return result with { ConfigurationObject = template with { ToolIds = templateToolIds }, Hints = hints };
|
||||
|
||||
@@ -265,14 +267,14 @@ public static class ConfigurationSnippetImport
|
||||
|
||||
if (!EnterpriseEncryption.IsEncrypted(encryptedApiKey))
|
||||
{
|
||||
hints.Add(TB("The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider."));
|
||||
hints.Add(TA("The pasted configuration contains an API key which is not encrypted, so it was left out. Please enter your API key before you save the provider."));
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (TryReadSecret(table, API_KEY_FIELD, out var apiKey))
|
||||
return apiKey;
|
||||
|
||||
hints.Add(TB("The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider."));
|
||||
hints.Add(TA("The API key in the pasted configuration cannot be decrypted on this computer. Please enter your API key before you save the provider."));
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
@@ -303,7 +305,7 @@ public static class ConfigurationSnippetImport
|
||||
{
|
||||
var secretFieldName = DataSourceERI_V1.GetSecretFieldName(authMethod);
|
||||
if (EnterpriseEncryption.IsEncrypted(ReadString(table, secretFieldName)) && !TryReadSecret(table, secretFieldName, out _))
|
||||
return TB("The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured.");
|
||||
return TA("The token or password of the data source cannot be decrypted on this computer. Such data sources can only be imported where the encryption secret of your organization is configured.");
|
||||
}
|
||||
|
||||
return string.Format(TB("The pasted configuration is not a valid configuration of the type '{0}'."), type.Name);
|
||||
|
||||
@@ -25,6 +25,8 @@ public sealed class ConfigurationSnippetParser
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSnippetParser).Namespace, nameof(ConfigurationSnippetParser));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ConfigurationSnippetParser).Namespace, nameof(ConfigurationSnippetParser));
|
||||
|
||||
/// <summary>
|
||||
/// Reads a snippet of the form <c>CONFIG["SECTION"][#CONFIG["SECTION"]+1] = { ... }</c>.
|
||||
/// </summary>
|
||||
@@ -39,7 +41,7 @@ public sealed class ConfigurationSnippetParser
|
||||
issue = string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(source) || source.Length > 1_000_000)
|
||||
{
|
||||
issue = TB("Please paste one exported configuration of at most 1 MB.");
|
||||
issue = TA("Please paste one exported configuration of at most 1 MB.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,7 +128,7 @@ public sealed class ConfigurationSnippetParser
|
||||
|
||||
// In Lua, nil leaves a field out, and in a list it ends what the parsers read of it:
|
||||
if (this.TakeWord("nil"))
|
||||
throw new FormatException(string.Format(TB("The value nil at character {0} is not supported. Leave the field out instead."), this.position - 2));
|
||||
throw new FormatException(string.Format(TA("The value nil at character {0} is not supported. Leave the field out instead."), this.position - 2));
|
||||
|
||||
var start = this.position;
|
||||
if (this.Peek() == '-')
|
||||
@@ -280,7 +282,7 @@ public sealed class ConfigurationSnippetParser
|
||||
// is refused:
|
||||
//
|
||||
if (this.IsLongBracketAt(this.position + 2))
|
||||
throw new FormatException(string.Format(TB("The block comment at character {0} is not supported. Please use line comments, which start with -- and end at the end of the line."), this.position + 1));
|
||||
throw new FormatException(string.Format(TA("The block comment at character {0} is not supported. Please use line comments, which start with -- and end at the end of the line."), this.position + 1));
|
||||
|
||||
this.position += 2;
|
||||
while (this.position < this.source.Length && this.source[this.position] != '\n')
|
||||
|
||||
@@ -35,6 +35,8 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginBase).Namespace, nameof(PluginBase));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(PluginBase).Namespace, nameof(PluginBase));
|
||||
|
||||
private readonly IReadOnlyCollection<string> baseIssues;
|
||||
protected readonly LuaState State;
|
||||
|
||||
@@ -509,7 +511,7 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
if (!this.State.Environment["DEPRECATION_MESSAGE"].TryRead(out deprecationMessage))
|
||||
{
|
||||
deprecationMessage = string.Empty;
|
||||
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.");
|
||||
message = TA("The field DEPRECATION_MESSAGE does not exist or is not a valid string. This message is optional: use an empty string to indicate that the plugin is not deprecated.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -244,6 +244,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
// Config: save energy while the AI streams its answers?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.IsSavingEnergy, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: how should the texts address the user?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AddressForm, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: show prompt-injection alert dialogs?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowPromptInjectionAlert, this.Id, settingsTable, dryRun);
|
||||
|
||||
@@ -519,7 +522,6 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
// Config: BeyondFacts Lab defaults?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.MinimumProviderConfidence, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.AddressForm, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BeyondFactsLab, x => x.AllowPromptOverrides, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: Assistant Builder defaults?
|
||||
|
||||
@@ -5,6 +5,8 @@ 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 string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(PluginLanguage).Namespace, nameof(PluginLanguage));
|
||||
private static readonly ILogger<PluginLanguage> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginLanguage>();
|
||||
|
||||
private readonly Dictionary<string, string> content = [];
|
||||
@@ -61,21 +63,21 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
|
||||
if (string.IsNullOrWhiteSpace(readLangCultureTag))
|
||||
{
|
||||
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.");
|
||||
message = TA("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 = 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.");
|
||||
message = TA("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 = 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.");
|
||||
message = TA("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;
|
||||
}
|
||||
@@ -84,7 +86,7 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
for (var i = 0; i < 2; i++)
|
||||
if (!char.IsLower(readLangCultureTag[i]))
|
||||
{
|
||||
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.");
|
||||
message = TA("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;
|
||||
}
|
||||
@@ -93,7 +95,7 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
for (var i = 3; i < 5; i++)
|
||||
if (!char.IsUpper(readLangCultureTag[i]))
|
||||
{
|
||||
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.");
|
||||
message = TA("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;
|
||||
}
|
||||
@@ -113,7 +115,7 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin
|
||||
|
||||
if (string.IsNullOrWhiteSpace(readLangName))
|
||||
{
|
||||
message = TB("The field LANG_NAME is empty. Use a valid language name.");
|
||||
message = TA("The field LANG_NAME is empty. Use a valid language name.");
|
||||
readLangName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ internal static partial class LayoutPurposeExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => PluginSystem.I18N.I.T(fallbackEN, typeof(LayoutPurposeExtensions).Namespace, nameof(LayoutPurposeExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => PluginSystem.I18N.I.TA(fallbackEN, typeof(LayoutPurposeExtensions).Namespace, nameof(LayoutPurposeExtensions));
|
||||
|
||||
/// <summary>
|
||||
/// What the forms offer for a kind of slide the user leaves to the LLM.
|
||||
/// </summary>
|
||||
@@ -70,7 +72,7 @@ internal static partial class LayoutPurposeExtensions
|
||||
/// The help below the choice: its question, and whether a choice adds such a slide.
|
||||
/// </summary>
|
||||
public string Help => purpose.ChoiceAddsSlide
|
||||
? $"{purpose.Question} {TB("Choosing a layout also adds such a slide to the presentation.")}"
|
||||
? $"{purpose.Question} {TA("Choosing a layout also adds such a slide to the presentation.")}"
|
||||
: purpose.Question;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ public sealed class AugmentationOne : IAugmentationProcess
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AugmentationOne).Namespace, nameof(AugmentationOne));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(AugmentationOne).Namespace, nameof(AugmentationOne));
|
||||
|
||||
#region Implementation of IAugmentationProcess
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -86,7 +88,7 @@ public sealed class AugmentationOne : IAugmentationProcess
|
||||
// The user switched this check on. Continuing without it silently would hide
|
||||
// that the answer rests on unfiltered passages:
|
||||
//
|
||||
await MessageBus.INSTANCE.SendWarning(new(AppIcons.PASSAGE_RELEVANCE_CHECK, TB("The check of which passages fit your question failed. This answer uses all passages that were found.")));
|
||||
await MessageBus.INSTANCE.SendWarning(new(AppIcons.PASSAGE_RELEVANCE_CHECK, TA("The check of which passages fit your question failed. This answer uses all passages that were found.")));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -111,7 +113,7 @@ public sealed class AugmentationOne : IAugmentationProcess
|
||||
// and each one may miss something else.
|
||||
//
|
||||
if (selection.LeftOutCount > 0)
|
||||
await MessageBus.INSTANCE.SendWarning(new(AppIcons.CONTENT_SHORTENED, string.Format(TB("Not every passage found in your data sources fits into what the selected model can read. This answer had to do without {0} of them. To give the AI every passage, switch on Semantic Search for this chat, lower the token limit of your data sources, or choose fewer data sources."), selection.LeftOutCount.CompactCount())));
|
||||
await MessageBus.INSTANCE.SendWarning(new(AppIcons.CONTENT_SHORTENED, string.Format(TA("Not every passage found in your data sources fits into what the selected model can read. This answer had to do without {0} of them. To give the AI every passage, switch on Semantic Search for this chat, lower the token limit of your data sources, or choose fewer data sources."), selection.LeftOutCount.CompactCount())));
|
||||
|
||||
//
|
||||
// Add the augmented data to the chat thread, as the very last step: the caller restricts
|
||||
|
||||
@@ -13,6 +13,8 @@ public static class ModelImageErrorCodeExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ModelImageErrorCodeExtensions).Namespace, nameof(ModelImageErrorCodeExtensions));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ModelImageErrorCodeExtensions).Namespace, nameof(ModelImageErrorCodeExtensions));
|
||||
|
||||
/// <param name="code">Why the image could not be prepared.</param>
|
||||
extension(ModelImageErrorCode code)
|
||||
{
|
||||
@@ -28,10 +30,10 @@ public static class ModelImageErrorCodeExtensions
|
||||
{
|
||||
ModelImageErrorCode.FILE_NOT_FOUND => TB("The image '{0}' does not exist anymore and was not sent."),
|
||||
ModelImageErrorCode.FILE_NOT_READABLE => TB("The image '{0}' could not be opened and was not sent. When the image is stored on a network drive, the drive might be unavailable."),
|
||||
ModelImageErrorCode.UNSUPPORTED_FORMAT => TB("The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG."),
|
||||
ModelImageErrorCode.CONTENT_MISMATCH => TB("The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension."),
|
||||
ModelImageErrorCode.UNSUPPORTED_FORMAT => TA("The image '{0}' uses a format which AI Studio cannot give to an AI model, so it was not sent. Please convert the image to JPG or PNG."),
|
||||
ModelImageErrorCode.CONTENT_MISMATCH => TA("The image '{0}' is not what its file extension claims, so it was not sent. Please correct its file extension."),
|
||||
ModelImageErrorCode.NOT_DECODABLE => TB("The image '{0}' could not be read and was not sent. It might be damaged or transferred incompletely."),
|
||||
ModelImageErrorCode.TOO_MANY_PIXELS => TB("The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first."),
|
||||
ModelImageErrorCode.TOO_MANY_PIXELS => TA("The image '{0}' has too many pixels for AI Studio to prepare it, so it was not sent. Please scale the image down first."),
|
||||
ModelImageErrorCode.BUDGET_NOT_REACHABLE => TB("The image '{0}' could not be made small enough for the selected provider, so it was not sent."),
|
||||
|
||||
_ => TB("AI Studio was not able to prepare the image '{0}' for the AI, so it was not sent."),
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(AssistantPluginGenerationService).Namespace, nameof(AssistantPluginGenerationService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(AssistantPluginGenerationService).Namespace, nameof(AssistantPluginGenerationService));
|
||||
|
||||
// Internal rather than private, so that a test can check the JSON the models get:
|
||||
internal static readonly JsonSerializerOptions UNTRUSTED_PROMPT_JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
@@ -44,17 +46,17 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
public async Task<AssistantPluginDraftGenerationResult> GenerateAssistantDraftAsync(AssistantPluginDraftGenerationRequest request, ProviderSettings provider, CancellationToken token = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.AssistantDescription))
|
||||
return DraftFailure(TB("Please describe the assistant you want to create."));
|
||||
return DraftFailure(TA("Please describe the assistant you want to create."));
|
||||
|
||||
if (!IsValidChatLaunchRequest(request.ChatLaunch))
|
||||
return DraftFailure(TB("The chat launcher configuration is incomplete or invalid."));
|
||||
|
||||
if (!ProviderIsUsable(provider))
|
||||
return DraftFailure(TB("Please select a provider."));
|
||||
return DraftFailure(TA("Please select a provider."));
|
||||
|
||||
var context = await this.LoadAssistantBuilderContextAsync();
|
||||
if (string.IsNullOrWhiteSpace(context))
|
||||
return DraftFailure(TB("The Assistant-Builder was not able to read the plugin manifest and therefore cannot safely generate your assistant right now."));
|
||||
return DraftFailure(TA("The Assistant-Builder was not able to read the plugin manifest and therefore cannot safely generate your assistant right now."));
|
||||
|
||||
var prompt = BuildAssistantDraftPrompt(request, context);
|
||||
var markdown = await this.GenerateTextAsync(provider, prompt, TB("Assistant Draft"), BuildDraftSystemPrompt(), token);
|
||||
@@ -67,13 +69,13 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
public async Task<AssistantPluginGenerationDraft> GenerateInitialLuaAsync(AssistantPluginLuaGenerationRequest request, ProviderSettings provider, CancellationToken token = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.ApprovedAssistantDraft))
|
||||
return InitialFailure(TB("Please create an assistant draft first."));
|
||||
return InitialFailure(TA("Please create an assistant draft first."));
|
||||
|
||||
if (!IsValidChatLaunchRequest(request.ChatLaunch))
|
||||
return InitialFailure(TB("The chat launcher configuration is incomplete or invalid."));
|
||||
|
||||
if (!ProviderIsUsable(provider))
|
||||
return InitialFailure(TB("Please select a provider."));
|
||||
return InitialFailure(TA("Please select a provider."));
|
||||
|
||||
//
|
||||
// A launcher is fully described by the Builder form, so nothing about it is left for a
|
||||
@@ -120,7 +122,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
return InitialFailure(TB("The generated assistant metadata does not match the generated plugin."));
|
||||
|
||||
if (this.FindUnknownToolIds(generatedAssistant) is { Count: > 0 } unknownToolIds)
|
||||
return InitialFailure(string.Format(TB("The generated assistant plugin asks for tools this AI Studio does not have: '{0}'. Please try again."), string.Join(", ", unknownToolIds)));
|
||||
return InitialFailure(string.Format(TA("The generated assistant plugin asks for tools this AI Studio does not have: '{0}'. Please try again."), string.Join(", ", unknownToolIds)));
|
||||
|
||||
return new(true, fullLua, parsedResponse.Plugin?.Name ?? string.Empty, string.Empty);
|
||||
}
|
||||
@@ -200,10 +202,10 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
return RevisionFailure(TB("The current plugin.lua content is empty."));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(changeRequest))
|
||||
return RevisionFailure(TB("Please describe what should be changed."));
|
||||
return RevisionFailure(TA("Please describe what should be changed."));
|
||||
|
||||
if (!ProviderIsUsable(provider))
|
||||
return RevisionFailure(TB("Please select a provider."));
|
||||
return RevisionFailure(TA("Please select a provider."));
|
||||
|
||||
var context = await this.LoadAssistantBuilderContextAsync();
|
||||
if (string.IsNullOrWhiteSpace(context))
|
||||
@@ -240,7 +242,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
return RevisionFailure(TB("The revised assistant metadata does not match the revised plugin."));
|
||||
|
||||
if (this.FindUnknownToolIds(revisedAssistant, plugin) is { Count: > 0 } unknownToolIds)
|
||||
return RevisionFailure(string.Format(TB("The revised assistant plugin asks for tools this AI Studio does not have: '{0}'. Please try again."), string.Join(", ", unknownToolIds)));
|
||||
return RevisionFailure(string.Format(TA("The revised assistant plugin asks for tools this AI Studio does not have: '{0}'. Please try again."), string.Join(", ", unknownToolIds)));
|
||||
|
||||
return new(true, revisedLua, parsedResponse.Plugin?.Name ?? plugin.Name, string.Empty);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ public sealed class DataBackupService(ILogger<DataBackupService> logger, Setting
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataBackupService).Namespace, nameof(DataBackupService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataBackupService).Namespace, nameof(DataBackupService));
|
||||
|
||||
private readonly Lock stateLock = new();
|
||||
|
||||
private CancellationTokenSource? running;
|
||||
@@ -92,14 +94,14 @@ public sealed class DataBackupService(ILogger<DataBackupService> logger, Setting
|
||||
public async Task<DataBackupResult> CreateBackupAsync(string archivePath)
|
||||
{
|
||||
if (!settingsManager.ConfigurationData.App.AllowUserToBackUpData)
|
||||
return this.Refuse(archivePath, TB("Your organization does not allow backups."));
|
||||
return this.Refuse(archivePath, TA("Your organization does not allow backups."));
|
||||
|
||||
//
|
||||
// A chat which is being written while it is copied may end up broken in the backup. An
|
||||
// answer or a transcript stores its chat once it is done, so the backup waits for them:
|
||||
//
|
||||
if (aiJobService.HasActiveJobs || mediaTranscriptionService.GetSnapshots().Any(snapshot => snapshot.Status is MediaImportStatus.QUEUED or MediaImportStatus.RUNNING or MediaImportStatus.CANCELING))
|
||||
return this.Refuse(archivePath, TB("AI Studio is still writing an answer or a transcript. Please start the backup once it is done."));
|
||||
return this.Refuse(archivePath, TA("AI Studio is still writing an answer or a transcript. Please start the backup once it is done."));
|
||||
|
||||
CancellationTokenSource cancellation;
|
||||
lock (this.stateLock)
|
||||
@@ -135,7 +137,7 @@ public sealed class DataBackupService(ILogger<DataBackupService> logger, Setting
|
||||
{
|
||||
this.TryDeleteArchive(archivePath);
|
||||
logger.LogError(exception, "The backup into '{ArchivePath}' failed, since the file system takes no file this large. The incomplete backup was removed.", archivePath);
|
||||
return this.End(new(false, false, archivePath, 0, 0, false, TB("The backup is too large for the chosen drive, which takes no files beyond 4 GB. USB sticks formatted with FAT32 are such drives. Please choose another drive.")));
|
||||
return this.End(new(false, false, archivePath, 0, 0, false, TA("The backup is too large for the chosen drive, which takes no files beyond 4 GB. USB sticks formatted with FAT32 are such drives. Please choose another drive.")));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ public sealed class DataRestoreService(ILogger<DataRestoreService> logger, Setti
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataRestoreService).Namespace, nameof(DataRestoreService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataRestoreService).Namespace, nameof(DataRestoreService));
|
||||
|
||||
private readonly Lock stateLock = new();
|
||||
|
||||
private readonly TaskCompletionSource startupStateLoaded = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
@@ -245,16 +247,16 @@ public sealed class DataRestoreService(ILogger<DataRestoreService> logger, Setti
|
||||
public async Task<DataRestoreResult> PrepareRestoreAsync(string archivePath)
|
||||
{
|
||||
if (!settingsManager.ConfigurationData.App.AllowUserToRestoreData)
|
||||
return this.Refuse(TB("Your organization does not allow restoring backups."));
|
||||
return this.Refuse(TA("Your organization does not allow restoring backups."));
|
||||
|
||||
if (dataBackupService.IsRunning)
|
||||
return this.Refuse(TB("A backup is running. Please restore once it is done."));
|
||||
return this.Refuse(TA("A backup is running. Please restore once it is done."));
|
||||
|
||||
if (this.IsRestoreUnfinished)
|
||||
return this.Refuse(TB("AI Studio has not finished restoring an earlier backup yet. Please restart AI Studio first."));
|
||||
return this.Refuse(TA("AI Studio has not finished restoring an earlier backup yet. Please restart AI Studio first."));
|
||||
|
||||
if (aiJobService.HasActiveJobs || mediaTranscriptionService.GetSnapshots().Any(snapshot => snapshot.Status is MediaImportStatus.QUEUED or MediaImportStatus.RUNNING or MediaImportStatus.CANCELING))
|
||||
return this.Refuse(TB("AI Studio is still writing an answer or a transcript. Please restore once it is done."));
|
||||
return this.Refuse(TA("AI Studio is still writing an answer or a transcript. Please restore once it is done."));
|
||||
|
||||
CancellationTokenSource cancellation;
|
||||
lock (this.stateLock)
|
||||
|
||||
@@ -68,6 +68,8 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceEmbeddingService).Namespace, nameof(DataSourceEmbeddingService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataSourceEmbeddingService).Namespace, nameof(DataSourceEmbeddingService));
|
||||
|
||||
/// <summary>
|
||||
/// The pauses of the indexing, cf. PauseIndexingAsync.
|
||||
/// </summary>
|
||||
@@ -637,7 +639,7 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
||||
else
|
||||
{
|
||||
logger.LogError(exception, "Background embedding failed for data source '{DataSourceName}' ({DataSourceId}). RefreshMode={RefreshMode}.", dataSource.Name, dataSource.Id, refreshMode);
|
||||
this.UpsertStatus(this.GetFallbackStatus(dataSource, string.Format(TB("The data source '{0}' could not be processed. The log file holds the details."), dataSource.Name)));
|
||||
this.UpsertStatus(this.GetFallbackStatus(dataSource, string.Format(TA("The data source '{0}' could not be processed. The log file holds the details."), dataSource.Name)));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -866,15 +868,15 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
this.UpsertStatus(this.GetFallbackStatus(dataSource, DataSourceEmbeddingProviders.IsProhibited(settingsManager, dataSource)
|
||||
? TB("The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.")
|
||||
: TB("The selected embedding provider is not available. Please check it in the settings.")));
|
||||
? TA("The use of the embedding provider of this data source has been prohibited by your organization. Please select a different embedding provider in the settings of the data source.")
|
||||
: TA("The selected embedding provider is not available. Please check it in the settings.")));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!AllowsEmbedding(dataSource, embeddingProvider.GetConfidenceLevel(settingsManager)))
|
||||
{
|
||||
var errorMessage = dataSource is DataSourceMailbox && !dataSource.ConfidenceLevel.IsAllowedMailboxConfidence
|
||||
? TB("The mailbox has no valid confidence level, so no provider may read it. Please choose one in the settings of the mailbox.")
|
||||
? TA("The mailbox has no valid confidence level, so no provider may read it. Please choose one in the settings of the mailbox.")
|
||||
: string.Format(TB("The selected embedding provider is not allowed to index this data source. The data source asks for the confidence level '{0}', while the embedding provider has '{1}'."), dataSource.ConfidenceLevel.Name, embeddingProvider.GetConfidenceLevel(settingsManager).Name);
|
||||
logger.LogWarning(
|
||||
"Skipping background embeddings for data source '{DataSourceName}' ({DataSourceId}) because embedding provider '{EmbeddingProviderName}' ({EmbeddingProviderId}) does not meet the required confidence. RequiredConfidence={RequiredConfidence}, EmbeddingProviderConfidence={EmbeddingProviderConfidence}.",
|
||||
@@ -1387,13 +1389,13 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
||||
/// the user needs to read is what this means for their chats and where the way out is.
|
||||
/// </remarks>
|
||||
private DataSourceEmbeddingStatus GetUnreadableVectorStoreStatus(IDataSourceBase dataSource) =>
|
||||
this.GetRepairStatus(dataSource, string.Format(TB("The index of the data source '{0}' cannot be read anymore. The data source stays out of your chats until its index was built anew. Use the repair action to start that."), dataSource.Name));
|
||||
this.GetRepairStatus(dataSource, string.Format(TA("The index of the data source '{0}' cannot be read anymore. The data source stays out of your chats until its index was built anew. Use the repair action to start that."), dataSource.Name));
|
||||
|
||||
/// <remarks>
|
||||
/// The same way out as for a store which cannot be read, cf. GetUnreadableVectorStoreStatus.
|
||||
/// </remarks>
|
||||
private DataSourceEmbeddingStatus GetMissingVectorStoreStatus(IDataSourceBase dataSource) =>
|
||||
this.GetRepairStatus(dataSource, string.Format(TB("The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that."), dataSource.Name));
|
||||
this.GetRepairStatus(dataSource, string.Format(TA("The index of the data source '{0}' is missing. The data source stays out of your chats until its index was built anew. Use the repair action to start that."), dataSource.Name));
|
||||
|
||||
/// <summary>
|
||||
/// Shows a data source as waiting for the user to have its index built anew, cf. NeedsIndexRepair.
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
{
|
||||
private static string TB(string fallbackEn) => I18N.I.T(fallbackEn, typeof(DirectChatService).Namespace, nameof(DirectChatService));
|
||||
|
||||
private static string TA(string fallbackEn) => I18N.I.TA(fallbackEn, typeof(DirectChatService).Namespace, nameof(DirectChatService));
|
||||
|
||||
public async Task<DirectChatStartResult> TryCreateAssistantChatAsync(PluginAssistants assistantPlugin)
|
||||
{
|
||||
if (assistantPlugin.ChatLaunchConfiguration is not { } launchConfiguration)
|
||||
@@ -315,8 +317,8 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
//
|
||||
if (provider == ProviderSettings.NONE)
|
||||
return new(null, originChatTemplate is null
|
||||
? TB("The assistant chat launcher selects data sources, but no provider is available for chats. Please choose a default provider for chats first. No chat was created.")
|
||||
: string.Format(TB("The chat template '{0}' selects data sources, but no provider is available for chats. Please choose a default provider for chats first. No chat was created."), originChatTemplate.GetSafeName()));
|
||||
? TA("The assistant chat launcher selects data sources, but no provider is available for chats. Please choose a default provider for chats first. No chat was created.")
|
||||
: string.Format(TA("The chat template '{0}' selects data sources, but no provider is available for chats. Please choose a default provider for chats first. No chat was created."), originChatTemplate.GetSafeName()));
|
||||
|
||||
var requestedDataSources = new List<IDataSource>(chosenOptions.PreselectedDataSourceIds.Count);
|
||||
foreach (var dataSourceId in chosenOptions.PreselectedDataSourceIds)
|
||||
|
||||
@@ -43,6 +43,8 @@ internal sealed partial class FileSourceIndexer(SettingsManager settingsManager,
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileSourceIndexer).Namespace, nameof(FileSourceIndexer));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(FileSourceIndexer).Namespace, nameof(FileSourceIndexer));
|
||||
|
||||
private enum RagFileIndexingDecision
|
||||
{
|
||||
INDEXABLE,
|
||||
@@ -94,7 +96,7 @@ internal sealed partial class FileSourceIndexer(SettingsManager settingsManager,
|
||||
&& inputFiles.Files.Count == 0 && inputFiles.IgnoredFiles.Count == 0
|
||||
&& inputFiles.FilePatternExclusionCount == 0 && inputFiles.ExcludedFolderCount == 0
|
||||
&& inputFiles.FailedFiles == 0 && inputFiles.UnreadableDirectories.Count == 0)
|
||||
return this.StopWithoutRoot(context, dataSource, string.Format(TB("The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source."), directory.Path));
|
||||
return this.StopWithoutRoot(context, dataSource, string.Format(TA("The folder '{0}' currently appears empty. AI Studio kept the index of this data source, because the folder of a drive that is not connected can look empty. Connect the drive and refresh this data source. If you emptied the folder on purpose, remove this data source."), directory.Path));
|
||||
|
||||
var indexedFiles = inputFiles.Files;
|
||||
var totalFiles = indexedFiles.Count + inputFiles.FailedFiles + inputFiles.IgnoredFiles.Count;
|
||||
@@ -762,7 +764,7 @@ internal sealed partial class FileSourceIndexer(SettingsManager settingsManager,
|
||||
this.EnumerateAccessibleFiles(localDirectory, manifest, mediaTranscriptionAvailability, result);
|
||||
if (result.UnreadableDirectories.Any(directory => string.Equals(Path.TrimEndingDirectorySeparator(Path.GetFullPath(directory)),
|
||||
Path.TrimEndingDirectorySeparator(Path.GetFullPath(localDirectory.Path)), PathTools.COMPARISON)))
|
||||
result.UnreachableRootReason = string.Format(TB("The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index."), localDirectory.Path);
|
||||
result.UnreachableRootReason = string.Format(TA("The folder '{0}' could not be opened. Please check whether you are allowed to read it. Nothing was removed from the index."), localDirectory.Path);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -773,11 +775,11 @@ internal sealed partial class FileSourceIndexer(SettingsManager settingsManager,
|
||||
switch (dataSource)
|
||||
{
|
||||
case DataSourceLocalFile localFile:
|
||||
result.UnreachableRootReason = string.Format(TB("The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source."), localFile.FilePath);
|
||||
result.UnreachableRootReason = string.Format(TA("The file '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the file was moved or renamed. Nothing was removed from the index. If the file was moved or renamed, edit this data source to select its new location. Once the file is back, refresh this data source. When it is gone for good, remove this data source."), localFile.FilePath);
|
||||
break;
|
||||
|
||||
case DataSourceLocalDirectory localDirectory:
|
||||
result.UnreachableRootReason = string.Format(TB("The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source."), localDirectory.Path);
|
||||
result.UnreachableRootReason = string.Format(TA("The folder '{0}' cannot be reached. Perhaps the drive it is on is not connected, or the folder was moved or renamed. Nothing was removed from the index. If the folder was moved or renamed, edit this data source to select its new location. Once the folder is back, refresh this data source."), localDirectory.Path);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -802,7 +804,7 @@ internal sealed partial class FileSourceIndexer(SettingsManager settingsManager,
|
||||
reportInaccessibleDirectory: (directoryPath, exception) =>
|
||||
{
|
||||
logger.LogWarning(exception, "Cannot access directory '{DirectoryPath}' while indexing.", directoryPath);
|
||||
result.AddUnreadableDirectory(directoryPath, string.Format(TB("The folder '{0}' could not be opened. Please check whether you are allowed to read it."), directoryPath));
|
||||
result.AddUnreadableDirectory(directoryPath, string.Format(TA("The folder '{0}' could not be opened. Please check whether you are allowed to read it."), directoryPath));
|
||||
}))
|
||||
{
|
||||
if (!selection.Selects(filePath))
|
||||
@@ -824,7 +826,7 @@ internal sealed partial class FileSourceIndexer(SettingsManager settingsManager,
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogWarning(exception, "Cannot inspect file '{FilePath}' while indexing.", filePath);
|
||||
result.AddFailure(filePath, string.Format(TB("The file '{0}' could not be read. Please check whether you are allowed to read it."), filePath));
|
||||
result.AddFailure(filePath, string.Format(TA("The file '{0}' could not be read. Please check whether you are allowed to read it."), filePath));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ internal sealed class IndexedRunContext(IIndexedDataSource dataSource, Embedding
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(IndexedRunContext).Namespace, nameof(IndexedRunContext));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(IndexedRunContext).Namespace, nameof(IndexedRunContext));
|
||||
|
||||
/// <summary>
|
||||
/// One chunk on its way to the stores, with the id it is stored under and the hash of its text.
|
||||
/// </summary>
|
||||
@@ -743,20 +745,20 @@ internal sealed class IndexedRunContext(IIndexedDataSource dataSource, Embedding
|
||||
}
|
||||
|
||||
if (vectors.Count != texts.Count)
|
||||
throw new InvalidOperationException(string.Format(TB("The embedding provider answered with {0} vectors for {1} parts of '{2}'. Please select another embedding model or provider."), vectors.Count, texts.Count, document.Key));
|
||||
throw new InvalidOperationException(string.Format(TA("The embedding provider answered with {0} vectors for {1} parts of '{2}'. Please select another embedding model or provider."), vectors.Count, texts.Count, document.Key));
|
||||
|
||||
var vectorSize = vectors.FirstOrDefault()?.Count ?? 0;
|
||||
if (vectorSize <= 0)
|
||||
throw new InvalidOperationException(TB("The embedding provider answered with an empty vector. Please select another embedding model or provider."));
|
||||
throw new InvalidOperationException(TA("The embedding provider answered with an empty vector. Please select another embedding model or provider."));
|
||||
|
||||
if (vectors.Any(vector => vector.Count != vectorSize))
|
||||
throw new InvalidOperationException(TB("The embedding provider answered with vectors of different sizes. Please select another embedding model or provider."));
|
||||
throw new InvalidOperationException(TA("The embedding provider answered with vectors of different sizes. Please select another embedding model or provider."));
|
||||
|
||||
if (vectors.Any(vector => vector.Any(value => !float.IsFinite(value))))
|
||||
throw new InvalidOperationException(TB("The embedding provider answered with a vector containing an invalid number. Please select another embedding model or provider."));
|
||||
throw new InvalidOperationException(TA("The embedding provider answered with a vector containing an invalid number. Please select another embedding model or provider."));
|
||||
|
||||
if (manifest.VectorSize > 0 && manifest.VectorSize != vectorSize)
|
||||
throw new InvalidOperationException(string.Format(TB("The size of the embedding vectors changed from {0} to {1}. Please save the data source again to index it from scratch."), manifest.VectorSize, vectorSize));
|
||||
throw new InvalidOperationException(string.Format(TA("The size of the embedding vectors changed from {0} to {1}. Please save the data source again to index it from scratch."), manifest.VectorSize, vectorSize));
|
||||
|
||||
return vectors;
|
||||
}
|
||||
@@ -782,7 +784,7 @@ internal sealed class IndexedRunContext(IIndexedDataSource dataSource, Embedding
|
||||
await vectorStore.DeleteVectorStore(this.CollectionName, token);
|
||||
ensureResult = await vectorStore.EnsureVectorStoreExists(this.CollectionName, dataSource.Name, vectorSize, token);
|
||||
if (!ensureResult.Created)
|
||||
throw new InvalidOperationException(string.Format(TB("The local index '{0}' could not be created again. Please restart AI Studio and try once more."), this.CollectionName));
|
||||
throw new InvalidOperationException(string.Format(TA("The local index '{0}' could not be created again. Please restart AI Studio and try once more."), this.CollectionName));
|
||||
}
|
||||
|
||||
await indexStore.UpdateVectorSizeAsync(dataSource.Id, vectorSize, token);
|
||||
|
||||
@@ -74,6 +74,8 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(MailboxIndexer).Namespace, nameof(MailboxIndexer));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(MailboxIndexer).Namespace, nameof(MailboxIndexer));
|
||||
|
||||
/// <summary>
|
||||
/// One folder after the first pass of a run.
|
||||
/// </summary>
|
||||
@@ -144,7 +146,7 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
var password = await this.ReadPasswordAsync(mailbox);
|
||||
if (password is null)
|
||||
{
|
||||
(await this.CreateStoredStateProgressAsync(context, token)).PublishRunFailure(TB("The password of the mailbox could not be read from the operating system. Please enter it again in the settings of the mailbox."));
|
||||
(await this.CreateStoredStateProgressAsync(context, token)).PublishRunFailure(TA("The password of the mailbox could not be read from the operating system. Please enter it again in the settings of the mailbox."));
|
||||
return IndexedRunOutcome.DONE;
|
||||
}
|
||||
|
||||
@@ -224,7 +226,7 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
// would remove every mail from the index, and embedding them again costs hours.
|
||||
//
|
||||
logger.LogWarning("The server no longer lists the root folder of mailbox '{MailboxId}'. The sync stops, and nothing is removed from the index.", mailbox.Id);
|
||||
(await this.CreateStoredStateProgressAsync(context, token)).PublishRunFailure(string.Format(TB("The server no longer lists the folder '{0}' to which this mailbox is limited. Perhaps it was renamed or deleted. Nothing was removed from the index. Rename the folder back on the server, or add the mailbox anew."), mailbox.RootFolder));
|
||||
(await this.CreateStoredStateProgressAsync(context, token)).PublishRunFailure(string.Format(TA("The server no longer lists the folder '{0}' to which this mailbox is limited. Perhaps it was renamed or deleted. Nothing was removed from the index. Rename the folder back on the server, or add the mailbox anew."), mailbox.RootFolder));
|
||||
return IndexedRunOutcome.DONE;
|
||||
}
|
||||
|
||||
@@ -607,11 +609,11 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
}
|
||||
|
||||
private static string GetAuthFailureMessage(MailboxAuthFailure failure) => string.Format(
|
||||
TB("Signing in to the mailbox failed on {0}. Presumably your password changed. AI Studio does not try again on its own, so that your account is not locked. Please enter your current password in the settings of the mailbox."),
|
||||
TA("Signing in to the mailbox failed on {0}. Presumably your password changed. AI Studio does not try again on its own, so that your account is not locked. Please enter your current password in the settings of the mailbox."),
|
||||
failure.FailedAtUtc.ToLocalTime().ToString("g", I18N.I.Culture));
|
||||
|
||||
private static string GetRemovalMessage(int removalCount) => string.Format(
|
||||
TB("This sync would remove {0} mails from the index of AI Studio at once, so it waits for you to agree. On the server, the mails stay as they are. Should they come back later, e.g. because you choose a larger period again, they have to be embedded anew, which takes time and, with a cloud provider, money."),
|
||||
TA("This sync would remove {0} mails from the index of AI Studio at once, so it waits for you to agree. On the server, the mails stay as they are. Should they come back later, e.g. because you choose a larger period again, they have to be embedded anew, which takes time and, with a cloud provider, money."),
|
||||
removalCount.CompactCount());
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,6 +23,8 @@ public sealed class LocalIndexSearchService(SettingsManager settingsManager, Rus
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(LocalIndexSearchService).Namespace, nameof(LocalIndexSearchService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(LocalIndexSearchService).Namespace, nameof(LocalIndexSearchService));
|
||||
|
||||
//
|
||||
// Which gaps the user was already told about in this session. Retrieval runs for every single
|
||||
// message, so without this one broken embedding provider would put a warning on every prompt.
|
||||
@@ -84,7 +86,7 @@ public sealed class LocalIndexSearchService(SettingsManager settingsManager, Rus
|
||||
if (!DataSourceEmbeddingProviders.TryResolve(settingsManager, dataSource, out var embeddingProvider))
|
||||
{
|
||||
logger.LogWarning("Skipping vector retrieval for data source '{DataSourceName}' ({DataSourceId}) because the selected embedding provider is not available.", dataSource.Name, dataSource.Id);
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.PARTLY_SEARCHED, "no-embedding-provider", string.Format(TB("The data source '{0}' was left out of the answer: its embedding provider is not available. Please check it in the settings."), dataSource.Name));
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.PARTLY_SEARCHED, "no-embedding-provider", string.Format(TA("The data source '{0}' was left out of the answer: its embedding provider is not available. Please check it in the settings."), dataSource.Name));
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -136,7 +138,7 @@ public sealed class LocalIndexSearchService(SettingsManager settingsManager, Rus
|
||||
// answer into one the user can do something about.
|
||||
//
|
||||
logger.LogWarning(exception, "Vector retrieval failed for data source '{DataSourceName}' ({DataSourceId}) because its vector store cannot be read.", dataSource.Name, dataSource.Id);
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.PARTLY_SEARCHED, "vector-store-unreadable", string.Format(TB("The data source '{0}' was left out of the answer: its index cannot be read anymore. You can repair it in your data source settings."), dataSource.Name));
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.PARTLY_SEARCHED, "vector-store-unreadable", string.Format(TA("The data source '{0}' was left out of the answer: its index cannot be read anymore. You can repair it in your data source settings."), dataSource.Name));
|
||||
return [];
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -249,7 +251,7 @@ public sealed class LocalIndexSearchService(SettingsManager settingsManager, Rus
|
||||
query.Length,
|
||||
RustService.MAX_TOKEN_COUNT_REQUEST_TEXT_LENGTH,
|
||||
providerTokenLimit);
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.QUERY_NOT_SEARCHABLE, "query-too-long", string.Format(TB("The data source '{0}' was left out of the answer because your message is too long to search with."), dataSource.Name));
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.QUERY_NOT_SEARCHABLE, "query-too-long", string.Format(TA("The data source '{0}' was left out of the answer because your message is too long to search with."), dataSource.Name));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -276,7 +278,7 @@ public sealed class LocalIndexSearchService(SettingsManager settingsManager, Rus
|
||||
queryTokenCount,
|
||||
embeddingProvider.Name,
|
||||
providerTokenLimit);
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.QUERY_NOT_SEARCHABLE, "query-over-token-limit", string.Format(TB("The data source '{0}' was left out of the answer because your message is longer than its embedding provider '{1}' accepts."), dataSource.Name, embeddingProvider.Name));
|
||||
await this.ReportRetrievalGapAsync(dataSource, run, RetrievalGap.QUERY_NOT_SEARCHABLE, "query-over-token-limit", string.Format(TA("The data source '{0}' was left out of the answer because your message is longer than its embedding provider '{1}' accepts."), dataSource.Name, embeddingProvider.Name));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ public sealed partial class PluginInstallService
|
||||
// alongside them in a subdirectory.
|
||||
//
|
||||
if (PluginFactory.IsOrganizationConfigurationPath(plugin.LocalPath))
|
||||
return TB("Plugins deployed by your organization cannot be deleted.");
|
||||
return TA("Plugins deployed by your organization cannot be deleted.");
|
||||
|
||||
//
|
||||
// Organizations also roll plugins out past these directories, e.g. through their MDM
|
||||
@@ -193,7 +193,7 @@ public sealed partial class PluginInstallService
|
||||
// manual placement as the only way in, and the file system as the way back out.
|
||||
//
|
||||
if (plugin.IsManagedByConfigServer)
|
||||
return TB("Plugins deployed by your organization cannot be deleted.");
|
||||
return TA("Plugins deployed by your organization cannot be deleted.");
|
||||
|
||||
if (!PluginFactory.IsInsidePluginsRoot(plugin.LocalPath) || PluginFactory.IsPluginsRoot(plugin.LocalPath))
|
||||
return TB("This individual plugin’s directory is outside the expected plugins directory.");
|
||||
|
||||
@@ -26,10 +26,10 @@ public sealed partial class PluginInstallService
|
||||
public async Task<AssistantPluginInstallResult> InstallArchiveAsync(string archivePath, Func<PluginImportPreview, Task<bool>> confirmAsync, CancellationToken token)
|
||||
{
|
||||
if (!this.settingsManager.ConfigurationData.App.AllowUserToImportPlugins)
|
||||
return Error(TB("Your organization has disabled importing plugins."));
|
||||
return Error(TA("Your organization has disabled importing plugins."));
|
||||
|
||||
if (!FileTypes.IsAllowedPath(archivePath, FileTypes.PLUGIN_ARCHIVE))
|
||||
return Error(TB("Please select a plugin archive with the extension .mwplugin or .zip."));
|
||||
return Error(TA("Please select a plugin archive with the extension .mwplugin or .zip."));
|
||||
|
||||
if (!File.Exists(archivePath))
|
||||
return Error(TB("The selected plugin archive does not exist."));
|
||||
@@ -112,7 +112,7 @@ public sealed partial class PluginInstallService
|
||||
// A plugin the user imports by hand never comes from a config server. We reject such
|
||||
// archives because AI Studio trusts this self-declared flag: an imported plugin claiming it
|
||||
// would be neither replaceable nor deletable through the user interface:
|
||||
PluginAssistants { IsManagedByConfigServer: true } => TB("This plugin archive declares itself as managed by a config server. Only the IT department of your organization might deploy such plugins."),
|
||||
PluginAssistants { IsManagedByConfigServer: true } => TA("This plugin archive declares itself as managed by a config server. Only the IT department of your organization might deploy such plugins."),
|
||||
|
||||
PluginConfiguration configurationPlugin => this.GetConfigurationImportEligibilityIssue(configurationPlugin),
|
||||
|
||||
@@ -129,16 +129,16 @@ public sealed partial class PluginInstallService
|
||||
private string GetConfigurationImportEligibilityIssue(PluginConfiguration configurationPlugin)
|
||||
{
|
||||
if (!this.settingsManager.ConfigurationData.App.AllowUserToImportConfigurationPlugins)
|
||||
return TB("Your organization has disabled importing configuration plugins.");
|
||||
return TA("Your organization has disabled importing configuration plugins.");
|
||||
|
||||
if (configurationPlugin.DeployedUsingConfigServer is true)
|
||||
return TB("This plugin archive declares itself as managed by a config server. Only the IT department of your organization might deploy such plugins.");
|
||||
return TA("This plugin archive declares itself as managed by a config server. Only the IT department of your organization might deploy such plugins.");
|
||||
|
||||
// Never let an imported configuration take the place of one the organization deployed. This
|
||||
// also covers a deployed configuration which currently cannot be loaded, e.g. because of an
|
||||
// error in its Lua code:
|
||||
if (PluginFactory.IsEnterpriseConfigurationPlugin(configurationPlugin.Id))
|
||||
return TB("Your organization deployed a configuration with the same ID. An imported configuration must not take its place.");
|
||||
return TA("Your organization deployed a configuration with the same ID. An imported configuration must not take its place.");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public sealed partial class PluginInstallService
|
||||
return string.Empty;
|
||||
|
||||
if (existingPlugin.IsManagedByConfigServer)
|
||||
return TB("Plugins deployed by your organization cannot be replaced.");
|
||||
return TA("Plugins deployed by your organization cannot be replaced.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(existingPlugin.LocalPath))
|
||||
return string.Empty;
|
||||
@@ -232,7 +232,7 @@ public sealed partial class PluginInstallService
|
||||
};
|
||||
|
||||
return isManagedByConfigServer
|
||||
? TB("Plugins deployed by your organization cannot be replaced.")
|
||||
? TA("Plugins deployed by your organization cannot be replaced.")
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ public sealed partial class PluginInstallService
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginInstallService).Namespace, nameof(PluginInstallService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(PluginInstallService).Namespace, nameof(PluginInstallService));
|
||||
|
||||
private const string PLUGIN_FILE_NAME = "plugin.lua";
|
||||
private const string ASSISTANT_BUILDER_DIRECTORY_PREFIX = "assistant-builder";
|
||||
private const string PLUGIN_IMPORT_DIRECTORY_PREFIX = "plugin-import";
|
||||
|
||||
@@ -13,6 +13,8 @@ public sealed class PluginShareService(NativeShareService nativeShareService, Ru
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginShareService).Namespace, nameof(PluginShareService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(PluginShareService).Namespace, nameof(PluginShareService));
|
||||
|
||||
private const string PLUGIN_FILE_NAME = "plugin.lua";
|
||||
|
||||
/// <remarks>
|
||||
@@ -101,7 +103,7 @@ public sealed class PluginShareService(NativeShareService nativeShareService, Ru
|
||||
|
||||
if (!settingsManager.ConfigurationData.App.AllowUserToSharePlugins)
|
||||
{
|
||||
issue = TB("Your organization has disabled sharing plugins.");
|
||||
issue = TA("Your organization has disabled sharing plugins.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -195,7 +197,7 @@ public sealed class PluginShareService(NativeShareService nativeShareService, Ru
|
||||
// We keep it for that case; the clean-up removes it once it expired:
|
||||
//
|
||||
case ShareSheetOutcome.UNRESPONSIVE:
|
||||
return ShareSheetUnavailable(plugin, TB("The share dialog of your operating system did not respond."));
|
||||
return ShareSheetUnavailable(plugin, TA("The share dialog of your operating system did not respond."));
|
||||
|
||||
case ShareSheetOutcome.FAILED:
|
||||
case ShareSheetOutcome.UNKNOWN:
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed partial class RustService
|
||||
/// <param name="text">The text to copy to the clipboard.</param>
|
||||
public async Task CopyText2Clipboard(string text)
|
||||
{
|
||||
var message = TB("Successfully copied the text to your clipboard");
|
||||
var message = TA("Successfully copied the text to your clipboard");
|
||||
var succeeded = false;
|
||||
try
|
||||
{
|
||||
@@ -24,7 +24,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 = TB("Failed to copy the text to your clipboard.");
|
||||
message = TA("Failed to copy the text to your clipboard.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed partial class RustService
|
||||
if (!state.Success)
|
||||
{
|
||||
this.logger!.LogError("Failed to copy the text to the clipboard.");
|
||||
message = TB("Failed to copy the text to your clipboard.");
|
||||
message = TA("Failed to copy the text to your clipboard.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ public sealed partial class RustService
|
||||
{
|
||||
SecretStoreIssueCode.NONE => issue,
|
||||
SecretStoreIssueCode.SECRET_NOT_FOUND => TB("No saved secret was found."),
|
||||
SecretStoreIssueCode.NO_DEFAULT_COLLECTION => TB("AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default."),
|
||||
SecretStoreIssueCode.COLLECTION_LOCKED => TB("AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection."),
|
||||
SecretStoreIssueCode.PROMPT_DISMISSED => TB("The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt."),
|
||||
SecretStoreIssueCode.SERVICE_UNAVAILABLE => TB("No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service."),
|
||||
_ => TB("AI Studio could not access secure storage. See the log for technical details."),
|
||||
SecretStoreIssueCode.NO_DEFAULT_COLLECTION => TA("AI Studio could not access secure storage because no default collection is configured. Open a compatible password manager, create or select a collection, unlock it, and set it as the default."),
|
||||
SecretStoreIssueCode.COLLECTION_LOCKED => TA("AI Studio could not access secure storage because the default collection is locked. Open your password manager and unlock the default collection."),
|
||||
SecretStoreIssueCode.PROMPT_DISMISSED => TA("The secure-storage confirmation was canceled. Repeat the operation and confirm the password manager prompt."),
|
||||
SecretStoreIssueCode.SERVICE_UNAVAILABLE => TA("No compatible secure-storage service is available. Configure a password manager that provides the FreeDesktop Secret Service."),
|
||||
_ => TA("AI Studio could not access secure storage. See the log for technical details."),
|
||||
};
|
||||
|
||||
private static StoreSecretResponse TranslateSecretStoreIssue(StoreSecretResponse response) =>
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace AIStudio.Tools.Services;
|
||||
public sealed partial class RustService : BackgroundService
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, nameof(RustService));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(RustService).Namespace, nameof(RustService));
|
||||
|
||||
private readonly HttpClient http;
|
||||
|
||||
|
||||
@@ -9,6 +9,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 string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(UpdateService).Namespace, nameof(UpdateService));
|
||||
|
||||
private static bool IS_INITIALIZED;
|
||||
|
||||
@@ -100,7 +102,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
|
||||
if (notifyUserWhenNoUpdate)
|
||||
{
|
||||
await this.messageBus.SendError(new(AppIcons.ERROR, TB("Failed to check for updates. Please try again later.")));
|
||||
await this.messageBus.SendError(new(AppIcons.ERROR, TA("Failed to check for updates. Please try again later.")));
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -127,7 +129,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await this.messageBus.SendError(new(AppIcons.ERROR, TB("Failed to install update automatically. Please try again manually.")));
|
||||
await this.messageBus.SendError(new(AppIcons.ERROR, TA("Failed to install update automatically. Please try again manually.")));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace AIStudio.Tools.ToolCallingSystem.Harness;
|
||||
/// </remarks>
|
||||
public static class ToolCallingMessages
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ToolCallingMessages).Namespace, nameof(ToolCallingMessages));
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ToolCallingMessages).Namespace, nameof(ToolCallingMessages));
|
||||
|
||||
/// <summary>
|
||||
/// Builds the exception for a response that cannot be used to continue.
|
||||
@@ -20,7 +20,7 @@ public static class ToolCallingMessages
|
||||
/// <param name="providerInstanceName">The provider instance the user configured.</param>
|
||||
public static ProviderRequestException InvalidToolCallingResponse(string providerInstanceName) => new(
|
||||
ProviderRequestFailureReason.NONE,
|
||||
string.Format(TB("The provider '{0}' returned an invalid tool calling response. Check the provider's tool calling configuration and see the logs for details."), providerInstanceName));
|
||||
string.Format(TA("The provider '{0}' returned an invalid tool calling response. Check the provider's tool calling configuration and see the logs for details."), providerInstanceName));
|
||||
|
||||
/// <summary>
|
||||
/// Tells the user that a tool round could not be requested at all.
|
||||
@@ -32,5 +32,5 @@ public static class ToolCallingMessages
|
||||
/// <param name="statusCode">The status code the provider answered with.</param>
|
||||
public static async Task SendToolCallingRequestFailedAsync(int statusCode) => await MessageBus.INSTANCE.SendError(new(
|
||||
AppIcons.TOOL,
|
||||
string.Format(TB("The tool calling request failed with status code {0}. See the logs for details."), statusCode)));
|
||||
string.Format(TA("The tool calling request failed with status code {0}. See the logs for details."), statusCode)));
|
||||
}
|
||||
@@ -6,20 +6,20 @@ namespace AIStudio.Tools.ToolCallingSystem;
|
||||
|
||||
public static class ToolCallingAvailabilityExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ToolCallingAvailabilityExtensions).Namespace, nameof(ToolCallingAvailabilityExtensions));
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ToolCallingAvailabilityExtensions).Namespace, nameof(ToolCallingAvailabilityExtensions));
|
||||
|
||||
extension(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
public ToolCallingAvailability GetToolCallingAvailability()
|
||||
{
|
||||
if (provider == AIStudio.Settings.Provider.NONE || provider.UsedLLMProvider is LLMProviders.NONE)
|
||||
return new(false, TB("Please select an LLM provider."));
|
||||
return new(false, TA("Please select an LLM provider."));
|
||||
|
||||
var modelProfile = provider.GetModelProfile();
|
||||
var supportsRequiredApis = modelProfile.HasAny(Capability.CHAT_COMPLETION_API | Capability.RESPONSES_API);
|
||||
|
||||
if (!supportsRequiredApis || !modelProfile.Has(Capability.FUNCTION_CALLING))
|
||||
return new(false, TB("Tool calling support is not enabled by default for this model, but you can enable this capability in the expert settings of the provider if you are sure the model supports it."));
|
||||
return new(false, TA("Tool calling support is not enabled by default for this model, but you can enable this capability in the expert settings of the provider if you are sure the model supports it."));
|
||||
|
||||
return ToolCallingAvailability.Available();
|
||||
}
|
||||
|
||||
+7
-5
@@ -38,6 +38,8 @@ public sealed class ConfluenceSearchTool(WebPageRetrievalService webPageRetrieva
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfluenceSearchTool).Namespace, nameof(ConfluenceSearchTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ConfluenceSearchTool).Namespace, nameof(ConfluenceSearchTool));
|
||||
|
||||
private const string BASE_URL_SETTING = "baseUrl";
|
||||
private const string TIMEOUT_SECONDS_SETTING = "timeoutSeconds";
|
||||
private const string QUERY_ARGUMENT = "query";
|
||||
@@ -137,7 +139,7 @@ public sealed class ConfluenceSearchTool(WebPageRetrievalService webPageRetrieva
|
||||
|
||||
public string GetDisplayName() => TB("Search Confluence");
|
||||
|
||||
public string GetDescription() => TB("Find pages in your company's Confluence wiki.");
|
||||
public string GetDescription() => TA("Find pages in your company's Confluence wiki.");
|
||||
|
||||
public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
|
||||
{
|
||||
@@ -148,7 +150,7 @@ public sealed class ConfluenceSearchTool(WebPageRetrievalService webPageRetrieva
|
||||
|
||||
public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
|
||||
{
|
||||
BASE_URL_SETTING => TB("The HTTPS address of your Confluence Data Center wiki, including its path if present, such as https://wiki.example.org/confluence/. Confluence Cloud is not supported yet. When your wiki has a private or VPN address, also add its host to the allowed private hosts of Read Web Page, which opens the pages found. Where a chat may send data after a search is decided by the setting 'Where a chat may send data after reading internal pages' of Read Web Page, which applies to wiki pages as well."),
|
||||
BASE_URL_SETTING => TA("The HTTPS address of your Confluence Data Center wiki, including its path if present, such as https://wiki.example.org/confluence/. Confluence Cloud is not supported yet. When your wiki has a private or VPN address, also add its host to the allowed private hosts of Read Web Page, which opens the pages found. Where a chat may send data after a search is decided by the setting 'Where a chat may send data after reading internal pages' of Read Web Page, which applies to wiki pages as well."),
|
||||
TIMEOUT_SECONDS_SETTING => TB("(Optional) Search request timeout in seconds."),
|
||||
_ => TB(fieldDefinition.Description),
|
||||
};
|
||||
@@ -165,7 +167,7 @@ public sealed class ConfluenceSearchTool(WebPageRetrievalService webPageRetrieva
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = TB("Enter a valid HTTPS Confluence base URL without a query or fragment."),
|
||||
Message = TA("Enter a valid HTTPS Confluence base URL without a query or fragment."),
|
||||
});
|
||||
|
||||
if (!ToolSettingsValueParser.TryReadBoundedOptionalPositiveInt(settingsValues, TIMEOUT_SECONDS_SETTING, MAX_TIMEOUT_SECONDS,
|
||||
@@ -193,7 +195,7 @@ public sealed class ConfluenceSearchTool(WebPageRetrievalService webPageRetrieva
|
||||
// a High-confidence provider.
|
||||
//
|
||||
if (context.ProviderConfidence < ConfidenceLevel.HIGH)
|
||||
throw new ToolExecutionBlockedException(TB("Searching your company's wiki requires a High-confidence provider."));
|
||||
throw new ToolExecutionBlockedException(TA("Searching your company's wiki requires a High-confidence provider."));
|
||||
|
||||
if (!TryParseBaseUrl(context.SettingsValues.GetValueOrDefault(BASE_URL_SETTING), out var baseUrl))
|
||||
throw new InvalidOperationException(TB("The Confluence base URL is not configured correctly."));
|
||||
@@ -247,7 +249,7 @@ public sealed class ConfluenceSearchTool(WebPageRetrievalService webPageRetrieva
|
||||
throw new InvalidOperationException(TB("Confluence redirected the search outside the configured wiki."));
|
||||
|
||||
if (IsLoginPage(page.FinalUrl))
|
||||
throw new InvalidOperationException(TB("Confluence asked for a sign-in instead of showing search results. AI Studio signs in with your operating system account only when your wiki has a private or VPN address, and either the wiki did not accept that sign-in or its address is public. Open the wiki in your browser to check your access."));
|
||||
throw new InvalidOperationException(TA("Confluence asked for a sign-in instead of showing search results. AI Studio signs in with your operating system account only when your wiki has a private or VPN address, and either the wiki did not accept that sign-in or its address is public. Open the wiki in your browser to check your access."));
|
||||
|
||||
var markdown = retrievedPage.ExtractedPage.Markdown;
|
||||
if (string.IsNullOrWhiteSpace(markdown))
|
||||
|
||||
+3
-1
@@ -31,6 +31,8 @@ public sealed class CountMailsTool(SettingsManager settingsManager, MailboxRetri
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(CountMailsTool).Namespace, nameof(CountMailsTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(CountMailsTool).Namespace, nameof(CountMailsTool));
|
||||
|
||||
private const string GROUP_BY_ARGUMENT = "group_by";
|
||||
private const string TOOL_ACTION = "count";
|
||||
|
||||
@@ -124,7 +126,7 @@ public sealed class CountMailsTool(SettingsManager settingsManager, MailboxRetri
|
||||
|
||||
public string GetDisplayName() => TB("Count Mails");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI count the mails in your mailboxes, e.g., the unread ones or those in a project folder.");
|
||||
public string GetDescription() => TA("Lets the AI count the mails in your mailboxes, e.g., the unread ones or those in a project folder.");
|
||||
|
||||
public Task<ToolConfigurationState?> ValidateConfigurationAsync(ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) =>
|
||||
Task.FromResult(MailToolConfiguration.GetState(settingsManager));
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.Mailboxes;
|
||||
/// </summary>
|
||||
internal static class MailToolConfiguration
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(MailToolConfiguration).Namespace, nameof(MailToolConfiguration));
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(MailToolConfiguration).Namespace, nameof(MailToolConfiguration));
|
||||
|
||||
/// <summary>
|
||||
/// The state the selection shows for a mail tool: not set up while there is no mailbox at all.
|
||||
@@ -24,6 +24,6 @@ internal static class MailToolConfiguration
|
||||
: new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = TB("To use this tool, add a mailbox to your data sources first."),
|
||||
Message = TA("To use this tool, add a mailbox to your data sources first."),
|
||||
};
|
||||
}
|
||||
+3
-1
@@ -17,6 +17,8 @@ public sealed class MailboxToolCollection : IToolCollection
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(MailboxToolCollection).Namespace, nameof(MailboxToolCollection));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(MailboxToolCollection).Namespace, nameof(MailboxToolCollection));
|
||||
|
||||
public ToolCollectionDefinition GetDefinition() => new()
|
||||
{
|
||||
Id = ToolSelectionRules.MAILBOXES_COLLECTION_ID,
|
||||
@@ -29,5 +31,5 @@ public sealed class MailboxToolCollection : IToolCollection
|
||||
|
||||
public string GetDisplayName() => TB("Mailboxes");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI search, read, and count the mails in your mailboxes, including their attachments.");
|
||||
public string GetDescription() => TA("Lets the AI search, read, and count the mails in your mailboxes, including their attachments.");
|
||||
}
|
||||
+3
-1
@@ -28,6 +28,8 @@ public sealed class ReadMailTool(SettingsManager settingsManager, MailboxRetriev
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ReadMailTool).Namespace, nameof(ReadMailTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ReadMailTool).Namespace, nameof(ReadMailTool));
|
||||
|
||||
private const string MAIL_ID_ARGUMENT = "mail_id";
|
||||
private const string ATTACHMENT_ARGUMENT = "attachment";
|
||||
private const string INCLUDE_HEADERS_ARGUMENT = "include_headers";
|
||||
@@ -108,7 +110,7 @@ public sealed class ReadMailTool(SettingsManager settingsManager, MailboxRetriev
|
||||
|
||||
public string GetDisplayName() => TB("Read Mail");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI read the mails it found in your mailboxes, including their attachments.");
|
||||
public string GetDescription() => TA("Lets the AI read the mails it found in your mailboxes, including their attachments.");
|
||||
|
||||
public Task<ToolConfigurationState?> ValidateConfigurationAsync(ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) =>
|
||||
Task.FromResult(MailToolConfiguration.GetState(settingsManager));
|
||||
|
||||
+3
-1
@@ -31,6 +31,8 @@ public sealed class SearchMailsTool(SettingsManager settingsManager, MailboxRetr
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SearchMailsTool).Namespace, nameof(SearchMailsTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(SearchMailsTool).Namespace, nameof(SearchMailsTool));
|
||||
|
||||
private const string QUERY_ARGUMENT = "query";
|
||||
private const string PAGE_ARGUMENT = "page";
|
||||
private const string TOOL_ACTION = "search";
|
||||
@@ -187,7 +189,7 @@ public sealed class SearchMailsTool(SettingsManager settingsManager, MailboxRetr
|
||||
|
||||
public string GetDisplayName() => TB("Search Mails");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI search your mailboxes, list mails by sender, date, or flags, and quote what they say.");
|
||||
public string GetDescription() => TA("Lets the AI search your mailboxes, list mails by sender, date, or flags, and quote what they say.");
|
||||
|
||||
public Task<ToolConfigurationState?> ValidateConfigurationAsync(ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) =>
|
||||
Task.FromResult(MailToolConfiguration.GetState(settingsManager));
|
||||
|
||||
+3
-1
@@ -27,6 +27,8 @@ public sealed class CalculateTool(SettingsManager settingsManager, MathComputati
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(CalculateTool).Namespace, nameof(CalculateTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(CalculateTool).Namespace, nameof(CalculateTool));
|
||||
|
||||
private const string EXPRESSION_ARGUMENT = "expression";
|
||||
private const string DIGITS_ARGUMENT = "digits";
|
||||
|
||||
@@ -76,7 +78,7 @@ public sealed class CalculateTool(SettingsManager settingsManager, MathComputati
|
||||
|
||||
public string GetDisplayName() => TB("Calculate");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI calculate exactly instead of guessing digits, e.g., fractions, roots, or integrals, and check an equation. Everything is computed on your computer.");
|
||||
public string GetDescription() => TA("Lets the AI calculate exactly instead of guessing digits, e.g., fractions, roots, or integrals, and check an equation. Everything is computed on your computer.");
|
||||
|
||||
public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default)
|
||||
{
|
||||
|
||||
+3
-1
@@ -30,6 +30,8 @@ public sealed class ComputeSymbolicallyTool(SettingsManager settingsManager, Mat
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ComputeSymbolicallyTool).Namespace, nameof(ComputeSymbolicallyTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ComputeSymbolicallyTool).Namespace, nameof(ComputeSymbolicallyTool));
|
||||
|
||||
private const string OPERATION_ARGUMENT = "operation";
|
||||
private const string EXPRESSION_ARGUMENT = "expression";
|
||||
private const string VARIABLE_ARGUMENT = "variable";
|
||||
@@ -122,7 +124,7 @@ public sealed class ComputeSymbolicallyTool(SettingsManager settingsManager, Mat
|
||||
|
||||
public string GetDisplayName() => TB("Compute Symbolically");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI work with formulas: simplify and factor them, differentiate and integrate them, take limits, and solve equations. Every result is checked numerically where possible, right on your computer.");
|
||||
public string GetDescription() => TA("Lets the AI work with formulas: simplify and factor them, differentiate and integrate them, take limits, and solve equations. Every result is checked numerically where possible, right on your computer.");
|
||||
|
||||
public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default)
|
||||
{
|
||||
|
||||
+3
-1
@@ -18,6 +18,8 @@ public sealed class MathematicsToolCollection : IToolCollection
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(MathematicsToolCollection).Namespace, nameof(MathematicsToolCollection));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(MathematicsToolCollection).Namespace, nameof(MathematicsToolCollection));
|
||||
|
||||
public ToolCollectionDefinition GetDefinition() => new()
|
||||
{
|
||||
Id = ToolSelectionRules.MATHEMATICS_COLLECTION_ID,
|
||||
@@ -30,5 +32,5 @@ public sealed class MathematicsToolCollection : IToolCollection
|
||||
|
||||
public string GetDisplayName() => TB("Mathematics");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI calculate exactly and work with formulas, such as solving equations or checking an integral, right on your computer.");
|
||||
public string GetDescription() => TA("Lets the AI calculate exactly and work with formulas, such as solving equations or checking an integral, right on your computer.");
|
||||
}
|
||||
+8
-6
@@ -15,6 +15,8 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool));
|
||||
|
||||
private const int DEFAULT_TIMEOUT_SECONDS = 60;
|
||||
private const int DEFAULT_MAX_CONTENT_CHARACTERS = 30000;
|
||||
private const int MAX_TIMEOUT_SECONDS = 240;
|
||||
@@ -111,7 +113,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
|
||||
public string GetDisplayName() => TB("Read Web Page");
|
||||
|
||||
public string GetDescription() => TB("Load a web page and extract its readable content, links, and page details.");
|
||||
public string GetDescription() => TA("Load a web page and extract its readable content, links, and page details.");
|
||||
|
||||
public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
|
||||
{
|
||||
@@ -127,9 +129,9 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
{
|
||||
TIMEOUT_SECONDS_SETTING => TB("(Optional) HTTP timeout for loading a web page in seconds."),
|
||||
MAX_CONTENT_CHARACTERS_SETTING => TB("(Optional) Global truncation limit for extracted characters returned to the model."),
|
||||
ALLOWED_PRIVATE_HOSTS_SETTING => TB("(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider. For allowed HTTPS internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."),
|
||||
FREE_ADDRESS_CHOICE_SETTING => TB("(Optional) With free address choice off, the AI reads only web addresses that appear in the chat, such as in your messages, attached documents, or data sources, or that a tool returned. AI Studio refuses every other address. With it on, the AI may also choose addresses itself. Off is the default."),
|
||||
INTERNAL_PAGE_OUTBOUND_DATA_RESTRICTION_SETTING => string.Format(TB("(Optional) Internal pages are web pages on a private or VPN address and the pages of your Confluence wiki, whatever its address. Once a chat read one, or searched your wiki, a web search or a web address chosen by the AI could carry internal content to third parties. This setting decides what the chat may still use afterwards. With '{0}', the default, the chat does not search the web, but it still reads web pages whose addresses stand in the chat. With '{1}', it reads only pages of your Confluence wiki and of the allowed private hosts. A change applies to pages read later; a chat keeps the level it had when it read a page."), OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.Name, OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.Name),
|
||||
ALLOWED_PRIVATE_HOSTS_SETTING => TA("(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider. For allowed HTTPS internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."),
|
||||
FREE_ADDRESS_CHOICE_SETTING => TA("(Optional) With free address choice off, the AI reads only web addresses that appear in the chat, such as in your messages, attached documents, or data sources, or that a tool returned. AI Studio refuses every other address. With it on, the AI may also choose addresses itself. Off is the default."),
|
||||
INTERNAL_PAGE_OUTBOUND_DATA_RESTRICTION_SETTING => string.Format(TA("(Optional) Internal pages are web pages on a private or VPN address and the pages of your Confluence wiki, whatever its address. Once a chat has read one or searched your wiki, a web search or a web address chosen by the AI could carry internal content to third parties. This setting decides what the chat may still use afterwards. With '{0}', the default, the chat does not search the web, but it still reads web pages whose addresses stand in the chat. With '{1}', it reads only pages of your Confluence wiki and of the allowed private hosts. A change applies to pages read later; a chat keeps the level it had when it read a page."), OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.Name, OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.Name),
|
||||
_ => TB(fieldDefinition.Description),
|
||||
};
|
||||
|
||||
@@ -175,7 +177,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
// may be misspelled. Guessing what it meant would decide on the organization's behalf
|
||||
// whether the AI may choose addresses, so it is reported instead:
|
||||
//
|
||||
if (!ToolSettingsValueParser.TryValidateOptionValue(settingsValues, FREE_ADDRESS_CHOICE_SETTING, ToolSettingsOptionSources.FREE_ADDRESS_CHOICE, TB("The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values."), out var freeAddressChoiceError))
|
||||
if (!ToolSettingsValueParser.TryValidateOptionValue(settingsValues, FREE_ADDRESS_CHOICE_SETTING, ToolSettingsOptionSources.FREE_ADDRESS_CHOICE, TA("The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values."), out var freeAddressChoiceError))
|
||||
{
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
@@ -185,7 +187,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
}
|
||||
|
||||
// Reported for the same reason: a guess would decide where intranet content may go.
|
||||
if (!ToolSettingsValueParser.TryValidateOptionValue(settingsValues, INTERNAL_PAGE_OUTBOUND_DATA_RESTRICTION_SETTING, ToolSettingsOptionSources.OUTBOUND_DATA_RESTRICTION, TB("The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values."), out var restrictionError))
|
||||
if (!ToolSettingsValueParser.TryValidateOptionValue(settingsValues, INTERNAL_PAGE_OUTBOUND_DATA_RESTRICTION_SETTING, ToolSettingsOptionSources.OUTBOUND_DATA_RESTRICTION, TA("The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values."), out var restrictionError))
|
||||
{
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
|
||||
+3
-1
@@ -35,6 +35,8 @@ public sealed class SemanticSearchTool(SettingsManager settingsManager, DataSour
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SemanticSearchTool).Namespace, nameof(SemanticSearchTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(SemanticSearchTool).Namespace, nameof(SemanticSearchTool));
|
||||
|
||||
private const string QUERY_ARGUMENT = "query";
|
||||
private const string DATA_SOURCE_IDS_ARGUMENT = "data_source_ids";
|
||||
private const string PAGE_ARGUMENT = "page";
|
||||
@@ -345,7 +347,7 @@ public sealed class SemanticSearchTool(SettingsManager settingsManager, DataSour
|
||||
|
||||
public string GetDisplayName() => TB("Semantic Search");
|
||||
|
||||
public string GetDescription() => TB("Lets the AI search the data sources of your chat itself, whenever a question calls for it.");
|
||||
public string GetDescription() => TA("Lets the AI search the data sources of your chat itself, whenever a question calls for it.");
|
||||
|
||||
public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default)
|
||||
{
|
||||
|
||||
+3
-1
@@ -14,6 +14,8 @@ public sealed class SearXNGSearchBackend : IWebSearchBackend
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SearXNGSearchBackend).Namespace, nameof(SearXNGSearchBackend));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(SearXNGSearchBackend).Namespace, nameof(SearXNGSearchBackend));
|
||||
|
||||
private const string SETTINGS_GROUP = "searxng";
|
||||
|
||||
private const string BASE_URL_SETTING = $"{SETTINGS_GROUP}.baseUrl";
|
||||
@@ -59,7 +61,7 @@ public sealed class SearXNGSearchBackend : IWebSearchBackend
|
||||
|
||||
public string GetSettingsFieldDescription(string fieldName) => fieldName switch
|
||||
{
|
||||
BASE_URL_SETTING => TB("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint. The instance must have the JSON format enabled, which means 'json' has to be listed under 'search.formats' in its settings.yml. Public instances usually serve only the web interface and additionally block automated requests, so a self-hosted instance is the reliable option."),
|
||||
BASE_URL_SETTING => TA("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint. The instance must have the JSON format enabled, which means 'json' has to be listed under 'search.formats' in its settings.yml. Public instances usually serve only the web interface and additionally block automated requests, so a self-hosted instance is the reliable option."),
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
|
||||
+4
-2
@@ -18,6 +18,8 @@ public sealed class StaanSearchBackend : IWebSearchBackend
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(StaanSearchBackend).Namespace, nameof(StaanSearchBackend));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(StaanSearchBackend).Namespace, nameof(StaanSearchBackend));
|
||||
|
||||
private const string SETTINGS_GROUP = "staan";
|
||||
|
||||
private const string API_KEY_SETTING = $"{SETTINGS_GROUP}.apiKey";
|
||||
@@ -85,7 +87,7 @@ public sealed class StaanSearchBackend : IWebSearchBackend
|
||||
|
||||
public string GetSettingsFieldDescription(string fieldName) => fieldName switch
|
||||
{
|
||||
API_KEY_SETTING => TB("Your Staan API key. It is kept in your operating system's keyring, not in a settings file. Staan is a European search index; the first requests are free of charge, after which searching is billed per thousand requests."),
|
||||
API_KEY_SETTING => TA("Your Staan API key. It is kept in your operating system's keyring, not in a settings file. Staan is a European search index; the first requests are free of charge, after which searching is billed per thousand requests."),
|
||||
MARKET_SETTING => TB("The market Staan searches in. Staan searches one market at a time and offers only these three. When the AI model asks for German, English, or French, the matching market is used no matter what is chosen here; this setting decides what happens for every other language and when no language is requested at all."),
|
||||
|
||||
_ => string.Empty,
|
||||
@@ -113,7 +115,7 @@ public sealed class StaanSearchBackend : IWebSearchBackend
|
||||
if (string.IsNullOrWhiteSpace(market) || IsSupportedMarket(market))
|
||||
return true;
|
||||
|
||||
error = string.Format(TB("The configured Staan market '{0}' is not one of the markets Staan offers. Please choose one of these: {1}."), market, string.Join(", ", SUPPORTED_MARKETS));
|
||||
error = string.Format(TA("The configured Staan market '{0}' is not one of the markets Staan offers. Please choose one of these: {1}."), market, string.Join(", ", SUPPORTED_MARKETS));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -15,6 +15,8 @@ public sealed class TavilySearchBackend : IWebSearchBackend
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(TavilySearchBackend).Namespace, nameof(TavilySearchBackend));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(TavilySearchBackend).Namespace, nameof(TavilySearchBackend));
|
||||
|
||||
private const string SETTINGS_GROUP = "tavily";
|
||||
|
||||
private const string API_KEY_SETTING = $"{SETTINGS_GROUP}.apiKey";
|
||||
@@ -78,8 +80,8 @@ public sealed class TavilySearchBackend : IWebSearchBackend
|
||||
|
||||
public string GetSettingsFieldDescription(string fieldName) => fieldName switch
|
||||
{
|
||||
API_KEY_SETTING => TB("Your Tavily API key. It is kept in your operating system's keyring, not in a settings file. Tavily grants 1,000 requests per month without a credit card, which is enough for everyday use."),
|
||||
SEARCH_DEPTH_SETTING => TB("How thoroughly Tavily searches. A basic search costs one of your monthly requests, an advanced search costs two and looks at more of each page before deciding how well it matches. Basic is the sensible choice unless you notice that results are missing the point."),
|
||||
API_KEY_SETTING => TA("Your Tavily API key. It is kept in your operating system's keyring, not in a settings file. Tavily grants 1,000 requests per month without a credit card, which is enough for everyday use."),
|
||||
SEARCH_DEPTH_SETTING => TA("How thoroughly Tavily searches. A basic search costs one of your monthly requests, an advanced search costs two and looks at more of each page before deciding how well it matches. Basic is the sensible choice unless you notice that results are missing the point."),
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
@@ -106,7 +108,7 @@ public sealed class TavilySearchBackend : IWebSearchBackend
|
||||
if (string.IsNullOrWhiteSpace(searchDepth) || IsSupportedSearchDepth(searchDepth))
|
||||
return true;
|
||||
|
||||
error = string.Format(TB("The configured Tavily search depth '{0}' is not one this app supports. Please choose one of these: {1}."), searchDepth, string.Join(", ", SUPPORTED_SEARCH_DEPTHS));
|
||||
error = string.Format(TA("The configured Tavily search depth '{0}' is not one this app supports. Please choose one of these: {1}."), searchDepth, string.Join(", ", SUPPORTED_SEARCH_DEPTHS));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -20,6 +20,8 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WebSearchDispatcher).Namespace, nameof(WebSearchDispatcher));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(WebSearchDispatcher).Namespace, nameof(WebSearchDispatcher));
|
||||
|
||||
public IReadOnlyList<IWebSearchBackend> Backends { get; } = backends.OrderBy(backend => backend.Backend).ToList();
|
||||
|
||||
/// <summary>
|
||||
@@ -115,7 +117,7 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
|
||||
var remainingBackends = backendsToAsk.Where(backend => backend.Capabilities.SupportsSafeSearch).ToList();
|
||||
if (remainingBackends.Count is 0)
|
||||
throw new InvalidOperationException(TB("None of the search services this search would use can filter explicit results, which the configured safe search policy requires. Please configure a search service that can filter, or turn the policy off."));
|
||||
throw new InvalidOperationException(TA("None of the search services this search would use can filter explicit results, which the configured safe search policy requires. Please configure a search service that can filter, or turn the policy off."));
|
||||
|
||||
foreach (var backend in backendsToAsk.Where(backend => !backend.Capabilities.SupportsSafeSearch))
|
||||
notes.Add($"{backend.Backend.Name} was not asked, because it cannot filter explicit results and the configured safe search policy requires that.");
|
||||
|
||||
+12
-10
@@ -24,6 +24,8 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WebSearchTool).Namespace, nameof(WebSearchTool));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(WebSearchTool).Namespace, nameof(WebSearchTool));
|
||||
|
||||
//
|
||||
// The dispatcher holds the backends: which of them answers a search, and in which order,
|
||||
// is what it decides, so the order they are offered and tried in belongs to it rather than
|
||||
@@ -185,7 +187,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
|
||||
public string GetDisplayName() => TB("Web Search");
|
||||
|
||||
public string GetDescription() => TB("Search the web with one of the configured search services and retrieve the readable content of the best matching pages.");
|
||||
public string GetDescription() => TA("Search the web with one of the configured search services and retrieve the readable content of the best matching pages.");
|
||||
|
||||
public string GetSettingsGroupLabel(string groupKey) => this.FindBackend(groupKey)?.GetSettingsGroupLabel() ?? groupKey;
|
||||
|
||||
@@ -221,9 +223,9 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
|
||||
return fieldName switch
|
||||
{
|
||||
BACKEND_STRATEGY_SETTING => TB("What to do with the search services you configured. Asking them one after another moves on to the next one whenever the one before it found nothing, which is the sensible choice for almost everyone. Asking all of them at once combines their results and uses one request of every service for each search, which finds more but spends your free requests several times as fast. When this is not set, the services are asked one after another."),
|
||||
PRIMARY_BACKEND_SETTING => TB("Which search service to ask first, and the only one asked when you chose to use just the preferred one. When this is not set, the services are asked in a fixed order."),
|
||||
DEFAULT_LANGUAGE_SETTING => TB("The language to search in when the AI model does not ask for a specific one. This is required: without a language, many search engines return no results at all, and the search would come back empty without telling you why. Choose 'Any language' if you do not want to restrict the results."),
|
||||
BACKEND_STRATEGY_SETTING => TA("What to do with the search services you configured. Asking them one after another moves on to the next one whenever the one before it found nothing, which is the sensible choice for almost everyone. Asking all of them at once combines their results and uses one request of every service for each search, which finds more but spends your free requests several times as fast. When this is not set, the services are asked one after another."),
|
||||
PRIMARY_BACKEND_SETTING => TA("Which search service to ask first, and the only one asked when you chose to use just the preferred one. When this is not set, the services are asked in a fixed order."),
|
||||
DEFAULT_LANGUAGE_SETTING => TA("The language to search in when the AI model does not ask for a specific one. This is required: without a language, many search engines return no results at all, and the search would come back empty without telling you why. Choose 'Any language' if you do not want to restrict the results."),
|
||||
DEFAULT_SAFE_SEARCH_SETTING => TB("Optional safe search policy sent to the search service when configured."),
|
||||
MAX_RESULTS_SETTING => TB("Optional default maximum number of results returned to the model when the model does not provide a limit."),
|
||||
SEARCH_TIMEOUT_SECONDS_SETTING => TB("Optional HTTP timeout for the search request in seconds."),
|
||||
@@ -302,7 +304,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
{
|
||||
var positiveIntegerErrorFormat = TB("The setting '{0}' must be a positive integer.");
|
||||
var maximumErrorFormat = TB("The setting '{0}' must be less than or equal to {1}.");
|
||||
var invalidOptionErrorFormat = TB("The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values.");
|
||||
var invalidOptionErrorFormat = TA("The setting '{0}' holds the value '{1}', which is not one of the available options. Please choose one of the offered values.");
|
||||
|
||||
//
|
||||
// No backend field is required in the schema, because requiring one would mean every
|
||||
@@ -315,7 +317,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = TB("Please configure at least one search service for the web search."),
|
||||
Message = TA("Please configure at least one search service for the web search."),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -362,7 +364,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = string.Format(TB("The preferred search service {0} is not configured. Please configure it, or choose one of the services you did configure."), primaryBackend.Value.Name),
|
||||
Message = string.Format(TA("The preferred search service {0} is not configured. Please configure it, or choose one of the services you did configure."), primaryBackend.Value.Name),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -371,7 +373,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = TB("Please choose the preferred search service, or let the services be used one after another."),
|
||||
Message = TA("Please choose the preferred search service, or let the services be used one after another."),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -412,7 +414,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = TB("None of the configured search services can filter explicit results, but a safe search policy is configured. Please configure a search service that can filter, or set the safe search policy to off."),
|
||||
Message = TA("None of the configured search services can filter explicit results, but a safe search policy is configured. Please configure a search service that can filter, or set the safe search policy to off."),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -429,7 +431,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
|
||||
{
|
||||
IsConfigured = false,
|
||||
Message = string.Format(TB("The preferred search service {0} cannot filter explicit results, but a safe search policy is configured and it is the only service that would be used. Please choose another service, let the services be used one after another, or set the safe search policy to off."), chosenBackend.Value.Name),
|
||||
Message = string.Format(TA("The preferred search service {0} cannot filter explicit results, but a safe search policy is configured and it is the only service that would be used. Please choose another service, let the services be used one after another, or set the safe search policy to off."), chosenBackend.Value.Name),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ public sealed class DataSourceValidation
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(DataSourceValidation).Namespace, nameof(DataSourceValidation));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(DataSourceValidation).Namespace, nameof(DataSourceValidation));
|
||||
|
||||
public static bool IsNameValid(string name) => !string.IsNullOrWhiteSpace(name) && name.Length <= MAX_NAME_LENGTH && !name.Any(char.IsControl);
|
||||
|
||||
public Func<string> GetSecretStorageIssue { get; init; } = () => string.Empty;
|
||||
@@ -48,7 +50,7 @@ public sealed class DataSourceValidation
|
||||
public string? ValidatingHostname(string hostname)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(hostname))
|
||||
return TB("Please enter a hostname, e.g., http://localhost");
|
||||
return TA("Please enter a hostname, e.g., http://localhost");
|
||||
|
||||
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
return TB("The hostname must start with either http:// or https://");
|
||||
@@ -70,14 +72,14 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateSecurityPolicy(DataSourceSecurity securityPolicy)
|
||||
{
|
||||
if(securityPolicy is DataSourceSecurity.NOT_SPECIFIED)
|
||||
return TB("Please select your security policy.");
|
||||
return TA("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 TB("This data source can only be used with a self-hosted LLM provider. Please change the security policy.");
|
||||
return TA("This data source can only be used with a self-hosted LLM provider. Please change the security policy.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -106,10 +108,10 @@ public sealed class DataSourceValidation
|
||||
if (string.IsNullOrWhiteSpace(secret))
|
||||
return authMethod switch
|
||||
{
|
||||
AuthMethod.TOKEN => TB("Please enter your secure access token."),
|
||||
AuthMethod.USERNAME_PASSWORD => TB("Please enter your password."),
|
||||
AuthMethod.TOKEN => TA("Please enter your secure access token."),
|
||||
AuthMethod.USERNAME_PASSWORD => TA("Please enter your password."),
|
||||
|
||||
_ => TB("Please enter the secret necessary for authentication.")
|
||||
_ => TA("Please enter the secret necessary for authentication.")
|
||||
};
|
||||
|
||||
return null;
|
||||
@@ -118,7 +120,7 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateRetrievalProcess(RetrievalInfo retrievalInfo)
|
||||
{
|
||||
if(retrievalInfo == default)
|
||||
return TB("Please select one retrieval process.");
|
||||
return TA("Please select one retrieval process.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -136,7 +138,7 @@ public sealed class DataSourceValidation
|
||||
|
||||
var lowerName = dataSourceName.ToLowerInvariant();
|
||||
if(lowerName != this.GetPreviousDataSourceName() && this.GetUsedDataSourceNames().Contains(lowerName))
|
||||
return TB("The name is already used by another data source. Please choose a different name.");
|
||||
return TA("The name is already used by another data source. Please choose a different name.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -144,10 +146,10 @@ public sealed class DataSourceValidation
|
||||
public string? ValidatePath(string path)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(path))
|
||||
return TB("The path must not be empty. Please select a directory.");
|
||||
return TA("The path must not be empty. Please select a directory.");
|
||||
|
||||
if(!Directory.Exists(path))
|
||||
return TB("The path does not exist. Please select a valid directory.");
|
||||
return TA("The path does not exist. Please select a valid directory.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -155,10 +157,10 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateFilePath(string filePath)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(filePath))
|
||||
return TB("The file path must not be empty. Please select a file.");
|
||||
return TA("The file path must not be empty. Please select a file.");
|
||||
|
||||
if(!File.Exists(filePath))
|
||||
return TB("The file does not exist. Please select a valid file.");
|
||||
return TA("The file does not exist. Please select a valid file.");
|
||||
|
||||
if(!FileTypes.IsAllowedPath(filePath, FileTypes.DOCUMENT, FileTypes.AUDIO, FileTypes.VIDEO))
|
||||
return TB("The selected file is not a supported document, readable text file, audio file, or video file.");
|
||||
@@ -171,7 +173,7 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateEmbeddingId(string embeddingId)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(embeddingId))
|
||||
return TB("Please select an embedding provider.");
|
||||
return TA("Please select an embedding provider.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -187,11 +189,11 @@ public sealed class DataSourceValidation
|
||||
// data source keeps pointing at a provider nobody can reach, and nothing of it is embedded:
|
||||
//
|
||||
if(this.GetSelectedEmbeddingProvider() is not { } selectedEmbedding)
|
||||
return TB("The embedding provider of this data source no longer exists. Please select another one.");
|
||||
return TA("The embedding provider of this data source no longer exists. Please select another one.");
|
||||
|
||||
// Saved like this, nothing of the data source would ever be embedded:
|
||||
if(this.GetSettingsManager() is { } settingsManager && settingsManager.IsProviderProhibited(selectedEmbedding.UsedLLMProvider))
|
||||
return TB("The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.");
|
||||
return TA("The use of this embedding provider has been prohibited by your organization. Please select a different embedding provider.");
|
||||
|
||||
return this.ValidateSelectedEmbeddingProviderAccess();
|
||||
}
|
||||
@@ -199,7 +201,7 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateDataSourceConfidenceLevel(ConfidenceLevel confidenceLevel)
|
||||
{
|
||||
if(confidenceLevel is ConfidenceLevel.NONE)
|
||||
return TB("Please select a required provider confidence level.");
|
||||
return TA("Please select a required provider confidence level.");
|
||||
|
||||
return this.ValidateSelectedEmbeddingProviderAccess();
|
||||
}
|
||||
@@ -207,7 +209,7 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateMailboxConfidenceLevel(ConfidenceLevel confidenceLevel)
|
||||
{
|
||||
if(confidenceLevel is ConfidenceLevel.NONE)
|
||||
return TB("Please select a required provider confidence level.");
|
||||
return TA("Please select a required provider confidence level.");
|
||||
|
||||
if(!confidenceLevel.IsAllowedMailboxConfidence)
|
||||
return string.Format(TB("A mailbox requires a provider confidence level from '{0}' to '{1}'."), ConfidenceLevel.VERY_LOW.Name, ConfidenceLevel.HIGH.Name);
|
||||
@@ -218,10 +220,10 @@ public sealed class DataSourceValidation
|
||||
public static string? ValidateMailboxHost(string host)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(host))
|
||||
return TB("Please enter the host of the IMAP server, e.g., imap.example.org.");
|
||||
return TA("Please enter the host of the IMAP server, e.g., imap.example.org.");
|
||||
|
||||
if(Uri.CheckHostName(host.Trim()) is not (UriHostNameType.Dns or UriHostNameType.IPv4 or UriHostNameType.IPv6))
|
||||
return TB("Please enter the host alone, without a protocol, a port, or a path, e.g., imap.example.org.");
|
||||
return TA("Please enter the host alone, without a protocol, a port, or a path, e.g., imap.example.org.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -229,7 +231,7 @@ public sealed class DataSourceValidation
|
||||
public static string? ValidateMailboxTransportSecurity(MailboxTransportSecurity transportSecurity)
|
||||
{
|
||||
if(transportSecurity is MailboxTransportSecurity.UNKNOWN)
|
||||
return TB("Please select how the connection to the server is encrypted.");
|
||||
return TA("Please select how the connection to the server is encrypted.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -249,7 +251,7 @@ public sealed class DataSourceValidation
|
||||
return secretStorageIssue;
|
||||
|
||||
if(string.IsNullOrEmpty(password))
|
||||
return TB("Please enter your password.");
|
||||
return TA("Please enter your password.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -265,10 +267,10 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateUserAcknowledgedCloudEmbedding(bool value)
|
||||
{
|
||||
if(this.GetSelectedCloudTranscription() && !value)
|
||||
return TB("Please acknowledge that you are aware of the cloud transcription implications.");
|
||||
return TA("Please acknowledge that you are aware of the cloud transcription implications.");
|
||||
|
||||
if(this.GetSelectedCloudEmbedding() && !value)
|
||||
return TB("Please acknowledge that you are aware of the cloud embedding implications.");
|
||||
return TA("Please acknowledge that you are aware of the cloud embedding implications.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -276,10 +278,10 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateTestedConnection()
|
||||
{
|
||||
if(!this.GetTestedConnection())
|
||||
return TB("Please test the connection before saving.");
|
||||
return TA("Please test the connection before saving.");
|
||||
|
||||
if(!this.GetTestedConnectionResult())
|
||||
return TB("The connection test failed. Please check the connection settings.");
|
||||
return TA("The connection test failed. Please check the connection settings.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -287,7 +289,7 @@ public sealed class DataSourceValidation
|
||||
public string? ValidateAuthMethod(AuthMethod authMethod)
|
||||
{
|
||||
if(!this.GetAvailableAuthMethods().Contains(authMethod))
|
||||
return TB("Please select one valid authentication method.");
|
||||
return TA("Please select one valid authentication method.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -304,7 +306,7 @@ public sealed class DataSourceValidation
|
||||
return null;
|
||||
|
||||
return string.Format(
|
||||
TB("The selected embedding provider has confidence '{0}', but this data source requires provider confidence '{1}'. Select an embedding provider with equal or higher confidence or lower the required confidence level."),
|
||||
TA("The selected embedding provider has confidence '{0}', but this data source requires provider confidence '{1}'. Select an embedding provider with equal or higher confidence or lower the required confidence level."),
|
||||
selectedEmbedding.GetConfidenceLevel(settingsManager).Name,
|
||||
confidenceLevel.Name);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ public static class FileExtensionValidation
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileExtensionValidation).Namespace, nameof(FileExtensionValidation));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(FileExtensionValidation).Namespace, nameof(FileExtensionValidation));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the use cases for file extension validation.
|
||||
/// </summary>
|
||||
@@ -46,7 +48,7 @@ public static class FileExtensionValidation
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(
|
||||
AppIcons.DOCUMENT,
|
||||
TB("This file format is not supported. Please convert the .doc file to .docx (e.g. with Microsoft Word).")));
|
||||
TA("This file format is not supported. Please convert the .doc file to .docx (e.g. with Microsoft Word).")));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,7 +79,7 @@ public static class FileExtensionValidation
|
||||
case UseCase.ATTACHING_CONTENT when !FileTypes.IsAllowedPath(filePath, FileTypes.MODEL_IMAGE):
|
||||
await MessageBus.INSTANCE.SendWarning(new(
|
||||
AppIcons.IMAGE_NOT_SUPPORTED,
|
||||
string.Format(TB("AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG."), Path.GetExtension(filePath).TrimStart('.').ToUpperInvariant())));
|
||||
string.Format(TA("AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG."), Path.GetExtension(filePath).TrimStart('.').ToUpperInvariant())));
|
||||
return false;
|
||||
|
||||
// In this use case, we don't validate the provider capabilities:
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace AIStudio.Tools.Validation;
|
||||
public sealed class ProviderValidation
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderValidation).Namespace, nameof(ProviderValidation));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(ProviderValidation).Namespace, nameof(ProviderValidation));
|
||||
|
||||
public Func<LLMProviders> GetProvider { get; init; } = () => LLMProviders.NONE;
|
||||
|
||||
@@ -36,7 +38,7 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(hostname))
|
||||
return TB("Please enter a hostname, e.g., http://localhost:1234");
|
||||
return TA("Please enter a hostname, e.g., http://localhost:1234");
|
||||
|
||||
if(!hostname.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
|
||||
return TB("The hostname must start with either http:// or https://");
|
||||
@@ -61,7 +63,7 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(apiKey))
|
||||
return TB("Please enter an API key.");
|
||||
return TA("Please enter an API key.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public sealed class ProviderValidation
|
||||
public string? ValidatingInstanceName(string instanceName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(instanceName))
|
||||
return TB("Please enter an instance name.");
|
||||
return TA("Please enter an instance name.");
|
||||
|
||||
if (instanceName.Length > 40)
|
||||
return TB("The instance name must not exceed 40 characters.");
|
||||
@@ -102,7 +104,7 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if (model == default)
|
||||
return TB("Please select a model.");
|
||||
return TA("Please select a model.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -110,7 +112,7 @@ public sealed class ProviderValidation
|
||||
public string? ValidatingProvider(LLMProviders llmProvider)
|
||||
{
|
||||
if (llmProvider == LLMProviders.NONE)
|
||||
return TB("Please select a provider.");
|
||||
return TA("Please select a provider.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -121,7 +123,7 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if (host == Host.NONE)
|
||||
return TB("Please select a host.");
|
||||
return TA("Please select a host.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -132,7 +134,7 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if (!inferenceProvider.SupportsChat)
|
||||
return TB("Please select an Hugging Face inference provider.");
|
||||
return TA("Please select an Hugging Face inference provider.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -163,10 +165,10 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if (inferenceProvider is HFInferenceProvider.NONE)
|
||||
return TB("Please select an Hugging Face inference provider.");
|
||||
return TA("Please select an Hugging Face inference provider.");
|
||||
|
||||
if (!inferenceProvider.SupportsEmbeddings)
|
||||
return TB("This Hugging Face inference provider does not create embeddings. Please select another one.");
|
||||
return TA("This Hugging Face inference provider does not create embeddings. Please select another one.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -186,10 +188,10 @@ public sealed class ProviderValidation
|
||||
return null;
|
||||
|
||||
if (inferenceProvider is HFInferenceProvider.NONE)
|
||||
return TB("Please select an Hugging Face inference provider.");
|
||||
return TA("Please select an Hugging Face inference provider.");
|
||||
|
||||
if (!inferenceProvider.SupportsTranscription)
|
||||
return TB("This Hugging Face inference provider does not transcribe audio. Please select another one.");
|
||||
return TA("This Hugging Face inference provider does not transcribe audio. Please select another one.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ public static partial class WorkspaceBehaviour
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
|
||||
|
||||
private static string TA(string fallbackEN) => I18N.I.TA(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
|
||||
|
||||
public static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
@@ -972,13 +974,13 @@ public static partial class WorkspaceBehaviour
|
||||
var sourceName = string.IsNullOrWhiteSpace(sourceChat.Name) ? TB("Unnamed chat") : sourceChat.Name;
|
||||
var dialogParameters = new DialogParameters<SingleInputDialog>
|
||||
{
|
||||
{ x => x.Message, string.Format(TB("Please enter a name for the copy of your chat '{0}':"), sourceName) },
|
||||
{ x => x.Message, string.Format(TA("Please enter a name for the copy of your chat '{0}':"), sourceName) },
|
||||
{ x => x.InputHeaderText, TB("Chat Name") },
|
||||
{ x => x.UserInput, string.Format(TB("Copy of {0}"), sourceName) },
|
||||
{ x => x.ConfirmText, TB("Copy") },
|
||||
{ x => x.ConfirmColor, Color.Info },
|
||||
{ x => x.AllowEmptyInput, false },
|
||||
{ x => x.EmptyInputErrorMessage, TB("Please enter a chat name.") },
|
||||
{ x => x.EmptyInputErrorMessage, TA("Please enter a chat name.") },
|
||||
};
|
||||
|
||||
var dialogReference = await dialogService.ShowAsync<SingleInputDialog>(TB("Copy Chat"), dialogParameters, Dialogs.DialogOptions.FULLSCREEN);
|
||||
@@ -1482,8 +1484,8 @@ public static partial class WorkspaceBehaviour
|
||||
{
|
||||
x => x.Message, (chat.WorkspaceId == Guid.Empty) switch
|
||||
{
|
||||
true => string.Format(TB("Are you sure you want to delete the temporary chat '{0}'?"), chat.Name),
|
||||
false => string.Format(TB("Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"), chat.Name, workspaceName),
|
||||
true => string.Format(TA("Are you sure you want to delete the temporary chat '{0}'?"), chat.Name),
|
||||
false => string.Format(TA("Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"), chat.Name, workspaceName),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user