Eric Xu commited on
Simplify web UX: one-click evaluate, no segment configuration
Browse filesUsers paste their entity and click Evaluate. The system automatically:
1. Analyzes the entity to determine appropriate evaluator segments
2. Generates personas for each segment
3. Runs evaluation with live progress
All intermediate steps (segment selection, cohort generation, persona
details) are shown in the progress log so users see what's happening
under the hood, but don't need to configure anything.
Segment config, panel size, bias calibration, and audience context
are available under Advanced Options for power users.
- web/static/index.html +152 -235
web/static/index.html
CHANGED
|
@@ -310,13 +310,13 @@
|
|
| 310 |
<div id="configBadge" class="config-badge">checking...</div>
|
| 311 |
</header>
|
| 312 |
|
| 313 |
-
<!-- STEP 1: Entity -->
|
| 314 |
<div class="step active" id="step1">
|
| 315 |
<div class="step-header">
|
| 316 |
<div class="step-num">1</div>
|
| 317 |
-
<div class="step-title">
|
| 318 |
</div>
|
| 319 |
-
<p class="step-desc">Paste
|
| 320 |
|
| 321 |
<div class="template-chips">
|
| 322 |
<span class="template-chip" onclick="loadTemplate('product')">Product</span>
|
|
@@ -325,71 +325,33 @@
|
|
| 325 |
</div>
|
| 326 |
|
| 327 |
<div class="field">
|
| 328 |
-
<textarea id="entityText" placeholder="Paste your entity here...
|
| 329 |
-
</div>
|
| 330 |
-
<button onclick="saveEntity()">Continue</button>
|
| 331 |
-
</div>
|
| 332 |
-
|
| 333 |
-
<!-- STEP 2: Cohort -->
|
| 334 |
-
<div class="step hidden" id="step2">
|
| 335 |
-
<div class="step-header">
|
| 336 |
-
<div class="step-num">2</div>
|
| 337 |
-
<div class="step-title">Build your evaluator panel</div>
|
| 338 |
-
</div>
|
| 339 |
-
<p class="step-desc">Define audience segments. The LLM generates realistic personas for each.</p>
|
| 340 |
-
|
| 341 |
-
<div class="field">
|
| 342 |
-
<label>Audience context</label>
|
| 343 |
-
<input type="text" id="cohortDesc" placeholder="e.g. 'Potential customers for a B2B SaaS analytics tool'">
|
| 344 |
-
</div>
|
| 345 |
-
|
| 346 |
-
<label>Segments</label>
|
| 347 |
-
<div id="segmentsList" class="segments-list"></div>
|
| 348 |
-
<div class="btn-row">
|
| 349 |
-
<button class="secondary" onclick="addSegment()">+ Add segment</button>
|
| 350 |
-
<button class="secondary" onclick="suggestSegments()">Re-suggest</button>
|
| 351 |
-
<button onclick="generateCohort()" id="genCohortBtn">Generate cohort</button>
|
| 352 |
-
</div>
|
| 353 |
-
|
| 354 |
-
<div id="cohortProgress" class="hidden mt-16">
|
| 355 |
-
<div class="progress-text" id="cohortProgressText">Generating personas...</div>
|
| 356 |
-
<div class="progress-bar"><div class="progress-fill" id="cohortProgressBar"></div></div>
|
| 357 |
</div>
|
| 358 |
|
| 359 |
-
<
|
| 360 |
-
<
|
| 361 |
-
|
| 362 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
</div>
|
| 364 |
-
|
| 365 |
-
</div>
|
| 366 |
-
</div>
|
| 367 |
|
| 368 |
-
|
| 369 |
-
<div class="step hidden" id="step3">
|
| 370 |
-
<div class="step-header">
|
| 371 |
-
<div class="step-num">3</div>
|
| 372 |
-
<div class="step-title">Run evaluation</div>
|
| 373 |
-
</div>
|
| 374 |
-
<p class="step-desc">Each evaluator scores your entity 1-10 with reasoning.</p>
|
| 375 |
|
| 376 |
-
<div class="
|
| 377 |
-
<
|
| 378 |
-
<
|
| 379 |
-
<input type="checkbox" id="biasCalibration">
|
| 380 |
-
<span style="font-size:0.8rem;color:var(--text2)">Bias calibration</span>
|
| 381 |
-
</label>
|
| 382 |
-
<div style="flex:1"></div>
|
| 383 |
-
<label style="display:flex;align-items:center;gap:6px;margin:0">
|
| 384 |
-
<span style="font-size:0.8rem;color:var(--text2)">Parallel:</span>
|
| 385 |
-
<input type="number" id="evalParallel" value="5" min="1" max="20"
|
| 386 |
-
style="width:60px;padding:6px;text-align:center">
|
| 387 |
-
</label>
|
| 388 |
-
</div>
|
| 389 |
-
|
| 390 |
-
<div id="evalProgress" class="hidden mt-16">
|
| 391 |
-
<div class="progress-text" id="evalProgressText">Evaluating...</div>
|
| 392 |
-
<div class="progress-bar"><div class="progress-fill" id="evalProgressBar"></div></div>
|
| 393 |
<div class="eval-log" id="evalLog"></div>
|
| 394 |
</div>
|
| 395 |
|
|
@@ -405,16 +367,16 @@
|
|
| 405 |
<div class="results-details" id="evalAnalysis"></div>
|
| 406 |
</details>
|
| 407 |
<div class="btn-row mt-16">
|
| 408 |
-
<button onclick="goToStep(
|
| 409 |
-
<button class="secondary" onclick="goToStep(
|
| 410 |
</div>
|
| 411 |
</div>
|
| 412 |
</div>
|
| 413 |
|
| 414 |
-
<!-- STEP
|
| 415 |
-
<div class="step hidden" id="
|
| 416 |
<div class="step-header">
|
| 417 |
-
<div class="step-num">
|
| 418 |
<div class="step-title">Find the highest-impact changes</div>
|
| 419 |
</div>
|
| 420 |
<p class="step-desc">List hypothetical changes. Undecided evaluators (scores 4-7) re-evaluate each one.</p>
|
|
@@ -455,10 +417,10 @@
|
|
| 455 |
</div>
|
| 456 |
</div>
|
| 457 |
|
| 458 |
-
<!-- STEP
|
| 459 |
-
<div class="step hidden" id="
|
| 460 |
<div class="step-header">
|
| 461 |
-
<div class="step-num">
|
| 462 |
<div class="step-title">Bias Audit</div>
|
| 463 |
</div>
|
| 464 |
<p class="step-desc">
|
|
@@ -509,8 +471,8 @@
|
|
| 509 |
<div class="results-details" id="auditReport"></div>
|
| 510 |
</details>
|
| 511 |
<div class="btn-row mt-16">
|
| 512 |
-
<button class="secondary" onclick="
|
| 513 |
-
<button class="secondary" onclick="goToStep(
|
| 514 |
</div>
|
| 515 |
</div>
|
| 516 |
</div>
|
|
@@ -594,7 +556,6 @@ async function init() {
|
|
| 594 |
badge.className = 'config-badge warn';
|
| 595 |
}
|
| 596 |
|
| 597 |
-
// Changes will be populated with defaults
|
| 598 |
addChange('', '');
|
| 599 |
addChange('', '');
|
| 600 |
}
|
|
@@ -608,7 +569,7 @@ function loadTemplate(name) {
|
|
| 608 |
// ── Step navigation ──
|
| 609 |
|
| 610 |
function goToStep(n) {
|
| 611 |
-
for (let i = 1; i <=
|
| 612 |
const el = document.getElementById(`step${i}`);
|
| 613 |
if (!el) continue;
|
| 614 |
if (i < n) {
|
|
@@ -622,200 +583,156 @@ function goToStep(n) {
|
|
| 622 |
el.classList.remove('active', 'done');
|
| 623 |
}
|
| 624 |
}
|
| 625 |
-
// When going to bias audit, keep steps 4/5 both visible context
|
| 626 |
-
if (n === 5) {
|
| 627 |
-
const step3 = document.getElementById('step3');
|
| 628 |
-
step3.classList.remove('hidden');
|
| 629 |
-
step3.classList.add('done');
|
| 630 |
-
}
|
| 631 |
}
|
| 632 |
|
| 633 |
-
// ──
|
| 634 |
|
| 635 |
-
|
| 636 |
-
const
|
| 637 |
-
|
|
|
|
|
|
|
| 638 |
|
| 639 |
-
|
| 640 |
-
method: 'POST',
|
| 641 |
-
headers: {'Content-Type': 'application/json'},
|
| 642 |
-
body: JSON.stringify({entity_text: text}),
|
| 643 |
-
});
|
| 644 |
-
const data = await resp.json();
|
| 645 |
-
sessionId = data.session_id;
|
| 646 |
|
| 647 |
-
|
| 648 |
-
const
|
| 649 |
-
|
| 650 |
-
if (!desc.value) desc.value = `People evaluating: ${firstLine.replace(/^#+\s*/, '').trim()}`;
|
| 651 |
|
| 652 |
-
|
|
|
|
| 653 |
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
|
|
|
|
|
|
| 657 |
|
| 658 |
-
|
| 659 |
-
const
|
| 660 |
const audienceCtx = document.getElementById('cohortDesc').value.trim();
|
| 661 |
-
if (!entityText) return;
|
| 662 |
-
|
| 663 |
-
// Clear existing segments and show loading
|
| 664 |
-
const list = document.getElementById('segmentsList');
|
| 665 |
-
list.innerHTML = '<div style="color:var(--text2);font-size:0.85rem;padding:8px">Suggesting segments...</div>';
|
| 666 |
|
| 667 |
try {
|
| 668 |
-
|
|
|
|
|
|
|
|
|
|
| 669 |
method: 'POST',
|
| 670 |
headers: {'Content-Type': 'application/json'},
|
| 671 |
-
body: JSON.stringify({entity_text:
|
| 672 |
-
});
|
| 673 |
-
const data = await resp.json();
|
| 674 |
-
list.innerHTML = '';
|
| 675 |
-
(data.segments || []).forEach(seg => {
|
| 676 |
-
addSegment(seg.label, seg.count || 8);
|
| 677 |
});
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
addSegment('', 8);
|
| 682 |
-
addSegment('', 8);
|
| 683 |
-
}
|
| 684 |
-
}
|
| 685 |
|
| 686 |
-
//
|
|
|
|
|
|
|
|
|
|
| 687 |
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
}
|
| 699 |
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
const rows = document.querySelectorAll('#segmentsList .seg-row');
|
| 705 |
-
const segments = [];
|
| 706 |
-
rows.forEach(row => {
|
| 707 |
-
const inputs = row.querySelectorAll('input');
|
| 708 |
-
const label = inputs[0].value.trim();
|
| 709 |
-
const count = parseInt(inputs[1].value) || 5;
|
| 710 |
-
if (label) segments.push({label, count});
|
| 711 |
-
});
|
| 712 |
|
| 713 |
-
|
|
|
|
|
|
|
| 714 |
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
progress.classList.remove('hidden');
|
| 719 |
-
document.getElementById('cohortProgressBar').style.width = '30%';
|
| 720 |
-
document.getElementById('cohortProgressText').textContent = 'Generating personas (this may take 30-60s)...';
|
| 721 |
|
| 722 |
-
|
| 723 |
-
const
|
| 724 |
method: 'POST',
|
| 725 |
headers: {'Content-Type': 'application/json'},
|
| 726 |
body: JSON.stringify({description: desc, segments, parallel: 3}),
|
| 727 |
});
|
| 728 |
-
const
|
| 729 |
-
sessionId = data.session_id;
|
| 730 |
-
|
| 731 |
-
// Update entity text in session
|
| 732 |
-
const entityText = document.getElementById('entityText').value.trim();
|
| 733 |
-
if (entityText) {
|
| 734 |
-
await fetch('/api/session', {
|
| 735 |
-
method: 'POST',
|
| 736 |
-
headers: {'Content-Type': 'application/json'},
|
| 737 |
-
body: JSON.stringify({entity_text: entityText}),
|
| 738 |
-
}).then(r => r.json()).then(d => { sessionId = d.session_id; });
|
| 739 |
-
// Upload cohort to new session
|
| 740 |
-
await fetch(`/api/cohort/upload/${sessionId}`, {
|
| 741 |
-
method: 'POST',
|
| 742 |
-
headers: {'Content-Type': 'application/json'},
|
| 743 |
-
body: JSON.stringify(data.cohort),
|
| 744 |
-
});
|
| 745 |
-
}
|
| 746 |
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
document.getElementById('cohortProgressText').textContent = 'Failed';
|
| 754 |
-
} finally {
|
| 755 |
-
btn.disabled = false;
|
| 756 |
-
}
|
| 757 |
-
}
|
| 758 |
|
| 759 |
-
|
|
|
|
| 760 |
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
const biasCal = document.getElementById('biasCalibration').checked;
|
| 765 |
-
const btn = document.getElementById('evalBtn');
|
| 766 |
-
btn.disabled = true;
|
| 767 |
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
|
| 772 |
-
|
| 773 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
|
|
|
|
|
|
| 795 |
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
const d = JSON.parse(e.data);
|
| 799 |
-
evalResultsData = d.results;
|
| 800 |
-
|
| 801 |
-
document.getElementById('evalProgressBar').style.width = '100%';
|
| 802 |
-
document.getElementById('evalProgressText').textContent =
|
| 803 |
-
`Done in ${d.elapsed}s`;
|
| 804 |
-
|
| 805 |
-
document.getElementById('avgScore').innerHTML = `${d.avg_score}<span>/10</span>`;
|
| 806 |
-
document.getElementById('posCount').textContent = d.positive;
|
| 807 |
-
document.getElementById('neuCount').textContent = d.neutral;
|
| 808 |
-
document.getElementById('negCount').textContent = d.negative;
|
| 809 |
-
document.getElementById('evalAnalysis').textContent = d.analysis;
|
| 810 |
-
document.getElementById('evalResults').classList.remove('hidden');
|
| 811 |
-
btn.disabled = false;
|
| 812 |
-
});
|
| 813 |
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
|
|
|
| 817 |
btn.disabled = false;
|
| 818 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
}
|
| 820 |
|
| 821 |
// ── Step 4: Counterfactual ──
|
|
|
|
| 310 |
<div id="configBadge" class="config-badge">checking...</div>
|
| 311 |
</header>
|
| 312 |
|
| 313 |
+
<!-- STEP 1: Entity + Evaluate (one click) -->
|
| 314 |
<div class="step active" id="step1">
|
| 315 |
<div class="step-header">
|
| 316 |
<div class="step-num">1</div>
|
| 317 |
+
<div class="step-title">What are you optimizing?</div>
|
| 318 |
</div>
|
| 319 |
+
<p class="step-desc">Paste whatever you want evaluated — landing page, resume, pitch, dating profile, policy, anything.</p>
|
| 320 |
|
| 321 |
<div class="template-chips">
|
| 322 |
<span class="template-chip" onclick="loadTemplate('product')">Product</span>
|
|
|
|
| 325 |
</div>
|
| 326 |
|
| 327 |
<div class="field">
|
| 328 |
+
<textarea id="entityText" placeholder="Paste your entity here..."></textarea>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
</div>
|
| 330 |
|
| 331 |
+
<details class="mb-8">
|
| 332 |
+
<summary style="cursor:pointer;color:var(--text2);font-size:0.85rem">Advanced options</summary>
|
| 333 |
+
<div style="padding:12px 0">
|
| 334 |
+
<div class="field">
|
| 335 |
+
<label>Audience context (optional — auto-detected if blank)</label>
|
| 336 |
+
<input type="text" id="cohortDesc" placeholder="e.g. 'Women aged 25-35 evaluating a dating profile'">
|
| 337 |
+
</div>
|
| 338 |
+
<div class="field">
|
| 339 |
+
<label>Panel size</label>
|
| 340 |
+
<input type="number" id="panelSize" value="30" min="5" max="80"
|
| 341 |
+
style="width:80px;padding:6px;text-align:center">
|
| 342 |
+
</div>
|
| 343 |
+
<label style="display:flex;align-items:center;gap:6px;margin:0;font-size:0.85rem">
|
| 344 |
+
<input type="checkbox" id="biasCalibration">
|
| 345 |
+
Bias calibration (reduces framing/authority artifacts)
|
| 346 |
+
</label>
|
| 347 |
</div>
|
| 348 |
+
</details>
|
|
|
|
|
|
|
| 349 |
|
| 350 |
+
<button onclick="runFullPipeline()" id="goBtn">Evaluate</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
+
<div id="pipelineProgress" class="hidden mt-16">
|
| 353 |
+
<div class="progress-text" id="pipelineProgressText">Starting...</div>
|
| 354 |
+
<div class="progress-bar"><div class="progress-fill" id="pipelineProgressBar"></div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
<div class="eval-log" id="evalLog"></div>
|
| 356 |
</div>
|
| 357 |
|
|
|
|
| 367 |
<div class="results-details" id="evalAnalysis"></div>
|
| 368 |
</details>
|
| 369 |
<div class="btn-row mt-16">
|
| 370 |
+
<button onclick="goToStep(2)">Find what to change</button>
|
| 371 |
+
<button class="secondary" onclick="goToStep(3)">Run bias audit</button>
|
| 372 |
</div>
|
| 373 |
</div>
|
| 374 |
</div>
|
| 375 |
|
| 376 |
+
<!-- STEP 2: Counterfactual -->
|
| 377 |
+
<div class="step hidden" id="step2">
|
| 378 |
<div class="step-header">
|
| 379 |
+
<div class="step-num">2</div>
|
| 380 |
<div class="step-title">Find the highest-impact changes</div>
|
| 381 |
</div>
|
| 382 |
<p class="step-desc">List hypothetical changes. Undecided evaluators (scores 4-7) re-evaluate each one.</p>
|
|
|
|
| 417 |
</div>
|
| 418 |
</div>
|
| 419 |
|
| 420 |
+
<!-- STEP 3: Bias Audit -->
|
| 421 |
+
<div class="step hidden" id="step3">
|
| 422 |
<div class="step-header">
|
| 423 |
+
<div class="step-num">3</div>
|
| 424 |
<div class="step-title">Bias Audit</div>
|
| 425 |
</div>
|
| 426 |
<p class="step-desc">
|
|
|
|
| 471 |
<div class="results-details" id="auditReport"></div>
|
| 472 |
</details>
|
| 473 |
<div class="btn-row mt-16">
|
| 474 |
+
<button class="secondary" onclick="rerunWithCalibration()">Re-evaluate with bias calibration</button>
|
| 475 |
+
<button class="secondary" onclick="goToStep(2)">Continue to gradient</button>
|
| 476 |
</div>
|
| 477 |
</div>
|
| 478 |
</div>
|
|
|
|
| 556 |
badge.className = 'config-badge warn';
|
| 557 |
}
|
| 558 |
|
|
|
|
| 559 |
addChange('', '');
|
| 560 |
addChange('', '');
|
| 561 |
}
|
|
|
|
| 569 |
// ── Step navigation ──
|
| 570 |
|
| 571 |
function goToStep(n) {
|
| 572 |
+
for (let i = 1; i <= 3; i++) {
|
| 573 |
const el = document.getElementById(`step${i}`);
|
| 574 |
if (!el) continue;
|
| 575 |
if (i < n) {
|
|
|
|
| 583 |
el.classList.remove('active', 'done');
|
| 584 |
}
|
| 585 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
}
|
| 587 |
|
| 588 |
+
// ── Logging helper ──
|
| 589 |
|
| 590 |
+
function logStep(msg, cls = '') {
|
| 591 |
+
const log = document.getElementById('evalLog');
|
| 592 |
+
log.innerHTML += `<div class="${cls}">${msg}</div>`;
|
| 593 |
+
log.scrollTop = log.scrollHeight;
|
| 594 |
+
}
|
| 595 |
|
| 596 |
+
// ── Step 1: Full pipeline (one click) ──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
|
| 598 |
+
async function runFullPipeline() {
|
| 599 |
+
const text = document.getElementById('entityText').value.trim();
|
| 600 |
+
if (!text) return alert('Please enter your entity text.');
|
|
|
|
| 601 |
|
| 602 |
+
const btn = document.getElementById('goBtn');
|
| 603 |
+
btn.disabled = true;
|
| 604 |
|
| 605 |
+
const progress = document.getElementById('pipelineProgress');
|
| 606 |
+
progress.classList.remove('hidden');
|
| 607 |
+
document.getElementById('evalResults').classList.add('hidden');
|
| 608 |
+
document.getElementById('evalLog').innerHTML = '';
|
| 609 |
+
document.getElementById('pipelineProgressBar').style.width = '5%';
|
| 610 |
|
| 611 |
+
const biasCal = document.getElementById('biasCalibration').checked;
|
| 612 |
+
const panelSize = parseInt(document.getElementById('panelSize').value) || 30;
|
| 613 |
const audienceCtx = document.getElementById('cohortDesc').value.trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
|
| 615 |
try {
|
| 616 |
+
// Phase 1: Create session
|
| 617 |
+
document.getElementById('pipelineProgressText').textContent = 'Creating session...';
|
| 618 |
+
logStep('Creating session...');
|
| 619 |
+
const sessResp = await fetch('/api/session', {
|
| 620 |
method: 'POST',
|
| 621 |
headers: {'Content-Type': 'application/json'},
|
| 622 |
+
body: JSON.stringify({entity_text: text}),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
});
|
| 624 |
+
const sessData = await sessResp.json();
|
| 625 |
+
sessionId = sessData.session_id;
|
| 626 |
+
logStep(`Session: ${sessionId}`, 'pos');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
|
| 628 |
+
// Phase 2: Suggest segments
|
| 629 |
+
document.getElementById('pipelineProgressText').textContent = 'Analyzing entity and choosing evaluator segments...';
|
| 630 |
+
document.getElementById('pipelineProgressBar').style.width = '10%';
|
| 631 |
+
logStep('Analyzing entity to find the right evaluator segments...');
|
| 632 |
|
| 633 |
+
const segResp = await fetch('/api/suggest-segments', {
|
| 634 |
+
method: 'POST',
|
| 635 |
+
headers: {'Content-Type': 'application/json'},
|
| 636 |
+
body: JSON.stringify({
|
| 637 |
+
entity_text: text,
|
| 638 |
+
audience_context: audienceCtx || `People who would evaluate: ${text.substring(0, 200)}`,
|
| 639 |
+
}),
|
| 640 |
+
});
|
| 641 |
+
const segData = await segResp.json();
|
| 642 |
+
const segments = segData.segments || [];
|
|
|
|
| 643 |
|
| 644 |
+
// Scale segment counts to match requested panel size
|
| 645 |
+
const totalSuggested = segments.reduce((a, s) => a + (s.count || 8), 0);
|
| 646 |
+
const scale = panelSize / (totalSuggested || 1);
|
| 647 |
+
segments.forEach(s => { s.count = Math.max(2, Math.round((s.count || 8) * scale)); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
|
| 649 |
+
segments.forEach(s => logStep(` Segment: ${s.label} (${s.count} personas)`));
|
| 650 |
+
logStep(`${segments.length} segments identified`, 'pos');
|
| 651 |
+
document.getElementById('pipelineProgressBar').style.width = '20%';
|
| 652 |
|
| 653 |
+
// Phase 3: Generate cohort
|
| 654 |
+
document.getElementById('pipelineProgressText').textContent = 'Generating evaluator personas...';
|
| 655 |
+
logStep('Generating realistic personas for each segment...');
|
|
|
|
|
|
|
|
|
|
| 656 |
|
| 657 |
+
const desc = audienceCtx || `People evaluating: ${text.substring(0, 200)}`;
|
| 658 |
+
const cohortResp = await fetch('/api/cohort/generate', {
|
| 659 |
method: 'POST',
|
| 660 |
headers: {'Content-Type': 'application/json'},
|
| 661 |
body: JSON.stringify({description: desc, segments, parallel: 3}),
|
| 662 |
});
|
| 663 |
+
const cohortData = await cohortResp.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
|
| 665 |
+
// Upload cohort to our session
|
| 666 |
+
await fetch(`/api/cohort/upload/${sessionId}`, {
|
| 667 |
+
method: 'POST',
|
| 668 |
+
headers: {'Content-Type': 'application/json'},
|
| 669 |
+
body: JSON.stringify(cohortData.cohort),
|
| 670 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
|
| 672 |
+
logStep(`${cohortData.cohort_size} evaluators generated`, 'pos');
|
| 673 |
+
document.getElementById('pipelineProgressBar').style.width = '35%';
|
| 674 |
|
| 675 |
+
// Phase 4: Evaluate via SSE
|
| 676 |
+
document.getElementById('pipelineProgressText').textContent = 'Evaluating...';
|
| 677 |
+
logStep('Starting evaluation — each evaluator scores your entity...');
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
+
await new Promise((resolve, reject) => {
|
| 680 |
+
const params = new URLSearchParams({parallel: 5, bias_calibration: biasCal});
|
| 681 |
+
const es = new EventSource(`/api/evaluate/stream/${sessionId}?${params}`);
|
| 682 |
|
| 683 |
+
es.addEventListener('start', (e) => {
|
| 684 |
+
const d = JSON.parse(e.data);
|
| 685 |
+
const calLabel = d.bias_calibration ? ' (bias-calibrated)' : '';
|
| 686 |
+
document.getElementById('pipelineProgressText').textContent =
|
| 687 |
+
`Evaluating ${d.total} personas${calLabel}...`;
|
| 688 |
+
});
|
| 689 |
|
| 690 |
+
es.addEventListener('progress', (e) => {
|
| 691 |
+
const d = JSON.parse(e.data);
|
| 692 |
+
const base = 35;
|
| 693 |
+
const pct = base + Math.round(d.done / d.total * (100 - base));
|
| 694 |
+
document.getElementById('pipelineProgressBar').style.width = pct + '%';
|
| 695 |
+
document.getElementById('pipelineProgressText').textContent =
|
| 696 |
+
`${d.done}/${d.total} evaluated`;
|
| 697 |
+
|
| 698 |
+
const cls = d.error ? 'err' : d.action === 'positive' ? 'pos' : d.action === 'negative' ? 'neg' : 'neu';
|
| 699 |
+
const icon = d.error ? 'ERR' : d.action === 'positive' ? '+' : d.action === 'negative' ? '-' : '~';
|
| 700 |
+
const score = d.score != null ? `${d.score}/10` : '';
|
| 701 |
+
logStep(`[${d.done}/${d.total}] ${d.name}: ${icon} ${score}`, cls);
|
| 702 |
+
});
|
| 703 |
|
| 704 |
+
es.addEventListener('complete', (e) => {
|
| 705 |
+
es.close();
|
| 706 |
+
const d = JSON.parse(e.data);
|
| 707 |
+
evalResultsData = d.results;
|
| 708 |
+
|
| 709 |
+
document.getElementById('pipelineProgressBar').style.width = '100%';
|
| 710 |
+
document.getElementById('pipelineProgressText').textContent = `Done in ${d.elapsed}s`;
|
| 711 |
+
|
| 712 |
+
document.getElementById('avgScore').innerHTML = `${d.avg_score}<span>/10</span>`;
|
| 713 |
+
document.getElementById('posCount').textContent = d.positive;
|
| 714 |
+
document.getElementById('neuCount').textContent = d.neutral;
|
| 715 |
+
document.getElementById('negCount').textContent = d.negative;
|
| 716 |
+
document.getElementById('evalAnalysis').textContent = d.analysis;
|
| 717 |
+
document.getElementById('evalResults').classList.remove('hidden');
|
| 718 |
+
resolve();
|
| 719 |
+
});
|
| 720 |
|
| 721 |
+
es.onerror = () => { es.close(); reject(new Error('Connection lost')); };
|
| 722 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
|
| 724 |
+
} catch (e) {
|
| 725 |
+
document.getElementById('pipelineProgressText').textContent = `Error: ${e.message}`;
|
| 726 |
+
logStep(`Error: ${e.message}`, 'err');
|
| 727 |
+
} finally {
|
| 728 |
btn.disabled = false;
|
| 729 |
+
}
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
function rerunWithCalibration() {
|
| 733 |
+
document.getElementById('biasCalibration').checked = true;
|
| 734 |
+
goToStep(1);
|
| 735 |
+
runFullPipeline();
|
| 736 |
}
|
| 737 |
|
| 738 |
// ── Step 4: Counterfactual ──
|