refactor: redesign selection AI panel to a compact toolbar with independent summary and quiz actions and improved coordinate calculation
This commit is contained in:
@@ -3,49 +3,40 @@
|
||||
@using NexusReader.Application.DTOs.AI
|
||||
@inject KnowledgeCoordinator Coordinator
|
||||
@inject IReaderInteractionService InteractionService
|
||||
@inject IQuizStateService QuizService
|
||||
|
||||
@if (IsVisible)
|
||||
{
|
||||
<div class="selection-ai-panel expanded @(PositionBelow ? "below" : "")" style="@PanelStyle">
|
||||
<div class="ai-bubble">
|
||||
<div class="ai-avatar">
|
||||
<div class="avatar-ring"></div>
|
||||
<NexusIcon Name="robot" Size="48" Class="neon-pulse" />
|
||||
<div class="avatar-label">
|
||||
<span class="name">E-Czytnik</span>
|
||||
<span class="role">Asystent AI</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-content">
|
||||
@if (IsLoading)
|
||||
{
|
||||
<div class="loading-state">
|
||||
<div class="shimmer">Skanowanie fragmentu...</div>
|
||||
</div>
|
||||
}
|
||||
else if (Packet != null)
|
||||
{
|
||||
<div class="summary-box">
|
||||
<NexusTypography Variant="NexusTypography.TypographyVariant.UI">@Packet.Summary</NexusTypography>
|
||||
</div>
|
||||
<div class="ai-actions">
|
||||
<button class="action-btn neon-border" @onclick="GenerateFullQuiz">Generuj Quiz dla całej strony</button>
|
||||
<button class="action-btn ghost" @onclick="CloseAsync">Zamknij</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="summary-box">
|
||||
<NexusTypography Variant="NexusTypography.TypographyVariant.UI">Wykryto ciekawy fragment! Czy chcesz, abym wygenerował podsumowanie lub quiz z tego rozdziału?</NexusTypography>
|
||||
</div>
|
||||
<div class="ai-actions">
|
||||
<button class="action-btn neon-border" @onclick="RequestSummary">Podsumuj zaznaczenie</button>
|
||||
<button class="action-btn ghost" @onclick="CloseAsync">Pomiń</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="bubble-pointer"></div>
|
||||
</div>
|
||||
<div class="selection-ai-panel @(PositionBelow ? "below" : "")" style="@PanelStyle">
|
||||
<button class="toolbar-btn primary @(IsLoadingSummary ? "loading" : "") @(IsAnyLoading ? "disabled" : "")"
|
||||
disabled="@IsAnyLoading"
|
||||
@onclick="RequestSummaryAsync">
|
||||
@if (IsLoadingSummary)
|
||||
{
|
||||
<span class="spinner-inline"></span>
|
||||
<span class="btn-text">Podsumowywanie...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<NexusIcon Name="book-open" Size="14" Class="btn-icon" />
|
||||
<span class="btn-text">Podsumuj</span>
|
||||
}
|
||||
</button>
|
||||
<div class="toolbar-divider"></div>
|
||||
<button class="toolbar-btn secondary @(IsLoadingQuiz ? "loading" : "") @(IsAnyLoading ? "disabled" : "")"
|
||||
disabled="@IsAnyLoading"
|
||||
@onclick="GenerateQuizAsync">
|
||||
@if (IsLoadingQuiz)
|
||||
{
|
||||
<span class="spinner-inline"></span>
|
||||
<span class="btn-text">Generowanie...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<NexusIcon Name="target" Size="14" Class="btn-icon" />
|
||||
<span class="btn-text">Quiz</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -56,47 +47,89 @@
|
||||
[Parameter] public string FullPageContent { get; set; } = string.Empty;
|
||||
|
||||
private bool IsVisible => !string.IsNullOrEmpty(SelectedText) && Coordinates != null;
|
||||
private bool IsLoading = false;
|
||||
private KnowledgePacket? Packet;
|
||||
private bool IsLoadingSummary = false;
|
||||
private bool IsLoadingQuiz = false;
|
||||
private bool IsAnyLoading => IsLoadingSummary || IsLoadingQuiz;
|
||||
private bool PositionBelow => Coordinates != null && Coordinates.Top < 250;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
Console.WriteLine($"[SelectionAiPanel] Parameters set. SelectedText: {SelectedText.Length} chars, Coordinates: {Coordinates?.Top}, PositionBelow: {PositionBelow}");
|
||||
// Reset packet when selection changes
|
||||
Packet = null;
|
||||
// Reset loading states when parameters change
|
||||
IsLoadingSummary = false;
|
||||
IsLoadingQuiz = false;
|
||||
}
|
||||
|
||||
private string PanelStyle => Coordinates != null
|
||||
? string.Create(System.Globalization.CultureInfo.InvariantCulture,
|
||||
$"top: {(PositionBelow ? Coordinates.Bottom + 8 : Coordinates.Top - 8):F1}px !important; " +
|
||||
$"left: {Math.Clamp(Coordinates.Left + Coordinates.Width / 2, 280, 1600):F1}px !important; " +
|
||||
$"top: {(PositionBelow ? Coordinates.Bottom + 16 : Coordinates.Top - 16):F1}px !important; " +
|
||||
$"left: {Math.Max(140.0, Math.Min(Coordinates.ViewportWidth - 140.0, Coordinates.Left + Coordinates.Width / 2.0)):F1}px !important; " +
|
||||
$"transform: translate(-50%, {(PositionBelow ? "0" : "-100%")}) !important;")
|
||||
: "";
|
||||
|
||||
private async Task RequestSummary()
|
||||
private async Task RequestSummaryAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
var contextPrompt = !string.IsNullOrWhiteSpace(FullPageContent)
|
||||
? $"ANALYSIS CONTEXT (Full Page Content):\n{FullPageContent}\n\nUSER SELECTION TO SUMMARIZE:\n"
|
||||
: "";
|
||||
|
||||
var result = await Coordinator.RequestSummaryAndQuizAsync($"{contextPrompt}{SelectedText}");
|
||||
Packet = result.IsSuccess ? result.Value : null;
|
||||
IsLoading = false;
|
||||
if (IsAnyLoading) return;
|
||||
IsLoadingSummary = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var contextPrompt = !string.IsNullOrWhiteSpace(FullPageContent)
|
||||
? $"ANALYSIS CONTEXT (Full Page Content):\n{FullPageContent}\n\nUSER SELECTION TO SUMMARIZE:\n"
|
||||
: "";
|
||||
|
||||
var result = await Coordinator.RequestSummaryAndQuizAsync($"{contextPrompt}{SelectedText}");
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await CloseAsync();
|
||||
await InteractionService.RequestAssistant();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[SelectionAiPanel] Error requesting summary: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoadingSummary = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateFullQuiz()
|
||||
private async Task GenerateQuizAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
await Coordinator.RequestSummaryAndQuizAsync(FullPageContent);
|
||||
IsLoading = false;
|
||||
await CloseAsync();
|
||||
if (IsAnyLoading) return;
|
||||
IsLoadingQuiz = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var contextPrompt = !string.IsNullOrWhiteSpace(FullPageContent)
|
||||
? $"ANALYSIS CONTEXT (Full Page Content):\n{FullPageContent}\n\nUSER SELECTION TO SUMMARIZE:\n"
|
||||
: "";
|
||||
|
||||
var result = await Coordinator.RequestSummaryAndQuizAsync($"{contextPrompt}{SelectedText}");
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await CloseAsync();
|
||||
await QuizService.RequestQuiz(BlockId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[SelectionAiPanel] Error generating quiz: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoadingQuiz = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CloseAsync()
|
||||
{
|
||||
Packet = null;
|
||||
await InteractionService.NotifyTextSelected(string.Empty, string.Empty, null!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user