new-dynamo / training.html
MoShow's picture
import { neon } from "@neondatabase/serverless"
e02650b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"content="width=device-width, initial-scale=1.0">
<title>RAD-X Training Console</title>
<link href="https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.css" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'radx-blue': '#00b3ff',
'radx-dark': '#0b0f14',
'radx-panel': 'rgba(15,20,25,0.92)',
'radx-highlight': '#7ee0ff',
'radx-danger': '#ff4d4d',
'radx-warning': '#ffa500',
'radx-success': '#00ffa3'
}
}
}
}
</script>
<style>
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background-color: #000;
color: #e7f3ff;
overflow: hidden;
}
#map {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.hud-panel {
background: rgba(15, 20, 25, 0.92);
border: 1px solid rgba(0, 179, 255, 0.3);
border-radius: 12px;
padding: 14px;
backdrop-filter: blur(8px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
.hotspot-marker {
width: 20px;
height: 20px;
background-color: #e00;
border-radius: 50%;
box-shadow: 0 0 10px #f00;
border: 2px solid #fff;
position: relative;
animation: pulse 2s infinite;
z-index: 10;
}
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.pulse-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255, 0, 0, 0.5);
animation: pulseRing 2s infinite;
}
@keyframes pulseRing {
0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0.8; }
100% { transform: translate(-50%, -50%) scale(2); opacity: 0; }
}
.training-panel {
position: fixed;
top: 20px;
left: 20px;
width: 400px;
max-height: calc(100vh - 40px);
z-index: 10;
background: rgba(15, 20, 25, 0.92);
border: 1px solid rgba(0, 179, 255, 0.3);
border-radius: 12px;
padding: 14px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
overflow-y: auto;
}
.json-editor {
width: 100%;
height: 200px;
background: rgba(0, 0, 0, 0.35);
color: #fff;
border: 1px solid rgba(0, 179, 255, 0.45);
border-radius: 8px;
padding: 10px;
font-family: monospace;
font-size: 12px;
resize: vertical;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 6px;
}
.status-active {
background-color: #00ffa3;
box-shadow: 0 0 5px #00ffa3;
}
.status-warning {
background-color: #ffa500;
box-shadow: 0 0 5px #ffa500;
}
.status-critical {
background-color: #ff4d4d;
box-shadow: 0 0 5px #ff4d4d;
}
</style>
</head>
<body>
<!-- Map container -->
<div id="map"></div>
<!-- Training Panel -->
<div class="training-panel">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-radx-highlight">RAD-X TRAINING CONSOLE</h2>
<button id="close-training" class="text-radx-highlight hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4">
<h3 class="text-lg font-semibold text-radx-blue mb-2">DATABASE CONNECTION</h3>
<div class="flex items-center text-sm">
<span class="status-indicator status-active"></span>
<span>NeonDB Connected</span>
</div>
<div class="text-xs text-gray-400 mt-1">Status: Live | Tables: 8 | Records: 1,247</div>
</div>
<div class="mb-4">
<h3 class="text-lg font-semibold text-radx-blue mb-2">DATA INGESTION</h3>
<textarea id="json-input" class="json-editor" placeholder='[
{
"disease": "cholera",
"key": "sample_site",
"place": "Sample City",
"center": [36.80, -1.30],
"title": "Sample City — River Intake",
"info": "ROOT CAUSE: Demo import.\nTurbidity ~160 NTU; residual chlorine ~0.06 mg/L."
}
]'>[
{
"disease": "cholera",
"key": "lusaka_demo",
"place": "Lusaka",
"center": [28.2540, -15.4380],
"title": "Lusaka — Kanyama",
"info": "ROOT CAUSE: High water table + pit latrines → borehole infiltration."
},
{
"disease": "malaria",
"key": "kisumu_demo",
"place": "Kisumu",
"center": [34.7617, -0.0917],
"title": "Kisumu — Lakeside",
"info": "Vector proliferation near shoreline + wetlands (DEMO)."
}
]</textarea>
<div class="flex gap-2 mt-2">
<button id="ingest-btn" class="flex-1 bg-radx-blue text-black py-2 px-4 rounded font-semibold hover:bg-cyan-300 transition">
INGEST DATA
</button>
<button id="clear-btn" class="flex-1 bg-gray-700 text-white py-2 px-4 rounded hover:bg-gray-600 transition">
CLEAR
</button>
</div>
</div>
<div class="mb-4">
<h3 class="text-lg font-semibold text-radx-blue mb-2">TRAINING METRICS</h3>
<div class="grid grid-cols-2 gap-2 text-sm">
<div class="bg-gray-800/50 p-2 rounded">
<div class="text-gray-400">Index Size</div>
<div id="metric-index" class="text-radx-highlight">127</div>
</div>
<div class="bg-gray-800/50 p-2 rounded">
<div class="text-gray-400">Samples</div>
<div id="metric-samples" class="text-radx-highlight">0</div>
</div>
<div class="bg-gray-800/50 p-2 rounded">
<div class="text-gray-400">Last Ingest</div>
<div id="metric-last" class="text-radx-highlight"></div>
</div>
<div class="bg-gray-800/50 p-2 rounded">
<div class="text-gray-400">Status</div>
<div class="text-radx-success">Ready</div>
</div>
</div>
<div class="mt-2">
<div class="text-gray-400 text-sm mb-1">Progress</div>
<div class="h-2 bg-gray-700 rounded-full overflow-hidden">
<div id="progress-bar" class="h-full bg-radx-blue" style="width: 0%"></div>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-radx-blue mb-2">TRAINING COMMANDS</h3>
<div class="bg-gray-800/50 p-3 rounded text-sm">
<div class="mb-2"><span class="font-mono">train cholera</span> — open pipeline</div>
<div class="mb-2"><span class="font-mono">deploy model</span> — pretend-deploy (demo)</div>
<div><span class="font-mono">list diseases</span> — show supported keys</div>
</div>
</div>
</div>
<!-- Mapbox GL JS -->
<script src="https://api.mapbox.com/mapbox-gl-js/v3.14.0/mapbox-gl.js"></script>
<script>
// Mapbox access token
mapboxgl.accessToken = 'pk.eyJ1IjoiZGVja3VzZXIiLCJhIjoiY20xcjF5d2JxMGQzcTJqcHg1c2U5aG90cyJ9.TFJ7V8BYkx15my8fX6BM5A';
// Initialize map
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/satellite-streets-v12',
center: [20, 0],
zoom: 2,
pitch: 0,
bearing: 0
});
// Add terrain and fog
map.on('style.load', () => {
// Add DEM source and terrain
map.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512
});
map.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1.5 });
// Add fog
map.setFog({
'range': [0.8, 8],
'color': '#0b0f14',
'horizon-blend': 0.05
});
// Add intelligence data source
map.addSource('training_data', {
'type': 'geojson',
'data': {
"type": "FeatureCollection",
"features": []
}
});
// Add layers for training data
map.addLayer({
'id': 'training-points',
'type': 'circle',
'source': 'training_data',
'paint': {
'circle-radius': 8,
'circle-color': [
'match',
['get', 'disease'],
'cholera', '#ff0033',
'malaria', '#00ff88',
'dengue', '#bb66ff',
'#ffa500'
],
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff',
'circle-opacity': 0.8
}
});
// Add 3D buildings layer
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': '#aaa',
'fill-extrusion-height': [
'interpolate',
['linear'],
['zoom'],
15,
0,
15.05,
['get', 'height']
],
'fill-extrusion-base': [
'interpolate',
['linear'],
['zoom'],
15,
0,
15.05,
['get', 'min_height']
],
'fill-extrusion-opacity': 0.6
}
});
});
// Training functionality
const jsonInput = document.getElementById('json-input');
const ingestBtn = document.getElementById('ingest-btn');
const clearBtn = document.getElementById('clear-btn');
const closeBtn = document.getElementById('close-training');
const metricIndex = document.getElementById('metric-index');
const metricSamples = document.getElementById('metric-samples');
const metricLast = document.getElementById('metric-last');
const progressBar = document.getElementById('progress-bar');
// Close training panel
closeBtn.addEventListener('click', () => {
window.location.href = 'index.html';
});
// Clear JSON input
clearBtn.addEventListener('click', () => {
jsonInput.value = '[]';
});
// Ingest data
ingestBtn.addEventListener('click', () => {
try {
const data = JSON.parse(jsonInput.value);
if (!Array.isArray(data)) {
throw new Error('Data must be an array');
}
// Process data
processData(data);
} catch (error) {
alert('Invalid JSON: ' + error.message);
}
});
// Process training data
function processData(data) {
const features = [];
let processed = 0;
const total = data.length;
// Reset metrics
metricSamples.textContent = '0';
progressBar.style.width = '0%';
// Process each item
data.forEach((item, index) => {
setTimeout(() => {
// Create GeoJSON feature
const feature = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item.center
},
properties: {
disease: item.disease,
title: item.title,
info: item.info,
place: item.place,
key: item.key
}
};
features.push(feature);
// Add marker to map
const el = document.createElement('div');
el.className = 'hotspot-marker';
el.innerHTML = '<div class="pulse-circle"></div>';
new mapboxgl.Marker(el)
.setLngLat(item.center)
.setPopup(
new mapboxgl.Popup({ offset: 25 })
.setHTML(
`<h3>${item.title}</h3><p>${item.info}</p>`
)
)
.addTo(map);
// Update metrics
processed++;
metricSamples.textContent = processed;
progressBar.style.width = `${(processed / total) * 100}%`;
// Update last ingest time
if (processed === total) {
metricLast.textContent = new Date().toLocaleTimeString();
metricIndex.textContent = parseInt(metricIndex.textContent) + total;
// Update map source
map.getSource('training_data').setData({
type: 'FeatureCollection',
features: features
});
// Fly to first point
if (features.length > 0) {
const firstPoint = features[0].geometry.coordinates;
map.flyTo({
center: firstPoint,
zoom: 12,
pitch: 45,
bearing: 0,
duration: 3000
});
}
}
}, index * 500); // Simulate processing delay
});
}
// Initialize with sample data
window.addEventListener('load', () => {
// Fly to Africa
map.flyTo({
center: [20, 0],
zoom: 2,
pitch: 0,
bearing: 0,
duration: 0
});
});
</script>
</body>
</html>