Spaces:
Running
Running
Upload index.html with huggingface_hub
Browse files- index.html +146 -140
index.html
CHANGED
|
@@ -19,32 +19,33 @@
|
|
| 19 |
.result { background: #1a1a2e; color: #0f0; padding: 20px; border-radius: 10px; margin: 15px 0; font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.6; max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; }
|
| 20 |
.metrics { display: flex; gap: 10px; flex-wrap: wrap; margin: 15px 0; }
|
| 21 |
.metric { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px 20px; border-radius: 25px; font-weight: bold; }
|
| 22 |
-
.
|
| 23 |
-
.
|
| 24 |
-
.
|
|
|
|
| 25 |
.agent-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 15px 0; }
|
| 26 |
.agent-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px; border-radius: 10px; text-align: center; }
|
| 27 |
.agent-card h3 { margin: 0 0 5px 0; font-size: 14px; }
|
| 28 |
.agent-card p { margin: 0; font-size: 24px; font-weight: bold; }
|
| 29 |
-
.
|
| 30 |
-
.
|
| 31 |
-
|
| 32 |
</style>
|
| 33 |
</head>
|
| 34 |
<body>
|
| 35 |
<div class="container">
|
| 36 |
<h1>ContextFlow OpenEnv</h1>
|
| 37 |
-
<p class="subtitle">Learning Confusion Prediction
|
| 38 |
|
| 39 |
-
<div id="status" class="status
|
| 40 |
|
| 41 |
<div class="card">
|
| 42 |
<h2>Environment Setup</h2>
|
| 43 |
<div class="controls">
|
| 44 |
<select id="difficulty">
|
| 45 |
-
<option value="easy">Easy (50 steps)</option>
|
| 46 |
-
<option value="medium" selected>Medium (75 steps)</option>
|
| 47 |
-
<option value="hard">Hard (100 steps)</option>
|
| 48 |
</select>
|
| 49 |
<button onclick="reset()">Reset Environment</button>
|
| 50 |
</div>
|
|
@@ -76,7 +77,7 @@
|
|
| 76 |
|
| 77 |
<div class="card">
|
| 78 |
<h2>Results</h2>
|
| 79 |
-
<div id="result" class="result">
|
| 80 |
</div>
|
| 81 |
|
| 82 |
<div class="card">
|
|
@@ -85,32 +86,34 @@
|
|
| 85 |
</div>
|
| 86 |
|
| 87 |
<div class="card">
|
| 88 |
-
<h2>Active Agents</h2>
|
| 89 |
<div class="agent-info">
|
| 90 |
<div class="agent-card">
|
| 91 |
-
<h3>RL Predictor</h3>
|
| 92 |
<p>Q-Learning</p>
|
| 93 |
</div>
|
| 94 |
<div class="agent-card">
|
| 95 |
-
<h3>
|
| 96 |
-
<p>
|
| 97 |
</div>
|
| 98 |
<div class="agent-card">
|
| 99 |
-
<h3>
|
| 100 |
-
<p>
|
| 101 |
</div>
|
| 102 |
<div class="agent-card">
|
| 103 |
-
<h3>
|
| 104 |
-
<p>
|
| 105 |
</div>
|
| 106 |
</div>
|
| 107 |
</div>
|
| 108 |
</div>
|
| 109 |
|
| 110 |
<script>
|
|
|
|
| 111 |
let episodeId = null;
|
| 112 |
let stepCount = 0;
|
| 113 |
let episodeReward = 0;
|
|
|
|
| 114 |
|
| 115 |
const confusionSlider = document.getElementById('confusion');
|
| 116 |
const confusionValue = document.getElementById('confusionValue');
|
|
@@ -123,142 +126,145 @@
|
|
| 123 |
confusionValue.textContent = parseFloat(confusionSlider.value).toFixed(2);
|
| 124 |
};
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
function generateConfusion(step, difficulty) {
|
| 138 |
-
const configs = {
|
| 139 |
-
easy: { base: 0.3, noise: 0.1, spikeProb: 0.08 },
|
| 140 |
-
medium: { base: 0.5, noise: 0.2, spikeProb: 0.12 },
|
| 141 |
-
hard: { base: 0.6, noise: 0.3, spikeProb: 0.15 }
|
| 142 |
-
};
|
| 143 |
-
const cfg = configs[difficulty] || configs.medium;
|
| 144 |
-
let confusion = cfg.base + Math.sin(step * 0.1) * 0.2;
|
| 145 |
-
confusion += (Math.random() - 0.5) * 2 * cfg.noise;
|
| 146 |
-
if (Math.random() < cfg.spikeProb) confusion += 0.3;
|
| 147 |
-
return Math.max(0, Math.min(1, confusion));
|
| 148 |
}
|
| 149 |
|
| 150 |
-
function reset() {
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
state.interventions = [];
|
| 156 |
-
state.rewards = [];
|
| 157 |
-
state.ground_truth = [];
|
| 158 |
-
state.agent_epsilon = 1.0;
|
| 159 |
-
state.multimodal_fused = true;
|
| 160 |
-
stepCount = 0;
|
| 161 |
-
episodeReward = 0;
|
| 162 |
-
episodeId = 'ep_' + Date.now();
|
| 163 |
-
|
| 164 |
-
const gt = generateConfusion(0, difficulty);
|
| 165 |
-
state.ground_truth.push(gt);
|
| 166 |
-
|
| 167 |
-
statusEl.className = 'status ready';
|
| 168 |
-
statusEl.textContent = `Episode: ${episodeId.slice(0,12)}... | Difficulty: ${difficulty} | Steps: 0/${state.max_steps}`;
|
| 169 |
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
}
|
| 185 |
|
| 186 |
-
function step() {
|
|
|
|
|
|
|
| 187 |
const predicted = parseFloat(confusionSlider.value);
|
| 188 |
const intervention = document.getElementById('intervention').value;
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
const gt = state.ground_truth[state.ground_truth.length - 1];
|
| 194 |
-
const error = Math.abs(predicted - gt);
|
| 195 |
-
let reward = (1 - error) * 0.4;
|
| 196 |
-
|
| 197 |
-
if (state.ground_truth.length > 1) {
|
| 198 |
-
const prev = state.ground_truth[state.ground_truth.length - 2];
|
| 199 |
-
if (gt > prev && predicted > prev) reward += 0.2;
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
if (intervention) {
|
| 203 |
-
state.interventions.push({ step: stepCount, type: intervention, intensity: 0.7 });
|
| 204 |
-
if (gt > 0.5) reward += 0.3;
|
| 205 |
-
}
|
| 206 |
-
|
| 207 |
-
episodeReward += reward;
|
| 208 |
-
state.rewards.push(reward);
|
| 209 |
-
state.predictions.push(predicted);
|
| 210 |
-
|
| 211 |
-
const nextGt = generateConfusion(state.step_count, document.getElementById('difficulty').value);
|
| 212 |
-
state.ground_truth.push(nextGt);
|
| 213 |
-
state.agent_epsilon = Math.max(0.01, state.agent_epsilon * 0.995);
|
| 214 |
-
|
| 215 |
-
const done = stepCount >= state.max_steps;
|
| 216 |
|
| 217 |
-
|
| 218 |
-
const
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
}
|
| 225 |
-
const edr = spikes > 0 ? detected / spikes : 0;
|
| 226 |
-
const ie = state.interventions.length > 0 ? state.interventions.length / preds.length : 0;
|
| 227 |
-
const score = (1 - mae) * 0.4 + edr * 0.3 + ie * 0.3;
|
| 228 |
-
const threshold = document.getElementById('difficulty').value === 'easy' ? 0.5 : document.getElementById('difficulty').value === 'hard' ? 0.7 : 0.6;
|
| 229 |
-
const passed = score >= threshold;
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
}
|
| 241 |
-
}
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
statusEl.textContent =
|
| 247 |
-
} else {
|
| 248 |
-
resultEl.innerHTML = JSON.stringify({
|
| 249 |
-
step: stepCount,
|
| 250 |
-
reward: reward.toFixed(3),
|
| 251 |
-
ground_truth: gt.toFixed(3),
|
| 252 |
-
prediction_error: error.toFixed(3),
|
| 253 |
-
prediction: predicted.toFixed(2),
|
| 254 |
-
intervention: intervention || null,
|
| 255 |
-
agent_state: { epsilon: state.agent_epsilon.toFixed(3) }
|
| 256 |
-
}, null, 2);
|
| 257 |
-
statusEl.textContent = `Episode: ${episodeId.slice(0,12)}... | Steps: ${stepCount}/${state.max_steps} | Reward: ${episodeReward.toFixed(2)}`;
|
| 258 |
}
|
| 259 |
}
|
| 260 |
|
| 261 |
-
|
| 262 |
</script>
|
| 263 |
</body>
|
| 264 |
</html>
|
|
|
|
| 19 |
.result { background: #1a1a2e; color: #0f0; padding: 20px; border-radius: 10px; margin: 15px 0; font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.6; max-height: 400px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; }
|
| 20 |
.metrics { display: flex; gap: 10px; flex-wrap: wrap; margin: 15px 0; }
|
| 21 |
.metric { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px 20px; border-radius: 25px; font-weight: bold; }
|
| 22 |
+
.status { text-align: center; padding: 10px; border-radius: 10px; margin: 10px 0; font-weight: bold; }
|
| 23 |
+
.status.ready { background: #d4edda; color: #155724; }
|
| 24 |
+
.status.error { background: #f8d7da; color: #721c24; }
|
| 25 |
+
.status.loading { background: #fff3cd; color: #856404; }
|
| 26 |
.agent-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 15px 0; }
|
| 27 |
.agent-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px; border-radius: 10px; text-align: center; }
|
| 28 |
.agent-card h3 { margin: 0 0 5px 0; font-size: 14px; }
|
| 29 |
.agent-card p { margin: 0; font-size: 24px; font-weight: bold; }
|
| 30 |
+
.loading { text-align: center; padding: 50px; }
|
| 31 |
+
.spinner { border: 4px solid #f3f3f3; border-top: 4px solid #667eea; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; margin: 0 auto; }
|
| 32 |
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 33 |
</style>
|
| 34 |
</head>
|
| 35 |
<body>
|
| 36 |
<div class="container">
|
| 37 |
<h1>ContextFlow OpenEnv</h1>
|
| 38 |
+
<p class="subtitle">Learning Confusion Prediction with RL Agents</p>
|
| 39 |
|
| 40 |
+
<div id="status" class="status loading">Connecting to ContextFlow API...</div>
|
| 41 |
|
| 42 |
<div class="card">
|
| 43 |
<h2>Environment Setup</h2>
|
| 44 |
<div class="controls">
|
| 45 |
<select id="difficulty">
|
| 46 |
+
<option value="easy">Easy (50 steps, threshold: 0.5)</option>
|
| 47 |
+
<option value="medium" selected>Medium (75 steps, threshold: 0.6)</option>
|
| 48 |
+
<option value="hard">Hard (100 steps, threshold: 0.7)</option>
|
| 49 |
</select>
|
| 50 |
<button onclick="reset()">Reset Environment</button>
|
| 51 |
</div>
|
|
|
|
| 77 |
|
| 78 |
<div class="card">
|
| 79 |
<h2>Results</h2>
|
| 80 |
+
<div id="result" class="result">Click "Reset Environment" to start...</div>
|
| 81 |
</div>
|
| 82 |
|
| 83 |
<div class="card">
|
|
|
|
| 86 |
</div>
|
| 87 |
|
| 88 |
<div class="card">
|
| 89 |
+
<h2>Active ContextFlow Agents</h2>
|
| 90 |
<div class="agent-info">
|
| 91 |
<div class="agent-card">
|
| 92 |
+
<h3>RL Doubt Predictor</h3>
|
| 93 |
<p>Q-Learning</p>
|
| 94 |
</div>
|
| 95 |
<div class="agent-card">
|
| 96 |
+
<h3>Behavioral Analyzer</h3>
|
| 97 |
+
<p>Signal Fusion</p>
|
| 98 |
</div>
|
| 99 |
<div class="agent-card">
|
| 100 |
+
<h3>Knowledge Graph</h3>
|
| 101 |
+
<p>Prerequisites</p>
|
| 102 |
</div>
|
| 103 |
<div class="agent-card">
|
| 104 |
+
<h3>Gesture Recognizer</h3>
|
| 105 |
+
<p>Hand Tracking</p>
|
| 106 |
</div>
|
| 107 |
</div>
|
| 108 |
</div>
|
| 109 |
</div>
|
| 110 |
|
| 111 |
<script>
|
| 112 |
+
const API_BASE = 'https://namish10-contextflow-env-api.hf.space';
|
| 113 |
let episodeId = null;
|
| 114 |
let stepCount = 0;
|
| 115 |
let episodeReward = 0;
|
| 116 |
+
let currentDifficulty = 'medium';
|
| 117 |
|
| 118 |
const confusionSlider = document.getElementById('confusion');
|
| 119 |
const confusionValue = document.getElementById('confusionValue');
|
|
|
|
| 126 |
confusionValue.textContent = parseFloat(confusionSlider.value).toFixed(2);
|
| 127 |
};
|
| 128 |
|
| 129 |
+
async function checkAPI() {
|
| 130 |
+
try {
|
| 131 |
+
const resp = await fetch(API_BASE + '/health');
|
| 132 |
+
const data = await resp.json();
|
| 133 |
+
statusEl.className = 'status ready';
|
| 134 |
+
statusEl.textContent = 'Connected to ContextFlow API - ' + data.status;
|
| 135 |
+
} catch (e) {
|
| 136 |
+
statusEl.className = 'status error';
|
| 137 |
+
statusEl.textContent = 'API Connection Failed: ' + e.message;
|
| 138 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
+
async function reset() {
|
| 142 |
+
currentDifficulty = document.getElementById('difficulty').value;
|
| 143 |
+
statusEl.className = 'status loading';
|
| 144 |
+
statusEl.textContent = 'Resetting environment...';
|
| 145 |
+
stepBtn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
+
try {
|
| 148 |
+
const resp = await fetch(API_BASE + '/reset?difficulty=' + currentDifficulty, {
|
| 149 |
+
method: 'POST',
|
| 150 |
+
headers: { 'Content-Type': 'application/json' }
|
| 151 |
+
});
|
| 152 |
+
|
| 153 |
+
const data = await resp.json();
|
| 154 |
+
episodeId = data.episode_id;
|
| 155 |
+
stepCount = 0;
|
| 156 |
+
episodeReward = 0;
|
| 157 |
+
|
| 158 |
+
const obs = data.observation;
|
| 159 |
+
statusEl.className = 'status ready';
|
| 160 |
+
statusEl.textContent = `Episode: ${episodeId.substring(0, 12)}... | ${obs.learning_context.difficulty.toUpperCase()} | Steps: 0/${currentDifficulty === 'easy' ? 50 : currentDifficulty === 'hard' ? 100 : 75}`;
|
| 161 |
+
|
| 162 |
+
resultEl.innerHTML = JSON.stringify({
|
| 163 |
+
"Episode Started": {
|
| 164 |
+
episode_id: episodeId,
|
| 165 |
+
difficulty: obs.learning_context.difficulty,
|
| 166 |
+
topic: obs.learning_context.topic,
|
| 167 |
+
multimodal_fused: obs.multimodal_fused,
|
| 168 |
+
prediction_window: obs.prediction_window,
|
| 169 |
+
available_interventions: obs.available_interventions,
|
| 170 |
+
agent_state: { epsilon: 1.0 }
|
| 171 |
+
}
|
| 172 |
+
}, null, 2);
|
| 173 |
+
|
| 174 |
+
metricsEl.innerHTML = '';
|
| 175 |
+
stepBtn.disabled = false;
|
| 176 |
+
|
| 177 |
+
} catch (e) {
|
| 178 |
+
statusEl.className = 'status error';
|
| 179 |
+
statusEl.textContent = 'Error: ' + e.message;
|
| 180 |
+
}
|
| 181 |
}
|
| 182 |
|
| 183 |
+
async function step() {
|
| 184 |
+
if (!episodeId) return;
|
| 185 |
+
|
| 186 |
const predicted = parseFloat(confusionSlider.value);
|
| 187 |
const intervention = document.getElementById('intervention').value;
|
| 188 |
|
| 189 |
+
statusEl.className = 'status loading';
|
| 190 |
+
statusEl.textContent = 'Processing step...';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
+
try {
|
| 193 |
+
const resp = await fetch(API_BASE + '/step', {
|
| 194 |
+
method: 'POST',
|
| 195 |
+
headers: { 'Content-Type': 'application/json' },
|
| 196 |
+
body: JSON.stringify({
|
| 197 |
+
episode_id: episodeId,
|
| 198 |
+
action_type: intervention ? 'trigger_intervention' : 'predict_confusion',
|
| 199 |
+
predicted_confusion: predicted,
|
| 200 |
+
intervention_type: intervention || null,
|
| 201 |
+
intervention_intensity: intervention ? 0.7 : null
|
| 202 |
+
})
|
| 203 |
+
});
|
| 204 |
+
|
| 205 |
+
const data = await resp.json();
|
| 206 |
+
stepCount++;
|
| 207 |
+
|
| 208 |
+
if (data.error) {
|
| 209 |
+
statusEl.className = 'status error';
|
| 210 |
+
statusEl.textContent = data.error;
|
| 211 |
+
return;
|
| 212 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
+
const reward = data.reward;
|
| 215 |
+
const obs = data.observation;
|
| 216 |
+
episodeReward += reward.total;
|
| 217 |
+
|
| 218 |
+
if (data.done) {
|
| 219 |
+
const grader = data.info.grader_result;
|
| 220 |
+
statusEl.className = grader.passed ? 'status ready' : 'status error';
|
| 221 |
+
statusEl.textContent = grader.passed ? 'EPISODE PASSED!' : 'Episode Ended';
|
| 222 |
+
|
| 223 |
+
resultEl.innerHTML = JSON.stringify({
|
| 224 |
+
"Episode Complete": {
|
| 225 |
+
total_steps: stepCount,
|
| 226 |
+
total_reward: episodeReward.toFixed(3),
|
| 227 |
+
grader_result: grader
|
| 228 |
}
|
| 229 |
+
}, null, 2);
|
| 230 |
+
|
| 231 |
+
metricsEl.innerHTML = `
|
| 232 |
+
<div class="metric" style="font-size:28px;${grader.passed ? '' : 'background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);'}">${grader.passed ? 'PASSED' : 'FAILED'}</div>
|
| 233 |
+
<div class="metric">Score: ${grader.score.toFixed(3)}</div>
|
| 234 |
+
<div class="metric">MAE: ${grader.metrics.mae.toFixed(3)}</div>
|
| 235 |
+
<div class="metric">Early Detection: ${(grader.metrics.early_detection_rate * 100).toFixed(0)}%</div>
|
| 236 |
+
<div class="metric">Interventions: ${grader.metrics.total_interventions}</div>
|
| 237 |
+
`;
|
| 238 |
+
|
| 239 |
+
stepBtn.disabled = true;
|
| 240 |
+
} else {
|
| 241 |
+
statusEl.className = 'status ready';
|
| 242 |
+
statusEl.textContent = `Episode: ${episodeId.substring(0, 12)}... | Steps: ${stepCount}/${currentDifficulty === 'easy' ? 50 : currentDifficulty === 'hard' ? 100 : 75} | Reward: ${episodeReward.toFixed(2)}`;
|
| 243 |
+
|
| 244 |
+
resultEl.innerHTML = JSON.stringify({
|
| 245 |
+
step: stepCount,
|
| 246 |
+
reward: reward.total.toFixed(3),
|
| 247 |
+
ground_truth: reward.metadata.ground_truth.toFixed(3),
|
| 248 |
+
prediction_error: reward.metadata.prediction_error.toFixed(3),
|
| 249 |
+
prediction: predicted.toFixed(2),
|
| 250 |
+
intervention: intervention || null,
|
| 251 |
+
learner_state: obs.learner_state
|
| 252 |
+
}, null, 2);
|
| 253 |
+
|
| 254 |
+
metricsEl.innerHTML = `
|
| 255 |
+
<div class="metric">Step: ${stepCount}</div>
|
| 256 |
+
<div class="metric">Reward: ${reward.total.toFixed(3)}</div>
|
| 257 |
+
<div class="metric">Error: ${reward.metadata.prediction_error.toFixed(3)}</div>
|
| 258 |
+
`;
|
| 259 |
+
}
|
| 260 |
|
| 261 |
+
} catch (e) {
|
| 262 |
+
statusEl.className = 'status error';
|
| 263 |
+
statusEl.textContent = 'Error: ' + e.message;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
}
|
| 265 |
}
|
| 266 |
|
| 267 |
+
checkAPI();
|
| 268 |
</script>
|
| 269 |
</body>
|
| 270 |
</html>
|