[object Object]

← back to Wallco Ai

wallco.ai: motif tagger via llava on Mac2 — extracts 3-6 visual tags per design (peony/damask/geometric/etc) · running in background against all 55 designs · ~5 min total

1f5e6933112c7db97ae47c19c9db91eacdafe2c9 · 2026-05-11 19:04:54 -0700 · Steve

Files touched

Diff

commit 1f5e6933112c7db97ae47c19c9db91eacdafe2c9
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon May 11 19:04:54 2026 -0700

    wallco.ai: motif tagger via llava on Mac2 — extracts 3-6 visual tags per design (peony/damask/geometric/etc) · running in background against all 55 designs · ~5 min total
---
 data/chip-chats.json   | 15 +++++++-
 data/moodboards.json   |  2 +-
 data/pairings.json     | 70 +------------------------------------
 data/reviews.json      | 13 +++++--
 data/vision-cache.json |  2 +-
 scripts/tag_motifs.py  | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 123 insertions(+), 74 deletions(-)

diff --git a/data/chip-chats.json b/data/chip-chats.json
index 0967ef4..585cbe1 100644
--- a/data/chip-chats.json
+++ b/data/chip-chats.json
@@ -1 +1,14 @@
-{}
+{
+  "13:cat-floral": [
+    {
+      "role": "user",
+      "content": "What scale of room does this work in?",
+      "ts": "2026-05-12T02:04:42.341Z"
+    },
+    {
+      "role": "assistant",
+      "content": "\"Honey Garden No.13\" works well in medium to large rooms due to its balanced floral scale and warm, earthy tone (#87774c). The pattern’s moderate repetition avoids overwhelming smaller spaces but adds depth in larger areas. For smaller rooms, consider reducing the floral motif’s size or using a lighter base color to enhance openness.",
+      "ts": "2026-05-12T02:04:42.341Z"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/data/moodboards.json b/data/moodboards.json
index 0967ef4..9e26dfe 100644
--- a/data/moodboards.json
+++ b/data/moodboards.json
@@ -1 +1 @@
-{}
+{}
\ No newline at end of file
diff --git a/data/pairings.json b/data/pairings.json
index 9c98444..9e26dfe 100644
--- a/data/pairings.json
+++ b/data/pairings.json
@@ -1,69 +1 @@
-{
-  "14": {
-    "design_id": 14,
-    "vision_description": "Floral pattern with yellow flowers on a dark background; medium scale repeat; formal mood.",
-    "generated_at": "2026-05-12T01:39:04.080Z",
-    "suggestions": [
-      {
-        "type": "design",
-        "design_id": 12,
-        "label": "Honey Blossom No.12",
-        "reason": "Same family (floral), different palette — pattern echo with chromatic counterpoint",
-        "thumb_url": "/designs/img/1778541051623_1771215304.png",
-        "hex": "#a09279",
-        "idx": 0,
-        "pin_key": "d:12",
-        "pinned": true
-      },
-      {
-        "type": "design",
-        "design_id": 29,
-        "label": "Sapphire Fan No.29",
-        "reason": "Different language (geometric), shared palette — connector for a layered room",
-        "thumb_url": "/designs/img/1778543219867_428383571.png",
-        "hex": "#0b1631",
-        "idx": 1,
-        "pin_key": "d:29",
-        "pinned": false
-      },
-      {
-        "type": "color",
-        "hex": "#806224",
-        "label": "Complement · #806224",
-        "reason": "Direct chromatic opposite — trim, hardware, or a single accent piece",
-        "idx": 2,
-        "pin_key": "c:#806224",
-        "pinned": false
-      },
-      {
-        "type": "color",
-        "hex": "#342480",
-        "label": "Analogous · #342480",
-        "reason": "Neighboring hue — supporting fabric or upholstery, harmonious not loud",
-        "idx": 3,
-        "pin_key": "c:#342480",
-        "pinned": false
-      },
-      {
-        "type": "material",
-        "label": "Gold leaf accent wall",
-        "reason": "Amplifies yellow flowers' warmth; contrasts with dark base for formal elegance.",
-        "icon": "✦",
-        "vision_grounded": true,
-        "idx": 4,
-        "pin_key": "m:gold-leaf-accent-wall",
-        "pinned": false
-      },
-      {
-        "type": "material",
-        "label": "Matte black trim molding",
-        "reason": "Sharpens floral focus against deep background; adds structured sophistication.",
-        "icon": "✦",
-        "vision_grounded": true,
-        "idx": 5,
-        "pin_key": "m:matte-black-trim-molding",
-        "pinned": false
-      }
-    ]
-  }
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/data/reviews.json b/data/reviews.json
index a2bf57a..0965edb 100644
--- a/data/reviews.json
+++ b/data/reviews.json
@@ -5,7 +5,16 @@
     "color": 5,
     "style": 5,
     "decision": "keep",
-    "why": "The design is appealing but lacks vibrancy and distinctiveness in color and style, which detract from its overall impact despite its solid c",
-    "updated_at": "2026-05-12T01:54:12.033Z"
+    "why": "The design is well-executed, but the muted color palette and conventional style lack the vibrancy and originality needed for a higher rating",
+    "updated_at": "2026-05-12T02:04:05.753Z"
+  },
+  "12": {
+    "id": "12",
+    "design": 5,
+    "color": 5,
+    "style": 5,
+    "decision": "reject",
+    "why": "The design lacks visual interest and originality, resulting in a generic, uninspired floral pattern that fails to captivate or differentiate",
+    "updated_at": "2026-05-12T02:04:24.102Z"
   }
 }
\ No newline at end of file
diff --git a/data/vision-cache.json b/data/vision-cache.json
index e28a026..d1b3946 100644
--- a/data/vision-cache.json
+++ b/data/vision-cache.json
@@ -1,3 +1,3 @@
 {
-  "14": "Floral pattern with yellow flowers on a dark background; medium scale repeat; formal mood."
+  "14": "Floral pattern with yellow flowers on a dark background, large-scale repeating floral motif, formal, rich, and luxurious atmosphere."
 }
\ No newline at end of file
diff --git a/scripts/tag_motifs.py b/scripts/tag_motifs.py
new file mode 100644
index 0000000..e021c67
--- /dev/null
+++ b/scripts/tag_motifs.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+"""
+Run qwen2.5vl:7b on Mac1 against every spoon_all_designs row that's missing
+motifs[]. Extract 3-6 concrete motif tags per design (peony / damask / lattice
+/ stripe / dragon / etc.) and persist to PG.
+
+Idempotent: skips rows that already have motifs[].
+Cost: free (local Ollama). Wall time: ~10-15s per image.
+"""
+import base64, json, subprocess, time, sys
+from pathlib import Path
+import urllib.request
+
+OLLAMA = 'http://127.0.0.1:11434'   # Mac2 — Mac1's qwen2.5vl is currently saturated
+MODEL  = 'llava:latest'
+
+PROMPT = """Look at this wallpaper pattern image. Extract 3 to 6 concrete motif tags that describe its visual content.
+
+Choose from these or invent similar single-word tags:
+floral, peony, rose, leaf, vine, botanical, tropical, palm, bird, dragon, lattice, trellis,
+geometric, stripe, dot, diamond, hexagon, fan, scroll, scrollwork, damask, paisley,
+chinoiserie, toile, ikat, abstract, organic, gradient, brushstroke, ink, watercolour,
+art-deco, art-nouveau, mid-century, baroque, victorian, modern, minimal, dense, sparse.
+
+Return ONLY a JSON array of 3-6 lowercase tag strings. No commentary. No code fences.
+"""
+
+def psql(sql):
+    r = subprocess.run(['psql','dw_unified','-At','-q'], input=sql, capture_output=True, text=True)
+    if r.returncode != 0: raise RuntimeError(r.stderr)
+    return r.stdout.strip()
+
+def esc(s):
+    if s is None: return 'NULL'
+    return "'" + str(s).replace("'", "''") + "'"
+
+def tag_one(image_path):
+    img_b64 = base64.b64encode(Path(image_path).read_bytes()).decode()
+    body = json.dumps({
+        'model': MODEL,
+        'prompt': PROMPT,
+        'images': [img_b64],
+        'stream': False,
+        'options': { 'temperature': 0.2 }
+    }).encode()
+    req = urllib.request.Request(f'{OLLAMA}/api/generate',
+        data=body, headers={'Content-Type': 'application/json'})
+    with urllib.request.urlopen(req, timeout=120) as r:
+        out = json.loads(r.read())
+    text = (out.get('response') or '').strip()
+    # Try to extract a JSON array
+    import re
+    m = re.search(r'\[[^\]]+\]', text)
+    if not m: return []
+    try:
+        tags = json.loads(m.group(0))
+        if not isinstance(tags, list): return []
+        return [str(t).lower().strip()[:30] for t in tags if t][:6]
+    except Exception:
+        return []
+
+def main():
+    limit = int(sys.argv[1]) if len(sys.argv) > 1 else 20
+    rows = json.loads(psql(
+        f"SELECT COALESCE(json_agg(t),'[]'::json) FROM (SELECT id, local_path FROM spoon_all_designs "
+        f"WHERE local_path IS NOT NULL AND (motifs IS NULL OR array_length(motifs,1) IS NULL) "
+        f"AND generator='replicate' ORDER BY id LIMIT {limit}) t;") or '[]')
+    print(f'{len(rows)} designs pending motif tags')
+    sql_lines = ['BEGIN;']
+    ok = 0
+    for i, r in enumerate(rows, 1):
+        p = Path(r['local_path'])
+        if not p.exists():
+            print(f'  [{i}/{len(rows)}] missing file: {r["local_path"]}')
+            continue
+        try:
+            t0 = time.time()
+            tags = tag_one(p)
+            took = time.time() - t0
+            if tags:
+                arr = "ARRAY[" + ",".join(esc(x) for x in tags) + "]::text[]"
+                sql_lines.append(f"UPDATE spoon_all_designs SET motifs={arr} WHERE id={r['id']};")
+                print(f'  [{i:>3}/{len(rows)}] #{r["id"]:<4} ({took:.1f}s) → {tags}')
+                ok += 1
+            else:
+                print(f'  [{i:>3}/{len(rows)}] #{r["id"]:<4} no tags returned')
+        except Exception as e:
+            print(f'  [{i:>3}/{len(rows)}] #{r["id"]:<4} err: {e}')
+    sql_lines.append('COMMIT;')
+    Path('/tmp/sad_motifs.sql').write_text('\n'.join(sql_lines))
+    subprocess.run(['psql','dw_unified','-q','-f','/tmp/sad_motifs.sql'], check=True)
+    print(f'\nTagged {ok}/{len(rows)}')
+
+if __name__ == '__main__':
+    main()

← ae9a436 generator: one-step batch UI + fix /design/13 (age modal was  ·  back to Wallco Ai  ·  copyright: scrub AI vendor names + triple-layer watermark ev 1fa03aa →