← back to Wallco Ai
curator: auto-ID now PERSISTS its dotted error-boxes (auto-flagged/amber) to bad_examples.boxes — squares survive reload + accumulate as a standing learning corpus; ✓ promotes to confirmed(red), ✗ drops; unified candidate/confirmed on the auto flag
4a8fa2bb5e0391f415d6395363b464164d286521 · 2026-05-27 10:39:28 -0700 · Steve Abrams
Files touched
M public/admin/cactus-curator.htmlM server.js
Diff
commit 4a8fa2bb5e0391f415d6395363b464164d286521
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 27 10:39:28 2026 -0700
curator: auto-ID now PERSISTS its dotted error-boxes (auto-flagged/amber) to bad_examples.boxes — squares survive reload + accumulate as a standing learning corpus; ✓ promotes to confirmed(red), ✗ drops; unified candidate/confirmed on the auto flag
---
public/admin/cactus-curator.html | 27 ++++++++++++++++-----------
server.js | 28 +++++++++++++++++++++++++---
2 files changed, 41 insertions(+), 14 deletions(-)
diff --git a/public/admin/cactus-curator.html b/public/admin/cactus-curator.html
index 9b225ac..f9b99ca 100644
--- a/public/admin/cactus-curator.html
+++ b/public/admin/cactus-curator.html
@@ -398,27 +398,32 @@ function makeBox(b, isCand, onX, onOk){
function renderBoxes(d, el){
const thumb = el.querySelector('.thumb');
thumb.querySelectorAll('.bad-box').forEach(n => n.remove());
- (d.annotations || []).forEach((b, i) =>
- thumb.appendChild(makeBox(b, false, () => removeAnnotation(d, i, el))));
- (d.candidates || []).forEach((b, i) =>
- thumb.appendChild(makeBox(b, true,
- () => { d.candidates.splice(i, 1); renderBoxes(d, el); }, // × reject (discard)
- () => { d.candidates.splice(i, 1); addAnnotation(d, b, el); }))); // ✓ confirm → save as annotation
+ (d.annotations || []).forEach((b, i) => {
+ if (b.auto) {
+ // auto-detected (amber): ✓ promote to confirmed, × reject
+ thumb.appendChild(makeBox(b, true,
+ () => { d.annotations.splice(i, 1); renderBoxes(d, el); saveAnnotations(d); },
+ () => { b.auto = false; renderBoxes(d, el); saveAnnotations(d); }));
+ } else {
+ // human-confirmed (red): × remove
+ thumb.appendChild(makeBox(b, false, () => removeAnnotation(d, i, el)));
+ }
+ });
}
-// Auto-detect candidate bad areas via Gemini vision → render as amber pending boxes.
+// Auto-detect + PERSIST error boxes (auto-flagged/amber) so the squares survive
+// reloads and accumulate as the learning corpus. ✓ promotes to confirmed (red).
function autoId(d, el, btn){
if (btn) { btn.classList.add('busy'); btn.textContent = '… scanning'; }
return fetch(q(`/api/admin/cactus/${d.id}/auto-id`))
.then(r => r.json())
.then(j => {
if (!j.ok) { alert('Auto-ID failed: ' + (j.error||'unknown')); return; }
- d.candidates = j.candidates || [];
+ d.annotations = j.boxes || []; // persisted merged boxes (auto + confirmed)
renderBoxes(d, el);
- if (!d.candidates.length && btn) btn.textContent = '✓ clean';
+ if (btn) btn.textContent = d.annotations.some(b => b.auto) ? '🔍 Auto-ID' : '✓ clean';
})
.catch(err => alert('Auto-ID error: ' + err.message))
- .finally(() => { if (btn && d.candidates && d.candidates.length) { btn.classList.remove('busy'); btn.textContent = '🔍 Auto-ID'; }
- else if (btn) btn.classList.remove('busy'); });
+ .finally(() => { if (btn) btn.classList.remove('busy'); });
}
// Defect-type picker shown after drawing a bad-area box (collection-aware).
const BOX_LABELS = /drunk/i.test(CATEGORY)
diff --git a/server.js b/server.js
index 8fd5d52..90afe7b 100644
--- a/server.js
+++ b/server.js
@@ -1332,7 +1332,7 @@ app.post('/api/admin/cactus/:id/annotations', express.json({ limit: '32kb' }), (
const clamp01 = n => Math.max(0, Math.min(1, Number(n) || 0));
const boxes = raw.slice(0, 50).map(b => ({
x: clamp01(b.x), y: clamp01(b.y), w: clamp01(b.w), h: clamp01(b.h),
- label: String(b.label || 'bad').slice(0, 40),
+ label: String(b.label || 'bad').slice(0, 40), auto: !!b.auto,
})).filter(b => b.w > 0.005 && b.h > 0.005);
try {
if (boxes.length === 0) {
@@ -1408,9 +1408,31 @@ app.get('/api/admin/cactus/:id/auto-id', async (req, res) => {
try { boxes = JSON.parse(j.candidates?.[0]?.content?.parts?.[0]?.text || '[]'); } catch {}
const c01 = n => Math.max(0, Math.min(1, Number(n) || 0));
const candidates = (Array.isArray(boxes) ? boxes : []).slice(0, 8)
- .map(b => ({ x: c01(b.x), y: c01(b.y), w: c01(b.w), h: c01(b.h), label: String(b.label || 'bad').slice(0, 40) }))
+ .map(b => ({ x: c01(b.x), y: c01(b.y), w: c01(b.w), h: c01(b.h), label: String(b.label || 'bad').slice(0, 40), auto: true }))
.filter(b => b.w > 0.01 && b.h > 0.01);
- res.json({ ok: true, id, candidates });
+ // PERSIST: store auto-detected boxes so the error squares survive reloads and
+ // accumulate as the learning corpus. Keep any human-confirmed boxes (auto=false),
+ // replace prior auto boxes with this fresh detection.
+ let merged = candidates;
+ try {
+ const exRaw = psqlQuery(`SELECT boxes FROM bad_examples WHERE design_id=${id};`);
+ const existing = exRaw && exRaw.trim() ? (JSON.parse(exRaw) || []) : [];
+ const confirmed = (Array.isArray(existing) ? existing : []).filter(b => !b.auto);
+ merged = confirmed.concat(candidates);
+ } catch {}
+ try {
+ const bj = JSON.stringify(merged).replace(/'/g, "''");
+ psqlExecLocal(
+`INSERT INTO bad_examples (design_id, defect_tags, category, prompt, negative_prompt, seed, generator,
+ width_in, height_in, dominant_hex, parent_design_id, local_path, boxes, source, design_created_at)
+ SELECT s.id, ARRAY['auto_flagged']::text[], s.category, s.prompt, s.negative_prompt, s.seed, s.generator,
+ s.width_in, s.height_in, s.dominant_hex, s.parent_design_id, s.local_path, '${bj}'::jsonb, 'auto-id', s.created_at
+ FROM spoon_all_designs s WHERE s.id=${id}
+ ON CONFLICT (design_id) DO UPDATE SET boxes='${bj}'::jsonb,
+ defect_tags=(SELECT array_agg(DISTINCT t) FROM unnest(bad_examples.defect_tags || ARRAY['auto_flagged']) t),
+ recorded_at=now();`);
+ } catch (e) { console.warn('[auto-id] persist failed:', e.message); }
+ res.json({ ok: true, id, candidates, boxes: merged });
} catch (e) { res.status(500).json({ ok: false, error: e.message }); }
});
← 9cc30c0 curator: collapse per-card action buttons by default (compac
·
back to Wallco Ai
·
drunk-animals: scrub 'label'/'labeled' positive cues from bo 40ef64b →