diff --git a/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor b/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor
index a7b9e5fa..55142397 100644
--- a/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor
+++ b/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor
@@ -60,7 +60,11 @@
@T("Clear")
+
+ @T("Copy visible lines")
+
+
diff --git a/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor.cs b/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor.cs
index 23c7ce09..29444840 100644
--- a/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor.cs
+++ b/app/MindWork AI Studio/Assistants/LogViewer/AssistantLogViewer.razor.cs
@@ -60,6 +60,7 @@ public partial class AssistantLogViewer : MSGComponentBase
private bool isLoading;
private bool autoRefresh;
private bool filterOnly = true;
+ private bool invertFilters;
private bool showTimestamps = true;
private int maxLines = DEFAULT_MAX_LINES;
private int totalLineCount;
@@ -72,7 +73,11 @@ public partial class AssistantLogViewer : MSGComponentBase
private bool HasDropdownFilter => this.selectedLogLevels.Count > 0 || this.selectedLoggers.Count > 0 || this.selectedSourceDetails.Count > 0;
- private bool HasActiveFilter => !string.IsNullOrWhiteSpace(this.filterText) || this.HasDropdownFilter;
+ private bool HasActiveFilter => !string.IsNullOrWhiteSpace(this.filterText) || this.HasDropdownFilter || this.invertFilters;
+
+ private bool HasActiveVisibilityFilter => this.HasDropdownFilter || (this.filterOnly && this.activeSearchTerms.Length > 0);
+
+ private bool CanCopyDisplayedLines => this.displayLines.Count > 0;
private string FilterText
{
@@ -100,6 +105,19 @@ public partial class AssistantLogViewer : MSGComponentBase
}
}
+ private bool InvertFilters
+ {
+ get => this.invertFilters;
+ set
+ {
+ if (this.invertFilters == value)
+ return;
+
+ this.invertFilters = value;
+ this.RefreshDisplayLines();
+ }
+ }
+
private bool ShowTimestamps
{
get => this.showTimestamps;
@@ -208,6 +226,12 @@ public partial class AssistantLogViewer : MSGComponentBase
await this.RefreshLogAsync();
}
+ private async Task CopyDisplayedLines()
+ {
+ var text = string.Join(Environment.NewLine, this.displayLines.Select(this.GetPlainRenderedLine));
+ await this.RustService.CopyText2Clipboard(this.Snackbar, text);
+ }
+
private async Task OpenCurrentLogInFileManager()
{
try
@@ -276,6 +300,7 @@ public partial class AssistantLogViewer : MSGComponentBase
this.selectedLogLevels.Clear();
this.selectedLoggers.Clear();
this.selectedSourceDetails.Clear();
+ this.invertFilters = false;
this.RefreshDisplayLines();
}
@@ -386,6 +411,14 @@ public partial class AssistantLogViewer : MSGComponentBase
}
private bool LineMatchesFilters(LogLine line)
+ {
+ if (this.invertFilters && this.HasActiveVisibilityFilter)
+ return !this.LineMatchesAnyActiveFilter(line);
+
+ return this.LineMatchesActiveFilters(line);
+ }
+
+ private bool LineMatchesActiveFilters(LogLine line)
{
if (!MatchesSelection(line.Segments.Level, this.selectedLogLevels))
return false;
@@ -402,6 +435,20 @@ public partial class AssistantLogViewer : MSGComponentBase
return MatchesSearchTerms(this.GetPlainRenderedLine(line), this.activeSearchTerms);
}
+ private bool LineMatchesAnyActiveFilter(LogLine line)
+ {
+ if (ActiveSelectionMatches(line.Segments.Level, this.selectedLogLevels))
+ return true;
+
+ if (ActiveSelectionMatches(line.Segments.Logger, this.selectedLoggers))
+ return true;
+
+ if (ActiveSelectionMatches(line.Segments.SourceDetails, this.selectedSourceDetails))
+ return true;
+
+ return this.filterOnly && this.activeSearchTerms.Length > 0 && MatchesSearchTerms(this.GetPlainRenderedLine(line), this.activeSearchTerms);
+ }
+
private string RenderLine(LogLine line)
{
var text = this.GetPlainRenderedLine(line);
@@ -687,9 +734,14 @@ public partial class AssistantLogViewer : MSGComponentBase
}
private static bool MatchesSelection(string? value, HashSet selectedValues)
+ {
+ return selectedValues.Count == 0 || ActiveSelectionMatches(value, selectedValues);
+ }
+
+ private static bool ActiveSelectionMatches(string? value, HashSet selectedValues)
{
if (selectedValues.Count == 0)
- return true;
+ return false;
var normalizedValue = string.IsNullOrWhiteSpace(value) ? OTHER_OPTION_VALUE : value;
return selectedValues.Contains(normalizedValue);