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

@@ -8320,6 +8320,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1402243995"] = "Updates are mana
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library."
-- Trademarks & Brand Assets
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421823619"] = "Trademarks & Brand Assets"
-- Copies the allowed host pattern to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1513592659"] = "Copies the allowed host pattern to the clipboard"
@@ -8329,6 +8332,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1533382393"] = "Waiting for the
-- Encryption secret: is not configured
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Encryption secret: is not configured"
-- Organizations can replace these logos with their own icons through a configuration plugin. When your organization does so, it is responsible for holding the rights to the icons it provides.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T158845920"] = "Organizations can replace these logos with their own icons through a configuration plugin. When your organization does so, it is responsible for holding the rights to the icons it provides."
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1596483935"] = "AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active."
@@ -8344,6 +8350,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1630237140"] = "AI Studio create
-- Plugin directory:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1698127325"] = "Plugin directory:"
-- Several of the provider logos in AI Studio use the icon paths and brand colors published by the Simple Icons project, which releases them into the public domain under CC0. The trademarks themselves are not part of that release and remain the property of their respective owners.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1699089284"] = "Several of the provider logos in AI Studio use the icon paths and brand colors published by the Simple Icons project, which releases them into the public domain under CC0. The trademarks themselves are not part of that release and remain the property of their respective owners."
-- Consent:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T171952677"] = "Consent:"
@@ -8386,6 +8395,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Copies the follo
-- Copies the server URL to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2037899437"] = "Copies the server URL to the clipboard"
-- AI Studio shows the logo of an AI provider next to its entry, so you can see at a glance which service a provider connects to. All product names, logos, and trademarks are the property of their respective owners. Their use here identifies compatible services and implies no endorsement, sponsorship, or business relationship between MindWork AI Studio and these companies.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2124655767"] = "AI Studio shows the logo of an AI provider next to its entry, so you can see at a glance which service a provider connects to. All product names, logos, and trademarks are the property of their respective owners. Their use here identifies compatible services and implies no endorsement, sponsorship, or business relationship between MindWork AI Studio and these companies."
-- The windows-rs project provides access to Windows APIs from Rust. We use several libraries from this project: windows-registry is used to read the desired configuration in Windows enterprise environments. The windows and windows-collections libraries are used to open the native Windows share dialog, e.g., when you share a plugin with others.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2146481269"] = "The windows-rs project provides access to Windows APIs from Rust. We use several libraries from this project: windows-registry is used to read the desired configuration in Windows enterprise environments. The windows and windows-collections libraries are used to open the native Windows share dialog, e.g., when you share a plugin with others."
@@ -8650,6 +8662,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3764549776"] = "Username provide
-- Allowed host:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3774270763"] = "Allowed host:"
-- Some of these logos come from the Simple Icons project, which publishes them under CC0. The remaining ones were taken from the official brand resources of the respective provider. Every logo ships with AI Studio and is loaded from your device, so showing it never sends a request to the provider.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3775183188"] = "Some of these logos come from the Simple Icons project, which publishes them under CC0. The remaining ones were taken from the official brand resources of the respective provider. Every logo ships with AI Studio and is loaded from your device, so showing it never sends a request to the provider."
-- Configuration source:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3801531724"] = "Configuration source:"
@@ -1,2 +1,13 @@
@inherits MSGComponentBase
<ConfigurationSelect IsLocked="@this.IsLocked" OptionDescription="@T("Preselected provider")" Disabled="@(() => this.Disabled())" OptionHelp="@this.HelpText()" Data="@this.FilteredData()" SelectedValue="@this.SelectedValue" SelectionUpdate="@this.SelectionUpdate"/>
<ConfigurationSelect IsLocked="@this.IsLocked" OptionDescription="@T("Preselected provider")" Disabled="@(() => this.Disabled())" OptionHelp="@this.HelpText()" Data="@this.FilteredData()" SelectedValue="@this.SelectedValue" SelectionUpdate="@this.SelectionUpdate">
<ItemTemplate Context="providerData">
@if (this.GetProvider(providerData.Value) is { } provider)
{
<ProviderLabel ProviderSettings="@provider" Text="@providerData.Name" />
}
else
{
@providerData.Name
}
</ItemTemplate>
</ConfigurationSelect>
@@ -32,7 +32,7 @@ public partial class ConfigurationProviderSelection : MSGComponentBase
[Parameter]
public Func<bool> IsLocked { get; set; } = () => false;
private IEnumerable<ConfigurationSelectData<string>> FilteredData()
{
if(this.Component is not Tools.Components.NONE and not Tools.Components.APP_SETTINGS)
@@ -51,6 +51,15 @@ public partial class ConfigurationProviderSelection : MSGComponentBase
}
}
private AIStudio.Settings.Provider? GetProvider(string providerId)
{
if (string.IsNullOrWhiteSpace(providerId))
return null;
var provider = this.SettingsManager.GetProviderById(providerId);
return provider == AIStudio.Settings.Provider.NONE ? null : provider;
}
#region Overrides of MSGComponentBase
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
@@ -5,7 +5,14 @@
@foreach (var data in this.Data)
{
<MudSelectItem Value="@data.Value">
@data.Name
@if (this.ItemTemplate is null)
{
@data.Name
}
else
{
@this.ItemTemplate(data)
}
</MudSelectItem>
}
</MudSelect>
</MudSelect>
@@ -33,7 +33,13 @@ public partial class ConfigurationSelect<TConfig> : ConfigurationBaseCore
/// </summary>
[Parameter]
public Func<TConfig, Task> SelectionUpdateAsync { get; set; } = _ => Task.CompletedTask;
/// <summary>
/// Optional template used to render an item in the list.
/// </summary>
[Parameter]
public RenderFragment<ConfigurationSelectData<TConfig>>? ItemTemplate { get; set; }
#region Overrides of ConfigurationBase
/// <inheritdoc />
@@ -54,4 +60,4 @@ public partial class ConfigurationSelect<TConfig> : ConfigurationBaseCore
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}
}
}
@@ -0,0 +1 @@
<img src="@this.IconUrl" alt="" aria-hidden="true" draggable="false" class="@this.CssClass" style="@this.Style" />
@@ -0,0 +1,59 @@
using AIStudio.Provider;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Shows the icon of a provider.
/// </summary>
/// <remarks>
/// The icon is rendered as an image instead of inline SVG. That way the browser treats the icon as
/// a standalone, script-less document, which matters for the custom icons a configuration plugin
/// may supply.
/// </remarks>
public partial class ProviderIcon : ComponentBase
{
/// <summary>
/// The configured provider whose icon should be shown. Takes precedence over ProviderType.
/// </summary>
[Parameter]
public AIStudio.Settings.Provider? ProviderSettings { get; set; }
/// <summary>
/// The LLM provider whose icon should be shown when no ProviderSettings was given.
/// </summary>
[Parameter]
public LLMProviders ProviderType { get; set; } = LLMProviders.NONE;
/// <summary>
/// The validated custom icon supplied by a configuration plugin.
/// </summary>
[Parameter]
public string CustomIconDataUrl { get; set; } = string.Empty;
/// <summary>
/// Additional CSS class for the icon.
/// </summary>
[Parameter]
public string Class { get; set; } = string.Empty;
/// <summary>
/// Additional inline style for the icon.
/// </summary>
[Parameter]
public string Style { get; set; } = string.Empty;
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
/// <summary>
/// The provider-icon class carries the sizing from app.css. Callers add to it instead of
/// replacing it, so an icon cannot lose its size by setting a class of its own.
/// </summary>
private string CssClass => $"provider-icon {this.Class}".TrimEnd();
private string IconUrl => this.ProviderSettings?.GetIconUrl(this.SettingsManager.IsDarkMode)
?? this.ProviderType.GetIconUrl(this.SettingsManager.IsDarkMode, this.CustomIconDataUrl);
}
@@ -0,0 +1,4 @@
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Wrap="Wrap.NoWrap">
<ProviderIcon ProviderSettings="@this.ProviderSettings" ProviderType="@this.ProviderType" CustomIconDataUrl="@this.CustomIconDataUrl" />
<MudText Class="@this.TextClass">@this.Text</MudText>
</MudStack>
@@ -0,0 +1,46 @@
using AIStudio.Provider;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Shows a provider icon next to its name.
/// </summary>
/// <remarks>
/// Providers appear in select items, in table cells, and in group headers. All of them need the
/// same icon and text pairing, so this component owns that layout once instead of repeating it at
/// every call site.
/// </remarks>
public partial class ProviderLabel : ComponentBase
{
/// <summary>
/// The configured provider whose icon should be shown. Takes precedence over ProviderType.
/// </summary>
[Parameter]
public AIStudio.Settings.Provider? ProviderSettings { get; set; }
/// <summary>
/// The LLM provider whose icon should be shown when no ProviderSettings was given.
/// </summary>
[Parameter]
public LLMProviders ProviderType { get; set; } = LLMProviders.NONE;
/// <summary>
/// The validated custom icon supplied by a configuration plugin.
/// </summary>
[Parameter]
public string CustomIconDataUrl { get; set; } = string.Empty;
/// <summary>
/// The text shown next to the icon.
/// </summary>
[Parameter]
public string Text { get; set; } = string.Empty;
/// <summary>
/// Additional CSS class for the text.
/// </summary>
[Parameter]
public string TextClass { get; set; } = string.Empty;
}
@@ -1,11 +1,11 @@
@using AIStudio.Settings
@inherits MSGComponentBase
<MudSelect T="Provider" Value="@this.ProviderSettings" ValueChanged="@this.SelectionChanged" Validation="@this.ValidateProvider" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Apps" Margin="Margin.Dense" Label="@T("Provider")" Class="mb-3 rounded-lg" OuterClass="flex-grow-0" Variant="Variant.Outlined" Disabled="@this.Disabled">
<MudSelect T="Provider" Value="@this.ProviderSettings" ValueChanged="@this.SelectionChanged" Validation="@this.ValidateProvider" Margin="Margin.Dense" Label="@T("Provider")" Class="mb-3 rounded-lg" OuterClass="flex-grow-0" Variant="Variant.Outlined" Disabled="@this.Disabled">
@foreach (var providerItem in this.GetAvailableProviderSelectionItems())
{
<MudSelectItem Value="@providerItem.Provider">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="w-100" Wrap="Wrap.NoWrap">
<MudText Class="me-2">@providerItem.Provider</MudText>
<ProviderLabel ProviderSettings="@providerItem.Provider" Text="@providerItem.Provider.ToString()" TextClass="me-2" />
@if (providerItem.CapabilityIcons.Count > 0)
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Wrap="Wrap.NoWrap" Class="flex-grow-0">
@@ -105,12 +105,24 @@ public partial class ProviderSelection : MSGComponentBase
#region Overrides of MSGComponentBase
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED)
this.StateHasChanged();
{
//
// We hold a copy of the provider record, which is a snapshot taken when it was selected.
// Once the user edits that provider, our copy is stale and would keep showing the old
// name and the old icon, so we resolve it again and hand the fresh one to our parent:
//
var updatedProvider = this.SettingsManager.GetProviderById(this.ProviderSettings.Id);
if (updatedProvider != AIStudio.Settings.Provider.NONE && updatedProvider != this.ProviderSettings)
{
this.ProviderSettings = updatedProvider;
await this.ProviderSettingsChanged.InvokeAsync(updatedProvider);
}
return Task.CompletedTask;
this.StateHasChanged();
}
}
#endregion
@@ -118,4 +130,4 @@ public partial class ProviderSelection : MSGComponentBase
private readonly record struct CapabilityIcon(string Icon, string Tooltip);
private readonly record struct ProviderSelectionItem(AIStudio.Settings.Provider Provider, IReadOnlyList<CapabilityIcon> CapabilityIcons);
}
}
@@ -37,7 +37,18 @@
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked">
<ItemTemplate Context="providerData">
@if (this.GetTranscriptionProvider(providerData.Value) is { } provider)
{
<ProviderLabel ProviderType="@provider.UsedLLMProvider" CustomIconDataUrl="@provider.CustomIconDataUrl" Text="@providerData.Name" />
}
else
{
@providerData.Name
}
</ItemTemplate>
</ConfigurationSelect>
<ConfigurationShortcut Data="@this.VoiceRecordingShortcut" OptionDescription="@T("Voice recording shortcut")" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
}
@@ -106,6 +106,12 @@ public partial class SettingsPanelApp : SettingsPanelBase
}
}
private TranscriptionProvider? GetTranscriptionProvider(string providerId)
{
var provider = this.SettingsManager.GetTranscriptionProviderById(providerId);
return provider == TranscriptionProvider.NONE ? null : provider;
}
private void UpdatePreviewFeatures(PreviewVisibility previewVisibility)
{
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
@@ -30,11 +30,16 @@
</HeaderContent>
<GroupHeaderTemplate>
<MudTh Class="provider-group-header" colspan="4">
@context.Key
@if (context.Key is LLMProviders llmProvider)
{
<ProviderLabel ProviderType="@llmProvider" Text="@llmProvider.ToName()" />
}
</MudTh>
</GroupHeaderTemplate>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd>
<ProviderLabel ProviderType="@context.UsedLLMProvider" CustomIconDataUrl="@context.CustomIconDataUrl" Text="@context.Name" />
</MudTd>
<MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd>
<MudTd>
@@ -19,7 +19,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
{
Expandable = true,
IsInitiallyExpanded = false,
Selector = provider => provider.UsedLLMProvider.ToName(),
Selector = provider => provider.UsedLLMProvider,
};
[Parameter]
@@ -82,6 +82,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
{ x => x.DataId, embeddingProvider.Id },
{ x => x.DataName, embeddingProvider.Name },
{ x => x.DataLLMProvider, embeddingProvider.UsedLLMProvider },
{ x => x.DataCustomIconDataUrl, embeddingProvider.CustomIconDataUrl },
{ x => x.DataModel, embeddingProvider.Model },
{ x => x.DataHostname, embeddingProvider.Hostname },
{ x => x.IsSelfHosted, embeddingProvider.IsSelfHosted },
@@ -22,11 +22,16 @@
</HeaderContent>
<GroupHeaderTemplate>
<MudTh Class="provider-group-header" colspan="4">
@context.Key
@if (context.Key is LLMProviders llmProvider)
{
<ProviderLabel ProviderType="@llmProvider" Text="@llmProvider.ToName()" />
}
</MudTh>
</GroupHeaderTemplate>
<RowTemplate>
<MudTd>@context.InstanceName</MudTd>
<MudTd>
<ProviderLabel ProviderSettings="@context" Text="@context.InstanceName" />
</MudTd>
<MudTd>@this.GetLLMProviderModelName(context)</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@@ -1,7 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using AIStudio.Dialogs;
using AIStudio.Provider;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
@@ -20,7 +19,7 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
{
Expandable = true,
IsInitiallyExpanded = false,
Selector = provider => provider.UsedLLMProvider.ToName(),
Selector = provider => provider.UsedLLMProvider,
};
[Parameter]
@@ -77,6 +76,7 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
{ x => x.DataId, provider.Id },
{ x => x.DataInstanceName, provider.InstanceName },
{ x => x.DataLLMProvider, provider.UsedLLMProvider },
{ x => x.DataCustomIconDataUrl, provider.CustomIconDataUrl },
{ x => x.DataModel, provider.Model },
{ x => x.DataHostname, provider.Hostname },
{ x => x.IsSelfHosted, provider.IsSelfHosted },
@@ -26,11 +26,16 @@
</HeaderContent>
<GroupHeaderTemplate>
<MudTh Class="provider-group-header" colspan="4">
@context.Key
@if (context.Key is LLMProviders llmProvider)
{
<ProviderLabel ProviderType="@llmProvider" Text="@llmProvider.ToName()" />
}
</MudTh>
</GroupHeaderTemplate>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd>
<ProviderLabel ProviderType="@context.UsedLLMProvider" CustomIconDataUrl="@context.CustomIconDataUrl" Text="@context.Name" />
</MudTd>
<MudTd>@this.GetTranscriptionProviderModelName(context)</MudTd>
<MudTd>
@@ -1,5 +1,4 @@
using AIStudio.Dialogs;
using AIStudio.Provider;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
@@ -18,7 +17,7 @@ public partial class SettingsPanelTranscription : SettingsPanelProviderBase
{
Expandable = true,
IsInitiallyExpanded = false,
Selector = provider => provider.UsedLLMProvider.ToName(),
Selector = provider => provider.UsedLLMProvider,
};
[Parameter]
@@ -81,6 +80,7 @@ public partial class SettingsPanelTranscription : SettingsPanelProviderBase
{ x => x.DataId, transcriptionProvider.Id },
{ x => x.DataName, transcriptionProvider.Name },
{ x => x.DataLLMProvider, transcriptionProvider.UsedLLMProvider },
{ x => x.DataCustomIconDataUrl, transcriptionProvider.CustomIconDataUrl },
{ x => x.DataModel, transcriptionProvider.Model },
{ x => x.DataHostname, transcriptionProvider.Hostname },
{ x => x.IsSelfHosted, transcriptionProvider.IsSelfHosted },
@@ -50,7 +50,14 @@
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">
@embedding.Name
@if (this.GetEmbeddingProvider(embedding.Value) is { } provider)
{
<ProviderLabel ProviderType="@provider.UsedLLMProvider" CustomIconDataUrl="@provider.CustomIconDataUrl" Text="@embedding.Name" />
}
else
{
@embedding.Name
}
</MudSelectItem>
}
</MudSelect>
@@ -109,4 +116,4 @@
}
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -95,6 +95,12 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
}
#endregion
private EmbeddingProvider? GetEmbeddingProvider(string providerId)
{
var provider = this.SettingsManager.GetEmbeddingProviderById(providerId);
return provider == EmbeddingProvider.NONE ? null : provider;
}
private bool SelectedCloudEmbedding => !(this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsTrustedForDataSourceSecurityChecks(this.SettingsManager) ?? false);
@@ -124,4 +130,4 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
}
private void Cancel() => this.MudDialog.Cancel();
}
}
@@ -50,7 +50,14 @@
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">
@embedding.Name
@if (this.GetEmbeddingProvider(embedding.Value) is { } provider)
{
<ProviderLabel ProviderType="@provider.UsedLLMProvider" CustomIconDataUrl="@provider.CustomIconDataUrl" Text="@embedding.Name" />
}
else
{
@embedding.Name
}
</MudSelectItem>
}
</MudSelect>
@@ -109,4 +116,4 @@
}
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -95,6 +95,12 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
}
#endregion
private EmbeddingProvider? GetEmbeddingProvider(string providerId)
{
var provider = this.SettingsManager.GetEmbeddingProviderById(providerId);
return provider == EmbeddingProvider.NONE ? null : provider;
}
private bool SelectedCloudEmbedding => !(this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsTrustedForDataSourceSecurityChecks(this.SettingsManager) ?? false);
@@ -124,4 +130,4 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
}
private void Cancel() => this.MudDialog.Cancel();
}
}
@@ -13,13 +13,13 @@
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
<MudStack Row="@true" AlignItems="AlignItems.Center">
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingProvider">
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{
if (provider.ProvideEmbeddingAPI() || provider is LLMProviders.NONE)
{
<MudSelectItem Value="@provider">
@provider.ToName()
<ProviderLabel ProviderType="@provider" CustomIconDataUrl="@(provider == this.DataLLMProvider ? this.DataCustomIconDataUrl : string.Empty)" Text="@provider.ToName()" />
</MudSelectItem>
}
}
@@ -152,4 +152,4 @@
}
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -56,6 +56,12 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
/// </summary>
[Parameter]
public LLMProviders DataLLMProvider { get; set; } = LLMProviders.NONE;
/// <summary>
/// The validated custom icon supplied by a configuration plugin.
/// </summary>
[Parameter]
public string DataCustomIconDataUrl { get; set; } = string.Empty;
/// <summary>
/// The embedding model to use.
@@ -144,6 +150,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
Host = this.DataHost,
IsEnterpriseConfiguration = this.IsEnterpriseConfiguration,
EnterpriseConfigurationPluginId = Guid.Empty,
CustomIconDataUrl = this.DataCustomIconDataUrl,
};
}
@@ -343,4 +350,4 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
};
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
}
}
@@ -18,15 +18,12 @@
ValueChanged="@this.OnProviderChanged"
Label="@T("Provider")"
Class="mb-3"
OpenIcon="@Icons.Material.Filled.AccountBalance"
AdornmentColor="Color.Info"
Adornment="Adornment.Start"
Disabled="@this.IsEnterpriseConfiguration"
Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{
<MudSelectItem Value="@provider">
@provider.ToName()
<ProviderLabel ProviderType="@provider" CustomIconDataUrl="@(provider == this.DataLLMProvider ? this.DataCustomIconDataUrl : string.Empty)" Text="@provider.ToName()" />
</MudSelectItem>
}
</MudSelect>
@@ -78,6 +78,12 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
/// </summary>
[Parameter]
public LLMProviders DataLLMProvider { get; set; } = LLMProviders.NONE;
/// <summary>
/// The validated custom icon supplied by a configuration plugin.
/// </summary>
[Parameter]
public string DataCustomIconDataUrl { get; set; } = string.Empty;
/// <summary>
/// The LLM model to use, e.g., GPT-4o.
@@ -184,6 +190,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
HFInferenceProvider = this.HFInferenceProviderId,
AdditionalJsonApiParameters = this.AdditionalJsonApiParameters,
CapabilityOverrides = this.capabilityOverrides.HasOverrides ? this.capabilityOverrides : null,
CustomIconDataUrl = this.DataCustomIconDataUrl,
};
}
@@ -13,13 +13,13 @@
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
<MudStack Row="@true" AlignItems="AlignItems.Center">
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingProvider">
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" Disabled="@this.IsEnterpriseConfiguration" Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{
if (provider.ProvideTranscriptionAPI() || provider is LLMProviders.NONE)
{
<MudSelectItem Value="@provider">
@provider.ToName()
<ProviderLabel ProviderType="@provider" CustomIconDataUrl="@(provider == this.DataLLMProvider ? this.DataCustomIconDataUrl : string.Empty)" Text="@provider.ToName()" />
</MudSelectItem>
}
}
@@ -163,4 +163,4 @@
}
</MudButton>
</DialogActions>
</MudDialog>
</MudDialog>
@@ -56,6 +56,12 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
/// </summary>
[Parameter]
public LLMProviders DataLLMProvider { get; set; } = LLMProviders.NONE;
/// <summary>
/// The validated custom icon supplied by a configuration plugin.
/// </summary>
[Parameter]
public string DataCustomIconDataUrl { get; set; } = string.Empty;
/// <summary>
/// The transcription model to use.
@@ -159,6 +165,7 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
Host = this.DataHost,
IsEnterpriseConfiguration = this.IsEnterpriseConfiguration,
EnterpriseConfigurationPluginId = Guid.Empty,
CustomIconDataUrl = this.DataCustomIconDataUrl,
};
}
@@ -358,4 +365,4 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
};
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
}
}
@@ -393,8 +393,20 @@
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="@T("This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
<ThirdPartyComponent Name="wikEd diff" Developer="Cacycle & Open Source Community" LicenseName="None (public domain)" LicenseUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff#License" RepositoryUrl="https://en.wikipedia.org/wiki/User:Cacycle/diff" UseCase="@T("This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant.")"/>
<ThirdPartyComponent Name="CodeJar" Developer="Anton Medvedev" LicenseName="MIT" LicenseUrl="https://github.com/antonmedv/codejar/blob/master/LICENSE" RepositoryUrl="https://github.com/antonmedv/codejar/" UseCase="@T("CodeJar is a lightweight embeddable code editor for the browser.")"/>
<ThirdPartyComponent Name="Simple Icons" Developer="Simple Icons Contributors & Open Source Community" LicenseName="CC0-1.0" LicenseUrl="https://github.com/simple-icons/simple-icons/blob/16.21.0/LICENSE.md" RepositoryUrl="https://github.com/simple-icons/simple-icons" UseCase="@T("Several of the provider logos in AI Studio use the icon paths and brand colors published by the Simple Icons project, which releases them into the public domain under CC0. The trademarks themselves are not part of that release and remain the property of their respective owners.")"/>
</MudGrid>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Copyright" HeaderText="@T("Trademarks & Brand Assets")">
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("AI Studio shows the logo of an AI provider next to its entry, so you can see at a glance which service a provider connects to. All product names, logos, and trademarks are the property of their respective owners. Their use here identifies compatible services and implies no endorsement, sponsorship, or business relationship between MindWork AI Studio and these companies.")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Some of these logos come from the Simple Icons project, which publishes them under CC0. The remaining ones were taken from the official brand resources of the respective provider. Every logo ships with AI Studio and is loaded from your device, so showing it never sends a request to the provider.")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body1">
@T("Organizations can replace these logos with their own icons through a configuration plugin. When your organization does so, it is responsible for holding the rights to the icons it provides.")
</MudJustifiedText>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Verified" HeaderText="License: FSL-1.1-MIT">
<MudMarkdown Value="@LICENSE" Props="Markdown.DefaultConfig" MarkdownPipeline="Markdown.SAFE_MARKDOWN_PIPELINE"/>
</ExpansionPanel>
+4 -1
View File
@@ -63,7 +63,10 @@
<MudTd>
<MudAvatar Size="Size.Medium" Style="width: 2em; height: 2em;" Class="align-content-stretch">
<div class="plugin-icon-container">
@((MarkupString)context.IconSVG)
@if (!string.IsNullOrEmpty(context.IconDataUrl))
{
<img src="@context.IconDataUrl" alt="" aria-hidden="true" draggable="false" />
}
</div>
</MudAvatar>
</MudTd>
@@ -94,6 +94,19 @@ CONFIG["LLM_PROVIDERS"] = {}
-- -- Please do not add the enclosing curly braces {} here. Also, no trailing comma is allowed.
-- ["AdditionalJsonApiParameters"] = "",
--
-- -- Optional: replace the built-in provider logo with a project-specific icon.
-- -- The path is relative to this plugin.lua and must point to an SVG file inside
-- -- this plugin directory, for example: assets/project-icon.svg. Absolute paths,
-- -- parent-directory segments (..), links leaving the plugin directory, files
-- -- larger than 32 KiB, and files which are not well-formed SVG are rejected.
-- -- An invalid or missing icon logs a warning while the provider still loads
-- -- with its built-in logo. AI Studio shows every icon in an isolated image
-- -- element, so scripts or external references inside an SVG never run; for the
-- -- same reason, the icon cannot inherit colors from the app and has to bring
-- -- its own. Provide a single icon with enough contrast on both light and dark
-- -- surfaces.
-- -- ["IconPath"] = "assets/project-icon.svg",
--
-- -- Optional: expert capability overrides.
-- -- Allowed keys are exactly:
-- -- AUDIO_INPUT, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, VIDEO_INPUT,
@@ -146,6 +159,10 @@ CONFIG["TRANSCRIPTION_PROVIDERS"] = {}
-- ["Host"] = "WHISPER_CPP",
-- ["Hostname"] = "<https address of the server>",
--
-- -- Optional: project-specific SVG icon. The same path, size, rendering, and
-- -- fallback rules described for IconPath under LLM_PROVIDERS apply here.
-- -- ["IconPath"] = "assets/project-icon.svg",
--
-- -- Optional: Encrypted API key (see LLM_PROVIDERS example for details)
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
--
@@ -173,6 +190,10 @@ CONFIG["EMBEDDING_PROVIDERS"] = {}
-- ["Host"] = "OLLAMA",
-- ["Hostname"] = "<https address of the server>",
--
-- -- Optional: project-specific SVG icon. The same path, size, rendering, and
-- -- fallback rules described for IconPath under LLM_PROVIDERS apply here.
-- -- ["IconPath"] = "assets/project-icon.svg",
--
-- -- Optional: Encrypted API key (see LLM_PROVIDERS example for details)
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
--
@@ -8322,6 +8322,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1402243995"] = "Updates werden v
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "Diese Bibliothek wird verwendet, um die MudBlazor-Bibliothek zu erweitern. Sie stellt zusätzliche Komponenten bereit, die nicht Teil der MudBlazor-Bibliothek sind."
-- Trademarks & Brand Assets
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421823619"] = "Marken und Markenressourcen"
-- Copies the allowed host pattern to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1513592659"] = "Kopiert das zulässige Hostmuster in die Zwischenablage"
@@ -8331,6 +8334,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1533382393"] = "Warten auf das K
-- Encryption secret: is not configured
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Geheimnis für die Verschlüsselung: ist nicht konfiguriert"
-- Organizations can replace these logos with their own icons through a configuration plugin. When your organization does so, it is responsible for holding the rights to the icons it provides.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T158845920"] = "Organisationen können diese Logos über ein Konfigurations-Plugin durch eigene Symbole ersetzen. In diesem Fall ist Ihre Organisation dafür verantwortlich, die Rechte an den bereitgestellten Symbolen zu besitzen."
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1596483935"] = "AI Studio wird mit Unternehmenskonfigurationen und Konfigurationsservern betrieben. Die Konfigurations-Plugins sind aktiv."
@@ -8346,6 +8352,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1630237140"] = "AI Studio erstel
-- Plugin directory:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1698127325"] = "Plugin-Verzeichnis:"
-- Several of the provider logos in AI Studio use the icon paths and brand colors published by the Simple Icons project, which releases them into the public domain under CC0. The trademarks themselves are not part of that release and remain the property of their respective owners.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1699089284"] = "Mehrere Anbieterlogos in AI Studio verwenden die vom Projekt Simple Icons veröffentlichten Icon-Pfade und Markenfarben, die unter CC0 gemeinfrei bereitgestellt werden. Die Marken selbst sind nicht Teil dieser Freigabe und bleiben Eigentum ihrer jeweiligen Inhaber."
-- Consent:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T171952677"] = "Zustimmung:"
@@ -8388,6 +8397,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Kopiert Folgende
-- Copies the server URL to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2037899437"] = "Kopiert die Server-URL in die Zwischenablage"
-- AI Studio shows the logo of an AI provider next to its entry, so you can see at a glance which service a provider connects to. All product names, logos, and trademarks are the property of their respective owners. Their use here identifies compatible services and implies no endorsement, sponsorship, or business relationship between MindWork AI Studio and these companies.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2124655767"] = "AI Studio zeigt neben dem jeweiligen Eintrag das Logo eines KI-Anbieters, damit Sie auf einen Blick sehen können, mit welchem Dienst ein Anbieter verbunden ist. Alle Produktnamen, Logos und Marken sind Eigentum ihrer jeweiligen Inhaber. Ihre Verwendung dient hier ausschließlich der Kennzeichnung kompatibler Dienste und bedeutet keine Empfehlung, Unterstützung oder Geschäftsbeziehung zwischen MindWork AI Studio und diesen Unternehmen."
-- The windows-rs project provides access to Windows APIs from Rust. We use several libraries from this project: windows-registry is used to read the desired configuration in Windows enterprise environments. The windows and windows-collections libraries are used to open the native Windows share dialog, e.g., when you share a plugin with others.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2146481269"] = "Das Projekt windows-rs ermöglicht den Zugriff auf Windows-APIs aus Rust. Wir verwenden mehrere Bibliotheken aus diesem Projekt: windows-registry wird verwendet, um die gewünschte Konfiguration in Windows-Unternehmensumgebungen auszulesen. Die Bibliotheken windows und windows-collections werden verwendet, um den nativen Windows-Dialog zum Teilen zu öffnen, zum Beispiel wenn Sie ein Plugin mit anderen teilen."
@@ -8652,6 +8664,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3764549776"] = "Vom Betriebssyst
-- Allowed host:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3774270763"] = "Zulässiger Host:"
-- Some of these logos come from the Simple Icons project, which publishes them under CC0. The remaining ones were taken from the official brand resources of the respective provider. Every logo ships with AI Studio and is loaded from your device, so showing it never sends a request to the provider.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3775183188"] = "Einige dieser Logos stammen aus dem Projekt Simple Icons, das sie unter CC0 veröffentlicht. Die übrigen wurden den offiziellen Markenressourcen des jeweiligen Anbieters entnommen. Jedes Logo wird mit AI Studio ausgeliefert und von Ihrem Gerät geladen. Beim Anzeigen wird daher keine Anfrage an den Anbieter gesendet."
-- Configuration source:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3801531724"] = "Quelle der Konfiguration:"
@@ -8322,6 +8322,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1402243995"] = "Updates are mana
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library."
-- Trademarks & Brand Assets
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421823619"] = "Trademarks & Brand Assets"
-- Copies the allowed host pattern to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1513592659"] = "Copies the allowed host pattern to the clipboard"
@@ -8331,6 +8334,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1533382393"] = "Waiting for the
-- Encryption secret: is not configured
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Encryption secret: is not configured"
-- Organizations can replace these logos with their own icons through a configuration plugin. When your organization does so, it is responsible for holding the rights to the icons it provides.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T158845920"] = "Organizations can replace these logos with their own icons through a configuration plugin. When your organization does so, it is responsible for holding the rights to the icons it provides."
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1596483935"] = "AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active."
@@ -8346,6 +8352,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1630237140"] = "AI Studio create
-- Plugin directory:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1698127325"] = "Plugin directory:"
-- Several of the provider logos in AI Studio use the icon paths and brand colors published by the Simple Icons project, which releases them into the public domain under CC0. The trademarks themselves are not part of that release and remain the property of their respective owners.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1699089284"] = "Several of the provider logos in AI Studio use the icon paths and brand colors published by the Simple Icons project, which releases them into the public domain under CC0. The trademarks themselves are not part of that release and remain the property of their respective owners."
-- Consent:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T171952677"] = "Consent:"
@@ -8388,6 +8397,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Copies the follo
-- Copies the server URL to the clipboard
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2037899437"] = "Copies the server URL to the clipboard"
-- AI Studio shows the logo of an AI provider next to its entry, so you can see at a glance which service a provider connects to. All product names, logos, and trademarks are the property of their respective owners. Their use here identifies compatible services and implies no endorsement, sponsorship, or business relationship between MindWork AI Studio and these companies.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2124655767"] = "AI Studio shows the logo of an AI provider next to its entry, so you can see at a glance which service a provider connects to. All product names, logos, and trademarks are the property of their respective owners. Their use here identifies compatible services and implies no endorsement, sponsorship, or business relationship between MindWork AI Studio and these companies."
-- The windows-rs project provides access to Windows APIs from Rust. We use several libraries from this project: windows-registry is used to read the desired configuration in Windows enterprise environments. The windows and windows-collections libraries are used to open the native Windows share dialog, e.g., when you share a plugin with others.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2146481269"] = "The windows-rs project provides access to Windows APIs from Rust. We use several libraries from this project: windows-registry is used to read the desired configuration in Windows enterprise environments. The windows and windows-collections libraries are used to open the native Windows share dialog, e.g., when you share a plugin with others."
@@ -8652,6 +8664,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3764549776"] = "Username provide
-- Allowed host:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3774270763"] = "Allowed host:"
-- Some of these logos come from the Simple Icons project, which publishes them under CC0. The remaining ones were taken from the official brand resources of the respective provider. Every logo ships with AI Studio and is loaded from your device, so showing it never sends a request to the provider.
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3775183188"] = "Some of these logos come from the Simple Icons project, which publishes them under CC0. The remaining ones were taken from the official brand resources of the respective provider. Every logo ships with AI Studio and is loaded from your device, so showing it never sends a request to the provider."
-- Configuration source:
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3801531724"] = "Configuration source:"
@@ -0,0 +1,42 @@
namespace AIStudio.Provider;
public static class LLMProvidersIconExtensions
{
private const string ICON_ROOT = "/images/provider-icons";
private const string SVG_DATA_URL_PREFIX = "data:image/svg+xml;base64,";
public static string GetIconUrl(this AIStudio.Settings.Provider provider, bool isDarkMode)
=> provider.UsedLLMProvider.GetIconUrl(isDarkMode, provider.CustomIconDataUrl);
public static string GetIconUrl(this LLMProviders provider, bool isDarkMode, string? customIconDataUrl)
{
if (customIconDataUrl?.StartsWith(SVG_DATA_URL_PREFIX, StringComparison.Ordinal) == true)
return customIconDataUrl;
return provider.GetIconUrl(isDarkMode);
}
public static string GetIconUrl(this LLMProviders provider, bool isDarkMode) => provider switch
{
LLMProviders.NONE => $"{ICON_ROOT}/provider{DarkVariant(isDarkMode)}.svg",
LLMProviders.OPEN_AI => $"{ICON_ROOT}/openai{DarkVariant(isDarkMode)}.svg",
LLMProviders.ANTHROPIC => $"{ICON_ROOT}/anthropic{DarkVariant(isDarkMode)}.svg",
LLMProviders.MISTRAL => $"{ICON_ROOT}/mistral.svg",
LLMProviders.GOOGLE => $"{ICON_ROOT}/google.svg",
LLMProviders.X => $"{ICON_ROOT}/x{DarkVariant(isDarkMode)}.svg",
LLMProviders.DEEP_SEEK => $"{ICON_ROOT}/deepseek.svg",
LLMProviders.ALIBABA_CLOUD => $"{ICON_ROOT}/alibaba-cloud.svg",
LLMProviders.PERPLEXITY => $"{ICON_ROOT}/perplexity.svg",
LLMProviders.OPEN_ROUTER => $"{ICON_ROOT}/openrouter.svg",
LLMProviders.HETZNER => $"{ICON_ROOT}/hetzner.svg",
LLMProviders.GROQ => $"{ICON_ROOT}/groq.svg",
LLMProviders.FIREWORKS => $"{ICON_ROOT}/fireworks.svg",
LLMProviders.HUGGINGFACE => $"{ICON_ROOT}/hugging-face.svg",
LLMProviders.SELF_HOSTED => $"{ICON_ROOT}/self-hosted{DarkVariant(isDarkMode)}.svg",
LLMProviders.HELMHOLTZ => $"{ICON_ROOT}/helmholtz.svg",
LLMProviders.GWDG => $"{ICON_ROOT}/gwdg.svg",
_ => $"{ICON_ROOT}/provider{DarkVariant(isDarkMode)}.svg",
};
private static string DarkVariant(bool isDarkMode) => isDarkMode ? "-dark" : string.Empty;
}
@@ -21,7 +21,8 @@ public sealed record EmbeddingProvider(
Guid EnterpriseConfigurationPluginId = default,
string Hostname = "http://localhost:1234",
Host Host = Host.NONE,
bool AllowUserProvidedAPIKey = false) : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey
bool AllowUserProvidedAPIKey = false,
string CustomIconDataUrl = "") : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey
{
private static readonly ILogger<EmbeddingProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<EmbeddingProvider>();
@@ -53,7 +54,7 @@ public sealed record EmbeddingProvider(
#endregion
public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -102,6 +103,15 @@ public sealed record EmbeddingProvider(
if (table.TryGetValue("AllowUserProvidedAPIKey", out var allowUserProvidedApiKeyValue) && allowUserProvidedApiKeyValue.TryRead<bool>(out var allowUserProvidedApiKeyBool))
allowUserProvidedApiKey = allowUserProvidedApiKeyBool;
var customIconDataUrl = string.Empty;
if (table.TryGetValue("IconPath", out var iconPathValue))
{
if (!iconPathValue.TryRead<string>(out var iconPath))
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid icon path. Falling back to the built-in provider icon. (Plugin ID: {configPluginId})");
else if (!PluginIconFile.TryLoadDataUrl(iconPath, pluginPath, out customIconDataUrl, out var iconIssue))
LOGGER.LogWarning($"The configured embedding provider {idx} contains an invalid icon path. Falling back to the built-in provider icon. Issue: {iconIssue} (Plugin ID: {configPluginId})");
}
provider = new EmbeddingProvider
{
Num = 0, // will be set later by the PluginConfigurationObject
@@ -115,6 +125,7 @@ public sealed record EmbeddingProvider(
Hostname = hostname,
Host = host,
AllowUserProvidedAPIKey = allowUserProvidedApiKey,
CustomIconDataUrl = customIconDataUrl,
};
// Handle an encrypted API key if present. When the user manages their own key for this
@@ -0,0 +1,125 @@
namespace AIStudio.Settings;
/// <summary>
/// Loads an icon file a configuration plugin points to, such as a custom provider logo.
/// </summary>
/// <remarks>
/// The checks here are about the path, not about the markup: they keep a plugin from turning an
/// arbitrary file somewhere on the system into a data URL. Whether the file is a usable SVG, and
/// how it becomes a data URL, is up to SvgIcon.
/// </remarks>
internal static class PluginIconFile
{
public static bool TryLoadDataUrl(string iconPath, string pluginPath, out string dataUrl, out string issue)
{
dataUrl = string.Empty;
issue = string.Empty;
if (string.IsNullOrWhiteSpace(iconPath))
{
issue = "The icon path is empty.";
return false;
}
if (Path.IsPathFullyQualified(iconPath))
{
issue = "The icon path must be relative to the configuration plugin directory.";
return false;
}
if (string.IsNullOrWhiteSpace(pluginPath))
{
issue = "The icon path cannot be resolved because the configuration plugin directory is unknown.";
return false;
}
var relativePath = iconPath
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
if (relativePath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Any(segment => segment == ".."))
{
issue = "The icon path must not contain '..' path segments.";
return false;
}
string pluginRoot;
string resolvedPath;
try
{
pluginRoot = Path.GetFullPath(pluginPath);
resolvedPath = Path.GetFullPath(Path.Combine(pluginRoot, relativePath));
}
catch (Exception e)
{
issue = $"The icon path is invalid: {e.Message}";
return false;
}
if (!IsInsideDirectory(pluginRoot, resolvedPath))
{
issue = "The icon path points outside of the configuration plugin directory.";
return false;
}
if (!string.Equals(Path.GetExtension(resolvedPath), ".svg", StringComparison.OrdinalIgnoreCase))
{
issue = "The icon file must use the .svg extension.";
return false;
}
if (!File.Exists(resolvedPath))
{
issue = "The icon file does not exist.";
return false;
}
try
{
// Check the size before reading, so an oversized file never reaches memory:
var fileInfo = new FileInfo(resolvedPath);
if (fileInfo.Length is <= 0 or > SvgIcon.MAX_ICON_SIZE_BYTES)
{
issue = $"The icon file must be between 1 byte and {SvgIcon.MAX_ICON_SIZE_BYTES / 1024} KiB.";
return false;
}
if (!LinksStayInsideDirectory(pluginRoot, resolvedPath))
{
issue = "The icon path contains a link that points outside of the configuration plugin directory.";
return false;
}
return SvgIcon.TryCreateDataUrl(File.ReadAllBytes(resolvedPath), out dataUrl, out issue);
}
catch (Exception e)
{
issue = $"The icon file could not be read: {e.Message}";
return false;
}
}
private static bool LinksStayInsideDirectory(string rootDirectory, string filePath)
{
var relativePath = Path.GetRelativePath(rootDirectory, filePath);
var currentPath = Path.GetFullPath(rootDirectory);
foreach (var segment in relativePath.Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], StringSplitOptions.RemoveEmptyEntries))
{
currentPath = Path.Combine(currentPath, segment);
FileSystemInfo pathInfo = Directory.Exists(currentPath) ? new DirectoryInfo(currentPath) : new FileInfo(currentPath);
var finalTarget = pathInfo.ResolveLinkTarget(true);
if (finalTarget is not null && !IsInsideDirectory(rootDirectory, finalTarget.FullName))
return false;
}
return true;
}
private static bool IsInsideDirectory(string rootDirectory, string path)
{
var root = Path.GetFullPath(rootDirectory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
var target = Path.GetFullPath(path);
var comparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return target.StartsWith(root, comparison);
}
}
+14 -2
View File
@@ -22,6 +22,7 @@ namespace AIStudio.Settings;
/// <param name="Hostname">The hostname of the provider. Useful for self-hosted providers.</param>
/// <param name="Model">The LLM model to use for chat.</param>
/// <param name="AllowUserProvidedAPIKey">When set by a configuration plugin, the user may set their own API key for this otherwise locked, enterprise-managed provider.</param>
/// <param name="CustomIconDataUrl">The validated custom SVG icon supplied by a configuration plugin.</param>
public sealed record Provider(
uint Num,
string Id,
@@ -36,7 +37,8 @@ public sealed record Provider(
HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE,
string AdditionalJsonApiParameters = "",
ProviderCapabilityOverrides? CapabilityOverrides = null,
bool AllowUserProvidedAPIKey = false) : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey
bool AllowUserProvidedAPIKey = false,
string CustomIconDataUrl = "") : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey
{
private static readonly ILogger<Provider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<Provider>();
@@ -93,7 +95,7 @@ public sealed record Provider(
#endregion
public static bool TryParseProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
public static bool TryParseProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -161,6 +163,15 @@ public sealed record Provider(
if (table.TryGetValue("AllowUserProvidedAPIKey", out var allowUserProvidedApiKeyValue) && allowUserProvidedApiKeyValue.TryRead<bool>(out var allowUserProvidedApiKeyBool))
allowUserProvidedApiKey = allowUserProvidedApiKeyBool;
var customIconDataUrl = string.Empty;
if (table.TryGetValue("IconPath", out var iconPathValue))
{
if (!iconPathValue.TryRead<string>(out var iconPath))
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid icon path. Falling back to the built-in provider icon. (Plugin ID: {configPluginId})");
else if (!PluginIconFile.TryLoadDataUrl(iconPath, pluginPath, out customIconDataUrl, out var iconIssue))
LOGGER.LogWarning($"The configured provider {idx} contains an invalid icon path. Falling back to the built-in provider icon. Issue: {iconIssue} (Plugin ID: {configPluginId})");
}
provider = new Provider
{
Num = 0, // will be set later by the PluginConfigurationObject
@@ -177,6 +188,7 @@ public sealed record Provider(
AdditionalJsonApiParameters = additionalJsonApiParameters,
CapabilityOverrides = capabilityOverrides,
AllowUserProvidedAPIKey = allowUserProvidedApiKey,
CustomIconDataUrl = customIconDataUrl,
};
// Handle an encrypted API key if present. When the user manages their own key for this
@@ -660,6 +660,29 @@ public sealed class SettingsManager
.ThenBy(x => x.Num)
.ToList();
/// <summary>
/// Returns the embedding provider with the given id, without applying any confidence filtering.
/// </summary>
/// <remarks>
/// This method resolves a stored embedding provider reference by its id. It applies neither the
/// global minimum confidence level nor any component-specific minimum, so it returns the
/// requested embedding provider even when the user enforces a higher global minimum. Callers
/// that intend to send data to the returned embedding provider must check it themselves, for
/// example through IsTrustedForDataSourceSecurityChecks.
/// </remarks>
/// <param name="embeddingProviderId">The id of the embedding provider to look up.</param>
/// <returns>The embedding provider, or EmbeddingProvider.NONE when no embedding provider with that id exists.</returns>
public EmbeddingProvider GetEmbeddingProviderById(string? embeddingProviderId)
{
if (string.IsNullOrWhiteSpace(embeddingProviderId))
return EmbeddingProvider.NONE;
if (string.Equals(embeddingProviderId, EmbeddingProvider.NONE.Id, StringComparison.OrdinalIgnoreCase))
return EmbeddingProvider.NONE;
return this.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id.Equals(embeddingProviderId, StringComparison.OrdinalIgnoreCase)) ?? EmbeddingProvider.NONE;
}
/// <summary>
/// Returns all configured transcription providers.
/// </summary>
@@ -675,6 +698,29 @@ public sealed class SettingsManager
.ThenBy(x => x.Num)
.ToList();
/// <summary>
/// Returns the transcription provider with the given id, without applying any confidence filtering.
/// </summary>
/// <remarks>
/// This method resolves a stored transcription provider reference by its id. It applies neither
/// the global minimum confidence level nor any component-specific minimum, so it returns the
/// requested transcription provider even when the user enforces a higher global minimum. Callers
/// that intend to send audio to the returned transcription provider must check its confidence
/// level themselves, the way GetFilteredTranscriptionProviders does for the app settings.
/// </remarks>
/// <param name="transcriptionProviderId">The id of the transcription provider to look up.</param>
/// <returns>The transcription provider, or TranscriptionProvider.NONE when no transcription provider with that id exists.</returns>
public TranscriptionProvider GetTranscriptionProviderById(string? transcriptionProviderId)
{
if (string.IsNullOrWhiteSpace(transcriptionProviderId))
return TranscriptionProvider.NONE;
if (string.Equals(transcriptionProviderId, TranscriptionProvider.NONE.Id, StringComparison.OrdinalIgnoreCase))
return TranscriptionProvider.NONE;
return this.ConfigurationData.TranscriptionProviders.FirstOrDefault(x => x.Id.Equals(transcriptionProviderId, StringComparison.OrdinalIgnoreCase)) ?? TranscriptionProvider.NONE;
}
public Profile GetPreselectedProfile(Tools.Components component)
{
var preselection = component.GetProfilePreselection(this);
@@ -21,7 +21,8 @@ public sealed record TranscriptionProvider(
Guid EnterpriseConfigurationPluginId = default,
string Hostname = "http://localhost:1234",
Host Host = Host.NONE,
bool AllowUserProvidedAPIKey = false) : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey
bool AllowUserProvidedAPIKey = false,
string CustomIconDataUrl = "") : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey
{
private static readonly ILogger<TranscriptionProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<TranscriptionProvider>();
@@ -53,7 +54,7 @@ public sealed record TranscriptionProvider(
#endregion
public static bool TryParseTranscriptionProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
public static bool TryParseTranscriptionProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -102,6 +103,15 @@ public sealed record TranscriptionProvider(
if (table.TryGetValue("AllowUserProvidedAPIKey", out var allowUserProvidedApiKeyValue) && allowUserProvidedApiKeyValue.TryRead<bool>(out var allowUserProvidedApiKeyBool))
allowUserProvidedApiKey = allowUserProvidedApiKeyBool;
var customIconDataUrl = string.Empty;
if (table.TryGetValue("IconPath", out var iconPathValue))
{
if (!iconPathValue.TryRead<string>(out var iconPath))
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid icon path. Falling back to the built-in provider icon. (Plugin ID: {configPluginId})");
else if (!PluginIconFile.TryLoadDataUrl(iconPath, pluginPath, out customIconDataUrl, out var iconIssue))
LOGGER.LogWarning($"The configured transcription provider {idx} contains an invalid icon path. Falling back to the built-in provider icon. Issue: {iconIssue} (Plugin ID: {configPluginId})");
}
provider = new TranscriptionProvider
{
Num = 0, // will be set later by the PluginConfigurationObject
@@ -115,6 +125,7 @@ public sealed record TranscriptionProvider(
Hostname = hostname,
Host = host,
AllowUserProvidedAPIKey = allowUserProvidedApiKey,
CustomIconDataUrl = customIconDataUrl,
};
// Handle an encrypted API key if present. When the user manages their own key for this
@@ -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;
+113
View File
@@ -0,0 +1,113 @@
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace AIStudio.Tools;
/// <summary>
/// Turns an SVG icon which came from outside the app into a data URL we can put into an img tag.
/// </summary>
/// <remarks>
/// <para>
/// Icons from plugins are never rendered inline. They go into an img element instead, where the
/// browser treats the SVG as a standalone document: it runs no script, fires no event handlers, and
/// loads nothing from the network. That is what makes a plugin-supplied icon harmless, so this
/// class does not try to filter active content out of the markup.
/// </para>
/// <para>
/// What is left to do is a size limit and the question of whether the icon is an SVG at all. The
/// latter is a diagnostic rather than a defense: it turns a broken icon into a log message and a
/// fallback instead of a broken image in the UI.
/// </para>
/// </remarks>
internal static class SvgIcon
{
/// <summary>
/// The largest icon we accept.
/// </summary>
/// <remarks>
/// Provider icons are persisted into the settings file as a data URL, so an oversized icon
/// would be written and parsed again on every single settings store. A logo needs far less.
/// </remarks>
public const int MAX_ICON_SIZE_BYTES = 32 * 1024;
private const string SVG_NAMESPACE = "http://www.w3.org/2000/svg";
private const string DATA_URL_PREFIX = "data:image/svg+xml;base64,";
private static readonly XNamespace SVG = SVG_NAMESPACE;
/// <summary>
/// Validates the given SVG and converts it into a data URL.
/// </summary>
/// <param name="svg">The SVG markup to convert.</param>
/// <param name="dataUrl">The resulting data URL, or an empty string when the icon was rejected.</param>
/// <param name="issue">The reason why the icon was rejected, or an empty string on success.</param>
/// <returns>True, when the icon could be converted.</returns>
public static bool TryCreateDataUrl(string svg, out string dataUrl, out string issue) => TryCreateDataUrl(Encoding.UTF8.GetBytes(svg), out dataUrl, out issue);
/// <inheritdoc cref="TryCreateDataUrl(string,out string,out string)"/>
public static bool TryCreateDataUrl(byte[] svg, out string dataUrl, out string issue)
{
dataUrl = string.Empty;
issue = string.Empty;
if (svg.Length is <= 0 or > MAX_ICON_SIZE_BYTES)
{
issue = $"The icon must be between 1 byte and {MAX_ICON_SIZE_BYTES / 1024} KiB.";
return false;
}
XDocument document;
try
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
MaxCharactersInDocument = MAX_ICON_SIZE_BYTES,
XmlResolver = null,
};
using var stream = new MemoryStream(svg, writable: false);
using var xmlReader = XmlReader.Create(stream, settings);
document = XDocument.Load(xmlReader, LoadOptions.None);
}
catch (Exception e)
{
issue = $"The icon is not valid SVG XML: {e.Message}";
return false;
}
if (document.Root is null || !string.Equals(document.Root.Name.LocalName, "svg", StringComparison.OrdinalIgnoreCase))
{
issue = "The icon does not have an SVG root element.";
return false;
}
//
// An SVG which is meant to be pasted into HTML often omits the namespace, because the HTML
// parser supplies it. A standalone document inside an img tag has no such help and would
// not render at all, so we add the namespace instead of rejecting such an icon:
//
if (document.Root.Name.Namespace == XNamespace.None)
{
// Materialize before renaming: we are about to change the very tree we walk.
foreach (var element in document.Root.DescendantsAndSelf().ToList())
if (element.Name.Namespace == XNamespace.None)
element.Name = SVG + element.Name.LocalName;
dataUrl = ToDataUrl(Encoding.UTF8.GetBytes(document.ToString(SaveOptions.DisableFormatting)));
return true;
}
if (document.Root.Name.Namespace != SVG)
{
issue = "The icon root element is not in the SVG namespace.";
return false;
}
dataUrl = ToDataUrl(svg);
return true;
}
private static string ToDataUrl(byte[] svg) => $"{DATA_URL_PREFIX}{Convert.ToBase64String(svg)}";
}
+10 -2
View File
@@ -71,9 +71,17 @@
height: var(--mud-icon-size-large);
}
.plugin-icon-container svg {
.plugin-icon-container img {
width: 100%;
height: 100%;
object-fit: contain;
}
.provider-icon {
width: 1.5em;
height: 1.5em;
object-fit: contain;
flex: none;
}
.mud-popover-open.InnerScrollingFix {
@@ -435,4 +443,4 @@ tr:has(> .provider-group-header) .mud-icon-button {
.provider-group-header {
font-weight: 600;
}
}
@@ -1,5 +1,7 @@
# v26.8.2, build 255 (2026-08-xx xx:xx UTC)
- Added protection against prompt injection. Documents, web pages, and retrieved content can carry instructions written for the AI rather than for you, for example, text telling it to ignore its rules or to hand over its instructions. AI Studio now always removes such passages before the content reaches a model, while the rest of your document stays intact and usable. When something was removed, AI Studio tells you and can show you which passages it took out. You can turn off the detailed dialog in the app settings. For IT departments: the new setting `DataApp.ShowPromptInjectionAlert` lets you configure the detailed dialog for your organization. Many thanks to Sabrina `Sabrina-devops` for implementing this feature and to Simon `SimonBpunkt` for his work on the detection patterns and their translations.
- Added provider logos throughout AI Studio, making models easier to recognize at a glance. Configuration plugins can now give managed LLM, transcription, and embedding providers their own project icon with the optional `IconPath` field.
- Improved the safety of plugin symbols: AI Studio now shows the symbol of a plugin in isolation, so nothing inside a symbol can reach the rest of the app.
- Improved how much memory AI Studio needs. Working with large documents used to grow the app to several gigabytes, and on macOS that memory was never handed back. AI Studio now stays at a fraction of that and returns memory to your system. This matters most on devices with little memory, such as a Raspberry Pi.
- Improved the preview for large documents. It now shows you the beginning of your document instead of loading all of it, so the dialog opens right away. Your complete document still goes to the AI.
- Fixed AI Studio reading a document to the end even after you closed its preview. Closing the dialog now stops that work immediately.
@@ -0,0 +1,15 @@
# Provider icon assets
All provider icons are shipped with AI Studio and loaded locally. No icon triggers an external image request.
## Sources
- `alibaba-cloud.svg`, `anthropic*.svg`, `deepseek.svg`, `hetzner.svg`, `mistral.svg`, `perplexity.svg`, and `x*.svg` use paths and brand colors from [Simple Icons 16.21.0](https://github.com/simple-icons/simple-icons/tree/16.21.0), licensed under [CC0-1.0](https://github.com/simple-icons/simple-icons/blob/16.21.0/LICENSE.md).
- `hugging-face.svg` was taken from their official website (https://huggingface.co/brand)
- `openai*.svg` uses the OpenAI mark path from [Simple Icons 15.15.0](https://github.com/simple-icons/simple-icons/blob/15.15.0/icons/openai.svg) and black/white variants following the [OpenAI Design Guidelines](https://openai.com/brand/).
- `fireworks.svg` is adapted from the [Fireworks AI site icon](https://fireworks.ai/icon0.svg).
- `groq.svg` is adapted from the [Groq site icon](https://groq.com/favicon.svg).
- `provider*.svg` and `self-hosted*.svg` are neutral project-owned fallback graphics.
- `gwdg.svg`, `openrouter.svg`, `google.svg` and `helmholtz.svg` were created by taking the official logo from their respective websites as images and creating a svg from them.
The `-dark` files are contrast variants for dark surfaces. All product names, logos, and trademarks remain the property of their respective owners. Their inclusion identifies compatible services and does not imply endorsement. Sources were retrieved on 2026-08-24.
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#FF6A00" d="M3.996 4.517h5.291L8.01 6.324 4.153 7.506a1.668 1.668 0 0 0-1.165 1.601v5.786a1.668 1.668 0 0 0 1.165 1.6l3.857 1.183 1.277 1.807H3.996A3.996 3.996 0 0 1 0 15.487V8.513a3.996 3.996 0 0 1 3.996-3.996m16.008 0h-5.291l1.277 1.807 3.857 1.182c.715.227 1.17.889 1.165 1.601v5.786a1.668 1.668 0 0 1-1.165 1.6l-3.857 1.183-1.277 1.807h5.291A3.996 3.996 0 0 0 24 15.487V8.513a3.996 3.996 0 0 0-3.996-3.996m-4.007 8.345H8.002v-1.804h7.995Z"/></svg>

After

Width:  |  Height:  |  Size: 524 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"/></svg>

After

Width:  |  Height:  |  Size: 262 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#191919" d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"/></svg>

After

Width:  |  Height:  |  Size: 262 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#5786FE" d="M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="#6720FF" d="M15.9851 19.1274c-.8882 0-1.6852-.5273-2.0251-1.3436L9.8626 8h2.3982l3.7383 8.9499L19.7339 8h2.3982l-4.1219 9.7873c-.3416.8128-1.1369 1.3401-2.0251 1.3401Zm5.3465 4.6755c-.8847 0-1.6783-.5237-2.0216-1.3331-.3451-.8163-.1664-1.7483.4572-2.3807l7.4627-7.5594.9319 2.1985-6.832 6.9073 9.7382-.0543L32 23.7797l-10.6667.0263-.0035-.0035h.0018ZM0 23.7766l.932-2.1985 9.7382.0543-6.8303-6.909.932-2.1985 7.4626 7.5589c.6237.6307.8041 1.5662.4573 2.3807-.3434.8111-1.1405 1.3332-2.0216 1.3332L.0035 23.7731Z"/></svg>

After

Width:  |  Height:  |  Size: 593 B

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title desc">
<title id="title">Gemini logo</title>
<desc id="desc">A four-point curved star with a red, yellow, green, and blue gradient on a transparent background.</desc>
<defs>
<path id="spark" d="M 255.5 20.5 C 256.83 20.5 258.92 21.42 260 22 C 261.08 22.58 261.17 23 262 24 C 262.83 25 264.33 26.67 265 28 C 265.67 29.33 265.5 30 266 32 C 266.5 34 267 37 268 40 C 269 43 270.5 46 272 50 C 273.5 54 275 59 277 64 C 279 69 281.17 74 284 80 C 286.83 86 290.33 93.33 294 100 C 297.67 106.67 301.67 113.33 306 120 C 310.33 126.67 314.83 133.33 320 140 C 325.17 146.67 330.67 153.33 337 160 C 343.33 166.67 350.17 173.33 358 180 C 365.83 186.67 375.5 194 384 200 C 392.5 206 400.17 211 409 216 C 417.83 221 427.83 226 437 230 C 446.17 234 457.33 237.67 464 240 C 470.67 242.33 473.33 242.67 477 244 C 480.67 245.33 484 246.67 486 248 C 488 249.33 488.25 250.75 489 252 C 489.75 253.25 490.5 254.17 490.5 255.5 C 490.5 256.83 489.58 258.92 489 260 C 488.42 261.08 488 261.17 487 262 C 486 262.83 484.33 264.33 483 265 C 481.67 265.67 481 265.5 479 266 C 477 266.5 474 267 471 268 C 468 269 465 270.5 461 272 C 457 273.5 452 275 447 277 C 442 279 437 281.17 431 284 C 425 286.83 417.67 290.33 411 294 C 404.33 297.67 397.67 301.67 391 306 C 384.33 310.33 377.67 314.83 371 320 C 364.33 325.17 357.67 330.67 351 337 C 344.33 343.33 337.67 350.17 331 358 C 324.33 365.83 317 375.5 311 384 C 305 392.5 300 400.17 295 409 C 290 417.83 285 427.83 281 437 C 277 446.17 273.33 457.33 271 464 C 268.67 470.67 268.33 473.33 267 477 C 265.67 480.67 264.33 484 263 486 C 261.67 488 260.25 488.25 259 489 C 257.75 489.75 256.83 490.5 255.5 490.5 C 254.17 490.5 252.08 489.58 251 489 C 249.92 488.42 249.83 488 249 487 C 248.17 486 246.67 484.33 246 483 C 245.33 481.67 245.5 481 245 479 C 244.5 477 244 474 243 471 C 242 468 240.5 465 239 461 C 237.5 457 236 452 234 447 C 232 442 229.83 437 227 431 C 224.17 425 220.67 417.67 217 411 C 213.33 404.33 209.33 397.67 205 391 C 200.67 384.33 196.17 377.67 191 371 C 185.83 364.33 180.33 357.67 174 351 C 167.67 344.33 160.83 337.67 153 331 C 145.17 324.33 135.5 317 127 311 C 118.5 305 110.83 300 102 295 C 93.17 290 83.17 285 74 281 C 64.83 277 53.67 273.33 47 271 C 40.33 268.67 37.67 268.33 34 267 C 30.33 265.67 27 264.33 25 263 C 23 261.67 22.75 260.25 22 259 C 21.25 257.75 20.5 256.83 20.5 255.5 C 20.5 254.17 21.42 252.08 22 251 C 22.58 249.92 23 249.83 24 249 C 25 248.17 26.67 246.67 28 246 C 29.33 245.33 30 245.5 32 245 C 34 244.5 37 244 40 243 C 43 242 46 240.5 50 239 C 54 237.5 59 236 64 234 C 69 232 74 229.83 80 227 C 86 224.17 93.33 220.67 100 217 C 106.67 213.33 113.33 209.33 120 205 C 126.67 200.67 133.33 196.17 140 191 C 146.67 185.83 153.33 180.33 160 174 C 166.67 167.67 173.33 160.83 180 153 C 186.67 145.17 194 135.5 200 127 C 206 118.5 211 110.83 216 102 C 221 93.17 226 83.17 230 74 C 234 64.83 237.67 53.67 240 47 C 242.33 40.33 242.67 37.67 244 34 C 245.33 30.33 246.67 27 248 25 C 249.33 23 250.75 22.75 252 22 C 253.25 21.25 254.17 20.5 255.5 20.5 Z"/>
<linearGradient id="vertical" x1="0" y1="20" x2="0" y2="491" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FA4342"/>
<stop offset="0.10" stop-color="#F64848"/>
<stop offset="0.21" stop-color="#DD586B"/>
<stop offset="0.31" stop-color="#A476AE"/>
<stop offset="0.42" stop-color="#628AE6"/>
<stop offset="0.51" stop-color="#3F8FF2"/>
<stop offset="0.64" stop-color="#3B9AD3"/>
<stop offset="0.81" stop-color="#30B18C"/>
<stop offset="1" stop-color="#0ABA62"/>
</linearGradient>
<radialGradient id="warm" cx="145" cy="135" r="230" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#F34A47" stop-opacity="0.84"/>
<stop offset="0.45" stop-color="#F34A47" stop-opacity="0.52"/>
<stop offset="0.88" stop-color="#F34A47" stop-opacity="0"/>
</radialGradient>
<radialGradient id="yellow" cx="21" cy="256" r="235" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FFC414"/>
<stop offset="0.35" stop-color="#FFC414" stop-opacity="0.92"/>
<stop offset="0.72" stop-color="#C9D72E" stop-opacity="0.45"/>
<stop offset="1" stop-color="#C9D72E" stop-opacity="0"/>
</radialGradient>
<radialGradient id="blue" cx="491" cy="256" r="365" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#3186FF"/>
<stop offset="0.38" stop-color="#3186FF" stop-opacity="0.96"/>
<stop offset="0.72" stop-color="#3186FF" stop-opacity="0.55"/>
<stop offset="1" stop-color="#3186FF" stop-opacity="0"/>
</radialGradient>
<radialGradient id="green" cx="256" cy="491" r="240" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#08B966"/>
<stop offset="0.38" stop-color="#10B66B" stop-opacity="0.72"/>
<stop offset="0.72" stop-color="#22B47B" stop-opacity="0.24"/>
<stop offset="1" stop-color="#22B47B" stop-opacity="0"/>
</radialGradient>
<clipPath id="sparkClip">
<use href="#spark"/>
</clipPath>
</defs>
<g clip-path="url(#sparkClip)">
<rect width="512" height="512" fill="url(#vertical)"/>
<rect width="512" height="512" fill="url(#blue)"/>
<rect width="512" height="512" fill="url(#warm)"/>
<rect width="512" height="512" fill="url(#yellow)"/>
<rect width="512" height="512" fill="url(#green)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33 33"><path fill="#F43E01" d="M.54.39h32v32h-32z"/><path fill="#FFFFFF" d="m18.445 4.406-9.468 13.74 7.341.665-1.69 9.578 9.469-13.74-7.342-.664 1.69-9.579Z"/></svg>

After

Width:  |  Height:  |  Size: 220 B

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="233"
height="214"
viewBox="0 0 233 214"
role="img"
aria-labelledby="title desc">
<title id="title">GWDG Digitalmedien – blaues Signet</title>
<desc id="desc">Aus der gelieferten PNG-Vorlage extrahiertes blaues Signet ohne graues Bogenelement.</desc>
<path fill="#009EE0" d="
M 82 0
H 128
C 138 0 146 8 146 18
V 64
C 146 74 138 82 128 82
H 84
C 75 82 67 90 67 99
V 128
C 67 137 75 145 84 145
H 95
C 104 145 111 138 111 129
V 114
C 111 105 119 97 128 97
H 144
C 153 97 161 105 161 114
V 128
C 161 137 168 144 178 144
H 216
C 225 144 233 152 233 161
V 197
C 233 206 225 214 216 214
H 178
C 168 214 161 206 161 197
V 165
C 161 155 154 148 144 148
H 131
C 122 148 114 156 114 165
V 179
C 114 188 107 196 97 196
H 80
C 71 196 63 188 63 179
V 163
C 63 154 56 146 47 146
H 17
C 8 146 0 138 0 129
V 97
C 0 87 8 79 17 79
H 47
C 56 79 64 71 64 62
V 18
C 64 8 72 0 82 0
Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="119" height="103" viewBox="0 0 119 103" role="img" aria-labelledby="title desc">
<title id="title">Blablador Icon</title>
<desc id="desc">Schwarzes, stilisiertes Gesichtssymbol auf weiß gefüllter Iconfläche; der Außenbereich ist transparent.</desc>
<!-- Die Iconfläche ist weiß; außerhalb dieses Pfads bleibt die SVG transparent. -->
<path
d="M36 7.5H84.5C94.2 7.5 102 15.3 102 25V91.5L85 79H35C25.1 79 17 70.9 17 61V25.5C17 15.6 25.1 7.5 35 7.5H36Z"
fill="#fff"
stroke="#000"
stroke-width="5"
stroke-linejoin="miter"
/>
<!-- Innere Falz/Trennlinie -->
<path d="M85 31V79" fill="none" stroke="#000" stroke-width="5" stroke-linecap="butt" />
<!-- Gesicht -->
<circle cx="36.5" cy="30.5" r="6.75" fill="#000" />
<circle cx="67.5" cy="30.5" r="6.75" fill="#000" />
<path d="M42.5 45.5H61V51H54.5V59.5H49V51H42.5Z" fill="#000" />
</svg>

After

Width:  |  Height:  |  Size: 932 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#D50C2D" d="M0 0v24h24V0H0zm4.602 4.025h2.244c.509 0 .716.215.716.717v5.64h8.883v-5.64c0-.509.215-.717.717-.717h2.229c.5 0 .71.23.724.717v14.516c0 .509-.215.717-.717.717h-2.23c-.51 0-.717-.215-.717-.717v-5.735H7.562v5.735c0 .516-.215.717-.716.717H4.602c-.51 0-.717-.208-.717-.717V4.742c0-.509.207-.717.717-.717z"/></svg>

After

Width:  |  Height:  |  Size: 393 B

@@ -0,0 +1,8 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M230.721 172.7C230.183 170.673 229.313 168.75 228.146 167.008C228.396 166.091 228.587 165.159 228.714 164.217C229.543 158.241 227.471 152.77 223.567 148.537C221.452 146.225 219.185 144.698 216.784 143.761C218.36 137.018 219.157 130.117 219.161 123.193C219.161 120.03 218.982 116.932 218.682 113.88C218.526 112.356 218.337 110.836 218.115 109.32C217.428 104.847 216.408 100.431 215.064 96.11C214.183 93.2707 213.164 90.476 212.01 87.736C210.281 83.6782 208.262 79.75 205.969 75.982C204.465 73.475 202.827 71.0508 201.062 68.72C200.197 67.543 199.296 66.3938 198.358 65.274C195.58 61.898 192.561 58.7277 189.325 55.788C188.25 54.7997 187.145 53.8453 186.01 52.926C184.893 51.9943 183.751 51.0927 182.586 50.222C180.241 48.4766 177.818 46.8392 175.324 45.315C161.543 36.945 145.382 32.145 128.109 32.145C77.817 32.145 37.057 72.907 37.057 123.196C37.055 130.208 37.867 137.196 39.477 144.02C37.317 144.958 35.247 146.42 33.327 148.535C29.424 152.766 27.351 158.217 28.18 164.193C28.306 165.142 28.495 166.082 28.747 167.006C27.5811 168.749 26.7117 170.673 26.174 172.7C24.974 177.261 25.369 181.374 26.894 184.978C25.236 189.688 25.65 194.704 27.809 199.065C29.379 202.25 31.626 204.714 34.396 206.916C37.689 209.534 41.811 211.758 46.783 213.892C52.715 216.422 59.956 218.799 63.249 219.671C71.755 221.873 79.911 223.269 88.177 223.337C99.954 223.446 110.096 220.677 117.357 213.59C120.924 214.027 124.515 214.246 128.109 214.244C131.906 214.236 135.699 213.997 139.467 213.529C146.711 220.661 156.892 223.455 168.712 223.343C176.977 223.277 185.133 221.881 193.617 219.676C196.932 218.804 204.17 216.427 210.105 213.897C215.077 211.76 219.199 209.536 222.514 206.922C225.263 204.719 227.508 202.256 229.079 199.071C231.26 194.709 231.652 189.693 230.017 184.983C231.527 181.379 231.92 177.257 230.721 172.7ZM222.281 184.673C223.952 187.844 224.059 191.427 222.585 194.764C220.349 199.821 214.795 203.805 204.008 208.082C197.3 210.742 191.158 212.443 191.104 212.458C182.232 214.759 174.208 215.928 167.262 215.928C155.76 215.928 147.201 212.754 141.773 206.486C132.594 208.05 123.222 208.103 114.026 206.644C108.591 212.808 100.081 215.928 88.676 215.928C81.729 215.928 73.706 214.759 64.833 212.458C64.779 212.443 58.639 210.742 51.929 208.082C41.143 203.805 35.587 199.824 33.352 194.764C31.878 191.427 31.985 187.844 33.656 184.673C33.81 184.378 33.976 184.091 34.153 183.813C33.1516 182.309 32.4799 180.61 32.182 178.827C31.8842 177.045 31.967 175.22 32.425 173.472C33.089 170.949 34.46 168.851 36.322 167.344C35.425 165.87 34.8365 164.23 34.592 162.522C34.056 158.808 35.289 155.1 38.062 152.076C40.222 149.723 43.275 148.428 46.655 148.428H46.745C44.1965 140.259 42.9044 131.75 42.913 123.193C42.913 76.522 80.749 38.683 127.427 38.683C174.104 38.683 211.94 76.518 211.94 123.193C211.947 131.773 210.646 140.304 208.081 148.492C208.489 148.452 208.889 148.432 209.282 148.431C212.662 148.431 215.716 149.726 217.874 152.079C220.647 155.1 221.881 158.811 221.344 162.525C221.1 164.233 220.511 165.873 219.615 167.347C221.477 168.854 222.849 170.952 223.512 173.475C223.97 175.223 224.053 177.048 223.755 178.831C223.458 180.613 222.786 182.312 221.784 183.816C221.961 184.091 222.129 184.378 222.281 184.673Z" fill="white"/>
<path d="M221.784 183.816C222.786 182.312 223.458 180.613 223.756 178.831C224.053 177.048 223.97 175.223 223.512 173.475C222.848 170.952 221.476 168.854 219.615 167.347C220.512 165.873 221.1 164.233 221.344 162.525C221.881 158.811 220.648 155.103 217.874 152.079C215.716 149.726 212.662 148.431 209.282 148.431C208.889 148.431 208.489 148.452 208.081 148.492C210.643 140.304 211.942 131.774 211.933 123.195C211.933 76.5231 174.097 38.6851 127.424 38.6851C80.75 38.6851 42.9099 76.5191 42.9099 123.195C42.9015 131.752 44.1936 140.261 46.742 148.43H46.6519C43.2719 148.43 40.219 149.724 38.06 152.077C35.287 155.098 34.0529 158.81 34.5899 162.523C34.8346 164.231 35.4231 165.872 36.3199 167.346C34.4579 168.852 33.086 170.95 32.422 173.473C31.9642 175.222 31.8817 177.047 32.1799 178.83C32.4781 180.612 33.1501 182.312 34.1519 183.816C33.9739 184.094 33.8099 184.381 33.6549 184.676C31.9849 187.847 31.877 191.43 33.352 194.767C35.588 199.824 41.1419 203.808 51.9289 208.085C58.6359 210.745 64.779 212.446 64.833 212.461C73.705 214.762 81.729 215.931 88.675 215.931C100.081 215.931 108.591 212.811 114.026 206.647C123.222 208.106 132.594 208.052 141.773 206.489C147.201 212.757 155.76 215.931 167.262 215.931C174.208 215.931 182.232 214.762 191.103 212.461C191.158 212.446 197.298 210.745 204.008 208.085C214.795 203.808 220.35 199.824 222.585 194.767C224.059 191.43 223.952 187.847 222.281 184.676C222.129 184.379 221.961 184.091 221.784 183.816ZM110.137 196.997C109.669 197.815 109.168 198.614 108.635 199.391C107.23 201.448 105.382 203.02 103.237 204.188C99.1369 206.424 93.947 207.205 88.675 207.205C80.346 207.205 71.808 205.256 67.023 204.015C66.787 203.954 37.689 195.735 41.373 188.739C41.993 187.562 43.0129 187.092 44.2979 187.092C49.4849 187.092 58.9299 194.816 62.9889 194.816C63.8959 194.816 64.5359 194.43 64.7969 193.488C66.5269 187.284 38.5039 184.676 40.8639 175.692C41.2799 174.102 42.41 173.456 43.998 173.456C50.856 173.455 66.248 185.516 69.467 185.516C69.714 185.516 69.8909 185.443 69.9869 185.291C70.0009 185.268 70.015 185.246 70.028 185.222C71.539 182.727 70.6719 180.913 60.3209 174.573L59.3269 173.968C47.9359 167.074 39.9409 162.925 44.4879 157.975C45.0109 157.404 45.7529 157.151 46.6539 157.151C47.7219 157.151 49.0149 157.508 50.4389 158.108C56.4549 160.645 64.793 167.564 68.276 170.581C68.8239 171.057 69.3683 171.538 69.9089 172.022C69.9089 172.022 74.319 176.608 76.985 176.608C77.599 176.608 78.1199 176.366 78.4729 175.768C80.364 172.58 60.9099 157.838 59.8129 151.755C59.0689 147.634 60.3349 145.546 62.6749 145.546C63.7879 145.546 65.1459 146.02 66.6449 146.971C71.2949 149.922 80.2729 165.35 83.5599 171.352C84.6619 173.363 86.5429 174.213 88.2379 174.213C91.6009 174.213 94.2299 170.87 88.5459 166.622C80.0029 160.23 83.001 149.782 87.078 149.139C87.252 149.111 87.4279 149.097 87.6029 149.097C91.3109 149.097 92.9459 155.486 92.9459 155.486C92.9459 155.486 97.7399 167.524 105.975 175.753C113.447 183.222 114.491 189.351 110.137 196.997ZM136.766 198.407L136.339 198.458L135.611 198.541C135.228 198.581 134.844 198.619 134.459 198.654L134.084 198.688L133.741 198.717L133.255 198.756L132.718 198.795L132.182 198.83L132.063 198.838C131.923 198.846 131.783 198.855 131.641 198.862L131.462 198.872C131.296 198.881 131.13 198.889 130.962 198.896L130.381 198.921L129.854 198.939L129.502 198.949H129.323C129.213 198.949 129.104 198.955 128.994 198.956H128.82C128.71 198.956 128.601 198.956 128.491 198.961L128.043 198.967H127.418C126.927 198.967 126.437 198.962 125.949 198.952L125.553 198.943C125.44 198.943 125.327 198.938 125.216 198.934L124.796 198.922L124.275 198.902L123.805 198.881L123.684 198.876L123.237 198.853C123.112 198.846 122.989 198.84 122.865 198.831L122.576 198.814C122.213 198.791 121.85 198.766 121.487 198.738L121.107 198.707C120.947 198.695 120.787 198.68 120.628 198.666C120.441 198.65 120.254 198.632 120.067 198.614C119.754 198.585 119.441 198.553 119.128 198.519H119.113C123.683 188.324 121.372 178.802 112.137 169.575C106.08 163.526 102.051 154.594 101.215 152.633C99.5229 146.828 95.045 140.375 87.608 140.375C86.979 140.375 86.351 140.425 85.73 140.523C82.472 141.036 79.624 142.911 77.592 145.733C75.396 143.002 73.262 140.831 71.332 139.605C68.422 137.76 65.5179 136.824 62.6889 136.824C59.1579 136.824 56.0019 138.274 53.8019 140.904L53.7459 140.971C53.7039 140.798 53.6639 140.625 53.6229 140.451L53.6179 140.428C53.1992 138.638 52.8477 136.833 52.5639 135.016C52.5639 135.004 52.5639 134.992 52.5579 134.98C52.5359 134.843 52.5159 134.705 52.4949 134.568C52.4334 134.162 52.3757 133.755 52.3219 133.348C52.2979 133.163 52.2719 132.978 52.2489 132.793L52.1809 132.238C52.1589 132.053 52.1409 131.885 52.1209 131.709L52.115 131.665C52.0351 130.945 51.9651 130.225 51.9049 129.503L51.8829 129.226L51.8479 128.754C51.8379 128.625 51.8279 128.495 51.8209 128.365C51.8209 128.334 51.8159 128.304 51.8149 128.275C51.7895 127.913 51.7678 127.55 51.7499 127.187C51.7399 126.998 51.7299 126.81 51.7219 126.62L51.7019 126.124L51.6969 125.974L5Line truncated
<path fill-rule="evenodd" clip-rule="evenodd" d="M203.21 123.685V123.194C203.21 81.34 169.292 47.411 127.435 47.411C85.5791 47.411 51.648 81.342 51.648 123.194V123.358C51.646 123.467 51.645 123.576 51.648 123.685C51.6529 123.848 51.6546 124.011 51.653 124.174L51.6581 124.534L51.661 124.663C51.661 124.723 51.6631 124.782 51.6651 124.842C51.6681 124.937 51.67 125.033 51.67 125.128L51.681 125.517L51.697 125.974L51.702 126.124L51.722 126.597V126.62C51.73 126.805 51.7401 126.989 51.7491 127.173L51.75 127.187C51.76 127.375 51.7701 127.564 51.7821 127.753C51.7921 127.927 51.802 128.101 51.815 128.275L51.8171 128.306C51.8258 128.455 51.8358 128.605 51.847 128.754L51.85 128.794L51.883 129.226L51.8861 129.254C51.8921 129.338 51.898 129.422 51.906 129.503C51.9658 130.224 52.0355 130.945 52.1151 131.664L52.12 131.709L52.181 132.238L52.2491 132.793L52.299 133.17L52.322 133.347C52.3753 133.755 52.433 134.162 52.495 134.568L52.4991 134.595L52.558 134.979C52.8435 136.808 53.1971 138.626 53.618 140.429L53.6231 140.451L53.655 140.586L53.746 140.971L53.802 140.904C56.002 138.274 59.158 136.824 62.689 136.824C65.519 136.824 68.4221 137.76 71.3321 139.605C73.2621 140.831 75.3961 143.002 77.5921 145.733C79.6241 142.911 82.4721 141.035 85.7301 140.523C86.3513 140.425 86.9792 140.376 87.6081 140.375C95.0441 140.375 99.523 146.828 101.215 152.633C102.051 154.594 106.08 163.526 112.156 169.568C121.392 178.795 123.703 188.316 119.132 198.511H119.148C119.459 198.546 119.772 198.578 120.087 198.607C120.274 198.625 120.46 198.643 120.648 198.659L120.714 198.665L121.127 198.7L121.507 198.73C121.869 198.758 122.232 198.784 122.596 198.807L122.885 198.824L123.114 198.838L123.256 198.846L123.703 198.869L123.825 198.874L124.294 198.895L124.816 198.915L125.235 198.927L125.305 198.929C125.394 198.933 125.483 198.936 125.572 198.936L125.668 198.939C126.258 198.953 126.847 198.96 127.437 198.959H128.063L128.51 198.954C128.62 198.949 128.729 198.949 128.84 198.949H129.014L129.165 198.945C129.224 198.943 129.283 198.941 129.343 198.941H129.522L129.873 198.932L130.401 198.914L130.982 198.888C131.15 198.882 131.316 198.873 131.482 198.865L131.661 198.854L131.927 198.84L132.083 198.831L132.201 198.823L132.738 198.788L133.274 198.749L133.761 198.71L134.103 198.681L134.479 198.647C135.107 198.591 135.733 198.525 136.359 198.45L136.786 198.399C132.287 188.247 134.616 178.767 143.813 169.577C149.876 163.519 153.905 154.587 154.745 152.625C156.438 146.821 160.914 140.368 168.352 140.368C168.981 140.368 169.61 140.418 170.231 140.516C173.486 141.028 176.334 142.904 178.369 145.726C180.566 142.996 182.699 140.823 184.63 139.597C187.539 137.753 190.445 136.817 193.272 136.817C196.388 136.817 199.212 137.947 201.345 140.02C201.384 139.851 201.422 139.682 201.459 139.512L201.568 139.006C201.607 138.821 201.646 138.636 201.683 138.451C201.749 138.124 201.815 137.797 201.878 137.467C201.944 137.125 202.007 136.781 202.067 136.437L202.098 136.251C202.117 136.141 202.135 136.031 202.156 135.92C202.19 135.748 202.218 135.576 202.246 135.402L202.257 135.336L202.328 134.883L202.398 134.424V134.42C202.449 134.081 202.497 133.742 202.542 133.403L202.553 133.319L202.616 132.841L202.667 132.433L202.757 131.629L202.792 131.306L202.801 131.218C202.82 131.044 202.838 130.87 202.854 130.696V130.682C202.867 130.544 202.881 130.405 202.893 130.266C202.964 129.478 203.024 128.686 203.072 127.891C203.081 127.761 203.088 127.63 203.096 127.499V127.493L203.122 127.002L203.128 126.892C203.144 126.56 203.158 126.228 203.169 125.896V125.884L203.174 125.754C203.179 125.645 203.183 125.535 203.183 125.425L203.185 125.381C203.189 125.278 203.193 125.172 203.193 125.067L203.196 124.977C203.199 124.872 203.202 124.768 203.202 124.663L203.204 124.574C203.207 124.441 203.21 124.307 203.21 124.174V123.685ZM108.638 199.391C114.64 190.59 114.214 183.984 105.98 175.754C97.7441 167.523 92.951 155.487 92.951 155.487C92.951 155.487 91.1621 148.496 87.0821 149.138C83.0021 149.78 80.0091 160.227 88.5521 166.622C97.0941 173.017 86.8521 177.353 83.5641 171.352C80.2761 165.35 71.299 149.923 66.645 146.972C61.991 144.021 58.718 145.675 59.815 151.757C60.36 154.776 65.4281 159.929 70.1631 164.743C74.9671 169.627 79.428 174.163 78.474 175.768C76.581 178.955 69.9141 172.023 69.9141 172.023C69.9141 172.023 49.038 153.025 44.494 157.976C40.304 162.539 46.765 166.418 56.7211 172.397C57.5671 172.905 58.4391 173.429 59.3321 173.969C70.7231 180.865 71.609 182.684 69.992 185.293C69.395 186.257 65.582 183.968 60.892 181.153C52.897 176.352 42.3551 170.023 40.8661 175.688C39.5781 180.591 47.334 183.595 54.368 186.32C60.228 188.59 65.5881 190.666 64.7991 193.484C63.9821 196.406 59.5531 193.969 54.7121 191.305C49.2771 188.314 43.3221 185.038 41.3731 188.735C37.6901 195.725 66.7831 203.954 67.0231 204.015C76.4231 206.453 100.295 211.619 108.638 199.391ZM147.303 199.391C141.301 190.59 141.727 183.984 149.962 175.754C158.197 167.523 162.99 155.487 162.99 155.487C162.99 155.487 164.779 148.496 168.859 149.138C172.939 149Line truncated
<path fill-rule="evenodd" clip-rule="evenodd" d="M152.047 102.567C153.229 102.985 154.108 104.257 154.944 105.468C156.074 107.104 157.126 108.627 158.74 107.769C160.644 106.756 162.205 105.202 163.225 103.302C164.246 101.402 164.681 99.2427 164.475 97.096C164.321 95.4908 163.813 93.9398 162.987 92.5548C162.161 91.1697 161.038 89.985 159.7 89.0862C158.361 88.1874 156.839 87.5968 155.245 87.3569C153.65 87.117 152.022 87.2339 150.478 87.699C148.934 88.1639 147.513 88.9653 146.316 90.0455C145.119 91.1257 144.176 92.4578 143.556 93.946C142.936 95.4342 142.653 97.0415 142.728 98.652C142.804 100.263 143.235 101.836 143.992 103.26C144.74 104.667 146.4 104.003 148.152 103.302C149.525 102.753 150.956 102.181 152.047 102.567ZM100.672 102.567C99.49 102.985 98.611 104.258 97.775 105.468C96.645 107.105 95.592 108.627 93.979 107.769C91.5845 106.501 89.7482 104.386 88.8278 101.838C87.9075 99.2895 87.9692 96.4896 89.0008 93.9841C90.0324 91.4786 91.9601 89.4471 94.408 88.2855C96.856 87.1239 99.6488 86.9156 102.242 87.701C104.307 88.3228 106.141 89.5427 107.513 91.2065C108.885 92.8704 109.732 94.9035 109.949 97.049C110.165 99.1945 109.74 101.356 108.728 103.26C107.979 104.667 106.319 104.003 104.567 103.303C103.193 102.753 101.764 102.181 100.672 102.567ZM144.099 149.318C152.242 142.903 155.233 132.429 155.233 125.977C155.233 120.877 151.802 122.482 146.309 125.202L145.999 125.355C140.957 127.852 134.245 131.177 126.877 131.177C119.508 131.177 112.796 127.852 107.755 125.354C102.084 122.545 98.527 120.783 98.527 125.978C98.527 132.634 101.709 143.563 110.443 149.912C111.596 147.573 113.219 145.497 115.211 143.813C117.202 142.129 119.52 140.874 122.018 140.126C122.89 139.866 123.788 141.367 124.707 142.904C125.594 144.386 126.501 145.902 127.423 145.902C128.406 145.902 129.371 144.408 130.314 142.95C131.299 141.425 132.26 139.94 133.189 140.237C137.864 141.738 141.775 144.993 144.099 149.318Z" fill="#32343D"/>
<path d="M144.097 149.317C139.856 152.659 134.219 154.9 126.878 154.9C119.981 154.9 114.587 152.922 110.443 149.911C111.596 147.572 113.219 145.495 115.211 143.812C117.202 142.128 119.52 140.873 122.018 140.125C123.73 139.614 125.545 145.901 127.423 145.901C129.433 145.901 131.37 139.655 133.189 140.236C137.863 141.738 141.773 144.993 144.097 149.317Z" fill="#FF323D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M81.2 111.64C80.2312 112.288 79.1173 112.687 77.9572 112.801C76.7971 112.916 75.6267 112.742 74.55 112.295C73.6893 111.94 72.9072 111.418 72.2488 110.759C71.5903 110.101 71.0684 109.319 70.713 108.458C70.267 107.381 70.0935 106.211 70.2082 105.051C70.3228 103.891 70.7219 102.777 71.37 101.808C72.1488 100.642 73.2558 99.7333 74.5512 99.1967C75.8466 98.6601 77.272 98.5197 78.6471 98.7935C80.0223 99.0672 81.2853 99.7427 82.2764 100.734C83.2675 101.726 83.9422 102.99 84.215 104.365C84.4883 105.74 84.3477 107.165 83.8113 108.46C83.2748 109.755 82.3654 110.861 81.2 111.64ZM182.613 111.64C181.644 112.288 180.53 112.687 179.37 112.801C178.209 112.916 177.039 112.742 175.962 112.295C175.101 111.939 174.319 111.418 173.661 110.759C173.003 110.101 172.481 109.319 172.125 108.458C171.68 107.381 171.507 106.211 171.621 105.051C171.736 103.891 172.135 102.777 172.782 101.808C173.364 100.936 174.133 100.205 175.032 99.6658C175.931 99.1269 176.938 98.7942 177.981 98.6917C179.025 98.5891 180.078 98.7193 181.064 99.0728C182.051 99.4264 182.947 99.9944 183.688 100.736C184.68 101.727 185.355 102.99 185.628 104.365C185.902 105.74 185.761 107.165 185.224 108.46C184.687 109.755 183.779 110.861 182.613 111.64Z" fill="#FFAD03"/>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#FA520F" d="M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z"/></svg>

After

Width:  |  Height:  |  Size: 270 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#000000" d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 401.25678 293.71975">
<path fill="#7624F4" d="M303.9475,17.19926c42.79734,0,77.48933,34.69327,77.48933,77.48933s-34.69199,77.48933-77.48933,77.48933l76.86166,76.86244c9.76367,9.76313,2.84903,26.45667-10.95697,26.45667h-220.88335c-71.32686,0-129.14889-57.82202-129.14889-129.14889S77.64197,17.19926,148.96884,17.19926h154.97866ZM148.96884,68.85881c-42.79607,0-77.48933,34.69327-77.48933,77.48933s34.69327,77.48933,77.48933,77.48933,77.48933-34.69327,77.48933-77.48933-34.69327-77.48933-77.48933-77.48933Z"/>
</svg>

After

Width:  |  Height:  |  Size: 608 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#1FB8CD" d="M22.3977 7.0896h-2.3106V.0676l-7.5094 6.3542V.1577h-1.1554v6.1966L4.4904 0v7.0896H1.6023v10.3976h2.8882V24l6.932-6.3591v6.2005h1.1554v-6.0469l6.9318 6.1807v-6.4879h2.8882V7.0896zm-3.4657-4.531v4.531h-5.355l5.355-4.531zm-13.2862.0676 4.8691 4.4634H5.6458V2.6262zM2.7576 16.332V8.245h7.8476l-6.1149 6.1147v1.9723H2.7576zm2.8882 5.0404v-3.8852h.0001v-2.6488l5.7763-5.7764v7.0111l-5.7764 5.2993zm12.7086.0248-5.7766-5.1509V9.0618l5.7766 5.7766v6.5588zm2.8882-5.0652h-1.733v-1.9723L13.3948 8.245h7.8478v8.087z"/></svg>

After

Width:  |  Height:  |  Size: 598 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#E8EAED" d="M5 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6m14 0a3 3 0 1 0 0 6 3 3 0 0 0 0-6M5 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6m14 0a3 3 0 1 0 0 6 3 3 0 0 0 0-6M8 5h8v2H8zm0 12h8v2H8zM4 9h2v6H4zm14 0h2v6h-2z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#5F6368" d="M5 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6m14 0a3 3 0 1 0 0 6 3 3 0 0 0 0-6M5 15a3 3 0 1 0 0 6 3 3 0 0 0 0-6m14 0a3 3 0 1 0 0 6 3 3 0 0 0 0-6M8 5h8v2H8zm0 12h8v2H8zM4 9h2v6H4zm14 0h2v6h-2z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#E8EAED" d="M4 3h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m0 10h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2m2-7v2h2V6zm0 10v2h2v-2zm4-10v2h8V6zm0 10v2h8v-2z"/></svg>

After

Width:  |  Height:  |  Size: 283 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#5F6368" d="M4 3h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m0 10h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2m2-7v2h2V6zm0 10v2h2v-2zm4-10v2h8V6zm0 10v2h8v-2z"/></svg>

After

Width:  |  Height:  |  Size: 283 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#FFFFFF" d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>

After

Width:  |  Height:  |  Size: 288 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#000000" d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>

After

Width:  |  Height:  |  Size: 288 B