mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 19:32:10 +00:00
added an assistant to view logs
This commit is contained in:
parent
5dc6282908
commit
11dd357a05
@ -1630,6 +1630,84 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4254597
|
||||
-- Ask your questions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Ask your questions"
|
||||
|
||||
-- Find
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1042076026"] = "Find"
|
||||
|
||||
-- The log file could not be read: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1147062477"] = "The log file could not be read: {0}"
|
||||
|
||||
-- Refresh
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T135637716"] = "Refresh"
|
||||
|
||||
-- Log type
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1649755707"] = "Log type"
|
||||
|
||||
-- Showing {0} of {1} loaded lines. {2} older lines were skipped. Last refresh: {3}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1747827400"] = "Showing {0} of {1} loaded lines. {2} older lines were skipped. Last refresh: {3}."
|
||||
|
||||
-- The log file does not exist: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "The log file does not exist: {0}"
|
||||
|
||||
-- Other
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Other"
|
||||
|
||||
-- Max lines
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1904230854"] = "Max lines"
|
||||
|
||||
-- All
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1974461284"] = "All"
|
||||
|
||||
-- Startup log
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2288538420"] = "Startup log"
|
||||
|
||||
-- Showing {0} of {1} lines. Last refresh: {2}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2378353570"] = "Showing {0} of {1} lines. Last refresh: {2}."
|
||||
|
||||
-- No matching log lines.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "No matching log lines."
|
||||
|
||||
-- Source details
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Source details"
|
||||
|
||||
-- Loaded {0} lines. Last refresh: {1}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2920304709"] = "Loaded {0} lines. Last refresh: {1}."
|
||||
|
||||
-- Filter only
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3152625639"] = "Filter only"
|
||||
|
||||
-- Loading log file...
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Loading log file..."
|
||||
|
||||
-- The log file path is not available yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "The log file path is not available yet."
|
||||
|
||||
-- Logger
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T376222229"] = "Logger"
|
||||
|
||||
-- Auto-refresh
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3837203600"] = "Auto-refresh"
|
||||
|
||||
-- not loaded yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3863250749"] = "not loaded yet"
|
||||
|
||||
-- Loading...
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T397479987"] = "Loading..."
|
||||
|
||||
-- Usage log
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Usage log"
|
||||
|
||||
-- Log file
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Log file"
|
||||
|
||||
-- Log Viewer
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Log Viewer"
|
||||
|
||||
-- Show timestamps
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Show timestamps"
|
||||
|
||||
-- Clear
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T77955010"] = "Clear"
|
||||
|
||||
-- You can enter text, attach one or more documents, or use both. At least one input is required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1442535450"] = "You can enter text, attach one or more documents, or use both. At least one input is required."
|
||||
|
||||
@ -6502,6 +6580,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Job Posting"
|
||||
-- Ask a question about a legal document.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Ask a question about a legal document."
|
||||
|
||||
-- Log Viewer
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4130241777"] = "Log Viewer"
|
||||
|
||||
-- ERI Server
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4204533420"] = "ERI Server"
|
||||
|
||||
@ -6523,6 +6604,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T782102948"] = "Bias of the Day"
|
||||
-- Learn about one cognitive bias every day.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Learn about one cognitive bias every day."
|
||||
|
||||
-- View and filter AI Studio log files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T892147062"] = "View and filter AI Studio log files."
|
||||
|
||||
-- Localization
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization"
|
||||
|
||||
@ -7723,6 +7807,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T4262148639"] = "Rewrite
|
||||
-- Localization Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T446674624"] = "Localization Assistant"
|
||||
|
||||
-- Log Viewer Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T555062689"] = "Log Viewer Assistant"
|
||||
|
||||
-- New Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T826248509"] = "New Chat"
|
||||
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
@attribute [Route(Routes.ASSISTANT_LOG_VIEWER)]
|
||||
@using AIStudio.Tools.Services
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<div class="inner-scrolling-context">
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
@T("Log Viewer")
|
||||
</MudText>
|
||||
|
||||
<InnerScrolling FillEntireHorizontalSpace="@true" Class="log-viewer-shell">
|
||||
<HeaderContent>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-2">
|
||||
<MudSelect T="LogFileKind" Value="@this.selectedLogFile" ValueChanged="@this.SelectedLogFileChanged" Label="@T("Log file")" Variant="Variant.Outlined" Margin="Margin.Dense" Class="log-viewer-select">
|
||||
<MudSelectItem T="LogFileKind" Value="@LogFileKind.APP">@T("Usage log")</MudSelectItem>
|
||||
<MudSelectItem T="LogFileKind" Value="@LogFileKind.STARTUP">@T("Startup log")</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudCheckBox T="bool" Value="@this.autoRefresh" ValueChanged="@this.AutoRefreshChanged" Label="@T("Auto-refresh")" Color="Color.Primary" />
|
||||
@if (!this.autoRefresh)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.RefreshLogAsync" Disabled="@this.isLoading">
|
||||
@T("Refresh")
|
||||
</MudButton>
|
||||
}
|
||||
<MudNumericField T="int" Value="@this.maxLines" ValueChanged="@this.MaxLinesChanged" Label="@T("Max lines")" Variant="Variant.Outlined" Margin="Margin.Dense" Min="@MIN_MAX_LINES" Max="@MAX_MAX_LINES" Step="500" Class="log-viewer-number" />
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-1">
|
||||
<MudSelect T="string" Label="@T("Log type")" MultiSelection="@true" SelectedValues="@this.selectedLogLevels" SelectedValuesChanged="@this.SelectedLogLevelsChanged" MultiSelectionTextFunc="@this.GetMultiSelectionText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="log-viewer-multiselect" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Label">
|
||||
@foreach (var option in this.logLevelOptions)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@option">
|
||||
@this.GetFilterOptionDisplay(option)
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string" Label="@T("Logger")" MultiSelection="@true" SelectedValues="@this.selectedLoggers" SelectedValuesChanged="@this.SelectedLoggersChanged" MultiSelectionTextFunc="@this.GetMultiSelectionText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="log-viewer-multiselect" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Source">
|
||||
@foreach (var option in this.loggerOptions)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@option">
|
||||
@this.GetFilterOptionDisplay(option)
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string" Label="@T("Source details")" MultiSelection="@true" SelectedValues="@this.selectedSourceDetails" SelectedValuesChanged="@this.SelectedSourceDetailsChanged" MultiSelectionTextFunc="@this.GetMultiSelectionText" Variant="Variant.Outlined" Margin="Margin.Dense" Class="log-viewer-multiselect" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings">
|
||||
@foreach (var option in this.sourceDetailOptions)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@option">
|
||||
@this.GetFilterOptionDisplay(option)
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudCheckBox T="bool" @bind-Value="@this.ShowTimestamps" Label="@T("Show timestamps")" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-1">
|
||||
<MudTextField T="string" @bind-Text="@this.FilterText" Immediate="@true" Label="@T("Find")" Variant="Variant.Outlined" Margin="Margin.Dense" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" Class="log-viewer-filter" />
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" StartIcon="@Icons.Material.Filled.Clear" Disabled="@(!this.HasActiveFilter)" OnClick="@this.ClearFilters">
|
||||
@T("Clear")
|
||||
</MudButton>
|
||||
<MudCheckBox T="bool" @bind-Value="@this.FilterOnly" Label="@T("Filter only")" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.body2" Class="log-viewer-path mb-1">
|
||||
@this.CurrentLogPath
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mb-2">
|
||||
@this.StatusText
|
||||
</MudText>
|
||||
</HeaderContent>
|
||||
|
||||
<ChildContent>
|
||||
@if (!string.IsNullOrWhiteSpace(this.loadError))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="@true" Variant="Variant.Outlined" Class="mb-2">
|
||||
@this.loadError
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<div class="log-viewer-pane">
|
||||
@if (this.isLoading && this.loadedLines.Count == 0)
|
||||
{
|
||||
<div class="log-viewer-empty">
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="@true" />
|
||||
<MudText Typo="Typo.body2">@T("Loading log file...")</MudText>
|
||||
</div>
|
||||
}
|
||||
else if (this.displayLines.Count == 0)
|
||||
{
|
||||
<div class="log-viewer-empty">
|
||||
<MudIcon Icon="@Icons.Material.Filled.SearchOff" />
|
||||
<MudText Typo="Typo.body2">@T("No matching log lines.")</MudText>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="log-viewer-lines" role="textbox" aria-readonly="true">
|
||||
@foreach (var line in this.displayLines)
|
||||
{
|
||||
<div class="@this.GetLineClass(line)">
|
||||
<span class="log-viewer-line-number">@line.Number</span>
|
||||
<span class="log-viewer-line-text">@((MarkupString)this.RenderLine(line))</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ChildContent>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
@ -0,0 +1,721 @@
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Assistants.LogViewer;
|
||||
|
||||
public partial class AssistantLogViewer : MSGComponentBase
|
||||
{
|
||||
private static readonly TimeSpan AUTO_REFRESH_INTERVAL = TimeSpan.FromSeconds(5);
|
||||
private static readonly char[] WORD_SPLIT_CHARS = [' ', '\t', '\r', '\n'];
|
||||
private static readonly Dictionary<string, int> LOG_LEVEL_ORDER = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["ERROR"] = 0,
|
||||
["CRITICAL"] = 1,
|
||||
["WARN"] = 2,
|
||||
["WARNING"] = 3,
|
||||
["INFO"] = 4,
|
||||
["INFORMATION"] = 5,
|
||||
["DEBUG"] = 6,
|
||||
["TRACE"] = 7,
|
||||
};
|
||||
|
||||
private const int DEFAULT_MAX_LINES = 5000;
|
||||
protected const int MIN_MAX_LINES = 100;
|
||||
protected const int MAX_MAX_LINES = 100000;
|
||||
private const string OTHER_OPTION_VALUE = "__OTHER__";
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private NavigationManager NavigationManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<AssistantLogViewer> Logger { get; init; } = null!;
|
||||
|
||||
private GetLogPathsResponse logPaths;
|
||||
private LogFileKind selectedLogFile = LogFileKind.APP;
|
||||
private List<LogLine> loadedLines = [];
|
||||
private List<LogLine> displayLines = [];
|
||||
private List<string> logLevelOptions = [OTHER_OPTION_VALUE];
|
||||
private List<string> loggerOptions = [OTHER_OPTION_VALUE];
|
||||
private List<string> sourceDetailOptions = [OTHER_OPTION_VALUE];
|
||||
private HashSet<string> selectedLogLevels = new(StringComparer.OrdinalIgnoreCase);
|
||||
private HashSet<string> selectedLoggers = new(StringComparer.OrdinalIgnoreCase);
|
||||
private HashSet<string> selectedSourceDetails = new(StringComparer.OrdinalIgnoreCase);
|
||||
private string[] activeSearchTerms = [];
|
||||
private CancellationTokenSource? autoRefreshCancellationTokenSource;
|
||||
private string filterText = string.Empty;
|
||||
private string loadError = string.Empty;
|
||||
private bool isLoading;
|
||||
private bool autoRefresh;
|
||||
private bool filterOnly = true;
|
||||
private bool showTimestamps = true;
|
||||
private int maxLines = DEFAULT_MAX_LINES;
|
||||
private int totalLineCount;
|
||||
private int skippedLineCount;
|
||||
private DateTimeOffset? lastLoadedAt;
|
||||
|
||||
private string CurrentLogPath => this.selectedLogFile is LogFileKind.APP ? this.logPaths.LogAppPath : this.logPaths.LogStartupPath;
|
||||
|
||||
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 string FilterText
|
||||
{
|
||||
get => this.filterText;
|
||||
set
|
||||
{
|
||||
if (this.filterText == value)
|
||||
return;
|
||||
|
||||
this.filterText = value;
|
||||
this.RefreshDisplayLines();
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterOnly
|
||||
{
|
||||
get => this.filterOnly;
|
||||
set
|
||||
{
|
||||
if (this.filterOnly == value)
|
||||
return;
|
||||
|
||||
this.filterOnly = value;
|
||||
this.RefreshDisplayLines();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShowTimestamps
|
||||
{
|
||||
get => this.showTimestamps;
|
||||
set
|
||||
{
|
||||
if (this.showTimestamps == value)
|
||||
return;
|
||||
|
||||
this.showTimestamps = value;
|
||||
this.RefreshDisplayLines();
|
||||
}
|
||||
}
|
||||
|
||||
private string StatusText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.isLoading)
|
||||
return T("Loading...");
|
||||
|
||||
var visibleLineCount = this.displayLines.Count.ToString("N0", CultureInfo.CurrentCulture);
|
||||
var loadedLineCount = this.loadedLines.Count.ToString("N0", CultureInfo.CurrentCulture);
|
||||
var totalLineCountText = this.totalLineCount.ToString("N0", CultureInfo.CurrentCulture);
|
||||
var lastLoadedText = this.lastLoadedAt?.LocalDateTime.ToString("g", CultureInfo.CurrentCulture) ?? T("not loaded yet");
|
||||
|
||||
if (this.loadedLines.Count == 0)
|
||||
return string.Format(T("Loaded {0} lines. Last refresh: {1}."), loadedLineCount, lastLoadedText);
|
||||
|
||||
if (this.skippedLineCount > 0)
|
||||
{
|
||||
var skippedLineCountText = this.skippedLineCount.ToString("N0", CultureInfo.CurrentCulture);
|
||||
return string.Format(T("Showing {0} of {1} loaded lines. {2} older lines were skipped. Last refresh: {3}."), visibleLineCount, loadedLineCount, skippedLineCountText, lastLoadedText);
|
||||
}
|
||||
|
||||
return string.Format(T("Showing {0} of {1} lines. Last refresh: {2}."), visibleLineCount, totalLineCountText, lastLoadedText);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
if (!this.SettingsManager.IsAssistantVisible(Tools.Components.LOG_VIEWER_ASSISTANT, assistantName: T("Log Viewer")))
|
||||
{
|
||||
this.NavigationManager.NavigateTo(Routes.ASSISTANTS);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.RefreshLogAsync();
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
this.StopAutoRefresh();
|
||||
}
|
||||
|
||||
private async Task SelectedLogFileChanged(LogFileKind value)
|
||||
{
|
||||
if (this.selectedLogFile == value)
|
||||
return;
|
||||
|
||||
this.selectedLogFile = value;
|
||||
await this.RefreshLogAsync();
|
||||
}
|
||||
|
||||
private Task SelectedLogLevelsChanged(IEnumerable<string?>? selectedValues)
|
||||
{
|
||||
UpdateSelectedValues(this.selectedLogLevels, selectedValues);
|
||||
this.RefreshDisplayLines();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SelectedLoggersChanged(IEnumerable<string?>? selectedValues)
|
||||
{
|
||||
UpdateSelectedValues(this.selectedLoggers, selectedValues);
|
||||
this.RefreshDisplayLines();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SelectedSourceDetailsChanged(IEnumerable<string?>? selectedValues)
|
||||
{
|
||||
UpdateSelectedValues(this.selectedSourceDetails, selectedValues);
|
||||
this.RefreshDisplayLines();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task AutoRefreshChanged(bool value)
|
||||
{
|
||||
this.autoRefresh = value;
|
||||
|
||||
if (this.autoRefresh)
|
||||
this.StartAutoRefresh();
|
||||
else
|
||||
this.StopAutoRefresh();
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task MaxLinesChanged(int value)
|
||||
{
|
||||
var normalizedValue = Math.Clamp(value, MIN_MAX_LINES, MAX_MAX_LINES);
|
||||
if (this.maxLines == normalizedValue)
|
||||
return;
|
||||
|
||||
this.maxLines = normalizedValue;
|
||||
await this.RefreshLogAsync();
|
||||
}
|
||||
|
||||
private void ClearFilters()
|
||||
{
|
||||
this.filterText = string.Empty;
|
||||
this.selectedLogLevels.Clear();
|
||||
this.selectedLoggers.Clear();
|
||||
this.selectedSourceDetails.Clear();
|
||||
this.RefreshDisplayLines();
|
||||
}
|
||||
|
||||
private async Task RefreshLogAsync()
|
||||
{
|
||||
if (this.isLoading)
|
||||
return;
|
||||
|
||||
this.isLoading = true;
|
||||
this.loadError = string.Empty;
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
|
||||
try
|
||||
{
|
||||
this.logPaths = await this.RustService.GetLogPaths();
|
||||
var path = this.CurrentLogPath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
this.loadedLines = [];
|
||||
this.totalLineCount = 0;
|
||||
this.skippedLineCount = 0;
|
||||
this.lastLoadedAt = null;
|
||||
this.loadError = T("The log file path is not available yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
this.loadedLines = [];
|
||||
this.totalLineCount = 0;
|
||||
this.skippedLineCount = 0;
|
||||
this.lastLoadedAt = null;
|
||||
this.loadError = string.Format(T("The log file does not exist: {0}"), path);
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshot = await ReadLogSnapshotAsync(path, this.maxLines);
|
||||
this.loadedLines = snapshot.Lines;
|
||||
this.totalLineCount = snapshot.TotalLineCount;
|
||||
this.skippedLineCount = snapshot.SkippedLineCount;
|
||||
this.lastLoadedAt = DateTimeOffset.Now;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogWarning(e, "Could not read the log file for the log viewer assistant.");
|
||||
this.loadedLines = [];
|
||||
this.totalLineCount = 0;
|
||||
this.skippedLineCount = 0;
|
||||
this.lastLoadedAt = null;
|
||||
this.loadError = string.Format(T("The log file could not be read: {0}"), e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.isLoading = false;
|
||||
this.RebuildFilterOptions();
|
||||
this.RefreshDisplayLines();
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<LogSnapshot> ReadLogSnapshotAsync(string path, int maxLines)
|
||||
{
|
||||
var queue = new Queue<string>(Math.Min(maxLines, 4096));
|
||||
var totalLineCount = 0;
|
||||
var skippedLineCount = 0;
|
||||
|
||||
await using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536, true);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8, true);
|
||||
|
||||
while (await reader.ReadLineAsync() is { } line)
|
||||
{
|
||||
totalLineCount++;
|
||||
queue.Enqueue(line);
|
||||
|
||||
if (queue.Count <= maxLines)
|
||||
continue;
|
||||
|
||||
queue.Dequeue();
|
||||
skippedLineCount++;
|
||||
}
|
||||
|
||||
var firstLineNumber = skippedLineCount + 1;
|
||||
var lines = queue
|
||||
.Select((line, index) => new LogLine(firstLineNumber + index, line, ParseLogSegments(line)))
|
||||
.ToList();
|
||||
|
||||
return new(lines, totalLineCount, skippedLineCount);
|
||||
}
|
||||
|
||||
private void RebuildFilterOptions()
|
||||
{
|
||||
this.logLevelOptions = BuildFilterOptions(this.loadedLines.Select(line => line.Segments.Level), CompareLogLevels);
|
||||
this.loggerOptions = BuildFilterOptions(this.loadedLines.Select(line => line.Segments.Logger), (left, right) => StringComparer.OrdinalIgnoreCase.Compare(left, right));
|
||||
this.sourceDetailOptions = BuildFilterOptions(this.loadedLines.Select(line => line.Segments.SourceDetails), (left, right) => StringComparer.OrdinalIgnoreCase.Compare(left, right));
|
||||
|
||||
NormalizeSelectedValues(this.selectedLogLevels, this.logLevelOptions);
|
||||
NormalizeSelectedValues(this.selectedLoggers, this.loggerOptions);
|
||||
NormalizeSelectedValues(this.selectedSourceDetails, this.sourceDetailOptions);
|
||||
}
|
||||
|
||||
private void RefreshDisplayLines()
|
||||
{
|
||||
this.activeSearchTerms = BuildSearchTerms(this.filterText);
|
||||
this.displayLines = this.loadedLines
|
||||
.Where(this.LineMatchesFilters)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private bool LineMatchesFilters(LogLine line)
|
||||
{
|
||||
if (!MatchesSelection(line.Segments.Level, this.selectedLogLevels))
|
||||
return false;
|
||||
|
||||
if (!MatchesSelection(line.Segments.Logger, this.selectedLoggers))
|
||||
return false;
|
||||
|
||||
if (!MatchesSelection(line.Segments.SourceDetails, this.selectedSourceDetails))
|
||||
return false;
|
||||
|
||||
if (!this.filterOnly || this.activeSearchTerms.Length == 0)
|
||||
return true;
|
||||
|
||||
return MatchesSearchTerms(this.GetPlainRenderedLine(line), this.activeSearchTerms);
|
||||
}
|
||||
|
||||
private string RenderLine(LogLine line)
|
||||
{
|
||||
var text = this.GetPlainRenderedLine(line);
|
||||
var ranges = new List<HighlightRange>();
|
||||
AddSearchTermRanges(text, this.activeSearchTerms, ranges);
|
||||
|
||||
if (ranges.Count == 0)
|
||||
return WebUtility.HtmlEncode(text);
|
||||
|
||||
ranges = MergeRanges(ranges);
|
||||
var sb = new StringBuilder();
|
||||
var position = 0;
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
AppendEncoded(sb, text, position, range.Start - position);
|
||||
sb.Append("""<mark class="log-viewer-highlight">""");
|
||||
AppendEncoded(sb, text, range.Start, range.Length);
|
||||
sb.Append("</mark>");
|
||||
position = range.Start + range.Length;
|
||||
}
|
||||
|
||||
AppendEncoded(sb, text, position, text.Length - position);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GetPlainRenderedLine(LogLine line)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
var segments = line.Segments;
|
||||
|
||||
if (this.showTimestamps && !string.IsNullOrWhiteSpace(segments.Timestamp))
|
||||
parts.Add(segments.Timestamp);
|
||||
|
||||
if (!ShouldHideSelectedSegment(segments.Level, this.selectedLogLevels))
|
||||
AddIfNotWhiteSpace(parts, segments.Level);
|
||||
|
||||
if (!ShouldHideSelectedSegment(segments.Logger, this.selectedLoggers))
|
||||
AddIfNotWhiteSpace(parts, segments.Logger);
|
||||
|
||||
if (!ShouldHideSelectedSegment(segments.SourceDetails, this.selectedSourceDetails))
|
||||
AddIfNotWhiteSpace(parts, segments.SourceDetails);
|
||||
|
||||
AddIfNotWhiteSpace(parts, segments.Message);
|
||||
|
||||
return parts.Count == 0 ? string.Empty : string.Join(" ", parts);
|
||||
}
|
||||
|
||||
private string GetLineClass(LogLine line)
|
||||
{
|
||||
var level = line.Segments.Level ?? string.Empty;
|
||||
|
||||
if (level.Contains("ERROR", StringComparison.OrdinalIgnoreCase) || level.Contains("CRITICAL", StringComparison.OrdinalIgnoreCase))
|
||||
return "log-viewer-line log-viewer-line-error";
|
||||
|
||||
if (level.Contains("WARN", StringComparison.OrdinalIgnoreCase))
|
||||
return "log-viewer-line log-viewer-line-warn";
|
||||
|
||||
if (level.Contains("DEBUG", StringComparison.OrdinalIgnoreCase))
|
||||
return "log-viewer-line log-viewer-line-debug";
|
||||
|
||||
if (level.Contains("TRACE", StringComparison.OrdinalIgnoreCase))
|
||||
return "log-viewer-line log-viewer-line-trace";
|
||||
|
||||
return "log-viewer-line";
|
||||
}
|
||||
|
||||
private string GetFilterOptionDisplay(string value)
|
||||
{
|
||||
return value == OTHER_OPTION_VALUE ? T("Other") : value;
|
||||
}
|
||||
|
||||
private string GetMultiSelectionText(List<string?>? selectedValues)
|
||||
{
|
||||
if (selectedValues is null || selectedValues.Count == 0)
|
||||
return T("All");
|
||||
|
||||
var selectedLabels = selectedValues
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||
.Select(value => this.GetFilterOptionDisplay(value!))
|
||||
.ToList();
|
||||
|
||||
return selectedLabels.Count == 0 ? T("All") : string.Join(", ", selectedLabels);
|
||||
}
|
||||
|
||||
private void StartAutoRefresh()
|
||||
{
|
||||
this.StopAutoRefresh();
|
||||
this.autoRefreshCancellationTokenSource = new CancellationTokenSource();
|
||||
_ = this.AutoRefreshLoopAsync(this.autoRefreshCancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void StopAutoRefresh()
|
||||
{
|
||||
this.autoRefreshCancellationTokenSource?.Cancel();
|
||||
this.autoRefreshCancellationTokenSource?.Dispose();
|
||||
this.autoRefreshCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
private async Task AutoRefreshLoopAsync(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var timer = new PeriodicTimer(AUTO_REFRESH_INTERVAL);
|
||||
while (await timer.WaitForNextTickAsync(token))
|
||||
await this.InvokeAsync(this.RefreshLogAsync);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static LogSegments ParseLogSegments(string line)
|
||||
{
|
||||
var index = 0;
|
||||
var parsedAnySegment = false;
|
||||
string? timestamp = null;
|
||||
string? level = null;
|
||||
string? logger = null;
|
||||
string? sourceDetails = null;
|
||||
|
||||
if (TryReadBracket(line, index, out var bracket, out var content, out var nextIndex) && IsTimestamp(content))
|
||||
{
|
||||
timestamp = bracket;
|
||||
index = nextIndex;
|
||||
parsedAnySegment = true;
|
||||
}
|
||||
|
||||
var candidateIndex = SkipWhiteSpace(line, index);
|
||||
if (TryReadLogLevel(line, candidateIndex, out var detectedLevel, out nextIndex))
|
||||
{
|
||||
level = detectedLevel;
|
||||
index = nextIndex;
|
||||
parsedAnySegment = true;
|
||||
}
|
||||
|
||||
candidateIndex = SkipWhiteSpace(line, index);
|
||||
if (TryReadBracket(line, candidateIndex, out bracket, out content, out nextIndex))
|
||||
{
|
||||
if (IsSourceDetails(content))
|
||||
{
|
||||
sourceDetails = bracket;
|
||||
index = nextIndex;
|
||||
parsedAnySegment = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger = bracket;
|
||||
index = nextIndex;
|
||||
parsedAnySegment = true;
|
||||
|
||||
candidateIndex = SkipWhiteSpace(line, index);
|
||||
if (TryReadBracket(line, candidateIndex, out bracket, out content, out nextIndex) && IsSourceDetails(content))
|
||||
{
|
||||
sourceDetails = bracket;
|
||||
index = nextIndex;
|
||||
parsedAnySegment = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var message = parsedAnySegment ? ReadMessage(line, index) : line;
|
||||
return new(timestamp, level, logger, sourceDetails, message);
|
||||
}
|
||||
|
||||
private static bool TryReadBracket(string text, int start, out string bracket, out string content, out int nextIndex)
|
||||
{
|
||||
bracket = string.Empty;
|
||||
content = string.Empty;
|
||||
nextIndex = start;
|
||||
|
||||
if (start >= text.Length || text[start] != '[')
|
||||
return false;
|
||||
|
||||
var end = text.IndexOf(']', start + 1);
|
||||
if (end < 0)
|
||||
return false;
|
||||
|
||||
bracket = text[start..(end + 1)];
|
||||
content = text[(start + 1)..end];
|
||||
nextIndex = end + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadLogLevel(string text, int start, out string level, out int nextIndex)
|
||||
{
|
||||
level = string.Empty;
|
||||
nextIndex = start;
|
||||
|
||||
if (start >= text.Length || text[start] == '[')
|
||||
return false;
|
||||
|
||||
var end = start;
|
||||
while (end < text.Length && !char.IsWhiteSpace(text[end]))
|
||||
end++;
|
||||
|
||||
if (end == start)
|
||||
return false;
|
||||
|
||||
var candidate = text[start..end];
|
||||
if (candidate.Length > 20 || candidate.Any(character => !char.IsLetter(character)))
|
||||
return false;
|
||||
|
||||
var afterCandidate = SkipWhiteSpace(text, end);
|
||||
if (afterCandidate >= text.Length || text[afterCandidate] != '[')
|
||||
return false;
|
||||
|
||||
level = candidate;
|
||||
nextIndex = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsTimestamp(string content)
|
||||
{
|
||||
return DateTimeOffset.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out _);
|
||||
}
|
||||
|
||||
private static bool IsSourceDetails(string content)
|
||||
{
|
||||
return content.Contains("=", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static int SkipWhiteSpace(string text, int start)
|
||||
{
|
||||
var index = start;
|
||||
while (index < text.Length && char.IsWhiteSpace(text[index]))
|
||||
index++;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static string ReadMessage(string text, int start)
|
||||
{
|
||||
if (start >= text.Length)
|
||||
return string.Empty;
|
||||
|
||||
if (char.IsWhiteSpace(text[start]))
|
||||
start++;
|
||||
|
||||
return start >= text.Length ? string.Empty : text[start..];
|
||||
}
|
||||
|
||||
private static List<string> BuildFilterOptions(IEnumerable<string?> values, Comparison<string> comparison)
|
||||
{
|
||||
var options = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
continue;
|
||||
|
||||
options.TryAdd(value, value);
|
||||
}
|
||||
|
||||
var sortedOptions = options.Values.ToList();
|
||||
sortedOptions.Sort(comparison);
|
||||
sortedOptions.Add(OTHER_OPTION_VALUE);
|
||||
return sortedOptions;
|
||||
}
|
||||
|
||||
private static int CompareLogLevels(string left, string right)
|
||||
{
|
||||
var leftRank = LOG_LEVEL_ORDER.GetValueOrDefault(left, int.MaxValue);
|
||||
var rightRank = LOG_LEVEL_ORDER.GetValueOrDefault(right, int.MaxValue);
|
||||
var rankComparison = leftRank.CompareTo(rightRank);
|
||||
return rankComparison != 0 ? rankComparison : StringComparer.OrdinalIgnoreCase.Compare(left, right);
|
||||
}
|
||||
|
||||
private static void NormalizeSelectedValues(HashSet<string> selectedValues, List<string> options)
|
||||
{
|
||||
var validOptions = options.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
selectedValues.RemoveWhere(value => !validOptions.Contains(value));
|
||||
}
|
||||
|
||||
private static void UpdateSelectedValues(HashSet<string> target, IEnumerable<string?>? selectedValues)
|
||||
{
|
||||
target.Clear();
|
||||
if (selectedValues is null)
|
||||
return;
|
||||
|
||||
foreach (var value in selectedValues)
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
target.Add(value);
|
||||
}
|
||||
|
||||
private static bool MatchesSelection(string? value, HashSet<string> selectedValues)
|
||||
{
|
||||
if (selectedValues.Count == 0)
|
||||
return true;
|
||||
|
||||
var normalizedValue = string.IsNullOrWhiteSpace(value) ? OTHER_OPTION_VALUE : value;
|
||||
return selectedValues.Contains(normalizedValue);
|
||||
}
|
||||
|
||||
private static bool ShouldHideSelectedSegment(string? value, HashSet<string> selectedValues)
|
||||
{
|
||||
return selectedValues.Count == 1 && !string.IsNullOrWhiteSpace(value) && selectedValues.Contains(value);
|
||||
}
|
||||
|
||||
private static string[] BuildSearchTerms(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return [];
|
||||
|
||||
return text
|
||||
.Split(WORD_SPLIT_CHARS, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static bool MatchesSearchTerms(string text, string[] terms)
|
||||
{
|
||||
return terms.Length == 0 || terms.Any(term => text.Contains(term, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static void AddSearchTermRanges(string text, string[] terms, List<HighlightRange> ranges)
|
||||
{
|
||||
foreach (var term in terms)
|
||||
AddLiteralRanges(text, term, ranges);
|
||||
}
|
||||
|
||||
private static void AddLiteralRanges(string line, string value, List<HighlightRange> ranges)
|
||||
{
|
||||
var index = 0;
|
||||
while ((index = line.IndexOf(value, index, StringComparison.OrdinalIgnoreCase)) >= 0)
|
||||
{
|
||||
ranges.Add(new(index, value.Length));
|
||||
index += value.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<HighlightRange> MergeRanges(List<HighlightRange> ranges)
|
||||
{
|
||||
var mergedRanges = new List<HighlightRange>();
|
||||
foreach (var range in ranges.OrderBy(x => x.Start).ThenByDescending(x => x.Length))
|
||||
{
|
||||
if (mergedRanges.Count == 0)
|
||||
{
|
||||
mergedRanges.Add(range);
|
||||
continue;
|
||||
}
|
||||
|
||||
var previous = mergedRanges[^1];
|
||||
var previousEnd = previous.Start + previous.Length;
|
||||
var currentEnd = range.Start + range.Length;
|
||||
if (range.Start <= previousEnd)
|
||||
{
|
||||
mergedRanges[^1] = previous with { Length = Math.Max(previousEnd, currentEnd) - previous.Start };
|
||||
continue;
|
||||
}
|
||||
|
||||
mergedRanges.Add(range);
|
||||
}
|
||||
|
||||
return mergedRanges;
|
||||
}
|
||||
|
||||
private static void AppendEncoded(StringBuilder sb, string value, int start, int length)
|
||||
{
|
||||
if (length <= 0)
|
||||
return;
|
||||
|
||||
sb.Append(WebUtility.HtmlEncode(value.Substring(start, length)));
|
||||
}
|
||||
|
||||
private static void AddIfNotWhiteSpace(List<string> parts, string? value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
parts.Add(value);
|
||||
}
|
||||
|
||||
private readonly record struct LogLine(int Number, string Text, LogSegments Segments);
|
||||
|
||||
private readonly record struct LogSegments(string? Timestamp, string? Level, string? Logger, string? SourceDetails, string Message);
|
||||
|
||||
private readonly record struct LogSnapshot(List<LogLine> Lines, int TotalLineCount, int SkippedLineCount);
|
||||
|
||||
private readonly record struct HighlightRange(int Start, int Length);
|
||||
}
|
||||
|
||||
public enum LogFileKind
|
||||
{
|
||||
APP,
|
||||
STARTUP,
|
||||
}
|
||||
@ -101,7 +101,8 @@
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Software Engineering",
|
||||
(Components.CODING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.ERI_ASSISTANT, PreviewFeatures.PRE_RAG_2024)
|
||||
(Components.ERI_ASSISTANT, PreviewFeatures.PRE_RAG_2024),
|
||||
(Components.LOG_VIEWER_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@ -110,6 +111,7 @@
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogCoding" Component="Components.CODING_ASSISTANT" Name="@T("Coding")" Description="@T("Get coding and debugging support from an LLM.")" Icon="@Icons.Material.Filled.Code" Link="@Routes.ASSISTANT_CODING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogERIServer" Component="Components.ERI_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_RAG_2024" Name="@T("ERI Server")" Description="@T("Generate an ERI server to integrate business systems.")" Icon="@Icons.Material.Filled.PrivateConnectivity" Link="@Routes.ASSISTANT_ERI"/>
|
||||
<AssistantBlock TSettings="NoSettingsPanel" Component="Components.LOG_VIEWER_ASSISTANT" Name="@T("Log Viewer")" Description="@T("View and filter AI Studio log files.")" Icon="@Icons.Material.Filled.Article" Link="@Routes.ASSISTANT_LOG_VIEWER"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
|
||||
@ -319,7 +319,8 @@ CONFIG["SETTINGS"] = {}
|
||||
-- CODING_ASSISTANT, TEXT_SUMMARIZER_ASSISTANT, EMAIL_ASSISTANT,
|
||||
-- LEGAL_CHECK_ASSISTANT, SYNONYMS_ASSISTANT, MY_TASKS_ASSISTANT,
|
||||
-- JOB_POSTING_ASSISTANT, BIAS_DAY_ASSISTANT, ERI_ASSISTANT,
|
||||
-- DOCUMENT_ANALYSIS_ASSISTANT, SLIDE_BUILDER_ASSISTANT, I18N_ASSISTANT
|
||||
-- DOCUMENT_ANALYSIS_ASSISTANT, SLIDE_BUILDER_ASSISTANT, I18N_ASSISTANT,
|
||||
-- LOG_VIEWER_ASSISTANT
|
||||
-- CONFIG["SETTINGS"]["DataApp.HiddenAssistants"] = { "ERI_ASSISTANT", "I18N_ASSISTANT" }
|
||||
|
||||
-- Configure enterprise approvals for assistant plugins.
|
||||
|
||||
@ -1632,6 +1632,84 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4254597
|
||||
-- Ask your questions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Stellen Sie ihre Fragen"
|
||||
|
||||
-- Find
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1042076026"] = "Suchen"
|
||||
|
||||
-- The log file could not be read: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1147062477"] = "Die Protokolldatei konnte nicht gelesen werden: {0}"
|
||||
|
||||
-- Refresh
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T135637716"] = "Aktualisieren"
|
||||
|
||||
-- Log type
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1649755707"] = "Protokolltyp"
|
||||
|
||||
-- Showing {0} of {1} loaded lines. {2} older lines were skipped. Last refresh: {3}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1747827400"] = "Anzeige von {0} von {1} geladenen Zeilen. {2} ältere Zeilen wurden übersprungen. Letzte Aktualisierung: {3}."
|
||||
|
||||
-- The log file does not exist: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "Die Protokolldatei existiert nicht: {0}"
|
||||
|
||||
-- Other
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Andere"
|
||||
|
||||
-- Max lines
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1904230854"] = "Max. Zeilen"
|
||||
|
||||
-- All
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1974461284"] = "Alle"
|
||||
|
||||
-- Startup log
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2288538420"] = "Startprotokoll"
|
||||
|
||||
-- Showing {0} of {1} lines. Last refresh: {2}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2378353570"] = "Anzeige von {0} von {1} Zeilen. Letzte Aktualisierung: {2}."
|
||||
|
||||
-- No matching log lines.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "Keine passenden Protokollzeilen."
|
||||
|
||||
-- Source details
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Quellendetails"
|
||||
|
||||
-- Loaded {0} lines. Last refresh: {1}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2920304709"] = "{0} Zeilen geladen. Letzte Aktualisierung: {1}."
|
||||
|
||||
-- Filter only
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3152625639"] = "Nur filtern"
|
||||
|
||||
-- Loading log file...
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Lade Protokolldatei..."
|
||||
|
||||
-- The log file path is not available yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "Der Pfad zur Protokolldatei ist noch nicht verfügbar."
|
||||
|
||||
-- Logger
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T376222229"] = "Protokoll"
|
||||
|
||||
-- Auto-refresh
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3837203600"] = "Automatisch aktualisieren"
|
||||
|
||||
-- not loaded yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3863250749"] = "noch nicht geladen"
|
||||
|
||||
-- Loading...
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T397479987"] = "Wird geladen..."
|
||||
|
||||
-- Usage log
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Nutzungsprotokoll"
|
||||
|
||||
-- Log file
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Protokolldatei"
|
||||
|
||||
-- Log Viewer
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Protokollanzeige"
|
||||
|
||||
-- Show timestamps
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Zeitstempel anzeigen"
|
||||
|
||||
-- Clear
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T77955010"] = "Löschen"
|
||||
|
||||
-- You can enter text, attach one or more documents, or use both. At least one input is required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1442535450"] = "Sie können Text eingeben, ein oder mehrere Dokumente anhängen oder beides verwenden. Mindestens eine Eingabe ist erforderlich."
|
||||
|
||||
@ -6504,6 +6582,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Stellenanzeige"
|
||||
-- Ask a question about a legal document.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Stellen Sie Fragen zu einem juristischen Dokument."
|
||||
|
||||
-- Log Viewer
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4130241777"] = "Protokollanzeige"
|
||||
|
||||
-- ERI Server
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4204533420"] = "ERI-Server"
|
||||
|
||||
@ -6525,6 +6606,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T782102948"] = "Vorurteil des Tage
|
||||
-- Learn about one cognitive bias every day.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Lerne jeden Tag einen kognitiven Bias kennen."
|
||||
|
||||
-- View and filter AI Studio log files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T892147062"] = "AI Studio-Protokolldateien anzeigen und filtern."
|
||||
|
||||
-- Localization
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Lokalisierung"
|
||||
|
||||
@ -7045,7 +7129,7 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T864851737"] = "Axum wird verwend
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "Für einige Datenübertragungen müssen wir die Daten in Base64 kodieren. Diese Rust-Bibliothek eignet sich dafür hervorragend."
|
||||
|
||||
-- How to update
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T906183311"] = "Update-Anleitung "
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T906183311"] = "Update-Anleitung"
|
||||
|
||||
-- Install Pandoc
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T986578435"] = "Pandoc installieren"
|
||||
@ -7725,6 +7809,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T4262148639"] = "Umformu
|
||||
-- Localization Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T446674624"] = "Lokalisierungs-Assistent"
|
||||
|
||||
-- Log Viewer Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T555062689"] = "Assistent für die Protokollanzeige"
|
||||
|
||||
-- New Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T826248509"] = "Neuer Chat"
|
||||
|
||||
|
||||
@ -1632,6 +1632,84 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4254597
|
||||
-- Ask your questions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Ask your questions"
|
||||
|
||||
-- Find
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1042076026"] = "Find"
|
||||
|
||||
-- The log file could not be read: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1147062477"] = "The log file could not be read: {0}"
|
||||
|
||||
-- Refresh
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T135637716"] = "Refresh"
|
||||
|
||||
-- Log type
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1649755707"] = "Log type"
|
||||
|
||||
-- Showing {0} of {1} loaded lines. {2} older lines were skipped. Last refresh: {3}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1747827400"] = "Showing {0} of {1} loaded lines. {2} older lines were skipped. Last refresh: {3}."
|
||||
|
||||
-- The log file does not exist: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1807514273"] = "The log file does not exist: {0}"
|
||||
|
||||
-- Other
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1849229205"] = "Other"
|
||||
|
||||
-- Max lines
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1904230854"] = "Max lines"
|
||||
|
||||
-- All
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T1974461284"] = "All"
|
||||
|
||||
-- Startup log
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2288538420"] = "Startup log"
|
||||
|
||||
-- Showing {0} of {1} lines. Last refresh: {2}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2378353570"] = "Showing {0} of {1} lines. Last refresh: {2}."
|
||||
|
||||
-- No matching log lines.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2511997530"] = "No matching log lines."
|
||||
|
||||
-- Source details
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2686813966"] = "Source details"
|
||||
|
||||
-- Loaded {0} lines. Last refresh: {1}.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T2920304709"] = "Loaded {0} lines. Last refresh: {1}."
|
||||
|
||||
-- Filter only
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3152625639"] = "Filter only"
|
||||
|
||||
-- Loading log file...
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T333036481"] = "Loading log file..."
|
||||
|
||||
-- The log file path is not available yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3686775689"] = "The log file path is not available yet."
|
||||
|
||||
-- Logger
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T376222229"] = "Logger"
|
||||
|
||||
-- Auto-refresh
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3837203600"] = "Auto-refresh"
|
||||
|
||||
-- not loaded yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T3863250749"] = "not loaded yet"
|
||||
|
||||
-- Loading...
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T397479987"] = "Loading..."
|
||||
|
||||
-- Usage log
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4031747274"] = "Usage log"
|
||||
|
||||
-- Log file
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4129341821"] = "Log file"
|
||||
|
||||
-- Log Viewer
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T4130241777"] = "Log Viewer"
|
||||
|
||||
-- Show timestamps
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T469116133"] = "Show timestamps"
|
||||
|
||||
-- Clear
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LOGVIEWER::ASSISTANTLOGVIEWER::T77955010"] = "Clear"
|
||||
|
||||
-- You can enter text, attach one or more documents, or use both. At least one input is required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1442535450"] = "You can enter text, attach one or more documents, or use both. At least one input is required."
|
||||
|
||||
@ -6504,6 +6582,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Job Posting"
|
||||
-- Ask a question about a legal document.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Ask a question about a legal document."
|
||||
|
||||
-- Log Viewer
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4130241777"] = "Log Viewer"
|
||||
|
||||
-- ERI Server
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4204533420"] = "ERI Server"
|
||||
|
||||
@ -6525,6 +6606,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T782102948"] = "Bias of the Day"
|
||||
-- Learn about one cognitive bias every day.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Learn about one cognitive bias every day."
|
||||
|
||||
-- View and filter AI Studio log files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T892147062"] = "View and filter AI Studio log files."
|
||||
|
||||
-- Localization
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization"
|
||||
|
||||
@ -7725,6 +7809,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T4262148639"] = "Rewrite
|
||||
-- Localization Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T446674624"] = "Localization Assistant"
|
||||
|
||||
-- Log Viewer Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T555062689"] = "Log Viewer Assistant"
|
||||
|
||||
-- New Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T826248509"] = "New Chat"
|
||||
|
||||
|
||||
@ -32,5 +32,6 @@ public sealed partial class Routes
|
||||
public const string ASSISTANT_DOCUMENT_ANALYSIS = "/assistant/document-analysis";
|
||||
public const string ASSISTANT_DYNAMIC = "/assistant/dynamic";
|
||||
public const string ASSISTANT_META_ASSISTANT = "/assistant/builder";
|
||||
public const string ASSISTANT_LOG_VIEWER = "/assistant/log-viewer";
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
|
||||
@ -29,4 +29,6 @@ public enum ConfigurableAssistant
|
||||
// ReSharper disable InconsistentNaming
|
||||
I18N_ASSISTANT,
|
||||
// ReSharper restore InconsistentNaming
|
||||
|
||||
LOG_VIEWER_ASSISTANT,
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@ public static class AssistantVisibilityExtensions
|
||||
Components.DOCUMENT_ANALYSIS_ASSISTANT => ConfigurableAssistant.DOCUMENT_ANALYSIS_ASSISTANT,
|
||||
Components.SLIDE_BUILDER_ASSISTANT => ConfigurableAssistant.SLIDE_BUILDER_ASSISTANT,
|
||||
Components.I18N_ASSISTANT => ConfigurableAssistant.I18N_ASSISTANT,
|
||||
Components.LOG_VIEWER_ASSISTANT => ConfigurableAssistant.LOG_VIEWER_ASSISTANT,
|
||||
|
||||
_ => ConfigurableAssistant.UNKNOWN,
|
||||
};
|
||||
|
||||
@ -35,4 +35,5 @@ public enum Components
|
||||
AGENT_DATA_SOURCE_SELECTION,
|
||||
AGENT_RETRIEVAL_CONTEXT_VALIDATION,
|
||||
AGENT_ASSISTANT_PLUGIN_AUDIT,
|
||||
LOG_VIEWER_ASSISTANT,
|
||||
}
|
||||
@ -17,6 +17,7 @@ public static class ComponentsExtensions
|
||||
Components.BIAS_DAY_ASSISTANT => false,
|
||||
Components.I18N_ASSISTANT => false,
|
||||
Components.DOCUMENT_ANALYSIS_ASSISTANT => false,
|
||||
Components.LOG_VIEWER_ASSISTANT => false,
|
||||
|
||||
Components.APP_SETTINGS => false,
|
||||
Components.WRITER => false,
|
||||
@ -50,6 +51,7 @@ public static class ComponentsExtensions
|
||||
Components.DOCUMENT_ANALYSIS_ASSISTANT => TB("Document Analysis Assistant"),
|
||||
Components.SLIDE_BUILDER_ASSISTANT => TB("Slide Planner Assistant"),
|
||||
Components.META_ASSISTANT => TB("Assistant Builder"),
|
||||
Components.LOG_VIEWER_ASSISTANT => TB("Log Viewer Assistant"),
|
||||
|
||||
Components.CHAT => TB("New Chat"),
|
||||
|
||||
|
||||
@ -192,3 +192,98 @@
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.log-viewer-shell {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.log-viewer-select {
|
||||
min-width: 12rem;
|
||||
}
|
||||
|
||||
.log-viewer-number {
|
||||
max-width: 9rem;
|
||||
}
|
||||
|
||||
.log-viewer-filter {
|
||||
min-width: 18rem;
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.log-viewer-multiselect {
|
||||
min-width: 16rem;
|
||||
max-width: 34rem;
|
||||
}
|
||||
|
||||
.log-viewer-path {
|
||||
color: var(--mud-palette-text-secondary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.log-viewer-pane {
|
||||
min-height: 28rem;
|
||||
border: 1px solid var(--mud-palette-lines-default);
|
||||
border-radius: 6px;
|
||||
background-color: var(--mud-palette-background-grey);
|
||||
}
|
||||
|
||||
.log-viewer-lines {
|
||||
margin: 0;
|
||||
padding: 0.5rem 0;
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.log-viewer-line {
|
||||
display: grid;
|
||||
grid-template-columns: 5.5rem minmax(0, 1fr);
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
.log-viewer-line:hover {
|
||||
background-color: var(--mud-palette-action-default-hover);
|
||||
}
|
||||
|
||||
.log-viewer-line-number {
|
||||
padding-right: 0.75rem;
|
||||
color: var(--mud-palette-text-secondary);
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
border-right: 1px solid var(--mud-palette-lines-default);
|
||||
}
|
||||
|
||||
.log-viewer-line-text {
|
||||
padding-left: 0.75rem;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.log-viewer-line-error {
|
||||
color: var(--mud-palette-error);
|
||||
}
|
||||
|
||||
.log-viewer-line-warn {
|
||||
color: var(--mud-palette-warning-darken);
|
||||
}
|
||||
|
||||
.log-viewer-line-debug,
|
||||
.log-viewer-line-trace {
|
||||
color: var(--mud-palette-text-secondary);
|
||||
}
|
||||
|
||||
.log-viewer-highlight {
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
background-color: var(--mud-palette-warning);
|
||||
color: var(--mud-palette-warning-text);
|
||||
}
|
||||
|
||||
.log-viewer-empty {
|
||||
display: flex;
|
||||
min-height: 20rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
color: var(--mud-palette-text-secondary);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
# v26.7.3, build 245 (2026-07-xx xx:xx UTC)
|
||||
- Added support for OpenAI GPT-5.6 Sol, Terra, and Luna; Anthropic Claude Fable 5 and Mythos 5; and Google Gemini 3 Flash, Gemini 3.1 Flash-Lite, Gemini 3.1 Pro, and Gemini 3.5 Flash.
|
||||
- Added a Log Viewer assistant that shows AI Studio log files in a read-only view with search, log filters, highlighting, and auto-refresh.
|
||||
- Improved the "My Tasks Assistant": you can now provide one or more documents in addition to text or use documents alone when asking to identify tasks.
|
||||
- Improved update guidance for Flatpak installations and added an enterprise option that lets organizations manage updates entirely through their IT department.
|
||||
- Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep.
|
||||
@ -7,4 +8,4 @@
|
||||
- Fixed voice recording not starting on Linux.
|
||||
- Upgraded Rust to v1.97.0.
|
||||
- Upgraded Tauri to v2.11.5.
|
||||
- Upgraded common dependencies.
|
||||
- Upgraded common dependencies.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user