Deadman922 commited on
Commit
1d056bd
Β·
verified Β·
1 Parent(s): 377ec8c

Upload index.html

Browse files
Files changed (1) hide show
  1. static/index.html +326 -3
static/index.html CHANGED
@@ -527,6 +527,123 @@
527
  ::-webkit-scrollbar-track { background: transparent; }
528
  ::-webkit-scrollbar-thumb { background: var(--card-border); border-radius: 3px; }
529
  ::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  </style>
531
  </head>
532
  <body>
@@ -569,6 +686,44 @@
569
  <!-- MAIN CONTENT -->
570
  <main class="main">
571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  <!-- ROW 1: Bento Grid (3 columns) -->
573
  <div class="bento-row bento-3">
574
 
@@ -1003,7 +1158,7 @@ async function runQuery() {
1003
  output.innerHTML = '<div class="output-error">' + escapeHtml(feedback) +
1004
  '<div class="error-tip">Tip: Use SELECT statements only in this panel. For data modification, use the FIX tab or Submit.</div></div>';
1005
  } else {
1006
- output.textContent = formatResult(r.data);
1007
  }
1008
  } else {
1009
  output.innerHTML = '<div class="output-error">Error: ' + escapeHtml(r.data.detail || 'Unknown error') + '</div>';
@@ -1114,7 +1269,7 @@ async function submitEpisode() {
1114
  state.done = r.data.done || false;
1115
  state.score = r.data.reward?.score ?? state.score;
1116
  updateResults(r.data);
1117
- output.textContent = formatResult(r.data);
1118
  if (state.done) {
1119
  updateStatusPill('done');
1120
  toast('Episode complete! Score: ' + (state.score * 100).toFixed(1) + '%', 'success');
@@ -1214,7 +1369,175 @@ function updateResults(stepData) {
1214
 
1215
  // ─── UTILITIES ─────────────────────────────────────────────────────────
1216
  function formatResult(data) {
1217
- return JSON.stringify(data, null, 2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1218
  }
1219
 
1220
  function escapeHtml(str) {
 
527
  ::-webkit-scrollbar-track { background: transparent; }
528
  ::-webkit-scrollbar-thumb { background: var(--card-border); border-radius: 3px; }
529
  ::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
530
+
531
+ /* ─── HELP / HOW TO USE ───────────────────────────────────────────── */
532
+ .help-section {
533
+ background: linear-gradient(135deg, #E8F5E9 0%, #F1F8E9 100%);
534
+ border: 1px solid #A5D6A7;
535
+ border-radius: var(--radius);
536
+ padding: 16px;
537
+ margin-bottom: 16px;
538
+ }
539
+ .help-title {
540
+ font-size: 14px;
541
+ font-weight: 500;
542
+ color: var(--primary);
543
+ margin-bottom: 12px;
544
+ display: flex;
545
+ align-items: center;
546
+ gap: 8px;
547
+ }
548
+ .help-steps {
549
+ display: grid;
550
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
551
+ gap: 12px;
552
+ }
553
+ .help-step {
554
+ background: var(--bg);
555
+ border: 1px solid var(--card-border);
556
+ border-radius: var(--radius);
557
+ padding: 12px;
558
+ }
559
+ .help-step-num {
560
+ display: inline-flex;
561
+ align-items: center;
562
+ justify-content: center;
563
+ width: 20px;
564
+ height: 20px;
565
+ background: var(--primary);
566
+ color: white;
567
+ font-size: 11px;
568
+ font-weight: 500;
569
+ border-radius: 50%;
570
+ margin-bottom: 6px;
571
+ }
572
+ .help-step-title {
573
+ font-size: 12px;
574
+ font-weight: 500;
575
+ color: var(--text-primary);
576
+ margin-bottom: 4px;
577
+ }
578
+ .help-step-desc {
579
+ font-size: 11px;
580
+ color: var(--text-secondary);
581
+ line-height: 1.4;
582
+ }
583
+ .help-actions {
584
+ display: flex;
585
+ gap: 8px;
586
+ margin-top: 12px;
587
+ padding-top: 12px;
588
+ border-top: 1px solid #A5D6A7;
589
+ }
590
+ .help-badge {
591
+ display: inline-flex;
592
+ align-items: center;
593
+ padding: 2px 6px;
594
+ font-size: 10px;
595
+ background: var(--bg);
596
+ border: 1px solid var(--card-border);
597
+ border-radius: 4px;
598
+ color: var(--text-secondary);
599
+ }
600
+
601
+ /* ─── FORMATTED RESULT TABLE ────────────────────────────────────────── */
602
+ .result-table {
603
+ width: 100%;
604
+ border-collapse: collapse;
605
+ font-size: 12px;
606
+ margin-top: 8px;
607
+ }
608
+ .result-table th {
609
+ text-align: left;
610
+ padding: 8px 10px;
611
+ background: var(--surface);
612
+ color: var(--text-secondary);
613
+ font-weight: 500;
614
+ border-bottom: 2px solid var(--card-border);
615
+ white-space: nowrap;
616
+ }
617
+ .result-table td {
618
+ padding: 6px 10px;
619
+ border-bottom: 1px solid var(--card-border);
620
+ color: var(--text-primary);
621
+ max-width: 200px;
622
+ overflow: hidden;
623
+ text-overflow: ellipsis;
624
+ }
625
+ .result-table tr:hover td {
626
+ background: var(--surface);
627
+ }
628
+ .result-summary {
629
+ padding: 10px 12px;
630
+ background: var(--surface);
631
+ border-radius: var(--radius);
632
+ margin-bottom: 12px;
633
+ font-size: 12px;
634
+ color: var(--text-secondary);
635
+ }
636
+ .result-summary strong {
637
+ color: var(--text-primary);
638
+ }
639
+ .result-null {
640
+ color: var(--text-tertiary);
641
+ font-style: italic;
642
+ }
643
+ .result-number {
644
+ color: var(--success);
645
+ font-weight: 500;
646
+ }
647
  </style>
648
  </head>
649
  <body>
 
686
  <!-- MAIN CONTENT -->
687
  <main class="main">
688
 
689
+ <!-- HOW TO USE SECTION -->
690
+ <div class="help-section" id="help-section">
691
+ <div class="help-title">
692
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
693
+ <circle cx="12" cy="12" r="10"/>
694
+ <path d="M12 16v-4M12 8h.01"/>
695
+ </svg>
696
+ How to Use This Application
697
+ </div>
698
+ <div class="help-steps">
699
+ <div class="help-step">
700
+ <span class="help-step-num">1</span>
701
+ <div class="help-step-title">Select Task</div>
702
+ <div class="help-step-desc">Choose a data quality task from the dropdown. Each has a specific data issue to investigate.</div>
703
+ </div>
704
+ <div class="help-step">
705
+ <span class="help-step-num">2</span>
706
+ <div class="help-step-title">Investigate</div>
707
+ <div class="help-step-desc">Use <strong>Query</strong> (SELECT only) to explore data. Use <strong>Profile</strong> to check column stats. Review the Schema panel.</div>
708
+ </div>
709
+ <div class="help-step">
710
+ <span class="help-step-num">3</span>
711
+ <div class="help-step-title">Identify Issue</div>
712
+ <div class="help-step-desc">Find the data quality problem: null values, type mismatches, duplicates, or orphans.</div>
713
+ </div>
714
+ <div class="help-step">
715
+ <span class="help-step-num">4</span>
716
+ <div class="help-step-title">Submit Fix</div>
717
+ <div class="help-step-desc">Use the appropriate tab: <strong>Text Answer</strong> for null_audit, <strong>SQL Fix</strong> for repairs, <strong>Dedup</strong> for duplicates.</div>
718
+ </div>
719
+ </div>
720
+ <div class="help-actions">
721
+ <span class="help-badge">Query = SELECT only</span>
722
+ <span class="help-badge">Fix = UPDATE/INSERT/DELETE allowed</span>
723
+ <span class="help-badge">Submit = Final answer</span>
724
+ </div>
725
+ </div>
726
+
727
  <!-- ROW 1: Bento Grid (3 columns) -->
728
  <div class="bento-row bento-3">
729
 
 
1158
  output.innerHTML = '<div class="output-error">' + escapeHtml(feedback) +
1159
  '<div class="error-tip">Tip: Use SELECT statements only in this panel. For data modification, use the FIX tab or Submit.</div></div>';
1160
  } else {
1161
+ output.innerHTML = formatResult(r.data);
1162
  }
1163
  } else {
1164
  output.innerHTML = '<div class="output-error">Error: ' + escapeHtml(r.data.detail || 'Unknown error') + '</div>';
 
1269
  state.done = r.data.done || false;
1270
  state.score = r.data.reward?.score ?? state.score;
1271
  updateResults(r.data);
1272
+ output.innerHTML = formatResult(r.data);
1273
  if (state.done) {
1274
  updateStatusPill('done');
1275
  toast('Episode complete! Score: ' + (state.score * 100).toFixed(1) + '%', 'success');
 
1369
 
1370
  // ─── UTILITIES ─────────────────────────────────────────────────────────
1371
  function formatResult(data) {
1372
+ // Format as readable table instead of raw JSON
1373
+ if (!data) return 'No data';
1374
+
1375
+ // Handle array of objects (query results)
1376
+ if (Array.isArray(data)) {
1377
+ if (data.length === 0) return '<div class="result-summary">0 rows returned</div>';
1378
+ return formatArrayAsTable(data);
1379
+ }
1380
+
1381
+ // Handle nested observation structure
1382
+ if (data.observation && data.observation.investigation_hint) {
1383
+ return formatInvestigationHint(data.observation.investigation_hint);
1384
+ }
1385
+
1386
+ // Handle reward feedback (text feedback)
1387
+ if (data.reward && data.reward.feedback) {
1388
+ return formatFeedback(data.reward.feedback);
1389
+ }
1390
+
1391
+ // Default: format as readable object
1392
+ return formatObjectAsReadable(data);
1393
+ }
1394
+
1395
+ function formatArrayAsTable(arr) {
1396
+ if (!arr || arr.length === 0) return '<div class="result-summary">0 rows returned</div>';
1397
+
1398
+ const columns = Object.keys(arr[0]);
1399
+ let html = '<div class="result-summary"><strong>' + arr.length + '</strong> row' + (arr.length > 1 ? 's' : '') + ' returned</div>';
1400
+
1401
+ html += '<table class="result-table"><thead><tr>';
1402
+ columns.forEach(col => {
1403
+ html += '<th>' + escapeHtml(col) + '</th>';
1404
+ });
1405
+ html += '</tr></thead><tbody>';
1406
+
1407
+ // Show up to 20 rows
1408
+ const displayRows = arr.slice(0, 20);
1409
+ displayRows.forEach(row => {
1410
+ html += '<tr>';
1411
+ columns.forEach(col => {
1412
+ const val = row[col];
1413
+ let formatted = formatCellValue(val);
1414
+ html += '<td>' + formatted + '</td>';
1415
+ });
1416
+ html += '</tr>';
1417
+ });
1418
+
1419
+ html += '</tbody></table>';
1420
+
1421
+ if (arr.length > 20) {
1422
+ html += '<div class="result-summary" style="margin-top:8px;">... and ' + (arr.length - 20) + ' more rows</div>';
1423
+ }
1424
+
1425
+ return html;
1426
+ }
1427
+
1428
+ function formatCellValue(val) {
1429
+ if (val === null || val === undefined) {
1430
+ return '<span class="result-null">NULL</span>';
1431
+ }
1432
+ if (typeof val === 'number') {
1433
+ return '<span class="result-number">' + escapeHtml(String(val)) + '</span>';
1434
+ }
1435
+ if (typeof val === 'boolean') {
1436
+ return '<span class="result-number">' + (val ? 'true' : 'false') + '</span>';
1437
+ }
1438
+ const str = String(val);
1439
+ if (str.length > 50) {
1440
+ return escapeHtml(str.substring(0, 50)) + '...';
1441
+ }
1442
+ return escapeHtml(str);
1443
+ }
1444
+
1445
+ function formatInvestigationHint(hint) {
1446
+ // Try to parse as JSON array
1447
+ try {
1448
+ const parsed = JSON.parse(hint.replace(/'/g, '"'));
1449
+ if (Array.isArray(parsed)) {
1450
+ return formatArrayAsTable(parsed);
1451
+ }
1452
+ } catch (e) {
1453
+ // Not valid JSON, show as text
1454
+ }
1455
+
1456
+ // If it looks like a list of records, try to format
1457
+ if (hint.includes('column_name') && hint.includes('null_count')) {
1458
+ // Parse null audit result format
1459
+ const rows = parseNullAuditResult(hint);
1460
+ if (rows.length > 0) {
1461
+ return formatArrayAsTable(rows);
1462
+ }
1463
+ }
1464
+
1465
+ return '<div class="result-summary">Query Result</div><div style="padding:12px;">' + escapeHtml(hint) + '</div>';
1466
+ }
1467
+
1468
+ function parseNullAuditResult(hint) {
1469
+ const rows = [];
1470
+ // Match patterns like {'column_name': 'city', 'null_count': 520, ...}
1471
+ const regex = /\{[^}]+\}/g;
1472
+ const matches = hint.match(regex);
1473
+ if (matches) {
1474
+ matches.forEach(match => {
1475
+ try {
1476
+ // Convert Python-style dict to JSON
1477
+ const jsonStr = match
1478
+ .replace(/'/g, '"')
1479
+ .replace(/(\w+):/g, '"$1":');
1480
+ const obj = JSON.parse(jsonStr);
1481
+ rows.push(obj);
1482
+ } catch (e) {}
1483
+ });
1484
+ }
1485
+ return rows;
1486
+ }
1487
+
1488
+ function formatFeedback(feedback) {
1489
+ // Format feedback text with better styling
1490
+ if (feedback.includes('|')) {
1491
+ // Multi-part feedback with dimensions
1492
+ const parts = feedback.split('|').map(p => p.trim()).filter(p => p);
1493
+ let html = '<div class="result-summary"><strong>Score Breakdown</strong></div>';
1494
+ html += '<div style="padding:12px;">';
1495
+ parts.forEach(part => {
1496
+ const [label, value] = part.split(':').map(s => s.trim());
1497
+ if (label && value) {
1498
+ html += '<div style="margin-bottom:6px;"><strong>' + escapeHtml(label) + ':</strong> ' + escapeHtml(value) + '</div>';
1499
+ } else {
1500
+ html += '<div style="margin-bottom:6px;">' + escapeHtml(part) + '</div>';
1501
+ }
1502
+ });
1503
+ html += '</div>';
1504
+ return html;
1505
+ }
1506
+
1507
+ // Simple feedback text
1508
+ return '<div style="padding:12px;">' + escapeHtml(feedback) + '</div>';
1509
+ }
1510
+
1511
+ function formatObjectAsReadable(obj) {
1512
+ let html = '<div style="padding:12px;">';
1513
+
1514
+ function renderValue(val, indent) {
1515
+ if (val === null) return '<span class="result-null">null</span>';
1516
+ if (typeof val === 'object') {
1517
+ if (Array.isArray(val)) {
1518
+ if (val.length === 0) return '[]';
1519
+ if (typeof val[0] === 'object') {
1520
+ return formatArrayAsTable(val);
1521
+ }
1522
+ return '[' + val.map(v => renderValue(v, indent)).join(', ') + ']';
1523
+ }
1524
+ // Object
1525
+ let objHtml = '<div style="margin-left:' + (indent * 16) + 'px;">';
1526
+ for (const [k, v] of Object.entries(val)) {
1527
+ objHtml += '<div><strong>' + escapeHtml(k) + ':</strong> ' + renderValue(v, indent + 1) + '</div>';
1528
+ }
1529
+ objHtml += '</div>';
1530
+ return objHtml;
1531
+ }
1532
+ return escapeHtml(String(val));
1533
+ }
1534
+
1535
+ for (const [key, value] of Object.entries(obj)) {
1536
+ html += '<div style="margin-bottom:8px;"><strong>' + escapeHtml(key) + ':</strong> ' + renderValue(value, 1) + '</div>';
1537
+ }
1538
+
1539
+ html += '</div>';
1540
+ return html;
1541
  }
1542
 
1543
  function escapeHtml(str) {