perf: Exclude vector-sync status polling from distributed tracing
Skip tracing for /app/vector-sync/status to reduce noise from HTMX polling. Metrics collection continues for this endpoint. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -34,7 +34,7 @@ services:
|
||||
- ./app-hooks:/docker-entrypoint-hooks.d:ro
|
||||
# Mount OIDC development directory outside /var/www/html to avoid rsync conflicts
|
||||
# The post-installation hook will register /opt/apps as an additional app directory
|
||||
- ./third_party:/opt/apps:ro
|
||||
#- ./third_party:/opt/apps:ro
|
||||
environment:
|
||||
- NEXTCLOUD_TRUSTED_DOMAINS=app
|
||||
- NEXTCLOUD_ADMIN_USER=admin
|
||||
@@ -82,6 +82,7 @@ services:
|
||||
- NEXTCLOUD_HOST=http://app:80
|
||||
- NEXTCLOUD_USERNAME=admin
|
||||
- NEXTCLOUD_PASSWORD=admin
|
||||
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
||||
|
||||
# Vector sync configuration (ADR-007)
|
||||
- VECTOR_SYNC_ENABLED=true
|
||||
|
||||
@@ -64,6 +64,13 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
else "unknown"
|
||||
)
|
||||
|
||||
# Get Nextcloud host for generating links to apps
|
||||
# Use public issuer URL if available (for browser-accessible links),
|
||||
# otherwise fall back to NEXTCLOUD_HOST
|
||||
import os
|
||||
|
||||
nextcloud_host = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL") or settings.nextcloud_host
|
||||
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -93,11 +100,15 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}}
|
||||
.controls {{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.control-row {{
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr auto;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
align-items: end;
|
||||
}}
|
||||
.control-group {{
|
||||
margin-bottom: 15px;
|
||||
}}
|
||||
@@ -107,7 +118,7 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}}
|
||||
input[type="text"], select {{
|
||||
input[type="text"], input[type="number"], select {{
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -117,6 +128,9 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
input[type="range"] {{
|
||||
width: 100%;
|
||||
}}
|
||||
select[multiple] {{
|
||||
min-height: 100px;
|
||||
}}
|
||||
.weight-display {{
|
||||
display: inline-block;
|
||||
min-width: 40px;
|
||||
@@ -136,6 +150,19 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
.btn:hover {{
|
||||
background: #0052a3;
|
||||
}}
|
||||
.btn-secondary {{
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}}
|
||||
.btn-secondary:hover {{
|
||||
background: #5a6268;
|
||||
}}
|
||||
#plot {{
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
@@ -145,11 +172,17 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}}
|
||||
.weight-controls {{
|
||||
display: none;
|
||||
.advanced-section {{
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
}}
|
||||
.weight-controls.active {{
|
||||
display: block;
|
||||
.advanced-grid {{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}}
|
||||
.info-box {{
|
||||
background: #e3f2fd;
|
||||
@@ -170,15 +203,16 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
|
||||
<form @submit.prevent="executeSearch">
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="control-group">
|
||||
<label>Search Query</label>
|
||||
<input type="text" x-model="query" placeholder="Enter search query..." />
|
||||
</div>
|
||||
<!-- Main Controls -->
|
||||
<div class="control-group">
|
||||
<label>Search Query</label>
|
||||
<input type="text" x-model="query" placeholder="Enter search query..." required />
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Search Algorithm</label>
|
||||
<select x-model="algorithm" @change="updateWeightControls">
|
||||
<div class="control-row">
|
||||
<div class="control-group" style="margin-bottom: 0;">
|
||||
<label>Algorithm</label>
|
||||
<select x-model="algorithm">
|
||||
<option value="semantic">Semantic (Vector Similarity)</option>
|
||||
<option value="keyword">Keyword (Token Matching)</option>
|
||||
<option value="fuzzy">Fuzzy (Character Overlap)</option>
|
||||
@@ -186,56 +220,71 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Document Types (multi-select)</label>
|
||||
<select x-model="docTypes" multiple size="4" style="height: auto;">
|
||||
<option value="">All Types (cross-app search)</option>
|
||||
<option value="note">Notes</option>
|
||||
<option value="file">Files</option>
|
||||
<option value="calendar">Calendar Events</option>
|
||||
<option value="contact">Contacts</option>
|
||||
<option value="deck">Deck Cards</option>
|
||||
</select>
|
||||
<small style="color: #666; display: block; margin-top: 4px;">
|
||||
Hold Ctrl/Cmd to select multiple. Select "All Types" for cross-app search.
|
||||
</small>
|
||||
<div style="display: flex; align-items: flex-end;">
|
||||
<button type="submit" class="btn" style="width: 100%;">Search & Visualize</button>
|
||||
</div>
|
||||
|
||||
<div class="control-group weight-controls" :class="{{ active: algorithm === 'hybrid' }}">
|
||||
<label>Hybrid Weights</label>
|
||||
<div style="display: flex; align-items: flex-end;">
|
||||
<button type="button" class="btn-secondary" @click="showAdvanced = !showAdvanced" style="white-space: nowrap;">
|
||||
<span x-text="showAdvanced ? 'Hide Advanced' : 'Advanced'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Options (Collapsible) -->
|
||||
<div class="advanced-section" x-show="showAdvanced" x-transition.opacity.duration.200ms>
|
||||
<h3 style="margin-top: 0; margin-bottom: 16px; font-size: 16px;">Advanced Options</h3>
|
||||
|
||||
<div class="advanced-grid">
|
||||
<div class="control-group">
|
||||
<label>Document Types</label>
|
||||
<select x-model="docTypes" multiple>
|
||||
<option value="">All Types (cross-app search)</option>
|
||||
<option value="note">Notes</option>
|
||||
<option value="file">Files</option>
|
||||
<option value="calendar">Calendar Events</option>
|
||||
<option value="contact">Contacts</option>
|
||||
<option value="deck">Deck Cards</option>
|
||||
</select>
|
||||
<small style="color: #666; display: block; margin-top: 4px;">
|
||||
Hold Ctrl/Cmd to select multiple
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="control-group">
|
||||
<label>Score Threshold (Semantic/Hybrid)</label>
|
||||
<input type="number" x-model.number="scoreThreshold" min="0" max="1" step="0.1" />
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Result Limit</label>
|
||||
<input type="number" x-model.number="limit" min="1" max="100" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hybrid Weights (only when hybrid selected) -->
|
||||
<div x-show="algorithm === 'hybrid'" style="margin-top: 16px; padding: 12px; background: #e9ecef; border-radius: 4px;">
|
||||
<label style="margin-bottom: 12px; display: block;">Hybrid Algorithm Weights</label>
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
<label style="display: inline-block; width: 100px;">Semantic:</label>
|
||||
<label style="display: inline-block; width: 100px; font-weight: normal;">Semantic:</label>
|
||||
<input type="range" x-model.number="semanticWeight" min="0" max="1" step="0.1" style="width: 200px; display: inline-block;">
|
||||
<span class="weight-display" x-text="semanticWeight.toFixed(1)"></span>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<label style="display: inline-block; width: 100px;">Keyword:</label>
|
||||
<label style="display: inline-block; width: 100px; font-weight: normal;">Keyword:</label>
|
||||
<input type="range" x-model.number="keywordWeight" min="0" max="1" step="0.1" style="width: 200px; display: inline-block;">
|
||||
<span class="weight-display" x-text="keywordWeight.toFixed(1)"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display: inline-block; width: 100px;">Fuzzy:</label>
|
||||
<label style="display: inline-block; width: 100px; font-weight: normal;">Fuzzy:</label>
|
||||
<input type="range" x-model.number="fuzzyWeight" min="0" max="1" step="0.1" style="width: 200px; display: inline-block;">
|
||||
<span class="weight-display" x-text="fuzzyWeight.toFixed(1)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="control-group">
|
||||
<label>Result Limit</label>
|
||||
<input type="number" x-model.number="limit" min="1" max="100" value="50" />
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Score Threshold (Semantic/Hybrid)</label>
|
||||
<input type="number" x-model.number="scoreThreshold" min="0" max="1" step="0.1" value="0.7" />
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<button type="submit" class="btn">Search & Visualize</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -251,7 +300,9 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
<h2>Search Results (<span x-text="results.length"></span>)</h2>
|
||||
<template x-for="result in results" :key="result.id">
|
||||
<div style="padding: 12px; border-bottom: 1px solid #eee;">
|
||||
<div style="font-weight: 500; color: #0066cc;" x-text="result.title"></div>
|
||||
<a :href="getNextcloudUrl(result)" target="_blank" style="font-weight: 500; color: #0066cc; text-decoration: none;">
|
||||
<span x-text="result.title"></span>
|
||||
</a>
|
||||
<div style="font-size: 14px; color: #666; margin-top: 4px;" x-text="result.excerpt"></div>
|
||||
<div style="font-size: 12px; color: #999; margin-top: 4px;">
|
||||
Score: <span x-text="result.score.toFixed(3)"></span> |
|
||||
@@ -267,6 +318,7 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
return {{
|
||||
query: '',
|
||||
algorithm: 'hybrid',
|
||||
showAdvanced: false,
|
||||
docTypes: [''], // Default to "All Types"
|
||||
limit: 50,
|
||||
scoreThreshold: 0.7,
|
||||
@@ -276,10 +328,6 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
loading: false,
|
||||
results: [],
|
||||
|
||||
updateWeightControls() {{
|
||||
// Update weight controls visibility based on algorithm
|
||||
}},
|
||||
|
||||
async executeSearch() {{
|
||||
this.loading = true;
|
||||
this.results = [];
|
||||
@@ -342,6 +390,27 @@ async def vector_visualization_html(request: Request) -> HTMLResponse:
|
||||
}};
|
||||
|
||||
Plotly.newPlot('plot', [trace], layout);
|
||||
}},
|
||||
|
||||
getNextcloudUrl(result) {{
|
||||
// Generate Nextcloud URL based on document type
|
||||
// Use the actual Nextcloud host (port 8080), not the MCP server
|
||||
const baseUrl = '{nextcloud_host}';
|
||||
|
||||
switch (result.doc_type) {{
|
||||
case 'note':
|
||||
return `${{baseUrl}}/apps/notes/note/${{result.id}}`;
|
||||
case 'file':
|
||||
return `${{baseUrl}}/apps/files/?fileId=${{result.id}}`;
|
||||
case 'calendar':
|
||||
return `${{baseUrl}}/apps/calendar`;
|
||||
case 'contact':
|
||||
return `${{baseUrl}}/apps/contacts`;
|
||||
case 'deck':
|
||||
return `${{baseUrl}}/apps/deck`;
|
||||
default:
|
||||
return `${{baseUrl}}`;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
@@ -547,7 +616,7 @@ async def vector_visualization_search(request: Request) -> JSONResponse:
|
||||
),
|
||||
limit=len(doc_ids) * 2, # Account for multiple chunks per doc
|
||||
with_vectors=True,
|
||||
with_payload=False,
|
||||
with_payload=["doc_id"], # Need doc_id to map vectors to results
|
||||
)
|
||||
|
||||
points = points_response[0]
|
||||
|
||||
@@ -66,8 +66,12 @@ class ObservabilityMiddleware(BaseHTTPMiddleware):
|
||||
# Record start time
|
||||
start_time = time.time()
|
||||
|
||||
# Skip tracing for health/metrics endpoints to reduce noise
|
||||
should_trace = not (path.startswith("/health/") or path == "/metrics")
|
||||
# Skip tracing for health/metrics/polling endpoints to reduce noise
|
||||
should_trace = not (
|
||||
path.startswith("/health/")
|
||||
or path == "/metrics"
|
||||
or path == "/app/vector-sync/status"
|
||||
)
|
||||
|
||||
try:
|
||||
if should_trace:
|
||||
|
||||
@@ -89,7 +89,7 @@ async def get_indexed_doc_types(user_id: str) -> set[str]:
|
||||
settings = get_settings()
|
||||
|
||||
qdrant_client = await get_qdrant_client()
|
||||
collection = settings.qdrant_collection
|
||||
collection = settings.get_collection_name()
|
||||
|
||||
# Use scroll to sample documents and extract doc_types
|
||||
# Note: This could be optimized with a facet/aggregation query if Qdrant adds support
|
||||
|
||||
@@ -85,7 +85,7 @@ class FuzzySearchAlgorithm(SearchAlgorithm):
|
||||
|
||||
# Scroll through Qdrant to get all matching documents
|
||||
qdrant_client = await get_qdrant_client()
|
||||
collection = settings.qdrant_collection
|
||||
collection = settings.get_collection_name()
|
||||
|
||||
all_points = []
|
||||
offset = None
|
||||
|
||||
@@ -83,7 +83,7 @@ class KeywordSearchAlgorithm(SearchAlgorithm):
|
||||
# Scroll through Qdrant to get all matching documents
|
||||
# We need title and excerpt from payload for token matching
|
||||
qdrant_client = await get_qdrant_client()
|
||||
collection = settings.qdrant_collection
|
||||
collection = settings.get_collection_name()
|
||||
|
||||
all_points = []
|
||||
offset = None
|
||||
|
||||
Reference in New Issue
Block a user