feat(search/rag): implement NexusSearchBox, dynamic Qdrant collection auto-provisioning, batch vector ingestion, mobile Serilog logging, and resolve 401 auth handler error (#51)
Resolves #52 This Pull Request introduces the **NexusSearchBox** search feature with premium unified styling, implements a robust **dynamic Qdrant collection auto-provisioning and batch-vector ingestion pipeline**, integrates a unified **Serilog logging infrastructure** for the Blazor Hybrid environment (MAUI), and resolves the **401 Unauthorized API header propagation error** inside mobile builds. ### 🚀 Key Implementations #### 1. Premium `NexusSearchBox` & Semantic Search UI * **NexusSearchBox Component:** Created an elegant search-as-you-type search box with smooth key navigation, quick-clearing, and seamless dynamic styling. * **Unified Aesthetics:** Refactored the search box isolated styling to align perfectly with the dashboard's design system using glassmorphism, `--nexus-neon` token gradients, and smooth pulse/fade animations. * **Semantic Search Integration:** Integrated semantic search query dispatching (`SearchLibrarySemanticallyQuery`) and wired up navigation seamlessly through the updated `ReaderNavigationService`. * **Tests Hardening:** Added/adapted query assertions in `QueryTests.cs` to guarantee safe parameterization and error boundary mapping. #### 2. Qdrant Collection Provisioning & Vector Ingestion * **Dynamic Auto-Provisioning:** Implemented dynamic checking and lazy-creation of the `knowledge_units` collection using 768 dimensions and Cosine distance. * **High-Performance Ingestion:** Optimized `ProcessKnowledgeUnitsAsync` with high-performance batch embedding generation using `_embeddingGenerator` and deterministic MD5 GUIDs for stable, duplicate-free upsertion. * **Database Cache Clear Sync:** Integrated Qdrant collection deletion in `ClearCacheAsync` to ensure absolute consistency between the PostgreSQL database cache and vector database indices. #### 3. Cross-Platform MAUI Logging (Serilog Infrastructure) * **Serilog Integration:** Configured cross-platform Serilog routing in `SerilogConfiguration.cs`, streaming diagnostic logs safely across native platforms and the Blazor Webview container. * **Interop Bridge:** Built `BlazorLoggingBridge.cs` to capture web console messages and pipe them directly to the native host logger. * **Demo Interface:** Added an interactive `SerilogDemo.razor` sandbox under Pages. #### 4. Resolving 401 Load Errors (Authentication Handler Flow) * **Authentication Header Handler:** Implemented the `MobileAuthenticationHeaderHandler` to correctly extract, validate, and inject bearer JWT tokens into outbound API requests. * **Configuration-based API Host:** Structured standard API URI routing to use clean configuration bindings in `appsettings.json`. --- ### 🧪 Verification & Build Status * Run `dotnet build` from the solution root: Successfully compiled the full multi-targeted solution (`Liczba błędów: 0`). * All unit and integration tests successfully executed and verified (`dotnet test`). --------- Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Co-authored-by: Marek Jaisński <jasins.marek@gmail.com> Reviewed-on: #51 Co-authored-by: Antigravity <antigravity@google.com> Co-committed-by: Antigravity <antigravity@google.com>
This commit was merged in pull request #51.
This commit is contained in:
@@ -152,4 +152,319 @@ main {
|
||||
0% { filter: drop-shadow(0 0 2px var(--nexus-neon)); transform: scale(1); }
|
||||
50% { filter: drop-shadow(0 0 10px var(--nexus-neon)); transform: scale(1.1); }
|
||||
100% { filter: drop-shadow(0 0 2px var(--nexus-neon)); transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Contextual Intelligence Panel Layout */
|
||||
.stacked-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.visual-workspace {
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.contextual-intelligence-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(13, 13, 13, 0.6);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.03);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.neon-accent-icon {
|
||||
color: var(--nexus-neon, #00f0ff);
|
||||
filter: drop-shadow(0 0 5px var(--nexus-neon, #00f0ff));
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.no-node-selected {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 150px;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.placeholder-glow {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(0, 240, 255, 0.15) 0%, transparent 70%);
|
||||
animation: glow-pulse 2s infinite ease-in-out;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@keyframes glow-pulse {
|
||||
0% { transform: scale(0.9); opacity: 0.5; }
|
||||
50% { transform: scale(1.1); opacity: 1; }
|
||||
100% { transform: scale(0.9); opacity: 0.5; }
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.node-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
animation: fade-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.node-header-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.node-group-badge {
|
||||
align-self: flex-start;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
letter-spacing: 0.08em;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* Badge specific styling matching category theme colors */
|
||||
.node-group-badge.rule {
|
||||
background: rgba(244, 63, 94, 0.1);
|
||||
color: #f43f5e;
|
||||
border-color: rgba(244, 63, 94, 0.3);
|
||||
}
|
||||
|
||||
.node-group-badge.definition {
|
||||
background: rgba(234, 179, 8, 0.1);
|
||||
color: #eab308;
|
||||
border-color: rgba(234, 179, 8, 0.3);
|
||||
}
|
||||
|
||||
.node-group-badge.table {
|
||||
background: rgba(168, 85, 247, 0.1);
|
||||
color: #a855f7;
|
||||
border-color: rgba(168, 85, 247, 0.3);
|
||||
}
|
||||
|
||||
.node-group-badge.section {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.node-group-badge.bridge {
|
||||
background: rgba(236, 72, 153, 0.1);
|
||||
color: #ec4899;
|
||||
border-color: rgba(236, 72, 153, 0.3);
|
||||
}
|
||||
|
||||
.node-group-badge.current {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.node-group-badge.concept {
|
||||
background: rgba(0, 240, 255, 0.1);
|
||||
color: #00f0ff;
|
||||
border-color: rgba(0, 240, 255, 0.3);
|
||||
}
|
||||
|
||||
.node-label {
|
||||
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.neon-sub-header {
|
||||
border-left: 2px solid var(--nexus-neon, #00f0ff);
|
||||
padding-left: 0.5rem;
|
||||
text-shadow: 0 0 10px rgba(0, 240, 255, 0.2);
|
||||
}
|
||||
|
||||
.node-description {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.node-summary {
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-left: 2px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0 4px 4px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.key-terms-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.key-term-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.82rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.term-bullet {
|
||||
color: var(--nexus-neon, #00f0ff);
|
||||
filter: drop-shadow(0 0 3px var(--nexus-neon, #00f0ff));
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.term-text {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Sidebar Footer & Open Quiz Button */
|
||||
.sidebar-footer {
|
||||
padding: 1rem 1.25rem;
|
||||
background: rgba(13, 13, 13, 0.95);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.open-quiz-btn {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-family: var(--nexus-font-sans, "Outfit", sans-serif);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: rgba(0, 240, 255, 0.03);
|
||||
border: 1px solid rgba(0, 240, 255, 0.3);
|
||||
color: var(--nexus-neon, #00f0ff);
|
||||
box-shadow: 0 4px 15px rgba(0, 240, 255, 0.05);
|
||||
}
|
||||
|
||||
.open-quiz-btn:hover {
|
||||
background: rgba(0, 240, 255, 0.1);
|
||||
border-color: var(--nexus-neon, #00f0ff);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 0 20px rgba(0, 240, 255, 0.25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.open-quiz-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.quiz-pulse-btn {
|
||||
animation: quiz-pulse-glow 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes quiz-pulse-glow {
|
||||
0% { border-color: rgba(0, 240, 255, 0.3); box-shadow: 0 0 5px rgba(0, 240, 255, 0.1); }
|
||||
50% { border-color: var(--nexus-neon, #00f0ff); box-shadow: 0 0 25px rgba(0, 240, 255, 0.3); }
|
||||
100% { border-color: rgba(0, 240, 255, 0.3); box-shadow: 0 0 5px rgba(0, 240, 255, 0.1); }
|
||||
}
|
||||
|
||||
/* Quiz Navigation Header */
|
||||
.quiz-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quiz-nav {
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
}
|
||||
|
||||
.back-to-graph-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.back-to-graph-btn:hover {
|
||||
color: var(--nexus-neon, #00f0ff);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
Reference in New Issue
Block a user