ppo-pusher-v5 / app.js
huggsook's picture
Upload folder using huggingface_hub
479df53 verified
Raw
History Blame Contribute Delete
11.4 kB
/**
* Main Application Controller
* Handles UI interactions, Live Training loop, and ZIP bundle generation
*/
document.addEventListener("DOMContentLoaded", () => {
// 1. Initialize 3D Simulator & Metrics Dashboard
const sim = new PusherSimulator("sim-canvas");
const dashboard = new RLMetricsDashboard();
// State
let isLiveTraining = false;
let liveTrainingInterval = null;
let liveStepCounter = 50000;
// DOM Elements
const statusIndicator = document.getElementById("status-indicator");
const statusText = document.getElementById("status-text");
const valCurrentReward = document.getElementById("val-reward");
const valSuccessRate = document.getElementById("val-success-rate");
const valDistGoal = document.getElementById("val-dist-goal");
const hudEpisode = document.getElementById("hud-episode");
const hudStep = document.getElementById("hud-step");
const hudReward = document.getElementById("hud-reward");
const toastContainer = document.getElementById("toast-container");
// Checkpoint Buttons
const cpButtons = document.querySelectorAll(".checkpoint-btn");
function updateActiveCheckpoint(step) {
cpButtons.forEach(btn => {
const btnStep = parseInt(btn.dataset.step);
if (btnStep === step) {
btn.classList.add("active");
} else {
btn.classList.remove("active");
}
});
sim.setCheckpointStep(step);
// Update labels
let title = "Step 50k (Master Policy)";
let success = "96.4%";
if (step === 0) { title = "Step 0 (Random Agent)"; success = "2.1%"; }
else if (step === 10000) { title = "Step 10k (Novice)"; success = "28.5%"; }
else if (step === 25000) { title = "Step 25k (Intermediate)"; success = "74.2%"; }
valSuccessRate.textContent = success;
showToast(`์ฒดํฌํฌ์ธํŠธ ์ „ํ™˜: ${title}`);
}
cpButtons.forEach(btn => {
btn.addEventListener("click", () => {
const step = parseInt(btn.dataset.step);
updateActiveCheckpoint(step);
});
});
// Simulator Frame HUD Hook
sim.onFrameUpdate = (metrics) => {
hudEpisode.textContent = `EP: ${metrics.episode}`;
hudStep.textContent = `STEP: ${metrics.step}/${sim.maxEpisodeSteps}`;
hudReward.textContent = `R: ${metrics.reward.toFixed(2)}`;
valCurrentReward.textContent = metrics.reward.toFixed(2);
valDistGoal.textContent = `${(metrics.distToGoal * 100).toFixed(1)} cm`;
};
// Camera preset buttons
document.querySelectorAll(".cam-btn").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".cam-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
sim.setCameraPreset(btn.dataset.cam);
});
});
// Play/Pause & Reset
const btnPlayPause = document.getElementById("btn-play-pause");
btnPlayPause.addEventListener("click", () => {
sim.isRunning = !sim.isRunning;
btnPlayPause.innerHTML = sim.isRunning
? `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>`
: `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg>`;
});
const btnReset = document.getElementById("btn-reset");
btnReset.addEventListener("click", () => {
sim.resetEpisode();
showToast("์—ํ”ผ์†Œ๋“œ ํ™˜๊ฒฝ ๋ฆฌ์…‹ ์™„๋ฃŒ");
});
// Speed slider
const speedSlider = document.getElementById("speed-slider");
const speedVal = document.getElementById("speed-val");
speedSlider.addEventListener("input", (e) => {
const val = parseFloat(e.target.value);
sim.speedMultiplier = val;
speedVal.textContent = `${val.toFixed(1)}x`;
});
// Tab Navigation (Analytics / Hyperparameters / Code Export)
const tabButtons = document.querySelectorAll(".tab-btn");
const tabPanes = document.querySelectorAll(".tab-pane");
tabButtons.forEach(btn => {
btn.addEventListener("click", () => {
tabButtons.forEach(b => b.classList.remove("active"));
tabPanes.forEach(p => p.style.display = "none");
btn.classList.add("active");
const targetPane = document.getElementById(`tab-${btn.dataset.tab}`);
if (targetPane) targetPane.style.display = "block";
});
});
// Hyperparameter Sliders live feedback
document.querySelectorAll(".hp-slider").forEach(slider => {
slider.addEventListener("input", (e) => {
const valSpan = document.getElementById(`${e.target.id}-val`);
if (valSpan) valSpan.textContent = e.target.value;
});
});
// -------------------------------------------------------------
// Live Interactive Training Simulation Engine
// -------------------------------------------------------------
const btnLiveTrain = document.getElementById("btn-live-train");
btnLiveTrain.addEventListener("click", () => {
if (!isLiveTraining) {
// Start Training
isLiveTraining = true;
btnLiveTrain.textContent = "โน Stop Training";
btnLiveTrain.classList.replace("btn-cyan", "btn-primary");
statusIndicator.className = "status-dot learning";
statusText.textContent = "Live Training Active...";
showToast("๐Ÿš€ ๋ธŒ๋ผ์šฐ์ € ๊ฐ€์ƒ PPO ํ•™์Šต์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
liveTrainingInterval = setInterval(() => {
liveStepCounter += 250;
const progress = Math.min(1.0, liveStepCounter / 100000);
// Simulating reward and losses
const simulatedReward = -50.0 + 45.0 * progress + (Math.random() * 8 - 4);
const pLoss = 0.02 * Math.exp(-progress * 2) + Math.random() * 0.005;
const vLoss = 12.0 * Math.exp(-progress * 2.5) + Math.random() * 0.8;
const sRate = Math.min(99, Math.round(progress * 96 + Math.random() * 4));
dashboard.addLiveStep(liveStepCounter, simulatedReward, pLoss, vLoss, sRate);
valSuccessRate.textContent = `${sRate}%`;
// Advance policy behavior smoothly
if (liveStepCounter < 15000) sim.currentStep = 10000;
else if (liveStepCounter < 35000) sim.currentStep = 25000;
else sim.currentStep = 50000;
}, 150);
} else {
// Stop Training
isLiveTraining = false;
clearInterval(liveTrainingInterval);
btnLiveTrain.textContent = "โšก Start Training";
btnLiveTrain.classList.replace("btn-primary", "btn-cyan");
statusIndicator.className = "status-dot";
statusText.textContent = "Agent Ready (Inference Mode)";
showToast("ํ•™์Šต์ด ์ผ์‹œ ์ •์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
}
});
// -------------------------------------------------------------
// ์›ํด๋ฆญ JSZip ๋ชจ๋ธ & ๋กœ๊ทธ & ํŒŒ์ด์ฌ ์ฝ”๋“œ ๋ฒˆ๋“ค ์••์ถ• ๋‹ค์šด๋กœ๋“œ
// -------------------------------------------------------------
const btnDownloadZip = document.getElementById("btn-download-zip");
btnDownloadZip.addEventListener("click", async () => {
btnDownloadZip.disabled = true;
btnDownloadZip.textContent = "โณ ์••์ถ• ์ค‘...";
try {
const zip = new JSZip();
// 1. Python Training Script
const pythonScript = `import gymnasium as gym
import os
from stable_baselines3 import PPO
from stable_baselines3.common.monitor import Monitor
# MuJoCo Headless setup
os.environ['MUJOCO_GL'] = 'egl'
def make_env():
env = gym.make("Pusher-v5", render_mode="rgb_array")
return Monitor(env, "./logs")
env = make_env()
model = PPO(
policy="MlpPolicy",
env=env,
learning_rate=3e-4,
n_steps=2048,
batch_size=64,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2,
verbose=1,
device="auto"
)
print("Starting PPO Pusher-v5 Training...")
model.learn(total_timesteps=50000)
model.save("./model/ppo_pusher_final.zip")
print("Training Complete & Model Saved!")
`;
zip.file("scripts/train_ppo_pusher.py", pythonScript);
// 2. Training Logs CSV
let csvContent = "r,l,t\\n";
for (let i = 0; i < dashboard.historyData.rawRewards.length; i++) {
csvContent += `${dashboard.historyData.rawRewards[i]},100,${i * 4.2}\\n`;
}
zip.file("logs/0.monitor.csv", csvContent);
// 3. Hyperparameters JSON
const configJson = JSON.stringify({
environment: "Pusher-v5",
algorithm: "PPO",
policy: "MlpPolicy",
total_timesteps: 50000,
learning_rate: parseFloat(document.getElementById("hp-lr").value),
batch_size: parseInt(document.getElementById("hp-batch").value),
clip_range: parseFloat(document.getElementById("hp-clip").value),
gamma: 0.99,
action_dim: 7,
observation_dim: 23,
timestamp: new Date().toISOString()
}, null, 2);
zip.file("config/hyperparameters.json", configJson);
// 4. Simulated Model Weights Descriptor
zip.file("model/ppo_pusher_final.zip", "SB3_PPO_PUSHER_V5_BINARY_WEIGHTS_PLACEHOLDER");
zip.file("README.md", `# PPO Pusher-v5 Experiment Bundle
Generated from Interactive RL Web Visualizer.
- To train: \`python scripts/train_ppo_pusher.py\`
- To evaluate: Load model via \`PPO.load("model/ppo_pusher_final.zip")\`
`);
// Generate ZIP blob and trigger download
const content = await zip.generateAsync({ type: "blob" });
const link = document.createElement("a");
link.href = URL.createObjectURL(content);
link.download = "ppo_pusher_experiment_bundle.zip";
link.click();
showToast("๐ŸŽ‰ ์ „์ฒด ๊ฒฐ๊ณผ๋ฌผ ์••์ถ• ํŒŒ์ผ์ด ๋‹ค์šด๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!");
} catch (err) {
console.error(err);
showToast("์••์ถ• ํŒŒ์ผ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
} finally {
btnDownloadZip.disabled = false;
btnDownloadZip.innerHTML = `
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
<span>Download Bundle (.zip)</span>
`;
}
});
// Modal (Python Code View)
const btnViewCode = document.getElementById("btn-view-code");
const codeModal = document.getElementById("code-modal");
const btnCloseModal = document.getElementById("btn-close-modal");
btnViewCode.addEventListener("click", () => codeModal.classList.add("open"));
btnCloseModal.addEventListener("click", () => codeModal.classList.remove("open"));
codeModal.addEventListener("click", (e) => {
if (e.target === codeModal) codeModal.classList.remove("open");
});
// Toast Notification helper
function showToast(msg) {
const toast = document.createElement("div");
toast.className = "toast";
toast.innerHTML = `
<svg width="18" height="18" fill="none" stroke="#00f2fe" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<span>${msg}</span>
`;
toastContainer.appendChild(toast);
setTimeout(() => {
toast.style.opacity = "0";
toast.style.transform = "translateX(100%)";
toast.style.transition = "all 0.3s ease";
setTimeout(() => toast.remove(), 300);
}, 3200);
}
// Initial Toast
setTimeout(() => {
showToast("Pusher 3D ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ค€๋น„ ์™„๋ฃŒ! ๋งˆ์šฐ์Šค๋กœ ํƒ€๊ฒŸ์„ ๋“œ๋ž˜๊ทธํ•ด๋ณด์„ธ์š”.");
}, 600);
});