Developer-Amar commited on
Commit
b3210a7
Β·
1 Parent(s): 52eb44f

Update UI with Chart.js and export features

Browse files
Files changed (1) hide show
  1. static/index.html +179 -15
static/index.html CHANGED
@@ -4,6 +4,7 @@
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
  <title>SocraticEnv β€” Live Dashboard</title>
 
7
  <style>
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
  body {
@@ -97,6 +98,13 @@
97
  }
98
  .turn-dot.done { background: #7c3aed; }
99
  .turn-dot.current { background: #a855f7; animation: pulse 1s infinite; }
 
 
 
 
 
 
 
100
  .main { display: flex; flex-direction: column; overflow: hidden; }
101
  .controls {
102
  background: #161b22; border-bottom: 1px solid #30363d;
@@ -310,6 +318,7 @@
310
  <div class="task-desc">Explain complex concepts using ONLY analogies. No technical jargon allowed!</div>
311
  </div>
312
  </div>
 
313
  <div class="sidebar-section">
314
  <div class="sidebar-title">Generate Custom Task</div>
315
  <div style="margin-bottom:8px;">
@@ -335,8 +344,6 @@
335
  </div>
336
  <div id="generateStatus" style="font-size:11px;color:#8b949e;min-height:16px;line-height:1.4;"></div>
337
  </div>
338
- <div class="sidebar-section">
339
- <div class="sidebar-title">Live Scores</div>
340
  <div class="score-grid">
341
  <div class="score-card full">
342
  <div class="score-value" id="overallScore">β€”</div>
@@ -351,6 +358,12 @@
351
  <div class="score-label">Last Reward</div>
352
  </div>
353
  </div>
 
 
 
 
 
 
354
  </div>
355
 
356
  <div class="sidebar-section">
@@ -423,6 +436,10 @@ let maxTurns = 3;
423
  let sessionResults = [];
424
  let currentHistory = [];
425
 
 
 
 
 
426
  async function checkStatus() {
427
  try {
428
  const r = await fetch(`${API}/ping`);
@@ -457,14 +474,77 @@ function selectTask(taskId) {
457
  document.getElementById('taskHint').textContent = hints[taskId];
458
  }
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  async function startEpisode() {
461
  clearDialogue();
462
  episodeActive = true;
463
  totalScore = 0;
464
  turnCount = 0;
465
  currentHistory = [];
466
-
 
 
 
 
 
 
 
 
 
467
  const maxMap = { factual_recall: 3, socratic_dialogue: 5, misconception_trap: 3, debate_mode: 4, analogy_challenge: 3 };
 
468
  maxTurns = maxMap[selectedTask];
469
  buildTurnTrack(maxTurns);
470
  updateScores();
@@ -481,6 +561,10 @@ async function startEpisode() {
481
  const data = await r.json();
482
  const question = data.observation.question;
483
  currentHistory.push({ role: 'tutor', content: question });
 
 
 
 
484
  addTutorMessage(question);
485
  enableInput();
486
  document.getElementById('turnLabel').textContent = `Turn 1 of ${maxTurns}`;
@@ -515,15 +599,33 @@ async function sendResponse(response) {
515
  totalScore += score;
516
  updateScores(score);
517
  updateTurnTrack(turnCount);
 
 
 
518
 
519
  const nextQuestion = data.observation.question;
520
  currentHistory.push({ role: 'tutor', content: nextQuestion });
 
 
 
 
 
 
 
 
 
 
521
  addTutorMessage(nextQuestion, data.reward);
522
 
523
  if (data.done) {
524
  episodeActive = false;
525
  stopAutoRun();
526
  const avg = totalScore / turnCount;
 
 
 
 
 
527
  showComplete(avg, data.reward.feedback);
528
  saveToHistory(selectedTask, avg);
529
  document.getElementById('btnStart').disabled = false;
@@ -630,17 +732,22 @@ function resetAll() {
630
  episodeActive = false;
631
  autoRunning = false;
632
  currentHistory = [];
 
633
  clearTimeout(autoRunTimer);
634
  stopAutoRun();
635
  clearDialogue();
636
  totalScore = 0; turnCount = 0;
 
637
  document.getElementById('overallScore').textContent = 'β€”';
638
  document.getElementById('turnCount').textContent = '0';
639
  document.getElementById('lastReward').textContent = 'β€”';
640
  document.getElementById('turnTrack').innerHTML = '';
641
  document.getElementById('turnLabel').textContent = 'No active episode';
642
  document.getElementById('btnStart').disabled = false;
 
 
643
  disableInput();
 
644
  document.getElementById('dialogueArea').innerHTML =
645
  `<div class="empty-state" id="emptyState">
646
  <div class="empty-icon">πŸŽ“</div>
@@ -714,16 +821,28 @@ function removeTyping() { document.getElementById('typingIndicator')?.remove();
714
  function showComplete(score, feedback) {
715
  const area = document.getElementById('dialogueArea');
716
  const div = document.createElement('div');
 
 
717
  div.innerHTML = `
718
- <div class="complete-banner">
719
- <div class="complete-left">
720
- <div class="complete-icon">${score >= 0.7 ? 'πŸ†' : score >= 0.5 ? 'βœ…' : 'πŸ“'}</div>
721
- <div>
722
- <div class="complete-title">Episode Complete</div>
723
- <div class="complete-sub">${feedback}</div>
 
 
724
  </div>
 
 
 
 
 
 
 
 
 
725
  </div>
726
- <div class="final-score">${score.toFixed(3)}</div>
727
  </div>`;
728
  area.appendChild(div);
729
  area.scrollTop = area.scrollHeight;
@@ -732,6 +851,56 @@ function showComplete(score, feedback) {
732
  score >= 0.7 ? '#3fb950' : score >= 0.5 ? '#d29922' : '#f85149';
733
  }
734
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
  function clearDialogue() { document.getElementById('dialogueArea').innerHTML = ''; }
736
 
737
  function enableInput() {
@@ -791,7 +960,6 @@ function saveToHistory(task, score) {
791
  </div>`).join('');
792
  }
793
 
794
- // ── Custom Task Generator ─────────────────────────────────
795
  async function generateTask() {
796
  const topic = document.getElementById('topicInput').value.trim();
797
  const difficulty = document.getElementById('genDifficulty').value;
@@ -823,11 +991,7 @@ async function generateTask() {
823
  } else {
824
  status.style.color = '#3fb950';
825
  status.textContent = `βœ… Ready! "${data.preview.substring(0, 60)}..."`;
826
-
827
- // Auto-select the matching task
828
  selectTask(data.task_id);
829
-
830
- // Clear input
831
  document.getElementById('topicInput').value = '';
832
  }
833
  } catch(e) {
 
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
  <title>SocraticEnv β€” Live Dashboard</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
  <style>
9
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
  body {
 
98
  }
99
  .turn-dot.done { background: #7c3aed; }
100
  .turn-dot.current { background: #a855f7; animation: pulse 1s infinite; }
101
+
102
+ /* NEW: Styling for the chart container */
103
+ .chart-container {
104
+ background: #0d1117; border: 1px solid #30363d;
105
+ border-radius: 8px; padding: 10px; margin-top: 8px;
106
+ }
107
+
108
  .main { display: flex; flex-direction: column; overflow: hidden; }
109
  .controls {
110
  background: #161b22; border-bottom: 1px solid #30363d;
 
318
  <div class="task-desc">Explain complex concepts using ONLY analogies. No technical jargon allowed!</div>
319
  </div>
320
  </div>
321
+
322
  <div class="sidebar-section">
323
  <div class="sidebar-title">Generate Custom Task</div>
324
  <div style="margin-bottom:8px;">
 
344
  </div>
345
  <div id="generateStatus" style="font-size:11px;color:#8b949e;min-height:16px;line-height:1.4;"></div>
346
  </div>
 
 
347
  <div class="score-grid">
348
  <div class="score-card full">
349
  <div class="score-value" id="overallScore">β€”</div>
 
358
  <div class="score-label">Last Reward</div>
359
  </div>
360
  </div>
361
+
362
+ <div class="sidebar-section" id="chartSection" style="display:none;">
363
+ <div class="sidebar-title">Score Progression</div>
364
+ <div class="chart-container">
365
+ <canvas id="scoreChart" width="100" height="60"></canvas>
366
+ </div>
367
  </div>
368
 
369
  <div class="sidebar-section">
 
436
  let sessionResults = [];
437
  let currentHistory = [];
438
 
439
+ // NEW: Globals for Chart and Export Data
440
+ let scoreChartInstance = null;
441
+ let exportData = null;
442
+
443
  async function checkStatus() {
444
  try {
445
  const r = await fetch(`${API}/ping`);
 
474
  document.getElementById('taskHint').textContent = hints[taskId];
475
  }
476
 
477
+ // NEW: Initialize the Chart.js graph
478
+ function initChart() {
479
+ const ctx = document.getElementById('scoreChart').getContext('2d');
480
+ if (scoreChartInstance) {
481
+ scoreChartInstance.destroy();
482
+ }
483
+
484
+ scoreChartInstance = new Chart(ctx, {
485
+ type: 'line',
486
+ data: {
487
+ labels: [],
488
+ datasets: [{
489
+ label: 'Reward Score',
490
+ data: [],
491
+ borderColor: '#a855f7',
492
+ backgroundColor: '#a855f720',
493
+ borderWidth: 2,
494
+ tension: 0.3,
495
+ fill: true,
496
+ pointBackgroundColor: '#e6edf3'
497
+ }]
498
+ },
499
+ options: {
500
+ responsive: true,
501
+ maintainAspectRatio: false,
502
+ scales: {
503
+ y: {
504
+ min: 0,
505
+ max: 1.05,
506
+ grid: { color: '#30363d' },
507
+ ticks: { color: '#8b949e', font: { size: 10 } }
508
+ },
509
+ x: {
510
+ grid: { display: false },
511
+ ticks: { color: '#8b949e', font: { size: 10 } }
512
+ }
513
+ },
514
+ plugins: {
515
+ legend: { display: false }
516
+ }
517
+ }
518
+ });
519
+ document.getElementById('chartSection').style.display = 'block';
520
+ }
521
+
522
+ // NEW: Update Chart data
523
+ function updateChart(turn, score) {
524
+ if (!scoreChartInstance) return;
525
+ scoreChartInstance.data.labels.push(`Turn ${turn}`);
526
+ scoreChartInstance.data.datasets[0].data.push(score);
527
+ scoreChartInstance.update();
528
+ }
529
+
530
  async function startEpisode() {
531
  clearDialogue();
532
  episodeActive = true;
533
  totalScore = 0;
534
  turnCount = 0;
535
  currentHistory = [];
536
+
537
+ // NEW: Reset export structure and chart
538
+ exportData = {
539
+ task: selectedTask,
540
+ timestamp: new Date().toLocaleString(),
541
+ turns: [],
542
+ final_score: 0,
543
+ feedback: ''
544
+ };
545
+ initChart();
546
  const maxMap = { factual_recall: 3, socratic_dialogue: 5, misconception_trap: 3, debate_mode: 4, analogy_challenge: 3 };
547
+
548
  maxTurns = maxMap[selectedTask];
549
  buildTurnTrack(maxTurns);
550
  updateScores();
 
561
  const data = await r.json();
562
  const question = data.observation.question;
563
  currentHistory.push({ role: 'tutor', content: question });
564
+
565
+ // NEW: Record initial state for export
566
+ exportData.turns.push({ turn: 0, tutor_opening: question });
567
+
568
  addTutorMessage(question);
569
  enableInput();
570
  document.getElementById('turnLabel').textContent = `Turn 1 of ${maxTurns}`;
 
599
  totalScore += score;
600
  updateScores(score);
601
  updateTurnTrack(turnCount);
602
+
603
+ // NEW: Update chart visually
604
+ updateChart(turnCount, score);
605
 
606
  const nextQuestion = data.observation.question;
607
  currentHistory.push({ role: 'tutor', content: nextQuestion });
608
+
609
+ // NEW: Save turn data for export
610
+ exportData.turns.push({
611
+ turn: turnCount,
612
+ agent_response: response,
613
+ reward_score: score,
614
+ breakdown: data.reward.breakdown,
615
+ tutor_next: nextQuestion
616
+ });
617
+
618
  addTutorMessage(nextQuestion, data.reward);
619
 
620
  if (data.done) {
621
  episodeActive = false;
622
  stopAutoRun();
623
  const avg = totalScore / turnCount;
624
+
625
+ // NEW: Finalize export data
626
+ exportData.final_score = avg;
627
+ exportData.feedback = data.reward.feedback;
628
+
629
  showComplete(avg, data.reward.feedback);
630
  saveToHistory(selectedTask, avg);
631
  document.getElementById('btnStart').disabled = false;
 
732
  episodeActive = false;
733
  autoRunning = false;
734
  currentHistory = [];
735
+ exportData = null;
736
  clearTimeout(autoRunTimer);
737
  stopAutoRun();
738
  clearDialogue();
739
  totalScore = 0; turnCount = 0;
740
+
741
  document.getElementById('overallScore').textContent = 'β€”';
742
  document.getElementById('turnCount').textContent = '0';
743
  document.getElementById('lastReward').textContent = 'β€”';
744
  document.getElementById('turnTrack').innerHTML = '';
745
  document.getElementById('turnLabel').textContent = 'No active episode';
746
  document.getElementById('btnStart').disabled = false;
747
+ document.getElementById('chartSection').style.display = 'none';
748
+ if(scoreChartInstance) scoreChartInstance.destroy();
749
  disableInput();
750
+
751
  document.getElementById('dialogueArea').innerHTML =
752
  `<div class="empty-state" id="emptyState">
753
  <div class="empty-icon">πŸŽ“</div>
 
821
  function showComplete(score, feedback) {
822
  const area = document.getElementById('dialogueArea');
823
  const div = document.createElement('div');
824
+
825
+ // NEW: Added the Export buttons to the completion banner
826
  div.innerHTML = `
827
+ <div style="display:flex; flex-direction:column; gap:12px;">
828
+ <div class="complete-banner">
829
+ <div class="complete-left">
830
+ <div class="complete-icon">${score >= 0.7 ? 'πŸ†' : score >= 0.5 ? 'βœ…' : 'πŸ“'}</div>
831
+ <div>
832
+ <div class="complete-title">Episode Complete</div>
833
+ <div class="complete-sub">${feedback}</div>
834
+ </div>
835
  </div>
836
+ <div class="final-score">${score.toFixed(3)}</div>
837
+ </div>
838
+ <div style="display:flex; gap:10px;">
839
+ <button class="btn btn-secondary" style="flex:1; justify-content:center" onclick="downloadExport('json')">
840
+ πŸ“„ Download JSON Data
841
+ </button>
842
+ <button class="btn btn-secondary" style="flex:1; justify-content:center" onclick="downloadExport('txt')">
843
+ πŸ“ Download Readable Report
844
+ </button>
845
  </div>
 
846
  </div>`;
847
  area.appendChild(div);
848
  area.scrollTop = area.scrollHeight;
 
851
  score >= 0.7 ? '#3fb950' : score >= 0.5 ? '#d29922' : '#f85149';
852
  }
853
 
854
+ // NEW: Functions to handle downloading the Episode Report
855
+ function downloadExport(format) {
856
+ if (!exportData) return;
857
+
858
+ let content = "";
859
+ let mimeType = "";
860
+ let filename = `SocraticEnv_${exportData.task}_${Date.now()}`;
861
+
862
+ if (format === 'json') {
863
+ content = JSON.stringify(exportData, null, 2);
864
+ mimeType = "application/json";
865
+ filename += ".json";
866
+ } else if (format === 'txt') {
867
+ // Generate a beautifully formatted Markdown/Text report
868
+ content = `================================================
869
+ SOCRATIC ENV β€” EVALUATION REPORT
870
+ ================================================
871
+ Task: ${exportData.task}
872
+ Timestamp: ${exportData.timestamp}
873
+ Final Score: ${exportData.final_score.toFixed(3)}
874
+ Feedback: ${exportData.feedback}
875
+ ------------------------------------------------\n\n`;
876
+
877
+ exportData.turns.forEach(t => {
878
+ if (t.turn === 0) {
879
+ content += `[TUTOR OPENING]\n${t.tutor_opening}\n\n`;
880
+ } else {
881
+ content += `[TURN ${t.turn}]\n`;
882
+ content += `Agent: ${t.agent_response}\n`;
883
+ content += `Reward: ${t.reward_score.toFixed(3)}\n`;
884
+ content += `Breakdown: ${JSON.stringify(t.breakdown)}\n`;
885
+ content += `Tutor: ${t.tutor_next}\n\n`;
886
+ }
887
+ });
888
+
889
+ mimeType = "text/plain";
890
+ filename += ".txt";
891
+ }
892
+
893
+ const blob = new Blob([content], { type: mimeType });
894
+ const url = URL.createObjectURL(blob);
895
+ const a = document.createElement("a");
896
+ a.href = url;
897
+ a.download = filename;
898
+ document.body.appendChild(a);
899
+ a.click();
900
+ document.body.removeChild(a);
901
+ URL.revokeObjectURL(url);
902
+ }
903
+
904
  function clearDialogue() { document.getElementById('dialogueArea').innerHTML = ''; }
905
 
906
  function enableInput() {
 
960
  </div>`).join('');
961
  }
962
 
 
963
  async function generateTask() {
964
  const topic = document.getElementById('topicInput').value.trim();
965
  const difficulty = document.getElementById('genDifficulty').value;
 
991
  } else {
992
  status.style.color = '#3fb950';
993
  status.textContent = `βœ… Ready! "${data.preview.substring(0, 60)}..."`;
 
 
994
  selectTask(data.task_id);
 
 
995
  document.getElementById('topicInput').value = '';
996
  }
997
  } catch(e) {