Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>{{ index }}/{{ total }} — {{ test.name }}</title> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> | |
| </head> | |
| <body class="annotate"> | |
| <header class="topbar"> | |
| <div class="topbar-left"> | |
| <a href="{{ url_for('index') }}" class="home-link">← Home</a> | |
| <span class="test-label">Test {{ test_id }}: {{ test.name }}</span> | |
| </div> | |
| <div class="topbar-center"> | |
| <span class="progress"> | |
| <b>{{ index }}</b> / {{ total }} | |
| · <span class="muted">{{ done_count }} annotated</span> | |
| </span> | |
| </div> | |
| <div class="topbar-right"> | |
| <span class="annotator-tag">{{ annotator }}</span> | |
| <form class="jump-form" onsubmit="return jumpTo(event)"> | |
| <input type="number" id="jump" min="1" max="{{ total }}" | |
| placeholder="jump…" value="{{ index }}"> | |
| </form> | |
| </div> | |
| </header> | |
| <section class="request-box"> | |
| <h2>User request <span class="muted small">({{ item.request_id }})</span></h2> | |
| <p>{{ item.user_request }}</p> | |
| <details> | |
| <summary>metadata</summary> | |
| <ul> | |
| <li><b>page_type:</b> {{ item.metadata.page_type }}</li> | |
| <li><b>domain:</b> {{ item.metadata.domain }}</li> | |
| <li><b>purpose:</b> {{ item.metadata.purpose }}</li> | |
| <li><b>components:</b> | |
| <ul> | |
| {% for c in item.metadata.components %}<li>{{ c }}</li>{% endfor %} | |
| </ul> | |
| </li> | |
| </ul> | |
| </details> | |
| </section> | |
| <section class="pair"> | |
| <figure class="ui-card"> | |
| <figcaption>Left (A)</figcaption> | |
| <a href="{{ url_for('image', model=mapping.left_model, filename=filename) }}" | |
| target="_blank" rel="noopener"> | |
| <img src="{{ url_for('image', model=mapping.left_model, filename=filename) }}" | |
| alt="Left UI"> | |
| </a> | |
| </figure> | |
| <figure class="ui-card"> | |
| <figcaption>Right (B)</figcaption> | |
| <a href="{{ url_for('image', model=mapping.right_model, filename=filename) }}" | |
| target="_blank" rel="noopener"> | |
| <img src="{{ url_for('image', model=mapping.right_model, filename=filename) }}" | |
| alt="Right UI"> | |
| </a> | |
| </figure> | |
| </section> | |
| <section class="rating"> | |
| <h3>Which UI better matches the request?</h3> | |
| <div class="rating-buttons"> | |
| {% set labels = { | |
| 'strong_left': ('1', 'Strong Left', 'Left is clearly better'), | |
| 'weak_left': ('2', 'Weak Left', 'Left is slightly better'), | |
| 'similar': ('3', 'Similar', 'About the same'), | |
| 'weak_right': ('4', 'Weak Right', 'Right is slightly better'), | |
| 'strong_right': ('5', 'Strong Right', 'Right is clearly better') | |
| } %} | |
| {% for c in choices %} | |
| {% set key, lbl, desc = labels[c] %} | |
| <button type="button" | |
| class="choice {% if current_choice == c %}selected{% endif %}" | |
| data-choice="{{ c }}" | |
| title="{{ desc }} (key {{ key }})"> | |
| <span class="key">{{ key }}</span> | |
| <span class="lbl">{{ lbl }}</span> | |
| <span class="desc">{{ desc }}</span> | |
| </button> | |
| {% endfor %} | |
| </div> | |
| <p class="status muted" id="status"> | |
| {% if current_choice %}Saved: <b>{{ current_choice }}</b>. Pick again to overwrite.{% else %}Pick one (or press 1–5). Auto-advances on save.{% endif %} | |
| </p> | |
| </section> | |
| <nav class="navbar"> | |
| {% if index > 1 %} | |
| <a class="nav prev" href="{{ url_for('annotate', test_id=test_id, index=index-1, annotator=annotator) }}">← Prev (←)</a> | |
| {% else %} | |
| <span class="nav prev disabled">← Prev</span> | |
| {% endif %} | |
| {% if index < total %} | |
| <a class="nav next" href="{{ url_for('annotate', test_id=test_id, index=index+1, annotator=annotator) }}">Next (→) →</a> | |
| {% else %} | |
| <span class="nav next disabled">Next →</span> | |
| {% endif %} | |
| </nav> | |
| <script> | |
| const CTX = { | |
| annotator: {{ annotator|tojson }}, | |
| test_id: {{ test_id|tojson }}, | |
| index: {{ index|tojson }}, | |
| total: {{ total|tojson }}, | |
| }; | |
| function nextUrl(i) { | |
| const u = new URL(window.location.origin + "{{ url_for('annotate', test_id=test_id, index=0, annotator=annotator) }}".replace('/0', '/' + i)); | |
| return u.toString(); | |
| } | |
| async function submitChoice(choice) { | |
| const status = document.getElementById('status'); | |
| status.textContent = 'Saving…'; | |
| try { | |
| const res = await fetch("{{ url_for('submit') }}", { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ | |
| annotator: CTX.annotator, | |
| test_id: CTX.test_id, | |
| index: CTX.index, | |
| choice: choice, | |
| }), | |
| }); | |
| const data = await res.json(); | |
| if (!data.ok) throw new Error(data.error || 'save failed'); | |
| document.querySelectorAll('.choice').forEach(b => { | |
| b.classList.toggle('selected', b.dataset.choice === choice); | |
| }); | |
| if (CTX.index < CTX.total) { | |
| window.location.href = nextUrl(CTX.index + 1); | |
| } else { | |
| status.textContent = 'Saved. This was the last pair.'; | |
| } | |
| } catch (e) { | |
| status.textContent = 'Error: ' + e.message; | |
| } | |
| } | |
| document.querySelectorAll('.choice').forEach(btn => { | |
| btn.addEventListener('click', () => submitChoice(btn.dataset.choice)); | |
| }); | |
| const KEY_MAP = { | |
| '1': 'strong_left', | |
| '2': 'weak_left', | |
| '3': 'similar', | |
| '4': 'weak_right', | |
| '5': 'strong_right', | |
| }; | |
| document.addEventListener('keydown', (e) => { | |
| if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; | |
| if (KEY_MAP[e.key]) { | |
| e.preventDefault(); | |
| submitChoice(KEY_MAP[e.key]); | |
| } else if (e.key === 'ArrowLeft' && CTX.index > 1) { | |
| window.location.href = nextUrl(CTX.index - 1); | |
| } else if (e.key === 'ArrowRight' && CTX.index < CTX.total) { | |
| window.location.href = nextUrl(CTX.index + 1); | |
| } | |
| }); | |
| function jumpTo(e) { | |
| e.preventDefault(); | |
| const v = parseInt(document.getElementById('jump').value, 10); | |
| if (v >= 1 && v <= CTX.total) window.location.href = nextUrl(v); | |
| return false; | |
| } | |
| </script> | |
| </body> | |
| </html> | |