Skip to content

Commit 77b3692

Browse files
committed
Improve frontend usability and replay clarity
1 parent 667f2df commit 77b3692

6 files changed

Lines changed: 258 additions & 37 deletions

File tree

app/frontend/static/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ if (nextRoundButton) {
312312
await pollJob(job.status_url, {
313313
onProgress: (snapshot) => {
314314
setStatus(snapshot.status_message);
315-
setProgress(snapshot.progress, snapshot.status_message || "Generating next round");
315+
setProgress(snapshot.progress, snapshot.status_message || "Generating next round");
316316
},
317317
});
318318
setStatus("Round generated. Refreshing session view...");
@@ -358,7 +358,7 @@ if (submitFeedbackButton) {
358358
await pollJob(job.status_url, {
359359
onProgress: (snapshot) => {
360360
setStatus(snapshot.status_message);
361-
setProgress(snapshot.progress, snapshot.status_message || "Applying feedback");
361+
setProgress(snapshot.progress, snapshot.status_message || "Applying feedback");
362362
},
363363
});
364364
setStatus("Feedback submitted. Refreshing session view...");

app/frontend/static/styles.css

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ h1, h2, h3, p { margin-top: 0; }
4444
.card { background: var(--paper); border: 1px solid var(--line); border-radius: 20px; padding: 22px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(31,26,23,0.05); }
4545
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
4646
.actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 16px; }
47+
.setup-steps {
48+
display: grid;
49+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
50+
gap: 12px;
51+
margin-top: 18px;
52+
}
53+
.mini-step {
54+
padding: 12px 14px;
55+
border-radius: 16px;
56+
border: 1px solid var(--line);
57+
background: rgba(255,255,255,0.55);
58+
display: grid;
59+
gap: 6px;
60+
}
61+
.mini-step span {
62+
color: var(--muted);
63+
font-size: 0.95rem;
64+
}
4765
.button {
4866
display: inline-flex;
4967
align-items: center;
@@ -65,6 +83,37 @@ h1, h2, h3, p { margin-top: 0; }
6583
.table th, .table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); }
6684
.form-grid { display: grid; gap: 14px; }
6785
.form-grid label { display: grid; gap: 8px; font-weight: 600; }
86+
.field-label {
87+
display: inline-flex;
88+
align-items: center;
89+
gap: 8px;
90+
}
91+
.soft-callout {
92+
padding: 14px 16px;
93+
border-radius: 16px;
94+
border: 1px solid var(--line);
95+
background: linear-gradient(180deg, #fffdf8, #f7f0e4);
96+
}
97+
.soft-callout p:last-child {
98+
margin-bottom: 0;
99+
}
100+
.summary-grid {
101+
display: grid;
102+
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
103+
gap: 12px;
104+
}
105+
.summary-item {
106+
padding: 12px 14px;
107+
border-radius: 16px;
108+
border: 1px solid var(--line);
109+
background: white;
110+
display: grid;
111+
gap: 6px;
112+
}
113+
.summary-label {
114+
color: var(--muted);
115+
font-size: 0.92rem;
116+
}
68117
input, select, textarea {
69118
width: 100%;
70119
padding: 10px 12px;
@@ -84,6 +133,19 @@ textarea { min-height: 100px; resize: vertical; }
84133
.image-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
85134
.image-card { background: white; border: 1px solid var(--line); border-radius: 16px; padding: 12px; }
86135
.image-card img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 12px; border: 1px solid var(--line); }
136+
.candidate-subtitle {
137+
color: var(--muted);
138+
font-size: 0.88rem;
139+
margin-bottom: 8px;
140+
word-break: break-all;
141+
}
142+
.candidate-meta {
143+
display: grid;
144+
gap: 6px;
145+
}
146+
.candidate-meta p {
147+
margin-bottom: 0;
148+
}
87149
.candidate-controls {
88150
display: grid;
89151
gap: 8px;
@@ -147,6 +209,15 @@ textarea { min-height: 100px; resize: vertical; }
147209
}
148210
.compact-card p { margin-bottom: 6px; }
149211
.code-block { background: #f8f4ed; padding: 12px; border-radius: 12px; overflow: auto; }
212+
.details-block {
213+
margin-top: 14px;
214+
}
215+
.details-block summary {
216+
cursor: pointer;
217+
font-weight: 600;
218+
color: var(--accent);
219+
margin-bottom: 10px;
220+
}
150221
code { background: #f2ebdf; padding: 2px 6px; border-radius: 8px; }
151222
.trace-card { background: linear-gradient(180deg, #fffaf2, #f4efe7); }
152223
.trace-log {
@@ -207,6 +278,70 @@ code { background: #f2ebdf; padding: 2px 6px; border-radius: 8px; }
207278
.hint {
208279
color: var(--muted);
209280
}
281+
.help-tip {
282+
position: relative;
283+
display: inline-flex;
284+
align-items: center;
285+
justify-content: center;
286+
width: 20px;
287+
height: 20px;
288+
border-radius: 999px;
289+
border: 1px solid var(--line);
290+
background: #fff8ef;
291+
color: var(--accent-2);
292+
font: 700 0.8rem/1 Georgia, "Times New Roman", serif;
293+
cursor: help;
294+
}
295+
.help-tip.inline {
296+
vertical-align: middle;
297+
}
298+
.help-tip::after {
299+
content: attr(data-tooltip);
300+
position: absolute;
301+
left: 50%;
302+
bottom: calc(100% + 10px);
303+
transform: translateX(-50%);
304+
width: min(280px, 70vw);
305+
padding: 10px 12px;
306+
border-radius: 12px;
307+
border: 1px solid var(--line);
308+
background: #1f1a17;
309+
color: #fffaf2;
310+
box-shadow: 0 12px 28px rgba(31, 26, 23, 0.2);
311+
font: 400 0.86rem/1.35 Georgia, "Times New Roman", serif;
312+
text-align: left;
313+
opacity: 0;
314+
pointer-events: none;
315+
transition: opacity 120ms ease, transform 120ms ease;
316+
z-index: 20;
317+
}
318+
.help-tip::before {
319+
content: "";
320+
position: absolute;
321+
left: 50%;
322+
bottom: calc(100% + 2px);
323+
transform: translateX(-50%);
324+
border-width: 8px 7px 0 7px;
325+
border-style: solid;
326+
border-color: #1f1a17 transparent transparent transparent;
327+
opacity: 0;
328+
transition: opacity 120ms ease;
329+
z-index: 21;
330+
}
331+
.help-tip:hover::after,
332+
.help-tip:hover::before,
333+
.help-tip:focus-visible::after,
334+
.help-tip:focus-visible::before {
335+
opacity: 1;
336+
}
337+
.help-tip:hover::after,
338+
.help-tip:focus-visible::after {
339+
transform: translateX(-50%) translateY(-2px);
340+
}
341+
.help-tip:focus-visible {
342+
outline: 2px solid var(--accent);
343+
outline-offset: 2px;
344+
}
210345

211346
@media (max-width: 700px) {
212347
.page { padding: 20px 14px 32px; }
@@ -215,4 +350,20 @@ code { background: #f2ebdf; padding: 2px 6px; border-radius: 8px; }
215350
width: 36px;
216351
height: 36px;
217352
}
353+
.help-tip::after {
354+
left: auto;
355+
right: 0;
356+
bottom: calc(100% + 10px);
357+
transform: none;
358+
width: min(240px, 78vw);
359+
}
360+
.help-tip:hover::after,
361+
.help-tip:focus-visible::after {
362+
transform: translateY(-2px);
363+
}
364+
.help-tip::before {
365+
left: auto;
366+
right: 10px;
367+
transform: none;
368+
}
218369
}

app/frontend/templates/replay.html

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,36 @@ <h1>{{ session.prompt }}</h1>
2121
<section class="card">
2222
<div class="section-head">
2323
<h2>Round {{ round.round_index }}</h2>
24-
<span>{{ round.render_status }}</span>
24+
<span>{{ round.render_status }} · {{ round.candidates|length }} candidates</span>
25+
</div>
26+
<div class="summary-grid">
27+
<div class="summary-item">
28+
<span class="summary-label">Winner</span>
29+
<strong>{{ round.update_summary.winner_candidate_id if round.update_summary else "Not selected yet" }}</strong>
30+
</div>
31+
<div class="summary-item">
32+
<span class="summary-label">Feedback events</span>
33+
<strong>{{ round.feedback_events|length }}</strong>
34+
</div>
35+
<div class="summary-item">
36+
<span class="summary-label">Seed policy</span>
37+
<strong>{{ round.seed_policy }}</strong>
38+
</div>
2539
</div>
2640
<div class="image-grid">
2741
{% for candidate in round.candidates %}
2842
<article class="image-card compact-card">
2943
<img src="{{ candidate.image_path }}" alt="Candidate {{ candidate.candidate_index }}">
30-
<p>{{ candidate.id }}</p>
31-
<p>{{ candidate.sampler_role }}</p>
44+
<h3>Candidate {{ candidate.candidate_index + 1 }}</h3>
45+
<p class="candidate-subtitle">{{ candidate.id }}</p>
46+
<p><strong>{{ candidate.sampler_role }}</strong></p>
3247
</article>
3348
{% endfor %}
3449
</div>
35-
<pre class="code-block">{{ round.update_summary }}</pre>
50+
<details class="details-block">
51+
<summary>Round update summary</summary>
52+
<pre class="code-block">{{ round.update_summary | tojson(indent=2) }}</pre>
53+
</details>
3654
</section>
3755
{% else %}
3856
<section class="card"><p>No rounds recorded yet.</p></section>

app/frontend/templates/session.html

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
<h1>{{ session.prompt }}</h1>
1414
<p class="lede">Sampler: {{ session.config.sampler }} | Updater: {{ session.config.updater }} | Feedback: {{ session.config.feedback_mode }}</p>
1515
<div class="badge-row">
16-
<span class="status-badge">Backend: {{ runtime_diagnostics.backend }}</span>
17-
<span class="status-badge">Device: {{ runtime_diagnostics.active_device or runtime_diagnostics.configured_device or "n/a" }}</span>
18-
<span class="status-badge">CUDA: {{ runtime_diagnostics.cuda_available }}</span>
16+
<span class="status-badge" title="The resolved generation backend for this session.">Backend: {{ runtime_diagnostics.backend }}</span>
17+
<span class="status-badge" title="The device currently used for inference.">Device: {{ runtime_diagnostics.active_device or runtime_diagnostics.configured_device or "n/a" }}</span>
18+
<span class="status-badge" title="Whether CUDA is available to the runtime on this machine.">CUDA: {{ runtime_diagnostics.cuda_available }}</span>
1919
</div>
2020
<div class="actions">
21-
<button class="button" data-session-id="{{ session.id }}" id="next-round-button" {% if session.status == "awaiting_feedback" %}disabled{% endif %}>Generate next round</button>
22-
<a class="button secondary" href="/sessions/{{ session.id }}/replay-view">Open replay</a>
23-
<a class="button secondary" href="/sessions/{{ session.id }}/trace-report">Open trace report</a>
21+
<button class="button" data-session-id="{{ session.id }}" id="next-round-button" title="Generate the next candidate batch. In round 1 this includes the raw prompt baseline; later rounds include the previous winner." {% if session.status == "awaiting_feedback" %}disabled{% endif %}>{% if session.current_round == 0 %}Generate first round{% else %}Generate next round{% endif %}</button>
22+
<a class="button secondary" href="/sessions/{{ session.id }}/replay-view" title="Inspect persisted rounds, winners, and update summaries for this session.">Open replay</a>
23+
<a class="button secondary" href="/sessions/{{ session.id }}/trace-report" title="Open the backend-saved HTML run report with images, events, and preference history.">Open trace report</a>
2424
</div>
2525
</section>
2626

@@ -39,9 +39,21 @@ <h2>Current state</h2>
3939
<div id="progress-bar" class="progress-bar" style="width: 0%"></div>
4040
</div>
4141
</div>
42-
<p>Current steering vector: <code>{{ session.current_z }}</code></p>
43-
<p>Status: <strong>{{ session.status }}</strong></p>
44-
<p>Feedback mode: <strong>{{ session.config.feedback_mode }}</strong></p>
42+
<div class="summary-grid">
43+
<div class="summary-item">
44+
<span class="summary-label">Session status <span class="help-tip inline" tabindex="0" role="note" aria-label="Session status help" data-tooltip="Ready means you can generate a round. Awaiting feedback means the current round must be reviewed before continuing.">?</span></span>
45+
<strong>{% if session.status == "awaiting_feedback" %}Waiting for your feedback{% elif session.current_round == 0 %}Ready to start{% else %}Ready for another round{% endif %}</strong>
46+
</div>
47+
<div class="summary-item">
48+
<span class="summary-label">Feedback mode <span class="help-tip inline" tabindex="0" role="note" aria-label="Feedback mode help" data-tooltip="This controls which feedback widget appears on each candidate card and how your selection is interpreted.">?</span></span>
49+
<strong>{{ session.config.feedback_mode }}</strong>
50+
</div>
51+
<div class="summary-item">
52+
<span class="summary-label">Steering vector <span class="help-tip inline" tabindex="0" role="note" aria-label="Steering vector help" data-tooltip="This is the current low-dimensional steering state that the updater moves after each round of feedback.">?</span></span>
53+
<code>{{ session.current_z }}</code>
54+
</div>
55+
</div>
56+
<p class="hint">{% if session.current_round == 0 %}Start by generating the first round. You will see one raw prompt baseline plus additional sampled variants.{% elif session.status == "awaiting_feedback" %}Review this round and submit feedback to unlock the next one.{% else %}Feedback is complete for the current round. You can continue exploring or open replay to review the history.{% endif %}</p>
4557
</section>
4658

4759
<section class="card" id="round-container">
@@ -54,9 +66,12 @@ <h2>Round {{ current_round.round_index }}</h2>
5466
{% for candidate in current_round.candidates %}
5567
<article class="image-card">
5668
<img src="{{ candidate.image_path }}" alt="Candidate {{ candidate.candidate_index }}">
57-
<h3>{{ candidate.id }}</h3>
58-
<p>Role: {{ candidate.sampler_role }}</p>
59-
<p>z: {{ candidate.z }}</p>
69+
<h3>Candidate {{ candidate.candidate_index + 1 }}</h3>
70+
<p class="candidate-subtitle">{{ candidate.id }}</p>
71+
<div class="candidate-meta">
72+
<p>Role <span class="help-tip inline" tabindex="0" role="note" aria-label="Candidate role help" data-tooltip="Baseline is the raw prompt, incumbent is the previous winner, and sampler roles describe how other candidates were proposed.">?</span>: <strong>{{ candidate.sampler_role }}</strong></p>
73+
<p>Steering <span class="help-tip inline" tabindex="0" role="note" aria-label="Candidate steering vector help" data-tooltip="This candidate's proposed steering coordinates for the current round.">?</span>: <code>{{ candidate.z }}</code></p>
74+
</div>
6075
<div class="candidate-controls" data-feedback-mode="{{ session.config.feedback_mode }}">
6176
{% if session.config.feedback_mode == "scalar_rating" %}
6277
<div class="rating-widget" data-candidate-id="{{ candidate.id }}">
@@ -124,13 +139,19 @@ <h3>{{ candidate.id }}</h3>
124139
{% endif %}
125140
</p>
126141
<div class="actions">
127-
<button class="button" id="submit-feedback-button" data-round-id="{{ current_round.id }}" data-feedback-mode="{{ session.config.feedback_mode }}">Submit feedback</button>
142+
<button class="button" id="submit-feedback-button" data-round-id="{{ current_round.id }}" data-feedback-mode="{{ session.config.feedback_mode }}" title="Submit your current preference selections and update the session model.">Submit feedback</button>
128143
</div>
129144
{% else %}
130-
<p class="hint">Feedback has already been submitted for this round. Generate the next round to continue.</p>
145+
<div class="soft-callout">
146+
<strong>Feedback recorded</strong>
147+
<p>Winner: <code>{{ current_round.update_summary.winner_candidate_id }}</code>. Generate the next round to continue or open replay to inspect the update summary.</p>
148+
</div>
131149
{% endif %}
132150
{% else %}
133-
<p>No rounds yet. Generate the first round to begin.</p>
151+
<div class="soft-callout">
152+
<strong>No rounds yet</strong>
153+
<p>Generate the first round to see the raw prompt baseline and the first sampled variations side by side.</p>
154+
</div>
134155
{% endif %}
135156
</section>
136157
<section class="card trace-card">

0 commit comments

Comments
 (0)