mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 11:13:48 +00:00
Added detailed plugin information to the plugins page (#1013)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
b371b3d54f
commit
f912af321f
26 files changed
+884
-95
No files matched your search
@@ -4303,6 +4303,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINDELETEACTION::T4033722845"] = "Do y
|
||||
-- Delete configuration plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINDELETEACTION::T459830575"] = "Delete configuration plugin"
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINLINKBUTTON::T1999487139"] = "Send a mail"
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINLINKBUTTON::T4239378936"] = "Open website"
|
||||
|
||||
-- Alpha phase means that we are working on the last details before the beta phase.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T166807685"] = "Alpha phase means that we are working on the last details before the beta phase."
|
||||
|
||||
@@ -7297,6 +7303,72 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T914647109"] = "Sends da
|
||||
-- Destination
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T994314591"] = "Destination"
|
||||
|
||||
-- Version
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1573770551"] = "Version"
|
||||
|
||||
-- Plugin ID
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1661076691"] = "Plugin ID"
|
||||
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1725856265"] = "Description"
|
||||
|
||||
-- Authors
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1985367263"] = "Authors"
|
||||
|
||||
-- Related link
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2077261368"] = "Related link"
|
||||
|
||||
-- Managed by your organization
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2100092229"] = "Managed by your organization"
|
||||
|
||||
-- No target groups specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2178642403"] = "No target groups specified"
|
||||
|
||||
-- Here are the details of this plugin. Most of them come from its authors, and AI Studio shows them as stated without being able to verify them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T223598799"] = "Here are the details of this plugin. Most of them come from its authors, and AI Studio shows them as stated without being able to verify them."
|
||||
|
||||
-- No categories specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2267956060"] = "No categories specified"
|
||||
|
||||
-- Target groups
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2402831960"] = "Target groups"
|
||||
|
||||
-- Support contact
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2434966596"] = "Support contact"
|
||||
|
||||
-- Last changed
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2839174209"] = "Last changed"
|
||||
|
||||
-- The authors marked this plugin as no longer maintained.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3041246111"] = "The authors marked this plugin as no longer maintained."
|
||||
|
||||
-- Email contact
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3304251427"] = "Email contact"
|
||||
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3448155331"] = "Close"
|
||||
|
||||
-- Type
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3512062061"] = "Type"
|
||||
|
||||
-- Copies the plugin ID to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3514306881"] = "Copies the plugin ID to the clipboard"
|
||||
|
||||
-- No authors specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3586343532"] = "No authors specified"
|
||||
|
||||
-- Categories
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T4034259341"] = "Categories"
|
||||
|
||||
-- The authors marked this plugin as deprecated: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T497068698"] = "The authors marked this plugin as deprecated: {0}"
|
||||
|
||||
-- Origin
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T529732719"] = "Origin"
|
||||
|
||||
-- Not specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T641241064"] = "Not specified"
|
||||
|
||||
-- Load what the AI should do from file
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1254789334"] = "Load what the AI should do from file"
|
||||
|
||||
@@ -10321,6 +10393,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T923110805"] = "A test configurat
|
||||
-- Install Pandoc
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Install Pandoc"
|
||||
|
||||
-- Plugin information: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1155531948"] = "Plugin information: {0}"
|
||||
|
||||
-- Potentially Dangerous Plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1229643769"] = "Potentially Dangerous Plugin"
|
||||
|
||||
@@ -10351,21 +10426,18 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T169921408"] = "Import plugin from a
|
||||
-- Disabled Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
|
||||
|
||||
-- Plugin information
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1743041840"] = "Plugin information"
|
||||
|
||||
-- Edit assistant plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1851885496"] = "Edit assistant plugin"
|
||||
|
||||
-- Plugin installed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1889482678"] = "Plugin installed."
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "Send a mail"
|
||||
|
||||
-- Enable plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
|
||||
|
||||
-- No source url available
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2058912565"] = "No source url available"
|
||||
|
||||
-- Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins"
|
||||
|
||||
@@ -10432,9 +10504,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4066679817"] = "The automatic securi
|
||||
-- The assistant plugin '{0}' has been successfully revised.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4157246824"] = "The assistant plugin '{0}' has been successfully revised."
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4239378936"] = "Open website"
|
||||
|
||||
-- Change what this tile opens
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4272203100"] = "Change what this tile opens"
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<tr>
|
||||
<td style="width: 210px;">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@this.Icon" Size="Size.Small"/>
|
||||
<MudText Typo="Typo.body2"><b>@this.Label</b></MudText>
|
||||
</MudStack>
|
||||
</td>
|
||||
<td>
|
||||
@this.ChildContent
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// One row of a table that shows information: an icon and a label in the first cell, the value in the
|
||||
/// second.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Place it inside the body of a MudSimpleTable. The value is free content, so a row may hold buttons
|
||||
/// next to its text.
|
||||
/// </remarks>
|
||||
public partial class InfoTableRow : ComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The icon in front of the label.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.Info;
|
||||
|
||||
/// <summary>
|
||||
/// The label, which names what the row shows.
|
||||
/// </summary>
|
||||
[Parameter, EditorRequired]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the row.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@using AIStudio.Tools.PluginSystem
|
||||
@inherits MSGComponentBase
|
||||
|
||||
@if (PluginLinks.TryParse(this.Url, out var link) && !PluginLinks.IsPlaceholder(this.Url))
|
||||
{
|
||||
if (PluginLinks.IsMail(link))
|
||||
{
|
||||
<MudTooltip Text="@T("Send a mail")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Email" Href="@link.AbsoluteUri" Target="_blank" Size="@this.Size"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTooltip Text="@T("Open website")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.OpenInBrowser" Href="@link.AbsoluteUri" Target="_blank" Size="@this.Size"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Opens a link from the metadata of a plugin: a website in the browser, or a new mail in the mail
|
||||
/// program.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The button only appears when there is something to open. A support contact may be plain text, such as
|
||||
/// a name or a phone number, and the links which the Assistant Builder fills in for every plugin tell
|
||||
/// nothing about the plugin itself. A disabled button would only leave the user wondering why it cannot
|
||||
/// be clicked.
|
||||
/// </remarks>
|
||||
public partial class PluginLinkButton : MSGComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The text to link, as the plugin states it.
|
||||
/// </summary>
|
||||
[Parameter, EditorRequired]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Size Size { get; set; } = Size.Medium;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
@inherits MSGComponentBase
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@T("Here are the details of this plugin. Most of them come from its authors, and AI Studio shows them as stated without being able to verify them.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudSimpleTable Dense="@true" Hover="@true" Bordered="@true" Striped="@true" Style="overflow-x: auto;">
|
||||
<tbody>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Description" Label="@T("Description")">
|
||||
<MudText Typo="Typo.body2">@this.Plugin.Description</MudText>
|
||||
</InfoTableRow>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Extension" Label="@T("Type")">
|
||||
<MudText Typo="Typo.body2">@this.TypeText</MudText>
|
||||
</InfoTableRow>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Tag" Label="@T("Version")">
|
||||
<MudText Typo="Typo.body2">@this.Plugin.Version</MudText>
|
||||
</InfoTableRow>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.EditCalendar" Label="@T("Last changed")">
|
||||
<MudText Typo="Typo.body2">@this.LastChangedText</MudText>
|
||||
</InfoTableRow>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.People" Label="@T("Authors")">
|
||||
<MudText Typo="Typo.body2">@this.AuthorsText</MudText>
|
||||
</InfoTableRow>
|
||||
@if (!string.IsNullOrWhiteSpace(this.Plugin.SupportContact))
|
||||
{
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.SupportAgent" Label="@T("Support contact")">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudText Typo="Typo.body2" Style="overflow-wrap: anywhere;">@this.SupportContactText</MudText>
|
||||
<PluginLinkButton Url="@this.Plugin.SupportContact" Size="Size.Small"/>
|
||||
</MudStack>
|
||||
</InfoTableRow>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(this.Plugin.SourceURL))
|
||||
{
|
||||
<InfoTableRow Icon="@this.SourceIcon" Label="@this.SourceLabel">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudText Typo="Typo.body2" Style="overflow-wrap: anywhere;">@this.SourceText</MudText>
|
||||
<PluginLinkButton Url="@this.Plugin.SourceURL" Size="Size.Small"/>
|
||||
</MudStack>
|
||||
</InfoTableRow>
|
||||
}
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Category" Label="@T("Categories")">
|
||||
<MudText Typo="Typo.body2">@this.CategoriesText</MudText>
|
||||
</InfoTableRow>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Groups" Label="@T("Target groups")">
|
||||
<MudText Typo="Typo.body2">@this.TargetGroupsText</MudText>
|
||||
</InfoTableRow>
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Fingerprint" Label="@T("Plugin ID")">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudText Typo="Typo.body2" Style="overflow-wrap: anywhere;">@this.Plugin.Id</MudText>
|
||||
<MudCopyClipboardButton StringContent="@this.Plugin.Id.ToString()" TooltipMessage="@T("Copies the plugin ID to the clipboard")"/>
|
||||
</MudStack>
|
||||
</InfoTableRow>
|
||||
@if (this.Plugin.IsManagedByConfigServer)
|
||||
{
|
||||
<InfoTableRow Icon="@Icons.Material.Filled.Business" Label="@T("Origin")">
|
||||
<MudText Typo="Typo.body2">@T("Managed by your organization")</MudText>
|
||||
</InfoTableRow>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
|
||||
@if (!this.Plugin.IsMaintained || !string.IsNullOrWhiteSpace(this.Plugin.DeprecationMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Class="mt-3">
|
||||
@(string.IsNullOrWhiteSpace(this.Plugin.DeprecationMessage)
|
||||
? T("The authors marked this plugin as no longer maintained.")
|
||||
: string.Format(T("The authors marked this plugin as deprecated: {0}"), this.Plugin.DeprecationMessage))
|
||||
</MudAlert>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
|
||||
@T("Close")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,70 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the details of a plugin on the plugins page: what its authors state about it, and whether an
|
||||
/// organization manages it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The plugins page lists only the name, the version, and the description of a plugin. Everything else
|
||||
/// a plugin states, such as its authors or where to get support, would otherwise only be found in its
|
||||
/// code.
|
||||
/// </remarks>
|
||||
public partial class PluginInfoDialog : MSGComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin to show. The metadata alone would not do, because only an available plugin knows
|
||||
/// whether an organization manages it.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IAvailablePlugin Plugin { get; set; } = null!;
|
||||
|
||||
private string TypeText => this.Plugin.Type.GetName();
|
||||
|
||||
private string AuthorsText => this.Plugin.Authors.Length > 0 ? string.Join(", ", this.Plugin.Authors) : this.T("No authors specified");
|
||||
|
||||
private string CategoriesText => this.Plugin.Categories.Length > 0 ? string.Join(", ", this.Plugin.Categories.Select(category => category.GetName())) : this.T("No categories specified");
|
||||
|
||||
private string TargetGroupsText => this.Plugin.TargetGroups.Length > 0 ? string.Join(", ", this.Plugin.TargetGroups.Select(targetGroup => targetGroup.Name())) : this.T("No target groups specified");
|
||||
|
||||
/// <summary>
|
||||
/// The plugin states the time in UTC; the user reads it in their own time zone.
|
||||
/// </summary>
|
||||
private string LastChangedText => this.Plugin.LastChanged?.ToLocalTime().ToString("g", I18N.I.Culture) ?? this.T("Not specified");
|
||||
|
||||
private string SupportContactText => this.GetLinkText(this.Plugin.SupportContact);
|
||||
|
||||
private string SourceText => this.GetLinkText(this.Plugin.SourceURL);
|
||||
|
||||
/// <summary>
|
||||
/// The field SOURCE_URL holds a website, such as a repository or a wiki, or a mail address. The
|
||||
/// label and the icon of its row name which of both it is, since "source" would fit neither a wiki
|
||||
/// nor a mail address.
|
||||
/// </summary>
|
||||
private bool IsSourceAMail => PluginLinks.TryParse(this.Plugin.SourceURL, out var link) && PluginLinks.IsMail(link);
|
||||
|
||||
private string SourceLabel => this.IsSourceAMail ? this.T("Email contact") : this.T("Related link");
|
||||
|
||||
private string SourceIcon => this.IsSourceAMail ? Icons.Material.Filled.ContactMail : Icons.Material.Filled.Link;
|
||||
|
||||
/// <summary>
|
||||
/// Shows a link the way the user recognizes it: a mail address without its scheme, and a placeholder
|
||||
/// of the Assistant Builder as not stated. A text which is no link stays as it is.
|
||||
/// </summary>
|
||||
private string GetLinkText(string text)
|
||||
{
|
||||
if (PluginLinks.IsPlaceholder(text))
|
||||
return this.T("Not specified");
|
||||
|
||||
return PluginLinks.TryParse(text, out var link) ? PluginLinks.GetDisplayText(link) : text;
|
||||
}
|
||||
|
||||
private void Close() => this.MudDialog.Close();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
@using AIStudio.Tools.PluginSystem
|
||||
@using AIStudio.Tools.PluginSystem.Assistants
|
||||
@using AIStudio.Tools.Services
|
||||
@inherits MSGComponentBase
|
||||
@attribute [Route(Routes.PLUGINS)]
|
||||
|
||||
@@ -79,9 +78,20 @@
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.h6" Class="mb-0 pb-0 gap-0">
|
||||
@context.Name
|
||||
</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-0 pb-0 gap-0">
|
||||
@context.Name
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary ms-1">
|
||||
v@(context.Version)
|
||||
</MudText>
|
||||
@if (context is IAvailablePlugin { IsInternal: false } infoPlugin)
|
||||
{
|
||||
<MudTooltip Text="@T("Plugin information")">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Info" Size="Size.Small" OnClick="@(() => this.ShowPluginInfoAsync(infoPlugin))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body1">
|
||||
@context.Description
|
||||
</MudText>
|
||||
@@ -106,26 +116,11 @@
|
||||
}
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="0" Class="ms-3" Style="gap: 4px;">
|
||||
@if (context is { IsInternal: false } && !string.IsNullOrWhiteSpace(context.SourceURL))
|
||||
@if (context is { IsInternal: false })
|
||||
{
|
||||
var sourceUrl = context.SourceURL;
|
||||
var isSendingMail = IsSendingMail(sourceUrl);
|
||||
if (isSendingMail)
|
||||
{
|
||||
var isDefaultSupportContact = string.Equals(sourceUrl, AssistantPluginGenerationService.DEFAULT_SUPPORT_CONTACT, StringComparison.Ordinal);
|
||||
<MudTooltip Text="@(isDefaultSupportContact ? string.Empty : T("Send a mail"))">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Email" Href="@sourceUrl" Target="_blank" Size="Size.Medium" Disabled="@isDefaultSupportContact"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
var isDefaultSourceUrl = string.Equals(sourceUrl, AssistantPluginGenerationService.DEFAULT_SOURCE_URL, StringComparison.Ordinal);
|
||||
<MudTooltip Text="@(isDefaultSourceUrl ? T("No source url available") : T("Open website"))">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.OpenInBrowser" Href="@sourceUrl" Target="_blank" Size="Size.Medium" Disabled="@isDefaultSourceUrl"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<PluginLinkButton Url="@context.SourceURL" Size="Size.Medium"/>
|
||||
}
|
||||
|
||||
|
||||
@if (context is IAvailablePlugin editablePlugin && CanEditAssistantPlugin(editablePlugin))
|
||||
{
|
||||
<MudTooltip Text="@T("Edit assistant plugin")">
|
||||
|
||||
@@ -485,7 +485,16 @@ public partial class Plugins : MSGComponentBase
|
||||
await dialogReference.Result;
|
||||
}
|
||||
|
||||
private static bool IsSendingMail(string sourceUrl) => sourceUrl.TrimStart().StartsWith("mailto:", StringComparison.OrdinalIgnoreCase);
|
||||
private async Task ShowPluginInfoAsync(IAvailablePlugin plugin)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<PluginInfoDialog>
|
||||
{
|
||||
{ x => x.Plugin, plugin },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<PluginInfoDialog>(string.Format(this.T("Plugin information: {0}"), plugin.Name), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
await dialogReference.Result;
|
||||
}
|
||||
|
||||
private PluginAssistants? TryGetAssistantPlugin(Guid pluginId) => PluginFactory.RunningPlugins.OfType<PluginAssistants>().FirstOrDefault(x => x.Id == pluginId);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ ID = "54f8f4a2-cd10-4a5f-b2d8-2e0f7875f9e4"
|
||||
NAME = "Translation"
|
||||
DESCRIPTION = "Assistant plugin example that translates text into a selected target language."
|
||||
VERSION = "1.0.0"
|
||||
LAST_CHANGED = "2026-09-22T12:00:00Z"
|
||||
TYPE = "ASSISTANT"
|
||||
AUTHORS = {"MindWork AI"}
|
||||
SUPPORT_CONTACT = "mailto:info@mindwork.ai"
|
||||
|
||||
@@ -22,6 +22,9 @@ DESCRIPTION = "This is a pre-defined configuration of <Company Name>"
|
||||
-- The version of the plugin:
|
||||
VERSION = "1.0.0"
|
||||
|
||||
-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.
|
||||
LAST_CHANGED = "2025-12-31T12:00:00Z"
|
||||
|
||||
-- The type of the plugin:
|
||||
TYPE = "ASSISTANT"
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ DESCRIPTION = "This is a pre-defined configuration of <Company Name>"
|
||||
-- The version of the plugin:
|
||||
VERSION = "1.0.0"
|
||||
|
||||
-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.
|
||||
LAST_CHANGED = "2025-12-31T12:00:00Z"
|
||||
|
||||
-- The type of the plugin:
|
||||
TYPE = "CONFIGURATION"
|
||||
|
||||
|
||||
+79
-10
@@ -4305,6 +4305,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINDELETEACTION::T4033722845"] = "Möc
|
||||
-- Delete configuration plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINDELETEACTION::T459830575"] = "Konfigurations-Plugin löschen"
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINLINKBUTTON::T1999487139"] = "E-Mail senden"
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINLINKBUTTON::T4239378936"] = "Website öffnen"
|
||||
|
||||
-- Alpha phase means that we are working on the last details before the beta phase.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T166807685"] = "Alpha-Phase bedeutet, dass wir an den letzten Details arbeiten, bevor die Beta-Phase beginnt."
|
||||
|
||||
@@ -7198,7 +7204,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T1642243064"] = "Quelle"
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T1974491324"] = "Sie sind dabei, ein Sprach-Plugin aus einer Datei zu installieren."
|
||||
|
||||
-- Authors
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T1985367263"] = "Autor:innen"
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T1985367263"] = "Autoren"
|
||||
|
||||
-- Data source
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T2034620186"] = "Datenquelle"
|
||||
@@ -7299,6 +7305,72 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T914647109"] = "Sendet D
|
||||
-- Destination
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T994314591"] = "Ziel"
|
||||
|
||||
-- Version
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1573770551"] = "Version"
|
||||
|
||||
-- Plugin ID
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1661076691"] = "Plugin-ID"
|
||||
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1725856265"] = "Beschreibung"
|
||||
|
||||
-- Authors
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1985367263"] = "Autoren"
|
||||
|
||||
-- Related link
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2077261368"] = "Weiterführender Link"
|
||||
|
||||
-- Managed by your organization
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2100092229"] = "Von Ihrer Organisation verwaltet"
|
||||
|
||||
-- No target groups specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2178642403"] = "Keine Zielgruppen angegeben"
|
||||
|
||||
-- Here are the details of this plugin. Most of them come from its authors, and AI Studio shows them as stated without being able to verify them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T223598799"] = "Hier finden Sie die Details zu diesem Plugin. Die meisten Angaben stammen von seinen Autoren. AI Studio zeigt sie so an, wie sie angegeben wurden, und kann sie nicht überprüfen."
|
||||
|
||||
-- No categories specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2267956060"] = "Keine Kategorien angegeben"
|
||||
|
||||
-- Target groups
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2402831960"] = "Zielgruppen"
|
||||
|
||||
-- Support contact
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2434966596"] = "Supportkontakt"
|
||||
|
||||
-- Last changed
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2839174209"] = "Zuletzt geändert"
|
||||
|
||||
-- The authors marked this plugin as no longer maintained.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3041246111"] = "Die Autoren haben dieses Plugin als nicht mehr gepflegt gekennzeichnet."
|
||||
|
||||
-- Email contact
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3304251427"] = "E-Mail-Kontakt"
|
||||
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3448155331"] = "Schließen"
|
||||
|
||||
-- Type
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3512062061"] = "Typ"
|
||||
|
||||
-- Copies the plugin ID to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3514306881"] = "Kopiert die Plugin-ID in die Zwischenablage"
|
||||
|
||||
-- No authors specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3586343532"] = "Keine Autoren angegeben"
|
||||
|
||||
-- Categories
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T4034259341"] = "Kategorien"
|
||||
|
||||
-- The authors marked this plugin as deprecated: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T497068698"] = "Die Autoren haben dieses Plugin als veraltet gekennzeichnet: {0}"
|
||||
|
||||
-- Origin
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T529732719"] = "Herkunft"
|
||||
|
||||
-- Not specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T641241064"] = "Nicht angegeben"
|
||||
|
||||
-- Load what the AI should do from file
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1254789334"] = "Lade aus einer Datei, was die KI tun soll"
|
||||
|
||||
@@ -10323,6 +10395,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T923110805"] = "Eine Testkonfigur
|
||||
-- Install Pandoc
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Pandoc installieren"
|
||||
|
||||
-- Plugin information: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1155531948"] = "Plugin-Informationen: {0}"
|
||||
|
||||
-- Potentially Dangerous Plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1229643769"] = "Potenziell gefährliches Plugin"
|
||||
|
||||
@@ -10353,21 +10428,18 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T169921408"] = "Plugin aus einer Date
|
||||
-- Disabled Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Deaktivierte Plugins"
|
||||
|
||||
-- Plugin information
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1743041840"] = "Plugin-Informationen"
|
||||
|
||||
-- Edit assistant plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1851885496"] = "Assistent-Plugin bearbeiten"
|
||||
|
||||
-- Plugin installed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1889482678"] = "Plugin installiert."
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "E-Mail senden"
|
||||
|
||||
-- Enable plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Plugin aktivieren"
|
||||
|
||||
-- No source url available
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2058912565"] = "Keine Quell-URL verfügbar"
|
||||
|
||||
-- Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins"
|
||||
|
||||
@@ -10434,9 +10506,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4066679817"] = "Die automatische Sic
|
||||
-- The assistant plugin '{0}' has been successfully revised.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4157246824"] = "Das Assistenten-Plugin „{0}“ wurde erfolgreich überarbeitet."
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4239378936"] = "Website öffnen"
|
||||
|
||||
-- Change what this tile opens
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4272203100"] = "Ändern, was diese Kachel öffnet"
|
||||
|
||||
|
||||
+78
-9
@@ -4305,6 +4305,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINDELETEACTION::T4033722845"] = "Do y
|
||||
-- Delete configuration plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINDELETEACTION::T459830575"] = "Delete configuration plugin"
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINLINKBUTTON::T1999487139"] = "Send a mail"
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PLUGINLINKBUTTON::T4239378936"] = "Open website"
|
||||
|
||||
-- Alpha phase means that we are working on the last details before the beta phase.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T166807685"] = "Alpha phase means that we are working on the last details before the beta phase."
|
||||
|
||||
@@ -7299,6 +7305,72 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T914647109"] = "Sends da
|
||||
-- Destination
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGINIMPORTDIALOG::T994314591"] = "Destination"
|
||||
|
||||
-- Version
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1573770551"] = "Version"
|
||||
|
||||
-- Plugin ID
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1661076691"] = "Plugin ID"
|
||||
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1725856265"] = "Description"
|
||||
|
||||
-- Authors
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T1985367263"] = "Authors"
|
||||
|
||||
-- Related link
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2077261368"] = "Related link"
|
||||
|
||||
-- Managed by your organization
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2100092229"] = "Managed by your organization"
|
||||
|
||||
-- No target groups specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2178642403"] = "No target groups specified"
|
||||
|
||||
-- Here are the details of this plugin. Most of them come from its authors, and AI Studio shows them as stated without being able to verify them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T223598799"] = "Here are the details of this plugin. Most of them come from its authors, and AI Studio shows them as stated without being able to verify them."
|
||||
|
||||
-- No categories specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2267956060"] = "No categories specified"
|
||||
|
||||
-- Target groups
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2402831960"] = "Target groups"
|
||||
|
||||
-- Support contact
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2434966596"] = "Support contact"
|
||||
|
||||
-- Last changed
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T2839174209"] = "Last changed"
|
||||
|
||||
-- The authors marked this plugin as no longer maintained.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3041246111"] = "The authors marked this plugin as no longer maintained."
|
||||
|
||||
-- Email contact
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3304251427"] = "Email contact"
|
||||
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3448155331"] = "Close"
|
||||
|
||||
-- Type
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3512062061"] = "Type"
|
||||
|
||||
-- Copies the plugin ID to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3514306881"] = "Copies the plugin ID to the clipboard"
|
||||
|
||||
-- No authors specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T3586343532"] = "No authors specified"
|
||||
|
||||
-- Categories
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T4034259341"] = "Categories"
|
||||
|
||||
-- The authors marked this plugin as deprecated: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T497068698"] = "The authors marked this plugin as deprecated: {0}"
|
||||
|
||||
-- Origin
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T529732719"] = "Origin"
|
||||
|
||||
-- Not specified
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PLUGININFODIALOG::T641241064"] = "Not specified"
|
||||
|
||||
-- Load what the AI should do from file
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1254789334"] = "Load what the AI should do from file"
|
||||
|
||||
@@ -10323,6 +10395,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T923110805"] = "A test configurat
|
||||
-- Install Pandoc
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Install Pandoc"
|
||||
|
||||
-- Plugin information: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1155531948"] = "Plugin information: {0}"
|
||||
|
||||
-- Potentially Dangerous Plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1229643769"] = "Potentially Dangerous Plugin"
|
||||
|
||||
@@ -10353,21 +10428,18 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T169921408"] = "Import plugin from a
|
||||
-- Disabled Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
|
||||
|
||||
-- Plugin information
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1743041840"] = "Plugin information"
|
||||
|
||||
-- Edit assistant plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1851885496"] = "Edit assistant plugin"
|
||||
|
||||
-- Plugin installed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1889482678"] = "Plugin installed."
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "Send a mail"
|
||||
|
||||
-- Enable plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
|
||||
|
||||
-- No source url available
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2058912565"] = "No source url available"
|
||||
|
||||
-- Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins"
|
||||
|
||||
@@ -10434,9 +10506,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4066679817"] = "The automatic securi
|
||||
-- The assistant plugin '{0}' has been successfully revised.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4157246824"] = "The assistant plugin '{0}' has been successfully revised."
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4239378936"] = "Open website"
|
||||
|
||||
-- Change what this tile opens
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4272203100"] = "Change what this tile opens"
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ DESCRIPTION = "Describes the models <Company Name> runs itself"
|
||||
-- The version of the plugin:
|
||||
VERSION = "1.0.0"
|
||||
|
||||
-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.
|
||||
LAST_CHANGED = "2025-12-31T12:00:00Z"
|
||||
|
||||
-- The type of the plugin:
|
||||
TYPE = "MODEL"
|
||||
|
||||
|
||||
+12
-1
@@ -69,11 +69,15 @@ public static class DirectChatLauncherLuaWriter
|
||||
/// <summary>
|
||||
/// Writes the complete plugin.lua for an installed launcher whose settings changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All metadata is carried over except the time of the last change: changing the settings
|
||||
/// changes the plugin, so that time becomes now.
|
||||
/// </remarks>
|
||||
/// <param name="plugin">The installed launcher whose metadata is carried over.</param>
|
||||
/// <param name="definition">The name, title, description, and chat settings the user chose.</param>
|
||||
/// <returns>The plugin.lua content, ready to be validated and written.</returns>
|
||||
public static string Write(PluginAssistants plugin, DirectChatLauncherDefinition definition) =>
|
||||
Write(DirectChatLauncherPluginMetadata.FromPlugin(plugin), definition);
|
||||
Write(DirectChatLauncherPluginMetadata.FromPlugin(plugin) with { LastChanged = DateTimeOffset.UtcNow }, definition);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the complete plugin.lua for a launcher.
|
||||
@@ -114,6 +118,13 @@ public static class DirectChatLauncherLuaWriter
|
||||
builder.AppendLine($"VERSION = \"{plugin.Version}\"");
|
||||
builder.AppendLine();
|
||||
|
||||
if (plugin.LastChanged is { } lastChanged)
|
||||
{
|
||||
builder.AppendLine("-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.");
|
||||
builder.AppendLine($"LAST_CHANGED = \"{PluginBase.FormatLastChanged(lastChanged)}\"");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
builder.AppendLine("-- The type of the plugin:");
|
||||
builder.AppendLine($"TYPE = \"{nameof(PluginType.ASSISTANT)}\"");
|
||||
builder.AppendLine();
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@ namespace AIStudio.Tools.PluginSystem.Assistants;
|
||||
/// </remarks>
|
||||
/// <param name="Id">The plugin ID, which stays with the plugin for its whole life.</param>
|
||||
/// <param name="Version">The plugin version, as it appears in the Lua file.</param>
|
||||
/// <param name="LastChanged">When the plugin was last changed, in UTC, when it states it.</param>
|
||||
/// <param name="Authors">The authors of the plugin.</param>
|
||||
/// <param name="SupportContact">Where users turn with questions about this plugin.</param>
|
||||
/// <param name="SourceURL">Where the plugin comes from.</param>
|
||||
@@ -18,12 +19,12 @@ namespace AIStudio.Tools.PluginSystem.Assistants;
|
||||
/// <param name="IsMaintained">Whether the plugin is still maintained.</param>
|
||||
/// <param name="DeprecationMessage">What users are told when the plugin is deprecated.</param>
|
||||
/// <param name="IsAssistantBuilderGenerated">Whether the Assistant Builder created this plugin.</param>
|
||||
public sealed record DirectChatLauncherPluginMetadata(Guid Id, string Version, IReadOnlyList<string> Authors, string SupportContact, string SourceURL,
|
||||
public sealed record DirectChatLauncherPluginMetadata(Guid Id, string Version, DateTimeOffset? LastChanged, IReadOnlyList<string> Authors, string SupportContact, string SourceURL,
|
||||
IReadOnlyList<PluginCategory> Categories, IReadOnlyList<PluginTargetGroup> TargetGroups, bool IsMaintained, string DeprecationMessage, bool IsAssistantBuilderGenerated)
|
||||
{
|
||||
/// <summary>
|
||||
/// Takes the metadata of an installed launcher for the case where one is edited.
|
||||
/// </summary>
|
||||
public static DirectChatLauncherPluginMetadata FromPlugin(PluginAssistants plugin) => new(plugin.Id, plugin.Version.ToString(), plugin.Authors,
|
||||
public static DirectChatLauncherPluginMetadata FromPlugin(PluginAssistants plugin) => new(plugin.Id, plugin.Version.ToString(), plugin.LastChanged, plugin.Authors,
|
||||
plugin.SupportContact, plugin.SourceURL, plugin.Categories, plugin.TargetGroups, plugin.IsMaintained, plugin.DeprecationMessage, plugin.IsAssistantBuilderGenerated);
|
||||
}
|
||||
@@ -37,6 +37,11 @@ public interface IPluginMetadata
|
||||
/// </summary>
|
||||
public PluginVersion Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When this plugin was last changed, in UTC, when the plugin states it.
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastChanged { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The authors of this plugin.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
using Lua;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
@@ -8,8 +10,31 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
/// </summary>
|
||||
public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
{
|
||||
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginBase));
|
||||
|
||||
/// <summary>
|
||||
/// The only format the field LAST_CHANGED accepts: a point in time in UTC, read and written with
|
||||
/// the invariant culture.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A date alone cannot be shown in the time zone of the user: whether a day in UTC is the same day
|
||||
/// locally depends on the time. The field therefore always carries the time, and the UI converts it.
|
||||
/// </remarks>
|
||||
public const string LAST_CHANGED_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
|
||||
/// <summary>
|
||||
/// Writes a point in time the way the field LAST_CHANGED expects it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The format ends in a literal Z, so the time has to be converted to UTC first. Otherwise, a
|
||||
/// local time would be written as if it were UTC.
|
||||
/// </remarks>
|
||||
/// <param name="lastChanged">When the plugin was last changed, in any time zone.</param>
|
||||
/// <returns>The time in UTC, formatted for the field LAST_CHANGED.</returns>
|
||||
public static string FormatLastChanged(DateTimeOffset lastChanged) => lastChanged.ToUniversalTime().ToString(LAST_CHANGED_FORMAT, CultureInfo.InvariantCulture);
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginBase).Namespace, nameof(PluginBase));
|
||||
|
||||
|
||||
private readonly IReadOnlyCollection<string> baseIssues;
|
||||
protected readonly LuaState State;
|
||||
|
||||
@@ -33,6 +58,9 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
/// <inheritdoc />
|
||||
public PluginVersion Version { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset? LastChanged { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string[] Authors { get; } = [];
|
||||
|
||||
@@ -119,7 +147,9 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
this.Version = version;
|
||||
else if(this is not NoPlugin)
|
||||
issues.Add(issue);
|
||||
|
||||
|
||||
this.LastChanged = this.ReadLastChanged();
|
||||
|
||||
if(this.TryInitAuthors(out issue, out var authors))
|
||||
this.Authors = authors;
|
||||
else if(this is not NoPlugin)
|
||||
@@ -274,6 +304,29 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads when the plugin was last changed, when the plugin states it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unlike the other fields, LAST_CHANGED is optional: existing plugins were shipped before this
|
||||
/// field existed, and a missing or malformed value is not a reason to refuse loading an otherwise
|
||||
/// valid plugin. A malformed value is logged and treated as absent.
|
||||
/// </remarks>
|
||||
/// <returns>The read point in time in UTC, or null when the field is absent or malformed.</returns>
|
||||
private DateTimeOffset? ReadLastChanged()
|
||||
{
|
||||
if (!this.State.Environment["LAST_CHANGED"].TryRead<string>(out var lastChangedText))
|
||||
return null;
|
||||
|
||||
if (!DateTimeOffset.TryParseExact(lastChangedText, LAST_CHANGED_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var lastChanged))
|
||||
{
|
||||
LOG.LogWarning("The field LAST_CHANGED of plugin '{PluginName}' is not a valid point in time in UTC of the form YYYY-MM-DDTHH:MM:SSZ. Ignoring it. Please fix the plugin.", this.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return lastChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to read the authors of the plugin.
|
||||
/// </summary>
|
||||
@@ -346,55 +399,22 @@ public abstract partial class PluginBase : IPluginMetadata, IDisposable
|
||||
}
|
||||
|
||||
url = url.Trim();
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var sourceUri))
|
||||
if (!PluginLinks.TryParse(url, out var sourceUri))
|
||||
{
|
||||
url = string.Empty;
|
||||
message = TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.");
|
||||
return false;
|
||||
}
|
||||
message = url.StartsWith($"{Uri.UriSchemeMailto}:", StringComparison.OrdinalIgnoreCase)
|
||||
? TB("The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.")
|
||||
: TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.");
|
||||
|
||||
var isHttp = sourceUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase);
|
||||
var isHttps = sourceUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
|
||||
var isMailTo = sourceUri.Scheme.Equals(Uri.UriSchemeMailto, StringComparison.OrdinalIgnoreCase);
|
||||
if (!isHttp && !isHttps && !isMailTo)
|
||||
{
|
||||
url = string.Empty;
|
||||
message = TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isMailTo)
|
||||
{
|
||||
var recipient = ExtractMailtoRecipient(url);
|
||||
if (string.IsNullOrWhiteSpace(recipient))
|
||||
{
|
||||
url = string.Empty;
|
||||
message = TB("The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
url = sourceUri.ToString();
|
||||
|
||||
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ExtractMailtoRecipient(string rawUrl)
|
||||
{
|
||||
var separatorIndex = rawUrl.IndexOf(':');
|
||||
if (separatorIndex < 0 || separatorIndex + 1 >= rawUrl.Length)
|
||||
return string.Empty;
|
||||
|
||||
var schemeSpecificPart = rawUrl[(separatorIndex + 1)..];
|
||||
var queryStart = schemeSpecificPart.IndexOf('?');
|
||||
var recipient = queryStart >= 0
|
||||
? schemeSpecificPart[..queryStart]
|
||||
: schemeSpecificPart;
|
||||
|
||||
return recipient.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to read the categories of the plugin.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Recognizes the links in the metadata of plugins.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The source URL of a plugin has to be such a link, and its support contact may be one. A plugin may
|
||||
/// only link to a website or to a mail address: another scheme, such as file: or javascript:, would let a
|
||||
/// plugin open anything on the computer of the user with a single click. Loading a plugin and showing its
|
||||
/// links both decide with this class, so that AI Studio never offers a link which loading would refuse.
|
||||
/// </remarks>
|
||||
public static class PluginLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to read a text as a link which a plugin may offer.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to read. Whitespace around it does not count.</param>
|
||||
/// <param name="uri">The link, when the text is one.</param>
|
||||
/// <returns>True, when the text is an absolute http, https, or mailto link, and a mailto link names a recipient.</returns>
|
||||
public static bool TryParse(string text, [NotNullWhen(true)] out Uri? uri)
|
||||
{
|
||||
if (Uri.TryCreate(text.Trim(), UriKind.Absolute, out uri) && IsAllowed(uri))
|
||||
return true;
|
||||
|
||||
uri = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a link writes a mail instead of opening a website.
|
||||
/// </summary>
|
||||
/// <param name="uri">The link to check.</param>
|
||||
/// <returns>True, when the link is a mailto link.</returns>
|
||||
public static bool IsMail(Uri uri) => uri.Scheme.Equals(Uri.UriSchemeMailto, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a text is one of the links which the Assistant Builder writes into the plugins it
|
||||
/// generates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every plugin has to state a support contact and a source URL, so the Assistant Builder fills in
|
||||
/// links to MindWork AI. They tell nothing about the plugin itself, so AI Studio treats them as not
|
||||
/// stated: it neither shows them nor offers a button for them. The check goes by the text alone, so
|
||||
/// these links count as not stated in any other plugin as well.
|
||||
/// </remarks>
|
||||
/// <param name="text">The text to check. Whitespace around it does not count.</param>
|
||||
/// <returns>True, when the text is one of these placeholders.</returns>
|
||||
public static bool IsPlaceholder(string text) => text.Trim() is AssistantPluginGenerationService.DEFAULT_SUPPORT_CONTACT or AssistantPluginGenerationService.DEFAULT_SOURCE_URL;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text by which the user recognizes a link.
|
||||
/// </summary>
|
||||
/// <param name="uri">The link.</param>
|
||||
/// <returns>For a mailto link its recipient, without the scheme and without a subject. For a website the link as the plugin states it.</returns>
|
||||
public static string GetDisplayText(Uri uri) => IsMail(uri) ? Uri.UnescapeDataString(GetRecipient(uri).Trim().ToString()) : uri.OriginalString;
|
||||
|
||||
private static bool IsAllowed(Uri uri)
|
||||
{
|
||||
// Without a recipient, the mail program would open a mail to nobody:
|
||||
if (IsMail(uri))
|
||||
return !GetRecipient(uri).IsWhiteSpace();
|
||||
|
||||
return uri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recipient of a mailto link, which is everything between the colon and the first
|
||||
/// question mark.
|
||||
/// </summary>
|
||||
/// <param name="mailUri">The mailto link.</param>
|
||||
/// <returns>The recipient as the link states it, which may be empty.</returns>
|
||||
private static ReadOnlySpan<char> GetRecipient(Uri mailUri)
|
||||
{
|
||||
var text = mailUri.OriginalString.AsSpan();
|
||||
var recipient = text[(text.IndexOf(':') + 1)..];
|
||||
var queryStart = recipient.IndexOf('?');
|
||||
return queryStart >= 0 ? recipient[..queryStart] : recipient;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ public sealed class PluginMetadata(PluginBase plugin, string localPath, bool isM
|
||||
/// <inheritdoc />
|
||||
public PluginVersion Version { get; } = plugin.Version;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset? LastChanged { get; } = plugin.LastChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string[] Authors { get; } = plugin.Authors;
|
||||
|
||||
|
||||
@@ -29,7 +29,13 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
private const string DEFAULT_AUTHOR = "MindWork AI - Assistant Builder";
|
||||
public const string DEFAULT_SUPPORT_CONTACT = "mailto:info@mindwork.ai";
|
||||
public const string DEFAULT_SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Now as the plugin field LAST_CHANGED expects it. Every plugin this service writes or has a
|
||||
/// model write is a change made now.
|
||||
/// </summary>
|
||||
private static string NowAsLastChanged => PluginBase.FormatLastChanged(DateTimeOffset.UtcNow);
|
||||
|
||||
private static readonly AssistantContextFile[] ASSISTANT_CONTEXT_FILES =
|
||||
[
|
||||
new("Assistant plugin schema", "Plugins/assistants/README.md", IsRequired: true),
|
||||
@@ -148,6 +154,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
var metadata = new DirectChatLauncherPluginMetadata(
|
||||
request.PluginId,
|
||||
DEFAULT_VERSION,
|
||||
DateTimeOffset.UtcNow,
|
||||
[DEFAULT_AUTHOR],
|
||||
DEFAULT_SUPPORT_CONTACT,
|
||||
DEFAULT_SOURCE_URL,
|
||||
@@ -426,6 +433,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
<fixed_metadata_defaults>
|
||||
ID = "{{request.PluginId}}"
|
||||
VERSION = "{{DEFAULT_VERSION}}"
|
||||
LAST_CHANGED = "{{NowAsLastChanged}}"
|
||||
TYPE = "ASSISTANT"
|
||||
AUTHORS = {"{{DEFAULT_AUTHOR}}"}
|
||||
SUPPORT_CONTACT = "{{DEFAULT_SUPPORT_CONTACT}}"
|
||||
@@ -620,6 +628,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
- A null value in the request JSON means that detail is not available. Never write the word "null" or a field name into the plugin.
|
||||
- Keep ID = "{{plugin.Id}}" exactly. Do not create a new plugin ID.
|
||||
- Keep TYPE = "ASSISTANT".
|
||||
- Set LAST_CHANGED = "{{NowAsLastChanged}}" right after VERSION, replacing any earlier value.
|
||||
- Keep the assistant locally managed. DEPLOYED_USING_CONFIG_SERVER must not be true.
|
||||
{{builderMetadataRule}}
|
||||
- Set assistant.kind to "CHAT_LAUNCHER" exactly when the revised ASSISTANT table uses LaunchBehavior = "OPEN_WORKSPACE_CHAT_BY_NAME" or "OPEN_TEMPORARY_CHAT"; otherwise set it to "FORM".
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
- Improved how AI Studio keeps the message you have not sent yet. When AI Studio stores your chats automatically, your message and its attachments now stay with the chat, and they are back when you open the chat again, for example, after you switched to another chat.
|
||||
- Improved how AI Studio keeps the message of a new chat. When AI Studio stores your chats automatically and you leave a new chat before you send your message, AI Studio now stores the chat with your message. You find it among your disappearing chats, or in its workspace when you started it there.
|
||||
- Improved how AI Studio indexes large local data sources. It now works through them in steps of a few minutes, so your other data sources, such as your mailboxes, stay up to date in between.
|
||||
- Improved the plugins page with the details of each plugin. The info button next to the name of a plugin shows, among other things, its version, its authors, and where to get support.
|
||||
- Improved the details of a plugin on the plugins page, which now show when the plugin was last changed, in your time zone. Plugin authors can state this time, and AI Studio fills it in by itself when it writes a plugin for you, for example, with the Assistant Builder.
|
||||
- Changed the banner on the start page to a fresh design that shows what AI Studio can do today.
|
||||
- Fixed a warning about the confidence level that stayed in the dialog for adding a local data source, even after you chose an embedding provider that meets it. The dialog now checks again whenever you change either of them, so a warning also appears when you switch to a provider that falls short.
|
||||
- Fixed two confidence levels sharing the same name in the German user interface. Moderate is called "Mäßig" again, and Medium stays "Mittel".
|
||||
|
||||
Reference in new issue
Block a user