From d374bfa1e55a4e60e07213622e25ae32e779fd78 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Mon, 17 Nov 2025 08:05:49 +0100 Subject: [PATCH] feat(viz): Add dual-score display and improve UI controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit enhances the vector visualization interface with better score transparency and improved UX: **Dual-Score Display:** - Store original algorithm scores before normalization (viz_routes.py:203) - Display both raw and normalized scores: "Raw Score: 0.842 (89% relative)" - Update plot hover text with dual scores (userinfo_routes.py:740) - Fixes issue where all queries showed at least one 100% match regardless of actual relevance (normalization artifact) **UI Improvements:** 1. Fusion Method dropdown: Changed from x-show to :disabled - Prevents jarring layout shift when switching algorithms - Dropdown stays visible but grayed out when Semantic is selected - Better UX with opacity: 0.5 and cursor: not-allowed 2. Score Threshold: Changed step from 0.1 to "any" - Allows arbitrary float precision (0.7, 0.85, 0.123) - Users can now fine-tune threshold values 3. Document Types: Converted multi-select to checkbox grid - Replaced clunky Ctrl/Cmd multi-select listbox - Checkbox grid with cleaner layout - Positioned left of Score Threshold and Result Limit inputs - More intuitive UX **Technical Details:** - Raw score ranges vary by algorithm: - Semantic: 0.0-1.0 (cosine similarity) - BM25 RRF: ~0.001-0.033 (Reciprocal Rank Fusion) - BM25 DBSF: Can exceed 1.0 (Distribution-Based Score Fusion) - Normalized scores (0-1) used for visual encoding (marker size, color) - Original scores preserved in API response via getattr fallback Files modified: - nextcloud_mcp_server/auth/viz_routes.py (store original_score) - nextcloud_mcp_server/auth/templates/vector_viz.html (UI controls) - nextcloud_mcp_server/auth/userinfo_routes.py (plot hover text) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitmodules | 6 +-- .../auth/templates/vector_viz.html | 48 ++++++++++++------- nextcloud_mcp_server/auth/userinfo_routes.py | 2 +- nextcloud_mcp_server/auth/viz_routes.py | 12 +++-- third_party/notes | 1 + 5 files changed, 46 insertions(+), 23 deletions(-) create mode 160000 third_party/notes diff --git a/.gitmodules b/.gitmodules index e70e53a..6dc5078 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "oidc"] - path = third_party/oidc - url = https://github.com/cbcoutinho/oidc [submodule "third_party/oidc"] path = third_party/oidc url = https://github.com/cbcoutinho/oidc +[submodule "third_party/notes"] + path = third_party/notes + url = https://github.com/cbcoutinho/notes diff --git a/nextcloud_mcp_server/auth/templates/vector_viz.html b/nextcloud_mcp_server/auth/templates/vector_viz.html index 7756b74..561afb2 100644 --- a/nextcloud_mcp_server/auth/templates/vector_viz.html +++ b/nextcloud_mcp_server/auth/templates/vector_viz.html @@ -186,9 +186,9 @@ -
+
- @@ -211,24 +211,39 @@
- - - - Hold Ctrl/Cmd to select multiple - + +
+ + + + + + +
- +
@@ -284,7 +299,8 @@
- Score: | + Raw Score: + (% relative) | Type:
diff --git a/nextcloud_mcp_server/auth/userinfo_routes.py b/nextcloud_mcp_server/auth/userinfo_routes.py index 335f995..d57806c 100644 --- a/nextcloud_mcp_server/auth/userinfo_routes.py +++ b/nextcloud_mcp_server/auth/userinfo_routes.py @@ -737,7 +737,7 @@ async def user_info_html(request: Request) -> HTMLResponse: y: coordinates.map(c => c[1]), mode: 'markers', type: 'scatter', - text: results.map(r => `${{r.title}}
Score: ${{r.score.toFixed(3)}}`), + text: results.map(r => `${{r.title}}
Raw Score: ${{r.original_score.toFixed(3)}} (${{(r.score * 100).toFixed(0)}}% relative)`), marker: {{ // Multi-channel encoding: size + opacity + color for visual hierarchy // Power scaling (score^2) amplifies visual differences dramatically diff --git a/nextcloud_mcp_server/auth/viz_routes.py b/nextcloud_mcp_server/auth/viz_routes.py index 98925d0..d6776f4 100644 --- a/nextcloud_mcp_server/auth/viz_routes.py +++ b/nextcloud_mcp_server/auth/viz_routes.py @@ -184,7 +184,7 @@ async def vector_visualization_search(request: Request) -> JSONResponse: search_results = all_results[:limit] search_duration = time.perf_counter() - search_start - # Normalize scores relative to this result set for better visualization + # Store original scores and normalize for visualization # (best result = 1.0, worst result = 0.0 within THIS result set) # This makes visual encoding meaningful regardless of RRF normalization if search_results: @@ -197,8 +197,11 @@ async def vector_visualization_search(request: Request) -> JSONResponse: f"→ [0.0, 1.0]" ) - # Rescale each result's score to 0-1 within this result set + # Store original score and rescale to 0-1 for visualization for r in search_results: + # Store original score before normalization + r.original_score = r.score + # Rescale for visual encoding r.score = (r.score - min_score) / score_range if not search_results: @@ -317,7 +320,10 @@ async def vector_visualization_search(request: Request) -> JSONResponse: "doc_type": r.doc_type, "title": r.title, "excerpt": r.excerpt, - "score": r.score, + "score": r.score, # Normalized score for visual encoding (0-1) + "original_score": getattr( + r, "original_score", r.score + ), # Raw score from algorithm "chunk_start_offset": r.chunk_start_offset, "chunk_end_offset": r.chunk_end_offset, } diff --git a/third_party/notes b/third_party/notes new file mode 160000 index 0000000..ce08e98 --- /dev/null +++ b/third_party/notes @@ -0,0 +1 @@ +Subproject commit ce08e985a7bc58475b857267da1e2a7421235cb7