Files
I150/srcs/RobotNet10/Components/RobotNet10.NavigationTuneUI/Components/TuningAdvisorPanel.razor
2026-07-03 16:37:12 +07:00

391 lines
17 KiB
Plaintext

@using RobotNet10.NavigationTune.Shared.Models
@using RobotNet10.NavigationTuneUI.Services
@using RobotNet10.NavigationTuneUI.Helpers
@inject TuningApiService ApiService
@inject ISnackbar Snackbar
<MudCard Style="width: 100%; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box;">
<MudCardHeader Style="flex-shrink: 0; box-sizing: border-box;">
<CardHeaderContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.AutoFixHigh" />
<MudText Typo="Typo.h6">Tuning Advisor</MudText>
@if (Report != null && Report.HasCriticalIssues)
{
<MudChip T="string" Color="Color.Error" Size="Size.Small">Critical</MudChip>
}
else if (Report != null && Report.HighPriorityCount > 0)
{
<MudChip T="string" Color="Color.Warning" Size="Size.Small">@Report.HighPriorityCount High</MudChip>
}
else if (Report != null && Report.Suggestions.Count > 0)
{
<MudChip T="string" Color="Color.Info" Size="Size.Small">@Report.Suggestions.Count Suggestions</MudChip>
}
</MudStack>
</CardHeaderContent>
<CardHeaderActions>
@if (Report != null)
{
<MudTooltip Text="Re-analyze">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
OnClick="HandleReAnalyze"
Disabled="@_isProcessing" />
</MudTooltip>
}
</CardHeaderActions>
</MudCardHeader>
<MudCardContent Style="flex: 1; overflow-y: auto; overflow-x: hidden; box-sizing: border-box;">
@if (Report == null)
{
<MudText Typo="Typo.body2" Color="Color.Secondary">
Chưa có dữ liệu phân tích. Chạy test để nhận gợi ý tuning tự động.
</MudText>
}
else
{
@* Overall Assessment *@
<MudAlert Severity="@GetOverallSeverity()" Dense="true" Class="mb-3">
@Report.OverallAssessment
</MudAlert>
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mb-2">
Controller: @Report.ControllerType | Samples: @Report.TelemetrySamplesAnalyzed | @Report.GeneratedAt.ToString("HH:mm:ss")
</MudText>
@* Detected Patterns *@
@if (Report.DetectedPatterns.Count > 0)
{
<MudExpansionPanels Elevation="0" Class="mb-3">
<MudExpansionPanel Text="@($"Detected Patterns ({Report.DetectedPatterns.Count})")"
Expanded="false"
Dense="true">
@foreach (var pattern in Report.DetectedPatterns.OrderByDescending(p => p.Severity))
{
<MudAlert Severity="@GetPatternSeverity(pattern.Severity)"
Dense="true" Class="mb-1" NoIcon="false">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudChip T="string" Size="Size.Small"
Color="@GetPatternChipColor(pattern.Severity)">
@pattern.Category
</MudChip>
<MudText Typo="Typo.body2">@pattern.Description</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">
(Severity: @pattern.Severity.ToString("F2"))
</MudText>
</MudStack>
</MudAlert>
}
</MudExpansionPanel>
</MudExpansionPanels>
}
@* Suggestions Table *@
@if (Report.Suggestions.Count > 0)
{
<MudText Typo="Typo.subtitle2" Class="mb-2">Suggestions</MudText>
<MudTable Items="@Report.Suggestions"
Dense="true"
Hover="true"
Bordered="false"
Striped="true"
Elevation="0"
T="TuningSuggestion">
<HeaderContent>
<MudTh Style="width: 40px;"></MudTh>
<MudTh>Priority</MudTh>
<MudTh>Parameter</MudTh>
<MudTh Style="text-align: right;">Current</MudTh>
<MudTh Style="text-align: center;"></MudTh>
<MudTh Style="text-align: right;">Suggested</MudTh>
<MudTh Style="text-align: right;">Change</MudTh>
<MudTh Style="text-align: center;">Confidence</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>
<MudCheckBox T="bool"
Value="@IsSelected(context)"
ValueChanged="@(v => ToggleSelection(context, v))"
Dense="true"
Color="Color.Primary" />
</MudTd>
<MudTd>
<MudChip T="string" Size="Size.Small" Color="@GetPriorityColor(context.Priority)">
@context.Priority
</MudChip>
</MudTd>
<MudTd>
<MudTooltip Text="@context.Reason">
<MudText Typo="Typo.body2">@context.ParameterDisplayName</MudText>
</MudTooltip>
</MudTd>
<MudTd Style="text-align: right;">
<MudText Typo="Typo.body2">@context.CurrentValue.ToString("F4")</MudText>
</MudTd>
<MudTd Style="text-align: center;">
<MudIcon Icon="@Icons.Material.Filled.ArrowForward" Size="Size.Small" />
</MudTd>
<MudTd Style="text-align: right;">
<MudText Typo="Typo.body2" Color="Color.Primary">
<b>@context.SuggestedValue.ToString("F4")</b>
</MudText>
</MudTd>
<MudTd Style="text-align: right;">
<MudText Typo="Typo.body2"
Color="@(context.ChangePercent > 0 ? Color.Success : Color.Error)">
@(context.ChangePercent > 0 ? "+" : "")@context.ChangePercent.ToString("F1")%
</MudText>
</MudTd>
<MudTd Style="text-align: center;">
<MudProgressLinear Value="@(context.Confidence * 100)"
Color="@GetConfidenceColor(context.Confidence)"
Style="width: 60px; height: 8px; display: inline-block;" />
<MudText Typo="Typo.caption">@((context.Confidence * 100).ToString("F0"))%</MudText>
</MudTd>
</RowTemplate>
<ChildRowContent>
<MudGrid Class="pa-2">
<MudItem xs="12" sm="6">
<MudText Typo="Typo.caption" Color="Color.Secondary">Reason</MudText>
<MudText Typo="Typo.body2">@context.Reason</MudText>
</MudItem>
<MudItem xs="12" sm="6">
<MudText Typo="Typo.caption" Color="Color.Secondary">Expected Impact</MudText>
<MudText Typo="Typo.body2">@context.ExpectedImpact</MudText>
</MudItem>
@if (context.RelatedPatterns.Count > 0)
{
<MudItem xs="12">
<MudText Typo="Typo.caption" Color="Color.Secondary">Related Patterns</MudText>
<MudStack Row="true" Spacing="1">
@foreach (var pattern in context.RelatedPatterns)
{
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined">@pattern</MudChip>
}
</MudStack>
</MudItem>
}
</MudGrid>
</ChildRowContent>
</MudTable>
@* Action Buttons *@
<MudStack Row="true" Spacing="2" Class="mt-3" Justify="Justify.FlexEnd">
<MudButton Variant="Variant.Text"
Size="Size.Small"
OnClick="SelectAll">
Select All
</MudButton>
<MudButton Variant="Variant.Text"
Size="Size.Small"
OnClick="DeselectAll">
Deselect All
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Preview"
OnClick="HandleApplySelected"
Disabled="@(_selectedIds.Count == 0 || _isProcessing)"
Size="Size.Small">
Apply Selected (Preview)
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Save"
OnClick="HandleApplyAndSave"
Disabled="@(_selectedIds.Count == 0 || _isProcessing)"
Size="Size.Small">
Apply & Save as New
</MudButton>
</MudStack>
}
else
{
<MudAlert Severity="Severity.Success" Dense="true">
Không phát hiện vấn đề. Tham số hiện tại hoạt động tốt.
</MudAlert>
}
}
@if (_isProcessing)
{
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mt-2" />
}
</MudCardContent>
</MudCard>
@code {
[Parameter] public TuningReport? Report { get; set; }
[Parameter] public NavigationParameterSet? CurrentParameters { get; set; }
[Parameter] public EventCallback<NavigationParameterSet> OnApplySuggestions { get; set; }
[Parameter] public EventCallback<NavigationParameterSet> OnSaveNewParameterSet { get; set; }
[Parameter] public Guid? TestRunId { get; set; }
private HashSet<Guid> _selectedIds = new();
private bool _isProcessing;
protected override void OnParametersSet()
{
// Auto-select high+ priority suggestions when new report arrives
if (Report != null && _selectedIds.Count == 0)
{
_selectedIds = Report.Suggestions
.Where(s => s.Priority >= SuggestionPriority.High)
.Select(s => s.Id)
.ToHashSet();
}
}
private bool IsSelected(TuningSuggestion suggestion) => _selectedIds.Contains(suggestion.Id);
private void ToggleSelection(TuningSuggestion suggestion, bool selected)
{
if (selected)
_selectedIds.Add(suggestion.Id);
else
_selectedIds.Remove(suggestion.Id);
}
private void SelectAll()
{
if (Report != null)
_selectedIds = Report.Suggestions.Select(s => s.Id).ToHashSet();
}
private void DeselectAll() => _selectedIds.Clear();
private async Task HandleApplySelected()
{
if (Report == null || CurrentParameters == null || _selectedIds.Count == 0)
return;
_isProcessing = true;
try
{
var modified = await ApiService.ApplySuggestionsAsync(
CurrentParameters.Id, _selectedIds.ToList(), Report);
if (modified != null)
{
await OnApplySuggestions.InvokeAsync(modified);
Snackbar.Add($"Đã áp dụng {_selectedIds.Count} gợi ý (preview)", Severity.Success);
}
}
catch (ApiException ex)
{
Snackbar.Add($"Lỗi áp dụng gợi ý: {ex.Message}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"Lỗi: {ErrorHelper.GetErrorMessage(ex)}", Severity.Error);
}
finally
{
_isProcessing = false;
}
}
private async Task HandleApplyAndSave()
{
if (Report == null || CurrentParameters == null || _selectedIds.Count == 0)
return;
_isProcessing = true;
try
{
var saved = await ApiService.ApplyAndSaveSuggestionsAsync(
CurrentParameters.Id, _selectedIds.ToList(), Report);
if (saved != null)
{
await OnSaveNewParameterSet.InvokeAsync(saved);
Snackbar.Add($"Đã lưu bộ thông số mới: {saved.Name}", Severity.Success);
}
}
catch (ApiException ex)
{
Snackbar.Add($"Lỗi lưu bộ thông số: {ex.Message}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"Lỗi: {ErrorHelper.GetErrorMessage(ex)}", Severity.Error);
}
finally
{
_isProcessing = false;
}
}
private async Task HandleReAnalyze()
{
if (TestRunId == null)
return;
_isProcessing = true;
try
{
var newReport = await ApiService.AnalyzeTestRunAsync(TestRunId.Value);
if (newReport != null)
{
Report = newReport;
_selectedIds = newReport.Suggestions
.Where(s => s.Priority >= SuggestionPriority.High)
.Select(s => s.Id)
.ToHashSet();
Snackbar.Add("Phân tích lại hoàn tất", Severity.Success);
}
}
catch (ApiException ex)
{
Snackbar.Add($"Lỗi phân tích: {ex.Message}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"Lỗi: {ErrorHelper.GetErrorMessage(ex)}", Severity.Error);
}
finally
{
_isProcessing = false;
}
}
private Severity GetOverallSeverity()
{
if (Report == null) return Severity.Info;
if (Report.HasCriticalIssues) return Severity.Error;
if (Report.HighPriorityCount > 0) return Severity.Warning;
if (Report.Suggestions.Count > 0) return Severity.Info;
return Severity.Success;
}
private static Severity GetPatternSeverity(double severity)
{
if (severity >= 0.7) return Severity.Error;
if (severity >= 0.4) return Severity.Warning;
return Severity.Info;
}
private static Color GetPatternChipColor(double severity)
{
if (severity >= 0.7) return Color.Error;
if (severity >= 0.4) return Color.Warning;
return Color.Info;
}
private static Color GetPriorityColor(SuggestionPriority priority) => priority switch
{
SuggestionPriority.Critical => Color.Error,
SuggestionPriority.High => Color.Warning,
SuggestionPriority.Medium => Color.Info,
_ => Color.Default
};
private static Color GetConfidenceColor(double confidence)
{
if (confidence >= 0.7) return Color.Success;
if (confidence >= 0.4) return Color.Warning;
return Color.Error;
}
}