Spaces:
Sleeping
Sleeping
Commit ·
dd2851f
1
Parent(s): f480f98
Unified dashboard for training and testing
Browse files- dashboard.html +82 -1
- requirements.txt +4 -1
- src/adaptive_alert_triage/server.py +79 -1
dashboard.html
CHANGED
|
@@ -242,6 +242,17 @@
|
|
| 242 |
.toast-info { background: rgba(59,130,246,0.9); color: white; }
|
| 243 |
@keyframes toastIn { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} }
|
| 244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
@media (max-width:1200px) {
|
| 246 |
.grid-stats { grid-template-columns: repeat(3, 1fr); }
|
| 247 |
.grid-main, .grid-bottom { grid-template-columns: 1fr; }
|
|
@@ -269,6 +280,25 @@
|
|
| 269 |
</div>
|
| 270 |
</div>
|
| 271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
<!-- ══════ Action Guide ══════ -->
|
| 273 |
<div class="help-bar">
|
| 274 |
<div class="help-item">
|
|
@@ -437,13 +467,14 @@
|
|
| 437 |
</div>
|
| 438 |
</div>
|
| 439 |
</div>
|
|
|
|
| 440 |
</div>
|
| 441 |
|
| 442 |
<script>
|
| 443 |
// ═══════════════════════════════════════════════════════════════════
|
| 444 |
// State
|
| 445 |
// ═══════════════════════════════════════════════════════════════════
|
| 446 |
-
const SERVER = 'https://tusharp2006-scaler-deployment.hf.space
|
| 447 |
let currentAlerts = [];
|
| 448 |
let episodeScores = [];
|
| 449 |
let episodeRewards = [];
|
|
@@ -772,11 +803,61 @@ function showToast(msg, type='success') {
|
|
| 772 |
setTimeout(() => el.remove(), 3500);
|
| 773 |
}
|
| 774 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
// ═══════════════════════════════════════════════════════════════════
|
| 776 |
// Init
|
| 777 |
// ═══════════════════════════════════════════════════════════════════
|
| 778 |
pollHealth();
|
|
|
|
| 779 |
setInterval(pollHealth, 5000);
|
|
|
|
| 780 |
</script>
|
| 781 |
</body>
|
| 782 |
</html>
|
|
|
|
| 242 |
.toast-info { background: rgba(59,130,246,0.9); color: white; }
|
| 243 |
@keyframes toastIn { from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:translateY(0)} }
|
| 244 |
|
| 245 |
+
/* ── Tabs & Training ────────────────────────────────────── */
|
| 246 |
+
.tabs { display: flex; gap: 14px; margin-bottom: 24px; border-bottom: 1px solid var(--border); padding-bottom: 10px; }
|
| 247 |
+
.tab { font-size: 14px; font-weight: 600; color: var(--text-muted); cursor: pointer; padding: 10px 16px; border-radius: 8px; transition: 0.2s; }
|
| 248 |
+
.tab:hover { background: rgba(255,255,255,0.05); color: var(--text-primary); }
|
| 249 |
+
.tab.active { background: var(--bg-input); color: var(--accent-blue); border: 1px solid var(--border); }
|
| 250 |
+
.training-container { display: none; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 24px; }
|
| 251 |
+
.training-container.active { display: block; }
|
| 252 |
+
.testing-container { display: none; }
|
| 253 |
+
.testing-container.active { display: block; }
|
| 254 |
+
.terminal { background: #000; color: #10b981; font-family: 'Courier New', monospace; padding: 16px; border-radius: 8px; height: 400px; overflow-y: auto; margin-top: 16px; font-size: 12px; border: 1px solid var(--border); line-height: 1.5; white-space: pre-wrap; }
|
| 255 |
+
|
| 256 |
@media (max-width:1200px) {
|
| 257 |
.grid-stats { grid-template-columns: repeat(3, 1fr); }
|
| 258 |
.grid-main, .grid-bottom { grid-template-columns: 1fr; }
|
|
|
|
| 280 |
</div>
|
| 281 |
</div>
|
| 282 |
|
| 283 |
+
<!-- ══════ Tabs ══════ -->
|
| 284 |
+
<div class="tabs">
|
| 285 |
+
<div class="tab active" id="tabLive" onclick="switchTab('live')">Live Testing</div>
|
| 286 |
+
<div class="tab" id="tabTrain" onclick="switchTab('train')">Agent Training</div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<!-- ══════ Training View ══════ -->
|
| 290 |
+
<div class="training-container" id="trainingView">
|
| 291 |
+
<h3>Train RL Agent (PPO)</h3>
|
| 292 |
+
<p style="color:var(--text-muted); font-size:12px; margin-top:8px; margin-bottom:16px;">Launch background training across varying difficulty tasks.</p>
|
| 293 |
+
<div class="controls">
|
| 294 |
+
<span style="font-size:12px">Easy Episodes:</span>
|
| 295 |
+
<input type="number" id="trainEpisodes" value="300" style="padding:6px; border-radius:6px; border:1px solid var(--border); background:var(--bg-input); color:var(--text-primary); width:100px;">
|
| 296 |
+
<button class="btn btn-primary" onclick="startTraining()" id="trainBtn">▶ Start Training</button>
|
| 297 |
+
</div>
|
| 298 |
+
<div class="terminal" id="trainTerminal">Ready to train. Run PPO to update weights.</div>
|
| 299 |
+
</div>
|
| 300 |
+
|
| 301 |
+
<div class="testing-container active" id="testingView">
|
| 302 |
<!-- ══════ Action Guide ══════ -->
|
| 303 |
<div class="help-bar">
|
| 304 |
<div class="help-item">
|
|
|
|
| 467 |
</div>
|
| 468 |
</div>
|
| 469 |
</div>
|
| 470 |
+
</div> <!-- end testing-container -->
|
| 471 |
</div>
|
| 472 |
|
| 473 |
<script>
|
| 474 |
// ═══════════════════════════════════════════════════════════════════
|
| 475 |
// State
|
| 476 |
// ═══════════════════════════════════════════════════════════════════
|
| 477 |
+
const SERVER = 'https://tusharp2006-scaler-deployment.hf.space';
|
| 478 |
let currentAlerts = [];
|
| 479 |
let episodeScores = [];
|
| 480 |
let episodeRewards = [];
|
|
|
|
| 803 |
setTimeout(() => el.remove(), 3500);
|
| 804 |
}
|
| 805 |
|
| 806 |
+
// ═══════════════════════════════════════════════════════════════════
|
| 807 |
+
// UI & Tabs
|
| 808 |
+
// ═══════════════════════════════════════════════════════════════════
|
| 809 |
+
function switchTab(tab) {
|
| 810 |
+
document.getElementById('tabLive').className = tab === 'live' ? 'tab active' : 'tab';
|
| 811 |
+
document.getElementById('tabTrain').className = tab === 'train' ? 'tab active' : 'tab';
|
| 812 |
+
document.getElementById('testingView').className = tab === 'live' ? 'testing-container active' : 'testing-container';
|
| 813 |
+
document.getElementById('trainingView').className = tab === 'train' ? 'training-container active' : 'training-container';
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
// ═══════════════════════════════════════════════════════════════════
|
| 817 |
+
// Training Webhooks
|
| 818 |
+
// ═══════════════════════════════════════════════════════════════════
|
| 819 |
+
let trainingInterval = null;
|
| 820 |
+
|
| 821 |
+
async function startTraining() {
|
| 822 |
+
const ep = document.getElementById('trainEpisodes').value || 300;
|
| 823 |
+
try {
|
| 824 |
+
const r = await api('POST', `/train?episodes=${ep}`);
|
| 825 |
+
if(r.error) showToast(r.error, 'error');
|
| 826 |
+
else {
|
| 827 |
+
showToast('Training started in background!', 'success');
|
| 828 |
+
if(!trainingInterval) trainingInterval = setInterval(pollTraining, 2000);
|
| 829 |
+
document.getElementById('trainBtn').disabled = true;
|
| 830 |
+
}
|
| 831 |
+
} catch(e) { showToast('Trainer error', 'error'); }
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
+
async function pollTraining() {
|
| 835 |
+
try {
|
| 836 |
+
const r = await api('GET', '/train/status');
|
| 837 |
+
if (r.error) return;
|
| 838 |
+
|
| 839 |
+
const term = document.getElementById('trainTerminal');
|
| 840 |
+
if (r.logs && r.logs.length) {
|
| 841 |
+
term.textContent = r.logs.join('\n');
|
| 842 |
+
term.scrollTop = term.scrollHeight; // Auto-scroll
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
+
if (!r.is_running) {
|
| 846 |
+
if(trainingInterval) { clearInterval(trainingInterval); trainingInterval = null; }
|
| 847 |
+
document.getElementById('trainBtn').disabled = false;
|
| 848 |
+
} else {
|
| 849 |
+
document.getElementById('trainBtn').disabled = true;
|
| 850 |
+
}
|
| 851 |
+
} catch(e) {}
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
// ═══════════════════════════════════════════════════════════════════
|
| 855 |
// Init
|
| 856 |
// ═══════════════════════════════════════════════════════════════════
|
| 857 |
pollHealth();
|
| 858 |
+
pollTraining();
|
| 859 |
setInterval(pollHealth, 5000);
|
| 860 |
+
if(document.getElementById('tabTrain').className.includes('active')) setInterval(pollTraining, 2000);
|
| 861 |
</script>
|
| 862 |
</body>
|
| 863 |
</html>
|
requirements.txt
CHANGED
|
@@ -20,4 +20,7 @@ openai>=1.0.0
|
|
| 20 |
requests>=2.31.0
|
| 21 |
|
| 22 |
# ── YAML parsing ─────────────────────────────────────────────────────────────
|
| 23 |
-
pyyaml>=6.0
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
requests>=2.31.0
|
| 21 |
|
| 22 |
# ── YAML parsing ─────────────────────────────────────────────────────────────
|
| 23 |
+
pyyaml>=6.0
|
| 24 |
+
|
| 25 |
+
# ── Auto-commit back to Space ─────────────────────────────────────────────────
|
| 26 |
+
huggingface_hub>=0.20.0
|
src/adaptive_alert_triage/server.py
CHANGED
|
@@ -100,7 +100,7 @@ def _norm(raw: str) -> str:
|
|
| 100 |
|
| 101 |
app = FastAPI(title="Adaptive Alert Triage RL Server", version="0.3.0")
|
| 102 |
app.add_middleware(CORSMiddleware, allow_origins=["*"],
|
| 103 |
-
allow_credentials=
|
| 104 |
#Changes
|
| 105 |
@app.middleware("http")
|
| 106 |
async def log_requests(request, call_next):
|
|
@@ -551,6 +551,84 @@ async def root():
|
|
| 551 |
}
|
| 552 |
|
| 553 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
@app.get("/web")
|
| 555 |
async def web_ui():
|
| 556 |
"""
|
|
|
|
| 100 |
|
| 101 |
app = FastAPI(title="Adaptive Alert Triage RL Server", version="0.3.0")
|
| 102 |
app.add_middleware(CORSMiddleware, allow_origins=["*"],
|
| 103 |
+
allow_credentials=False, allow_methods=["*"], allow_headers=["*"])
|
| 104 |
#Changes
|
| 105 |
@app.middleware("http")
|
| 106 |
async def log_requests(request, call_next):
|
|
|
|
| 551 |
}
|
| 552 |
|
| 553 |
|
| 554 |
+
import threading
|
| 555 |
+
import subprocess
|
| 556 |
+
|
| 557 |
+
_training_proc = None
|
| 558 |
+
_training_logs = []
|
| 559 |
+
|
| 560 |
+
def _run_training(episodes: int):
|
| 561 |
+
global _training_proc, _training_logs, _ppo_agents
|
| 562 |
+
_training_logs = [f"Starting training with --episodes {episodes}..."]
|
| 563 |
+
try:
|
| 564 |
+
_training_proc = subprocess.Popen(
|
| 565 |
+
[sys.executable, "train_rl.py", "--episodes", str(episodes)],
|
| 566 |
+
stdout=subprocess.PIPE,
|
| 567 |
+
stderr=subprocess.STDOUT,
|
| 568 |
+
text=True,
|
| 569 |
+
bufsize=1,
|
| 570 |
+
cwd=_project_root if _project_root else os.getcwd()
|
| 571 |
+
)
|
| 572 |
+
for line in iter(_training_proc.stdout.readline, ''):
|
| 573 |
+
if line:
|
| 574 |
+
_training_logs.append(line.rstrip('\n'))
|
| 575 |
+
if len(_training_logs) > 1000:
|
| 576 |
+
_training_logs.pop(0)
|
| 577 |
+
_training_proc.wait()
|
| 578 |
+
_training_logs.append(f"Training finished with exit code {- _training_proc.returncode if _training_proc.returncode < 0 else _training_proc.returncode}")
|
| 579 |
+
|
| 580 |
+
# Auto-reload PPO weights if training succeeded
|
| 581 |
+
if _training_proc.returncode == 0:
|
| 582 |
+
for tid in ("easy", "medium", "hard"):
|
| 583 |
+
agent = _load_ppo(tid)
|
| 584 |
+
if agent:
|
| 585 |
+
_ppo_agents[tid] = agent
|
| 586 |
+
_training_logs.append("Successfully reloaded PPO weights for all tasks.")
|
| 587 |
+
|
| 588 |
+
# Auto-save weights back to Hugging Face
|
| 589 |
+
try:
|
| 590 |
+
from huggingface_hub import HfApi
|
| 591 |
+
hf_token = os.environ.get("HF_TOKEN")
|
| 592 |
+
repo_id = os.environ.get("SPACE_ID", "tusharp2006/scaler-deployment")
|
| 593 |
+
|
| 594 |
+
if hf_token:
|
| 595 |
+
_training_logs.append(f"Pushing updated weights back to HF Hub ({repo_id})...")
|
| 596 |
+
api = HfApi(token=hf_token)
|
| 597 |
+
weights_dir = os.path.join(_project_root if _project_root else os.getcwd(), "weights")
|
| 598 |
+
|
| 599 |
+
if os.path.exists(weights_dir):
|
| 600 |
+
api.upload_folder(
|
| 601 |
+
repo_id=repo_id,
|
| 602 |
+
folder_path=weights_dir,
|
| 603 |
+
path_in_repo="weights",
|
| 604 |
+
repo_type="space",
|
| 605 |
+
commit_message="Auto-sync weights after RL training"
|
| 606 |
+
)
|
| 607 |
+
_training_logs.append("Weights successfully pushed and persisted to Hugging Face!")
|
| 608 |
+
else:
|
| 609 |
+
_training_logs.append("No HF_TOKEN found in environment. Skipping weight cloud-persistence.")
|
| 610 |
+
except ImportError:
|
| 611 |
+
_training_logs.append("huggingface_hub not installed. Skipping cloud backup.")
|
| 612 |
+
except Exception as e:
|
| 613 |
+
_training_logs.append(f"Failed to push weights to Hub: {e}")
|
| 614 |
+
|
| 615 |
+
except Exception as e:
|
| 616 |
+
_training_logs.append(f"Error starting training: {e}")
|
| 617 |
+
|
| 618 |
+
@app.post("/train")
|
| 619 |
+
async def start_training(episodes: int = 300):
|
| 620 |
+
global _training_proc
|
| 621 |
+
if _training_proc is not None and _training_proc.poll() is None:
|
| 622 |
+
return {"status": "already running"}
|
| 623 |
+
threading.Thread(target=_run_training, args=(episodes,), daemon=True).start()
|
| 624 |
+
return {"status": "started"}
|
| 625 |
+
|
| 626 |
+
@app.get("/train/status")
|
| 627 |
+
async def get_training_status():
|
| 628 |
+
global _training_proc, _training_logs
|
| 629 |
+
is_running = _training_proc is not None and _training_proc.poll() is None
|
| 630 |
+
return {"is_running": is_running, "logs": _training_logs}
|
| 631 |
+
|
| 632 |
@app.get("/web")
|
| 633 |
async def web_ui():
|
| 634 |
"""
|