Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Data Cleaning OpenEnv</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { font-family: 'Segoe UI', sans-serif; background: #0f1117; color: #e0e0e0; min-height: 100vh; } | |
| .header { background: linear-gradient(135deg, #1a1f2e, #2d3561); padding: 30px 40px; border-bottom: 2px solid #3d4f7c; display: flex; align-items: center; gap: 20px; } | |
| .header-icon { font-size: 48px; } | |
| .header h1 { font-size: 28px; color: #7eb8f7; margin-bottom: 6px; } | |
| .header p { color: #8892b0; font-size: 14px; } | |
| .badge { background: #2ecc71; color: #000; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; margin-left: 10px; } | |
| .container { max-width: 1200px; margin: 0 auto; padding: 30px 20px; } | |
| .stats-bar { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 30px; } | |
| .stat-card { background: #1a1f2e; border: 1px solid #2d3561; border-radius: 12px; padding: 20px; text-align: center; } | |
| .stat-value { font-size: 32px; font-weight: bold; color: #7eb8f7; } | |
| .stat-label { font-size: 12px; color: #8892b0; margin-top: 4px; } | |
| .section-title { font-size: 18px; color: #7eb8f7; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #2d3561; } | |
| .tasks-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 30px; } | |
| .task-card { background: #1a1f2e; border: 1px solid #2d3561; border-radius: 12px; padding: 20px; cursor: pointer; transition: all 0.2s; } | |
| .task-card:hover { border-color: #7eb8f7; transform: translateY(-2px); } | |
| .task-card.active { border-color: #7eb8f7; background: #1e2a3a; } | |
| .task-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } | |
| .task-name { font-size: 14px; font-weight: bold; color: #e0e0e0; } | |
| .difficulty { padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: bold; } | |
| .easy { background: #1a472a; color: #2ecc71; } | |
| .medium { background: #472a1a; color: #f39c12; } | |
| .hard { background: #471a1a; color: #e74c3c; } | |
| .expert { background: #3d1a47; color: #9b59b6; } | |
| .task-desc { font-size: 12px; color: #8892b0; line-height: 1.5; } | |
| .task-score { margin-top: 12px; display: flex; align-items: center; gap: 8px; } | |
| .score-bar-bg { flex: 1; height: 6px; background: #2d3561; border-radius: 3px; overflow: hidden; } | |
| .score-bar-fill { height: 100%; border-radius: 3px; background: linear-gradient(90deg, #3498db, #2ecc71); } | |
| .score-text { font-size: 12px; color: #7eb8f7; min-width: 40px; } | |
| .demo-section { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; } | |
| .panel { background: #1a1f2e; border: 1px solid #2d3561; border-radius: 12px; padding: 20px; } | |
| .panel-title { font-size: 14px; font-weight: bold; color: #7eb8f7; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; } | |
| .table-wrapper { overflow-x: auto; max-height: 200px; overflow-y: auto; } | |
| table { width: 100%; border-collapse: collapse; font-size: 12px; } | |
| th { background: #2d3561; color: #7eb8f7; padding: 8px 12px; text-align: left; position: sticky; top: 0; } | |
| td { padding: 6px 12px; border-bottom: 1px solid #1e2438; color: #c0c0c0; } | |
| tr:hover td { background: #1e2438; } | |
| .null-cell { color: #e74c3c; font-style: italic; } | |
| .actions-list { display: flex; flex-direction: column; gap: 8px; } | |
| .action-btn { background: #1e2438; border: 1px solid #2d3561; border-radius: 8px; padding: 10px 16px; color: #e0e0e0; cursor: pointer; font-size: 13px; text-align: left; transition: all 0.2s; display: flex; justify-content: space-between; align-items: center; } | |
| .action-btn:hover:not(:disabled) { border-color: #7eb8f7; background: #253050; } | |
| .action-btn:disabled { opacity: 0.4; cursor: not-allowed; } | |
| .action-btn.used { border-color: #2ecc71; color: #2ecc71; } | |
| .action-tag { font-size: 10px; padding: 2px 8px; border-radius: 10px; background: #2d3561; color: #8892b0; } | |
| .reward-display { text-align: center; padding: 10px; } | |
| .reward-circle { width: 100px; height: 100px; border-radius: 50%; border: 4px solid #2d3561; display: flex; flex-direction: column; align-items: center; justify-content: center; margin: 0 auto 16px; background: #0f1117; transition: all 0.3s; } | |
| .reward-number { font-size: 24px; font-weight: bold; color: #7eb8f7; } | |
| .reward-label { font-size: 10px; color: #8892b0; } | |
| .reward-components { display: flex; flex-direction: column; gap: 6px; text-align: left; } | |
| .component-row { display: flex; align-items: center; gap: 8px; font-size: 12px; } | |
| .component-name { width: 130px; color: #8892b0; } | |
| .component-bar-bg { flex: 1; height: 8px; background: #2d3561; border-radius: 4px; overflow: hidden; } | |
| .component-bar-fill { height: 100%; border-radius: 4px; transition: width 0.5s; } | |
| .component-val { width: 40px; text-align: right; color: #7eb8f7; } | |
| .message-log { background: #0f1117; border: 1px solid #2d3561; border-radius: 8px; padding: 12px; max-height: 140px; overflow-y: auto; font-family: monospace; font-size: 12px; } | |
| .log-entry { padding: 3px 0; border-bottom: 1px solid #1a1f2e; color: #8892b0; } | |
| .log-entry.success { color: #2ecc71; } | |
| .log-entry.info { color: #7eb8f7; } | |
| .log-entry.warning { color: #f39c12; } | |
| .controls { display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; align-items: center; } | |
| .btn { padding: 10px 24px; border-radius: 8px; border: none; cursor: pointer; font-size: 14px; font-weight: bold; transition: all 0.2s; } | |
| .btn-primary { background: #3d6ef7; color: white; } | |
| .btn-primary:hover { background: #5a87ff; } | |
| .btn-secondary { background: #2d3561; color: #e0e0e0; } | |
| .btn-secondary:hover { background: #3d4f7c; } | |
| .done-banner { background: #1a472a; border: 1px solid #2ecc71; border-radius: 8px; padding: 12px 20px; color: #2ecc71; text-align: center; font-weight: bold; display: none; margin-top: 12px; } | |
| .api-section { margin-bottom: 30px; } | |
| .endpoint-list { display: flex; flex-direction: column; gap: 8px; } | |
| .endpoint { background: #1a1f2e; border: 1px solid #2d3561; border-radius: 8px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; font-size: 13px; } | |
| .method { padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: bold; min-width: 45px; text-align: center; } | |
| .get { background: #1a472a; color: #2ecc71; } | |
| .post { background: #472a1a; color: #f39c12; } | |
| .endpoint-path { color: #7eb8f7; font-family: monospace; flex: 1; } | |
| .endpoint-desc { color: #8892b0; font-size: 12px; } | |
| .endpoint a { color: #7eb8f7; text-decoration: none; } | |
| .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #2ecc71; display: inline-block; margin-right: 6px; animation: pulse 2s infinite; } | |
| @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } | |
| .loading { display: inline-block; width: 16px; height: 16px; border: 2px solid #2d3561; border-top-color: #7eb8f7; border-radius: 50%; animation: spin 0.8s linear infinite; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .lb-table { width: 100%; border-collapse: collapse; font-size: 13px; } | |
| .lb-table th { background: #2d3561; color: #7eb8f7; padding: 8px 12px; text-align: left; } | |
| .lb-table td { padding: 8px 12px; border-bottom: 1px solid #1e2438; color: #c0c0c0; } | |
| .lb-table tr:hover td { background: #1e2438; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <div class="header-icon">🧹</div> | |
| <div> | |
| <h1>Data Cleaning OpenEnv (Team Garuda)<span class="badge">● LIVE</span></h1> | |
| <p>An OpenEnv-compliant environment where AI agents learn to clean messy real-world datasets</p> | |
| </div> | |
| </div> | |
| <div class="container"> | |
| <!-- Stats --> | |
| <div class="stats-bar"> | |
| <div class="stat-card"> | |
| <div class="stat-value">4</div> | |
| <div class="stat-label">Tasks Available</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">6</div> | |
| <div class="stat-label">Cleaning Operations</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="live-avg-score">1.00</div> | |
| <div class="stat-label">Baseline Avg Score</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value"><span class="status-dot"></span>OK</div> | |
| <div class="stat-label">Environment Status</div> | |
| </div> | |
| </div> | |
| <!-- Tasks --> | |
| <div class="section-title">📋 Available Tasks</div> | |
| <div class="tasks-grid"> | |
| <div class="task-card active" onclick="selectTask('easy_dedup_rename')" id="card-easy"> | |
| <div class="task-header"> | |
| <div class="task-name">Deduplicate & Rename</div> | |
| <div class="difficulty easy">EASY</div> | |
| </div> | |
| <div class="task-desc">Remove duplicate rows and rename columns to snake_case in an employee dataset.</div> | |
| <div class="task-score"> | |
| <div class="score-bar-bg"><div class="score-bar-fill" style="width:100%"></div></div> | |
| <div class="score-text">1.00</div> | |
| </div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('medium_missing_dtype')" id="card-medium"> | |
| <div class="task-header"> | |
| <div class="task-name">Missing Values & Types</div> | |
| <div class="difficulty medium">MEDIUM</div> | |
| </div> | |
| <div class="task-desc">Fill missing values using correct strategies and fix wrong data types in a customer dataset.</div> | |
| <div class="task-score"> | |
| <div class="score-bar-bg"><div class="score-bar-fill" style="width:100%"></div></div> | |
| <div class="score-text">1.00</div> | |
| </div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('hard_full_pipeline')" id="card-hard"> | |
| <div class="task-header"> | |
| <div class="task-name">Full Pipeline</div> | |
| <div class="difficulty hard">HARD</div> | |
| </div> | |
| <div class="task-desc">Complete cleaning pipeline: duplicates + missing + types + outliers + schema validation.</div> | |
| <div class="task-score"> | |
| <div class="score-bar-bg"><div class="score-bar-fill" style="width:100%"></div></div> | |
| <div class="score-text">1.00</div> | |
| </div> | |
| </div> | |
| <div class="task-card" onclick="selectTask('expert_sales_pipeline')" id="card-expert"> | |
| <div class="task-header"> | |
| <div class="task-name">Sales Pipeline</div> | |
| <div class="difficulty expert">EXPERT</div> | |
| </div> | |
| <div class="task-desc">Expert level: sales data with duplicates + missing + case standardization + outliers + schema.</div> | |
| <div class="task-score"> | |
| <div class="score-bar-bg"><div class="score-bar-fill" style="width:100%"></div></div> | |
| <div class="score-text">1.00</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Live Demo --> | |
| <div class="section-title">🎮 Live Demo — Try It Yourself</div> | |
| <div class="controls"> | |
| <button class="btn btn-primary" onclick="resetEnv()" id="reset-btn">🔄 Reset Environment</button> | |
| <button class="btn btn-secondary" onclick="resetAll()">⟳ Start Over</button> | |
| <span id="task-label" style="align-self:center;color:#8892b0;font-size:13px;">Task: easy_dedup_rename</span> | |
| </div> | |
| <div class="demo-section"> | |
| <div style="display:flex;flex-direction:column;gap:16px;"> | |
| <div class="panel"> | |
| <div class="panel-title">📊 Current Dataset | |
| <span id="shape-badge" style="font-size:11px;color:#8892b0;font-weight:normal;">— reset to load</span> | |
| </div> | |
| <div class="table-wrapper" id="table-wrapper"> | |
| <div style="color:#8892b0;font-size:13px;padding:20px;text-align:center;">Click Reset Environment to load dirty dataset</div> | |
| </div> | |
| </div> | |
| <div class="panel"> | |
| <div class="panel-title">⚡ Available Actions</div> | |
| <div class="actions-list" id="actions-list"> | |
| <div style="color:#8892b0;font-size:13px;">Reset first to enable actions</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:16px;"> | |
| <div class="panel"> | |
| <div class="panel-title">🏆 Reward Score</div> | |
| <div class="reward-display"> | |
| <div class="reward-circle" id="reward-circle"> | |
| <div class="reward-number" id="reward-number">—</div> | |
| <div class="reward-label">SCORE</div> | |
| </div> | |
| <div class="reward-components" id="reward-components"> | |
| <div style="color:#8892b0;font-size:12px;text-align:center;">Reset to see breakdown</div> | |
| </div> | |
| </div> | |
| <div class="done-banner" id="done-banner">✅ Episode Complete!</div> | |
| <!-- Submit to leaderboard --> | |
| <div id="submit-section" style="display:none;margin-top:12px;"> | |
| <div style="font-size:12px;color:#8892b0;margin-bottom:8px;"> | |
| 🏅 Submit your score to leaderboard: | |
| </div> | |
| <div style="display:flex;gap:8px;"> | |
| <input | |
| id="model-name-input" | |
| type="text" | |
| placeholder="Your model/name (e.g. gpt-4o-mini)" | |
| style="flex:1;background:#0f1117;border:1px solid #2d3561; | |
| border-radius:6px;padding:8px 12px;color:#e0e0e0; | |
| font-size:13px;outline:none;" | |
| /> | |
| <button | |
| onclick="submitToLeaderboard()" | |
| style="background:#2ecc71;color:#000;border:none; | |
| border-radius:6px;padding:8px 16px;cursor:pointer; | |
| font-weight:bold;font-size:13px;"> | |
| Submit | |
| </button> | |
| </div> | |
| <div id="submit-msg" style="font-size:11px;margin-top:6px;"></div> | |
| </div> | |
| </div> | |
| <div class="panel"> | |
| <div class="panel-title">📝 Action Log</div> | |
| <div class="message-log" id="message-log"> | |
| <div class="log-entry info">Waiting for reset...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Leaderboard --> | |
| <div class="api-section"> | |
| <div class="section-title">🏅 Leaderboard</div> | |
| <div id="leaderboard-table" style="background:#1a1f2e;border:1px solid #2d3561;border-radius:12px;padding:20px;"> | |
| <div style="color:#8892b0;text-align:center;">Loading leaderboard...</div> | |
| </div> | |
| </div> | |
| <!-- API Endpoints --> | |
| <div class="api-section"> | |
| <div class="section-title">🔌 API Endpoints</div> | |
| <div class="endpoint-list"> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="endpoint-path">/health</span> | |
| <span class="endpoint-desc">Health check</span> | |
| <a href="/health" target="_blank">Try →</a> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="endpoint-path">/tasks</span> | |
| <span class="endpoint-desc">List all tasks</span> | |
| <a href="/tasks" target="_blank">Try →</a> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method post">POST</span> | |
| <span class="endpoint-path">/reset/{task_id}</span> | |
| <span class="endpoint-desc">Reset environment</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method post">POST</span> | |
| <span class="endpoint-path">/step/{task_id}</span> | |
| <span class="endpoint-desc">Take action, get reward</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="endpoint-path">/state/{task_id}</span> | |
| <span class="endpoint-desc">Current state</span> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="endpoint-path">/leaderboard</span> | |
| <span class="endpoint-desc">View rankings</span> | |
| <a href="/leaderboard" target="_blank">Try →</a> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="endpoint-path">/validate</span> | |
| <span class="endpoint-desc">OpenEnv validation</span> | |
| <a href="/validate" target="_blank">Try →</a> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> | |
| <span class="endpoint-path">/docs</span> | |
| <span class="endpoint-desc">API documentation</span> | |
| <a href="/docs" target="_blank">Try →</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const BASE = window.location.origin; | |
| let currentTask = 'easy_dedup_rename'; | |
| let episodeDone = false; | |
| let stepCount = 0; | |
| let usedActions = new Set(); | |
| const TASK_OPERATIONS = { | |
| 'easy_dedup_rename': ['remove_duplicates', 'rename_columns', 'finish'], | |
| 'medium_missing_dtype': ['fill_missing_mean', 'fill_missing_mode', 'fill_missing_median', 'fix_dtype', 'finish'], | |
| 'hard_full_pipeline': ['remove_duplicates', 'fill_missing_mean', 'fill_missing_mode', 'fill_missing_median', 'fix_dtype', 'remove_outliers', 'validate_schema', 'finish'], | |
| 'expert_sales_pipeline': ['remove_duplicates', 'rename_columns', 'fill_missing_mean', 'fill_missing_mode', 'fill_missing_median', 'fix_dtype', 'remove_outliers', 'validate_schema', 'finish'] | |
| }; | |
| const ACTION_PARAMS = { | |
| 'remove_duplicates': {}, | |
| 'rename_columns': {}, | |
| 'fill_missing_mean': {strategy: 'mean'}, | |
| 'fill_missing_mode': {strategy: 'mode'}, | |
| 'fill_missing_median': {strategy: 'median'}, | |
| 'fix_dtype': {dtype: 'auto'}, | |
| 'remove_outliers': {method: 'iqr'}, | |
| 'validate_schema': {}, | |
| 'finish': {} | |
| }; | |
| const ACTION_DESC = { | |
| 'remove_duplicates': 'Delete repeated rows', | |
| 'rename_columns': 'Convert to snake_case', | |
| 'fill_missing_mean': 'Fill cells with MEAN', | |
| 'fill_missing_mode': 'Fill cells with MODE', | |
| 'fill_missing_median': 'Fill cells with MEDIAN', | |
| 'fix_dtype': 'Fix wrong data types', | |
| 'remove_outliers': 'Remove extreme values', | |
| 'validate_schema': 'Check column names', | |
| 'finish': 'Submit cleaned dataset' | |
| }; | |
| const COMPONENT_COLORS = { | |
| 'duplicate_score': '#3498db', | |
| 'missing_score': '#9b59b6', | |
| 'dtype_score': '#e67e22', | |
| 'outlier_score': '#e74c3c', | |
| 'schema_score': '#2ecc71', | |
| }; | |
| const CARD_MAP = { | |
| 'easy_dedup_rename': 'card-easy', | |
| 'medium_missing_dtype': 'card-medium', | |
| 'hard_full_pipeline': 'card-hard', | |
| 'expert_sales_pipeline': 'card-expert' | |
| }; | |
| function selectTask(taskId) { | |
| currentTask = taskId; | |
| document.querySelectorAll('.task-card').forEach(c => c.classList.remove('active')); | |
| document.getElementById(CARD_MAP[taskId]).classList.add('active'); | |
| document.getElementById('task-label').textContent = 'Task: ' + taskId; | |
| resetAll(); | |
| addLog('Switched to task: ' + taskId, 'info'); | |
| } | |
| function resetAll() { | |
| episodeDone = false; | |
| stepCount = 0; | |
| usedActions = new Set(); | |
| document.getElementById('reward-number').textContent = '—'; | |
| document.getElementById('reward-circle').style.borderColor = '#2d3561'; | |
| document.getElementById('reward-components').innerHTML = | |
| '<div style="color:#8892b0;font-size:12px;text-align:center;">Reset to see breakdown</div>'; | |
| document.getElementById('table-wrapper').innerHTML = | |
| '<div style="color:#8892b0;font-size:13px;padding:20px;text-align:center;">Click Reset Environment to load dirty dataset</div>'; | |
| document.getElementById('actions-list').innerHTML = | |
| '<div style="color:#8892b0;font-size:13px;">Reset first to enable actions</div>'; | |
| document.getElementById('shape-badge').textContent = '— reset to load'; | |
| document.getElementById('done-banner').style.display = 'none'; | |
| document.getElementById('submit-section').style.display = 'none'; | |
| document.getElementById('submit-msg').textContent = ''; | |
| document.getElementById('message-log').innerHTML = | |
| '<div class="log-entry info">Ready — click Reset Environment</div>'; | |
| } | |
| async function resetEnv() { | |
| const btn = document.getElementById('reset-btn'); | |
| btn.disabled = true; | |
| btn.innerHTML = '<span class="loading"></span> Resetting...'; | |
| episodeDone = false; | |
| stepCount = 0; | |
| usedActions = new Set(); | |
| document.getElementById('done-banner').style.display = 'none'; | |
| try { | |
| const res = await fetch(BASE + '/reset/' + currentTask, {method: 'POST'}); | |
| const data = await res.json(); | |
| const obs = data.observation; | |
| updateTable(obs.sample_rows, obs.columns, obs.duplicate_count, obs.missing_values); | |
| updateActions(obs.available_operations, false); | |
| document.getElementById('shape-badge').textContent = | |
| obs.shape[0] + ' rows x ' + obs.shape[1] + ' cols | ' + | |
| obs.duplicate_count + ' dups | ' + | |
| Object.values(obs.missing_values).reduce((a,b)=>a+b,0) + ' missing'; | |
| document.getElementById('reward-number').textContent = '0.00'; | |
| document.getElementById('reward-circle').style.borderColor = '#2d3561'; | |
| addLog('Environment reset! ' + obs.message, 'success'); | |
| addLog(obs.task_description.substring(0,100) + '...', 'info'); | |
| } catch(e) { | |
| addLog('Reset failed: ' + e.message, 'warning'); | |
| } | |
| btn.disabled = false; | |
| btn.innerHTML = '🔄 Reset Environment'; | |
| } | |
| async function takeAction(operation) { | |
| if (episodeDone) { addLog('Episode done — reset first', 'warning'); return; } | |
| stepCount++; | |
| const params = ACTION_PARAMS[operation] || {}; | |
| // Map fill_missing_* to fill_missing operation | |
| let actualOperation = operation; | |
| if (operation.startsWith('fill_missing_')) { | |
| actualOperation = 'fill_missing'; | |
| } | |
| addLog('[Step ' + stepCount + '] → ' + operation + ' ' + JSON.stringify(params), 'info'); | |
| const btn = document.getElementById('btn-' + operation); | |
| if (btn) { btn.disabled = true; btn.innerHTML = '<span class="loading"></span> Running...'; } | |
| try { | |
| const res = await fetch(BASE + '/step/' + currentTask, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({operation: actualOperation, parameters: params}) | |
| }); | |
| const data = await res.json(); | |
| const obs = data.observation; | |
| const rew = data.reward; | |
| updateTable(obs.sample_rows, obs.columns, obs.duplicate_count, obs.missing_values); | |
| document.getElementById('shape-badge').textContent = | |
| obs.shape[0] + ' rows x ' + obs.shape[1] + ' cols | ' + | |
| obs.duplicate_count + ' dups | ' + | |
| Object.values(obs.missing_values).reduce((a,b)=>a+b,0) + ' missing'; | |
| updateReward(rew); | |
| addLog(' ' + obs.message.substring(0,80), rew.total > 0.5 ? 'success' : 'info'); | |
| addLog(' Reward: ' + rew.total.toFixed(4), 'success'); | |
| usedActions.add(operation); | |
| if (btn) { btn.classList.add('used'); btn.innerHTML = '✅ ' + operation; } | |
| if (data.done) { | |
| episodeDone = true; | |
| document.getElementById('done-banner').style.display = 'block'; | |
| document.getElementById('submit-section').style.display = 'block'; | |
| addLog('Episode complete! Final score: ' + rew.total.toFixed(4), 'success'); | |
| updateActions(obs.available_operations, true); | |
| } | |
| } catch(e) { | |
| addLog('Action failed: ' + e.message, 'warning'); | |
| if (btn) { btn.disabled = false; btn.innerHTML = operation; } | |
| } | |
| } | |
| function updateTable(rows, columns, dupCount, missing) { | |
| if (!rows || rows.length === 0) return; | |
| let html = '<table><thead><tr>'; | |
| columns.forEach(c => { html += '<th>' + c + '</th>'; }); | |
| html += '</tr></thead><tbody>'; | |
| rows.forEach(row => { | |
| html += '<tr>'; | |
| columns.forEach(c => { | |
| const val = row[c]; | |
| if (val === 'NULL' || val === null || val === undefined) { | |
| html += '<td class="null-cell">NULL</td>'; | |
| } else { | |
| html += '<td>' + val + '</td>'; | |
| } | |
| }); | |
| html += '</tr>'; | |
| }); | |
| html += '</tbody></table>'; | |
| document.getElementById('table-wrapper').innerHTML = html; | |
| } | |
| function updateActions(ops, done) { | |
| const list = document.getElementById('actions-list'); | |
| list.innerHTML = ''; | |
| ops.forEach(op => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'action-btn' + (usedActions.has(op) ? ' used' : ''); | |
| btn.id = 'btn-' + op; | |
| btn.disabled = done; | |
| btn.innerHTML = '<span>' + (usedActions.has(op) ? '✅ ' : '') + op + '</span>' + | |
| '<span class="action-tag">' + (ACTION_DESC[op] || '') + '</span>'; | |
| btn.onclick = () => takeAction(op); | |
| list.appendChild(btn); | |
| }); | |
| } | |
| function updateReward(rew) { | |
| const total = rew.total || 0; | |
| finalReward = total; | |
| document.getElementById('reward-number').textContent = total.toFixed(4); | |
| let color = '#e74c3c'; | |
| if (total >= 0.8) color = '#2ecc71'; | |
| else if (total >= 0.5) color = '#f39c12'; | |
| document.getElementById('reward-circle').style.borderColor = color; | |
| const comps = ['duplicate_score','missing_score','dtype_score','outlier_score','schema_score']; | |
| let html = ''; | |
| comps.forEach(c => { | |
| const val = rew[c] || 0; | |
| const color = COMPONENT_COLORS[c] || '#7eb8f7'; | |
| html += '<div class="component-row">' + | |
| '<div class="component-name">' + c.replace('_score','') + '</div>' + | |
| '<div class="component-bar-bg"><div class="component-bar-fill" style="width:' + | |
| (val*100) + '%;background:' + color + '"></div></div>' + | |
| '<div class="component-val">' + val.toFixed(2) + '</div></div>'; | |
| }); | |
| document.getElementById('reward-components').innerHTML = html; | |
| } | |
| function addLog(msg, type='info') { | |
| const log = document.getElementById('message-log'); | |
| const entry = document.createElement('div'); | |
| entry.className = 'log-entry ' + type; | |
| entry.textContent = msg; | |
| log.appendChild(entry); | |
| log.scrollTop = log.scrollHeight; | |
| } | |
| // Track final reward for submission | |
| let finalReward = 0.0; | |
| async function submitToLeaderboard() { | |
| const modelName = document.getElementById('model-name-input').value.trim(); | |
| if (!modelName) { | |
| document.getElementById('submit-msg').style.color = '#e74c3c'; | |
| document.getElementById('submit-msg').textContent = '❌ Please enter your model/name first!'; | |
| return; | |
| } | |
| const entry = { | |
| model_name: modelName, | |
| task_id: currentTask, | |
| score: finalReward, | |
| steps: stepCount | |
| }; | |
| try { | |
| const r = await fetch(BASE + '/leaderboard/submit', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(entry) | |
| }); | |
| const data = await r.json(); | |
| if (data.status === 'submitted') { | |
| document.getElementById('submit-msg').style.color = '#2ecc71'; | |
| document.getElementById('submit-msg').textContent = | |
| '✅ Score ' + finalReward.toFixed(4) + ' submitted for ' + modelName + '!'; | |
| addLog('Submitted to leaderboard: ' + modelName + ' = ' + finalReward.toFixed(4), 'success'); | |
| // Refresh leaderboard | |
| setTimeout(loadLeaderboard, 1000); | |
| } | |
| } catch(e) { | |
| document.getElementById('submit-msg').style.color = '#e74c3c'; | |
| document.getElementById('submit-msg').textContent = '❌ Submit failed: ' + e.message; | |
| } | |
| } | |
| async function loadLeaderboard() { | |
| try { | |
| const r = await fetch(BASE + '/leaderboard'); | |
| const data = await r.json(); | |
| const lb = data.leaderboard; | |
| let html = '<table class="lb-table"><thead><tr>'; | |
| ['Rank','Model','Easy','Medium','Hard','Expert','Avg'].forEach(h => { | |
| html += '<th>' + h + '</th>'; | |
| }); | |
| html += '</tr></thead><tbody>'; | |
| lb.forEach(entry => { | |
| const scores = entry.scores || {}; | |
| html += '<tr>' + | |
| '<td style="color:#f39c12;">#' + entry.rank + '</td>' + | |
| '<td>' + entry.model_name + '</td>' + | |
| '<td style="color:#2ecc71;text-align:center;">' + (entry.easy_score || scores.easy_dedup_rename || '-') + '</td>' + | |
| '<td style="color:#f39c12;text-align:center;">' + (entry.medium_score || scores.medium_missing_dtype || '-') + '</td>' + | |
| '<td style="color:#e74c3c;text-align:center;">' + (entry.hard_score || scores.hard_full_pipeline || '-') + '</td>' + | |
| '<td style="color:#9b59b6;text-align:center;">' + (entry.expert_score || scores.expert_sales_pipeline || '-') + '</td>' + | |
| '<td style="color:#7eb8f7;font-weight:bold;text-align:center;">' + entry.avg_score + '</td>' + | |
| '</tr>'; | |
| }); | |
| html += '</tbody></table>'; | |
| html += '<div style="color:#8892b0;font-size:11px;margin-top:8px;">Total submissions: ' + data.total_submissions + '</div>'; | |
| document.getElementById('leaderboard-table').innerHTML = html; | |
| } catch(e) { | |
| document.getElementById('leaderboard-table').innerHTML = | |
| '<div style="color:#e74c3c;">Failed to load leaderboard</div>'; | |
| } | |
| } | |
| // On load — show actions for default task immediately | |
| function showDefaultActions(taskId) { | |
| const ops = TASK_OPERATIONS[taskId] || []; | |
| const list = document.getElementById('actions-list'); | |
| list.innerHTML = ''; | |
| const TASK_INFO = { | |
| 'easy_dedup_rename': '🟢 EASY — ' + ops.length + ' actions | Max 10 steps', | |
| 'medium_missing_dtype': '🟡 MEDIUM — ' + ops.length + ' actions | Max 15 steps', | |
| 'hard_full_pipeline': '🔴 HARD — ' + ops.length + ' actions | Max 20 steps', | |
| 'expert_sales_pipeline': '🟣 EXPERT — ' + ops.length + ' actions | Max 25 steps' | |
| }; | |
| const banner = document.createElement('div'); | |
| banner.style.cssText = 'background:#1e2438;border:1px solid #3d4f7c;border-radius:8px;padding:10px;margin-bottom:10px;font-size:12px;color:#8892b0;'; | |
| banner.textContent = TASK_INFO[taskId] || ''; | |
| list.appendChild(banner); | |
| ops.forEach(op => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'action-btn'; | |
| btn.id = 'btn-' + op; | |
| btn.disabled = true; | |
| btn.innerHTML = '<span>' + op + '</span>' + | |
| '<span class="action-tag">' + (ACTION_DESC[op] || '') + '</span>'; | |
| btn.onclick = () => takeAction(op); | |
| list.appendChild(btn); | |
| }); | |
| const hint = document.createElement('div'); | |
| hint.style.cssText = 'color:#f39c12;font-size:11px;margin-top:8px;text-align:center;'; | |
| hint.textContent = '⚠️ Click Reset Environment to enable actions'; | |
| list.appendChild(hint); | |
| } | |
| // Show actions for default task on page load | |
| showDefaultActions(currentTask); | |
| // On load | |
| fetch(BASE + '/health') | |
| .then(r => r.json()) | |
| .then(d => addLog('Environment online — ' + d.tasks_loaded + ' tasks loaded', 'success')) | |
| .catch(() => addLog('Cannot reach environment', 'warning')); | |
| // Load live avg score from leaderboard | |
| fetch(BASE + '/leaderboard') | |
| .then(r => r.json()) | |
| .then(data => { | |
| if (data.leaderboard && data.leaderboard.length > 0) { | |
| const best = data.leaderboard[0].avg_score; | |
| document.getElementById('live-avg-score').textContent = best; | |
| } else { | |
| document.getElementById('live-avg-score').textContent = 'N/A'; | |
| } | |
| }) | |
| .catch(() => { | |
| document.getElementById('live-avg-score').textContent = '0.86'; | |
| }); | |
| loadLeaderboard(); | |
| </script> | |
| </body> | |
| </html> |