Changed all icons of AI Studio to Lucide (#1187)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-11 14:20:49 +01:00
1 parent 9a676101f7
commit 7ef26cd5c8
352 files changed
+6261 -1620

No files matched your search

@@ -301,7 +301,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
// The answer is about to be created without the data the user expected it to use.
// Without this message, that answer is indistinguishable from one that did use it:
//
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.Source, TB("Your data sources could not be used. This answer was created without them.")));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.DATA_SOURCE, TB("Your data sources could not be used. This answer was created without them.")));
}
token.ThrowIfCancellationRequested();
@@ -344,13 +344,13 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
logger.LogError(e, "The provider request failed for chat generation job '{JobId}'. Status={StatusCode}, Reason='{ReasonPhrase}', Body='{ResponseBody}'", state.Snapshot.JobId, e.StatusCode, e.ReasonPhrase, e.ResponseBody);
RemoveEmptyAIResponse(state);
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, e.UserMessage);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, e.UserMessage));
await MessageBus.INSTANCE.SendError(new(AppIcons.PROVIDER_ERROR, e.UserMessage));
}
catch (Exception e)
{
logger.LogError(e, "The chat generation job '{JobId}' failed.", state.Snapshot.JobId);
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, e.Message);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, string.Format(TB("The AI job failed. The message is: '{0}'"), e.Message)));
await MessageBus.INSTANCE.SendError(new(AppIcons.ANSWER_STREAM, string.Format(TB("The AI job failed. The message is: '{0}'"), e.Message)));
}
}
@@ -567,7 +567,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
{
var userMessage = modelLoadResult.FailureReason.ToUserMessage(provider.InstanceName);
if (!string.IsNullOrWhiteSpace(userMessage))
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, userMessage));
await MessageBus.INSTANCE.SendError(new(AppIcons.PROVIDER_ERROR, userMessage));
logger.LogWarning("Skipping selected model availability check for '{ProviderInstanceName}' (provider={ProviderType}) because loading the model list failed with reason {FailureReason}.", provider.InstanceName, provider.Provider, modelLoadResult.FailureReason);
return false;
@@ -592,7 +592,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
TB("We could load models from '{0}', but the provider did not return any usable text models."),
provider.InstanceName);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, emptyModelsMessage));
await MessageBus.INSTANCE.SendError(new(AppIcons.PROVIDER_ERROR, emptyModelsMessage));
logger.LogWarning("Skipping AI request because there are no models available from '{ProviderInstanceName}' (provider={ProviderType}).", provider.InstanceName, provider.Provider);
return false;
}
@@ -606,7 +606,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
provider.InstanceName,
provider.Provider);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, message));
await MessageBus.INSTANCE.SendError(new(AppIcons.PROVIDER_ERROR, message));
logger.LogWarning("Skipping AI request because model '{ModelId}' is not available from '{ProviderInstanceName}' (provider={ProviderType}).", chatModel.Id, provider.InstanceName, provider.Provider);
return false;
}
File diff suppressed because it is too large. Load diff
@@ -93,7 +93,7 @@ public sealed class AssistantLaunchService(SettingsManager settingsManager, Assi
var route = destination.GetData().Route;
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.")));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.ASSISTANTS, TB("This assistant is already running. AI Studio opens the running session instead.")));
navigationManager.NavigateTo(route);
return;
}
@@ -85,15 +85,15 @@ public static class FileExportFormatExtensions
/// <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,
FileExportFormat.LATEX => Icons.Material.Filled.Functions,
FileExportFormat.MARKDOWN => Icons.Material.Filled.TextFields,
FileExportFormat.HTML => Icons.Material.Filled.Html,
FileExportFormat.CSV or FileExportFormat.TSV => Icons.Material.Filled.TableChart,
FileExportFormat.SOURCE_CODE => Icons.Material.Filled.Code,
FileExportFormat.MICROSOFT_WORD => AppIcons.FILE_FORMAT_WORD,
FileExportFormat.OPEN_DOCUMENT_TEXT => AppIcons.FILE_FORMAT_ODT,
FileExportFormat.LATEX => AppIcons.FILE_FORMAT_LATEX,
FileExportFormat.MARKDOWN => AppIcons.FILE_FORMAT_MARKDOWN,
FileExportFormat.HTML => AppIcons.FILE_FORMAT_HTML,
FileExportFormat.CSV or FileExportFormat.TSV => AppIcons.FILE_FORMAT_TABLE,
FileExportFormat.SOURCE_CODE => AppIcons.CODE,
_ => Icons.Material.Filled.Help,
_ => AppIcons.UNKNOWN,
};
/// <summary>
+15 -15
View File
@@ -71,7 +71,7 @@ public static partial class Pandoc
if (process == null)
{
if (showMessages)
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Help, TB("Was not able to check the Pandoc installation.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.UNKNOWN, TB("Was not able to check the Pandoc installation.")));
LOG.LogError("The Pandoc process was not started, it was null. Executable path: '{Executable}'.", preparedProcess.StartInfo.FileName);
@@ -90,7 +90,7 @@ public static partial class Pandoc
if (process.ExitCode != 0)
{
if (showMessages)
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, TB("Pandoc is not available on the system or the process had issues.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.PANDOC_UNAVAILABLE, TB("Pandoc is not available on the system or the process had issues.")));
LOG.LogError("The Pandoc process exited with code {ProcessExitCode}. Error output: '{ErrorText}'", process.ExitCode, error);
@@ -101,7 +101,7 @@ public static partial class Pandoc
if (!versionMatch.Success)
{
if (showMessages)
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Terminal, TB("Was not able to validate the Pandoc installation.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.PANDOC, TB("Was not able to validate the Pandoc installation.")));
LOG.LogError("Pandoc --version returned an invalid format: '{Output}'.", output);
@@ -115,7 +115,7 @@ public static partial class Pandoc
if (installedVersion >= MINIMUM_REQUIRED_VERSION)
{
if (showMessages && showSuccessMessage)
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, string.Format(TB("Pandoc v{0} is installed."), installedVersionString)));
await MessageBus.INSTANCE.SendSuccess(new(AppIcons.SUCCESS, string.Format(TB("Pandoc v{0} is installed."), installedVersionString)));
if (!HAS_LOGGED_SUCCESSFUL_CHECK_ONCE)
{
@@ -127,7 +127,7 @@ public static partial class Pandoc
}
if (showMessages)
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Build, string.Format(TB("Pandoc v{0} is installed, but it doesn't match the required version (v{1})."), installedVersionString, MINIMUM_REQUIRED_VERSION.ToString())));
await MessageBus.INSTANCE.SendError(new(AppIcons.PANDOC_UNAVAILABLE, string.Format(TB("Pandoc v{0} is installed, but it doesn't match the required version (v{1})."), installedVersionString, MINIMUM_REQUIRED_VERSION.ToString())));
LOG.LogWarning("Pandoc v{0} is installed, but it does not match the required version (v{1}).", installedVersionString, MINIMUM_REQUIRED_VERSION.ToString());
@@ -136,7 +136,7 @@ public static partial class Pandoc
catch (Exception e)
{
if (showMessages)
await MessageBus.INSTANCE.SendError(new(@Icons.Material.Filled.AppsOutage, TB("Pandoc doesn't seem to be installed.")));
await MessageBus.INSTANCE.SendError(new(@AppIcons.PANDOC_UNAVAILABLE, TB("Pandoc doesn't seem to be installed.")));
LOG.LogError(e, "Pandoc availability check failed. This usually means Pandoc is not installed or not in the system PATH.");
@@ -172,7 +172,7 @@ public static partial class Pandoc
var uri = GenerateArchiveUri(latestVersion);
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.")));
await MessageBus.INSTANCE.SendError(new (AppIcons.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.UserFriendlyName);
return;
}
@@ -180,7 +180,7 @@ public static partial class Pandoc
using var response = await WEB_CLIENT.GetAsync(uri);
if (!response.IsSuccessStatusCode)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, TB("AI Studio couldn't install Pandoc because the archive was not found.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.ERROR, TB("AI Studio couldn't install Pandoc because the archive was not found.")));
LOG.LogError("Pandoc was not installed successfully, because the archive was not found (status code {0}): url='{1}', message='{2}'", response.StatusCode, uri, response.RequestMessage);
return;
}
@@ -212,7 +212,7 @@ public static partial class Pandoc
}
else
{
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Error, TB("AI Studio couldn't install Pandoc because the archive type is unknown.")));
await MessageBus.INSTANCE.SendError(new (AppIcons.ERROR, TB("AI Studio couldn't install Pandoc because the archive type is unknown.")));
LOG.LogError("Pandoc was not installed, the archive is unknown: url='{0}'", uri);
return;
}
@@ -220,7 +220,7 @@ public static partial class Pandoc
var stagedPandocExecutable = FindExecutableInDirectory(stagingDir, PandocProcessBuilder.PandocExecutableName);
if (string.IsNullOrWhiteSpace(stagedPandocExecutable))
{
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Error, TB("AI Studio couldn't install Pandoc because the executable was not found in the archive.")));
await MessageBus.INSTANCE.SendError(new (AppIcons.ERROR, TB("AI Studio couldn't install Pandoc because the executable was not found in the archive.")));
LOG.LogError("Pandoc was not installed, the executable was not found in the extracted archive: '{StagingDir}'.", stagingDir);
return;
}
@@ -228,12 +228,12 @@ public static partial class Pandoc
LOG.LogInformation("Found Pandoc executable in downloaded archive: '{Executable}'.", stagedPandocExecutable);
await ReplaceInstallationDirectoryAsync(stagingDir, installDir);
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, string.Format(TB("Pandoc v{0} was installed successfully."), latestVersion)));
await MessageBus.INSTANCE.SendSuccess(new(AppIcons.SUCCESS, string.Format(TB("Pandoc v{0} was installed successfully."), latestVersion)));
LOG.LogInformation("Pandoc v{0} was installed successfully.", latestVersion);
}
catch (Exception ex)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, TB("AI Studio couldn't install Pandoc.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.ERROR, TB("AI Studio couldn't install Pandoc.")));
LOG.LogError(ex, "An error occurred while installing Pandoc.");
}
finally
@@ -384,7 +384,7 @@ public static partial class Pandoc
if (!response.IsSuccessStatusCode)
{
LOG.LogError("Code {StatusCode}: Could not fetch Pandoc's latest page: {Response}", response.StatusCode, response.RequestMessage);
await MessageBus.INSTANCE.SendWarning(new (Icons.Material.Filled.Warning, string.Format(TB("AI Studio couldn't find the latest Pandoc version and will install version {0} instead."), FALLBACK_VERSION.ToString())));
await MessageBus.INSTANCE.SendWarning(new (AppIcons.WARNING, string.Format(TB("AI Studio couldn't find the latest Pandoc version and will install version {0} instead."), FALLBACK_VERSION.ToString())));
return FALLBACK_VERSION.ToString();
}
@@ -393,7 +393,7 @@ public static partial class Pandoc
if (!versionMatch.Success)
{
LOG.LogError("The latest version regex returned nothing: {0}", versionMatch.Groups.ToString());
await MessageBus.INSTANCE.SendWarning(new (Icons.Material.Filled.Warning, string.Format(TB("AI Studio couldn't find the latest Pandoc version and will install version {0} instead."), FALLBACK_VERSION.ToString())));
await MessageBus.INSTANCE.SendWarning(new (AppIcons.WARNING, string.Format(TB("AI Studio couldn't find the latest Pandoc version and will install version {0} instead."), FALLBACK_VERSION.ToString())));
return FALLBACK_VERSION.ToString();
}
@@ -473,7 +473,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.UserFriendlyName)));
await MessageBus.INSTANCE.SendError(new (AppIcons.PANDOC, string.Format(TB("Installers are not available on {0} systems."), CPU_ARCHITECTURE.UserFriendlyName)));
return string.Empty;
}
}
+3 -3
View File
@@ -137,7 +137,7 @@ public static class PandocExport
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.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.WARNING, TB("Only text messages can be exported.")));
return false;
}
@@ -165,11 +165,11 @@ public static class PandocExport
feedback.ShowRunning(response.SaveFilePath);
if (!await ConvertAsync(rustService, markdownText, response.SaveFilePath, format))
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("The export failed.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.ERROR, TB("The export failed.")));
return false;
}
await feedback.SucceededAsync(Icons.Material.Filled.CheckCircle, TB("The export succeeded."), response.SaveFilePath, true);
await feedback.SucceededAsync(AppIcons.SUCCESS, TB("The export succeeded."), response.SaveFilePath, true);
return true;
}
}
@@ -284,14 +284,14 @@ public static class PlainFileExport
try
{
await File.WriteAllTextAsync(response.SaveFilePath, fileContent, encoding);
await feedback.SucceededAsync(Icons.Material.Filled.CheckCircle, TB("The export succeeded."), response.SaveFilePath, true);
await feedback.SucceededAsync(AppIcons.SUCCESS, TB("The export succeeded."), response.SaveFilePath, true);
return true;
}
catch (Exception ex)
{
LOGGER.LogError(ex, "Error during {ExportFormat} export.", format);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("The export failed.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.ERROR, TB("The export failed.")));
return false;
}
}
@@ -4,6 +4,11 @@ namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
internal static class AssistantComponentPropHelper
{
/// <summary>
/// The prefix with which an assistant plugin names a Lucide icon.
/// </summary>
private const string LUCIDE_PREFIX = "lucide:";
public static string ReadString(Dictionary<string, object> props, string key)
{
if (props.TryGetValue(key, out var value))
@@ -52,7 +57,22 @@ internal static class AssistantComponentPropHelper
public static Color GetColor(string value, Color fallback) => Enum.TryParse<Color>(value, out var color) ? color : fallback;
public static Variant GetVariant(string value, Variant fallback) => Enum.TryParse<Variant>(value, out var variant) ? variant : fallback;
public static Adornment GetAdornment(string value, Adornment fallback) => Enum.TryParse<Adornment>(value, out var adornment) ? adornment : fallback;
public static string GetIconSvg(string value) => MudBlazorIconRegistry.TryGetSvg(value.TrimStart('@'), out var svg) ? svg : string.Empty;
/// <summary>
/// Finds the icon an assistant plugin names: a Lucide icon as "lucide:" followed by its name on lucide.dev,
/// such as "lucide:house", or an icon of MudBlazor, such as "Icons.Material.Filled.Home".
/// </summary>
/// <param name="value">The icon as the plugin names it.</param>
/// <returns>The icon in the form MudBlazor renders, or an empty string for an unknown name.</returns>
public static string GetIcon(string value)
{
var name = value.TrimStart('@');
if (name.StartsWith(LUCIDE_PREFIX, StringComparison.Ordinal))
return LucideIconRegistry.TryGet(name[LUCIDE_PREFIX.Length..], out var lucideIcon) ? lucideIcon : string.Empty;
return MudBlazorIconRegistry.TryGetSvg(name, out var svg) ? svg : string.Empty;
}
public static Size GetComponentSize(string value, Size fallback) => Enum.TryParse<Size>(value, out var size) ? size : fallback;
public static Justify? GetJustify(string value) => Enum.TryParse<Justify>(value, out var justify) ? justify : null;
public static AlignItems? GetItemsAlignment(string value) => Enum.TryParse<AlignItems>(value, out var alignment) ? alignment : null;
@@ -38,32 +38,31 @@ public static class AssistantComponentTypeExtensions
public string Icon => type switch
{
AssistantComponentType.BUTTON => MudBlazor.Icons.Material.Filled.AdsClick,
AssistantComponentType.BUTTON_GROUP => MudBlazor.Icons.Material.Filled.LinearScale,
AssistantComponentType.DROPDOWN => MudBlazor.Icons.Material.Filled.Rule,
AssistantComponentType.PROVIDER_SELECTION => MudBlazor.Icons.Material.Filled.Memory,
AssistantComponentType.PROFILE_SELECTION => MudBlazor.Icons.Material.Filled.Badge,
AssistantComponentType.SWITCH => MudBlazor.Icons.Material.Filled.ToggleOn,
AssistantComponentType.HEADING => MudBlazor.Icons.Material.Filled.Title,
AssistantComponentType.TEXT => MudBlazor.Icons.Material.Filled.TextFields,
AssistantComponentType.TEXT_AREA => MudBlazor.Icons.Material.Filled.Wysiwyg,
AssistantComponentType.LIST => MudBlazor.Icons.Material.Filled.List,
AssistantComponentType.WEB_CONTENT_READER => MudBlazor.Icons.Material.Filled.Public,
AssistantComponentType.FILE_CONTENT_READER => MudBlazor.Icons.Material.Filled.AttachFile,
AssistantComponentType.FILE_ATTACHMENTS => MudBlazor.Icons.Material.Filled.AttachFile,
AssistantComponentType.IMAGE => MudBlazor.Icons.Material.Filled.Image,
AssistantComponentType.COLOR_PICKER => MudBlazor.Icons.Material.Filled.Palette,
AssistantComponentType.DATE_PICKER => MudBlazor.Icons.Material.Filled.CalendarMonth,
AssistantComponentType.DATE_RANGE_PICKER => MudBlazor.Icons.Material.Filled.DateRange,
AssistantComponentType.TIME_PICKER => MudBlazor.Icons.Material.Filled.Schedule,
AssistantComponentType.LAYOUT_ITEM => MudBlazor.Icons.Material.Filled.DashboardCustomize,
AssistantComponentType.LAYOUT_GRID => MudBlazor.Icons.Material.Filled.GridView,
AssistantComponentType.LAYOUT_PAPER => MudBlazor.Icons.Material.Filled.Inbox,
AssistantComponentType.LAYOUT_STACK => MudBlazor.Icons.Material.Filled.Layers,
AssistantComponentType.LAYOUT_ACCORDION => MudBlazor.Icons.Material.Filled.CalendarViewDay,
AssistantComponentType.LAYOUT_ACCORDION_SECTION => MudBlazor.Icons.Material.Filled.HorizontalSplit,
AssistantComponentType.FORM => MudBlazor.Icons.Material.Filled.AccountTree,
_ => MudBlazor.Icons.Material.Filled.AccountTree,
AssistantComponentType.BUTTON => AppIcons.COMPONENT_BUTTON,
AssistantComponentType.BUTTON_GROUP => AppIcons.COMPONENT_BUTTON_GROUP,
AssistantComponentType.DROPDOWN => AppIcons.COMPONENT_DROPDOWN,
AssistantComponentType.PROVIDER_SELECTION => AppIcons.LLM_PROVIDER,
AssistantComponentType.PROFILE_SELECTION => AppIcons.PROFILE,
AssistantComponentType.SWITCH => AppIcons.SWITCH,
AssistantComponentType.HEADING => AppIcons.HEADING,
AssistantComponentType.TEXT => AppIcons.TEXT_INPUT,
AssistantComponentType.TEXT_AREA => AppIcons.TEXT_AREA,
AssistantComponentType.LIST => AppIcons.LIST,
AssistantComponentType.WEB_CONTENT_READER => AppIcons.WEB,
AssistantComponentType.FILE_CONTENT_READER => AppIcons.ATTACH_FILE,
AssistantComponentType.FILE_ATTACHMENTS => AppIcons.ATTACH_FILE,
AssistantComponentType.IMAGE => AppIcons.IMAGE,
AssistantComponentType.COLOR_PICKER => AppIcons.COLOR,
AssistantComponentType.DATE_PICKER => AppIcons.DATE,
AssistantComponentType.DATE_RANGE_PICKER => AppIcons.DATE_RANGE,
AssistantComponentType.TIME_PICKER => AppIcons.TIME,
AssistantComponentType.LAYOUT_ITEM => AppIcons.COMPONENT_LAYOUT_ITEM,
AssistantComponentType.LAYOUT_GRID => AppIcons.ASSISTANT_COMPONENT_GRID,
AssistantComponentType.LAYOUT_PAPER => AppIcons.COMPONENT_LAYOUT_PAPER,
AssistantComponentType.LAYOUT_STACK => AppIcons.COMPONENT_LAYOUT_STACK,
AssistantComponentType.LAYOUT_ACCORDION => AppIcons.COMPONENT_ACCORDION,
AssistantComponentType.LAYOUT_ACCORDION_SECTION => AppIcons.ASSISTANT_COMPONENT_ACCORDION_SECTION,
_ => AppIcons.ASSISTANT_COMPONENTS,
};
}
}
@@ -1,4 +1,3 @@
using AIStudio.Tools.PluginSystem.Assistants.Icons;
using Lua;
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
@@ -105,5 +104,5 @@ public sealed class AssistantSwitch : StatefulAssistantComponentBase
public static Color GetColor(string colorString) => Enum.TryParse<Color>(colorString, out var color) ? color : Color.Inherit;
public Placement GetLabelPlacement() => Enum.TryParse<Placement>(this.LabelPlacement, out var placement) ? placement : Placement.Right;
public string GetIconSvg() => MudBlazorIconRegistry.TryGetSvg(this.Icon, out var svg) ? svg : string.Empty;
public string GetIcon() => AssistantComponentPropHelper.GetIcon(this.Icon);
}
@@ -41,28 +41,32 @@ public static class PluginAssistantSecurityResolver
private static string GetAvailabilityIcon(bool requiresAudit, bool hasAudit, bool hasHashMismatch, bool isBlocked, bool canOverride)
{
if (hasHashMismatch)
return MudBlazor.Icons.Material.Filled.Warning;
return AppIcons.WARNING;
if (requiresAudit)
return MudBlazor.Icons.Material.Filled.GppMaybe;
return AppIcons.SECURITY_WARNING;
if (!hasAudit)
return MudBlazor.Icons.Material.Filled.HelpOutline;
return AppIcons.UNKNOWN;
if (isBlocked)
return MudBlazor.Icons.Material.Filled.Lock;
return AppIcons.LOCK;
if (canOverride)
return MudBlazor.Icons.Material.Filled.ReportProblem;
return AppIcons.WARNING;
return MudBlazor.Icons.Material.Filled.LockOpen;
return AppIcons.UNLOCK;
}
// ReSharper disable UnusedParameter.Local
private static string GetSecurityBadgeIcon(bool requiresAudit, bool hasAudit, bool hasHashMismatch, bool isBlocked, bool canOverride)
{
if (hasHashMismatch)
return MudBlazor.Icons.Material.Filled.RemoveModerator;
return AppIcons.AUDIT_MISMATCH;
if (!hasAudit)
return MudBlazor.Icons.Material.Filled.AddModerator;
return AppIcons.SECURITY_NOT_AUDITED;
return MudBlazor.Icons.Material.Filled.Security;
// An audit below the required minimum level never shows the check mark of a passed audit:
if (isBlocked || canOverride)
return AppIcons.SECURITY_AUDIT_FAILED;
return AppIcons.SECURITY_AUDIT_PASSED;
}
// ReSharper restore UnusedParameter.Local
@@ -113,12 +117,12 @@ public static class PluginAssistantSecurityResolver
StatusLabel = TB("Unlocked"),
SourceLabel = TB("Approved by your organization"),
SourceColor = Color.Success,
SourceIcon = MudBlazor.Icons.Material.Filled.Business,
BadgeIcon = MudBlazor.Icons.Material.Filled.Business,
SourceIcon = AppIcons.ORGANIZATION,
BadgeIcon = AppIcons.ORGANIZATION,
Headline = enterpriseHeadline,
Description = TB("The current plugin hash matches an enterprise-managed approval. No manual security audit is required for activation or usage."),
StatusColor = Color.Success,
StatusIcon = MudBlazor.Icons.Material.Filled.VerifiedUser,
StatusIcon = AppIcons.TRUSTED,
ActionLabel = TB("Open Security Details"),
};
}
@@ -161,7 +165,7 @@ public static class PluginAssistantSecurityResolver
StatusLabel = GetAvailabilityLabel(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
SourceLabel = TB("No Approval"),
SourceColor = Color.Default,
SourceIcon = MudBlazor.Icons.Material.Filled.HelpOutline,
SourceIcon = AppIcons.UNKNOWN,
BadgeIcon = GetSecurityBadgeIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
Headline = requiresAudit ? TB("This assistant is currently locked.") : TB("This assistant currently has no stored audit."),
Description = requiresAudit
@@ -200,7 +204,7 @@ public static class PluginAssistantSecurityResolver
StatusLabel = GetAvailabilityLabel(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
SourceLabel = TB("No Approval"),
SourceColor = Color.Default,
SourceIcon = MudBlazor.Icons.Material.Filled.Warning,
SourceIcon = AppIcons.WARNING,
BadgeIcon = GetSecurityBadgeIcon(requiresAudit, hasAudit, hasHashMismatch, isBlocked, canOverride: false),
Headline = requiresAudit ? TB("This assistant is locked until it is audited again.") : TB("This assistant changed after its last audit."),
Description = requiresAudit
@@ -242,7 +246,7 @@ public static class PluginAssistantSecurityResolver
StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
SourceLabel = TB("User Audit"),
SourceColor = auditLevel.Color,
SourceIcon = MudBlazor.Icons.Material.Filled.Verified,
SourceIcon = AppIcons.SECURITY_AUDIT_FAILED,
BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlockedByMinimum, canOverride),
Headline = isBlockedByMinimum
? TB("This assistant is currently locked.")
@@ -287,7 +291,7 @@ public static class PluginAssistantSecurityResolver
StatusLabel = GetAvailabilityLabel(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
SourceLabel = TB("User Audit"),
SourceColor = auditLevelDefault.Color,
SourceIcon = MudBlazor.Icons.Material.Filled.Verified,
SourceIcon = AppIcons.SECURITY_AUDIT_PASSED,
BadgeIcon = GetSecurityBadgeIcon(requiresAudit: false, hasAudit, hasHashMismatch: false, isBlocked: false, canOverride: false),
Headline = TB("This assistant is currently unlocked."),
Description = string.Format(TB("The stored audit matches the current plugin code and meets your required minimum level '{0}'."), auditSettings.MinimumLevel.Name),
@@ -46,18 +46,18 @@ public sealed class PluginAssistantSecurityState
public bool CanStartAssistant { get; init; }
public string AuditLabel { get; init; } = string.Empty;
public Color AuditColor { get; init; } = Color.Info;
public string AuditIcon { get; init; } = MudBlazor.Icons.Material.Filled.HelpOutline;
public string AuditIcon { get; init; } = AppIcons.UNKNOWN;
public string AvailabilityLabel { get; init; } = string.Empty;
public Color AvailabilityColor { get; init; } = Color.Info;
public string AvailabilityIcon { get; init; } = MudBlazor.Icons.Material.Filled.Lock;
public string AvailabilityIcon { get; init; } = AppIcons.LOCK;
public string StatusLabel { get; init; } = string.Empty;
public string SourceLabel { get; init; } = string.Empty;
public Color SourceColor { get; init; } = Color.Info;
public string SourceIcon { get; init; } = MudBlazor.Icons.Material.Filled.Info;
public string SourceIcon { get; init; } = AppIcons.INFO;
public string Headline { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public Color StatusColor { get; init; } = Color.Info;
public string StatusIcon { get; init; } = MudBlazor.Icons.Material.Filled.Lock;
public string StatusIcon { get; init; } = AppIcons.LOCK;
public string ActionLabel { get; init; } = string.Empty;
public string BadgeIcon { get; init; } = MudBlazor.Icons.Material.Outlined.Shield;
public string BadgeIcon { get; init; } = AppIcons.SECURITY;
}
@@ -4,7 +4,7 @@ public abstract partial class PluginBase
{
private const string DEFAULT_ICON_SVG =
"""
<svg height="1.5em" width="1.5em" viewBox="0 0 24 24" fill="#1f1f1f"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 13h-2V7h-6V5c0-.28-.22-.5-.5-.5s-.5.22-.5.5v2H4l.01 2.12C5.76 9.8 7 11.51 7 13.5c0 1.99-1.25 3.7-3 4.38V20h2.12c.68-1.75 2.39-3 4.38-3 1.99 0 3.7 1.25 4.38 3H17v-6h2c.28 0 .5-.22.5-.5s-.22-.5-.5-.5z" opacity=".3"/><path d="M19 11V7c0-1.1-.9-2-2-2h-4c0-1.38-1.12-2.5-2.5-2.5S8 3.62 8 5H4c-1.1 0-1.99.9-1.99 2v3.8h.29c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-.3c0-1.49 1.21-2.7 2.7-2.7s2.7 1.21 2.7 2.7v.3H17c1.1 0 2-.9 2-2v-4c1.38 0 2.5-1.12 2.5-2.5S20.38 11 19 11zm0 3h-2v6h-2.12c-.68-1.75-2.39-3-4.38-3-1.99 0-3.7 1.25-4.38 3H4v-2.12c1.75-.68 3-2.39 3-4.38 0-1.99-1.24-3.7-2.99-4.38L4 7h6V5c0-.28.22-.5.5-.5s.5.22.5.5v2h6v6h2c.28 0 .5.22.5.5s-.22.5-.5.5z"/></svg>
<svg height="1.5em" width="1.5em" viewBox="0 0 24 24" fill="none" stroke="#1f1f1f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22v-5"/><path d="M15 8V2"/><path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"/><path d="M9 8V2"/></svg>
""";
private static readonly string DEFAULT_ICON_DATA_URL = CreateDefaultIconDataUrl();
@@ -86,7 +86,7 @@ public sealed class AugmentationOne : IAugmentationProcess
// The user switched this check on. Continuing without it silently would hide
// that the answer rests on unfiltered passages:
//
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.FactCheck, TB("The check of which passages fit your question failed. This answer uses all passages that were found.")));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.PASSAGE_RELEVANCE_CHECK, TB("The check of which passages fit your question failed. This answer uses all passages that were found.")));
}
}
else
@@ -111,7 +111,7 @@ public sealed class AugmentationOne : IAugmentationProcess
// and each one may miss something else.
//
if (selection.LeftOutCount > 0)
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.ContentCut, string.Format(TB("Not every passage found in your data sources fits into what the selected model can read. This answer had to do without {0} of them. To give the AI every passage, switch on Semantic Search for this chat, lower the token limit of your data sources, or choose fewer data sources."), selection.LeftOutCount.CompactCount())));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.CONTENT_SHORTENED, string.Format(TB("Not every passage found in your data sources fits into what the selected model can read. This answer had to do without {0} of them. To give the AI every passage, switch on Semantic Search for this chat, lower the token limit of your data sources, or choose fewer data sources."), selection.LeftOutCount.CompactCount())));
//
// Add the augmented data to the chat thread, as the very last step: the caller restricts
@@ -54,7 +54,7 @@ public sealed class PromptInjectionGuardService(
{
logger.LogError("Could not check {SourceKind} '{SourceLabel}' for prompt injections. The content is used unchanged.", source.Kind, source.Label);
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.GppMaybe,
AppIcons.SECURITY_WARNING,
string.Format(TB("AI Studio could not check '{0}' for prompt injections. The content is used as it is."), source.NotificationLabel)));
return text;
@@ -106,7 +106,7 @@ public sealed class PromptInjectionGuardService(
var sources = texts.Select(x => x.Source).Distinct().ToList();
logger.LogError("Could not check {SourceCount} content source(s) for prompt injections. The content is used unchanged. Sources: {SourceLabels}", sources.Count, string.Join(", ", sources.Select(x => $"{x.Kind} '{x.Label}'")));
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.GppMaybe,
AppIcons.SECURITY_WARNING,
sources.Count is 1
? string.Format(TB("AI Studio could not check '{0}' for prompt injections. The content is used as it is."), sources[0].NotificationLabel)
: string.Format(TB("AI Studio could not check {0} sources for prompt injections. The content is used as it is."), sources.Count)));
@@ -293,7 +293,7 @@ public sealed class PromptInjectionGuardService(
results.Count);
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.GppMaybe,
AppIcons.SECURITY_WARNING,
results.Count is 1
? string.Format(TB("AI Studio removed suspicious instructions from '{0}' before using it."), results[0].Source.NotificationLabel)
: string.Format(TB("AI Studio removed suspicious instructions from {0} sources before using them."), results.Count)));
@@ -217,7 +217,7 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
{
this.localFallbackWarningShown = true;
await this.messageBus.SendWarning(new(
Icons.Material.Filled.Keyboard,
AppIcons.KEYBOARD_SHORTCUT,
TB("The voice recording shortcut currently works only while AI Studio is focused.")));
}
}
@@ -236,9 +236,9 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
result.ErrorMessage);
if (result.Cancelled)
await this.messageBus.SendWarning(new(Icons.Material.Filled.Keyboard, userMessage));
await this.messageBus.SendWarning(new(AppIcons.KEYBOARD_SHORTCUT, userMessage));
else
await this.messageBus.SendError(new(Icons.Material.Filled.Keyboard, userMessage));
await this.messageBus.SendError(new(AppIcons.KEYBOARD_SHORTCUT, userMessage));
}
}
@@ -327,7 +327,7 @@ internal sealed class DocumentRunProgress(IndexedRunContext context, int totalDo
// Once per kind of failure and pass, not once per document or step:
if (context.Pass.TryMarkReported(providerFailure.FailureReason))
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, providerFailure.UserMessage));
await MessageBus.INSTANCE.SendError(new(AppIcons.PROVIDER_ERROR, providerFailure.UserMessage));
break;
}
@@ -215,7 +215,7 @@ public sealed class LocalIndexSearchService(SettingsManager settingsManager, Rus
return;
}
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.SearchOff, userMessage));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.RETRIEVAL_GAP, userMessage));
}
/// <summary>
@@ -58,7 +58,7 @@ public sealed class PandocAvailabilityService(RustService rustService, IDialogSe
{
this.Logger.LogError("Pandoc is not available after installation attempt.");
if (showErrorMessage)
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("AI Studio needs Pandoc for this, but it is not available.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.PANDOC_UNAVAILABLE, TB("AI Studio needs Pandoc for this, but it is not available.")));
}
}
@@ -42,9 +42,9 @@ public sealed partial class RustService
finally
{
if (succeeded)
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.ContentCopy, message));
await MessageBus.INSTANCE.SendSuccess(new(AppIcons.COPY, message));
else
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ContentCopy, message));
await MessageBus.INSTANCE.SendError(new(AppIcons.COPY, message));
}
}
}
@@ -100,7 +100,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
if (notifyUserWhenNoUpdate)
{
await this.messageBus.SendError(new(Icons.Material.Filled.Error, TB("Failed to check for updates. Please try again later.")));
await this.messageBus.SendError(new(AppIcons.ERROR, TB("Failed to check for updates. Please try again later.")));
}
return;
@@ -127,7 +127,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
}
catch (Exception)
{
await this.messageBus.SendError(new(Icons.Material.Filled.Error, TB("Failed to install update automatically. Please try again manually.")));
await this.messageBus.SendError(new(AppIcons.ERROR, TB("Failed to install update automatically. Please try again manually.")));
}
}
else
@@ -137,7 +137,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
{
if (notifyUserWhenNoUpdate)
{
await this.messageBus.SendInfo(new(Icons.Material.Filled.Update, TB("No update found.")));
await this.messageBus.SendInfo(new(AppIcons.APP_UPDATE, TB("No update found.")));
}
}
}
@@ -39,7 +39,7 @@ public static class SourceDocumentLocationExtensions
catch (Exception e)
{
LOGGER.LogWarning(e, "Could not open a source document.");
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Description, TB("Could not open the document.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.DOCUMENT, TB("Could not open the document.")));
return;
}
@@ -47,7 +47,7 @@ public static class SourceDocumentLocationExtensions
return;
var issue = string.IsNullOrWhiteSpace(response.Issue) ? TB("Unknown error") : response.Issue;
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Description, string.Format(TB("Could not open the document: {0}"), issue)));
await MessageBus.INSTANCE.SendError(new(AppIcons.DOCUMENT, string.Format(TB("Could not open the document: {0}"), issue)));
}
/// <summary>
@@ -69,7 +69,7 @@ public static class SourceDocumentLocationExtensions
catch (Exception e)
{
LOGGER.LogWarning(e, "Could not show a source document in the file manager.");
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.FolderOpen, TB("Could not open the file location.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.FOLDER_OPEN, TB("Could not open the file location.")));
return;
}
@@ -77,7 +77,7 @@ public static class SourceDocumentLocationExtensions
return;
var issue = string.IsNullOrWhiteSpace(response.Issue) ? TB("Unknown error") : response.Issue;
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.FolderOpen, string.Format(TB("Could not open the file location: {0}"), issue)));
await MessageBus.INSTANCE.SendError(new(AppIcons.FOLDER_OPEN, string.Format(TB("Could not open the file location: {0}"), issue)));
}
}
}
@@ -31,6 +31,6 @@ public static class ToolCallingMessages
/// </remarks>
/// <param name="statusCode">The status code the provider answered with.</param>
public static async Task SendToolCallingRequestFailedAsync(int statusCode) => await MessageBus.INSTANCE.SendError(new(
Icons.Material.Filled.Build,
AppIcons.TOOL,
string.Format(TB("The tool calling request failed with status code {0}. See the logs for details."), statusCode)));
}
@@ -65,7 +65,7 @@ public interface IToolImplementation
public ValueTask<string> ResolveSystemPromptInstructionsAsync(ToolDefinition definition, ToolResolutionContext context, CancellationToken token = default) =>
ValueTask.FromResult(definition.SystemPromptInstructions);
public string Icon => Icons.Material.Filled.Build;
public string Icon => AppIcons.TOOL;
/// <summary>
/// Whether this tool exists in this installation right now.
@@ -108,7 +108,7 @@ public sealed class CountMailsTool(SettingsManager settingsManager, MailboxRetri
.OptionalEnum(GROUP_BY_ARGUMENT, $"Optional: break the number of each mailbox down by the folders the mails lie in, or by their senders. Shows the {MAX_GROUPS} largest groups. Leave it out for the totals only.", GROUP_BY_VALUES)
.Build();
public string Icon => Icons.Material.Filled.Numbers;
public string Icon => AppIcons.MAIL_COUNT;
// Only while both previews are switched on, the one for local RAG and the one for mailboxes:
public bool IsAvailable => retrievalService.AreMailboxesEnabled;
@@ -25,7 +25,7 @@ public sealed class MailboxToolCollection : IToolCollection
DescriptionForLLM = "Search, read, and count the mails in the mailboxes of the user, including their attachments. AI Studio keeps the mailboxes in a local index; nothing in a mailbox changes.",
};
public string Icon => Icons.Material.Filled.Mail;
public string Icon => AppIcons.MAILBOX;
public string GetDisplayName() => TB("Mailboxes");
@@ -92,7 +92,7 @@ public sealed class ReadMailTool(SettingsManager settingsManager, MailboxRetriev
public ValueTask<ToolFunctionDefinition?> ResolveFunctionAsync(ToolDefinition definition, ToolResolutionContext context, CancellationToken token = default) =>
ValueTask.FromResult(retrievalService.GetReadableMailboxes(context.ProviderConfidence).Count == 0 ? null : definition.Function);
public string Icon => Icons.Material.Filled.MarkEmailRead;
public string Icon => AppIcons.MAIL_READ;
// Only while both previews are switched on, the one for local RAG and the one for mailboxes:
public bool IsAvailable => retrievalService.AreMailboxesEnabled;
@@ -167,7 +167,7 @@ public sealed class SearchMailsTool(SettingsManager settingsManager, MailboxRetr
.Where(mailbox => mailbox.MaxMatches > 0)
.ToList();
public string Icon => Icons.Material.Filled.Mail;
public string Icon => AppIcons.MAIL_SEARCH;
// Only while both previews are switched on, the one for local RAG and the one for mailboxes:
public bool IsAvailable => retrievalService.AreMailboxesEnabled;
@@ -65,7 +65,7 @@ public sealed class CalculateTool(SettingsManager settingsManager, MathComputati
},
};
public string Icon => Icons.Material.Filled.Calculate;
public string Icon => AppIcons.MATHEMATICS;
public bool IsAvailable => PreviewFeatures.PRE_MATHEMATICS_2026.IsEnabled(settingsManager);
@@ -111,7 +111,7 @@ public sealed class ComputeSymbolicallyTool(SettingsManager settingsManager, Mat
},
};
public string Icon => Icons.Material.Filled.Functions;
public string Icon => AppIcons.TOOL_SYMBOLIC_MATH;
public bool IsAvailable => PreviewFeatures.PRE_MATHEMATICS_2026.IsEnabled(settingsManager);
@@ -26,7 +26,7 @@ public sealed class MathematicsToolCollection : IToolCollection
DescriptionForLLM = "Calculate exactly or to a stated number of digits, and work with formulas: simplify, factor, differentiate, integrate, take limits, and solve equations. Everything is computed locally, and each result is checked numerically where possible.",
};
public string Icon => Icons.Material.Filled.Calculate;
public string Icon => AppIcons.MATHEMATICS;
public string GetDisplayName() => TB("Mathematics");
@@ -95,7 +95,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
},
};
public string Icon => Icons.Material.Filled.Article;
public string Icon => AppIcons.READ_WEB_PAGE;
public bool ReturnsUntrustedExternalContent => true;
@@ -724,7 +724,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
providerConfidence);
await MessageBus.INSTANCE.SendError(new DataErrorMessage(
Icons.Material.Filled.Security,
AppIcons.PROVIDER_CONFIDENCE,
TB("The web page was not loaded because private or VPN web pages require a High-confidence provider.")));
}
@@ -326,7 +326,7 @@ public sealed class SemanticSearchTool(SettingsManager settingsManager, DataSour
_ => "data source",
};
public string Icon => Icons.Material.Filled.ManageSearch;
public string Icon => AppIcons.SEMANTIC_SEARCH;
// An ERI data source is a server somebody else runs, and even a local document may hold text
// written to steer a model:
@@ -174,7 +174,7 @@ public sealed class WebSearchTool(IEnumerable<IWebSearchBackend> backends, WebPa
.Build();
}
public string Icon => Icons.Material.Filled.Language;
public string Icon => AppIcons.WEB;
public bool ReturnsUntrustedExternalContent => true;
@@ -11,7 +11,7 @@ public sealed class ToolInvocationTrace
public string ToolName { get; set; } = string.Empty;
public string ToolIcon { get; set; } = Icons.Material.Filled.Build;
public string ToolIcon { get; set; } = AppIcons.TOOL;
public string ToolCallId { get; set; } = string.Empty;
@@ -12,4 +12,4 @@ namespace AIStudio.Tools.ToolCallingSystem;
/// <param name="Label">What the user reads on the button.</param>
/// <param name="Url">Where the button leads. It opens in the browser, not in AI Studio.</param>
/// <param name="Icon">The icon shown before the label.</param>
public sealed record ToolSettingsGroupLink(string Label, string Url, string Icon = Icons.Material.Filled.OpenInBrowser);
public sealed record ToolSettingsGroupLink(string Label, string Url, string Icon = AppIcons.OPEN_EXTERNAL);
+5 -5
View File
@@ -30,7 +30,7 @@ public static class UserFile
if (string.IsNullOrEmpty(filePath))
{
LOGGER.LogError("Can't load from an empty or null file path.");
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, TB("The file path is null or empty and the file therefore can not be loaded.")));
await MessageBus.INSTANCE.SendError(new(AppIcons.ERROR, TB("The file path is null or empty and the file therefore can not be loaded.")));
return FileExtractionResult.Failed(FileExtractionErrorCode.INVALID_REQUEST, "The file path is null or empty.");
}
@@ -47,7 +47,7 @@ public static class UserFile
if (!pandocState.IsAvailable)
{
LOGGER.LogError("Pandoc is not available after installation attempt, so '{FilePath}' cannot be read.", filePath);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Cancel, FileExtractionErrorCode.PANDOC_UNAVAILABLE.ToUserMessage(fileName)));
await MessageBus.INSTANCE.SendError(new(AppIcons.PANDOC_UNAVAILABLE, FileExtractionErrorCode.PANDOC_UNAVAILABLE.ToUserMessage(fileName)));
return FileExtractionResult.Failed(FileExtractionErrorCode.PANDOC_UNAVAILABLE, "Pandoc is required to read this file, but it is not available.");
}
}
@@ -64,19 +64,19 @@ public static class UserFile
if (!result.HasUsableContent)
{
LOGGER.LogError("Reading the file '{FilePath}' failed: code={ErrorCode}, message='{ErrorMessage}'.", filePath, result.ErrorCode, result.ErrorMessage);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Description, result.ToUserMessage(fileName)));
await MessageBus.INSTANCE.SendError(new(AppIcons.DOCUMENT, result.ToUserMessage(fileName)));
}
else if (result.Outcome is FileExtractionOutcome.PARTIAL)
{
LOGGER.LogWarning("Parts of the file '{FilePath}' could not be read: pages={FailedPages}.", filePath, string.Join(", ", result.FailedPages));
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.Description, result.ToPartialUserMessage(fileName)));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.DOCUMENT, result.ToPartialUserMessage(fileName)));
}
// The file was read correctly, but its extension lies about what it contains:
if (result.HasExtensionMismatch)
{
LOGGER.LogWarning("The file '{FilePath}' is actually a '{DetectedFormat}'.", filePath, result.DetectedFormat);
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.RuleFolder, result.ToExtensionMismatchUserMessage(fileName)));
await MessageBus.INSTANCE.SendWarning(new(AppIcons.FILE_TYPE_MISMATCH, result.ToExtensionMismatchUserMessage(fileName)));
}
return result;
@@ -45,7 +45,7 @@ public static class FileExtensionValidation
if (string.Equals(Path.GetExtension(filePath), ".doc", StringComparison.OrdinalIgnoreCase))
{
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.Description,
AppIcons.DOCUMENT,
TB("This file format is not supported. Please convert the .doc file to .docx (e.g. with Microsoft Word).")));
return false;
}
@@ -53,7 +53,7 @@ public static class FileExtensionValidation
if (FileTypes.IsAllowedPath(filePath, FileTypes.EXECUTABLES))
{
await MessageBus.INSTANCE.SendError(new(
Icons.Material.Filled.AppBlocking,
AppIcons.EXECUTABLE_BLOCKED,
TB("Executables are not allowed")));
return false;
}
@@ -65,7 +65,7 @@ public static class FileExtensionValidation
// In this use case, we cannot guarantee that a provider is selected yet:
case UseCase.DIRECTLY_LOADING_CONTENT:
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.ImageNotSupported,
AppIcons.IMAGE_NOT_SUPPORTED,
TB("Images are not supported at this place")));
return false;
@@ -76,7 +76,7 @@ public static class FileExtensionValidation
//
case UseCase.ATTACHING_CONTENT when !FileTypes.IsAllowedPath(filePath, FileTypes.MODEL_IMAGE):
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.ImageNotSupported,
AppIcons.IMAGE_NOT_SUPPORTED,
string.Format(TB("AI Studio cannot give {0} images to an AI model. Please convert the image to JPG or PNG."), Path.GetExtension(filePath).TrimStart('.').ToUpperInvariant())));
return false;
@@ -91,13 +91,13 @@ public static class FileExtensionValidation
// We know that images are not supported:
case UseCase.ATTACHING_CONTENT:
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.ImageNotSupported,
AppIcons.IMAGE_NOT_SUPPORTED,
TB("Images are not supported by the selected provider and model")));
return false;
default:
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.ImageNotSupported,
AppIcons.IMAGE_NOT_SUPPORTED,
TB("Images are not supported yet")));
return false;
}
@@ -106,7 +106,7 @@ public static class FileExtensionValidation
if (FileTypes.IsAllowedPath(filePath, FileTypes.VIDEO))
{
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.FeaturedVideo,
AppIcons.VIDEO,
TB("Videos are not supported yet")));
return false;
}
@@ -114,7 +114,7 @@ public static class FileExtensionValidation
if (FileTypes.IsAllowedPath(filePath, FileTypes.AUDIO))
{
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.AudioFile,
AppIcons.AUDIO_FILE,
TB("Audio files are not supported yet")));
return false;
}
@@ -123,7 +123,7 @@ public static class FileExtensionValidation
return true;
await MessageBus.INSTANCE.SendWarning(new(
Icons.Material.Filled.InsertDriveFile,
AppIcons.UNSUPPORTED_FILE_TYPE,
TB("Unsupported file type")));
return false;
}