Eric Xu commited on
UX copy overhaul: intuitive labels, consistent terminology, plain language
Browse filesBased on Codex UX review (gpt-5.3-codex, xhigh):
Naming:
- "Entity" → "What should the panel review?"
- "Directions for your goal" → "Test what to change next"
- "Bias Audit" → "Check panel realism"
- "Suggest" → "Auto-fill"
- "Evaluate" → "Build panel and review"
Terminology (standardized on "panel"):
- "evaluators/personas/audience" → "panel members" throughout
- "positive/neutral/negative" → "would say yes/unsure/would say no"
Academic language removed:
- "persuadable middle" → "on the fence"
- "Framing effect" → "Does wording change the score?"
- "Authority bias" → "Do trust signals change the score?"
- "Probes" → "Checks"
Progress messages rewritten around user progress, not system actions.
Error messages now tell users what to do next.
- web/static/index.html +58 -59
web/static/index.html
CHANGED
|
@@ -306,7 +306,7 @@
|
|
| 306 |
<div class="container">
|
| 307 |
<header>
|
| 308 |
<h1>Semantic Gradient Optimization</h1>
|
| 309 |
-
<p>
|
| 310 |
<div id="configBadge" class="config-badge">checking...</div>
|
| 311 |
<div id="nemotronBadge" class="config-badge" style="margin-left:8px">checking...</div>
|
| 312 |
</header>
|
|
@@ -315,15 +315,15 @@
|
|
| 315 |
<div class="step hidden" id="nemotronSetup" style="border-color:var(--yellow)">
|
| 316 |
<div class="step-header">
|
| 317 |
<div class="step-num" style="border-color:var(--yellow);color:var(--yellow)">!</div>
|
| 318 |
-
<div class="step-title">
|
| 319 |
</div>
|
| 320 |
-
<p class="step-desc">SGO
|
| 321 |
<div class="field">
|
| 322 |
-
<label>
|
| 323 |
-
<input type="text" id="nemotronPath" placeholder="">
|
| 324 |
</div>
|
| 325 |
<div class="btn-row">
|
| 326 |
-
<button onclick="setupNemotron()">
|
| 327 |
</div>
|
| 328 |
<div id="nemotronStatus" class="hidden mt-16">
|
| 329 |
<div class="progress-text" id="nemotronStatusText"></div>
|
|
@@ -334,9 +334,9 @@
|
|
| 334 |
<div class="step active" id="step1">
|
| 335 |
<div class="step-header">
|
| 336 |
<div class="step-num">1</div>
|
| 337 |
-
<div class="step-title">
|
| 338 |
</div>
|
| 339 |
-
<p class="step-desc">Paste
|
| 340 |
|
| 341 |
<div class="template-chips">
|
| 342 |
<span class="template-chip" onclick="loadTemplate('product')">Product</span>
|
|
@@ -345,42 +345,42 @@
|
|
| 345 |
</div>
|
| 346 |
|
| 347 |
<div class="field">
|
| 348 |
-
<label>
|
| 349 |
-
<textarea id="entityText" placeholder="Paste
|
| 350 |
</div>
|
| 351 |
|
| 352 |
<div class="field">
|
| 353 |
<div style="display:flex;justify-content:space-between;align-items:center">
|
| 354 |
-
<label>
|
| 355 |
-
<button class="secondary" style="padding:4px 12px;font-size:0.75rem" onclick="inferSpec()">
|
| 356 |
</div>
|
| 357 |
<input type="text" id="goalText" placeholder="e.g. 'Increase conversion rate' or 'Get more interview callbacks'">
|
| 358 |
</div>
|
| 359 |
|
| 360 |
<div class="field">
|
| 361 |
<div style="display:flex;justify-content:space-between;align-items:center">
|
| 362 |
-
<label>
|
| 363 |
-
<button class="secondary" style="padding:4px 12px;font-size:0.75rem" onclick="inferSpec()">
|
| 364 |
</div>
|
| 365 |
<input type="text" id="cohortDesc" placeholder="e.g. 'Engineering managers at mid-stage startups' or 'US consumers aged 25-45'">
|
| 366 |
</div>
|
| 367 |
|
| 368 |
<details class="mb-8">
|
| 369 |
-
<summary style="cursor:pointer;color:var(--text2);font-size:0.85rem">
|
| 370 |
<div style="padding:12px 0">
|
| 371 |
<div class="field">
|
| 372 |
-
<label>Panel size</label>
|
| 373 |
<input type="number" id="panelSize" value="30" min="5" max="80"
|
| 374 |
style="width:80px;padding:6px;text-align:center">
|
| 375 |
</div>
|
| 376 |
<label style="display:inline-flex;align-items:center;gap:8px;margin:0;font-size:0.85rem;cursor:pointer">
|
| 377 |
<input type="checkbox" id="biasCalibration" checked style="width:auto;margin:0">
|
| 378 |
-
|
| 379 |
</label>
|
| 380 |
</div>
|
| 381 |
</details>
|
| 382 |
|
| 383 |
-
<button onclick="runFullPipeline()" id="goBtn">
|
| 384 |
|
| 385 |
<div id="pipelineProgress" class="hidden mt-16">
|
| 386 |
<div class="progress-text" id="pipelineProgressText">Starting...</div>
|
|
@@ -391,17 +391,17 @@
|
|
| 391 |
<div id="evalResults" class="hidden mt-16">
|
| 392 |
<div class="score-big" id="avgScore">0<span>/10</span></div>
|
| 393 |
<div class="stats-row">
|
| 394 |
-
<div class="stat pos"><div class="stat-val" id="posCount">0</div><div class="stat-label">
|
| 395 |
-
<div class="stat neu"><div class="stat-val" id="neuCount">0</div><div class="stat-label">
|
| 396 |
-
<div class="stat neg"><div class="stat-val" id="negCount">0</div><div class="stat-label">
|
| 397 |
</div>
|
| 398 |
<details>
|
| 399 |
<summary style="cursor:pointer;color:var(--text2);font-size:0.9rem">Full analysis</summary>
|
| 400 |
<div class="results-details" id="evalAnalysis"></div>
|
| 401 |
</details>
|
| 402 |
<div class="btn-row mt-16">
|
| 403 |
-
<button onclick="runDirections()">
|
| 404 |
-
<button class="secondary" onclick="goToStep(3)">
|
| 405 |
</div>
|
| 406 |
</div>
|
| 407 |
</div>
|
|
@@ -410,9 +410,9 @@
|
|
| 410 |
<div class="step hidden" id="step2">
|
| 411 |
<div class="step-header">
|
| 412 |
<div class="step-num">2</div>
|
| 413 |
-
<div class="step-title">
|
| 414 |
</div>
|
| 415 |
-
<p class="step-desc">Testing changes
|
| 416 |
|
| 417 |
<div id="cfProgress" class="mt-16">
|
| 418 |
<div class="progress-text" id="cfProgressText">Analyzing concerns...</div>
|
|
@@ -437,30 +437,30 @@
|
|
| 437 |
<div class="step hidden" id="step3">
|
| 438 |
<div class="step-header">
|
| 439 |
<div class="step-num">3</div>
|
| 440 |
-
<div class="step-title">
|
| 441 |
</div>
|
| 442 |
<p class="step-desc">
|
| 443 |
-
|
| 444 |
-
|
| 445 |
</p>
|
| 446 |
|
| 447 |
<div class="field">
|
| 448 |
-
<label>
|
| 449 |
<div style="display:flex;gap:16px;margin-bottom:12px">
|
| 450 |
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 451 |
-
<input type="checkbox" id="probeFraming" checked>
|
| 452 |
</label>
|
| 453 |
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 454 |
-
<input type="checkbox" id="probeAuthority" checked>
|
| 455 |
</label>
|
| 456 |
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 457 |
-
<input type="checkbox" id="probeOrder" checked>
|
| 458 |
</label>
|
| 459 |
</div>
|
| 460 |
</div>
|
| 461 |
|
| 462 |
<div class="btn-row">
|
| 463 |
-
<button onclick="runBiasAudit()" id="auditBtn">Run
|
| 464 |
<div style="flex:1"></div>
|
| 465 |
<label style="display:flex;align-items:center;gap:6px;margin:0">
|
| 466 |
<span style="font-size:0.8rem;color:var(--text2)">Sample size:</span>
|
|
@@ -475,10 +475,10 @@
|
|
| 475 |
</div>
|
| 476 |
|
| 477 |
<div id="auditResults" class="hidden mt-16">
|
| 478 |
-
<h3 style="margin-bottom:12px">
|
| 479 |
<table class="gradient-table" id="auditTable">
|
| 480 |
<thead>
|
| 481 |
-
<tr><th>
|
| 482 |
</thead>
|
| 483 |
<tbody></tbody>
|
| 484 |
</table>
|
|
@@ -487,8 +487,8 @@
|
|
| 487 |
<div class="results-details" id="auditReport"></div>
|
| 488 |
</details>
|
| 489 |
<div class="btn-row mt-16">
|
| 490 |
-
<button class="secondary" onclick="rerunWithCalibration()">
|
| 491 |
-
<button class="secondary" onclick="goToStep(2)">
|
| 492 |
</div>
|
| 493 |
</div>
|
| 494 |
</div>
|
|
@@ -601,7 +601,7 @@ async function init() {
|
|
| 601 |
|
| 602 |
async function setupNemotron() {
|
| 603 |
const path = document.getElementById('nemotronPath').value.trim();
|
| 604 |
-
if (!path) return alert('
|
| 605 |
|
| 606 |
const status = document.getElementById('nemotronStatus');
|
| 607 |
const text = document.getElementById('nemotronStatusText');
|
|
@@ -695,7 +695,7 @@ async function inferSpec() {
|
|
| 695 |
|
| 696 |
async function runFullPipeline() {
|
| 697 |
const text = document.getElementById('entityText').value.trim();
|
| 698 |
-
if (!text) return alert('
|
| 699 |
|
| 700 |
const btn = document.getElementById('goBtn');
|
| 701 |
btn.disabled = true;
|
|
@@ -710,8 +710,8 @@ async function runFullPipeline() {
|
|
| 710 |
const goalField = document.getElementById('goalText');
|
| 711 |
const audienceField = document.getElementById('cohortDesc');
|
| 712 |
if (!goalField.value.trim() || !audienceField.value.trim()) {
|
| 713 |
-
document.getElementById('pipelineProgressText').textContent = '
|
| 714 |
-
logStep('
|
| 715 |
await inferSpec();
|
| 716 |
if (goalField.value) logStep(`Goal: ${goalField.value}`, 'pos');
|
| 717 |
if (audienceField.value) logStep(`Audience: ${audienceField.value}`, 'pos');
|
|
@@ -723,8 +723,7 @@ async function runFullPipeline() {
|
|
| 723 |
|
| 724 |
try {
|
| 725 |
// Phase 1: Create session
|
| 726 |
-
document.getElementById('pipelineProgressText').textContent = '
|
| 727 |
-
logStep('Creating session...');
|
| 728 |
const sessResp = await fetch('/api/session', {
|
| 729 |
method: 'POST',
|
| 730 |
headers: {'Content-Type': 'application/json'},
|
|
@@ -732,12 +731,12 @@ async function runFullPipeline() {
|
|
| 732 |
});
|
| 733 |
const sessData = await sessResp.json();
|
| 734 |
sessionId = sessData.session_id;
|
| 735 |
-
|
| 736 |
|
| 737 |
// Phase 2: Suggest segments
|
| 738 |
-
document.getElementById('pipelineProgressText').textContent = '
|
| 739 |
document.getElementById('pipelineProgressBar').style.width = '10%';
|
| 740 |
-
logStep('
|
| 741 |
|
| 742 |
const segResp = await fetch('/api/suggest-segments', {
|
| 743 |
method: 'POST',
|
|
@@ -755,13 +754,13 @@ async function runFullPipeline() {
|
|
| 755 |
const scale = panelSize / (totalSuggested || 1);
|
| 756 |
segments.forEach(s => { s.count = Math.max(2, Math.round((s.count || 8) * scale)); });
|
| 757 |
|
| 758 |
-
segments.forEach(s => logStep(`
|
| 759 |
-
logStep(`${segments.length} segments
|
| 760 |
document.getElementById('pipelineProgressBar').style.width = '20%';
|
| 761 |
|
| 762 |
// Phase 3: Generate cohort
|
| 763 |
-
document.getElementById('pipelineProgressText').textContent = '
|
| 764 |
-
logStep('
|
| 765 |
|
| 766 |
const desc = audienceCtx || `People evaluating: ${text.substring(0, 200)}`;
|
| 767 |
const cohortResp = await fetch('/api/cohort/generate', {
|
|
@@ -789,7 +788,7 @@ async function runFullPipeline() {
|
|
| 789 |
if (f.occupation) parts.push(f.occupation);
|
| 790 |
logStep(`Filtered: ${parts.join(', ')}`, 'pos');
|
| 791 |
}
|
| 792 |
-
logStep(`${cohortData.cohort_size}
|
| 793 |
if (panelSize < 40) {
|
| 794 |
logStep(`Tip: a panel of 40-60 gives more reliable results across segments`, 'neu');
|
| 795 |
}
|
|
@@ -797,7 +796,7 @@ async function runFullPipeline() {
|
|
| 797 |
|
| 798 |
// Phase 4: Evaluate via SSE
|
| 799 |
document.getElementById('pipelineProgressText').textContent = 'Evaluating...';
|
| 800 |
-
logStep('
|
| 801 |
|
| 802 |
await new Promise((resolve, reject) => {
|
| 803 |
const params = new URLSearchParams({parallel: 5, bias_calibration: biasCal});
|
|
@@ -841,7 +840,7 @@ async function runFullPipeline() {
|
|
| 841 |
resolve();
|
| 842 |
});
|
| 843 |
|
| 844 |
-
es.onerror = () => { es.close(); reject(new Error('Connection lost')); };
|
| 845 |
});
|
| 846 |
|
| 847 |
} catch (e) {
|
|
@@ -875,7 +874,7 @@ async function runDirections() {
|
|
| 875 |
|
| 876 |
try {
|
| 877 |
// Phase 1: Extract concerns from persuadable middle
|
| 878 |
-
document.getElementById('cfProgressText').textContent = '
|
| 879 |
const persuadable = evalResultsData.filter(r => r && r.score >= 4 && r.score <= 7);
|
| 880 |
const concerns = [];
|
| 881 |
persuadable.forEach(r => {
|
|
@@ -888,11 +887,11 @@ async function runDirections() {
|
|
| 888 |
concerns.slice(0, 8).forEach(c => {
|
| 889 |
log.innerHTML += `<div style="color:var(--text2)">Concern: ${esc(c)}</div>`;
|
| 890 |
});
|
| 891 |
-
log.innerHTML += `<div>${concerns.length}
|
| 892 |
document.getElementById('cfProgressBar').style.width = '15%';
|
| 893 |
|
| 894 |
// Phase 2: LLM generates candidate changes from concerns
|
| 895 |
-
document.getElementById('cfProgressText').textContent = '
|
| 896 |
const suggestResp = await fetch('/api/suggest-changes', {
|
| 897 |
method: 'POST',
|
| 898 |
headers: {'Content-Type': 'application/json'},
|
|
@@ -974,7 +973,7 @@ async function runDirections() {
|
|
| 974 |
resolve();
|
| 975 |
});
|
| 976 |
|
| 977 |
-
es.onerror = () => { es.close(); reject(new Error('Connection lost')); };
|
| 978 |
});
|
| 979 |
|
| 980 |
} catch (e) {
|
|
@@ -1086,7 +1085,7 @@ function renderGradientTable(results, changes, ranked) {
|
|
| 1086 |
// ── Step 5: Bias Audit ──
|
| 1087 |
|
| 1088 |
function runBiasAudit() {
|
| 1089 |
-
if (!sessionId) return alert('
|
| 1090 |
const probes = [];
|
| 1091 |
if (document.getElementById('probeFraming').checked) probes.push('framing');
|
| 1092 |
if (document.getElementById('probeAuthority').checked) probes.push('authority');
|
|
@@ -1179,7 +1178,7 @@ function runBiasAudit() {
|
|
| 1179 |
|
| 1180 |
es.onerror = () => {
|
| 1181 |
es.close();
|
| 1182 |
-
document.getElementById('auditProgressText').textContent = 'Connection lost';
|
| 1183 |
btn.disabled = false;
|
| 1184 |
};
|
| 1185 |
}
|
|
|
|
| 306 |
<div class="container">
|
| 307 |
<header>
|
| 308 |
<h1>Semantic Gradient Optimization</h1>
|
| 309 |
+
<p>Build a panel. See what they think. Test what to change next.</p>
|
| 310 |
<div id="configBadge" class="config-badge">checking...</div>
|
| 311 |
<div id="nemotronBadge" class="config-badge" style="margin-left:8px">checking...</div>
|
| 312 |
</header>
|
|
|
|
| 315 |
<div class="step hidden" id="nemotronSetup" style="border-color:var(--yellow)">
|
| 316 |
<div class="step-header">
|
| 317 |
<div class="step-num" style="border-color:var(--yellow);color:var(--yellow)">!</div>
|
| 318 |
+
<div class="step-title">Panel data not found</div>
|
| 319 |
</div>
|
| 320 |
+
<p class="step-desc">SGO needs its persona dataset to build realistic panels. Choose where to load it from, or download it now (~2 GB).</p>
|
| 321 |
<div class="field">
|
| 322 |
+
<label>Folder for panel data</label>
|
| 323 |
+
<input type="text" id="nemotronPath" placeholder="e.g. data/nemotron">
|
| 324 |
</div>
|
| 325 |
<div class="btn-row">
|
| 326 |
+
<button onclick="setupNemotron()">Use this folder</button>
|
| 327 |
</div>
|
| 328 |
<div id="nemotronStatus" class="hidden mt-16">
|
| 329 |
<div class="progress-text" id="nemotronStatusText"></div>
|
|
|
|
| 334 |
<div class="step active" id="step1">
|
| 335 |
<div class="step-header">
|
| 336 |
<div class="step-num">1</div>
|
| 337 |
+
<div class="step-title">Build your panel review</div>
|
| 338 |
</div>
|
| 339 |
+
<p class="step-desc">Paste what you want reviewed, set your goal and audience, and let the panel react.</p>
|
| 340 |
|
| 341 |
<div class="template-chips">
|
| 342 |
<span class="template-chip" onclick="loadTemplate('product')">Product</span>
|
|
|
|
| 345 |
</div>
|
| 346 |
|
| 347 |
<div class="field">
|
| 348 |
+
<label>What should the panel review?</label>
|
| 349 |
+
<textarea id="entityText" placeholder="Paste the text someone would actually see: landing page, resume, pitch, profile, policy..."></textarea>
|
| 350 |
</div>
|
| 351 |
|
| 352 |
<div class="field">
|
| 353 |
<div style="display:flex;justify-content:space-between;align-items:center">
|
| 354 |
+
<label>What outcome are you optimizing for?</label>
|
| 355 |
+
<button class="secondary" style="padding:4px 12px;font-size:0.75rem" onclick="inferSpec()">Auto-fill</button>
|
| 356 |
</div>
|
| 357 |
<input type="text" id="goalText" placeholder="e.g. 'Increase conversion rate' or 'Get more interview callbacks'">
|
| 358 |
</div>
|
| 359 |
|
| 360 |
<div class="field">
|
| 361 |
<div style="display:flex;justify-content:space-between;align-items:center">
|
| 362 |
+
<label>Who should be on the panel?</label>
|
| 363 |
+
<button class="secondary" style="padding:4px 12px;font-size:0.75rem" onclick="inferSpec()">Auto-fill</button>
|
| 364 |
</div>
|
| 365 |
<input type="text" id="cohortDesc" placeholder="e.g. 'Engineering managers at mid-stage startups' or 'US consumers aged 25-45'">
|
| 366 |
</div>
|
| 367 |
|
| 368 |
<details class="mb-8">
|
| 369 |
+
<summary style="cursor:pointer;color:var(--text2);font-size:0.85rem">Panel settings</summary>
|
| 370 |
<div style="padding:12px 0">
|
| 371 |
<div class="field">
|
| 372 |
+
<label>Panel size (more people = steadier results)</label>
|
| 373 |
<input type="number" id="panelSize" value="30" min="5" max="80"
|
| 374 |
style="width:80px;padding:6px;text-align:center">
|
| 375 |
</div>
|
| 376 |
<label style="display:inline-flex;align-items:center;gap:8px;margin:0;font-size:0.85rem;cursor:pointer">
|
| 377 |
<input type="checkbox" id="biasCalibration" checked style="width:auto;margin:0">
|
| 378 |
+
Reduce framing and authority effects
|
| 379 |
</label>
|
| 380 |
</div>
|
| 381 |
</details>
|
| 382 |
|
| 383 |
+
<button onclick="runFullPipeline()" id="goBtn">Build panel and review</button>
|
| 384 |
|
| 385 |
<div id="pipelineProgress" class="hidden mt-16">
|
| 386 |
<div class="progress-text" id="pipelineProgressText">Starting...</div>
|
|
|
|
| 391 |
<div id="evalResults" class="hidden mt-16">
|
| 392 |
<div class="score-big" id="avgScore">0<span>/10</span></div>
|
| 393 |
<div class="stats-row">
|
| 394 |
+
<div class="stat pos"><div class="stat-val" id="posCount">0</div><div class="stat-label">would say yes</div></div>
|
| 395 |
+
<div class="stat neu"><div class="stat-val" id="neuCount">0</div><div class="stat-label">unsure</div></div>
|
| 396 |
+
<div class="stat neg"><div class="stat-val" id="negCount">0</div><div class="stat-label">would say no</div></div>
|
| 397 |
</div>
|
| 398 |
<details>
|
| 399 |
<summary style="cursor:pointer;color:var(--text2);font-size:0.9rem">Full analysis</summary>
|
| 400 |
<div class="results-details" id="evalAnalysis"></div>
|
| 401 |
</details>
|
| 402 |
<div class="btn-row mt-16">
|
| 403 |
+
<button onclick="runDirections()">Test what to change next</button>
|
| 404 |
+
<button class="secondary" onclick="goToStep(3)">Check panel realism</button>
|
| 405 |
</div>
|
| 406 |
</div>
|
| 407 |
</div>
|
|
|
|
| 410 |
<div class="step hidden" id="step2">
|
| 411 |
<div class="step-header">
|
| 412 |
<div class="step-num">2</div>
|
| 413 |
+
<div class="step-title">Test what to change next</div>
|
| 414 |
</div>
|
| 415 |
+
<p class="step-desc">Testing changes with people who are <em>on the fence</em> — the ones who could go either way.</p>
|
| 416 |
|
| 417 |
<div id="cfProgress" class="mt-16">
|
| 418 |
<div class="progress-text" id="cfProgressText">Analyzing concerns...</div>
|
|
|
|
| 437 |
<div class="step hidden" id="step3">
|
| 438 |
<div class="step-header">
|
| 439 |
<div class="step-num">3</div>
|
| 440 |
+
<div class="step-title">Check panel realism</div>
|
| 441 |
</div>
|
| 442 |
<p class="step-desc">
|
| 443 |
+
See whether your panel reacts more like real people or more like an LLM.
|
| 444 |
+
Based on <a href="https://arxiv.org/abs/2509.13588" target="_blank" style="color:var(--accent2)">CoBRA (CHI'26)</a>.
|
| 445 |
</p>
|
| 446 |
|
| 447 |
<div class="field">
|
| 448 |
+
<label>Checks to run</label>
|
| 449 |
<div style="display:flex;gap:16px;margin-bottom:12px">
|
| 450 |
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 451 |
+
<input type="checkbox" id="probeFraming" checked> Does wording change the score?
|
| 452 |
</label>
|
| 453 |
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 454 |
+
<input type="checkbox" id="probeAuthority" checked> Do trust signals change the score?
|
| 455 |
</label>
|
| 456 |
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 457 |
+
<input type="checkbox" id="probeOrder" checked> Does section order change the score?
|
| 458 |
</label>
|
| 459 |
</div>
|
| 460 |
</div>
|
| 461 |
|
| 462 |
<div class="btn-row">
|
| 463 |
+
<button onclick="runBiasAudit()" id="auditBtn">Run checks</button>
|
| 464 |
<div style="flex:1"></div>
|
| 465 |
<label style="display:flex;align-items:center;gap:6px;margin:0">
|
| 466 |
<span style="font-size:0.8rem;color:var(--text2)">Sample size:</span>
|
|
|
|
| 475 |
</div>
|
| 476 |
|
| 477 |
<div id="auditResults" class="hidden mt-16">
|
| 478 |
+
<h3 style="margin-bottom:12px">Panel Realism Check</h3>
|
| 479 |
<table class="gradient-table" id="auditTable">
|
| 480 |
<thead>
|
| 481 |
+
<tr><th>Check</th><th>Shifted %</th><th>Avg score change</th><th>Human baseline</th><th>Assessment</th></tr>
|
| 482 |
</thead>
|
| 483 |
<tbody></tbody>
|
| 484 |
</table>
|
|
|
|
| 487 |
<div class="results-details" id="auditReport"></div>
|
| 488 |
</details>
|
| 489 |
<div class="btn-row mt-16">
|
| 490 |
+
<button class="secondary" onclick="rerunWithCalibration()">Run again with realism tuning</button>
|
| 491 |
+
<button class="secondary" onclick="goToStep(2)">Test what to change next</button>
|
| 492 |
</div>
|
| 493 |
</div>
|
| 494 |
</div>
|
|
|
|
| 601 |
|
| 602 |
async function setupNemotron() {
|
| 603 |
const path = document.getElementById('nemotronPath').value.trim();
|
| 604 |
+
if (!path) return alert('Enter a folder path for the panel data.');
|
| 605 |
|
| 606 |
const status = document.getElementById('nemotronStatus');
|
| 607 |
const text = document.getElementById('nemotronStatusText');
|
|
|
|
| 695 |
|
| 696 |
async function runFullPipeline() {
|
| 697 |
const text = document.getElementById('entityText').value.trim();
|
| 698 |
+
if (!text) return alert('Paste something for the panel to review first.');
|
| 699 |
|
| 700 |
const btn = document.getElementById('goBtn');
|
| 701 |
btn.disabled = true;
|
|
|
|
| 710 |
const goalField = document.getElementById('goalText');
|
| 711 |
const audienceField = document.getElementById('cohortDesc');
|
| 712 |
if (!goalField.value.trim() || !audienceField.value.trim()) {
|
| 713 |
+
document.getElementById('pipelineProgressText').textContent = 'Understanding your goal and audience...';
|
| 714 |
+
logStep('Understanding what you want to optimize and who should judge it...');
|
| 715 |
await inferSpec();
|
| 716 |
if (goalField.value) logStep(`Goal: ${goalField.value}`, 'pos');
|
| 717 |
if (audienceField.value) logStep(`Audience: ${audienceField.value}`, 'pos');
|
|
|
|
| 723 |
|
| 724 |
try {
|
| 725 |
// Phase 1: Create session
|
| 726 |
+
document.getElementById('pipelineProgressText').textContent = 'Setting up...';
|
|
|
|
| 727 |
const sessResp = await fetch('/api/session', {
|
| 728 |
method: 'POST',
|
| 729 |
headers: {'Content-Type': 'application/json'},
|
|
|
|
| 731 |
});
|
| 732 |
const sessData = await sessResp.json();
|
| 733 |
sessionId = sessData.session_id;
|
| 734 |
+
// session created silently
|
| 735 |
|
| 736 |
// Phase 2: Suggest segments
|
| 737 |
+
document.getElementById('pipelineProgressText').textContent = 'Choosing panel segments...';
|
| 738 |
document.getElementById('pipelineProgressBar').style.width = '10%';
|
| 739 |
+
logStep('Choosing the right panel segments...');
|
| 740 |
|
| 741 |
const segResp = await fetch('/api/suggest-segments', {
|
| 742 |
method: 'POST',
|
|
|
|
| 754 |
const scale = panelSize / (totalSuggested || 1);
|
| 755 |
segments.forEach(s => { s.count = Math.max(2, Math.round((s.count || 8) * scale)); });
|
| 756 |
|
| 757 |
+
segments.forEach(s => logStep(` Added segment: ${s.label} (${s.count} people)`));
|
| 758 |
+
logStep(`${segments.length} segments chosen`, 'pos');
|
| 759 |
document.getElementById('pipelineProgressBar').style.width = '20%';
|
| 760 |
|
| 761 |
// Phase 3: Generate cohort
|
| 762 |
+
document.getElementById('pipelineProgressText').textContent = 'Building your panel...';
|
| 763 |
+
logStep('Building panel members for each segment...');
|
| 764 |
|
| 765 |
const desc = audienceCtx || `People evaluating: ${text.substring(0, 200)}`;
|
| 766 |
const cohortResp = await fetch('/api/cohort/generate', {
|
|
|
|
| 788 |
if (f.occupation) parts.push(f.occupation);
|
| 789 |
logStep(`Filtered: ${parts.join(', ')}`, 'pos');
|
| 790 |
}
|
| 791 |
+
logStep(`${cohortData.cohort_size} panel members ready — ${src}`, 'pos');
|
| 792 |
if (panelSize < 40) {
|
| 793 |
logStep(`Tip: a panel of 40-60 gives more reliable results across segments`, 'neu');
|
| 794 |
}
|
|
|
|
| 796 |
|
| 797 |
// Phase 4: Evaluate via SSE
|
| 798 |
document.getElementById('pipelineProgressText').textContent = 'Evaluating...';
|
| 799 |
+
logStep('Running the panel review — each member scores what you wrote...');
|
| 800 |
|
| 801 |
await new Promise((resolve, reject) => {
|
| 802 |
const params = new URLSearchParams({parallel: 5, bias_calibration: biasCal});
|
|
|
|
| 840 |
resolve();
|
| 841 |
});
|
| 842 |
|
| 843 |
+
es.onerror = () => { es.close(); reject(new Error('Connection lost — you can try again without losing your draft')); };
|
| 844 |
});
|
| 845 |
|
| 846 |
} catch (e) {
|
|
|
|
| 874 |
|
| 875 |
try {
|
| 876 |
// Phase 1: Extract concerns from persuadable middle
|
| 877 |
+
document.getElementById('cfProgressText').textContent = 'Looking at what\u2019s holding back the on-the-fence group...';
|
| 878 |
const persuadable = evalResultsData.filter(r => r && r.score >= 4 && r.score <= 7);
|
| 879 |
const concerns = [];
|
| 880 |
persuadable.forEach(r => {
|
|
|
|
| 887 |
concerns.slice(0, 8).forEach(c => {
|
| 888 |
log.innerHTML += `<div style="color:var(--text2)">Concern: ${esc(c)}</div>`;
|
| 889 |
});
|
| 890 |
+
log.innerHTML += `<div>${concerns.length} concerns from ${persuadable.length} on-the-fence panel members</div>`;
|
| 891 |
document.getElementById('cfProgressBar').style.width = '15%';
|
| 892 |
|
| 893 |
// Phase 2: LLM generates candidate changes from concerns
|
| 894 |
+
document.getElementById('cfProgressText').textContent = 'Proposing changes to test...';
|
| 895 |
const suggestResp = await fetch('/api/suggest-changes', {
|
| 896 |
method: 'POST',
|
| 897 |
headers: {'Content-Type': 'application/json'},
|
|
|
|
| 973 |
resolve();
|
| 974 |
});
|
| 975 |
|
| 976 |
+
es.onerror = () => { es.close(); reject(new Error('Connection lost — you can try again without losing your draft')); };
|
| 977 |
});
|
| 978 |
|
| 979 |
} catch (e) {
|
|
|
|
| 1085 |
// ── Step 5: Bias Audit ──
|
| 1086 |
|
| 1087 |
function runBiasAudit() {
|
| 1088 |
+
if (!sessionId) return alert('Start with Step 1 to create a panel review.');
|
| 1089 |
const probes = [];
|
| 1090 |
if (document.getElementById('probeFraming').checked) probes.push('framing');
|
| 1091 |
if (document.getElementById('probeAuthority').checked) probes.push('authority');
|
|
|
|
| 1178 |
|
| 1179 |
es.onerror = () => {
|
| 1180 |
es.close();
|
| 1181 |
+
document.getElementById('auditProgressText').textContent = 'Connection lost — you can try again without losing your draft';
|
| 1182 |
btn.disabled = false;
|
| 1183 |
};
|
| 1184 |
}
|