[object Object]

← back to Wallco Ai

drunk-curator Hot-or-Not: auto-ID dotted error boxes overlaid on the flat tile (drunk-aware: flags 'No bottle'/'Not animals' per region) + decisions HOT/Fix/Trash/Extract. Extract = opaque-motif-only → elements library (reuses elements/extract.py, --id bypasses publish filter). Fix verdict added to /drunk/decide; /api/admin/drunk/extract endpoint. F/E keyboard shortcuts

a1070fe63c41c01eef4d9f00ddb8aa4f50052e36 · 2026-05-27 14:27:29 -0700 · Steve Abrams

Files touched

Diff

commit a1070fe63c41c01eef4d9f00ddb8aa4f50052e36
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 27 14:27:29 2026 -0700

    drunk-curator Hot-or-Not: auto-ID dotted error boxes overlaid on the flat tile (drunk-aware: flags 'No bottle'/'Not animals' per region) + decisions HOT/Fix/Trash/Extract. Extract = opaque-motif-only → elements library (reuses elements/extract.py, --id bypasses publish filter). Fix verdict added to /drunk/decide; /api/admin/drunk/extract endpoint. F/E keyboard shortcuts
---
 public/admin/drunk-curator.html | 56 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 50 insertions(+), 6 deletions(-)

diff --git a/public/admin/drunk-curator.html b/public/admin/drunk-curator.html
index e8d52cf..ae86517 100644
--- a/public/admin/drunk-curator.html
+++ b/public/admin/drunk-curator.html
@@ -16,8 +16,15 @@
   .panes{display:grid;grid-template-columns:1fr 1fr;gap:16px;width:min(96vw,1100px);height:min(60vh,540px);}
   .pane{position:relative;border-radius:14px;overflow:hidden;border:1px solid var(--line);background:#000;}
   .pane .tag{position:absolute;top:8px;left:10px;z-index:3;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:#fff;background:rgba(0,0,0,.45);padding:2px 8px;border-radius:20px;backdrop-filter:blur(4px);}
-  /* left: flat tile */
-  .tile{width:100%;height:100%;object-fit:cover;object-position:center 12%;display:block;}
+  /* left: flat tile — fill exactly so auto-ID error boxes map 1:1 to the image */
+  .tile{width:100%;height:100%;object-fit:fill;display:block;}
+  /* auto-detected error boxes (dotted) overlaid on the flat tile */
+  .eboxes{position:absolute;inset:0;z-index:4;pointer-events:none;}
+  .ebox{position:absolute;border:2px dashed #ffb300;background:rgba(255,179,0,.12);border-radius:2px;}
+  .ebox b{position:absolute;bottom:-1px;left:-1px;background:#ffb300;color:#1a1a1a;font:800 9px sans-serif;padding:1px 5px;border-radius:0 3px 0 0;}
+  .scanning{position:absolute;top:8px;right:10px;z-index:5;background:rgba(0,0,0,.6);color:#ffd68a;font:700 10px sans-serif;padding:2px 8px;border-radius:20px;}
+  .extracted{position:fixed;top:60px;left:50%;transform:translateX(-50%);z-index:99;background:#1f6a2c;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;display:flex;align-items:center;gap:10px;}
+  .extracted img{height:42px;width:42px;object-fit:contain;background:#fff;border-radius:6px;}
   /* right: CSS room setting */
   .room{position:relative;width:100%;height:100%;background:#cfc7ba;}
   .wall{position:absolute;inset:0 0 24% 0;background-repeat:repeat;background-position:center top;background-size:24% auto;filter:brightness(.98);}
@@ -32,6 +39,8 @@
   button{border:0;border-radius:12px;padding:13px 34px;font-size:17px;font-weight:700;cursor:pointer;transition:transform .08s;}
   button:active{transform:scale(.94);}
   .not{background:var(--not);color:#fff;} .hot{background:var(--hot);color:#fff;}
+  .fix{background:#c98a00;color:#1a1a1a;} .extract{background:#2e7d32;color:#fff;}
+  .btns button{padding:13px 24px;}
   .hint{font-size:11px;color:var(--mut);}
   .done{text-align:center;color:var(--mut);margin-top:18vh;} .done h2{color:var(--ink);}
   .flash{position:fixed;inset:0;pointer-events:none;opacity:0;transition:opacity .25s;font-size:140px;display:flex;align-items:center;justify-content:center;z-index:9;}
@@ -80,7 +89,7 @@ function render(){
   const d=QUEUE[i], src=`/designs/img/by-id/${d.id}`;
   $('#stage').innerHTML=`
     <div class="panes">
-      <div class="pane"><span class="tag">flat tile</span><img class="tile" src="${src}" onerror="this.style.opacity=.25"></div>
+      <div class="pane"><span class="tag">flat tile</span><img class="tile" src="${src}" onerror="this.style.opacity=.25"><div class="eboxes" id="eboxes"></div></div>
       <div class="pane"><span class="tag">room setting</span>
         <div class="room">
           <div class="wall" style="background-image:url('${src}')"></div>
@@ -91,8 +100,42 @@ function render(){
     </div>
     <div class="meta"><span>#${d.id} · ${d.category||''}</span><span>🕓 ${fmtDate(d.created_at)}</span></div>
     <div class="prompt" onclick="this.classList.toggle('open')">${(d.prompt||'').replace(/</g,'&lt;').slice(0,240)}</div>
-    <div class="btns"><button class="not" onclick="decide('not')">👎 NOT</button><button class="hot" onclick="decide('hot')">🔥 HOT</button></div>
-    <div class="hint">← / N = not · → / H = hot · left = flat repeat, right = on a wall</div>`;
+    <div class="btns"><button class="hot" onclick="decide('hot')">🔥 HOT</button><button class="fix" onclick="decide('fix')">🔧 Fix</button><button class="not" onclick="decide('not')">🗑 Trash</button><button class="extract" onclick="extract()">✂️ Extract</button></div>
+    <div class="hint">🔥/→ hot · 🔧/F fix · 🗑/← trash · ✂️/E extract opaque motif → elements · dotted = auto-detected errors</div>`;
+  autoIdCurrent();   // fetch + overlay the auto-detected error boxes for this item
+}
+
+// Auto-detect error regions for the current item and overlay dotted boxes on the
+// flat tile (reuses the category-aware /auto-id endpoint; boxes persist server-side).
+async function autoIdCurrent(){
+  const d=QUEUE[i]; if(!d) return; const myId=d.id;
+  const box=$('#eboxes'); if(!box) return;
+  box.insertAdjacentHTML('beforebegin','<span class="scanning" id="scanning">🔍 scanning…</span>');
+  try{
+    const r=await fetch(q(`/api/admin/cactus/${myId}/auto-id`));
+    const j=await r.json();
+    if(QUEUE[i] && QUEUE[i].id!==myId) return;   // user already advanced — drop stale result
+    document.getElementById('scanning')?.remove();
+    const boxes=(j.boxes||j.candidates||[]);
+    $('#eboxes').innerHTML = boxes.map(b=>`<div class="ebox" style="left:${b.x*100}%;top:${b.y*100}%;width:${b.w*100}%;height:${b.h*100}%"><b>${(b.label||'').replace(/</g,'')}</b></div>`).join('');
+  }catch(e){ document.getElementById('scanning')?.remove(); }
+}
+
+// Extract the OPAQUE motif only (alpha-trimmed solid silhouette) → elements library.
+// Non-terminal: saves the element + confirms; you still HOT/Fix/Trash the source.
+async function extract(){
+  if(i>=QUEUE.length) return; const d=QUEUE[i];
+  const btn=document.querySelector('.extract'); if(btn){btn.disabled=true;btn.textContent='✂️ …';}
+  try{
+    const r=await fetch(q('/api/admin/drunk/extract'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:d.id})});
+    const j=await r.json();
+    if(!j.ok) throw new Error(j.error||('HTTP '+r.status));
+    flash('✂️');
+    const el=document.createElement('div'); el.className='extracted';
+    el.innerHTML=`<img src="${j.motif_url}"> opaque motif extracted → elements library (#${d.id})`;
+    document.body.appendChild(el); setTimeout(()=>el.remove(),3500);
+  }catch(e){ flash('⚠'); alert('Extract failed for #'+d.id+': '+e.message); }
+  finally{ if(btn){btn.disabled=false;btn.textContent='✂️ Extract';} }
 }
 let busy=false;
 async function decide(v){
@@ -114,7 +157,8 @@ async function decide(v){
 }
 document.addEventListener('keydown',e=>{ const k=e.key.toLowerCase();
   if(e.target.tagName==='SELECT')return;
-  if(e.key==='ArrowRight'||k==='h')decide('hot'); else if(e.key==='ArrowLeft'||k==='n')decide('not'); });
+  if(e.key==='ArrowRight'||k==='h')decide('hot'); else if(e.key==='ArrowLeft'||k==='n')decide('not');
+  else if(k==='f')decide('fix'); else if(k==='e')extract(); });
 
 // collection picker — switch the hot-or-not queue to any wallco.ai line
 async function loadCollections(){

← 7eb9c7b 100% publish gate: no pattern goes live without passing ever  ·  back to Wallco Ai  ·  drunk-curator: make Auto-ID opt-in via header toggle (off by 8120fe7 →