Spaces:
Sleeping
Sleeping
File size: 7,643 Bytes
a9d7edb b74e85b a9d7edb b74e85b a9d7edb edb73aa a9d7edb edb73aa a9d7edb edb73aa a9d7edb b74e85b a9d7edb b74e85b a9d7edb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Image preference study</title>
<style>
:root { --bg:#0f1115; --fg:#e8e8ea; --mut:#9aa0a6; --acc:#4f8cff; --card:#1a1d23; --line:#262a31; }
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--fg); font:16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif; }
.wrap { max-width:1000px; margin:0 auto; padding:20px 14px 72px; }
h1 { font-size:22px; margin:0 0 8px; } .mut { color:var(--mut); } .center { text-align:center; }
.card { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:20px; margin-top:16px; }
input[type=email] { width:100%; padding:12px; border-radius:8px; border:1px solid #333; background:#0c0e12; color:var(--fg); font-size:16px; }
button.primary { cursor:pointer; border:0; border-radius:8px; padding:11px 16px; font-size:15px; font-weight:600; background:var(--acc); color:#fff; }
button.primary:disabled { opacity:.4; cursor:default; }
.bar { height:6px; background:var(--line); border-radius:6px; overflow:hidden; margin:8px 0; }
.bar > div { height:100%; background:var(--acc); width:0; transition:width .2s; }
.prompt { text-align:center; font-size:18px; margin:6px 0 6px; }
.hint { text-align:center; color:var(--mut); margin:0 0 14px; font-size:14px; }
.pair { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
.opt { background:#0c0e12; border:2px solid var(--line); border-radius:12px; padding:8px; text-align:center; cursor:pointer; transition:border-color .1s, transform .05s; }
.opt:hover { border-color:var(--acc); } .opt:active { transform:scale(.99); }
.opt img { width:100%; border-radius:8px; display:block; }
.opt .tag { margin-top:6px; font-weight:700; color:var(--mut); letter-spacing:.04em; font-size:13px; }
.hide { display:none; } .err { color:#ff7a7a; margin-top:10px; }
table { width:100%; border-collapse:collapse; margin-top:10px; font-size:15px; }
th,td { text-align:left; padding:6px; border-bottom:1px solid var(--line); }
@media (max-width:560px){ .pair{ grid-template-columns:1fr; } }
</style>
</head>
<body>
<div class="wrap">
<h1>Image preference study</h1>
<div id="intro" class="card">
<p>Thank you for helping! You'll see <b>pairs of AI-generated images</b> made from the same text prompt.
For each pair, just <b>click the image you prefer</b> (better overall — more appealing and faithful to the prompt).</p>
<p class="mut">About <span id="npairs">…</span> quick clicks, ~<span id="mins">…</span> min. Your email is only used so each person answers once; it is not shared or published.</p>
<p><input id="email" type="email" placeholder="you@example.com" autocomplete="email"></p>
<p><label style="cursor:pointer"><input type="checkbox" id="optrec" checked style="vertical-align:middle"> Allow my anonymized responses to be recorded for research.</label></p>
<p><button class="primary" id="start">Start</button></p>
<div id="introErr" class="err"></div>
</div>
<div id="study" class="card hide">
<div class="mut"><span id="pos">1</span> / <span id="total">0</span></div>
<div class="bar"><div id="prog"></div></div>
<div class="prompt">“<span id="prompt"></span>”</div>
<p class="hint">Click the image you prefer — or “Cannot decide” if they look equally good.</p>
<div class="pair">
<div class="opt" id="optL"><img id="imgL"><div class="tag">IMAGE 1</div></div>
<div class="opt" id="optR"><img id="imgR"><div class="tag">IMAGE 2</div></div>
</div>
<div class="center" style="margin-top:14px">
<button class="primary" id="optC" style="background:#3a3f48">Cannot decide</button>
</div>
</div>
<div id="done" class="card hide">
<h1 class="center">All done — thank you! 🎉</h1>
<p id="recnote" class="mut center"></p>
<div id="myresults"></div>
<p class="mut center" style="margin-top:16px">You may close this tab.</p>
</div>
</div>
<script>
let token=null, pairs=[], i=0, votes=[], busy=false;
const $ = id => document.getElementById(id);
async function start(){
$("introErr").textContent=""; $("start").disabled=true;
try{
const r = await fetch("/api/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:$("email").value, record:$("optrec").checked})});
const d = await r.json();
if(!r.ok){ $("introErr").textContent=d.detail||"Could not start."; $("start").disabled=false; return; }
token=d.token; pairs=d.pairs; i=0; votes=[];
$("total").textContent=pairs.length;
$("intro").classList.add("hide"); $("study").classList.remove("hide");
render();
}catch(e){ $("introErr").textContent="Network error."; $("start").disabled=false; }
}
function render(){
const p=pairs[i];
$("pos").textContent=i+1; $("prog").style.width=(100*i/pairs.length)+"%";
$("prompt").textContent=p.prompt; $("imgL").src=p.left; $("imgR").src=p.right;
window.scrollTo(0,0);
}
async function choose(side){
if(busy) return;
votes.push({pair_id:pairs[i].pair_id, choice:side});
i++;
if(i<pairs.length){ render(); return; }
busy=true; $("prog").style.width="100%";
let data;
try{
const r = await fetch("/api/submit",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,votes})});
data = await r.json();
if(!r.ok){ alert(data.detail||"Submit failed"); i--; votes.pop(); busy=false; return; }
}catch(e){ alert("Network error on submit; please retry."); i--; votes.pop(); busy=false; return; }
$("recnote").textContent = data.recorded
? "Your anonymized responses have been recorded — thank you for contributing!"
: "Per your choice, your responses were NOT recorded. Here is your personal summary anyway:";
renderResults(data.results);
$("study").classList.add("hide"); $("done").classList.remove("hide");
}
const STYLE_LBL = {photo:"Photo", anime:"Anime", "concept-art":"Concept art", paintings:"Paintings"};
function renderResults(res){
const p=res.pref, pct=x=> x==null?"—":Math.round(100*x)+"%";
const ks=Object.keys(p.by_style);
let rows="";
for(const s of ks){
const ps=p.by_style[s]||{};
rows+=`<tr><td>${STYLE_LBL[s]||s}</td><td>${pct(ps.win_rate)}</td><td>${pct(ps.tie_rate)}</td><td>${pct(ps.lose_rate)}</td></tr>`;
}
const table = ks.length>1 ? `<table><tr class="mut"><th>Style</th><th>Ours</th><th>No pref.</th><th>Other</th></tr>${rows}</table>` : "";
$("myresults").innerHTML = `
<p class="center">Over your <b>${p.n}</b> comparisons: you preferred <b>ours ${pct(p.win_rate)}</b>,
had <b>no preference ${pct(p.tie_rate)}</b>, and preferred <b>the other ${pct(p.lose_rate)}</b>
<span class="mut">(the method identity was hidden).</span></p>${table}`;
}
fetch("/api/info").then(r=>r.json()).then(d=>{ $("npairs").textContent=d.n_pairs; $("mins").textContent=d.est_minutes; }).catch(()=>{ $("npairs").textContent="a few dozen"; });
$("start").onclick=start;
$("email").addEventListener("keydown",e=>{ if(e.key==="Enter") start(); });
$("optL").onclick=()=>choose("left");
$("optR").onclick=()=>choose("right");
$("optC").onclick=()=>choose("cannot");
document.addEventListener("keydown",e=>{
if($("study").classList.contains("hide")) return;
if(e.key==="ArrowLeft"||e.key==="a"||e.key==="A") choose("left");
if(e.key==="ArrowRight"||e.key==="b"||e.key==="B") choose("right");
if(e.key==="c"||e.key==="C"||e.key==="ArrowDown"||e.key===" ") { e.preventDefault(); choose("cannot"); }
});
</script>
</body>
</html>
|