Spaces:
Sleeping
Sleeping
Aryanshh commited on
Commit ·
802c5f2
1
Parent(s): 7877689
feat: Add Live Impact Preview to HUD Control Center
Browse files- dashboard/app.js +37 -7
- dashboard/index.html +7 -0
- dashboard/style.css +26 -0
dashboard/app.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
| 1 |
const API_BASE = window.location.origin;
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
// State Management
|
| 4 |
async function updateState() {
|
| 5 |
try {
|
|
@@ -11,15 +18,11 @@ async function updateState() {
|
|
| 11 |
status.textContent = 'ONLINE';
|
| 12 |
status.className = 'status-badge status-online';
|
| 13 |
|
| 14 |
-
// Update Multi-objective Metrics
|
| 15 |
document.getElementById('carbon-value').textContent = data.carbon.toFixed(0);
|
| 16 |
document.getElementById('cash-value').textContent = data.cash.toLocaleString();
|
| 17 |
-
|
| 18 |
-
// Update Inventory UI
|
| 19 |
document.getElementById('chips-count').textContent = data.inventory.chips;
|
| 20 |
document.getElementById('sensors-count').textContent = data.inventory.sensors;
|
| 21 |
|
| 22 |
-
// Update News
|
| 23 |
const newsAlert = document.getElementById('news-alert');
|
| 24 |
if (data.news) {
|
| 25 |
document.getElementById('news-text').textContent = data.news;
|
|
@@ -28,7 +31,6 @@ async function updateState() {
|
|
| 28 |
newsAlert.classList.add('hidden');
|
| 29 |
}
|
| 30 |
|
| 31 |
-
// Update Shipments
|
| 32 |
const container = document.getElementById('shipments-container');
|
| 33 |
if (data.orders && data.orders.length > 0) {
|
| 34 |
container.innerHTML = data.orders.map(order => `
|
|
@@ -51,17 +53,44 @@ async function updateState() {
|
|
| 51 |
|
| 52 |
// UI Controls
|
| 53 |
const actionSelect = document.getElementById('action-select');
|
|
|
|
|
|
|
| 54 |
const orderOptions = document.getElementById('order-options');
|
| 55 |
const productionOptions = document.getElementById('production-options');
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
actionSelect.addEventListener('change', () => {
|
| 58 |
orderOptions.classList.add('hidden');
|
| 59 |
productionOptions.classList.add('hidden');
|
| 60 |
|
| 61 |
if (actionSelect.value === 'order_parts') orderOptions.classList.remove('hidden');
|
| 62 |
if (actionSelect.value === 'produce') productionOptions.classList.remove('hidden');
|
|
|
|
| 63 |
});
|
| 64 |
|
|
|
|
|
|
|
| 65 |
function log(message, type = 'system') {
|
| 66 |
const box = document.getElementById('activity-log');
|
| 67 |
const entry = document.createElement('p');
|
|
@@ -85,8 +114,8 @@ async function executeAction(overrideType = null) {
|
|
| 85 |
let actionObj = { action_type: type };
|
| 86 |
|
| 87 |
if (type === 'order_parts') {
|
| 88 |
-
actionObj.part_type =
|
| 89 |
-
actionObj.mode =
|
| 90 |
actionObj.quantity = 5;
|
| 91 |
} else if (type === 'produce') {
|
| 92 |
actionObj.product = document.getElementById('product-select').value;
|
|
@@ -151,3 +180,4 @@ document.getElementById('reset-btn').addEventListener('click', manualReset);
|
|
| 151 |
// Start
|
| 152 |
updateState();
|
| 153 |
setInterval(updateState, 3000);
|
|
|
|
|
|
| 1 |
const API_BASE = window.location.origin;
|
| 2 |
|
| 3 |
+
// Simulation Specs (Mirrors env.py)
|
| 4 |
+
const SPECS = {
|
| 5 |
+
sea: { cost: 1.0, carbon: 0.1 },
|
| 6 |
+
air: { cost: 5.0, carbon: 2.0 },
|
| 7 |
+
rail: { cost: 2.5, carbon: 0.5 }
|
| 8 |
+
};
|
| 9 |
+
|
| 10 |
// State Management
|
| 11 |
async function updateState() {
|
| 12 |
try {
|
|
|
|
| 18 |
status.textContent = 'ONLINE';
|
| 19 |
status.className = 'status-badge status-online';
|
| 20 |
|
|
|
|
| 21 |
document.getElementById('carbon-value').textContent = data.carbon.toFixed(0);
|
| 22 |
document.getElementById('cash-value').textContent = data.cash.toLocaleString();
|
|
|
|
|
|
|
| 23 |
document.getElementById('chips-count').textContent = data.inventory.chips;
|
| 24 |
document.getElementById('sensors-count').textContent = data.inventory.sensors;
|
| 25 |
|
|
|
|
| 26 |
const newsAlert = document.getElementById('news-alert');
|
| 27 |
if (data.news) {
|
| 28 |
document.getElementById('news-text').textContent = data.news;
|
|
|
|
| 31 |
newsAlert.classList.add('hidden');
|
| 32 |
}
|
| 33 |
|
|
|
|
| 34 |
const container = document.getElementById('shipments-container');
|
| 35 |
if (data.orders && data.orders.length > 0) {
|
| 36 |
container.innerHTML = data.orders.map(order => `
|
|
|
|
| 53 |
|
| 54 |
// UI Controls
|
| 55 |
const actionSelect = document.getElementById('action-select');
|
| 56 |
+
const partSelect = document.getElementById('part-select');
|
| 57 |
+
const modeSelect = document.getElementById('mode-select');
|
| 58 |
const orderOptions = document.getElementById('order-options');
|
| 59 |
const productionOptions = document.getElementById('production-options');
|
| 60 |
|
| 61 |
+
function updatePreview() {
|
| 62 |
+
const type = actionSelect.value;
|
| 63 |
+
let cost = 0;
|
| 64 |
+
let carbon = 0;
|
| 65 |
+
|
| 66 |
+
if (type === 'order_parts') {
|
| 67 |
+
const mode = modeSelect.value;
|
| 68 |
+
const s = SPECS[mode];
|
| 69 |
+
cost = 10 * 5 * s.cost; // Base $10 * 5 qty * mode mult
|
| 70 |
+
carbon = 5 * s.carbon; // 5 qty * mode carbon
|
| 71 |
+
} else if (type === 'produce') {
|
| 72 |
+
cost = 0; // Production is free but uses parts
|
| 73 |
+
carbon = 5.0; // Minimal factory footprint
|
| 74 |
+
} else if (type === 'offset') {
|
| 75 |
+
cost = 100 * 2.0; // $2 per unit
|
| 76 |
+
carbon = -100;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
document.getElementById('preview-cost').textContent = `$${cost.toFixed(0)}`;
|
| 80 |
+
document.getElementById('preview-carbon').textContent = `${carbon > 0 ? '+' : ''}${carbon.toFixed(1)}kg CO2`;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
actionSelect.addEventListener('change', () => {
|
| 84 |
orderOptions.classList.add('hidden');
|
| 85 |
productionOptions.classList.add('hidden');
|
| 86 |
|
| 87 |
if (actionSelect.value === 'order_parts') orderOptions.classList.remove('hidden');
|
| 88 |
if (actionSelect.value === 'produce') productionOptions.classList.remove('hidden');
|
| 89 |
+
updatePreview();
|
| 90 |
});
|
| 91 |
|
| 92 |
+
modeSelect.addEventListener('change', updatePreview);
|
| 93 |
+
|
| 94 |
function log(message, type = 'system') {
|
| 95 |
const box = document.getElementById('activity-log');
|
| 96 |
const entry = document.createElement('p');
|
|
|
|
| 114 |
let actionObj = { action_type: type };
|
| 115 |
|
| 116 |
if (type === 'order_parts') {
|
| 117 |
+
actionObj.part_type = partSelect.value;
|
| 118 |
+
actionObj.mode = modeSelect.value;
|
| 119 |
actionObj.quantity = 5;
|
| 120 |
} else if (type === 'produce') {
|
| 121 |
actionObj.product = document.getElementById('product-select').value;
|
|
|
|
| 180 |
// Start
|
| 181 |
updateState();
|
| 182 |
setInterval(updateState, 3000);
|
| 183 |
+
updatePreview();
|
dashboard/index.html
CHANGED
|
@@ -50,6 +50,13 @@
|
|
| 50 |
</select>
|
| 51 |
</div>
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
<div class="action-buttons">
|
| 54 |
<button id="step-btn" class="btn btn-primary">Execute Action</button>
|
| 55 |
<button id="skip-btn" class="btn btn-secondary">Skip Day</button>
|
|
|
|
| 50 |
</select>
|
| 51 |
</div>
|
| 52 |
|
| 53 |
+
<div id="impact-preview" class="impact-preview">
|
| 54 |
+
<span class="preview-label">Live Estimate:</span>
|
| 55 |
+
<span id="preview-cost" class="val">$0</span>
|
| 56 |
+
<span class="sep">|</span>
|
| 57 |
+
<span id="preview-carbon" class="val">0kg CO2</span>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
<div class="action-buttons">
|
| 61 |
<button id="step-btn" class="btn btn-primary">Execute Action</button>
|
| 62 |
<button id="skip-btn" class="btn btn-secondary">Skip Day</button>
|
dashboard/style.css
CHANGED
|
@@ -275,6 +275,32 @@ select {
|
|
| 275 |
100% { border-color: #FFC1C1; }
|
| 276 |
}
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
footer {
|
| 279 |
text-align: center;
|
| 280 |
margin-top: 4rem;
|
|
|
|
| 275 |
100% { border-color: #FFC1C1; }
|
| 276 |
}
|
| 277 |
|
| 278 |
+
.impact-preview {
|
| 279 |
+
margin: 1.5rem 0;
|
| 280 |
+
padding: 1rem;
|
| 281 |
+
background: #F0F4F0;
|
| 282 |
+
border-radius: 8px;
|
| 283 |
+
border-left: 4px solid var(--primary-green);
|
| 284 |
+
font-size: 0.9rem;
|
| 285 |
+
display: flex;
|
| 286 |
+
gap: 0.75rem;
|
| 287 |
+
align-items: center;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.impact-preview .preview-label {
|
| 291 |
+
font-weight: 600;
|
| 292 |
+
color: var(--text-muted);
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.impact-preview .val {
|
| 296 |
+
font-weight: 600;
|
| 297 |
+
color: var(--deep-forest);
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.impact-preview .sep {
|
| 301 |
+
color: var(--border-tan);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
footer {
|
| 305 |
text-align: center;
|
| 306 |
margin-top: 4rem;
|