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);