mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 22:47:21 +00:00
Turned getter-like extension methods into extension properties (#1157)
This commit is contained in:
1 parent
0375318b1b
commit
f649febf88
286 files changed
+1379
-1109
No files matched your search
@@ -38,7 +38,7 @@ public static class AppUserAgent
|
||||
/// <param name="settingsManager">The settings, which say whether the user shares the feature usage.</param>
|
||||
/// <returns>True when the provider runs on a self-hosted server and sharing is switched on; otherwise, false.</returns>
|
||||
public static bool IsAllowedFor(LLMProviders provider, SettingsManager settingsManager) =>
|
||||
provider.RunsOnSelfHostedServer() && settingsManager.ConfigurationData.App.ShareFeatureUsageWithSelfHostedServerOperators;
|
||||
provider.RunsOnSelfHostedServer && settingsManager.ConfigurationData.App.ShareFeatureUsageWithSelfHostedServerOperators;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the User-Agent as default header of the given HTTP client.
|
||||
@@ -109,7 +109,7 @@ public static class AppUserAgent
|
||||
|
||||
private static string CreateBase()
|
||||
{
|
||||
var rid = RIDExtensions.GetCurrentRID().AsMicrosoftRid();
|
||||
var rid = RID.Current.MicrosoftRid;
|
||||
return string.IsNullOrWhiteSpace(rid)
|
||||
? $"MindWorkAIStudio/{META_DATA.Version}"
|
||||
: $"MindWorkAIStudio/{META_DATA.Version} ({rid})";
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class AssistantCategoryExtensions
|
||||
|
||||
extension(AssistantCategory category)
|
||||
{
|
||||
public string Name() => category switch
|
||||
public string Name => category switch
|
||||
{
|
||||
AssistantCategory.AS_IS => TB("Select the assistant category"),
|
||||
AssistantCategory.GENERAL => TB("General"),
|
||||
@@ -23,12 +23,15 @@ public static class AssistantCategoryExtensions
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
public string NameSelecting()
|
||||
public string NameSelecting
|
||||
{
|
||||
if(category is AssistantCategory.AS_IS)
|
||||
return TB("Please select the assistant category");
|
||||
get
|
||||
{
|
||||
if(category is AssistantCategory.AS_IS)
|
||||
return TB("Please select the assistant category");
|
||||
|
||||
return category.Name();
|
||||
return category.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ public sealed class AssistantLaunchService(SettingsManager settingsManager, Assi
|
||||
public async Task LaunchAsync(Components destination, TextLaunchPayload payload)
|
||||
{
|
||||
var route = destination.GetData().Route;
|
||||
if (destination.HasSingleSessionSlot() && assistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.Component == destination))
|
||||
if (destination.HasSingleSessionSlot && assistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.Component == destination))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.Apps, TB("This assistant is already running. AI Studio opens the running session instead.")));
|
||||
navigationManager.NavigateTo(route);
|
||||
@@ -101,7 +101,7 @@ public sealed class AssistantLaunchService(SettingsManager settingsManager, Assi
|
||||
// Only components with a single session slot may be cleared as a group. The visual briefing
|
||||
// assistant keys its sessions per briefing, so clearing by component would discard the
|
||||
// status of every stored briefing instead of the one we are about to open.
|
||||
if (destination.HasSingleSessionSlot())
|
||||
if (destination.HasSingleSessionSlot)
|
||||
await assistantSessionService.ClearInactiveSessionsForComponentAsync(destination);
|
||||
|
||||
MessageBus.INSTANCE.DeferMessage(null, TextEventFor(destination), payload);
|
||||
@@ -117,7 +117,7 @@ public sealed class AssistantLaunchService(SettingsManager settingsManager, Assi
|
||||
/// </remarks>
|
||||
/// <param name="destination">The component which opens.</param>
|
||||
/// <returns>The text sent, or null when nothing was sent to the component.</returns>
|
||||
public static TextLaunchPayload? TakeTextPayload(Components destination) => destination.AllowSendTo()
|
||||
public static TextLaunchPayload? TakeTextPayload(Components destination) => destination.AllowSendTo
|
||||
? MessageBus.INSTANCE.TakeDeferredMessages<TextLaunchPayload>(TextEventFor(destination)).LastOrDefault()
|
||||
: null;
|
||||
|
||||
@@ -146,11 +146,11 @@ public sealed class AssistantLaunchService(SettingsManager settingsManager, Assi
|
||||
/// <param name="isVisible">Whether the user sees the given assistant at all.</param>
|
||||
/// <returns>The destinations the user can choose from.</returns>
|
||||
internal static IReadOnlyList<Components> SelectDestinations(Components sender, Func<Components, bool> isVisible) => Enum.GetValues<Components>()
|
||||
.Where(destination => destination.AllowSendTo())
|
||||
.Where(destination => destination.AllowSendTo)
|
||||
.Where(destination => destination != sender || destination is Components.CHAT)
|
||||
.Where(isVisible)
|
||||
.OrderBy(destination => destination.Name().Length)
|
||||
.OrderBy(destination => destination.Name.Length)
|
||||
.ToArray();
|
||||
|
||||
private bool IsVisible(Components destination) => settingsManager.IsAssistantVisible(destination, withLogging: false, requiredPreviewFeature: destination.RequiredPreviewFeature());
|
||||
private bool IsVisible(Components destination) => settingsManager.IsAssistantVisible(destination, withLogging: false, requiredPreviewFeature: destination.RequiredPreviewFeature);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public static class AuthMethodsV1Extensions
|
||||
|
||||
extension(AuthMethod authMethod)
|
||||
{
|
||||
public string DisplayName() => authMethod switch
|
||||
public string DisplayName => authMethod switch
|
||||
{
|
||||
AuthMethod.NONE => TB("None"),
|
||||
AuthMethod.USERNAME_PASSWORD => TB("Username & Password"),
|
||||
|
||||
@@ -9,8 +9,8 @@ public static class CodeTokenKindExtensions
|
||||
/// The CSS class which MudBlazor.Markdown gives this kind of part, so that it takes its color from
|
||||
/// the palette of the theme, just as the languages the library highlights itself.
|
||||
/// </summary>
|
||||
/// <returns>The CSS class of the part.</returns>
|
||||
public string ToCssClass() => kind switch
|
||||
/// <value>The CSS class of the part.</value>
|
||||
public string CssClass => kind switch
|
||||
{
|
||||
CodeTokenKind.KEYWORD => "hljs-keyword",
|
||||
CodeTokenKind.TYPE => "hljs-type",
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class CommonLanguageExtensions
|
||||
|
||||
extension(CommonLanguages language)
|
||||
{
|
||||
public string Name() => language switch
|
||||
public string Name => language switch
|
||||
{
|
||||
CommonLanguages.AS_IS => TB("Do not change the language"),
|
||||
|
||||
@@ -27,7 +27,7 @@ public static class CommonLanguageExtensions
|
||||
_ => TB("Other"),
|
||||
};
|
||||
|
||||
public string ToIETFTag() => language switch
|
||||
public string IETFTag => language switch
|
||||
{
|
||||
CommonLanguages.AS_IS => string.Empty,
|
||||
|
||||
@@ -51,14 +51,14 @@ public static class CommonLanguageExtensions
|
||||
CommonLanguages.AS_IS => "Use the language the text is written in for the summary.",
|
||||
CommonLanguages.OTHER => $"Output your summary in {customLanguage}.",
|
||||
|
||||
_ => $"Output your summary in {language.Name()} ({language}).",
|
||||
_ => $"Output your summary in {language.Name} ({language}).",
|
||||
};
|
||||
|
||||
public string PromptTranslation(string customLanguage) => language switch
|
||||
{
|
||||
CommonLanguages.OTHER => $"Translate the source text to {customLanguage}.",
|
||||
|
||||
_ => $"Translate the source text to {language.Name()} ({language}).",
|
||||
_ => $"Translate the source text to {language.Name} ({language}).",
|
||||
};
|
||||
|
||||
public string PromptGeneralPurpose(string customLanguage) => language switch
|
||||
@@ -66,23 +66,29 @@ public static class CommonLanguageExtensions
|
||||
CommonLanguages.AS_IS => "Use the language the user input is written in for the output.",
|
||||
CommonLanguages.OTHER => $"use the language {customLanguage} for your output.",
|
||||
|
||||
_ => $"Use the language {language.Name()} ({language}) for your output.",
|
||||
_ => $"Use the language {language.Name} ({language}) for your output.",
|
||||
};
|
||||
|
||||
public string NameSelecting()
|
||||
public string NameSelecting
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return TB("Please select the target language");
|
||||
|
||||
return language.Name();
|
||||
get
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return TB("Please select the target language");
|
||||
|
||||
return language.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public string NameSelectingOptional()
|
||||
public string NameSelectingOptional
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return TB("Do not specify the language");
|
||||
|
||||
return language.Name();
|
||||
get
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return TB("Do not specify the language");
|
||||
|
||||
return language.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ public static class ComponentsExtensions
|
||||
/// return <see cref="PreviewFeatures.NONE"/>. This is the single place that maps a component to
|
||||
/// its preview feature, so visibility checks never need to special-case one assistant.
|
||||
/// </summary>
|
||||
/// <returns>The required preview feature.</returns>
|
||||
public PreviewFeatures RequiredPreviewFeature() => component switch
|
||||
/// <value>The required preview feature.</value>
|
||||
public PreviewFeatures RequiredPreviewFeature => component switch
|
||||
{
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => PreviewFeatures.PRE_VISUAL_BRIEFING_ASSISTANT_2026,
|
||||
|
||||
@@ -37,8 +37,8 @@ public static class ComponentsExtensions
|
||||
/// component. All must be excluded from the single-slot checks, which is why this is a
|
||||
/// capability and not a component comparison.
|
||||
/// </remarks>
|
||||
/// <returns><c>true</c> when the component owns exactly one session slot.</returns>
|
||||
public bool HasSingleSessionSlot() => component switch
|
||||
/// <value><c>true</c> when the component owns exactly one session slot.</value>
|
||||
public bool HasSingleSessionSlot => component switch
|
||||
{
|
||||
Components.CHAT or Components.DYNAMIC_ASSISTANT => false,
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => false,
|
||||
@@ -49,15 +49,15 @@ public static class ComponentsExtensions
|
||||
/// <summary>
|
||||
/// Gets the kind of media-import owner a component creates for its attachments.
|
||||
/// </summary>
|
||||
/// <returns>The media-import owner kind.</returns>
|
||||
public MediaImportOwnerKind MediaOwnerKind() => component switch
|
||||
/// <value>The media-import owner kind.</value>
|
||||
public MediaImportOwnerKind MediaOwnerKind => component switch
|
||||
{
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => MediaImportOwnerKind.VISUAL_BRIEFING,
|
||||
|
||||
_ => MediaImportOwnerKind.ASSISTANT,
|
||||
};
|
||||
|
||||
public bool AllowSendTo() => component switch
|
||||
public bool AllowSendTo => component switch
|
||||
{
|
||||
Components.NONE => false,
|
||||
Components.DYNAMIC_ASSISTANT => false,
|
||||
@@ -81,7 +81,7 @@ public static class ComponentsExtensions
|
||||
_ => true,
|
||||
};
|
||||
|
||||
public string Name() => component switch
|
||||
public string Name => component switch
|
||||
{
|
||||
Components.GRAMMAR_SPELLING_ASSISTANT => TB("Grammar & Spelling Assistant"),
|
||||
Components.TEXT_SUMMARIZER_ASSISTANT => TB("Text Summarizer Assistant"),
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class ConfidenceSchemesExtensions
|
||||
|
||||
extension(ConfidenceSchemes scheme)
|
||||
{
|
||||
public string GetListDescription() => scheme switch
|
||||
public string ListDescription => scheme switch
|
||||
{
|
||||
ConfidenceSchemes.TRUST_ALL => TB("Trust all LLM providers"),
|
||||
ConfidenceSchemes.TRUST_USA_EUROPE => TB("Trust LLM providers from the USA and Europe"),
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class ProviderTypeExtensions
|
||||
|
||||
extension(ProviderType providerType)
|
||||
{
|
||||
public string Explain() => providerType switch
|
||||
public string Explanation => providerType switch
|
||||
{
|
||||
ProviderType.NONE => TB("The related data is not allowed to be sent to any LLM provider. This means that this data source cannot be used at the moment."),
|
||||
ProviderType.ANY => TB("The related data can be sent to any provider, regardless of where it is hosted (cloud or self-hosted)."),
|
||||
|
||||
@@ -23,43 +23,49 @@ public static class FileExportFormatExtensions
|
||||
private static readonly Encoding WITH_BYTE_ORDER_MARK = new UTF8Encoding(true);
|
||||
private static readonly Encoding WITHOUT_BYTE_ORDER_MARK = new UTF8Encoding(false);
|
||||
|
||||
/// <summary>
|
||||
/// The formats which lay the text out as a document you would hand to somebody, in the order
|
||||
/// the export menu shows them.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<FileExportFormat> DOCUMENT_FORMATS =
|
||||
private static readonly IReadOnlyList<FileExportFormat> DOCUMENT_FORMATS =
|
||||
[
|
||||
FileExportFormat.MICROSOFT_WORD,
|
||||
FileExportFormat.OPEN_DOCUMENT_TEXT,
|
||||
FileExportFormat.LATEX,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The formats which keep the text as text, in the order the export menu shows them.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<FileExportFormat> TEXT_FORMATS =
|
||||
private static readonly IReadOnlyList<FileExportFormat> TEXT_FORMATS =
|
||||
[
|
||||
FileExportFormat.MARKDOWN,
|
||||
FileExportFormat.HTML,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Every format an entire answer can be written as.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tabular formats are missing on purpose: they hold one table out of an answer, never the
|
||||
/// answer itself. Whoever offers a table adds them.
|
||||
/// </remarks>
|
||||
public static readonly IReadOnlyList<FileExportFormat> ANSWER_FORMATS = [..DOCUMENT_FORMATS, ..TEXT_FORMATS];
|
||||
private static readonly IReadOnlyList<FileExportFormat> ANSWER_FORMATS = [..DOCUMENT_FORMATS, ..TEXT_FORMATS];
|
||||
|
||||
/// <param name="format">The format.</param>
|
||||
extension(FileExportFormat format)
|
||||
{
|
||||
/// <summary>
|
||||
/// The formats which lay the text out as a document you would hand to somebody, in the order
|
||||
/// the export menu shows them.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<FileExportFormat> DocumentFormats => DOCUMENT_FORMATS;
|
||||
|
||||
/// <summary>
|
||||
/// The formats which keep the text as text, in the order the export menu shows them.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<FileExportFormat> TextFormats => TEXT_FORMATS;
|
||||
|
||||
/// <summary>
|
||||
/// Every format an entire answer can be written as.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tabular formats are missing on purpose: they hold one table out of an answer, never the
|
||||
/// answer itself. Whoever offers a table adds them.
|
||||
/// </remarks>
|
||||
public static IReadOnlyList<FileExportFormat> AnswerFormats => ANSWER_FORMATS;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the format as shown to the user.
|
||||
/// </summary>
|
||||
/// <returns>The name of the format.</returns>
|
||||
public string ToName() => format switch
|
||||
/// <value>The name of the format.</value>
|
||||
public string Name => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD => TB("Microsoft Word (.docx)"),
|
||||
FileExportFormat.OPEN_DOCUMENT_TEXT => TB("OpenDocument Text (.odt), e.g. LibreOffice"),
|
||||
@@ -76,8 +82,8 @@ public static class FileExportFormatExtensions
|
||||
/// <summary>
|
||||
/// Returns the icon of the format.
|
||||
/// </summary>
|
||||
/// <returns>The icon of the format.</returns>
|
||||
public string ToIcon() => format switch
|
||||
/// <value>The icon of the format.</value>
|
||||
public string Icon => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD => Icons.Custom.FileFormats.FileWord,
|
||||
FileExportFormat.OPEN_DOCUMENT_TEXT => Icons.Custom.FileFormats.FileDocument,
|
||||
@@ -93,9 +99,9 @@ public static class FileExportFormatExtensions
|
||||
/// <summary>
|
||||
/// Returns the file extension of the format, including the leading dot.
|
||||
/// </summary>
|
||||
/// <returns>The file extension, or an empty string when the format writes no file or, as code
|
||||
/// does, takes the extension from its language.</returns>
|
||||
public string ToFileExtension() => format switch
|
||||
/// <value>The file extension, or an empty string when the format writes no file or, as code
|
||||
/// does, takes the extension from its language.</value>
|
||||
public string FileExtension => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD => ".docx",
|
||||
FileExportFormat.OPEN_DOCUMENT_TEXT => ".odt",
|
||||
@@ -111,8 +117,8 @@ public static class FileExportFormatExtensions
|
||||
/// <summary>
|
||||
/// Determines whether the format holds a table rather than a text.
|
||||
/// </summary>
|
||||
/// <returns>True for the formats a spreadsheet opens.</returns>
|
||||
public bool IsTabular() => format is FileExportFormat.CSV or FileExportFormat.TSV;
|
||||
/// <value>True for the formats a spreadsheet opens.</value>
|
||||
public bool IsTabular => format is FileExportFormat.CSV or FileExportFormat.TSV;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a file of the format is plain text, which AI Studio writes as it is.
|
||||
@@ -124,8 +130,8 @@ public static class FileExportFormatExtensions
|
||||
/// list is spelled out on purpose, so a format added later counts as plain text only once
|
||||
/// somebody says so.
|
||||
/// </remarks>
|
||||
/// <returns>True, when a text written as it is makes a valid file of the format.</returns>
|
||||
public bool IsPlainText() => format is FileExportFormat.LATEX or FileExportFormat.MARKDOWN or FileExportFormat.HTML or FileExportFormat.CSV or FileExportFormat.TSV or FileExportFormat.SOURCE_CODE;
|
||||
/// <value>True, when a text written as it is makes a valid file of the format.</value>
|
||||
public bool IsPlainText => format is FileExportFormat.LATEX or FileExportFormat.MARKDOWN or FileExportFormat.HTML or FileExportFormat.CSV or FileExportFormat.TSV or FileExportFormat.SOURCE_CODE;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file name the save dialog starts with.
|
||||
@@ -138,14 +144,14 @@ public static class FileExportFormatExtensions
|
||||
/// <param name="name">What the file is about, for example the heading above a table. Anything
|
||||
/// a file name cannot hold is removed. Null or blank falls back to a generic name.</param>
|
||||
/// <returns>The suggested file name, including its extension.</returns>
|
||||
public string ToSuggestedFileName(string? name = null) => SuggestFileName(name, format.ToFileExtension());
|
||||
public string ToSuggestedFileName(string? name = null) => SuggestFileName(name, format.FileExtension);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the filter which the save dialog offers for the format.
|
||||
/// </summary>
|
||||
/// <returns>The filter, or null when the format cannot be written or, as code does, takes the
|
||||
/// filter from its language.</returns>
|
||||
public FileTypeFilter? ToFileTypeFilter() => format switch
|
||||
/// <value>The filter, or null when the format cannot be written or, as code does, takes the
|
||||
/// filter from its language.</value>
|
||||
public FileTypeFilter? FileTypeFilter => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD => FileTypes.MS_WORD,
|
||||
FileExportFormat.OPEN_DOCUMENT_TEXT => FileTypes.ODT,
|
||||
@@ -167,8 +173,8 @@ public static class FileExportFormatExtensions
|
||||
/// turns every umlaut into garbage. Text files get none: editors, compilers, and LaTeX have
|
||||
/// no use for it and some of them stumble over it.
|
||||
/// </remarks>
|
||||
/// <returns>The encoding to write the file with.</returns>
|
||||
public Encoding ToFileEncoding() => format switch
|
||||
/// <value>The encoding to write the file with.</value>
|
||||
public Encoding FileEncoding => format switch
|
||||
{
|
||||
FileExportFormat.CSV or FileExportFormat.TSV => WITH_BYTE_ORDER_MARK,
|
||||
|
||||
@@ -221,8 +227,8 @@ public static class FileExportFormatExtensions
|
||||
/// which page it was stays in the title of the source. Verified on 2026-09-15 with LibreOffice
|
||||
/// on an exported .odt. A format added later keeps the page unless it is known to stumble too.
|
||||
/// </remarks>
|
||||
/// <returns>True, when a reader of this format follows such a link.</returns>
|
||||
public bool FollowsPageAnchors() => format switch
|
||||
/// <value>True, when a reader of this format follows such a link.</value>
|
||||
public bool FollowsPageAnchors => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD or FileExportFormat.OPEN_DOCUMENT_TEXT => false,
|
||||
|
||||
@@ -238,14 +244,14 @@ public static class FileExportFormatExtensions
|
||||
/// no such title, and handed one anyway, they keep it as a document property nobody asked for;
|
||||
/// verified with Pandoc 3.8.3 on 2026-09-23. LaTeX ignores it.
|
||||
/// </remarks>
|
||||
/// <returns>True, when a document of this format needs a title besides its content.</returns>
|
||||
public bool NeedsPageTitle() => format is FileExportFormat.HTML;
|
||||
/// <value>True, when a document of this format needs a title besides its content.</value>
|
||||
public bool NeedsPageTitle => format is FileExportFormat.HTML;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name Pandoc knows the format by.
|
||||
/// </summary>
|
||||
/// <returns>The Pandoc output format, or an empty string when AI Studio writes the file itself.</returns>
|
||||
public string ToPandocOutputFormat() => format switch
|
||||
/// <value>The Pandoc output format, or an empty string when AI Studio writes the file itself.</value>
|
||||
public string PandocOutputFormat => format switch
|
||||
{
|
||||
FileExportFormat.MICROSOFT_WORD => "docx",
|
||||
FileExportFormat.OPEN_DOCUMENT_TEXT => "odt",
|
||||
@@ -258,39 +264,39 @@ public static class FileExportFormatExtensions
|
||||
/// <summary>
|
||||
/// Determines whether writing the format needs Pandoc.
|
||||
/// </summary>
|
||||
/// <returns>True, when Pandoc converts the message; false, when AI Studio writes the file itself.</returns>
|
||||
public bool UsesPandoc() => !string.IsNullOrWhiteSpace(format.ToPandocOutputFormat());
|
||||
}
|
||||
/// <value>True, when Pandoc converts the message; false, when AI Studio writes the file itself.</value>
|
||||
public bool UsesPandoc => !string.IsNullOrWhiteSpace(format.PandocOutputFormat);
|
||||
|
||||
/// <summary>
|
||||
/// Reads which format a model means when it names a language behind the opening fence of a
|
||||
/// code block.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A model which answers with a finished file puts it into a code block and names its language,
|
||||
/// as in ```html. Models do not agree on the spelling, so we accept the usual names of a format
|
||||
/// in any case. Only formats which are plain text appear here: a code block holds text, never
|
||||
/// a Word document. A block in any other language, or in none, is code, see
|
||||
/// SourceCodeLanguages.FromCodeFenceLanguage.
|
||||
/// </remarks>
|
||||
/// <param name="language">The language behind the opening fence, as Markdig reads it into
|
||||
/// FencedCodeBlock.Info.</param>
|
||||
/// <param name="format">The format the language names, or NONE when it names none of ours.</param>
|
||||
/// <returns>True, when the language names a format AI Studio writes.</returns>
|
||||
public static bool TryFromCodeFenceLanguage(string? language, out FileExportFormat format)
|
||||
{
|
||||
format = language?.Trim().ToLowerInvariant() switch
|
||||
/// <summary>
|
||||
/// Reads which format a model means when it names a language behind the opening fence of a
|
||||
/// code block.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A model which answers with a finished file puts it into a code block and names its language,
|
||||
/// as in ```html. Models do not agree on the spelling, so we accept the usual names of a format
|
||||
/// in any case. Only formats which are plain text appear here: a code block holds text, never
|
||||
/// a Word document. A block in any other language, or in none, is code, see
|
||||
/// SourceCodeLanguages.FromCodeFenceLanguage.
|
||||
/// </remarks>
|
||||
/// <param name="language">The language behind the opening fence, as Markdig reads it into
|
||||
/// FencedCodeBlock.Info.</param>
|
||||
/// <param name="namedFormat">The format the language names, or NONE when it names none of ours.</param>
|
||||
/// <returns>True, when the language names a format AI Studio writes.</returns>
|
||||
public static bool TryFromCodeFenceLanguage(string? language, out FileExportFormat namedFormat)
|
||||
{
|
||||
"html" => FileExportFormat.HTML,
|
||||
"latex" or "tex" => FileExportFormat.LATEX,
|
||||
"markdown" or "md" => FileExportFormat.MARKDOWN,
|
||||
"csv" => FileExportFormat.CSV,
|
||||
"tsv" => FileExportFormat.TSV,
|
||||
namedFormat = language?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"html" => FileExportFormat.HTML,
|
||||
"latex" or "tex" => FileExportFormat.LATEX,
|
||||
"markdown" or "md" => FileExportFormat.MARKDOWN,
|
||||
"csv" => FileExportFormat.CSV,
|
||||
"tsv" => FileExportFormat.TSV,
|
||||
|
||||
_ => FileExportFormat.NONE,
|
||||
};
|
||||
_ => FileExportFormat.NONE,
|
||||
};
|
||||
|
||||
return format is not FileExportFormat.NONE;
|
||||
return namedFormat is not FileExportFormat.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,8 +22,8 @@ internal static class FileExtractionErrorCodeExtensions
|
||||
/// Gets a value indicating whether reading the file again will fail again, as long as the file
|
||||
/// itself does not change.
|
||||
/// </summary>
|
||||
/// <returns>True, when the reason lies in the file itself.</returns>
|
||||
internal bool IsPermanentIndexingFailure() => code switch
|
||||
/// <value>True, when the reason lies in the file itself.</value>
|
||||
internal bool IsPermanentIndexingFailure => code switch
|
||||
{
|
||||
//
|
||||
// The reason lies in the file. Reading it again without changing it produces the same
|
||||
@@ -60,8 +60,8 @@ internal static class FileExtractionErrorCodeExtensions
|
||||
/// Used to group the files of an indexing run by what happened to them: nine hundred entries
|
||||
/// which all say the same sentence are one cause, not nine hundred.
|
||||
/// </remarks>
|
||||
/// <returns>The localized name of the cause, or an empty text when the code has none.</returns>
|
||||
internal string GetIndexingCauseName() => code switch
|
||||
/// <value>The localized name of the cause, or an empty text when the code has none.</value>
|
||||
internal string IndexingCauseName => code switch
|
||||
{
|
||||
FileExtractionErrorCode.NO_TEXT_EXTRACTED => TB("No readable text"),
|
||||
FileExtractionErrorCode.NO_CONTENT => TB("No content"),
|
||||
|
||||
@@ -11,7 +11,7 @@ public static class MailboxConnectionFailureExtensions
|
||||
/// <summary>
|
||||
/// What went wrong, and what the user can do about it.
|
||||
/// </summary>
|
||||
public string GetDescription() => failure switch
|
||||
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."),
|
||||
|
||||
@@ -11,7 +11,7 @@ public static class MailboxProviderTemplateExtensions
|
||||
/// <summary>
|
||||
/// What the user enters as the username at this provider.
|
||||
/// </summary>
|
||||
public string GetDescription() => format switch
|
||||
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."),
|
||||
|
||||
@@ -16,7 +16,7 @@ public static class MediaImportOwnerKindExtensions
|
||||
/// the file, nor deliver the completed transcripts back into its own list afterwards, because
|
||||
/// the owner already holds them. All other owners rely on that delivery instead.
|
||||
/// </remarks>
|
||||
/// <returns><c>true</c> when the owner persists its own sources.</returns>
|
||||
public bool PersistsOwnSources() => kind is MediaImportOwnerKind.VISUAL_BRIEFING;
|
||||
/// <value><c>true</c> when the owner persists its own sources.</value>
|
||||
public bool PersistsOwnSources => kind is MediaImportOwnerKind.VISUAL_BRIEFING;
|
||||
}
|
||||
}
|
||||
@@ -32,24 +32,24 @@ public sealed record MessageFile(int Ordinal, int Position, string Caption, File
|
||||
/// Returns the name of the file's type as shown to the user, for example "Webpage (.html)" or
|
||||
/// "Python (.py)".
|
||||
/// </summary>
|
||||
public string ToName() => this.IsSourceCode ? this.Language.ToName() : this.Format.ToName();
|
||||
public string ToName() => this.IsSourceCode ? this.Language.ToName() : this.Format.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the icon of the file's type.
|
||||
/// </summary>
|
||||
public string ToIcon() => this.Format.ToIcon();
|
||||
public string ToIcon() => this.Format.Icon;
|
||||
|
||||
/// <summary>
|
||||
/// Returns what tells a file of this type apart: its extension, for example ".html", or the
|
||||
/// fixed name a tool looks for, such as Dockerfile.
|
||||
/// </summary>
|
||||
public string ToFileExtensionOrName() => this.IsSourceCode ? this.Language.ToFileExtensionOrName() : this.Format.ToFileExtension();
|
||||
public string ToFileExtensionOrName() => this.IsSourceCode ? this.Language.ToFileExtensionOrName() : this.Format.FileExtension;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the filter which the save dialog offers for the file.
|
||||
/// </summary>
|
||||
/// <returns>The filter, or null when AI Studio cannot write the file itself.</returns>
|
||||
public FileTypeFilter? ToFileTypeFilter() => this.IsSourceCode ? this.Language.ToFileTypeFilter() : this.Format.ToFileTypeFilter();
|
||||
public FileTypeFilter? ToFileTypeFilter() => this.IsSourceCode ? this.Language.ToFileTypeFilter() : this.Format.FileTypeFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file name the save dialog starts with.
|
||||
@@ -61,5 +61,5 @@ public sealed record MessageFile(int Ordinal, int Position, string Caption, File
|
||||
/// <summary>
|
||||
/// Returns the encoding the file gets written with.
|
||||
/// </summary>
|
||||
public Encoding ToFileEncoding() => this.IsSourceCode ? this.Language.ToFileEncoding() : this.Format.ToFileEncoding();
|
||||
public Encoding ToFileEncoding() => this.IsSourceCode ? this.Language.ToFileEncoding() : this.Format.FileEncoding;
|
||||
}
|
||||
@@ -14,12 +14,12 @@ public static class MudThemeExtensions
|
||||
|
||||
public string GetActivityIndicatorColor(SettingsManager settingsManager) => settingsManager.IsDarkMode switch
|
||||
{
|
||||
true => theme.GetActivityIndicatorDarkColor(),
|
||||
false => theme.GetActivityIndicatorLightColor(),
|
||||
true => theme.ActivityIndicatorDarkColor,
|
||||
false => theme.ActivityIndicatorLightColor,
|
||||
};
|
||||
|
||||
public string GetActivityIndicatorLightColor() => theme.PaletteLight.Info.Value;
|
||||
public string ActivityIndicatorLightColor => theme.PaletteLight.Info.Value;
|
||||
|
||||
public string GetActivityIndicatorDarkColor() => theme.PaletteDark.InfoLighten;
|
||||
public string ActivityIndicatorDarkColor => theme.PaletteDark.InfoLighten;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public static partial class Pandoc
|
||||
private static string TB(string fallbackEN) => PluginSystem.I18N.I.T(fallbackEN, typeof(Pandoc).Namespace, nameof(Pandoc));
|
||||
|
||||
// Pandoc is a program of its own, so it has to fit the machine, not the build of AI Studio:
|
||||
private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID();
|
||||
private static readonly RID CPU_ARCHITECTURE = RID.Current;
|
||||
|
||||
private const string DOWNLOAD_URL = "https://github.com/jgm/pandoc/releases/download";
|
||||
private const string LATEST_URL = "https://github.com/jgm/pandoc/releases/latest";
|
||||
@@ -173,7 +173,7 @@ public static partial class Pandoc
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Error, TB("AI Studio couldn't install Pandoc because the archive type is unknown.")));
|
||||
LOG.LogError("Pandoc was not installed, no archive is available for architecture '{Architecture}'.", CPU_ARCHITECTURE.ToUserFriendlyName());
|
||||
LOG.LogError("Pandoc was not installed, no archive is available for architecture '{Architecture}'.", CPU_ARCHITECTURE.UserFriendlyName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ public static partial class Pandoc
|
||||
return $"{baseUri}arm64-macOS.pkg";
|
||||
|
||||
default:
|
||||
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Terminal, string.Format(TB("Installers are not available on {0} systems."), CPU_ARCHITECTURE.ToUserFriendlyName())));
|
||||
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Terminal, string.Format(TB("Installers are not available on {0} systems."), CPU_ARCHITECTURE.UserFriendlyName)));
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public static class PandocExport
|
||||
/// <returns>True, when the document was written.</returns>
|
||||
public static async Task<bool> ConvertAsync(RustService rustService, string markdownText, string targetFilePath, FileExportFormat format, CancellationToken token = default)
|
||||
{
|
||||
if (!format.UsesPandoc())
|
||||
if (!format.UsesPandoc)
|
||||
throw new ArgumentOutOfRangeException(nameof(format), format, "Pandoc cannot write this format.");
|
||||
|
||||
var tempMarkdownFilePath = string.Empty;
|
||||
@@ -49,7 +49,7 @@ public static class PandocExport
|
||||
.Create()
|
||||
.UseStandaloneMode()
|
||||
.WithInputFormat("gfm+emoji+tex_math_dollars")
|
||||
.WithOutputFormat(format.ToPandocOutputFormat())
|
||||
.WithOutputFormat(format.PandocOutputFormat)
|
||||
.WithOutputFile(targetFilePath)
|
||||
.WithInputFile(tempMarkdownFilePath);
|
||||
|
||||
@@ -58,7 +58,7 @@ public static class PandocExport
|
||||
// reaches the page as a string which Pandoc escapes; only a file named true or false
|
||||
// is read as a switch and keeps the temporary name.
|
||||
//
|
||||
if (format.NeedsPageTitle())
|
||||
if (format.NeedsPageTitle)
|
||||
pandocBuilder.AddArgument("-M").AddArgument($"pagetitle={Path.GetFileNameWithoutExtension(targetFilePath)}");
|
||||
|
||||
var pandoc = await pandocBuilder.BuildAsync(rustService);
|
||||
@@ -127,14 +127,14 @@ public static class PandocExport
|
||||
/// <returns>True, when the document was written.</returns>
|
||||
public static async Task<bool> ToDocument(RustService rustService, PandocAvailabilityService pandocAvailability, AIGeneratedContentDisclosureService? disclosureService, string dialogTitle, FileExportFormat format, IContent markdownContent, string? fileName = null)
|
||||
{
|
||||
if (!format.UsesPandoc() || format.ToFileTypeFilter() is not { } fileTypeFilter)
|
||||
if (!format.UsesPandoc || format.FileTypeFilter is not { } fileTypeFilter)
|
||||
throw new ArgumentOutOfRangeException(nameof(format), format, "Pandoc cannot write this format.");
|
||||
|
||||
//
|
||||
// We read the text before we ask for a path: when there is nothing to convert, the user
|
||||
// should learn that right away instead of picking a file first and getting an error afterwards.
|
||||
//
|
||||
if (!markdownContent.TryGetExportMarkdown(out var markdownText, format.FollowsPageAnchors()))
|
||||
if (!markdownContent.TryGetExportMarkdown(out var markdownText, format.FollowsPageAnchors))
|
||||
{
|
||||
LOGGER.LogWarning("Cannot export the content as {ExportFormat}, because it carries no text.", format);
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("Only text messages can be exported.")));
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace AIStudio.Tools;
|
||||
public sealed class PandocProcessBuilder
|
||||
{
|
||||
// Pandoc is a program of its own, so it has to fit the machine, not the build of AI Studio:
|
||||
private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID();
|
||||
private static readonly RID CPU_ARCHITECTURE = RID.Current;
|
||||
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(PandocProcessBuilder));
|
||||
private const string FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY = "/app/plugins/pandoc/bin";
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public static class PlainFileExport
|
||||
.Where(entry => entry.File is not null)
|
||||
.OrderBy(entry => entry.Position)
|
||||
.Select(entry => new MessageFile(
|
||||
entry.File!.Format.IsTabular() ? ++numberOfTables : ++numberOfCodeBlocks,
|
||||
entry.File!.Format.IsTabular ? ++numberOfTables : ++numberOfCodeBlocks,
|
||||
entry.Position,
|
||||
Caption: HeadingAbove(entry.Position) is { Length: > 0 } heading ? heading : entry.File!.Fallback,
|
||||
entry.File!.Format,
|
||||
@@ -153,10 +153,10 @@ public static class PlainFileExport
|
||||
|
||||
var content = block.Lines.ToString();
|
||||
var language = CodeLanguageDetector.FenceLanguageOf(block.Info, content);
|
||||
if (!FileExportFormatExtensions.TryFromCodeFenceLanguage(language, out var format))
|
||||
if (!FileExportFormat.TryFromCodeFenceLanguage(language, out var format))
|
||||
return new(string.Empty, FileExportFormat.SOURCE_CODE, SourceCodeLanguages.FromCodeFenceLanguage(language), content);
|
||||
|
||||
if (!format.IsTabular())
|
||||
if (!format.IsTabular)
|
||||
return new(string.Empty, format, SourceCodeLanguage.NONE, content);
|
||||
|
||||
var blockSeparator = format is FileExportFormat.TSV ? '\t' : ',';
|
||||
@@ -239,10 +239,10 @@ public static class PlainFileExport
|
||||
/// <returns>True, when the file was written.</returns>
|
||||
public static async Task<bool> ToFile(RustService rustService, string dialogTitle, FileExportFormat format, string fileContent, string? fileName = null)
|
||||
{
|
||||
if (!format.IsPlainText() || format.ToFileTypeFilter() is not { } fileTypeFilter)
|
||||
if (!format.IsPlainText || format.FileTypeFilter is not { } fileTypeFilter)
|
||||
throw new ArgumentOutOfRangeException(nameof(format), format, "AI Studio cannot write this format itself.");
|
||||
|
||||
return await WriteFile(rustService, dialogTitle, format, fileTypeFilter, format.ToSuggestedFileName(fileName), format.ToFileEncoding(), fileContent);
|
||||
return await WriteFile(rustService, dialogTitle, format, fileTypeFilter, format.ToSuggestedFileName(fileName), format.FileEncoding, fileContent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -262,7 +262,7 @@ public static class PlainFileExport
|
||||
/// <returns>True, when the file was written.</returns>
|
||||
public static async Task<bool> ToFile(RustService rustService, string dialogTitle, MessageFile file, string fileContent, string? fileName = null)
|
||||
{
|
||||
if (!file.Format.IsPlainText() || file.ToFileTypeFilter() is not { } fileTypeFilter)
|
||||
if (!file.Format.IsPlainText || file.ToFileTypeFilter() is not { } fileTypeFilter)
|
||||
throw new ArgumentOutOfRangeException(nameof(file), file.Format, "AI Studio cannot write this format itself.");
|
||||
|
||||
return await WriteFile(rustService, dialogTitle, file.Format, fileTypeFilter, file.ToSuggestedFileName(fileName), file.ToFileEncoding(), fileContent);
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class AssistantPluginAuditService(AssistantAuditAgent auditAgent)
|
||||
AuditProviderName = provider == Settings.Provider.NONE
|
||||
? string.Empty
|
||||
: provider.InstanceName,
|
||||
Level = AssistantAuditLevelExtensions.Parse(result.Level),
|
||||
Level = AssistantAuditLevel.ParseOrUnknown(result.Level),
|
||||
Summary = result.Summary,
|
||||
Confidence = result.Confidence,
|
||||
PromptPreview = promptPreview,
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ public static class AssistantComponentTypeExtensions
|
||||
|
||||
extension(AssistantComponentType type)
|
||||
{
|
||||
public string GetDisplayName() => type switch
|
||||
public string DisplayName => type switch
|
||||
{
|
||||
AssistantComponentType.FORM => TB("Root"),
|
||||
AssistantComponentType.TEXT_AREA => TB("Text Area"),
|
||||
@@ -36,7 +36,7 @@ public static class AssistantComponentTypeExtensions
|
||||
_ => TB("Unknown Element")
|
||||
};
|
||||
|
||||
public string GetIcon() => type switch
|
||||
public string Icon => type switch
|
||||
{
|
||||
AssistantComponentType.BUTTON => MudBlazor.Icons.Material.Filled.AdsClick,
|
||||
AssistantComponentType.BUTTON_GROUP => MudBlazor.Icons.Material.Filled.LinearScale,
|
||||
|
||||
+18
-18
@@ -105,8 +105,8 @@ public static class PluginAssistantSecurityResolver
|
||||
CanActivatePlugin = true,
|
||||
CanStartAssistant = true,
|
||||
AuditLabel = TB("Safe"),
|
||||
AuditColor = AssistantAuditLevel.SAFE.GetColor(),
|
||||
AuditIcon = AssistantAuditLevel.SAFE.GetIcon(),
|
||||
AuditColor = AssistantAuditLevel.SAFE.Color,
|
||||
AuditIcon = AssistantAuditLevel.SAFE.Icon,
|
||||
AvailabilityLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit: true, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
@@ -153,8 +153,8 @@ public static class PluginAssistantSecurityResolver
|
||||
CanActivatePlugin = !isBlocked,
|
||||
CanStartAssistant = !isBlocked,
|
||||
AuditLabel = TB("Unknown"),
|
||||
AuditColor = AssistantAuditLevel.UNKNOWN.GetColor(),
|
||||
AuditIcon = AssistantAuditLevel.UNKNOWN.GetIcon(),
|
||||
AuditColor = AssistantAuditLevel.UNKNOWN.Color,
|
||||
AuditIcon = AssistantAuditLevel.UNKNOWN.Icon,
|
||||
AvailabilityLabel = GetAvailabilityLabel(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
@@ -192,8 +192,8 @@ public static class PluginAssistantSecurityResolver
|
||||
CanActivatePlugin = !isBlocked,
|
||||
CanStartAssistant = !isBlocked,
|
||||
AuditLabel = TB("Unknown"),
|
||||
AuditColor = AssistantAuditLevel.UNKNOWN.GetColor(),
|
||||
AuditIcon = AssistantAuditLevel.UNKNOWN.GetIcon(),
|
||||
AuditColor = AssistantAuditLevel.UNKNOWN.Color,
|
||||
AuditIcon = AssistantAuditLevel.UNKNOWN.Icon,
|
||||
AvailabilityLabel = GetAvailabilityLabel(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
|
||||
@@ -233,15 +233,15 @@ public static class PluginAssistantSecurityResolver
|
||||
CanOverride = !isBlockedByMinimum,
|
||||
CanActivatePlugin = !isBlockedByMinimum,
|
||||
CanStartAssistant = !isBlockedByMinimum,
|
||||
AuditLabel = auditLevel.GetName(),
|
||||
AuditColor = auditLevel.GetColor(),
|
||||
AuditIcon = auditLevel.GetIcon(),
|
||||
AuditLabel = auditLevel.Name,
|
||||
AuditColor = auditLevel.Color,
|
||||
AuditIcon = auditLevel.Icon,
|
||||
AvailabilityLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
SourceLabel = TB("User Audit"),
|
||||
SourceColor = auditLevel.GetColor(),
|
||||
SourceColor = auditLevel.Color,
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.Verified,
|
||||
BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
Headline = isBlockedByMinimum
|
||||
@@ -250,10 +250,10 @@ public static class PluginAssistantSecurityResolver
|
||||
? TB("This assistant can still be used because audit enforcement is disabled.")
|
||||
: TB("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.GetName(), auditSettings.MinimumLevel.GetName())
|
||||
? 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)
|
||||
: 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.GetName(), auditSettings.MinimumLevel.GetName())
|
||||
: 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.GetName(), auditSettings.MinimumLevel.GetName()),
|
||||
? 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),
|
||||
StatusColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
StatusIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
|
||||
ActionLabel = TB("Open Security Check"),
|
||||
@@ -278,19 +278,19 @@ public static class PluginAssistantSecurityResolver
|
||||
CanOverride = false,
|
||||
CanActivatePlugin = canUsePlugin,
|
||||
CanStartAssistant = canUsePlugin,
|
||||
AuditLabel = auditLevelDefault.GetName(),
|
||||
AuditColor = auditLevelDefault.GetColor(),
|
||||
AuditIcon = auditLevelDefault.GetIcon(),
|
||||
AuditLabel = auditLevelDefault.Name,
|
||||
AuditColor = auditLevelDefault.Color,
|
||||
AuditIcon = auditLevelDefault.Icon,
|
||||
AvailabilityLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityColor = GetAvailabilityColor(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
AvailabilityIcon = GetAvailabilityIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
|
||||
SourceLabel = TB("User Audit"),
|
||||
SourceColor = auditLevelDefault.GetColor(),
|
||||
SourceColor = auditLevelDefault.Color,
|
||||
SourceIcon = MudBlazor.Icons.Material.Filled.Verified,
|
||||
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.GetName()),
|
||||
Description = string.Format(TB("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"),
|
||||
|
||||
@@ -306,6 +306,6 @@ public static class ConfigurationSnippetImport
|
||||
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 string.Format(TB("The pasted configuration is not a valid configuration of the type '{0}'."), type.GetName());
|
||||
return string.Format(TB("The pasted configuration is not a valid configuration of the type '{0}'."), type.Name);
|
||||
}
|
||||
}
|
||||
@@ -51,8 +51,8 @@ public sealed class ConfigurationSnippetParser
|
||||
var section = parser.ReadBracketedString();
|
||||
if (section != expectedSection)
|
||||
throw new FormatException(PluginConfigurationObjectDescriptors.TryGet(section, out var otherDescriptor)
|
||||
? string.Format(TB("The pasted configuration describes the type '{0}', but only the type '{1}' can be imported here."), otherDescriptor.Type.GetName(), type.GetName())
|
||||
: string.Format(TB("The pasted configuration does not describe the type '{0}', which can be imported here."), type.GetName()));
|
||||
? string.Format(TB("The pasted configuration describes the type '{0}', but only the type '{1}' can be imported here."), otherDescriptor.Type.Name, type.Name)
|
||||
: string.Format(TB("The pasted configuration does not describe the type '{0}', which can be imported here."), type.Name));
|
||||
|
||||
parser.Expect('[');
|
||||
parser.Expect('#');
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public readonly record struct InternalPluginData(PluginType Type, Guid Id, string ShortName)
|
||||
{
|
||||
public string ResourcePath => $"{this.Type.GetDirectory()}/{this.ShortName.ToLowerInvariant()}-{this.Id}";
|
||||
public string ResourcePath => $"{this.Type.Directory}/{this.ShortName.ToLowerInvariant()}-{this.Id}";
|
||||
|
||||
public string ResourceName => $"{this.ShortName.ToLowerInvariant()}-{this.Id}";
|
||||
}
|
||||
@@ -6,7 +6,7 @@ public static class PluginCategoryExtensions
|
||||
|
||||
extension(PluginCategory type)
|
||||
{
|
||||
public string GetName() => type switch
|
||||
public string Name => type switch
|
||||
{
|
||||
PluginCategory.NONE => TB("None"),
|
||||
PluginCategory.CORE => TB("AI Studio Core"),
|
||||
|
||||
@@ -489,7 +489,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
|
||||
var resultFileFormatIsValid = ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.ResultFileFormat, this.Id, settingsTable, dryRun, validator: DataBatchProcessing.IsValidResultFileFormat);
|
||||
if (!resultFileFormatIsValid && settingsTable.TryGetValue("DataBatchProcessing.ResultFileFormat", out _))
|
||||
LOG.LogWarning("The Batch Processing result file format configured by plugin {ConfigPluginId} must be one of {ResultFileFormats}.", this.Id, string.Join(", ", FileExportFormatExtensions.ANSWER_FORMATS));
|
||||
LOG.LogWarning("The Batch Processing result file format configured by plugin {ConfigPluginId} must be one of {ResultFileFormats}.", this.Id, string.Join(", ", FileExportFormat.AnswerFormats));
|
||||
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.CsvFileName, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.ResultColumnHeader, this.Id, settingsTable, dryRun);
|
||||
|
||||
@@ -58,13 +58,13 @@ public sealed record PluginConfigurationObject
|
||||
private static string DescribeEndpoint(IConfigurationObject configObject) => configObject switch
|
||||
{
|
||||
Settings.Provider { IsSelfHosted: true } provider => provider.Hostname,
|
||||
Settings.Provider provider => Provider.LLMProvidersExtensions.ToName(provider.UsedLLMProvider),
|
||||
Settings.Provider provider => Provider.LLMProvidersExtensions.ToName(provider.UsedLLMProvider, translate: true),
|
||||
|
||||
EmbeddingProvider { IsSelfHosted: true } embeddingProvider => embeddingProvider.Hostname,
|
||||
EmbeddingProvider embeddingProvider => Provider.LLMProvidersExtensions.ToName(embeddingProvider.UsedLLMProvider),
|
||||
EmbeddingProvider embeddingProvider => Provider.LLMProvidersExtensions.ToName(embeddingProvider.UsedLLMProvider, translate: true),
|
||||
|
||||
TranscriptionProvider { IsSelfHosted: true } transcriptionProvider => transcriptionProvider.Hostname,
|
||||
TranscriptionProvider transcriptionProvider => Provider.LLMProvidersExtensions.ToName(transcriptionProvider.UsedLLMProvider),
|
||||
TranscriptionProvider transcriptionProvider => Provider.LLMProvidersExtensions.ToName(transcriptionProvider.UsedLLMProvider, translate: true),
|
||||
|
||||
DataSourceERI_V1 dataSource => dataSource.Hostname,
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ public static class PluginConfigurationObjectTypeExtensions
|
||||
/// <summary>
|
||||
/// The name of a type of configuration object, as users read it, e.g., in a message.
|
||||
/// </summary>
|
||||
public string GetName() => type switch
|
||||
public string Name => type switch
|
||||
{
|
||||
PluginConfigurationObjectType.LLM_PROVIDER => TB("LLM Provider"),
|
||||
PluginConfigurationObjectType.EMBEDDING_PROVIDER => TB("Embedding Provider"),
|
||||
|
||||
@@ -87,7 +87,7 @@ public static partial class PluginFactory
|
||||
{
|
||||
await using var inputStream = resourceFilePath.CreateReadStream();
|
||||
|
||||
var pluginTypeBasePath = Path.Join(INTERNAL_PLUGINS_ROOT, metaData.Type.GetDirectory());
|
||||
var pluginTypeBasePath = Path.Join(INTERNAL_PLUGINS_ROOT, metaData.Type.Directory);
|
||||
|
||||
if (!Directory.Exists(INTERNAL_PLUGINS_ROOT))
|
||||
Directory.CreateDirectory(INTERNAL_PLUGINS_ROOT);
|
||||
|
||||
@@ -6,7 +6,7 @@ public static class PluginTargetGroupExtensions
|
||||
|
||||
extension(PluginTargetGroup group)
|
||||
{
|
||||
public string Name() => group switch
|
||||
public string Name => group switch
|
||||
{
|
||||
PluginTargetGroup.NONE => TB("No target group"),
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ public static class PluginTypeExtensions
|
||||
|
||||
extension(PluginType type)
|
||||
{
|
||||
public string GetName() => type switch
|
||||
public string Name => type switch
|
||||
{
|
||||
PluginType.LANGUAGE => TB("Language plugin"),
|
||||
PluginType.ASSISTANT => TB("Assistant plugin"),
|
||||
@@ -17,7 +17,7 @@ public static class PluginTypeExtensions
|
||||
_ => TB("Unknown plugin type"),
|
||||
};
|
||||
|
||||
public string GetDirectory() => type switch
|
||||
public string Directory => type switch
|
||||
{
|
||||
PluginType.LANGUAGE => "languages",
|
||||
PluginType.ASSISTANT => "assistants",
|
||||
|
||||
@@ -46,7 +46,7 @@ internal sealed record ChosenLayouts(IReadOnlyDictionary<LayoutPurpose, LayoutFa
|
||||
/// <summary>
|
||||
/// The kinds of slides the presentation has to have, because the user chose a layout for them.
|
||||
/// </summary>
|
||||
public IEnumerable<LayoutPurpose> AddedSlides => Enum.GetValues<LayoutPurpose>().Where(purpose => purpose.ChoiceAddsSlide() && this.Families.ContainsKey(purpose));
|
||||
public IEnumerable<LayoutPurpose> AddedSlides => Enum.GetValues<LayoutPurpose>().Where(purpose => purpose.ChoiceAddsSlide && this.Families.ContainsKey(purpose));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the choices as rules for the model, one line per kind of slide, in the order of the kinds.
|
||||
@@ -64,14 +64,14 @@ internal sealed record ChosenLayouts(IReadOnlyDictionary<LayoutPurpose, LayoutFa
|
||||
var rules = new StringBuilder(" - The user chose layouts for some kinds of slides. A choice decides which layout such a slide uses, and it comes before every other rule on choosing layouts. Whether the presentation has such a slide follows from the content and the occasion, except for the slides the next rule requires. Vary the layouts only where no choice applies:");
|
||||
foreach (var purpose in Enum.GetValues<LayoutPurpose>())
|
||||
if (this.For(purpose) is { } family)
|
||||
rules.Append('\n').Append(" - ").Append(family.Id).Append(" for ").Append(purpose.PromptDescription()).Append('.');
|
||||
rules.Append('\n').Append(" - ").Append(family.Id).Append(" for ").Append(purpose.PromptDescription).Append('.');
|
||||
|
||||
var addedSlides = this.AddedSlides.ToList();
|
||||
if (addedSlides.Count > 0)
|
||||
{
|
||||
rules.Append('\n').Append(" - The presentation has at least one slide of each of these kinds, on its layout:");
|
||||
foreach (var purpose in addedSlides)
|
||||
rules.Append('\n').Append(" - ").Append(this.Families[purpose].Id).Append(": ").Append(purpose.PromptDescription()).Append(", ").Append(purpose.PromptPlacement()).Append('.');
|
||||
rules.Append('\n').Append(" - ").Append(this.Families[purpose].Id).Append(": ").Append(purpose.PromptDescription).Append(", ").Append(purpose.PromptPlacement).Append('.');
|
||||
}
|
||||
|
||||
return rules.ToString();
|
||||
|
||||
@@ -13,7 +13,7 @@ internal static class LayoutPurposeAreaExtensions
|
||||
/// <summary>
|
||||
/// The name of the area, as the forms show it above its kinds of slides.
|
||||
/// </summary>
|
||||
public string Label() => area switch
|
||||
public string Label => area switch
|
||||
{
|
||||
LayoutPurposeArea.FRAMING => TB("Structure and framing"),
|
||||
LayoutPurposeArea.GENERAL => TB("General content"),
|
||||
|
||||
@@ -22,23 +22,23 @@ internal static partial class LayoutPurposeExtensions
|
||||
/// <summary>
|
||||
/// The area the forms list a kind of slide in.
|
||||
/// </summary>
|
||||
public LayoutPurposeArea Area() => Describe(purpose).Area;
|
||||
public LayoutPurposeArea Area => Describe(purpose).Area;
|
||||
|
||||
/// <summary>
|
||||
/// The short name of the choice, e.g., "Layout for chapters".
|
||||
/// </summary>
|
||||
public string Label() => Describe(purpose).Label;
|
||||
public string Label => Describe(purpose).Label;
|
||||
|
||||
/// <summary>
|
||||
/// The question the choice answers, which says which slides it applies to.
|
||||
/// </summary>
|
||||
public string Question() => Describe(purpose).Question;
|
||||
public string Question => Describe(purpose).Question;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the slides of this kind for the model, so that it knows which of its slides a choice applies to.
|
||||
/// </summary>
|
||||
/// <returns>The description, e.g., "timelines, e.g., the course of a project".</returns>
|
||||
public string PromptDescription() => Describe(purpose).PromptDescription;
|
||||
/// <value>The description, e.g., "timelines, e.g., the course of a project".</value>
|
||||
public string PromptDescription => Describe(purpose).PromptDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Whether choosing a layout for this kind of slide also puts such a slide into the presentation.
|
||||
@@ -48,13 +48,13 @@ internal static partial class LayoutPurposeExtensions
|
||||
/// presentation has one follows from its content. The slides which frame a presentation are
|
||||
/// different: whoever chooses a layout for the table of contents or the imprint expects them.
|
||||
/// </remarks>
|
||||
public bool ChoiceAddsSlide() => purpose.PromptPlacement() is not null;
|
||||
public bool ChoiceAddsSlide => purpose.PromptPlacement is not null;
|
||||
|
||||
/// <summary>
|
||||
/// Where a slide goes which a choice puts into the presentation, and what it holds, for the model.
|
||||
/// </summary>
|
||||
/// <returns>The rule, or null when a choice adds no slide of this kind.</returns>
|
||||
public string? PromptPlacement() => purpose switch
|
||||
/// <value>The rule, or null when a choice adds no slide of this kind.</value>
|
||||
public string? PromptPlacement => purpose switch
|
||||
{
|
||||
LayoutPurpose.TABLE_OF_CONTENTS => "right after the title slide",
|
||||
LayoutPurpose.Q_AND_A => "near the end, before the closing slide; where the occasion suits it, e.g., in a lecture or a workshop, also at the end of chapters, then naming the topic of the chapter",
|
||||
@@ -69,9 +69,9 @@ internal static partial class LayoutPurposeExtensions
|
||||
/// <summary>
|
||||
/// 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();
|
||||
public string Help => purpose.ChoiceAddsSlide
|
||||
? $"{purpose.Question} {TB("Choosing a layout also adds such a slide to the presentation.")}"
|
||||
: purpose.Question;
|
||||
}
|
||||
|
||||
private static LayoutPurposeInfo Describe(LayoutPurpose purpose) =>
|
||||
|
||||
@@ -27,7 +27,7 @@ internal static class PlaceholderRoleExtensions
|
||||
|
||||
foreach (var candidate in SLOT_ROLES)
|
||||
{
|
||||
if (!string.Equals(words[0], candidate.CatalogName(), StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.Equals(words[0], candidate.CatalogName, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var number = 1;
|
||||
@@ -51,7 +51,7 @@ internal static class PlaceholderRoleExtensions
|
||||
/// Footer, slide number and date are filled as well, but by the presentation as a whole rather
|
||||
/// than by a single slide, so they are no slots of a layout.
|
||||
/// </remarks>
|
||||
public bool IsSlot() => role switch
|
||||
public bool IsSlot => role switch
|
||||
{
|
||||
PlaceholderRole.TITLE => true,
|
||||
PlaceholderRole.SUBTITLE => true,
|
||||
@@ -70,7 +70,7 @@ internal static class PlaceholderRoleExtensions
|
||||
/// A layout offering nothing but a title and some labels has no room for content. Such layouts are
|
||||
/// samples or blank pages, and they are left out of the catalog.
|
||||
/// </remarks>
|
||||
public bool CarriesContent() => role switch
|
||||
public bool CarriesContent => role switch
|
||||
{
|
||||
PlaceholderRole.SUBTITLE => true,
|
||||
PlaceholderRole.TEXT => true,
|
||||
@@ -83,7 +83,7 @@ internal static class PlaceholderRoleExtensions
|
||||
/// <summary>
|
||||
/// How the role is called in the catalog the model reads.
|
||||
/// </summary>
|
||||
public string CatalogName() => role switch
|
||||
public string CatalogName => role switch
|
||||
{
|
||||
PlaceholderRole.TITLE => "title",
|
||||
PlaceholderRole.SUBTITLE => "subtitle",
|
||||
|
||||
@@ -185,7 +185,7 @@ internal sealed partial class PresentationTemplate
|
||||
/// <returns>The entry, e.g., <c>element 2 (text box, right, middle, overlaps picture 1) "Image source"</c>.</returns>
|
||||
private string DescribeElement(TemplateSlideElement element, IReadOnlyList<TemplateSlot> slotsOnSlide)
|
||||
{
|
||||
var details = new List<string> { element.Kind.CatalogName() };
|
||||
var details = new List<string> { element.Kind.CatalogName };
|
||||
if (element.Bounds is { } bounds)
|
||||
{
|
||||
if (bounds.Area >= (double)this.SlideWidth * this.SlideHeight * BACKGROUND_AREA_SHARE)
|
||||
@@ -280,7 +280,7 @@ internal sealed partial class PresentationTemplate
|
||||
private static string NameOf(TemplateSlot slot, IReadOnlyList<TemplateSlot> slots)
|
||||
{
|
||||
var isOnlyOne = slot.Role is (PlaceholderRole.TITLE or PlaceholderRole.SUBTITLE) && slots.Count(other => other.Role == slot.Role) == 1;
|
||||
return isOnlyOne ? slot.Role.CatalogName() : $"{slot.Role.CatalogName()} {slot.Ordinal}";
|
||||
return isOnlyOne ? slot.Role.CatalogName : $"{slot.Role.CatalogName} {slot.Ordinal}";
|
||||
}
|
||||
|
||||
private string DescribePlace(SlotBounds bounds)
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed partial class PresentationTemplate
|
||||
/// </remarks>
|
||||
private static GroupedLayouts GroupIntoFamilies(IReadOnlyList<TemplateLayout> layouts, long slideWidth, long slideHeight)
|
||||
{
|
||||
var usableLayouts = layouts.Where(layout => layout.Slots.Any(slot => slot.Role.CarriesContent())).ToList();
|
||||
var usableLayouts = layouts.Where(layout => layout.Slots.Any(slot => slot.Role.CarriesContent)).ToList();
|
||||
var byFirstWord = Group(usableLayouts, VariantWord.FIRST, slideWidth, slideHeight);
|
||||
var byLastWord = Group(usableLayouts, VariantWord.LAST, slideWidth, slideHeight);
|
||||
var groups = CountVariants(byLastWord) > CountVariants(byFirstWord) ? byLastWord : byFirstWord;
|
||||
|
||||
@@ -148,7 +148,7 @@ internal sealed partial class PresentationTemplate
|
||||
continue;
|
||||
|
||||
var role = RoleOf(placeholder.Type, bounds, slideArea);
|
||||
if (!role.IsSlot())
|
||||
if (!role.IsSlot)
|
||||
continue;
|
||||
|
||||
//
|
||||
|
||||
@@ -159,7 +159,7 @@ internal static class SlideDeckSpecValidator
|
||||
{
|
||||
var familyId = chosen.Families[purpose].Id;
|
||||
if (!newSlideFamilyIds.Contains(familyId))
|
||||
issues.Add($"The presentation has no slide on layout {familyId} for {purpose.PromptDescription()}. Add one {purpose.PromptPlacement()}.");
|
||||
issues.Add($"The presentation has no slide on layout {familyId} for {purpose.PromptDescription}. Add one {purpose.PromptPlacement}.");
|
||||
}
|
||||
|
||||
if (chosen.For(LayoutPurpose.IMPRINT) is { } imprintFamily && newSlideFamilyIds.Contains(imprintFamily.Id) && newSlideFamilyIds[^1] != imprintFamily.Id)
|
||||
@@ -431,12 +431,12 @@ internal static class SlideDeckSpecValidator
|
||||
var slot = SlotOf(slots, role, 1);
|
||||
if (slot is null)
|
||||
{
|
||||
issues.Add($"Slide {number} has a {role.CatalogName()}, but layout {layoutId} has none. Choose a layout with a {role.CatalogName()}, or leave it empty.");
|
||||
issues.Add($"Slide {number} has a {role.CatalogName}, but layout {layoutId} has none. Choose a layout with a {role.CatalogName}, or leave it empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot.Capacity is { } capacity)
|
||||
ValidateCapacity([new SlideParagraph(text, 0)], capacity, $"Slide {number}: the {role.CatalogName()}", issues);
|
||||
ValidateCapacity([new SlideParagraph(text, 0)], capacity, $"Slide {number}: the {role.CatalogName}", issues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -450,7 +450,7 @@ internal static class SlideDeckSpecValidator
|
||||
return true;
|
||||
|
||||
issues.Add(slotCount == 0
|
||||
? $"Slide {number} has {count} {PluralOf(role, count)}, but layout {layoutId} has no {role.CatalogName()}. Choose a layout which has one."
|
||||
? $"Slide {number} has {count} {PluralOf(role, count)}, but layout {layoutId} has no {role.CatalogName}. Choose a layout which has one."
|
||||
: $"Slide {number} has {count} {PluralOf(role, count)}, but layout {layoutId} has only {slotCount}. Merge them, or choose a layout with more.");
|
||||
|
||||
return false;
|
||||
@@ -517,5 +517,5 @@ internal static class SlideDeckSpecValidator
|
||||
return Math.Max(1, (int)Math.Ceiling(paragraph.Text.Length / (double)width));
|
||||
}
|
||||
|
||||
private static string PluralOf(PlaceholderRole role, int count) => count == 1 ? role.CatalogName() : $"{role.CatalogName()}s";
|
||||
private static string PluralOf(PlaceholderRole role, int count) => count == 1 ? role.CatalogName : $"{role.CatalogName}s";
|
||||
}
|
||||
@@ -7,7 +7,7 @@ internal static class TemplateSlideElementKindExtensions
|
||||
/// <summary>
|
||||
/// How the kind is called in the list of template slides the model reads.
|
||||
/// </summary>
|
||||
public string CatalogName() => kind switch
|
||||
public string CatalogName => kind switch
|
||||
{
|
||||
TemplateSlideElementKind.TEXT_BOX => "text box",
|
||||
TemplateSlideElementKind.SHAPE => "shape",
|
||||
|
||||
@@ -22,7 +22,7 @@ public static class ProcessStepTextRouter
|
||||
/// <returns>The text representation of the process step.</returns>
|
||||
public static string GetText<T>(T step) where T : struct, Enum => step switch
|
||||
{
|
||||
ReadWebContentSteps x => x.GetText(),
|
||||
ReadWebContentSteps x => x.Text,
|
||||
_ => DeriveName(step)
|
||||
};
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class AISrcSelWithRetCtxVal : IRagProcess
|
||||
chatThread.RequireProviderConfidence(dataSource.ConfidenceLevel);
|
||||
|
||||
if (previousRequiredProviderConfidence != chatThread.RequiredProviderConfidence)
|
||||
LOGGER.LogInformation($"The required provider confidence of the chat thread was updated from '{previousRequiredProviderConfidence.GetName()}' to '{chatThread.RequiredProviderConfidence.GetName()}'.");
|
||||
LOGGER.LogInformation($"The required provider confidence of the chat thread was updated from '{previousRequiredProviderConfidence.Name}' to '{chatThread.RequiredProviderConfidence.Name}'.");
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -10,8 +10,8 @@ public static class RetrievalContentCategoryExtensions
|
||||
/// <summary>
|
||||
/// Converts an ERI content type to a common retrieval content category.
|
||||
/// </summary>
|
||||
/// <returns>The corresponding retrieval content category.</returns>
|
||||
public RetrievalContentCategory ToRetrievalContentCategory() => contentType switch
|
||||
/// <value>The corresponding retrieval content category.</value>
|
||||
public RetrievalContentCategory RetrievalContentCategory => contentType switch
|
||||
{
|
||||
ContentType.NONE => RetrievalContentCategory.NONE,
|
||||
ContentType.UNKNOWN => RetrievalContentCategory.UNKNOWN,
|
||||
|
||||
@@ -13,8 +13,8 @@ public static class SourcePassageExtensions
|
||||
/// <summary>
|
||||
/// Whether the passage comes from a transcript, e.g., of an audio or a video file.
|
||||
/// </summary>
|
||||
/// <returns>True for a transcript.</returns>
|
||||
public bool IsTranscript() => passage.Type is RetrievalContentType.TEXT_TRANSCRIPT;
|
||||
/// <value>True for a transcript.</value>
|
||||
public bool IsTranscript => passage.Type is RetrievalContentType.TEXT_TRANSCRIPT;
|
||||
|
||||
/// <summary>
|
||||
/// What sort of content the passage is, as the provenance store files it.
|
||||
@@ -23,8 +23,8 @@ public static class SourcePassageExtensions
|
||||
/// Told by what the content is rather than by where it came from: a mail is a mail, whether a
|
||||
/// mailbox data source or a tool handed it over.
|
||||
/// </remarks>
|
||||
/// <returns>The kind of its entry.</returns>
|
||||
public ProvenanceKind GetProvenanceKind() => passage.Type switch
|
||||
/// <value>The kind of its entry.</value>
|
||||
public ProvenanceKind ProvenanceKind => passage.Type switch
|
||||
{
|
||||
RetrievalContentType.TEXT_EMAIL => ProvenanceKind.MAIL,
|
||||
RetrievalContentType.TEXT_WEBSITE => ProvenanceKind.WEB_PAGE,
|
||||
@@ -34,8 +34,8 @@ public static class SourcePassageExtensions
|
||||
/// <summary>
|
||||
/// Whether the passage holds texts which belong into the provenance store, see SourcePassage.ProvenanceId.
|
||||
/// </summary>
|
||||
/// <returns>False once the texts are stored, and for a passage without any, such as an image.</returns>
|
||||
public bool HoldsTextsToStore() => passage.ProvenanceId is null && (passage.Text.Length > 0 || passage.SurroundingContent.Count > 0);
|
||||
/// <value>False once the texts are stored, and for a passage without any, such as an image.</value>
|
||||
public bool HoldsTextsToStore => passage.ProvenanceId is null && (passage.Text.Length > 0 || passage.SurroundingContent.Count > 0);
|
||||
|
||||
/// <summary>
|
||||
/// Names where the passage is in its document, e.g., "Page 4", "Pages 4–5", "Budget: rows 3–9", "Lines 40–85", or "Part 3".
|
||||
|
||||
@@ -11,8 +11,8 @@ public static class SecretStoreTypeExtensions
|
||||
/// <remarks>
|
||||
/// LLM_PROVIDER uses the legacy "provider" prefix for backward compatibility.
|
||||
/// </remarks>
|
||||
/// <returns>The corresponding prefix string.</returns>
|
||||
public string Prefix() => type switch
|
||||
/// <value>The corresponding prefix string.</value>
|
||||
public string Prefix => type switch
|
||||
{
|
||||
SecretStoreType.LLM_PROVIDER => "provider",
|
||||
SecretStoreType.EMBEDDING_PROVIDER => "embedding",
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class PromptInjectionFindingCategoryExtensions
|
||||
|
||||
extension(PromptInjectionFindingCategory category)
|
||||
{
|
||||
public string GetDisplayName() => category switch
|
||||
public string DisplayName => category switch
|
||||
{
|
||||
PromptInjectionFindingCategory.OVERRIDE => TB("Attempt to override instructions"),
|
||||
PromptInjectionFindingCategory.ROLE_OVERRIDE => TB("Attempt to change the AI's role"),
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class PromptInjectionSourceKindExtensions
|
||||
|
||||
extension(PromptInjectionSourceKind kind)
|
||||
{
|
||||
public string GetDisplayName() => kind switch
|
||||
public string DisplayName => kind switch
|
||||
{
|
||||
PromptInjectionSourceKind.WEB_CONTENT => TB("Web content"),
|
||||
PromptInjectionSourceKind.FILE_CONTENT => TB("File content"),
|
||||
|
||||
@@ -873,17 +873,17 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
||||
|
||||
if (!AllowsEmbedding(dataSource, embeddingProvider.GetConfidenceLevel(settingsManager)))
|
||||
{
|
||||
var errorMessage = dataSource is DataSourceMailbox && !dataSource.ConfidenceLevel.IsAllowedMailboxConfidence()
|
||||
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.")
|
||||
: 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.GetName(), embeddingProvider.GetConfidenceLevel(settingsManager).GetName());
|
||||
: 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}.",
|
||||
dataSource.Name,
|
||||
dataSource.Id,
|
||||
embeddingProvider.Name,
|
||||
embeddingProvider.Id,
|
||||
dataSource.ConfidenceLevel.GetName(),
|
||||
embeddingProvider.GetConfidenceLevel(settingsManager).GetName());
|
||||
dataSource.ConfidenceLevel.Name,
|
||||
embeddingProvider.GetConfidenceLevel(settingsManager).Name);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
this.UpsertStatus(this.GetFallbackStatus(dataSource, errorMessage));
|
||||
|
||||
@@ -406,7 +406,7 @@ public sealed class DataSourceService
|
||||
{
|
||||
if (!provider.ConfidenceLevel.AllowsDataSourceConfidenceLevel(internalSource.ConfidenceLevel))
|
||||
{
|
||||
this.logger.LogWarning($"The internal data source '{source.Name}' (id={source.Id}) requires provider confidence '{internalSource.ConfidenceLevel.GetName()}'. The {provider.Role} only has confidence '{provider.ConfidenceLevel.GetName()}'. We skip this source.");
|
||||
this.logger.LogWarning($"The internal data source '{source.Name}' (id={source.Id}) requires provider confidence '{internalSource.ConfidenceLevel.Name}'. The {provider.Role} only has confidence '{provider.ConfidenceLevel.Name}'. We skip this source.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -420,7 +420,7 @@ public sealed class DataSourceService
|
||||
var embeddingProviderConfidence = embeddingProvider.GetConfidenceLevel(this.settingsManager);
|
||||
if (!embeddingProviderConfidence.AllowsDataSourceConfidenceLevel(internalSource.ConfidenceLevel))
|
||||
{
|
||||
this.logger.LogWarning($"The internal data source '{source.Name}' (id={source.Id}) requires provider confidence '{internalSource.ConfidenceLevel.GetName()}'. Its embedding provider '{embeddingProvider.Name}' only has confidence '{embeddingProviderConfidence.GetName()}'. We skip this source.");
|
||||
this.logger.LogWarning($"The internal data source '{source.Name}' (id={source.Id}) requires provider confidence '{internalSource.ConfidenceLevel.Name}'. Its embedding provider '{embeddingProvider.Name}' only has confidence '{embeddingProviderConfidence.Name}'. We skip this source.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ internal sealed class DocumentRunProgress(IndexedRunContext context, int totalDo
|
||||
break;
|
||||
}
|
||||
|
||||
case FileExtractionException extractionFailure when extractionFailure.Code.IsPermanentIndexingFailure():
|
||||
case FileExtractionException extractionFailure when extractionFailure.Code.IsPermanentIndexingFailure:
|
||||
{
|
||||
//
|
||||
// The document itself is why this failed, so trying it again changes nothing until
|
||||
|
||||
@@ -118,7 +118,7 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
if (!policy.IsAllowed(mailbox.Host))
|
||||
{
|
||||
logger.LogInformation("Not connecting to mailbox '{MailboxId}' because the organization allows only its own mail servers, and this one is none of them.", mailbox.Id);
|
||||
(await this.CreateStoredStateProgressAsync(context, token)).PublishRunFailure(MailboxConnectionFailure.SERVER_NOT_ALLOWED.GetDescription(), DataSourceAttention.SERVER_NOT_ALLOWED);
|
||||
(await this.CreateStoredStateProgressAsync(context, token)).PublishRunFailure(MailboxConnectionFailure.SERVER_NOT_ALLOWED.Description, DataSourceAttention.SERVER_NOT_ALLOWED);
|
||||
return IndexedRunOutcome.DONE;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
progress.PublishRunFailure(GetAuthFailureMessage(failure), DataSourceAttention.AUTH_FAILED);
|
||||
}
|
||||
else
|
||||
progress.PublishRunFailure(e.Failure.GetDescription(), e.Failure is MailboxConnectionFailure.SERVER_NOT_ALLOWED ? DataSourceAttention.SERVER_NOT_ALLOWED : DataSourceAttention.NONE);
|
||||
progress.PublishRunFailure(e.Failure.Description, e.Failure is MailboxConnectionFailure.SERVER_NOT_ALLOWED ? DataSourceAttention.SERVER_NOT_ALLOWED : DataSourceAttention.NONE);
|
||||
|
||||
return IndexedRunOutcome.DONE;
|
||||
}
|
||||
@@ -307,7 +307,7 @@ internal sealed partial class MailboxIndexer(SettingsManager settingsManager, Ru
|
||||
this.RecordConnectionFailure(mailbox.Id, e.Failure);
|
||||
await context.OptimizeCollectionIfNeededAsync("mailbox sync stopped", token);
|
||||
progress ??= await this.CreateStoredStateProgressAsync(context, token);
|
||||
progress.PublishRunFailure(e.Failure.GetDescription());
|
||||
progress.PublishRunFailure(e.Failure.Description);
|
||||
return IndexedRunOutcome.DONE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,7 +690,7 @@ public sealed class MediaTranscriptionService(
|
||||
// unchanged when it already is a single mono 48 kHz Opus track in a WebM container and small
|
||||
// enough. Such a file keeps whatever bitrate it was made with, which is the better outcome --
|
||||
// re-encoding it could only take quality away, never add any.
|
||||
var opusBitrateBps = settingsManager.ConfigurationData.App.OpusBitrate.GetBitsPerSecond();
|
||||
var opusBitrateBps = settingsManager.ConfigurationData.App.OpusBitrate.BitsPerSecond;
|
||||
|
||||
// Which quality an upload was produced with is the first question to ask when a transcript
|
||||
// comes back missing something, so it has to be in the log of the job it belongs to:
|
||||
|
||||
@@ -247,7 +247,7 @@ public sealed partial class PluginInstallService
|
||||
private static string CreateDeleteBackupDirectory(IAvailablePlugin plugin)
|
||||
{
|
||||
var backupRoot = Path.Join(SettingsManager.DataDirectory, DELETE_BACKUP_DIRECTORY);
|
||||
return Path.Join(backupRoot, $"{plugin.Type.GetDirectory()}-{plugin.Id:N}-{Guid.NewGuid():N}");
|
||||
return Path.Join(backupRoot, $"{plugin.Type.Directory}-{plugin.Id:N}-{Guid.NewGuid():N}");
|
||||
}
|
||||
|
||||
private async Task TryRestoreDeletedPluginAsync(IAvailablePlugin plugin, string pluginDirectory, string backupDirectory, PluginDeleteSideEffects sideEffects, CancellationToken token)
|
||||
|
||||
@@ -133,7 +133,7 @@ public sealed partial class PluginInstallService
|
||||
return false;
|
||||
}
|
||||
|
||||
pluginRoot = Path.Join(dataDirectory, "plugins", pluginType.GetDirectory());
|
||||
pluginRoot = Path.Join(dataDirectory, "plugins", pluginType.Directory);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ public sealed partial class RustService
|
||||
// documentation/compatibility-shims/2026-06-self-hosted-secret-id.md
|
||||
private const string LEGACY_SELF_HOSTED_SECRET_ID_DE = "Selbst gehostet";
|
||||
|
||||
private static string APIKey(SecretStoreType storeType, ISecretId secretId) => $"{storeType.Prefix()}::{secretId.SecretId}::{secretId.SecretName}::api_key";
|
||||
private static string APIKey(SecretStoreType storeType, ISecretId secretId) => $"{storeType.Prefix}::{secretId.SecretId}::{secretId.SecretName}::api_key";
|
||||
|
||||
private static IEnumerable<string> LegacySelfHostedAPIKeys(ISecretId secretId, SecretStoreType storeType)
|
||||
{
|
||||
if (secretId.SecretId == SELF_HOSTED_SECRET_ID)
|
||||
yield return $"{storeType.Prefix()}::{LEGACY_SELF_HOSTED_SECRET_ID_DE}::{secretId.SecretName}::api_key";
|
||||
yield return $"{storeType.Prefix}::{LEGACY_SELF_HOSTED_SECRET_ID_DE}::{secretId.SecretName}::api_key";
|
||||
|
||||
if (secretId.SecretId == $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{SELF_HOSTED_SECRET_ID}")
|
||||
yield return $"{storeType.Prefix()}::{ISecretId.ENTERPRISE_KEY_PREFIX}::{LEGACY_SELF_HOSTED_SECRET_ID_DE}::{secretId.SecretName}::api_key";
|
||||
yield return $"{storeType.Prefix}::{ISecretId.ENTERPRISE_KEY_PREFIX}::{LEGACY_SELF_HOSTED_SECRET_ID_DE}::{secretId.SecretName}::api_key";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed partial class RustService
|
||||
private static DeleteSecretResponse TranslateSecretStoreIssue(DeleteSecretResponse response) =>
|
||||
response.Success ? response : response with { Issue = TranslateSecretStoreIssue(response.IssueCode, response.Issue) };
|
||||
|
||||
private static string SecretKey(ISecretId secretId, SecretStoreType storeType) => $"{storeType.Prefix()}::{secretId.SecretId}::{secretId.SecretName}";
|
||||
private static string SecretKey(ISecretId secretId, SecretStoreType storeType) => $"{storeType.Prefix}::{secretId.SecretId}::{secretId.SecretName}";
|
||||
|
||||
private static string LegacySecretKey(ISecretId secretId) => $"secret::{secretId.SecretId}::{secretId.SecretName}";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace AIStudio.Tools;
|
||||
/// Models do not agree on how they spell a language: Python comes as python, py, or Python3. The
|
||||
/// catalog therefore knows the usual aliases, the way GitHub Linguist and highlight.js spell them,
|
||||
/// in any case. The formats AI Studio writes as formats of their own, such as html, latex, markdown,
|
||||
/// or csv, are missing on purpose: FileExportFormatExtensions.TryFromCodeFenceLanguage reads them
|
||||
/// or csv, are missing on purpose: FileExportFormat.TryFromCodeFenceLanguage reads them
|
||||
/// first, and a file in one of them is treated apart from code, it takes the sources of the answer
|
||||
/// along, for example. Each language has exactly one extension; whoever needs another one renames
|
||||
/// the file.
|
||||
|
||||
@@ -11,7 +11,7 @@ public static class SafeSearchPolicyExtensions
|
||||
/// SearXNG takes the level as a number. That number stays here, at the edge towards the
|
||||
/// search engine, instead of travelling through the settings where nobody can read it.
|
||||
/// </remarks>
|
||||
public string ToSearXNGValue() => policy switch
|
||||
public string SearXNGValue => policy switch
|
||||
{
|
||||
SafeSearchPolicy.OFF => "0",
|
||||
SafeSearchPolicy.MODERATE => "1",
|
||||
@@ -28,6 +28,6 @@ public static class SafeSearchPolicyExtensions
|
||||
/// a strict one. Of the two ways to round that, filtering more than was asked for is the one
|
||||
/// that cannot surprise anyone.
|
||||
/// </remarks>
|
||||
public bool ToTavilyValue() => policy is not SafeSearchPolicy.OFF;
|
||||
public bool TavilyValue => policy is not SafeSearchPolicy.OFF;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -129,7 +129,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
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.GetName(), OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.GetName()),
|
||||
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),
|
||||
_ => TB(fieldDefinition.Description),
|
||||
};
|
||||
|
||||
@@ -266,7 +266,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
/// turn into ON without anybody having written it, see EnumNames. The configuration check
|
||||
/// reports such a value anyway and keeps the tool out of use until somebody corrects it.
|
||||
/// </remarks>
|
||||
internal static FreeAddressChoice ReadFreeAddressChoice(string? configuredValue) => EnumNames.TryParse<FreeAddressChoice>(configuredValue, out var freeAddressChoice) ? freeAddressChoice : DEFAULT_FREE_ADDRESS_CHOICE;
|
||||
internal static FreeAddressChoice ReadFreeAddressChoice(string? configuredValue) => FreeAddressChoice.TryParseName(configuredValue, out var freeAddressChoice) ? freeAddressChoice : DEFAULT_FREE_ADDRESS_CHOICE;
|
||||
|
||||
/// <summary>
|
||||
/// Words the rules the model follows when it reads web pages.
|
||||
@@ -517,7 +517,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
if (string.IsNullOrWhiteSpace(configuredValue))
|
||||
return DEFAULT_INTERNAL_PAGE_OUTBOUND_DATA_RESTRICTION;
|
||||
|
||||
return EnumNames.TryParse<OutboundDataRestriction>(configuredValue, out var restriction) ? restriction : OutboundDataRestriction.ONLY_CONFIGURED_SERVICES;
|
||||
return OutboundDataRestriction.TryParseName(configuredValue, out var restriction) ? restriction : OutboundDataRestriction.ONLY_CONFIGURED_SERVICES;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public sealed class SearXNGSearchBackend : IWebSearchBackend
|
||||
|
||||
// No configured policy sends nothing at all, which leaves the decision to the
|
||||
// instance's own configuration:
|
||||
var safeSearch = query.SafeSearch?.ToSearXNGValue();
|
||||
var safeSearch = query.SafeSearch?.SearXNGValue;
|
||||
var response = await this.searchClient.SearchAsync(new SearXNGSearchRequest(searchUri, query.Query, query.Language, query.TimeRange, query.Page, safeSearch, query.Limit, query.TimeoutSeconds), token);
|
||||
|
||||
//
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ public sealed class TavilySearchBackend : IWebSearchBackend
|
||||
// would rather have more hits than one language can choose any language instead.
|
||||
//
|
||||
FilterByLanguage = language is null ? null : true,
|
||||
SafeSearch = query.SafeSearch?.ToTavilyValue(),
|
||||
SafeSearch = query.SafeSearch?.TavilyValue,
|
||||
};
|
||||
|
||||
var response = await this.searchClient.SearchAsync(apiKey.Trim(), searchRequest, query.TimeoutSeconds, token);
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ public static class WebSearchBackendExtensions
|
||||
/// worded for people — in the settings dropdown, in a note explaining which service
|
||||
/// answered, and in the result the model reads.
|
||||
/// </remarks>
|
||||
public string ToName() => backend switch
|
||||
public string Name => backend switch
|
||||
{
|
||||
WebSearchBackend.SEARXNG => "SearXNG",
|
||||
WebSearchBackend.STAAN => "Staan",
|
||||
|
||||
+5
-5
@@ -80,7 +80,7 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
if (usesChosenBackend && chosenBackend is null && configuredBackends.Count > 1)
|
||||
{
|
||||
if (primaryBackend is not null)
|
||||
notes.Add($"The chosen search service {primaryBackend.Value.ToName()} is not configured, so the configured services were asked one after another instead.");
|
||||
notes.Add($"The chosen search service {primaryBackend.Value.Name} is not configured, so the configured services were asked one after another instead.");
|
||||
else if (strategy is WebSearchBackendStrategy.SPECIFIC)
|
||||
notes.Add("No search service is chosen, so the configured services were asked one after another instead.");
|
||||
}
|
||||
@@ -118,7 +118,7 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
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."));
|
||||
|
||||
foreach (var backend in backendsToAsk.Where(backend => !backend.Capabilities.SupportsSafeSearch))
|
||||
notes.Add($"{backend.Backend.ToName()} was not asked, because it cannot filter explicit results and the configured safe search policy requires that.");
|
||||
notes.Add($"{backend.Backend.Name} was not asked, because it cannot filter explicit results and the configured safe search policy requires that.");
|
||||
|
||||
return remainingBackends;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
throw new ArgumentException($"Argument 'page' must be less than or equal to {backendsToAsk.Max(backend => backend.Capabilities.MaxPage)}.");
|
||||
|
||||
foreach (var backend in backendsToAsk.Where(backend => query.Page > backend.Capabilities.MaxPage))
|
||||
notes.Add($"{backend.Backend.ToName()} was not asked, because it does not serve result page {query.Page}.");
|
||||
notes.Add($"{backend.Backend.Name} was not asked, because it does not serve result page {query.Page}.");
|
||||
|
||||
return remainingBackends;
|
||||
}
|
||||
@@ -205,7 +205,7 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
var attributesNotes = outcomes.Count > 1;
|
||||
foreach (var outcome in outcomes)
|
||||
{
|
||||
var backendName = outcome.Backend.Backend.ToName();
|
||||
var backendName = outcome.Backend.Backend.Name;
|
||||
var result = outcome.Result;
|
||||
if (result is null)
|
||||
{
|
||||
@@ -236,7 +236,7 @@ internal sealed class WebSearchDispatcher(IEnumerable<IWebSearchBackend> backend
|
||||
/// </remarks>
|
||||
private static void AppendUnsupportedFilterNotes(List<string> notes, IWebSearchBackend backend, WebSearchQuery query)
|
||||
{
|
||||
var backendName = backend.Backend.ToName();
|
||||
var backendName = backend.Backend.Name;
|
||||
var capabilities = backend.Capabilities;
|
||||
if (!capabilities.SupportsTimeRange && !string.IsNullOrWhiteSpace(query.TimeRange))
|
||||
notes.Add($"{backendName} cannot restrict a search to a period of time, so its hits are not limited to the requested time range '{query.TimeRange}'.");
|
||||
|
||||
+9
-9
@@ -286,7 +286,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
|
||||
var unfilteredBackends = this.dispatcher.GetConfiguredBackends(settingsValues)
|
||||
.Where(backend => !backend.Capabilities.SupportsSafeSearch)
|
||||
.Select(backend => backend.Backend.ToName())
|
||||
.Select(backend => backend.Backend.Name)
|
||||
.ToList();
|
||||
|
||||
if (unfilteredBackends.Count is 0)
|
||||
@@ -362,7 +362,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.ToName()),
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -429,7 +429,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.ToName()),
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -612,7 +612,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
// what tells apart a thin answer from one search service having nothing to say and
|
||||
// a thin answer from the others never having been asked.
|
||||
//
|
||||
["backends"] = BuildJsonArray(searchResponse.Backends.Select(backend => backend.ToName())),
|
||||
["backends"] = BuildJsonArray(searchResponse.Backends.Select(backend => backend.Name)),
|
||||
["candidate_count"] = searchResponse.CandidateCount,
|
||||
["result_count"] = retrievalResult.Results.Count,
|
||||
|
||||
@@ -654,7 +654,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
"Completed web search. ToolCallId={ToolCallId}, Strategy={Strategy}, Backends={Backends}, CandidateCount={CandidateCount}, ResultCount={ResultCount}, RetrievedPageCount={RetrievedPageCount}, BlockedPageCount={BlockedPageCount}, PageTimeoutCount={PageTimeoutCount}, FailedPageCount={FailedPageCount}, EmptyContentCount={EmptyContentCount}, RetrievalTimedOut={RetrievalTimedOut}, ReturnedContentCharacters={ReturnedContentCharacters}, TruncatedResultCount={TruncatedResultCount}, Notes={Notes}",
|
||||
context.ToolCallId,
|
||||
backendStrategy,
|
||||
string.Join(", ", searchResponse.Backends.Select(backend => backend.ToName())),
|
||||
string.Join(", ", searchResponse.Backends.Select(backend => backend.Name)),
|
||||
searchResponse.CandidateCount,
|
||||
retrievalResult.Results.Count,
|
||||
retrievedPageCount,
|
||||
@@ -694,7 +694,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
private static WebSearchBackendStrategy ReadBackendStrategy(IReadOnlyDictionary<string, string> settingsValues)
|
||||
{
|
||||
var configuredStrategy = settingsValues.GetValueOrDefault(BACKEND_STRATEGY_SETTING);
|
||||
return EnumNames.TryParse<WebSearchBackendStrategy>(configuredStrategy, out var strategy) ? strategy : DEFAULT_BACKEND_STRATEGY;
|
||||
return WebSearchBackendStrategy.TryParseName(configuredStrategy, out var strategy) ? strategy : DEFAULT_BACKEND_STRATEGY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -708,7 +708,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
private static WebSearchBackend? ReadPrimaryBackend(IReadOnlyDictionary<string, string> settingsValues)
|
||||
{
|
||||
var configuredBackend = settingsValues.GetValueOrDefault(PRIMARY_BACKEND_SETTING);
|
||||
return EnumNames.TryParse<WebSearchBackend>(configuredBackend, out var backend) ? backend : null;
|
||||
return WebSearchBackend.TryParseName(configuredBackend, out var backend) ? backend : null;
|
||||
}
|
||||
|
||||
private static JsonObject BuildResultJson(WebSearchPageResult result, WebPageModelContent sanitizedContent)
|
||||
@@ -719,7 +719,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
|
||||
// Two services having found the same page says something about the page that
|
||||
// neither of them says alone, so it is reported per hit and not only per search:
|
||||
["backends"] = BuildJsonArray(result.Candidate.Backends.Select(backend => backend.ToName())),
|
||||
["backends"] = BuildJsonArray(result.Candidate.Backends.Select(backend => backend.Name)),
|
||||
};
|
||||
|
||||
//
|
||||
@@ -875,6 +875,6 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
|
||||
private static SafeSearchPolicy? ReadSafeSearchPolicy(IReadOnlyDictionary<string, string> settingsValues)
|
||||
{
|
||||
var configuredPolicy = settingsValues.GetValueOrDefault(DEFAULT_SAFE_SEARCH_SETTING);
|
||||
return EnumNames.TryParse<SafeSearchPolicy>(configuredPolicy, out var policy) ? policy : null;
|
||||
return SafeSearchPolicy.TryParseName(configuredPolicy, out var policy) ? policy : null;
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public static class ToolSettingsOptionSources
|
||||
// translation. Adding a search service therefore adds it to this list, and to the
|
||||
// dropdown offering it, without a line of code here:
|
||||
//
|
||||
WEB_SEARCH_BACKENDS => Enum.GetValues<WebSearchBackend>().Select(backend => new ToolSettingsOption(backend.ToString(), backend.ToName())).ToList(),
|
||||
WEB_SEARCH_BACKENDS => Enum.GetValues<WebSearchBackend>().Select(backend => new ToolSettingsOption(backend.ToString(), backend.Name)).ToList(),
|
||||
WEB_SEARCH_BACKEND_STRATEGY =>
|
||||
[
|
||||
new(nameof(WebSearchBackendStrategy.FAILOVER), TB("One after another, until one answers")),
|
||||
@@ -114,7 +114,7 @@ public static class ToolSettingsOptionSources
|
||||
new(nameof(FreeAddressChoice.ON), TB("On: the AI may also choose web addresses itself")),
|
||||
],
|
||||
|
||||
OUTBOUND_DATA_RESTRICTION => Enum.GetValues<OutboundDataRestriction>().Select(restriction => new ToolSettingsOption(restriction.ToString(), restriction.GetName())).ToList(),
|
||||
OUTBOUND_DATA_RESTRICTION => Enum.GetValues<OutboundDataRestriction>().Select(restriction => new ToolSettingsOption(restriction.ToString(), restriction.Name)).ToList(),
|
||||
|
||||
_ => [],
|
||||
};
|
||||
@@ -136,9 +136,9 @@ public static class ToolSettingsOptionSources
|
||||
// user may keep a text as it is or type a language of their own. A search engine needs
|
||||
// a concrete tag, and ANY_LANGUAGE above already covers "no preference".
|
||||
//
|
||||
var tag = language.ToIETFTag();
|
||||
var tag = language.IETFTag;
|
||||
if (!string.IsNullOrWhiteSpace(tag))
|
||||
options.Add(new(tag, language.Name()));
|
||||
options.Add(new(tag, language.Name));
|
||||
}
|
||||
|
||||
return options;
|
||||
|
||||
@@ -209,8 +209,8 @@ public sealed class DataSourceValidation
|
||||
if(confidenceLevel is ConfidenceLevel.NONE)
|
||||
return TB("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.GetName(), ConfidenceLevel.HIGH.GetName());
|
||||
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);
|
||||
|
||||
return this.ValidateSelectedEmbeddingProviderAccess();
|
||||
}
|
||||
@@ -305,7 +305,7 @@ public sealed class DataSourceValidation
|
||||
|
||||
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."),
|
||||
selectedEmbedding.GetConfidenceLevel(settingsManager).GetName(),
|
||||
confidenceLevel.GetName());
|
||||
selectedEmbedding.GetConfidenceLevel(settingsManager).Name,
|
||||
confidenceLevel.Name);
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ public sealed class ProviderValidation
|
||||
if(this.GetProvider() is not LLMProviders.HUGGINGFACE)
|
||||
return null;
|
||||
|
||||
if (!inferenceProvider.SupportsChat())
|
||||
if (!inferenceProvider.SupportsChat)
|
||||
return TB("Please select an Hugging Face inference provider.");
|
||||
|
||||
return null;
|
||||
@@ -165,7 +165,7 @@ public sealed class ProviderValidation
|
||||
if (inferenceProvider is HFInferenceProvider.NONE)
|
||||
return TB("Please select an Hugging Face inference provider.");
|
||||
|
||||
if (!inferenceProvider.SupportsEmbeddings())
|
||||
if (!inferenceProvider.SupportsEmbeddings)
|
||||
return TB("This Hugging Face inference provider does not create embeddings. Please select another one.");
|
||||
|
||||
return null;
|
||||
@@ -188,7 +188,7 @@ public sealed class ProviderValidation
|
||||
if (inferenceProvider is HFInferenceProvider.NONE)
|
||||
return TB("Please select an Hugging Face inference provider.");
|
||||
|
||||
if (!inferenceProvider.SupportsTranscription())
|
||||
if (!inferenceProvider.SupportsTranscription)
|
||||
return TB("This Hugging Face inference provider does not transcribe audio. Please select another one.");
|
||||
|
||||
return null;
|
||||
|
||||
Reference in new issue
Block a user