mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-02 02:39:18 +00:00
Fixed issues when multiple configurations intro texts or mandatory infos share the same ID (#932)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
parent
7d9a4f5ab1
commit
afdcc0455d
@ -385,18 +385,89 @@ public static partial class PluginFactory
|
|||||||
|
|
||||||
public static IReadOnlyList<DataMandatoryInfo> GetMandatoryInfos()
|
public static IReadOnlyList<DataMandatoryInfo> GetMandatoryInfos()
|
||||||
{
|
{
|
||||||
return RUNNING_PLUGINS
|
return ResolveLivePluginContent<DataMandatoryInfo>("mandatory info", plugin => plugin.MandatoryInfos).ToList();
|
||||||
.OfType<PluginConfiguration>()
|
|
||||||
.SelectMany(plugin => plugin.MandatoryInfos)
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<DataIntroduction> GetIntroductions()
|
public static IReadOnlyList<DataIntroduction> GetIntroductions()
|
||||||
{
|
{
|
||||||
return RUNNING_PLUGINS
|
return ResolveLivePluginContent<DataIntroduction>("introduction", plugin => plugin.Introductions)
|
||||||
.OfType<PluginConfiguration>()
|
|
||||||
.SelectMany(plugin => plugin.Introductions)
|
|
||||||
.OrderBy(introduction => introduction.Index)
|
.OrderBy(introduction => introduction.Index)
|
||||||
|
.ThenBy(introduction => introduction.Id, StringComparer.Ordinal)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collects live content from all running configuration plugins, so that each content ID appears exactly once.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The IDs of live content are chosen by whoever writes the configuration, so two configuration
|
||||||
|
/// plugins may use the same ID. We resolve such a collision the same way a collision on a setting
|
||||||
|
/// is resolved: a configuration which acts on behalf of the organization wins, so nobody can push
|
||||||
|
/// aside what an organization deployed. Among configurations of the same origin, the declared
|
||||||
|
/// priority decides, and when even that is equal, the plugin which started later wins.<br/><br/>
|
||||||
|
/// Duplicates are not merely a cosmetic problem: the home page keys its panels by the introduction
|
||||||
|
/// ID, and the acceptance of a mandatory info is stored per ID as well.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="contentKind">The kind of content, used to report a collision in the log.</param>
|
||||||
|
/// <param name="selector">Selects the content of one configuration plugin.</param>
|
||||||
|
/// <typeparam name="T">The type of the live plugin content.</typeparam>
|
||||||
|
/// <returns>The content of all configuration plugins, with every ID resolved to one winner.</returns>
|
||||||
|
private static IEnumerable<T> ResolveLivePluginContent<T>(string contentKind, Func<PluginConfiguration, IEnumerable<T>> selector) where T : ILivePluginContent
|
||||||
|
{
|
||||||
|
var contentById = new Dictionary<string, (T Content, int Authority, int Priority)>(StringComparer.Ordinal);
|
||||||
|
foreach (var plugin in RUNNING_PLUGINS.OfType<PluginConfiguration>())
|
||||||
|
{
|
||||||
|
var authority = GetConfigurationAuthority(plugin.PluginPath);
|
||||||
|
foreach (var content in selector(plugin))
|
||||||
|
{
|
||||||
|
if (contentById.TryGetValue(content.Id, out var currentWinner))
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// The candidate needs the higher authority to take over. Within the same
|
||||||
|
// authority, the higher priority wins, and an equal priority falls back to the
|
||||||
|
// start order, where the plugin processed later wins:
|
||||||
|
//
|
||||||
|
var isTakingOver = authority > currentWinner.Authority || (authority == currentWinner.Authority && plugin.Priority >= currentWinner.Priority);
|
||||||
|
var winnerPluginId = isTakingOver ? content.EnterpriseConfigurationPluginId : currentWinner.Content.EnterpriseConfigurationPluginId;
|
||||||
|
var ignoredPluginId = isTakingOver ? currentWinner.Content.EnterpriseConfigurationPluginId : content.EnterpriseConfigurationPluginId;
|
||||||
|
|
||||||
|
if (winnerPluginId == ignoredPluginId)
|
||||||
|
LOG.LogWarning($"The configuration plugin '{winnerPluginId}' defines the {contentKind} ID '{content.Id}' more than once. Using its last definition and ignoring the earlier one. Please use each ID only once.");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var reason = isTakingOver
|
||||||
|
? DescribeConfigurationPrecedence(authority, plugin.Priority, currentWinner.Authority, currentWinner.Priority)
|
||||||
|
: DescribeConfigurationPrecedence(currentWinner.Authority, currentWinner.Priority, authority, plugin.Priority);
|
||||||
|
|
||||||
|
LOG.LogWarning($"Multiple configuration plugins define the {contentKind} ID '{content.Id}'. Using the one from the configuration plugin '{winnerPluginId}' and ignoring the one from the configuration plugin '{ignoredPluginId}', because {reason}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTakingOver)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentById[content.Id] = (content, authority, plugin.Priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentById.Values.Select(entry => entry.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explains in one phrase why one configuration plugin won a collision against another.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Administrators read this in the log while they are testing their configuration. Naming the
|
||||||
|
/// deciding rule saves them from guessing why their change had no effect.
|
||||||
|
/// </remarks>
|
||||||
|
private static string DescribeConfigurationPrecedence(int winnerAuthority, int winnerPriority, int ignoredAuthority, int ignoredPriority)
|
||||||
|
{
|
||||||
|
if (winnerAuthority != ignoredAuthority)
|
||||||
|
return "a configuration which acts on behalf of your organization takes precedence over a locally placed one";
|
||||||
|
|
||||||
|
if (winnerPriority != ignoredPriority)
|
||||||
|
return $"it declares the higher priority ({winnerPriority} instead of {ignoredPriority})";
|
||||||
|
|
||||||
|
return $"both declare the same priority ({winnerPriority}), so the configuration plugin which started later wins";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -5,3 +5,4 @@
|
|||||||
- Fixed AI Studio reading a document to the end even after you closed its preview. Closing the dialog now stops that work immediately.
|
- Fixed AI Studio reading a document to the end even after you closed its preview. Closing the dialog now stops that work immediately.
|
||||||
- Fixed AI Studio holding on to finished chats, presentation images, and plugin data. It releases them now, so memory no longer grows the longer you keep the app running.
|
- Fixed AI Studio holding on to finished chats, presentation images, and plugin data. It releases them now, so memory no longer grows the longer you keep the app running.
|
||||||
- Fixed assistants handing an outdated result to the chat. When you sent several results without opening the chat in between, you now receive the one you sent last.
|
- Fixed assistants handing an outdated result to the chat. When you sent several results without opening the chat in between, you now receive the one you sent last.
|
||||||
|
- Fixed rare issues when multiple configurations provided introduction texts or mandatory information under the same ID.
|
||||||
Loading…
Reference in New Issue
Block a user