| | <!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> |
| |
|