Spaces:
Running
Running
fix: src/merge.js
Browse files- src/merge.js +24 -3
src/merge.js
CHANGED
|
@@ -249,7 +249,7 @@ export function mergeResults(interiors, products, options = {}) {
|
|
| 249 |
// are enlarged and ringed exactly like the newest interiors, so a product
|
| 250 |
// added on vai-avatar appears in this Space's "mới" front row with a neon
|
| 251 |
// frame, no query needed.
|
| 252 |
-
const RING_LIMIT = options.recentProductRing ??
|
| 253 |
const recents = (options.recentProducts ?? [])
|
| 254 |
.filter(item => item && typeof item === 'object' && item.sku && !seen.has(productKey(item.sku)))
|
| 255 |
.sort((a, b) => (String(a.addedAt || '') < String(b.addedAt || '') ? 1 : String(a.addedAt || '') > String(b.addedAt || '') ? -1 : 0));
|
|
@@ -288,8 +288,24 @@ export function mergeResults(interiors, products, options = {}) {
|
|
| 288 |
const showNewRow = String(options.query ?? "").trim().length === 0;
|
| 289 |
out.unshift(...(showNewRow ? fresh : []));
|
| 290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
for (const image of interiors ?? []) {
|
| 292 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
seen.add(interiorKey(image.id));
|
| 294 |
out.push(image);
|
| 295 |
if (out.length >= interiorLimit) break;
|
|
@@ -322,7 +338,12 @@ export function mergeResults(interiors, products, options = {}) {
|
|
| 322 |
if (seen.has(key)) continue;
|
| 323 |
seen.add(key);
|
| 324 |
const isExact = exact.has(key);
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
if (scoredHits.length >= productLimit) break;
|
| 327 |
}
|
| 328 |
// Products arrive already ranked by the worker; the score field is the
|
|
|
|
| 249 |
// are enlarged and ringed exactly like the newest interiors, so a product
|
| 250 |
// added on vai-avatar appears in this Space's "mới" front row with a neon
|
| 251 |
// frame, no query needed.
|
| 252 |
+
const RING_LIMIT = options.recentProductRing ?? 12;
|
| 253 |
const recents = (options.recentProducts ?? [])
|
| 254 |
.filter(item => item && typeof item === 'object' && item.sku && !seen.has(productKey(item.sku)))
|
| 255 |
.sort((a, b) => (String(a.addedAt || '') < String(b.addedAt || '') ? 1 : String(a.addedAt || '') > String(b.addedAt || '') ? -1 : 0));
|
|
|
|
| 288 |
const showNewRow = String(options.query ?? "").trim().length === 0;
|
| 289 |
out.unshift(...(showNewRow ? fresh : []));
|
| 290 |
|
| 291 |
+
// A relevance floor, so a search never paints tiles that only look like the
|
| 292 |
+
// query by chance. CLIP cosine answers "how close in meaning", and the lowest
|
| 293 |
+
// results in a top-60 pull are semantically unrelated to anything typed. The
|
| 294 |
+
// floor is RELATIVE to the query's own top match, so it adapts to however
|
| 295 |
+
// decisive the query is: "bếp đá" finds a clear winner and keeps nothing
|
| 296 |
+
// below half its strength, while a fuzzy query keeps a slightly broader tail.
|
| 297 |
+
// Tiles without a score (browse mode) are unaffected.
|
| 298 |
+
const cosineFloor = options.cosFloor ?? 0.18;
|
| 299 |
+
const cosineRel = options.cosRel ?? 0.45;
|
| 300 |
+
let topCosine = 0;
|
| 301 |
for (const image of interiors ?? []) {
|
| 302 |
+
if (Number.isFinite(image?.score) && image.score > topCosine) topCosine = image.score;
|
| 303 |
+
}
|
| 304 |
+
const effectiveFloor = Math.max(cosineFloor, topCosine * cosineRel);
|
| 305 |
+
for (const image of interiors ?? []) {
|
| 306 |
+
const score = image?.score;
|
| 307 |
+
const filteredScore = score === undefined || !Number.isFinite(score) || score >= effectiveFloor;
|
| 308 |
+
if (!image?.id || !filteredScore || seen.has(interiorKey(image.id))) continue;
|
| 309 |
seen.add(interiorKey(image.id));
|
| 310 |
out.push(image);
|
| 311 |
if (out.length >= interiorLimit) break;
|
|
|
|
| 338 |
if (seen.has(key)) continue;
|
| 339 |
seen.add(key);
|
| 340 |
const isExact = exact.has(key);
|
| 341 |
+
const productFloor = options.productFloor ?? 20;
|
| 342 |
+
const score = Number.isFinite(item?.score) ? item.score : 0;
|
| 343 |
+
// A product that only scraped a 3-point token overlap is noise, not an
|
| 344 |
+
// answer: it would paint an unrelated photograph next to the query typed.
|
| 345 |
+
if (score < productFloor) continue;
|
| 346 |
+
scoredHits.push({ product, src, key, isExact, score });
|
| 347 |
if (scoredHits.length >= productLimit) break;
|
| 348 |
}
|
| 349 |
// Products arrive already ranked by the worker; the score field is the
|