@using NexusReader.Application.DTOs.AI @using NexusReader.Application.Abstractions.Services @using NexusReader.Application.DTOs.User @using NexusReader.UI.Shared.Components.Atoms @using NexusReader.UI.Shared.Services @using NexusReader.UI.Shared.Models @using System.Net.Http.Json @namespace NexusReader.UI.Shared.Components.Organisms @inject HttpClient Http @inject IKnowledgeService KnowledgeService @inject IReaderNavigationService NavigationService

Asystent AI Nexus

Zadawaj pytania do swojej biblioteki

@if (_chatMessages.Count == 0) {

Zadaj pytanie asystentowi

KM-RAG przeszukuje całą treść książki, wyciąga semantyczne powiązania i generuje precyzyjne odpowiedzi wraz z przypisami źródłowymi.

} else { @foreach (var message in _chatMessages) {
@if (message.Sender == "User") { } else { }
@(message.Sender == "User" ? "Ty" : "Asystent") @message.Timestamp.ToString("HH:mm")
@foreach (var segment in message.Segments) { @if (segment.IsCitation) { [@segment.CitationId] } else { @RenderMarkdown(segment.Text) } }
} @if (_isLoading) {
Asystent Generowanie...
Analiza grafu pojęć...
} }
Obszar: @(string.IsNullOrEmpty(_activeBookTitle) ? "Cała biblioteka" : _activeBookTitle)
@if (_selectedCitation != null) {
}
@code { private static readonly System.Text.RegularExpressions.Regex CitationRegex = new( @"\[Source ID:\s*([^\]]+)\]|\[([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\]", System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Compiled); private static readonly System.Text.RegularExpressions.Regex BoldRegex = new( @"\*\*(.*?)\*\*", System.Text.RegularExpressions.RegexOptions.Compiled); private static readonly System.Text.RegularExpressions.Regex ItalicRegex = new( @"\*(.*?)\*", System.Text.RegularExpressions.RegexOptions.Compiled); private static readonly System.Text.RegularExpressions.Regex CodeBlockRegex = new( @"```(?:[a-zA-Z0-9+#]+)?\s*([\s\S]*?)\s*```", System.Text.RegularExpressions.RegexOptions.Compiled); private static readonly System.Text.RegularExpressions.Regex InlineCodeRegex = new( @"`(.*?)`", System.Text.RegularExpressions.RegexOptions.Compiled); [Parameter] public bool IsOpen { get; set; } [Parameter] public EventCallback OnClose { get; set; } [Parameter] public string? TenantId { get; set; } private string _question = string.Empty; private bool _isLoading; private string _activeBookTitle = string.Empty; private List _chatMessages = new(); private CitationDto? _selectedCitation; protected override async Task OnParametersSetAsync() { if (IsOpen && string.IsNullOrEmpty(_activeBookTitle) && NavigationService.CurrentEbookId != Guid.Empty) { _activeBookTitle = NavigationService.ChapterTitle ?? "Aktywna książka"; } } private async Task HandleClose() { if (OnClose.HasDelegate) { await OnClose.InvokeAsync(); } } private async Task HandleKeyUp(KeyboardEventArgs e) { if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_question) && !_isLoading) { await AskQuestionAsync(); } } private void HandleCitationClick(string citationId) { _selectedCitation = _chatMessages .SelectMany(m => m.Citations) .FirstOrDefault(c => c.CitationId.Equals(citationId, StringComparison.OrdinalIgnoreCase)) ?? new CitationDto { CitationId = citationId, SourceBook = "Grounded Document Chunk", Snippet = "Context snippet retrieved from vector search node." }; } private void CloseCitationModal() { _selectedCitation = null; } private async Task AskQuestionAsync() { if (string.IsNullOrWhiteSpace(_question) || _isLoading) return; var userQuestion = _question; _question = string.Empty; _isLoading = true; _chatMessages.Add(new ChatMessage { Sender = "User", Text = userQuestion, Segments = new List { new ResponseSegment { Text = userQuestion, IsCitation = false } } }); StateHasChanged(); try { Guid? ebookId = null; if (NavigationService.CurrentEbookId != Guid.Empty) { ebookId = NavigationService.CurrentEbookId; } var tenantId = TenantId ?? "global"; var result = await KnowledgeService.AskQuestionAsync(userQuestion, tenantId, ebookId); if (result.IsSuccess) { var response = result.Value; _chatMessages.Add(new ChatMessage { Sender = "AI", Text = response.Answer, Segments = ParseSegments(response.Answer), Citations = response.Citations }); } else { var errMsg = $"Błąd: {result.Errors.FirstOrDefault()?.Message ?? "Wystąpił nieznany problem."}"; _chatMessages.Add(new ChatMessage { Sender = "AI", Text = errMsg, Segments = new List { new ResponseSegment { Text = errMsg, IsCitation = false } } }); } } catch (Exception ex) { var errMsg = $"Błąd sieci/API: {ex.Message}"; _chatMessages.Add(new ChatMessage { Sender = "AI", Text = errMsg, Segments = new List { new ResponseSegment { Text = errMsg, IsCitation = false } } }); } finally { _isLoading = false; StateHasChanged(); } } private List ParseSegments(string text) { var segments = new List(); if (string.IsNullOrEmpty(text)) return segments; var matches = CitationRegex.Matches(text); int lastIndex = 0; foreach (System.Text.RegularExpressions.Match match in matches) { if (match.Index > lastIndex) { segments.Add(new ResponseSegment { Text = text.Substring(lastIndex, match.Index - lastIndex), IsCitation = false }); } var citationId = match.Groups[1].Success ? match.Groups[1].Value.Trim() : match.Groups[2].Value.Trim(); segments.Add(new ResponseSegment { IsCitation = true, CitationId = citationId }); lastIndex = match.Index + match.Length; } if (lastIndex < text.Length) { segments.Add(new ResponseSegment { Text = text.Substring(lastIndex), IsCitation = false }); } return segments; } private MarkupString RenderMarkdown(string text) { if (string.IsNullOrEmpty(text)) return new MarkupString(string.Empty); var html = System.Net.WebUtility.HtmlEncode(text); html = BoldRegex.Replace(html, "$1"); html = ItalicRegex.Replace(html, "$1"); html = CodeBlockRegex.Replace(html, "
$1
"); html = InlineCodeRegex.Replace(html, "$1"); html = html.Replace("\n", "
"); return new MarkupString(html); } }