SupportOps / app /static /index.html
prathvi9607's picture
done phase 1
9c1645f
Raw History Blame Contribute Delete
16 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SupportOps Arena - IT Incident Triage RL Environment</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&family=Sora:wght@600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #FFFFFF;
--bg-secondary: #F0F8FF;
--bg-card: #FFFFFF;
--bg-glass: rgba(255,255,255,0.8);
--gradient-main: linear-gradient(135deg, #FFFFFF 0%, #E0F4FF 40%, #B8E4FF 100%);
--gradient-card: linear-gradient(145deg, rgba(255,255,255,0.95), rgba(224,244,255,0.6));
--sky-300: #87CEEB;
--sky-500: #2196F3;
--sky-600: #1976D2;
--text-primary: #0D1B2A;
--text-secondary: #3A5068;
--text-muted: #7B9BB5;
--success: #10B981;
--warning: #F59E0B;
--danger: #EF4444;
--info: #3B82F6;
--border-light: rgba(135, 206, 235, 0.3);
--shadow-md: 0 4px 20px rgba(33, 150, 243, 0.12);
--radius-md: 12px;
--transition-med: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'DM Sans', sans-serif;
background: var(--gradient-main);
color: var(--text-primary);
min-height: 100vh;
}
h1, h2, h3 {
font-family: 'Sora', sans-serif;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: var(--gradient-card);
backdrop-filter: blur(12px);
padding: 20px 30px;
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 28px;
color: var(--sky-600);
}
.status-badge {
padding: 8px 16px;
background: var(--success);
color: white;
border-radius: 20px;
font-weight: 600;
font-size: 14px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.card {
background: var(--gradient-card);
backdrop-filter: blur(12px);
border-radius: var(--radius-md);
padding: 20px;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-light);
}
.card h2 {
font-size: 20px;
margin-bottom: 15px;
color: var(--sky-600);
}
.button {
padding: 10px 20px;
background: var(--sky-500);
color: white;
border: none;
border-radius: var(--radius-md);
font-family: 'DM Sans', sans-serif;
font-weight: 600;
cursor: pointer;
transition: var(--transition-med);
margin-right: 10px;
margin-bottom: 10px;
}
.button:hover {
background: var(--sky-600);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.button-secondary {
background: var(--text-secondary);
}
.button-secondary:hover {
background: var(--text-primary);
}
select, input {
padding: 10px;
border: 2px solid var(--border-light);
border-radius: var(--radius-md);
font-family: 'DM Sans', sans-serif;
margin-right: 10px;
background: white;
}
.log-entry {
padding: 10px;
margin-bottom: 8px;
background: white;
border-left: 4px solid var(--info);
border-radius: 6px;
font-family: 'DM Mono', monospace;
font-size: 13px;
}
.log-entry.error {
border-left-color: var(--danger);
}
.log-entry.warn {
border-left-color: var(--warning);
}
.log-entry.info {
border-left-color: var(--success);
}
.action-history {
max-height: 300px;
overflow-y: auto;
}
.action-item {
padding: 10px;
margin-bottom: 8px;
background: white;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
}
.reward {
font-weight: 600;
padding: 4px 8px;
border-radius: 4px;
}
.reward.positive {
background: var(--success);
color: white;
}
.reward.negative {
background: var(--danger);
color: white;
}
.metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 15px;
}
.metric {
text-align: center;
padding: 15px;
background: white;
border-radius: var(--radius-md);
}
.metric-value {
font-size: 32px;
font-weight: 700;
color: var(--sky-600);
}
.metric-label {
font-size: 14px;
color: var(--text-muted);
margin-top: 5px;
}
.hidden {
display: none;
}
.loading {
color: var(--text-muted);
font-style: italic;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎯 SupportOps Arena</h1>
<div class="status-badge" id="statusBadge">Ready</div>
</div>
<div class="card">
<h2>Environment Controls</h2>
<div>
<select id="taskLevel">
<option value="easy">Easy - Wi-Fi Connectivity</option>
<option value="medium">Medium - VPN Access</option>
<option value="hard">Hard - Cross-System Failure</option>
</select>
<input type="number" id="seedInput" placeholder="Seed (optional)">
<button class="button" onclick="resetEnvironment()">Reset Environment</button>
<button class="button button-secondary" onclick="runBaseline()">Run Baseline Agent</button>
</div>
</div>
<div class="grid">
<div class="card">
<h2>Current Ticket</h2>
<div id="ticketInfo">
<p class="loading">Reset environment to start...</p>
</div>
</div>
<div class="card">
<h2>Observations</h2>
<div id="observations">
<p class="loading">No active episode</p>
</div>
</div>
</div>
<div class="card">
<h2>Manual Actions</h2>
<div id="actionButtons">
<!-- Action buttons will be populated -->
</div>
</div>
<div class="grid">
<div class="card">
<h2>System Logs</h2>
<div id="systemLogs" style="max-height: 300px; overflow-y: auto;">
<p class="loading">No logs yet</p>
</div>
</div>
<div class="card">
<h2>Action History</h2>
<div id="actionHistory" class="action-history">
<p class="loading">No actions yet</p>
</div>
</div>
</div>
<div class="card">
<h2>Episode Metrics</h2>
<div class="metrics" id="metrics">
<div class="metric">
<div class="metric-value" id="stepsValue">0</div>
<div class="metric-label">Steps</div>
</div>
<div class="metric">
<div class="metric-value" id="rewardValue">0.00</div>
<div class="metric-label">Cumulative Reward</div>
</div>
<div class="metric">
<div class="metric-value" id="diagnosticsValue">0</div>
<div class="metric-label">Diagnostics Taken</div>
</div>
</div>
</div>
</div>
<script>
let sessionId = null;
let currentObservation = null;
let cumulativeReward = 0;
const actions = [
'inspect_network', 'inspect_logs', 'check_authentication',
'check_permissions', 'query_device_status', 'search_internal_kb',
'contact_user_for_info', 'run_diagnostic_script', 'flush_dns',
'reconfigure_client', 'restart_service', 'reset_credentials',
'escalate_ticket', 'resolve_ticket', 'close_without_fix'
];
function updateStatus(text, type = 'success') {
const badge = document.getElementById('statusBadge');
badge.textContent = text;
badge.style.background = type === 'success' ? 'var(--success)' :
type === 'warning' ? 'var(--warning)' :
'var(--danger)';
}
async function resetEnvironment() {
const taskLevel = document.getElementById('taskLevel').value;
const seed = document.getElementById('seedInput').value || null;
updateStatus('Resetting...', 'warning');
try {
const response = await fetch('/reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(sessionId ? { 'X-Session-ID': sessionId } : {})
},
body: JSON.stringify({ task_level: taskLevel, seed: seed ? parseInt(seed) : null })
});
const data = await response.json();
sessionId = data.session_id;
currentObservation = data.observation;
cumulativeReward = 0;
updateUI();
createActionButtons();
updateStatus('Episode Active', 'success');
} catch (error) {
console.error('Reset failed:', error);
updateStatus('Error', 'error');
}
}
async function takeAction(actionName) {
if (!sessionId) {
alert('Please reset environment first');
return;
}
try {
const response = await fetch('/step', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify({ action_name: actionName })
});
const data = await response.json();
currentObservation = data.observation;
cumulativeReward += data.reward;
updateUI();
if (data.done) {
updateStatus('Episode Complete', 'info');
alert(`Episode finished! Final reward: ${cumulativeReward.toFixed(2)}`);
}
} catch (error) {
console.error('Step failed:', error);
updateStatus('Error', 'error');
}
}
async function runBaseline() {
updateStatus('Running Baseline...', 'warning');
try {
const response = await fetch('/baseline/run', { method: 'POST' });
const data = await response.json();
let message = 'Baseline Complete!\n\n';
for (const [level, result] of Object.entries(data.results)) {
message += `${level}: score=${result.grade_score.toFixed(3)}, steps=${result.steps}\n`;
}
alert(message);
updateStatus('Ready', 'success');
} catch (error) {
console.error('Baseline failed:', error);
updateStatus('Error', 'error');
}
}
function updateUI() {
if (!currentObservation) return;
// Update ticket info
document.getElementById('ticketInfo').innerHTML = `
<p><strong>Ticket ID:</strong> ${currentObservation.ticket_id}</p>
<p><strong>Summary:</strong> ${currentObservation.ticket_summary}</p>
<p><strong>Task Level:</strong> ${currentObservation.task_level}</p>
`;
// Update observations
document.getElementById('observations').innerHTML = `
<p><strong>Network:</strong> ${currentObservation.network_status}</p>
<p><strong>VPN:</strong> ${currentObservation.vpn_status}</p>
<p><strong>Auth:</strong> ${currentObservation.auth_status}</p>
<p><strong>Escalation Allowed:</strong> ${currentObservation.escalation_allowed ? 'Yes' : 'No'}</p>
`;
// Update logs
const logsDiv = document.getElementById('systemLogs');
logsDiv.innerHTML = '';
currentObservation.system_logs.forEach(log => {
const entry = document.createElement('div');
entry.className = `log-entry ${log.level.toLowerCase()}`;
entry.textContent = `[${log.timestamp.split('T')[1].split('.')[0]}] ${log.level} ${log.service}: ${log.message}`;
logsDiv.appendChild(entry);
});
// Update action history
const historyDiv = document.getElementById('actionHistory');
historyDiv.innerHTML = '';
currentObservation.action_history.forEach(action => {
const item = document.createElement('div');
item.className = 'action-item';
item.innerHTML = `
<span>${action.step}. ${action.action_name}</span>
<span class="reward ${action.reward_received >= 0 ? 'positive' : 'negative'}">
${action.reward_received >= 0 ? '+' : ''}${action.reward_received.toFixed(2)}
</span>
`;
historyDiv.appendChild(item);
});
// Update metrics
document.getElementById('stepsValue').textContent = currentObservation.step_count;
document.getElementById('rewardValue').textContent = cumulativeReward.toFixed(2);
document.getElementById('diagnosticsValue').textContent = currentObservation.action_history.length;
}
function createActionButtons() {
const container = document.getElementById('actionButtons');
container.innerHTML = '';
actions.forEach(action => {
const btn = document.createElement('button');
btn.className = 'button button-secondary';
btn.textContent = action.replace(/_/g, ' ').toUpperCase();
btn.onclick = () => takeAction(action);
container.appendChild(btn);
});
}
// Initialize
updateStatus('Ready', 'success');
</script>
</body>
</html>