fe5ff81c98
## Overview This PR completes the architectural consolidation of the web project and stabilizes the Identity-based authentication flow for the NexusReader application. It also refines the UI aesthetic for the Book Ingestion Modal as requested in #33. ## Key Changes - **Project Consolidation**: Fully merged `NexusReader.Web.New` into `NexusReader.Web`. This includes updating all namespace references, VS Code launch/task configurations, and CI/CD (`Dockerfile`). - **Identity Stabilization**: - Implemented `IIdentityService` on the server using `SignInManager<NexusUser>` and `UserManager<NexusUser>`. - Fixed registration logic to include mandatory fields (`SubscriptionPlanId`, `TenantId`). - Updated `Login.razor` to force a page reload on successful login, ensuring proper synchronization of authentication cookies between SignalR and the browser. - **UI/UX Refinement**: - Updated `BookIngestionModal` styling to follow the **Nexus Neon** design system. - Added premium button styles with hover effects and glows. - Improved modal layout and interaction feedback (shimmer effects, spinner colors). - **Cleanup**: Removed obsolete interfaces and constants that were superseded by newer Application layer implementations. ## Verification - Successfully built the solution: `dotnet build NexusReader.slnx --no-restore` - Verified project structure and file moves. - Validated server-side authentication logic. Fixes #33 --------- Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Reviewed-on: #40 Co-authored-by: Antigravity <antigravity@google.com> Co-committed-by: Antigravity <antigravity@google.com>
102 lines
4.2 KiB
Plaintext
102 lines
4.2 KiB
Plaintext
@using NexusReader.UI.Shared.Services
|
|
@using NexusReader.Application.DTOs.AI
|
|
@inject KnowledgeCoordinator Coordinator
|
|
@inject IReaderInteractionService InteractionService
|
|
|
|
@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>
|
|
}
|
|
|
|
@code {
|
|
[Parameter] public string SelectedText { get; set; } = string.Empty;
|
|
[Parameter] public string BlockId { get; set; } = string.Empty;
|
|
[Parameter] public SelectionCoordinates? Coordinates { get; set; }
|
|
[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 PositionBelow => Coordinates != null && Coordinates.Top < 320;
|
|
|
|
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;
|
|
}
|
|
|
|
private string PanelStyle => Coordinates != null
|
|
? string.Create(System.Globalization.CultureInfo.InvariantCulture,
|
|
$"top: {(PositionBelow ? Coordinates.Top + 35 : Coordinates.Top - 15):F1}px !important; " +
|
|
$"left: {Math.Clamp(Coordinates.Left + Coordinates.Width / 2, 280, 1600):F1}px !important; " +
|
|
$"transform: translate(-50%, {(PositionBelow ? "0" : "-100%")}) !important;")
|
|
: "";
|
|
|
|
private async Task RequestSummary()
|
|
{
|
|
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;
|
|
}
|
|
|
|
private async Task GenerateFullQuiz()
|
|
{
|
|
IsLoading = true;
|
|
await Coordinator.RequestSummaryAndQuizAsync(FullPageContent);
|
|
IsLoading = false;
|
|
await CloseAsync();
|
|
}
|
|
|
|
private async Task CloseAsync()
|
|
{
|
|
Packet = null;
|
|
await InteractionService.NotifyTextSelected(string.Empty, string.Empty, null!);
|
|
}
|
|
}
|