feat(ui): implement premium NexusSearchBox component and integrate semantic search navigation

This commit is contained in:
2026-05-21 20:16:14 +02:00
parent 37bec89484
commit 0a3ca77d46
9 changed files with 632 additions and 116 deletions
@@ -1,18 +1,7 @@
using FluentResults;
using MediatR;
using Pgvector;
using Pgvector.EntityFrameworkCore;
using NexusReader.Application.Abstractions.Services;
using NexusReader.Application.DTOs.AI;
using Microsoft.Extensions.AI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Resilience;
using Polly;
using Polly.Registry;
using Mapster;
using MapsterMapper;
using NexusReader.Data.Persistence;
namespace NexusReader.Application.Queries.Library;
@@ -21,21 +10,11 @@ public record SearchLibrarySemanticallyQuery(string QueryText, string TenantId,
public class SearchLibrarySemanticallyQueryHandler : IRequestHandler<SearchLibrarySemanticallyQuery, Result<List<SemanticSearchResultDto>>>
{
private readonly IEmbeddingGenerator<string, Embedding<float>> _embeddingGenerator;
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
private readonly ResiliencePipeline _retryPipeline;
private readonly IMapper _mapper;
private readonly IKnowledgeService _knowledgeService;
public SearchLibrarySemanticallyQueryHandler(
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
IDbContextFactory<AppDbContext> dbContextFactory,
ResiliencePipelineProvider<string> pipelineProvider,
IMapper mapper)
public SearchLibrarySemanticallyQueryHandler(IKnowledgeService knowledgeService)
{
_embeddingGenerator = embeddingGenerator;
_dbContextFactory = dbContextFactory;
_retryPipeline = pipelineProvider.GetPipeline("ai-retry");
_mapper = mapper;
_knowledgeService = knowledgeService;
}
public async Task<Result<List<SemanticSearchResultDto>>> Handle(SearchLibrarySemanticallyQuery request, CancellationToken cancellationToken)
@@ -45,19 +24,10 @@ public class SearchLibrarySemanticallyQueryHandler : IRequestHandler<SearchLibra
return Result.Fail("Query text cannot be empty.");
}
// Generate embedding with retry
var embeddingResponse = await _retryPipeline.ExecuteAsync(async ct =>
await _embeddingGenerator.GenerateAsync(new[] { request.QueryText }, cancellationToken: ct), cancellationToken);
var queryVector = new Vector(embeddingResponse.First().Vector.ToArray());
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
var cacheEntries = await dbContext.SemanticKnowledgeCache
.Where(c => c.TenantId == request.TenantId && c.Embedding != null)
.OrderBy(c => c.Embedding!.CosineDistance(queryVector))
.Take(request.Limit)
.ToListAsync(cancellationToken);
var dtos = _mapper.Map<List<SemanticSearchResultDto>>(cacheEntries);
return Result.Ok(dtos);
return await _knowledgeService.SearchLibrarySemanticallyAsync(
request.QueryText,
request.TenantId,
request.Limit,
cancellationToken);
}
}