|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Wavetype Frequency Grid - Auto-Auth Edition</title> |
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" rel="stylesheet"> |
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.1/font/bootstrap-icons.min.css" rel="stylesheet"> |
|
|
<style> |
|
|
:root { |
|
|
--border-html: #ff0000; |
|
|
--border-python: #0000ff; |
|
|
--border-image: #00ff00; |
|
|
--border-video: #800080; |
|
|
--border-ai: #ff00ff; |
|
|
--border-url: #00ffff; |
|
|
--bg-shade: 0.1; |
|
|
} |
|
|
|
|
|
body, html { |
|
|
margin: 0; padding: 0; width: 100%; height: 100vh; |
|
|
overflow: hidden; background-color: #000; |
|
|
font-family: "Courier New", Courier, monospace; color: white; |
|
|
} |
|
|
|
|
|
|
|
|
#sidebar { |
|
|
position: fixed; left: 0; top: 0; width: 320px; height: 100vh; |
|
|
background: linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%); |
|
|
border-right: 2px solid #333; z-index: 2000; |
|
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.5); overflow-y: auto; |
|
|
} |
|
|
|
|
|
#sidebar.collapsed { transform: translateX(-320px); } |
|
|
|
|
|
#sidebar-toggle { |
|
|
position: fixed; left: 320px; top: 20px; width: 40px; height: 40px; |
|
|
background: #1a1a1a; border: 2px solid #333; border-left: none; |
|
|
border-radius: 0 8px 8px 0; cursor: pointer; z-index: 2001; |
|
|
display: flex; align-items: center; justify-content: center; color: white; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
#sidebar.collapsed ~ #sidebar-toggle { left: 0; } |
|
|
|
|
|
.auth-section { padding: 15px; border-bottom: 1px solid #333; background: #111; } |
|
|
.token-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #28a745; color: white; display: none; } |
|
|
|
|
|
#grid-container { |
|
|
display: grid; width: 100vw; height: 100vh; gap: 4px; padding: 4px; |
|
|
padding-left: 324px; transition: padding-left 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
} |
|
|
|
|
|
#sidebar.collapsed ~ #grid-container { padding-left: 4px; } |
|
|
|
|
|
.block { |
|
|
position: relative; background-color: rgba(255, 255, 255, var(--bg-shade)); |
|
|
border: 2px solid #333; display: flex; flex-direction: column; |
|
|
align-items: center; justify-content: center; cursor: pointer; |
|
|
overflow: hidden; transition: all 0.5s; |
|
|
} |
|
|
|
|
|
.type-ai { border-color: var(--border-ai); } |
|
|
|
|
|
.block.active { |
|
|
position: fixed; top: 0; left: 0; width: 100vw !important; |
|
|
height: 100vh !important; z-index: 2500; background: #000; padding: 40px; |
|
|
cursor: default; |
|
|
} |
|
|
|
|
|
.close-btn { |
|
|
display: none; position: absolute; top: 20px; right: 20px; |
|
|
background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; |
|
|
} |
|
|
.block.active .close-btn { display: block; } |
|
|
|
|
|
.ai-interface { display: none; width: 100%; max-width: 800px; flex-direction: column; gap: 15px; } |
|
|
.block.active .ai-interface { display: flex; } |
|
|
|
|
|
.gen-image-preview { |
|
|
width: 100%; max-height: 50vh; object-fit: contain; |
|
|
border: 2px solid var(--border-ai); display: none; background: #111; |
|
|
} |
|
|
|
|
|
.add-block-btn { |
|
|
position: absolute; bottom: 8px; right: 8px; width: 28px; height: 28px; |
|
|
background: #00ff00; border-radius: 50%; color: #000; border: none; font-weight: bold; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div id="sidebar"> |
|
|
<div class="auth-section"> |
|
|
<div class="d-flex justify-content-between align-items-center"> |
|
|
<span class="small text-muted">HF AUTH STATUS</span> |
|
|
<span id="tokenStatus" class="token-badge">AUTO-LOADED</span> |
|
|
</div> |
|
|
<div id="login-section" class="mt-2"> |
|
|
<button class="btn btn-outline-primary btn-sm w-100" onclick="handleLogin('Cloud')"> |
|
|
<i class="bi bi-cloud-arrow-up"></i> Sync VPS Profile |
|
|
</button> |
|
|
</div> |
|
|
<div id="user-profile" style="display:none" class="mt-2 text-success small fw-bold"> |
|
|
<i class="bi bi-check-circle-fill"></i> Connected to Flux Node |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="p-3"> |
|
|
<div class="mb-4"> |
|
|
<label class="form-label small">GRID NODES</label> |
|
|
<input type="range" class="form-range" id="blockSlider" min="1" max="25" value="4" oninput="updateGrid()"> |
|
|
<div class="d-flex justify-content-between small text-muted"> |
|
|
<span id="countVal">4</span> Instances |
|
|
<span id="saveStatus">Synced</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<label class="form-label small">HF ACCESS TOKEN</label> |
|
|
<input type="password" class="form-control form-control-sm bg-dark text-white border-secondary" id="hfToken" placeholder="hf_..." onchange="saveToCache()"> |
|
|
<div class="form-text text-muted" style="font-size: 9px;">Leave empty if using URL ?token=...</div> |
|
|
</div> |
|
|
|
|
|
<button class="btn btn-success btn-sm w-100 mb-2" onclick="saveToCache()">PERSIST CONFIG</button> |
|
|
<button class="btn btn-outline-danger btn-sm w-100" onclick="clearGrid()">PURGE CACHE</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button id="sidebar-toggle" onclick="toggleSidebar()"><i class="bi bi-chevron-left"></i></button> |
|
|
|
|
|
<div id="grid-container"></div> |
|
|
|
|
|
<script> |
|
|
const container = document.getElementById("grid-container"); |
|
|
const countDisplay = document.getElementById("countVal"); |
|
|
let blocks = []; |
|
|
let currentUser = null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function detectToken() { |
|
|
const urlParams = new URLSearchParams(window.location.search); |
|
|
const urlToken = urlParams.get('token'); |
|
|
const hfInput = document.getElementById("hfToken"); |
|
|
|
|
|
if (urlToken) { |
|
|
hfInput.value = urlToken; |
|
|
document.getElementById("tokenStatus").style.display = "inline"; |
|
|
console.log("Token auto-loaded from URL."); |
|
|
|
|
|
window.history.replaceState({}, document.title, window.location.pathname); |
|
|
} else { |
|
|
const cache = localStorage.getItem("wavetype_vps_cache"); |
|
|
if (cache) { |
|
|
const data = JSON.parse(cache); |
|
|
if (data.token) { |
|
|
hfInput.value = data.token; |
|
|
document.getElementById("tokenStatus").style.display = "inline"; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function saveToCache() { |
|
|
const data = { |
|
|
blocks, |
|
|
token: document.getElementById("hfToken").value, |
|
|
user: currentUser |
|
|
}; |
|
|
localStorage.setItem("wavetype_vps_cache", JSON.stringify(data)); |
|
|
showStatus("Saved"); |
|
|
if(data.token) document.getElementById("tokenStatus").style.display = "inline"; |
|
|
} |
|
|
|
|
|
function loadFromCache() { |
|
|
const cache = localStorage.getItem("wavetype_vps_cache"); |
|
|
if (cache) { |
|
|
const data = JSON.parse(cache); |
|
|
blocks = data.blocks || []; |
|
|
document.getElementById("blockSlider").value = blocks.length || 4; |
|
|
if (data.user) handleLogin('Cloud'); |
|
|
renderGrid(); |
|
|
} else { |
|
|
updateGrid(); |
|
|
} |
|
|
detectToken(); |
|
|
} |
|
|
|
|
|
function updateGrid() { |
|
|
const count = parseInt(document.getElementById("blockSlider").value); |
|
|
countDisplay.innerText = count; |
|
|
while (blocks.length < count) { |
|
|
blocks.push({ id: Date.now() + Math.random(), type: 'ai', prompt: "" }); |
|
|
} |
|
|
while (blocks.length > count) blocks.pop(); |
|
|
renderGrid(); |
|
|
saveToCache(); |
|
|
} |
|
|
|
|
|
function renderGrid() { |
|
|
const cols = Math.ceil(Math.sqrt(blocks.length)) || 1; |
|
|
container.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; |
|
|
container.innerHTML = ""; |
|
|
|
|
|
blocks.forEach((b, i) => { |
|
|
const el = document.createElement("div"); |
|
|
el.className = `block type-${b.type}`; |
|
|
el.onclick = () => { if(!el.classList.contains('active')) el.classList.add('active'); }; |
|
|
|
|
|
el.innerHTML = ` |
|
|
<div class="ai-interface"> |
|
|
<h4 style="color:var(--border-ai)">FLUX.1 [KLEIN-9B]</h4> |
|
|
<textarea class="form-control bg-dark text-white border-secondary mb-2" id="prompt-${i}" rows="3" placeholder="Enter prompt...">${b.prompt || ""}</textarea> |
|
|
<button class="btn btn-primary w-100" onclick="generateFlux(${i}, this)">EXECUTE VPS INFERENCE</button> |
|
|
<div id="loader-${i}" class="text-center mt-2" style="display:none"><div class="spinner-grow spinner-grow-sm text-info"></div> GEN IN PROGRESS...</div> |
|
|
<img id="img-${i}" class="gen-image-preview mt-3"> |
|
|
</div> |
|
|
<div class="small fw-bold opacity-50">NODE_0${i+1}</div> |
|
|
<button class="close-btn" onclick="event.stopPropagation(); this.parentElement.classList.remove('active')">DISCONNECT</button> |
|
|
<button class="add-block-btn" onclick="event.stopPropagation(); updateGrid()">+</button> |
|
|
`; |
|
|
container.appendChild(el); |
|
|
}); |
|
|
} |
|
|
|
|
|
async function generateFlux(index, btn) { |
|
|
event.stopPropagation(); |
|
|
const prompt = document.getElementById(`prompt-${index}`).value; |
|
|
const token = document.getElementById("hfToken").value; |
|
|
const imgEl = document.getElementById(`img-${index}`); |
|
|
const loader = document.getElementById(`loader-${index}`); |
|
|
|
|
|
if (!token) { |
|
|
alert("Auth Error: HuggingFace Token Missing."); |
|
|
return; |
|
|
} |
|
|
|
|
|
loader.style.display = "block"; |
|
|
imgEl.style.display = "none"; |
|
|
blocks[index].prompt = prompt; |
|
|
saveToCache(); |
|
|
|
|
|
try { |
|
|
const response = await fetch( |
|
|
"https://api-inference.huggingface.co/models/Comfy-Org/flux2-klein-9B", |
|
|
{ |
|
|
headers: { Authorization: `Bearer ${token}` }, |
|
|
method: "POST", |
|
|
body: JSON.stringify({ inputs: prompt }), |
|
|
} |
|
|
); |
|
|
|
|
|
const blob = await response.blob(); |
|
|
imgEl.src = URL.createObjectURL(blob); |
|
|
imgEl.style.display = "block"; |
|
|
} catch (err) { |
|
|
console.error(err); |
|
|
} finally { |
|
|
loader.style.display = "none"; |
|
|
} |
|
|
} |
|
|
|
|
|
function handleLogin(type) { |
|
|
currentUser = { name: "VPS_ADMIN" }; |
|
|
document.getElementById("login-section").style.display = "none"; |
|
|
document.getElementById("user-profile").style.display = "block"; |
|
|
} |
|
|
|
|
|
function showStatus(msg) { |
|
|
document.getElementById("saveStatus").innerText = msg; |
|
|
setTimeout(() => document.getElementById("saveStatus").innerText = "Synced", 2000); |
|
|
} |
|
|
|
|
|
function toggleSidebar() { document.getElementById("sidebar").classList.toggle("collapsed"); } |
|
|
function clearGrid() { localStorage.clear(); location.reload(); } |
|
|
|
|
|
window.onload = loadFromCache; |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|