Initial commit: HyperSwap GPU Program Swapper with REST API, MCP 2.0, Real-Time Dashboard and Memory Orchestrator

This commit is contained in:
drjones
2026-08-22 00:59:40 -07:00
commit f909dd23fb
10 changed files with 1853 additions and 0 deletions

288
static/app.js Normal file
View File

@@ -0,0 +1,288 @@
let evtSource = null;
let currentInstalledModels = [];
// Initialize SSE Stream
function initSSE() {
if (evtSource) {
evtSource.close();
}
evtSource = new EventSource('/api/stream');
evtSource.onopen = () => {
document.getElementById('sse-badge').textContent = 'SSE LIVE';
document.getElementById('sse-badge').className = 'text-emerald-400 font-bold';
};
evtSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
updateDashboard(data);
} catch (err) {
console.error('Error parsing SSE event:', err);
}
};
evtSource.onerror = (err) => {
console.warn('SSE disconnected, retrying...', err);
document.getElementById('sse-badge').textContent = 'RECONNECTING...';
document.getElementById('sse-badge').className = 'text-rose-400 font-bold';
};
}
function updateDashboard(data) {
if (!data) return;
// 1. GPU VRAM Stats
const gpu = data.gpu || {};
if (gpu.available) {
document.getElementById('gpu-chip-name').textContent = gpu.device_name || 'NVIDIA GPU';
document.getElementById('vram-total-used').textContent = gpu.vram_used_gb || '0.0';
document.getElementById('vram-used-pct').textContent = `${gpu.vram_used_pct || 0}% USED`;
const bd = gpu.breakdown || {};
const totalBytes = gpu.vram_total_bytes || (16 * 1024**3);
const ollamaPct = ((bd.ollama_gb || 0) / (gpu.vram_total_gb || 16)) * 100;
const comfyPct = ((bd.comfyui_gb || 0) / (gpu.vram_total_gb || 16)) * 100;
const systemPct = ((bd.system_gb || 0) / (gpu.vram_total_gb || 16)) * 100;
const freePct = Math.max(0, 100 - (ollamaPct + comfyPct + systemPct));
document.getElementById('bar-ollama').style.width = `${ollamaPct}%`;
document.getElementById('bar-comfy').style.width = `${comfyPct}%`;
document.getElementById('bar-system').style.width = `${systemPct}%`;
document.getElementById('bar-free').style.width = `${freePct}%`;
document.getElementById('tooltip-ollama').textContent = `${bd.ollama_gb || 0} GB`;
document.getElementById('tooltip-comfy').textContent = `${bd.comfyui_gb || 0} GB`;
document.getElementById('tooltip-system').textContent = `${bd.system_gb || 0} GB`;
document.getElementById('legend-ollama').textContent = `${bd.ollama_gb || 0} GB`;
document.getElementById('legend-comfy').textContent = `${bd.comfyui_gb || 0} GB`;
document.getElementById('legend-system').textContent = `${bd.system_gb || 0} GB`;
document.getElementById('legend-free').textContent = `${bd.free_gb || 0} GB`;
// Hardware sensors
document.getElementById('gpu-util-val').textContent = `${gpu.gpu_util_pct || 0}%`;
document.getElementById('gpu-temp-val').textContent = `${gpu.temperature_c || 0}°C`;
document.getElementById('gpu-power-val').textContent = `${gpu.power_w || 0} W`;
document.getElementById('gpu-fan-val').textContent = `${gpu.fan_pct || 0}%`;
// Processes table
const tbody = document.getElementById('gpu-proc-table');
if (bd.processes && bd.processes.length > 0) {
tbody.innerHTML = bd.processes.map(p => {
let tagClass = 'text-slate-400';
let badge = '';
if (p.is_ollama) {
tagClass = 'text-purple-400 font-bold';
badge = '<span class="px-1 py-0.2 bg-purple-950 text-purple-300 rounded border border-purple-800 text-[9px] mr-1">OLLAMA</span>';
} else if (p.is_comfy) {
tagClass = 'text-cyan-400 font-bold';
badge = '<span class="px-1 py-0.2 bg-cyan-950 text-cyan-300 rounded border border-cyan-800 text-[9px] mr-1">COMFY</span>';
}
return `
<tr class="hover:bg-slate-900/50">
<td class="py-1 text-slate-500 font-mono text-[10px]">${p.pid}</td>
<td class="py-1 ${tagClass} text-[11px] truncate max-w-[140px]">${badge}${p.name}</td>
<td class="py-1 text-right font-mono font-bold text-slate-200 text-[11px]">${p.vram_mb} MB</td>
</tr>
`;
}).join('');
} else {
tbody.innerHTML = '<tr><td colspan="3" class="py-2 text-center text-slate-500">No compute processes running</td></tr>';
}
}
// 2. System RAM & Page Cache
const ram = data.ram || {};
if (ram.total_bytes) {
document.getElementById('ram-cached-gb').textContent = ram.cached_gb || '0.0';
document.getElementById('ram-total-text').textContent = `${ram.total_gb || 0} GB Total (${ram.cache_ratio_pct || 0}% in Cache)`;
const usedPct = ((ram.used_bytes || 0) / ram.total_bytes) * 100;
const cachePct = ((ram.cached_bytes || 0) / ram.total_bytes) * 100;
const freePct = Math.max(0, 100 - (usedPct + cachePct));
document.getElementById('bar-ram-used').style.width = `${usedPct}%`;
document.getElementById('bar-ram-cache').style.width = `${cachePct}%`;
document.getElementById('bar-ram-free').style.width = `${freePct}%`;
document.getElementById('legend-ram-used').textContent = `${ram.used_gb || 0} GB`;
document.getElementById('legend-ram-cached').textContent = `${ram.cached_gb || 0} GB`;
document.getElementById('legend-ram-free').textContent = `${ram.free_gb || 0} GB`;
}
// 3. Ollama State
const ollama = data.ollama || {};
if (ollama.online) {
document.getElementById('ollama-status-text').textContent = 'ONLINE';
document.getElementById('ollama-status-text').className = 'text-emerald-400 font-mono font-bold';
document.getElementById('ollama-dot').className = 'relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500';
document.getElementById('ollama-pulse').className = 'animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75';
if (ollama.active_model_name) {
document.getElementById('ollama-active-model').textContent = ollama.active_model_name;
document.getElementById('ollama-vram-badge').textContent = `${ollama.active_model_vram_gb || 0} GB VRAM`;
document.getElementById('ollama-vram-badge').className = 'px-2 py-0.5 text-[10px] font-mono rounded bg-purple-900/80 text-purple-200 border border-purple-600 font-bold';
document.getElementById('ollama-context').textContent = `${ollama.active_context || 0} ctx`;
} else {
document.getElementById('ollama-active-model').textContent = 'None Loaded (VRAM Free)';
document.getElementById('ollama-vram-badge').textContent = '0.0 GB VRAM';
document.getElementById('ollama-vram-badge').className = 'px-2 py-0.5 text-[10px] font-mono rounded bg-slate-800 text-slate-400 border border-slate-700';
document.getElementById('ollama-context').textContent = 'Idle';
}
if (ollama.installed_models && ollama.installed_models.length > 0) {
document.getElementById('ollama-total-models').textContent = ollama.installed_models.length;
updateModelSelect(ollama.installed_models, ollama.active_model_name);
}
} else {
document.getElementById('ollama-status-text').textContent = 'OFFLINE';
document.getElementById('ollama-status-text').className = 'text-rose-400 font-mono font-bold';
document.getElementById('ollama-dot').className = 'relative inline-flex rounded-full h-2.5 w-2.5 bg-rose-500';
document.getElementById('ollama-pulse').className = 'hidden';
}
// 4. ComfyUI State
const comfy = data.comfyui || {};
if (comfy.online) {
document.getElementById('comfy-status-text').textContent = 'ONLINE';
document.getElementById('comfy-status-text').className = 'text-emerald-400 font-mono font-bold';
document.getElementById('comfy-dot').className = 'relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500';
document.getElementById('comfy-pulse').className = 'animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75';
document.getElementById('comfy-queue-count').textContent = comfy.queue_running + comfy.queue_remaining;
if (comfy.executing) {
document.getElementById('comfy-exec-badge').textContent = 'GENERATING...';
document.getElementById('comfy-exec-badge').className = 'px-2 py-0.5 text-[10px] font-mono rounded bg-amber-950 text-amber-300 border border-amber-600 animate-pulse font-bold';
document.getElementById('comfy-model-info').textContent = `Prompt #${comfy.current_prompt_id || 'Active'}`;
} else {
document.getElementById('comfy-exec-badge').textContent = 'IDLE / READY';
document.getElementById('comfy-exec-badge').className = 'px-2 py-0.5 text-[10px] font-mono rounded bg-emerald-950 text-emerald-300 border border-emerald-800';
document.getElementById('comfy-model-info').textContent = 'Dynamic Model Offloader';
}
document.getElementById('comfy-vram-avail').textContent = `${(comfy.vram_free_mb / 1024).toFixed(1)} GB`;
} else {
document.getElementById('comfy-status-text').textContent = 'OFFLINE';
document.getElementById('comfy-status-text').className = 'text-rose-400 font-mono font-bold';
document.getElementById('comfy-dot').className = 'relative inline-flex rounded-full h-2.5 w-2.5 bg-rose-500';
document.getElementById('comfy-pulse').className = 'hidden';
}
document.getElementById('comfy-discovered-count').textContent = `${data.comfy_models_count || 0} Files`;
// 5. Switch History Timeline
const history = data.history || [];
const logContainer = document.getElementById('switch-log-container');
if (history.length > 0) {
const latest = history[0];
document.getElementById('ollama-last-swap').textContent = `${latest.duration_ms} ms`;
document.getElementById('ollama-cache-hit').textContent = latest.cache_status || 'OK';
logContainer.innerHTML = history.slice(0, 10).map(item => {
const isHit = (item.cache_status || '').includes('RAM Cache Hit') || (item.cache_status || '').includes('RAM-Cached');
const badgeClass = isHit
? 'bg-emerald-950/80 text-emerald-300 border-emerald-800'
: 'bg-amber-950/80 text-amber-300 border-amber-800';
return `
<div class="p-2.5 rounded-xl bg-slate-950 border border-slate-800 text-xs font-mono flex items-center justify-between">
<div class="space-y-0.5">
<div class="flex items-center space-x-1.5">
<span class="text-slate-500 text-[10px]">${item.timestamp}</span>
<span class="text-indigo-400 font-bold">${item.event_type}</span>
</div>
<div class="text-[11px] text-slate-300 truncate max-w-[260px]">
<span class="text-slate-500">${item.source}</span> → <span class="text-cyan-300 font-bold">${item.target}</span>
</div>
</div>
<div class="text-right">
<div class="text-slate-100 font-bold">${item.duration_ms} ms</div>
<span class="px-1.5 py-0.2 text-[9px] rounded border ${badgeClass}">${item.cache_status}</span>
</div>
</div>
`;
}).join('');
}
}
function updateModelSelect(models, activeModel) {
const select = document.getElementById('ollama-model-select');
const currentVal = select.value;
if (JSON.stringify(models.map(m => m.name)) === JSON.stringify(currentInstalledModels)) {
return;
}
currentInstalledModels = models.map(m => m.name);
select.innerHTML = models.map(m => {
const isSelected = m.name === activeModel || m.name === currentVal;
const sizeGb = (m.size / (1024**3)).toFixed(1);
const quant = m.details?.quantization_level || '';
return `<option value="${m.name}" ${isSelected ? 'selected' : ''}>${m.name} (${sizeGb} GB ${quant})</option>`;
}).join('');
}
// User Actions
async function triggerModelSwitch() {
const select = document.getElementById('ollama-model-select');
const targetModel = select.value;
if (!targetModel) return;
const btn = document.getElementById('btn-switch-model');
btn.disabled = true;
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> <span>Swapping...</span>';
try {
const resp = await fetch('/api/switch-model', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: targetModel, keep_alive: '30m' })
});
const result = await resp.json();
if (!resp.ok) {
alert(`Switch failed: ${result.detail || 'Unknown error'}`);
}
} catch (err) {
alert(`Error: ${err}`);
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="fa-solid fa-shuffle"></i> <span>Hot Swap</span>';
}
}
async function freeOllamaVRAM() {
try {
const resp = await fetch('/api/free-vram', { method: 'POST' });
const data = await resp.json();
console.log('Ollama VRAM yielded:', data);
} catch (err) {
alert(`Error: ${err}`);
}
}
async function freeComfyVRAM() {
try {
const resp = await fetch('/api/comfy-free', { method: 'POST' });
const data = await resp.json();
console.log('ComfyUI VRAM freed:', data);
} catch (err) {
alert(`Error: ${err}`);
}
}
async function warmAllModels() {
try {
const resp = await fetch('/api/warm-all', { method: 'POST' });
const data = await resp.json();
alert(`Warming completed in ${data.total_duration_ms} ms! All models are now cached in 64GB RAM.`);
} catch (err) {
alert(`Error warming models: ${err}`);
}
}
// Startup
document.addEventListener('DOMContentLoaded', () => {
initSSE();
});

426
static/index.html Normal file
View File

@@ -0,0 +1,426 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HYPERSWAP // Dual-Engine Model Orchestrator & Live Telemetry</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="/static/styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen font-sans antialiased selection:bg-cyan-500 selection:text-white">
<!-- TOP HEADER -->
<header class="border-b border-slate-800 bg-slate-900/80 backdrop-blur sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3 flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-cyan-500 via-indigo-500 to-purple-500 flex items-center justify-center shadow-lg shadow-cyan-500/20">
<i class="fa-solid fa-bolt-lightning text-white text-lg"></i>
</div>
<div>
<div class="flex items-center space-x-2">
<h1 class="text-xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-cyan-400 via-sky-300 to-indigo-400">
HYPERSWAP
</h1>
<span class="text-xs uppercase tracking-widest px-2 py-0.5 rounded bg-cyan-950/80 text-cyan-400 border border-cyan-800 font-mono">
v1.0-DEPLOY
</span>
</div>
<p class="text-xs text-slate-400 font-mono">NVIDIA RTX 4080 SUPER 16GB // 64GB DDR5 RAM // Ubuntu Linux</p>
</div>
</div>
<div class="flex items-center space-x-3">
<!-- Service Status Indicators -->
<div class="flex items-center space-x-2 px-3 py-1.5 rounded-lg bg-slate-800/80 border border-slate-700/60 text-xs">
<span class="relative flex h-2.5 w-2.5">
<span id="ollama-pulse" class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span id="ollama-dot" class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
</span>
<span class="font-medium text-slate-300">Ollama LLM:</span>
<span id="ollama-status-text" class="text-emerald-400 font-mono font-bold">ONLINE</span>
</div>
<div class="flex items-center space-x-2 px-3 py-1.5 rounded-lg bg-slate-800/80 border border-slate-700/60 text-xs">
<span class="relative flex h-2.5 w-2.5">
<span id="comfy-pulse" class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span id="comfy-dot" class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
</span>
<span class="font-medium text-slate-300">ComfyUI:</span>
<span id="comfy-status-text" class="text-emerald-400 font-mono font-bold">ONLINE</span>
</div>
<div class="flex items-center space-x-1.5 px-3 py-1.5 rounded-lg bg-slate-800/80 border border-slate-700/60 text-xs text-slate-400 font-mono">
<i class="fa-solid fa-satellite-dish text-cyan-400 text-xs animate-pulse"></i>
<span id="sse-badge">SSE 1Hz</span>
</div>
</div>
</div>
</header>
<!-- MAIN CONTAINER -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6">
<!-- HERO MEMORY GAUGES -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- GPU VRAM ALLOCATION GAUGE -->
<div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-5 relative overflow-hidden backdrop-blur">
<div class="flex justify-between items-start mb-3">
<div>
<div class="flex items-center space-x-2">
<i class="fa-solid fa-microchip text-cyan-400"></i>
<h2 class="text-sm font-semibold text-slate-200 tracking-wide uppercase">GPU VRAM (16 GB Dedicated)</h2>
</div>
<p class="text-xs text-slate-400 mt-0.5">Real-time allocation breakdown on RTX 4080 SUPER</p>
</div>
<div class="text-right font-mono">
<span id="vram-total-used" class="text-2xl font-bold text-slate-100">0.0</span>
<span class="text-xs text-slate-400">/ 16.0 GB</span>
<div id="vram-used-pct" class="text-xs text-cyan-400 font-bold">0% USED</div>
</div>
</div>
<!-- Segmented Bar -->
<div class="h-6 w-full bg-slate-950 rounded-lg overflow-hidden flex border border-slate-800 p-0.5">
<div id="bar-ollama" class="bg-gradient-to-r from-purple-600 to-indigo-500 h-full rounded-l transition-all duration-500 relative group" style="width: 0%">
<div class="opacity-0 group-hover:opacity-100 absolute -top-8 left-1/2 -translate-x-1/2 bg-slate-800 text-[10px] text-white px-2 py-0.5 rounded border border-slate-700 whitespace-nowrap z-20">
Ollama: <span id="tooltip-ollama">0 GB</span>
</div>
</div>
<div id="bar-comfy" class="bg-gradient-to-r from-cyan-500 to-sky-400 h-full transition-all duration-500 relative group" style="width: 0%">
<div class="opacity-0 group-hover:opacity-100 absolute -top-8 left-1/2 -translate-x-1/2 bg-slate-800 text-[10px] text-white px-2 py-0.5 rounded border border-slate-700 whitespace-nowrap z-20">
ComfyUI: <span id="tooltip-comfy">0 GB</span>
</div>
</div>
<div id="bar-system" class="bg-slate-600 h-full transition-all duration-500 relative group" style="width: 0%">
<div class="opacity-0 group-hover:opacity-100 absolute -top-8 left-1/2 -translate-x-1/2 bg-slate-800 text-[10px] text-white px-2 py-0.5 rounded border border-slate-700 whitespace-nowrap z-20">
System: <span id="tooltip-system">0 GB</span>
</div>
</div>
<div id="bar-free" class="bg-slate-900 h-full rounded-r transition-all duration-500" style="width: 100%"></div>
</div>
<!-- Legend -->
<div class="grid grid-cols-4 gap-2 mt-3 text-xs font-mono">
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-purple-500"></span>
<span class="text-slate-400">Ollama:</span>
<span id="legend-ollama" class="text-slate-200 font-bold">0 GB</span>
</div>
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-cyan-400"></span>
<span class="text-slate-400">Comfy:</span>
<span id="legend-comfy" class="text-slate-200 font-bold">0 GB</span>
</div>
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-slate-500"></span>
<span class="text-slate-400">System:</span>
<span id="legend-system" class="text-slate-200 font-bold">0 GB</span>
</div>
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-slate-800 border border-slate-700"></span>
<span class="text-slate-400">Free:</span>
<span id="legend-free" class="text-emerald-400 font-bold">0 GB</span>
</div>
</div>
</div>
<!-- SYSTEM RAM & PAGE CACHE GAUGE -->
<div class="bg-slate-900/70 border border-slate-800 rounded-2xl p-5 relative overflow-hidden backdrop-blur">
<div class="flex justify-between items-start mb-3">
<div>
<div class="flex items-center space-x-2">
<i class="fa-solid fa-memory text-amber-400"></i>
<h2 class="text-sm font-semibold text-slate-200 tracking-wide uppercase">Host RAM & Model Page Cache (64 GB)</h2>
</div>
<p class="text-xs text-slate-400 mt-0.5">Models stay resident in RAM for instant PCIe hot-swaps</p>
</div>
<div class="text-right font-mono">
<span id="ram-cached-gb" class="text-2xl font-bold text-amber-400">0.0</span>
<span class="text-xs text-slate-400">GB CACHED</span>
<div id="ram-total-text" class="text-xs text-slate-400">60.3 GB Total</div>
</div>
</div>
<!-- Segmented Bar -->
<div class="h-6 w-full bg-slate-950 rounded-lg overflow-hidden flex border border-slate-800 p-0.5">
<div id="bar-ram-used" class="bg-gradient-to-r from-rose-600 to-orange-500 h-full rounded-l transition-all duration-500" style="width: 10%"></div>
<div id="bar-ram-cache" class="bg-gradient-to-r from-amber-500 to-yellow-400 h-full transition-all duration-500" style="width: 50%"></div>
<div id="bar-ram-free" class="bg-slate-900 h-full rounded-r transition-all duration-500" style="width: 40%"></div>
</div>
<!-- Legend -->
<div class="grid grid-cols-3 gap-2 mt-3 text-xs font-mono">
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-rose-500"></span>
<span class="text-slate-400">Apps Used:</span>
<span id="legend-ram-used" class="text-slate-200 font-bold">0 GB</span>
</div>
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-amber-400"></span>
<span class="text-slate-400">Models in RAM:</span>
<span id="legend-ram-cached" class="text-amber-400 font-bold">0 GB</span>
</div>
<div class="flex items-center space-x-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-slate-800 border border-slate-700"></span>
<span class="text-slate-400">Free RAM:</span>
<span id="legend-ram-free" class="text-emerald-400 font-bold">0 GB</span>
</div>
</div>
</div>
</div>
<!-- 4-CARD CONTROL & TELEMETRY GRID -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- CARD 1: OLLAMA LLM ENGINE -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 flex flex-col justify-between space-y-4">
<div>
<div class="flex items-center justify-between pb-3 border-b border-slate-800">
<div class="flex items-center space-x-2">
<div class="p-2 rounded-lg bg-purple-950/80 border border-purple-800 text-purple-400">
<i class="fa-solid fa-brain text-sm"></i>
</div>
<div>
<h3 class="font-bold text-slate-100 text-sm">Ollama LLM Engine</h3>
<p class="text-xs text-slate-400">Port :11434 // FlashAttention + Q4 KV Cache</p>
</div>
</div>
<button onclick="freeOllamaVRAM()" class="px-2.5 py-1 text-xs font-semibold rounded-lg bg-rose-950/70 border border-rose-800 text-rose-300 hover:bg-rose-900 transition flex items-center space-x-1">
<i class="fa-solid fa-arrow-down-to-bracket"></i>
<span>Soft-Yield VRAM</span>
</button>
</div>
<!-- Active Model Box -->
<div class="mt-4 p-4 rounded-xl bg-slate-950 border border-slate-800/80 space-y-3">
<div class="flex justify-between items-center">
<span class="text-xs text-slate-400 uppercase font-mono">Active Model in VRAM</span>
<span id="ollama-vram-badge" class="px-2 py-0.5 text-[10px] font-mono rounded bg-purple-900/60 text-purple-300 border border-purple-700">
0.0 GB VRAM
</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2 overflow-hidden">
<i class="fa-solid fa-cube text-purple-400"></i>
<span id="ollama-active-model" class="text-base font-bold font-mono text-slate-100 truncate">None Loaded</span>
</div>
<span id="ollama-context" class="text-xs font-mono text-slate-400">0 ctx</span>
</div>
</div>
<!-- Quick Hot-Swap Control -->
<div class="mt-4 space-y-2">
<label class="text-xs font-semibold text-slate-300 flex items-center justify-between">
<span>Instant Model Hot-Swap:</span>
<span class="text-[10px] text-cyan-400 font-mono">RAM Cache Optimized</span>
</label>
<div class="flex space-x-2">
<select id="ollama-model-select" class="flex-1 bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-purple-500">
<option value="">Loading installed models...</option>
</select>
<button id="btn-switch-model" onclick="triggerModelSwitch()" class="px-4 py-2 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-500 hover:to-indigo-500 text-white text-xs font-bold rounded-lg shadow-md transition flex items-center space-x-1.5">
<i class="fa-solid fa-shuffle"></i>
<span>Hot Swap</span>
</button>
</div>
</div>
</div>
<!-- Ollama Stats Footer -->
<div class="pt-3 border-t border-slate-800 grid grid-cols-3 gap-2 text-center font-mono text-xs">
<div class="p-2 rounded bg-slate-950 border border-slate-800">
<span class="text-[10px] text-slate-500 block">LAST SWAP TIME</span>
<span id="ollama-last-swap" class="text-cyan-400 font-bold">-- ms</span>
</div>
<div class="p-2 rounded bg-slate-950 border border-slate-800">
<span class="text-[10px] text-slate-500 block">RAM HIT STATUS</span>
<span id="ollama-cache-hit" class="text-emerald-400 font-bold">--</span>
</div>
<div class="p-2 rounded bg-slate-950 border border-slate-800">
<span class="text-[10px] text-slate-500 block">TOTAL MODELS</span>
<span id="ollama-total-models" class="text-slate-300 font-bold">0</span>
</div>
</div>
</div>
<!-- CARD 2: COMFYUI DIFFUSION PIPELINE -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 flex flex-col justify-between space-y-4">
<div>
<div class="flex items-center justify-between pb-3 border-b border-slate-800">
<div class="flex items-center space-x-2">
<div class="p-2 rounded-lg bg-cyan-950/80 border border-cyan-800 text-cyan-400">
<i class="fa-solid fa-palette text-sm"></i>
</div>
<div>
<h3 class="font-bold text-slate-100 text-sm">ComfyUI Diffusion Engine</h3>
<p class="text-xs text-slate-400">Port :8188 // DynamicVRAM + Pinned Async Offload</p>
</div>
</div>
<button onclick="freeComfyVRAM()" class="px-2.5 py-1 text-xs font-semibold rounded-lg bg-rose-950/70 border border-rose-800 text-rose-300 hover:bg-rose-900 transition flex items-center space-x-1">
<i class="fa-solid fa-broom"></i>
<span>Purge VRAM</span>
</button>
</div>
<!-- Comfy Execution Box -->
<div class="mt-4 p-4 rounded-xl bg-slate-950 border border-slate-800/80 space-y-3">
<div class="flex justify-between items-center">
<span class="text-xs text-slate-400 uppercase font-mono">Pipeline Status</span>
<span id="comfy-exec-badge" class="px-2 py-0.5 text-[10px] font-mono rounded bg-emerald-950 text-emerald-300 border border-emerald-800">
IDLE / READY
</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<i class="fa-solid fa-layer-group text-cyan-400"></i>
<span id="comfy-model-info" class="text-sm font-mono text-slate-200">Dynamic Model Offloader</span>
</div>
<div class="text-xs font-mono text-slate-400">
Queue: <span id="comfy-queue-count" class="text-cyan-400 font-bold">0</span>
</div>
</div>
</div>
<!-- Comfy Feature Checklist -->
<div class="mt-4 space-y-2 text-xs">
<div class="p-2.5 rounded-lg bg-slate-950 border border-slate-800 space-y-1.5">
<div class="flex items-center justify-between text-slate-300">
<span class="flex items-center space-x-1.5">
<i class="fa-solid fa-check text-emerald-400 text-xs"></i>
<span>Host Pinned Memory:</span>
</span>
<span class="font-mono font-bold text-emerald-400">53.6 GB Staging Buffer</span>
</div>
<div class="flex items-center justify-between text-slate-300">
<span class="flex items-center space-x-1.5">
<i class="fa-solid fa-check text-emerald-400 text-xs"></i>
<span>Async PCIe Offloading:</span>
</span>
<span class="font-mono font-bold text-cyan-400">Enabled (2 Streams)</span>
</div>
<div class="flex items-center justify-between text-slate-300">
<span class="flex items-center space-x-1.5">
<i class="fa-solid fa-check text-emerald-400 text-xs"></i>
<span>Fast Disk RAM Mmap:</span>
</span>
<span class="font-mono font-bold text-amber-400">Active</span>
</div>
</div>
</div>
</div>
<!-- Comfy Stats Footer -->
<div class="pt-3 border-t border-slate-800 grid grid-cols-2 gap-2 text-center font-mono text-xs">
<div class="p-2 rounded bg-slate-950 border border-slate-800">
<span class="text-[10px] text-slate-500 block">DISCOVERED MODELS</span>
<span id="comfy-discovered-count" class="text-cyan-400 font-bold">0 Files</span>
</div>
<div class="p-2 rounded bg-slate-950 border border-slate-800">
<span class="text-[10px] text-slate-500 block">VRAM AVAILABLE</span>
<span id="comfy-vram-avail" class="text-emerald-400 font-bold">15.9 GB</span>
</div>
</div>
</div>
<!-- CARD 3: GPU HARDWARE TELEMETRY -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-800">
<div class="flex items-center space-x-2">
<div class="p-2 rounded-lg bg-emerald-950/80 border border-emerald-800 text-emerald-400">
<i class="fa-solid fa-gauge-high text-sm"></i>
</div>
<div>
<h3 class="font-bold text-slate-100 text-sm">GPU Live Telemetry (NVML)</h3>
<p class="text-xs text-slate-400" id="gpu-chip-name">NVIDIA GeForce RTX 4080 SUPER</p>
</div>
</div>
</div>
<!-- Quick Gauges Grid -->
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 font-mono">
<div class="p-3 rounded-xl bg-slate-950 border border-slate-800 text-center">
<span class="text-[10px] text-slate-400 uppercase block">GPU Util</span>
<span id="gpu-util-val" class="text-xl font-bold text-cyan-400">0%</span>
</div>
<div class="p-3 rounded-xl bg-slate-950 border border-slate-800 text-center">
<span class="text-[10px] text-slate-400 uppercase block">Temperature</span>
<span id="gpu-temp-val" class="text-xl font-bold text-emerald-400">0°C</span>
</div>
<div class="p-3 rounded-xl bg-slate-950 border border-slate-800 text-center">
<span class="text-[10px] text-slate-400 uppercase block">Power Draw</span>
<span id="gpu-power-val" class="text-xl font-bold text-amber-400">0 W</span>
</div>
<div class="p-3 rounded-xl bg-slate-950 border border-slate-800 text-center">
<span class="text-[10px] text-slate-400 uppercase block">Fan Speed</span>
<span id="gpu-fan-val" class="text-xl font-bold text-slate-300">0%</span>
</div>
</div>
<!-- Process Table -->
<div class="space-y-2">
<span class="text-xs font-semibold text-slate-400 uppercase font-mono">Active Compute Processes</span>
<div class="rounded-xl bg-slate-950 border border-slate-800 p-2 max-h-36 overflow-y-auto font-mono text-xs">
<table class="w-full text-left">
<thead>
<tr class="text-slate-500 border-b border-slate-800/80 text-[10px]">
<th class="pb-1">PID</th>
<th class="pb-1">Process</th>
<th class="pb-1 text-right">VRAM</th>
</tr>
</thead>
<tbody id="gpu-proc-table" class="divide-y divide-slate-900">
<tr><td colspan="3" class="py-2 text-center text-slate-500">Scanning GPU processes...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- CARD 4: REAL-TIME SWITCH TIMELINE & OPTIMIZER -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 flex flex-col justify-between space-y-4">
<div>
<div class="flex items-center justify-between pb-3 border-b border-slate-800">
<div class="flex items-center space-x-2">
<div class="p-2 rounded-lg bg-indigo-950/80 border border-indigo-800 text-indigo-400">
<i class="fa-solid fa-clock-rotate-left text-sm"></i>
</div>
<div>
<h3 class="font-bold text-slate-100 text-sm">Model Switch Timeline & Optimizer</h3>
<p class="text-xs text-slate-400">Real-time latency logger and memory warmer</p>
</div>
</div>
<button onclick="warmAllModels()" class="px-2.5 py-1 text-xs font-semibold rounded-lg bg-amber-950/70 border border-amber-800 text-amber-300 hover:bg-amber-900 transition flex items-center space-x-1">
<i class="fa-solid fa-fire text-amber-400"></i>
<span>Warm All to RAM</span>
</button>
</div>
<!-- Switch Events Log -->
<div class="mt-4 space-y-2">
<span class="text-xs font-semibold text-slate-400 uppercase font-mono">Recent Model Swaps</span>
<div id="switch-log-container" class="space-y-2 max-h-48 overflow-y-auto pr-1">
<div class="p-3 rounded-xl bg-slate-950 border border-slate-800 text-xs text-slate-400 text-center font-mono">
No recent model swaps recorded yet.
</div>
</div>
</div>
</div>
<!-- Optimizer Banner -->
<div class="p-3 rounded-xl bg-gradient-to-r from-cyan-950/50 via-slate-950 to-purple-950/50 border border-slate-800 text-xs flex items-center justify-between">
<div class="flex items-center space-x-2">
<i class="fa-solid fa-wand-magic-sparkles text-cyan-400"></i>
<span class="text-slate-300">64GB RAM Cache holds all models in memory</span>
</div>
<span class="font-mono text-cyan-400 font-bold">PCIe x16 (~31.5 GB/s)</span>
</div>
</div>
</div>
</main>
<script src="/static/app.js"></script>
</body>
</html>

28
static/styles.css Normal file
View File

@@ -0,0 +1,28 @@
/* Custom Scrollbars */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #020617;
}
::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #334155;
}
@keyframes pulseGlow {
0%, 100% {
box-shadow: 0 0 15px rgba(6, 182, 212, 0.2);
}
50% {
box-shadow: 0 0 25px rgba(6, 182, 212, 0.4);
}
}
.glow-card {
animation: pulseGlow 4s infinite ease-in-out;
}