Fix WebUI search results
This commit is contained in:
@@ -10,3 +10,10 @@ def test_templates_do_not_slice_filter_results_inline():
|
|||||||
offenders.append(str(template))
|
offenders.append(str(template))
|
||||||
|
|
||||||
assert offenders == []
|
assert offenders == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_template_does_not_require_browser_api_client():
|
||||||
|
content = Path("webui/app/templates/search.html").read_text()
|
||||||
|
|
||||||
|
assert "window.docsApiClient" not in content
|
||||||
|
assert "{% for result in results %}" in content
|
||||||
|
|||||||
+8
-4
@@ -246,22 +246,26 @@ async def upload_file(
|
|||||||
|
|
||||||
@app.get("/search")
|
@app.get("/search")
|
||||||
async def search_form(request: Request):
|
async def search_form(request: Request):
|
||||||
return templates.TemplateResponse("search.html", {"request": request, "query": "", "results": []})
|
return templates.TemplateResponse(
|
||||||
|
"search.html",
|
||||||
|
{"request": request, "query": "", "results": [], "limit": 10, "error": None},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/search/results")
|
@app.get("/search/results")
|
||||||
async def search_results(request: Request, q: str = "", limit: int = 10):
|
async def search_results(request: Request, q: str = "", limit: int = 10):
|
||||||
client = get_client()
|
client = get_client()
|
||||||
results = []
|
results = []
|
||||||
|
error = None
|
||||||
if q:
|
if q:
|
||||||
try:
|
try:
|
||||||
data = await client.post("/search", json={"query": q, "library_id": None, "limit": limit})
|
data = await client.post("/search", json={"query": q, "library_id": None, "limit": limit})
|
||||||
results = data.get("results", [])
|
results = data.get("results", [])
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
results = []
|
error = str(exc)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"search.html",
|
"search.html",
|
||||||
{"request": request, "query": q, "results": results, "limit": limit},
|
{"request": request, "query": q, "results": results, "limit": limit, "error": error},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,61 +11,34 @@
|
|||||||
|
|
||||||
<label for="limit">Limit results:</label>
|
<label for="limit">Limit results:</label>
|
||||||
<select id="limit" name="limit">
|
<select id="limit" name="limit">
|
||||||
<option value="5">5</option>
|
{% for option in [5, 10, 20, 50] %}
|
||||||
<option value="10" selected>10</option>
|
<option value="{{ option }}" {% if limit == option %}selected{% endif %}>{{ option }}</option>
|
||||||
<option value="20">20</option>
|
{% endfor %}
|
||||||
<option value="50">50</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button type="submit">Search</button>
|
<button type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="search-results" class="results-box"></div>
|
{% if error %}
|
||||||
|
<p class="error">Error loading results: {{ error }}</p>
|
||||||
{% if results %}
|
{% elif query and not results %}
|
||||||
|
<p>No results found.</p>
|
||||||
|
{% elif results %}
|
||||||
<div class="results-count">{{ results|length }} results found</div>
|
<div class="results-count">{{ results|length }} results found</div>
|
||||||
|
<div class="results-box">
|
||||||
|
{% for result in results %}
|
||||||
|
<article class="result-card">
|
||||||
|
<h3>{{ result.title or result.path or result.library_id or 'Document result' }}</h3>
|
||||||
|
<p>{{ (result.content or '')[:500] }}{% if (result.content or '')|length > 500 %}...{% endif %}</p>
|
||||||
|
<p>
|
||||||
|
<strong>Library:</strong> {{ result.library_id or 'unknown' }}
|
||||||
|
{% if result.score is defined %} | <strong>Score:</strong> {{ '%.3f'|format(result.score) }}{% endif %}
|
||||||
|
</p>
|
||||||
|
{% if result.library_id %}
|
||||||
|
<a href="/libraries/{{ result.library_id }}/docs">View library documents</a>
|
||||||
|
{% endif %}
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
<script>
|
|
||||||
async function loadResults(query, limit) {
|
|
||||||
const searchBox = document.getElementById("search-results");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const payload = { query: query || "{{ initial_query or '' }}", library_id: null, limit: parseInt(limit) };
|
|
||||||
const api = window.docsApiClient;
|
|
||||||
|
|
||||||
const result = await api.post("/search", payload);
|
|
||||||
|
|
||||||
if (result.results && Array.isArray(result.results)) {
|
|
||||||
searchBox.className = "results-box";
|
|
||||||
let html = '<div class="results-count">' + result.results.length + ' results found</div>';
|
|
||||||
|
|
||||||
for (const r of result.results) {
|
|
||||||
const title = r.title || (r.content || '').substring(0, 100);
|
|
||||||
const content = (r.content || '').substring(0, 500);
|
|
||||||
html += '<div class="result-card">' +
|
|
||||||
'<h3>' + escapeHtml(title) + '</h3>' +
|
|
||||||
'<p>' + escapeHtml(content) + '...</p>' +
|
|
||||||
'<a href="/docs/' + (r.library_id || '') + '">View Full</a></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<a href="/search/form" class="new-search-link">← New Search</a>';
|
|
||||||
searchBox.innerHTML = html;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
searchBox.innerHTML = '<p class="error">Error loading results: ' + escapeHtml(err.message) + '</p>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load initial results if query parameter exists in URL
|
|
||||||
var urlParams = new URLSearchParams(window.location.search);
|
|
||||||
{% if query %}loadResults(urlParams.get('q') || urlParams.get('q'), urlParams.get('limit'));{% endif %}
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
|
||||||
if (!str) return "";
|
|
||||||
var e = document.createElement('div');
|
|
||||||
e.textContent = str;
|
|
||||||
return e.innerHTML;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user