← 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
M data/chip-chats.jsonM data/moodboards.jsonM data/pairings.jsonM data/reviews.jsonM data/vision-cache.jsonA scripts/tag_motifs.py
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 →