Added provider icons (#934)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Peer HogeterpandThorsten Sommer authored and GitHub committed 2026-08-25 12:46:10 +02:00
1 parent afdcc0455d
commit e78a0e61b9
74 files changed
+1018 -74

No files matched your search

@@ -3,9 +3,14 @@ namespace AIStudio.Tools.PluginSystem;
public interface IPluginMetadata
{
/// <summary>
/// The icon of this plugin.
/// The icon of this plugin, as a data URL ready for the src attribute of an image element.
/// </summary>
public string IconSVG { get; }
/// <remarks>
/// Deliberately a data URL and not the raw markup: the icon comes from the plugin, so it must
/// never be rendered inline into the DOM. Inside an image element the browser treats it as a
/// standalone document which runs no script and loads nothing from the network.
/// </remarks>
public string IconDataUrl { get; }
/// <summary>
/// The type of this plugin.
@@ -7,39 +7,62 @@ public abstract partial class PluginBase
<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>
""";
private static readonly string DEFAULT_ICON_DATA_URL = CreateDefaultIconDataUrl();
#region Initialization-related methods
/// <summary>
/// Tries to initialize the icon of the plugin.
/// </summary>
/// <remarks>
/// When no icon is specified, the default icon will be used.
/// <para>
/// When no icon is specified, or when the specified icon is unusable, the default icon will be
/// used. A plugin never fails to load over its icon.
/// </para>
/// <para>
/// The icon is handed out as a data URL, not as markup: plugins are shown through an image
/// element so the browser treats their icon as a standalone, script-less document. Rendering
/// plugin-supplied markup inline would hand every plugin author a way to run code in the app.
/// </para>
/// </remarks>
/// <param name="message">The error message, when the icon could not be read.</param>
/// <param name="iconSVG">The read icon as SVG.</param>
/// <param name="iconDataUrl">The read icon as a data URL.</param>
/// <returns>True, when the icon could be read successfully.</returns>
// ReSharper disable once OutParameterValueIsAlwaysDiscarded.Local
// ReSharper disable once UnusedMethodReturnValue.Local
private bool TryInitIconSVG(out string message, out string iconSVG)
private bool TryInitIconDataUrl(out string message, out string iconDataUrl)
{
if (!this.State.Environment["ICON_SVG"].TryRead(out iconSVG))
if (!this.State.Environment["ICON_SVG"].TryRead<string>(out var iconSVG))
{
iconSVG = DEFAULT_ICON_SVG;
iconDataUrl = DEFAULT_ICON_DATA_URL;
message = "The field ICON_SVG does not exist or is not a valid string.";
return true;
}
if (string.IsNullOrWhiteSpace(iconSVG))
{
iconSVG = DEFAULT_ICON_SVG;
iconDataUrl = DEFAULT_ICON_DATA_URL;
message = "The field ICON_SVG is empty. The icon must be a non-empty string.";
return true;
}
if (!SvgIcon.TryCreateDataUrl(iconSVG, out iconDataUrl, out var issue))
{
iconDataUrl = DEFAULT_ICON_DATA_URL;
message = $"The field ICON_SVG is not a usable icon: {issue}";
return true;
}
message = string.Empty;
return true;
}
private static string CreateDefaultIconDataUrl()
{
SvgIcon.TryCreateDataUrl(DEFAULT_ICON_SVG, out var dataUrl, out _);
return dataUrl;
}
#endregion
}
@@ -16,8 +16,8 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
protected readonly List<string> PluginIssues = [];
/// <inheritdoc />
public string IconSVG { get; }
public string IconDataUrl { get; }
/// <inheritdoc />
public PluginType Type { get; }
@@ -88,14 +88,14 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
if (this is NoPlugin or NoPluginLanguage)
{
this.IsInternal = isInternal;
this.IconSVG = string.Empty;
this.IconDataUrl = string.Empty;
this.baseIssues = issues;
return;
}
// Notice: when no icon is specified, the default icon will be used.
this.TryInitIconSVG(out _, out var iconSVG);
this.IconSVG = iconSVG;
this.TryInitIconDataUrl(out _, out var iconDataUrl);
this.IconDataUrl = iconDataUrl;
if(this.TryInitId(out var issue, out var id))
{
@@ -297,13 +297,13 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
this.TryProcessEnterpriseApprovedAssistantPlugins(settingsTable, dryRun);
// Handle configured LLM providers:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun);
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun, this.PluginPath);
// Handle configured transcription providers:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, x => x.NextTranscriptionNum, mainTable, this.Id, ref this.configObjects, dryRun);
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, x => x.NextTranscriptionNum, mainTable, this.Id, ref this.configObjects, dryRun, this.PluginPath);
// Handle configured embedding providers:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, x => x.NextEmbeddingNum, mainTable, this.Id, ref this.configObjects, dryRun);
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, x => x.NextEmbeddingNum, mainTable, this.Id, ref this.configObjects, dryRun, this.PluginPath);
// Handle configured chat templates:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun, this.PluginPath);
@@ -598,4 +598,4 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
LOG.LogWarning("The table 'INTRODUCTIONS' entry at index {Index} does not contain a valid introduction (config plugin id: {ConfigPluginId}).", i, this.Id);
}
}
}
}
@@ -142,11 +142,11 @@ public sealed record PluginConfigurationObject
var (wasParsingSuccessful, configObject) = configObjectType switch
{
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject),
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, pluginPath, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject),
PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, pluginPath, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject),
PluginConfigurationObjectType.PROFILE => (Profile.TryParseProfileTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Profile.NO_PROFILE, configurationObject),
PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER => (TranscriptionProvider.TryParseTranscriptionProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != TranscriptionProvider.NONE, configurationObject),
PluginConfigurationObjectType.EMBEDDING_PROVIDER => (EmbeddingProvider.TryParseEmbeddingProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != EmbeddingProvider.NONE, configurationObject),
PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER => (TranscriptionProvider.TryParseTranscriptionProviderTable(i, luaObjectTable, configPluginId, pluginPath, out var configurationObject) && configurationObject != TranscriptionProvider.NONE, configurationObject),
PluginConfigurationObjectType.EMBEDDING_PROVIDER => (EmbeddingProvider.TryParseEmbeddingProviderTable(i, luaObjectTable, configPluginId, pluginPath, out var configurationObject) && configurationObject != EmbeddingProvider.NONE, configurationObject),
PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY => (DataDocumentAnalysisPolicy.TryProcessConfiguration(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject is DataDocumentAnalysisPolicy, configurationObject),
_ => (false, NoConfigurationObject.INSTANCE)
@@ -426,4 +426,4 @@ public sealed record PluginConfigurationObject
return wasConfigurationChanged;
}
}
}
@@ -5,7 +5,7 @@ public sealed class PluginMetadata(PluginBase plugin, string localPath, bool isM
#region Implementation of IPluginMetadata
/// <inheritdoc />
public string IconSVG { get; } = plugin.IconSVG;
public string IconDataUrl { get; } = plugin.IconDataUrl;
/// <inheritdoc />
public PluginType Type { get; } = plugin.Type;