semantic search
This commit is contained in:
parent
f2ce952bb8
commit
47445877d2
@ -15,14 +15,14 @@
|
||||
<style>
|
||||
#ss-widget-launcher {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
bottom: 74px; /* stacked above the theme toggle (bottom:20px, 44px tall) with a real gap - */
|
||||
left: 20px; /* not touching, so they read as two distinct controls, not one fused blob */
|
||||
z-index: 999;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-inner);
|
||||
border: 1.5px solid var(--border-gold);
|
||||
@ -47,7 +47,8 @@
|
||||
#ss-widget-panel {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
left: 20px; /* anchored on the same side as the launcher, so the panel feels connected to
|
||||
where you clicked rather than popping up across the screen */
|
||||
z-index: 1000;
|
||||
width: 360px;
|
||||
max-width: calc(100vw - 40px);
|
||||
@ -177,6 +178,13 @@
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ss-widget-scope-clear {
|
||||
color: var(--brand);
|
||||
-webkit-text-fill-color: var(--brand);
|
||||
text-decoration: underline dotted;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a href="javascript:void(0);" id="ss-widget-launcher" title="جستجوی معنایی — پیدا کردن شعر با توصیف معنای آن" onclick="ssWidgetOpen()">
|
||||
@ -206,6 +214,7 @@
|
||||
<script>
|
||||
var ssWidgetApiRoot = "@APIRoot.SemanticSearchUrl";
|
||||
var ssWidgetMaximized = false;
|
||||
var ssWidgetLastQuery = ""; // the last SUBMITTED query, for the "search globally instead" link
|
||||
|
||||
function ssWidgetOpen() {
|
||||
document.getElementById("ss-widget-launcher").style.display = "none";
|
||||
@ -243,11 +252,20 @@
|
||||
e.preventDefault();
|
||||
var query = document.getElementById("ss-widget-query").value.trim();
|
||||
if (!query) return false;
|
||||
ssWidgetRunSearch(query);
|
||||
ssWidgetLastQuery = query;
|
||||
ssWidgetRunSearch(query, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
function ssWidgetRunSearch(query) {
|
||||
// "search globally instead" - the escape hatch for when auto-detection guesses wrong.
|
||||
// Reuses the last SUBMITTED query, not whatever's currently sitting in the input box
|
||||
// (which the user may have started editing without resubmitting).
|
||||
function ssWidgetSearchGlobally() {
|
||||
if (!ssWidgetLastQuery) return;
|
||||
ssWidgetRunSearch(ssWidgetLastQuery, true);
|
||||
}
|
||||
|
||||
function ssWidgetRunSearch(query, disableScopeDetection) {
|
||||
var resultsEl = document.getElementById("ss-widget-results");
|
||||
var statusEl = document.getElementById("ss-widget-status");
|
||||
resultsEl.innerHTML = "";
|
||||
@ -258,7 +276,7 @@
|
||||
type: "POST",
|
||||
url: ssWidgetApiRoot + "/api/ganjoor/search/semantic",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({ query: query, topK: 10 }),
|
||||
data: JSON.stringify({ query: query, topK: 10, disableScopeDetection: !!disableScopeDetection }),
|
||||
success: function (response) {
|
||||
statusEl.style.display = "none";
|
||||
ssWidgetRenderResults(response);
|
||||
@ -289,7 +307,8 @@
|
||||
var html = "";
|
||||
if (response.detectedPoetName || response.detectedCategoryName) {
|
||||
var scopeText = [response.detectedPoetName, response.detectedCategoryName].filter(Boolean).join(" » ");
|
||||
html += '<p class="ss-widget-scope-note">نتایج محدود به: ' + ssWidgetEscapeHtml(scopeText) + '</p>';
|
||||
html += '<p class="ss-widget-scope-note">نتایج محدود به: ' + ssWidgetEscapeHtml(scopeText) +
|
||||
' <a href="javascript:void(0);" onclick="ssWidgetSearchGlobally()" class="ss-widget-scope-clear">(جستجوی سراسری)</a></p>';
|
||||
}
|
||||
|
||||
response.results.forEach(function (item) {
|
||||
|
||||
@ -22,6 +22,16 @@ namespace RMuseum.Models.Ganjoor.SemanticSearch
|
||||
/// </summary>
|
||||
public int? PoetId { get; set; }
|
||||
public int? CatId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skips auto-detection entirely for this request — the "search globally instead"
|
||||
/// escape hatch. Needed because substring-based detection is genuinely ambiguous
|
||||
/// sometimes: "شمع و پروانه" is both a common poetic theme AND the literal title of a
|
||||
/// book by a specific poet, so a query using it as a theme could get silently locked to
|
||||
/// that one book with no way out otherwise. An explicit PoetId/CatId still applies even
|
||||
/// with this set — that's a deliberate scope the caller asked for, not something guessed.
|
||||
/// </summary>
|
||||
public bool DisableScopeDetection { get; set; }
|
||||
}
|
||||
|
||||
public class SemanticSearchVerseDto
|
||||
|
||||
@ -79,27 +79,35 @@ namespace RMuseum.Services.Implementation
|
||||
// detection failure here is swallowed on top of LazyQueryScopeIndex's own
|
||||
// try/catch, as a second safety net - this is a nice-to-have, never worth
|
||||
// failing the whole search over.
|
||||
//
|
||||
// request.DisableScopeDetection skips this block entirely - the "search globally
|
||||
// instead" escape hatch for when detection guessed wrong (e.g. "شمع و پروانه" as
|
||||
// a theme, not the specific book of that name by a different poet). An explicit
|
||||
// PoetId/CatId still applies even here, since that's a deliberate ask, not a guess.
|
||||
int? scopePoetId = request.PoetId;
|
||||
int? scopeCatId = request.CatId;
|
||||
try
|
||||
if (!request.DisableScopeDetection)
|
||||
{
|
||||
var scopeIndex = await _queryScopeIndex.TryGetIndexAsync(context);
|
||||
if (scopeIndex != null)
|
||||
try
|
||||
{
|
||||
var detected = scopeIndex.DetectScope(request.Query);
|
||||
if (detected.HasAny)
|
||||
var scopeIndex = await _queryScopeIndex.TryGetIndexAsync(context);
|
||||
if (scopeIndex != null)
|
||||
{
|
||||
scopePoetId = scopePoetId ?? detected.PoetId;
|
||||
scopeCatId = scopeCatId ?? detected.CatId;
|
||||
response.DetectedPoetName = detected.PoetName;
|
||||
response.DetectedCategoryName = detected.CategoryName;
|
||||
var detected = scopeIndex.DetectScope(request.Query);
|
||||
if (detected.HasAny)
|
||||
{
|
||||
scopePoetId = scopePoetId ?? detected.PoetId;
|
||||
scopeCatId = scopeCatId ?? detected.CatId;
|
||||
response.DetectedPoetName = detected.PoetName;
|
||||
response.DetectedCategoryName = detected.CategoryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// scope detection is best-effort only - fall through with no scope rather
|
||||
// than fail the search
|
||||
catch (Exception)
|
||||
{
|
||||
// scope detection is best-effort only - fall through with no scope rather
|
||||
// than fail the search
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<int> allowedPoemIds = null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user