← back to Debate Ring Viewer

scripts/four-horsemen-review.sh

76 lines

#!/bin/bash
# Four-horsemen design panel review of an image (storyboard frame OR
# extracted MP4 keyframe). Each "horseman" is a different prompt sent in
# parallel to local Ollama llava — keeps it free, offline, and fast (~30s).
#   • Figma  — composition / hierarchy / alignment
#   • 21st   — UI surface critique / contrast / a11y flags
#   • Paper  — color palette / tonal balance
#   • Canva  — first-impression / marketing read
# Falls through to a stub report if Ollama is unreachable.
set -u
IMAGE="${1:?usage: four-horsemen-review.sh <image-path> <output.md>}"
OUT="${2:?usage: four-horsemen-review.sh <image-path> <output.md>}"

OLLAMA="${OLLAMA:-http://localhost:11434}"
MODEL="${VISION_MODEL:-llava:latest}"

if [ ! -s "$IMAGE" ]; then
  echo "[4hm] missing image $IMAGE" >&2; exit 2
fi

PROBE=$(curl -sf --max-time 3 "$OLLAMA/api/tags" >/dev/null 2>&1 && echo OK || echo DOWN)
B64=$(base64 -i "$IMAGE")

call_horse () {
  # $1 = prompt
  if [ "$PROBE" != "OK" ]; then
    echo '_(stub: ollama unreachable — pre/post review will run when Mac2 ollama is up)_'
    return
  fi
  local prompt="$1"
  python3 - "$prompt" "$B64" <<'PY' 2>/dev/null
import json, sys, urllib.request
prompt, b64 = sys.argv[1], sys.argv[2]
req = urllib.request.Request(
    "http://localhost:11434/api/generate",
    data=json.dumps({
        "model": "llava:latest",
        "prompt": prompt,
        "images": [b64],
        "stream": False,
        "options": {"num_predict": 220, "temperature": 0.4}
    }).encode(),
    headers={"content-type":"application/json"})
try:
    with urllib.request.urlopen(req, timeout=90) as r:
        d = json.loads(r.read().decode())
        print(d.get("response","_(no response)_").strip())
except Exception as e:
    print(f"_(error: {e})_")
PY
}

write_section () { printf '\n## %s\n\n%s\n' "$1" "$2" >> "$OUT"; }

{
  echo "# Four-horsemen design review — $(date -Iseconds)"
  echo ""
  echo "**Image:** \`$IMAGE\`  "
  echo "**Vision backend:** $OLLAMA / $MODEL — $PROBE"
} > "$OUT"

# Run the four reviewers IN PARALLEL.
T1=$(mktemp); T2=$(mktemp); T3=$(mktemp); T4=$(mktemp)
call_horse "You are a Figma layout critic. Comment briefly on this UI's composition: visual hierarchy, focal point, alignment, white-space. Give exactly 4 short bullets." > "$T1" &
call_horse "You are an accessibility / UI critic. Identify the top 3 contrast or readability concerns in this frame, and 1 thing it does well. 4 bullets." > "$T2" &
call_horse "You are a color palette analyst. Name the 5 dominant colors in this image (rough hex if you can guess, else descriptive name) and say whether the palette feels cohesive or chaotic." > "$T3" &
call_horse "You are a marketing reviewer seeing this for the first time. In 2 sentences: what is this product, and would you watch a video about it?" > "$T4" &
wait

write_section "Horseman 1 — Figma (composition)" "$(cat "$T1")"
write_section "Horseman 2 — 21st.dev (UI / a11y)" "$(cat "$T2")"
write_section "Horseman 3 — Paper (palette)"      "$(cat "$T3")"
write_section "Horseman 4 — Canva (first read)"   "$(cat "$T4")"
rm -f "$T1" "$T2" "$T3" "$T4"
echo "[4hm] wrote $OUT"