mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 05:33:37 +00:00
Make the default drop target follow the open panel
This commit is contained in:
parent
b94d2a3a14
commit
b052810db7
@ -128,7 +128,9 @@ else
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyAnalysisRules" Validation="@this.ValidateAnalysisRules" Immediate="@true" Label="@T("Analysis rules")" HelperText="@T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load analysis rules from document")" @bind-FileContent="@this.policyAnalysisRules" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
@* No default target for the rule zones: while the policy definition is open, three
|
||||
zones are in play, so each drop has to be aimed at the one it belongs to. *@
|
||||
<ReadFileContent Text="@T("Load analysis rules from document")" @bind-FileContent="@this.policyAnalysisRules" EnableDragDrop="true" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points.")
|
||||
@ -136,7 +138,7 @@ else
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyOutputRules" Validation="@this.ValidateOutputRules" Immediate="@true" Label="@T("Output rules")" HelperText="@T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules" EnableDragDrop="true" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
@ -153,7 +155,7 @@ else
|
||||
|
||||
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6" />
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.DocumentScanner" HeaderText="@(T("Document selection - Policy") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@(this.selectedPolicy?.IsProtected ?? false)">
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.DocumentScanner" HeaderText="@(T("Document selection - Policy") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@this.documentSelectionExpanded" ExpandedChanged="@this.DocumentSelectionExpandedChanged">
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
@T("Policy Description")
|
||||
</MudText>
|
||||
@ -166,7 +168,10 @@ else
|
||||
@T("Documents for the analysis")
|
||||
</MudText>
|
||||
|
||||
<AttachDocuments Name="Document Analysis Files" @bind-DocumentPaths="@this.loadedDocumentPaths" CatchAllDocuments="true" UseSmallForm="false" Provider="@this.ProviderSettings"/>
|
||||
@* The whole assistant catches drops, but only while this panel is the open one. A
|
||||
collapsed panel keeps its content in the DOM with a height of zero, so without this
|
||||
the invisible zone would swallow the drops meant for the policy definition. *@
|
||||
<AttachDocuments Name="Document Analysis Files" @bind-DocumentPaths="@this.loadedDocumentPaths" CatchAllDocuments="@this.documentSelectionExpanded" UseSmallForm="false" Provider="@this.ProviderSettings"/>
|
||||
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
|
||||
@ -244,6 +244,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
}
|
||||
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
this.documentSelectionExpanded = !this.policyDefinitionExpanded;
|
||||
await base.OnInitializedAsync();
|
||||
this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.PLUGINS_RELOADED ]);
|
||||
this.UpdateProviders();
|
||||
@ -285,6 +286,17 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
private bool policyIsProtected;
|
||||
private bool policyHidePolicyDefinition;
|
||||
private bool policyDefinitionExpanded;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the document selection panel is the open one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only one of the two panels is ever open, so this is normally the opposite of the field above
|
||||
/// -- but not always: the user can collapse both. It is tracked rather than derived because it
|
||||
/// decides whether the document zone is the default target of the whole assistant, and a
|
||||
/// collapsed zone must not hold that role.
|
||||
/// </remarks>
|
||||
private bool documentSelectionExpanded;
|
||||
private string policyName = string.Empty;
|
||||
private string policyDescription = string.Empty;
|
||||
private string policyAnalysisRules = string.Empty;
|
||||
@ -333,7 +345,11 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
state.Restore(SELECTED_POLICY_STATE_KEY, value => this.selectedPolicy = value);
|
||||
state.Restore(POLICY_IS_PROTECTED_STATE_KEY, value => this.policyIsProtected = value);
|
||||
state.Restore(POLICY_HIDE_POLICY_DEFINITION_STATE_KEY, value => this.policyHidePolicyDefinition = value);
|
||||
state.Restore(POLICY_DEFINITION_EXPANDED_STATE_KEY, value => this.policyDefinitionExpanded = value);
|
||||
state.Restore(POLICY_DEFINITION_EXPANDED_STATE_KEY, value =>
|
||||
{
|
||||
this.policyDefinitionExpanded = value;
|
||||
this.documentSelectionExpanded = !value;
|
||||
});
|
||||
state.Restore(POLICY_NAME_STATE_KEY, value => this.policyName = value);
|
||||
state.Restore(POLICY_DESCRIPTION_STATE_KEY, value => this.policyDescription = value);
|
||||
state.Restore(POLICY_ANALYSIS_RULES_STATE_KEY, value => this.policyAnalysisRules = value);
|
||||
@ -359,6 +375,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
this.selectedPolicy = policy;
|
||||
this.ResetForm();
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
this.documentSelectionExpanded = !this.policyDefinitionExpanded;
|
||||
this.ApplyPolicyPreselection(preferPolicyPreselection: true);
|
||||
|
||||
this.Form?.ResetValidation();
|
||||
@ -368,6 +385,22 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
private Task PolicyDefinitionExpandedChanged(bool isExpanded)
|
||||
{
|
||||
this.policyDefinitionExpanded = isExpanded;
|
||||
|
||||
// The panels do not allow multi expansion, so opening this one closes the other:
|
||||
if (isExpanded)
|
||||
this.documentSelectionExpanded = false;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task DocumentSelectionExpandedChanged(bool isExpanded)
|
||||
{
|
||||
this.documentSelectionExpanded = isExpanded;
|
||||
|
||||
// The panels do not allow multi expansion, so opening this one closes the other:
|
||||
if (isExpanded)
|
||||
this.policyDefinitionExpanded = false;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -457,6 +490,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
this.policyIsProtected = state;
|
||||
this.selectedPolicy.IsProtected = state;
|
||||
this.policyDefinitionExpanded = !state;
|
||||
this.documentSelectionExpanded = state;
|
||||
await this.AutoSave(true);
|
||||
}
|
||||
|
||||
@ -634,6 +668,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
|
||||
// Update the expansion state based on the policy protection:
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
this.documentSelectionExpanded = !this.policyDefinitionExpanded;
|
||||
|
||||
// Update available providers:
|
||||
this.UpdateProviders();
|
||||
|
||||
@ -108,6 +108,7 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
private readonly string dropZoneId = $"attach-documents-{Guid.NewGuid():N}";
|
||||
|
||||
private bool isDefaultZone;
|
||||
private bool hasReportedDefaultZoneProblem;
|
||||
private bool isDraggingOver;
|
||||
private bool isFileDialogOpen;
|
||||
private MediaImportOwner EffectiveImportOwner => this.OwnerChat is not null
|
||||
@ -124,7 +125,6 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
{
|
||||
this.MediaTranscriptionService.StateChanged += this.OnMediaImportStateChanged;
|
||||
this.ApplyFilters([], [ Event.HIGHLIGHT_DROP_ZONE, Event.PATHS_DROPPED ]);
|
||||
this.ClaimDefaultZoneRole();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
@ -132,6 +132,7 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
/// <summary>Rehydrates results after the component is assigned another chat or target.</summary>
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
this.UpdateDefaultZoneRole();
|
||||
await base.OnParametersSetAsync();
|
||||
await this.SyncCompletedMediaAttachmentsAsync();
|
||||
}
|
||||
@ -262,11 +263,35 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Asks the area for the role of its default target, if this component wants it.
|
||||
/// Keeps the role of the default target in step with the CatchAllDocuments parameter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The flag is a parameter, so it can change while this component lives. A zone inside a
|
||||
/// collapsed panel is the case this exists for: MudBlazor leaves the content of a collapsed
|
||||
/// panel in the DOM with a height of zero, so the zone stays alive and cannot be aimed at --
|
||||
/// yet it would keep the role and swallow every drop meant for the visible part of the page.
|
||||
/// </remarks>
|
||||
private void UpdateDefaultZoneRole()
|
||||
{
|
||||
if (this.CatchAllDocuments)
|
||||
{
|
||||
this.ClaimDefaultZoneRole();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isDefaultZone)
|
||||
return;
|
||||
|
||||
this.Scope?.ReleaseDefaultZone(this);
|
||||
this.isDefaultZone = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the area for the role of its default target.
|
||||
/// </summary>
|
||||
private void ClaimDefaultZoneRole()
|
||||
{
|
||||
if (!this.CatchAllDocuments)
|
||||
if (this.isDefaultZone)
|
||||
return;
|
||||
|
||||
if (this.Scope is null)
|
||||
@ -275,15 +300,27 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
// There is nothing to claim: the surrounding page, assistant, or dialog is not a drop
|
||||
// area at all. The flag would then do nothing, and silently -- which is how a zone ends
|
||||
// up promising a behaviour it cannot deliver. So say it out loud: either the area needs
|
||||
// a DropZoneScope, or the flag does not belong here.
|
||||
// a DropZoneScope, or the flag does not belong here. Reported once only, because the
|
||||
// claim is retried on every parameter change.
|
||||
//
|
||||
this.Logger.LogWarning("The attachment zone '{Name}' wants to be the default target of its area, but it does not live in a drop zone scope. Dropping next to this zone will do nothing.", this.Name);
|
||||
if (!this.hasReportedDefaultZoneProblem)
|
||||
{
|
||||
this.hasReportedDefaultZoneProblem = true;
|
||||
this.Logger.LogWarning("The attachment zone '{Name}' wants to be the default target of its area, but it does not live in a drop zone scope. Dropping next to this zone will do nothing.", this.Name);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDefaultZone = this.Scope.TryBecomeDefaultZone(this);
|
||||
if (!this.isDefaultZone)
|
||||
this.Logger.LogDebug("The attachment zone '{Name}' asked to be the default target of its area, which another zone already is. It now takes only the drops aimed at itself.", this.Name);
|
||||
|
||||
// Losing the role to a neighbour is a decision, not a defect -- and it can be undone later,
|
||||
// when that neighbour goes away. So this one only goes to the debug log, and only once:
|
||||
if (this.isDefaultZone || this.hasReportedDefaultZoneProblem)
|
||||
return;
|
||||
|
||||
this.hasReportedDefaultZoneProblem = true;
|
||||
this.Logger.LogDebug("The attachment zone '{Name}' asked to be the default target of its area, which another zone already is. It now takes only the drops aimed at itself.", this.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -64,17 +64,22 @@ public partial class PathDropZone : MSGComponentBase
|
||||
private string dragClass = DEFAULT_DRAG_CLASS;
|
||||
private bool isDefaultZone;
|
||||
private bool isHighlighted;
|
||||
private bool hasReportedDefaultZoneProblem;
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], [ Event.HIGHLIGHT_DROP_ZONE, Event.PATHS_DROPPED ]);
|
||||
this.ClaimDefaultZoneRole();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
this.UpdateDefaultZoneRole();
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hands the role of the default target back to the area.
|
||||
/// </summary>
|
||||
@ -116,11 +121,35 @@ public partial class PathDropZone : MSGComponentBase
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Asks the area for the role of its default target, if this zone wants it.
|
||||
/// Keeps the role of the default target in step with the CatchAllDocuments parameter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The flag is a parameter, so it can change while this zone lives. A zone inside a collapsed
|
||||
/// panel is the case this exists for: MudBlazor leaves the content of a collapsed panel in the
|
||||
/// DOM with a height of zero, so the zone stays alive and cannot be aimed at -- yet it would
|
||||
/// keep the role and swallow every drop meant for the part of the page one can actually see.
|
||||
/// </remarks>
|
||||
private void UpdateDefaultZoneRole()
|
||||
{
|
||||
if (this.CatchAllDocuments)
|
||||
{
|
||||
this.ClaimDefaultZoneRole();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isDefaultZone)
|
||||
return;
|
||||
|
||||
this.Scope?.ReleaseDefaultZone(this);
|
||||
this.isDefaultZone = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the area for the role of its default target.
|
||||
/// </summary>
|
||||
private void ClaimDefaultZoneRole()
|
||||
{
|
||||
if (!this.CatchAllDocuments)
|
||||
if (this.isDefaultZone)
|
||||
return;
|
||||
|
||||
if (this.Scope is null)
|
||||
@ -131,13 +160,28 @@ public partial class PathDropZone : MSGComponentBase
|
||||
// up promising a behaviour it cannot deliver. So say it out loud: either the area needs
|
||||
// a DropZoneScope, or the flag does not belong here.
|
||||
//
|
||||
this.Logger.LogWarning("The path drop zone '{ZoneId}' wants to be the default target of its area, but it does not live in a drop zone scope. Dropping next to this zone will do nothing.", this.dropZoneId);
|
||||
//
|
||||
// Reported once only: the claim is retried on every parameter change, and repeating
|
||||
// the message on every render would bury the log.
|
||||
//
|
||||
if (!this.hasReportedDefaultZoneProblem)
|
||||
{
|
||||
this.hasReportedDefaultZoneProblem = true;
|
||||
this.Logger.LogWarning("The path drop zone '{ZoneId}' wants to be the default target of its area, but it does not live in a drop zone scope. Dropping next to this zone will do nothing.", this.dropZoneId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDefaultZone = this.Scope.TryBecomeDefaultZone(this);
|
||||
if (!this.isDefaultZone)
|
||||
this.Logger.LogDebug("The path drop zone '{ZoneId}' asked to be the default target of its area, which another zone already is. It now takes only the drops aimed at itself.", this.dropZoneId);
|
||||
|
||||
// Losing the role to a neighbour is a decision, not a defect -- and it can be undone later,
|
||||
// when that neighbour goes away. So this one only goes to the debug log, and only once:
|
||||
if (this.isDefaultZone || this.hasReportedDefaultZoneProblem)
|
||||
return;
|
||||
|
||||
this.hasReportedDefaultZoneProblem = true;
|
||||
this.Logger.LogDebug("The path drop zone '{ZoneId}' asked to be the default target of its area, which another zone already is. It now takes only the drops aimed at itself.", this.dropZoneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -93,6 +93,7 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
private string dragClass = DEFAULT_DRAG_CLASS;
|
||||
private bool isDefaultZone;
|
||||
private bool isHighlighted;
|
||||
private bool hasReportedDefaultZoneProblem;
|
||||
private bool isFileDialogOpen;
|
||||
private bool hasLoadedFileContent;
|
||||
private string loadedFileName = string.Empty;
|
||||
@ -120,6 +121,7 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
this.loadedFileName = string.Empty;
|
||||
}
|
||||
|
||||
this.UpdateDefaultZoneRole();
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
@ -127,10 +129,7 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
{
|
||||
this.MediaTranscriptionService.StateChanged += this.OnMediaImportStateChanged;
|
||||
if (this.EnableDragDrop)
|
||||
{
|
||||
this.ApplyFilters([], [ Event.HIGHLIGHT_DROP_ZONE, Event.PATHS_DROPPED ]);
|
||||
this.ClaimDefaultZoneRole();
|
||||
}
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
await this.SyncCompletedMediaTextAsync();
|
||||
@ -240,11 +239,35 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Asks the area for the role of its default target, if this component wants it.
|
||||
/// Keeps the role of the default target in step with the CatchAllDocuments parameter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The flag is a parameter, so it can change while this component lives. A zone inside a
|
||||
/// collapsed panel is the case this exists for: MudBlazor leaves the content of a collapsed
|
||||
/// panel in the DOM with a height of zero, so the zone stays alive and cannot be aimed at --
|
||||
/// yet it would keep the role and swallow every drop meant for the visible part of the page.
|
||||
/// </remarks>
|
||||
private void UpdateDefaultZoneRole()
|
||||
{
|
||||
if (this.EnableDragDrop && this.CatchAllDocuments)
|
||||
{
|
||||
this.ClaimDefaultZoneRole();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isDefaultZone)
|
||||
return;
|
||||
|
||||
this.Scope?.ReleaseDefaultZone(this);
|
||||
this.isDefaultZone = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the area for the role of its default target.
|
||||
/// </summary>
|
||||
private void ClaimDefaultZoneRole()
|
||||
{
|
||||
if (!this.CatchAllDocuments)
|
||||
if (this.isDefaultZone)
|
||||
return;
|
||||
|
||||
if (this.Scope is null)
|
||||
@ -253,15 +276,27 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
// There is nothing to claim: the surrounding page, assistant, or dialog is not a drop
|
||||
// area at all. The flag would then do nothing, and silently -- which is how a zone ends
|
||||
// up promising a behaviour it cannot deliver. So say it out loud: either the area needs
|
||||
// a DropZoneScope, or the flag does not belong here.
|
||||
// a DropZoneScope, or the flag does not belong here. Reported once only, because the
|
||||
// claim is retried on every parameter change.
|
||||
//
|
||||
this.Logger.LogWarning("The file zone '{ZoneId}' wants to be the default target of its area, but it does not live in a drop zone scope. Dropping next to this zone will do nothing.", this.dropZoneId);
|
||||
if (!this.hasReportedDefaultZoneProblem)
|
||||
{
|
||||
this.hasReportedDefaultZoneProblem = true;
|
||||
this.Logger.LogWarning("The file zone '{ZoneId}' wants to be the default target of its area, but it does not live in a drop zone scope. Dropping next to this zone will do nothing.", this.dropZoneId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDefaultZone = this.Scope.TryBecomeDefaultZone(this);
|
||||
if (!this.isDefaultZone)
|
||||
this.Logger.LogDebug("The file zone '{ZoneId}' asked to be the default target of its area, which another zone already is. It now takes only the drops aimed at itself.", this.dropZoneId);
|
||||
|
||||
// Losing the role to a neighbour is a decision, not a defect -- and it can be undone later,
|
||||
// when that neighbour goes away. So this one only goes to the debug log, and only once:
|
||||
if (this.isDefaultZone || this.hasReportedDefaultZoneProblem)
|
||||
return;
|
||||
|
||||
this.hasReportedDefaultZoneProblem = true;
|
||||
this.Logger.LogDebug("The file zone '{ZoneId}' asked to be the default target of its area, which another zone already is. It now takes only the drops aimed at itself.", this.dropZoneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user