← back to Wallco Ai
Fix design concierge returning 0 results
126ee4fa740e8f90c2c953592e7bb7aacf3135e5 · 2026-06-23 09:42:19 -0700 · Steve Abrams
toolSearchDesigns had three compounding bugs that all returned empty-but-200:
- generator='replicate' scope filter excluded ~84% of the published catalog
(incl. every comfy/gemini/wallpapersback row and ALL damask rows)
- exact category= match against a vocabulary (floral/damask/...) the catalog's
real taxonomy (drunk-animals/chinoiserie/...) never uses -> always 0
- image_url built from local_path basename 404'd; switched to /designs/img/by-id/:id
Now scopes to brand='wallco.ai' AND is_published, treats category as a soft
keyword (category OR prompt ILIKE), enlarges the candidate pool when a color
filter is active, and fixes hueNameToDeg returning 0(=rose) for unknown colors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 126ee4fa740e8f90c2c953592e7bb7aacf3135e5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 23 09:42:19 2026 -0700
Fix design concierge returning 0 results
toolSearchDesigns had three compounding bugs that all returned empty-but-200:
- generator='replicate' scope filter excluded ~84% of the published catalog
(incl. every comfy/gemini/wallpapersback row and ALL damask rows)
- exact category= match against a vocabulary (floral/damask/...) the catalog's
real taxonomy (drunk-animals/chinoiserie/...) never uses -> always 0
- image_url built from local_path basename 404'd; switched to /designs/img/by-id/:id
Now scopes to brand='wallco.ai' AND is_published, treats category as a soft
keyword (category OR prompt ILIKE), enlarges the candidate pool when a color
filter is active, and fixes hueNameToDeg returning 0(=rose) for unknown colors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
src/chat.js | 50 ++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 40 insertions(+), 10 deletions(-)
diff --git a/src/chat.js b/src/chat.js
index 4ff2de2..2aecb34 100644
--- a/src/chat.js
+++ b/src/chat.js
@@ -103,13 +103,26 @@ function extractToolCall(text) {
// -------- Tool implementations --------
function toolSearchDesigns({ category, hue, hex_near, motif_kw, limit = 12 } = {}) {
- const where = [`generator='replicate'`];
- if (category) where.push(`category=${esc(category)}`);
+ // Scope to the LIVE wallco.ai catalog only — the same brand + publish gate the
+ // public /api/designs grid uses. The previous `generator='replicate'` filter
+ // was the primary bug: it excluded ~84% of the published catalog (incl. every
+ // 'comfy' / 'gemini-*' / 'wallpapersback.com' row and EVERY damask row), so the
+ // concierge returned 0 results for every query while still answering HTTP 200.
+ const where = [`brand='wallco.ai'`, `is_published = TRUE`];
+ // The concierge's category vocabulary (floral|geometric|damask|mixed) does NOT
+ // match the catalog's real taxonomy (drunk-animals, chinoiserie, stripe, …), so
+ // an exact `category=` match also always yielded 0. Treat category as a SOFT
+ // keyword: match the category column OR the prompt text.
+ if (category) where.push(`(category ILIKE ${esc('%' + category + '%')} OR prompt ILIKE ${esc('%' + category + '%')})`);
if (motif_kw) where.push(`prompt ILIKE ${esc('%' + motif_kw + '%')}`);
const lim = Math.min(parseInt(limit, 10) || 12, 30);
+ // A hue / hex_near filter is applied in JS AFTER the query, so pull a larger
+ // candidate pool first — otherwise the color filter starves on the newest N
+ // rows and silently returns nothing.
+ const poolLim = (hue || hex_near) ? 400 : lim;
const sql = `SELECT COALESCE(json_agg(t),'[]'::json) FROM (
- SELECT id, kind, category, dominant_hex, prompt, local_path
- FROM spoon_all_designs WHERE ${where.join(' AND ')} ORDER BY created_at DESC LIMIT ${lim}) t;`;
+ SELECT id, kind, category, dominant_hex, prompt
+ FROM spoon_all_designs WHERE ${where.join(' AND ')} ORDER BY created_at DESC LIMIT ${poolLim}) t;`;
const rows = JSON.parse(psql(sql) || '[]');
// Hex-near filter (post-query): Lab distance in JS is overkill; use Euclidean in RGB
let filtered = rows;
@@ -121,14 +134,23 @@ function toolSearchDesigns({ category, hue, hex_near, motif_kw, limit = 12 } = {
.sort((a, b) => a._dist - b._dist);
} else if (hue) {
const huedeg = hueNameToDeg(hue);
- filtered = rows.filter(r => withinHue(r.dominant_hex, huedeg, 30));
+ // huedeg === null → unknown color name; skip the hue filter rather than
+ // defaulting to 0° (which silently filtered everything down to red/rose).
+ if (huedeg != null) {
+ const hueMatch = rows.filter(r => withinHue(r.dominant_hex, huedeg, 30));
+ // Hue is fuzzy — if nothing falls inside the cone, fall back to the
+ // keyword/category matches so the user still sees relevant designs.
+ filtered = hueMatch.length ? hueMatch : rows;
+ }
}
return filtered.slice(0, lim).map(r => ({
id: r.id,
title: titleFromPrompt(r.prompt),
category: r.category,
hex: r.dominant_hex,
- image_url: r.local_path ? '/designs/img/' + path.basename(r.local_path) : null
+ // Resolve via the by-id route. local_path basenames are NOT reliably served
+ // on every host (they 404'd here), but /designs/img/by-id/:id always resolves.
+ image_url: '/designs/img/by-id/' + r.id
}));
}
@@ -144,8 +166,16 @@ function hexToRgb(h) {
}
function rgbDist(a, b) { return Math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2); }
function hueNameToDeg(name) {
- const map = { rose:0, amber:25, honey:45, olive:70, sage:120, marine:180, sapphire:220, blue:220, mauve:270, plum:310, gold:45 };
- return map[(name || '').toLowerCase()] ?? 0;
+ const map = {
+ rose:0, red:0, copper:20, amber:25, bronze:35, honey:45, gold:45, brass:45,
+ olive:70, sage:120, green:120, emerald:150, teal:180, marine:180,
+ sapphire:220, blue:220, navy:220, indigo:230, mauve:270, purple:280,
+ plum:310, magenta:320
+ };
+ // Unknown color name → null (caller skips the hue filter). Returning 0 here
+ // silently collapsed every unknown hue down to red/rose.
+ const deg = map[(name || '').toLowerCase()];
+ return deg === undefined ? null : deg;
}
function withinHue(hex, deg, tolerance) {
if (!hex) return false;
@@ -278,8 +308,8 @@ ALWAYS:
- Write a 1-2 sentence preamble before the JSON block to explain what you're about to do.
- Keep the user's stated colors and motifs in the generate prompt.
-Categories: floral, geometric, damask, mixed.
-Color hue names you can use: rose, amber, honey, olive, sage, marine, sapphire, mauve, plum.`;
+Categories (soft keywords — also matched against the design's description): floral, geometric, damask, mixed, chinoiserie, stripe, scenic, mural.
+Color hue names you can use: rose, copper, amber, bronze, honey, gold, brass, olive, sage, emerald, teal, marine, sapphire, navy, indigo, mauve, purple, plum, magenta.`;
const SYS_DESIGN = (d) => `You are the wallco.ai design concierge, in dialogue about ONE specific design.
← 10c7ad7 auto-save: 2026-06-23T02:49:11 (1 files) — data/logs/gate-re
·
back to Wallco Ai
·
Concierge search reads in-memory designs.json cache, not Pos 3a7458b →