feat: Improve vector visualization with static assets and fixes

- Extract CSS and JavaScript into separate static files
  - Created nextcloud_mcp_server/auth/static/vector-viz.css
  - Created nextcloud_mcp_server/auth/static/vector-viz.js
  - Updated templates to reference external assets

- Fix vector visualization issues:
  - Normalize vectors before PCA to match Qdrant's cosine distance
  - Add zero-norm and NaN detection/handling for large datasets
  - Enable responsive Plotly sizing (autosize + responsive config)
  - Widen plot area to full viewport width with minimized margins

- Improve visualization accuracy:
  - Query point now positioned correctly relative to documents
  - Handles 200+ points without JSON serialization errors
  - Full-width plot maximizes screen space utilization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2025-11-19 04:10:44 +01:00
co-authored by Claude
parent 9db20a4d01
commit 53689d076b
25 changed files with 1781 additions and 400 deletions
+10 -1
View File
@@ -24,6 +24,7 @@ from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import JSONResponse, RedirectResponse
from starlette.routing import Mount, Route
from starlette.staticfiles import StaticFiles
from nextcloud_mcp_server.auth import (
InsufficientScopeError,
@@ -1491,7 +1492,7 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
# Create a separate Starlette app for browser routes that need session auth
# This prevents SessionAuthBackend from interfering with FastMCP's OAuth
browser_routes = [
Route("/", user_info_html, methods=["GET"]), # /app → webapp (HTML UI)
Route("/", user_info_html, methods=["GET"]), # /app → user info with all tabs
Route(
"/revoke", revoke_session, methods=["POST"], name="revoke_session_endpoint"
), # /app/revoke → revoke_session
@@ -1527,6 +1528,14 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
),
]
# Add static files mount if directory exists
static_dir = os.path.join(os.path.dirname(__file__), "auth", "static")
if os.path.isdir(static_dir):
browser_routes.append(
Mount("/static", StaticFiles(directory=static_dir), name="static")
)
logger.info(f"Mounted static files from {static_dir}")
browser_app = Starlette(routes=browser_routes)
browser_app.add_middleware(
AuthenticationMiddleware, # type: ignore[invalid-argument-type]