parthpethia's picture
Complete restructuring for hackathon validator compliance with server package, uv.lock, and openenv-core
1e1ca31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Triage OpenEnv - Interactive Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
color: white;
margin-bottom: 30px;
}
header h1 {
font-size: 2.5em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
header p {
font-size: 1.1em;
opacity: 0.9;
}
.main-layout {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.panel {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
}
.panel h2 {
color: #333;
font-size: 1.3em;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #667eea;
}
.panel h3 {
color: #555;
font-size: 1em;
margin-top: 15px;
margin-bottom: 10px;
}
.task-selector {
display: flex;
flex-direction: column;
gap: 10px;
}
.task-btn {
padding: 12px;
border: 2px solid #ddd;
border-radius: 8px;
background: white;
cursor: pointer;
font-size: 0.95em;
transition: all 0.3s;
text-align: left;
}
.task-btn:hover {
border-color: #667eea;
background: #f0f4ff;
}
.task-btn.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.task-btn-title {
font-weight: bold;
display: block;
margin-bottom: 5px;
}
.task-btn-desc {
font-size: 0.85em;
opacity: 0.8;
}
.email-display {
background: #f9f9f9;
border-left: 4px solid #667eea;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
min-height: 200px;
}
.email-header {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
font-size: 0.9em;
}
.email-field {
display: flex;
flex-direction: column;
}
.email-label {
color: #666;
font-weight: bold;
font-size: 0.85em;
margin-bottom: 5px;
}
.email-value {
color: #333;
padding: 8px;
background: white;
border-radius: 4px;
}
.email-subject {
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.email-body {
background: white;
padding: 12px;
border-radius: 4px;
line-height: 1.5;
color: #555;
max-height: 300px;
overflow-y: auto;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: bold;
font-size: 0.95em;
}
select,
input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 0.95em;
font-family: inherit;
}
select:focus,
input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 5px rgba(102, 126, 234, 0.3);
}
.button-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 15px;
}
button {
padding: 12px;
border: none;
border-radius: 6px;
font-size: 0.95em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
border: 1px solid #ddd;
}
.btn-secondary:hover:not(:disabled) {
background: #e0e0e0;
}
.btn-secondary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.stats-panel {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
.stat-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-label {
font-size: 0.9em;
opacity: 0.9;
margin-bottom: 5px;
}
.stat-value {
font-size: 1.8em;
font-weight: bold;
}
.results-area {
background: #f0f8ff;
border: 2px solid #667eea;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
min-height: 100px;
}
.result-item {
padding: 10px;
margin-bottom: 8px;
background: white;
border-left: 4px solid #667eea;
border-radius: 4px;
}
.result-label {
font-weight: bold;
color: #667eea;
font-size: 0.9em;
}
.result-value {
color: #333;
margin-top: 5px;
}
.reward-high {
color: #28a745;
font-weight: bold;
}
.reward-low {
color: #dc3545;
font-weight: bold;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-top: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s;
}
.status-message {
padding: 12px;
border-radius: 6px;
margin-bottom: 15px;
font-size: 0.95em;
}
.status-info {
background: #e7f3ff;
color: #00396b;
border: 1px solid #667eea;
}
.status-success {
background: #d4edda;
color: #155724;
border: 1px solid #28a745;
}
.status-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #dc3545;
}
.status-warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffc107;
}
.history-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 0.85em;
}
.history-table th {
background: #667eea;
color: white;
padding: 8px;
text-align: left;
}
.history-table td {
padding: 8px;
border-bottom: 1px solid #ddd;
}
.history-table tr:hover {
background: #f5f5f5;
}
.task-complete {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.task-complete h3 {
font-size: 1.5em;
margin-bottom: 10px;
color: white;
}
.idle-message {
text-align: center;
padding: 30px;
color: #999;
}
@media (max-width: 1200px) {
.main-layout {
grid-template-columns: 1fr;
}
header h1 {
font-size: 1.8em;
}
}
.loader {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>📧 Email Triage OpenEnv</h1>
<p>Interactive Dashboard - Test & Evaluate Email Classification Tasks</p>
</header>
<div id="statusMessage"></div>
<div class="main-layout">
<!-- Left Panel: Task Selection & Controls -->
<div class="panel">
<h2>📋 Tasks</h2>
<div class="task-selector">
<button class="task-btn active" onclick="selectTask('spam_detection')">
<span class="task-btn-title">Task 1: Spam Detection</span>
<span class="task-btn-desc">Easy - 10 emails</span>
</button>
<button class="task-btn" onclick="selectTask('multi_class_routing')">
<span class="task-btn-title">Task 2: Multi-Class Routing</span>
<span class="task-btn-desc">Medium - 12 emails</span>
</button>
<button class="task-btn" onclick="selectTask('context_aware_triage')">
<span class="task-btn-title">Task 3: Context-Aware Triage</span>
<span class="task-btn-desc">Hard - 20 emails</span>
</button>
</div>
<h3 style="margin-top: 25px;">⚙️ Controls</h3>
<button class="btn-primary" style="width: 100%; margin-bottom: 10px;" onclick="resetTask()">
🔄 Reset Task
</button>
<button class="btn-secondary" style="width: 100%;" onclick="loadTaskInfo()">
ℹ️ Task Info
</button>
<h3 style="margin-top: 25px;">📊 Statistics</h3>
<div class="stats-panel">
<div class="stat-box">
<div class="stat-label">Current Step</div>
<div class="stat-value" id="statStep">0</div>
</div>
<div class="stat-box">
<div class="stat-label">Total Reward</div>
<div class="stat-value" id="statReward">0.00</div>
</div>
<div class="stat-box">
<div class="stat-label">Average Reward</div>
<div class="stat-value" id="statAvg">0.00</div>
</div>
<div class="stat-box">
<div class="stat-label">Final Score</div>
<div class="stat-value" id="statScore">-</div>
</div>
</div>
</div>
<!-- Middle Panel: Email Display & Classification Form -->
<div class="panel">
<h2>✉️ Email Classification</h2>
<div id="emailContainer">
<div class="idle-message">
<p>Click "Reset Task" to start</p>
</div>
</div>
<div id="formContainer" style="display: none;">
<div class="form-group">
<label for="classification">📌 Classification:</label>
<select id="classification" onchange="updatePriorities()">
<option value="">-- Select Classification --</option>
<option value="spam">🚫 Spam</option>
<option value="normal">📄 Normal</option>
<option value="urgent">⚡ Urgent</option>
<option value="billing">💳 Billing</option>
</select>
</div>
<div class="form-group">
<label for="team">🏢 Route to Team:</label>
<select id="team">
<option value="none">🚫 None</option>
<option value="support">🆘 Support</option>
<option value="sales">💼 Sales</option>
<option value="billing">💰 Billing</option>
</select>
</div>
<div class="form-group">
<label for="priority">⭐ Priority (0-3):</label>
<select id="priority">
<option value="0">0 - Low</option>
<option value="1" selected>1 - Medium</option>
<option value="2">2 - High</option>
<option value="3">3 - Critical</option>
</select>
</div>
<div class="button-group">
<button class="btn-primary" onclick="submitAction()">✓ Submit</button>
<button class="btn-secondary" onclick="resetTask()">⟲ Reset</button>
</div>
<div id="resultArea" class="results-area" style="display: none;">
<div id="resultContent"></div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
</div>
</div>
<!-- Right Panel: History & Results -->
<div class="panel">
<h2>📝 History & Results</h2>
<div id="historyContainer">
<div class="idle-message">
<p>History will appear here</p>
</div>
</div>
<div id="completeMessage" style="display: none;">
<div class="task-complete">
<h3>✓ Task Complete!</h3>
<p>Final Score: <span id="finalScore">0.00</span></p>
<p>Steps Taken: <span id="finalSteps">0</span></p>
<p style="margin-top: 15px; font-size: 0.9em;">Click "Reset Task" to try again or select another
task</p>
</div>
</div>
</div>
</div>
</div>
<script>
let currentTask = 'spam_detection';
let currentState = null;
let history = [];
let totalReward = 0;
let taskDone = false;
const taskDescriptions = {
'spam_detection': {
title: 'Spam Detection (Easy)',
desc: 'Classify 10 emails as spam or legitimate. High accuracy expected.',
steps: 10,
categories: ['spam', 'normal']
},
'multi_class_routing': {
title: 'Multi-Class Routing (Medium)',
desc: 'Classify 12 emails into 4 categories and route to appropriate teams.',
steps: 12,
categories: ['spam', 'normal', 'urgent', 'billing']
},
'context_aware_triage': {
title: 'Context-Aware Triage (Hard)',
desc: 'Handle 20 emails with VIP flags, SLAs, and complex context.',
steps: 20,
categories: ['spam', 'normal', 'urgent', 'billing']
}
};
function showStatus(message, type = 'info') {
const statusEl = document.getElementById('statusMessage');
statusEl.className = `status-message status-${type}`;
statusEl.innerHTML = message;
statusEl.style.display = 'block';
setTimeout(() => {
statusEl.style.display = 'none';
}, 5000);
}
async function selectTask(taskName) {
currentTask = taskName;
document.querySelectorAll('.task-btn').forEach(btn => btn.classList.remove('active'));
event.target.closest('.task-btn').classList.add('active');
loadTaskInfo();
showStatus(`Selected: ${taskDescriptions[taskName].title}`, 'info');
}
function loadTaskInfo() {
const task = taskDescriptions[currentTask];
showStatus(`<strong>${task.title}</strong><br>${task.desc}`, 'info');
}
async function resetTask() {
try {
showStatus('🔄 Resetting task...', 'info');
const response = await fetch(`/reset?task=${currentTask}`, {
method: 'POST'
});
const data = await response.json();
currentState = data.observation;
history = [];
totalReward = 0;
taskDone = false;
document.getElementById('statStep').textContent = '0';
document.getElementById('statReward').textContent = '0.00';
document.getElementById('statAvg').textContent = '0.00';
document.getElementById('statScore').textContent = '-';
document.getElementById('completeMessage').style.display = 'none';
document.getElementById('formContainer').style.display = 'block';
displayEmail();
updateHistory();
showStatus(`✓ Task reset! Ready to classify emails.`, 'success');
} catch (error) {
showStatus(`Error resetting task: ${error.message}`, 'error');
}
}
function displayEmail() {
const email = currentState.current_email;
const emailHTML = `
<div class="email-display">
<div class="email-subject">Subject: ${email.subject}</div>
<div class="email-header">
<div class="email-field">
<span class="email-label">From:</span>
<span class="email-value">${email.sender_domain}</span>
</div>
<div class="email-field">
<span class="email-label">VIP Sender:</span>
<span class="email-value">${email.is_vip_sender ? '✓ Yes' : '✗ No'}</span>
</div>
</div>
<div class="email-header" style="margin-bottom: 15px;">
<div class="email-field">
<span class="email-label">SLA Hours:</span>
<span class="email-value">${email.sla_hours || 'N/A'}</span>
</div>
<div class="email-field">
<span class="email-label">Timestamp:</span>
<span class="email-value">${new Date(email.timestamp).toLocaleString()}</span>
</div>
</div>
<div class="email-body">${email.body.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>
</div>
`;
document.getElementById('emailContainer').innerHTML = emailHTML;
}
function updatePriorities() {
const classification = document.getElementById('classification').value;
updateTeamOptions();
}
function updateTeamOptions() {
const classification = document.getElementById('classification').value;
const teamSelect = document.getElementById('team');
if (classification === 'spam') {
teamSelect.value = 'none';
}
}
async function submitAction() {
const classification = document.getElementById('classification').value;
const team = document.getElementById('team').value;
const priority = parseInt(document.getElementById('priority').value);
if (!classification) {
showStatus('Please select a classification', 'warning');
return;
}
try {
const response = await fetch(`/step?task=${currentTask}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
classification: classification,
team: team,
priority: priority
})
});
const data = await response.json();
currentState = data.observation;
totalReward += data.reward.value;
taskDone = data.done;
const stepNum = history.length + 1;
history.push({
step: stepNum,
email_subject: currentState.current_email.subject.substring(0, 30),
classification: classification,
team: team,
priority: priority,
reward: data.reward.value,
done: taskDone
});
updateStats();
displayResults(data);
updateHistory();
if (taskDone) {
showTaskComplete();
} else {
setTimeout(() => {
displayEmail();
document.getElementById('classification').value = '';
document.getElementById('team').value = 'none';
document.getElementById('priority').value = '1';
document.getElementById('resultArea').style.display = 'none';
}, 1500);
}
showStatus(`✓ Step ${stepNum} submitted!`, 'success');
} catch (error) {
showStatus(`Error: ${error.message}`, 'error');
}
}
function updateStats() {
const stepNum = history.length;
const avgReward = stepNum > 0 ? (totalReward / stepNum) : 0;
document.getElementById('statStep').textContent = stepNum.toString();
document.getElementById('statReward').textContent = totalReward.toFixed(2);
document.getElementById('statAvg').textContent = avgReward.toFixed(2);
const progressPercent = (stepNum / taskDescriptions[currentTask].steps) * 100;
document.getElementById('progressFill').style.width = progressPercent + '%';
}
function displayResults(data) {
const resultHTML = `
<div class="result-item">
<span class="result-label">Reward:</span>
<span class="result-value ${data.reward.value >= 0.7 ? 'reward-high' : 'reward-low'}">
${data.reward.value.toFixed(3)}
</span>
</div>
<div class="result-item">
<span class="result-label">Breakdown:</span>
<span class="result-value">${JSON.stringify(data.reward.breakdown).replace(/[{}]/g, '')}</span>
</div>
<div class="result-item">
<span class="result-label">Status:</span>
<span class="result-value">${data.done ? '✓ Task Complete' : '▶ In Progress'}</span>
</div>
`;
document.getElementById('resultContent').innerHTML = resultHTML;
document.getElementById('resultArea').style.display = 'block';
}
function updateHistory() {
if (history.length === 0) {
document.getElementById('historyContainer').innerHTML = '<div class="idle-message"><p>No actions yet</p></div>';
return;
}
const tableHTML = `
<table class="history-table">
<thead>
<tr>
<th>Step</th>
<th>Classification</th>
<th>Team</th>
<th>Priority</th>
<th>Reward</th>
</tr>
</thead>
<tbody>
${history.map(h => `
<tr>
<td>${h.step}</td>
<td>${h.classification}</td>
<td>${h.team}</td>
<td>${h.priority}</td>
<td>${h.reward.toFixed(3)}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
document.getElementById('historyContainer').innerHTML = tableHTML;
}
function showTaskComplete() {
const finalScore = (totalReward / history.length).toFixed(3);
document.getElementById('finalScore').textContent = finalScore;
document.getElementById('finalSteps').textContent = history.length;
document.getElementById('completeMessage').style.display = 'block';
document.getElementById('statScore').textContent = finalScore;
document.getElementById('formContainer').style.display = 'none';
showStatus(`🎉 Task complete! Final score: ${finalScore}`, 'success');
}
// Initialize on load
window.addEventListener('load', () => {
loadTaskInfo();
});
</script>
</body>
</html>