| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Infinite-Scale Zero-Cost AI Playground</title> |
| <script src="https://tailwindcss.com"></script> |
| <style> |
| .custom-scrollbar::-webkit-scrollbar { width: 6px; } |
| .custom-scrollbar::-webkit-scrollbar-track { background: #1f2937; } |
| .custom-scrollbar::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 3px; } |
| </style> |
| </head> |
| <body class="bg-gray-950 text-gray-100 min-h-screen flex flex-col font-sans"> |
|
|
| <header class="border-b border-gray-800 bg-gray-900/50 backdrop-blur px-6 py-4 flex flex-wrap items-center justify-between gap-4"> |
| <div class="flex items-center space-x-3"> |
| <span class="text-2xl">⚡</span> |
| <div> |
| <h1 class="text-xl font-bold tracking-tight bg-gradient-to-r from-yellow-400 to-orange-500 bg-clip-text text-transparent">Zero-Cost AI Engine</h1> |
| <p class="text-xs text-gray-400">100% Client-Side Decentralized Architecture</p> |
| </div> |
| </div> |
| <div class="flex items-center space-x-3"> |
| <div id="auth-status" class="text-xs font-mono bg-gray-800 px-3 py-1.5 rounded border border-gray-700 text-gray-400"> |
| Using Shared Anonymous IP Limits |
| </div> |
| <button id="login-btn" class="bg-yellow-500 hover:bg-yellow-600 text-gray-950 px-4 py-1.5 rounded font-medium text-sm transition shadow-lg shadow-yellow-500/10"> |
| Link HF Account |
| </button> |
| </div> |
| </header> |
|
|
| <main class="flex-1 grid grid-cols-1 lg:grid-cols-12 gap-6 p-6 overflow-hidden"> |
| |
| <section class="lg:col-span-4 bg-gray-900 border border-gray-800 rounded-xl p-5 flex flex-col space-y-4 shadow-xl"> |
| <div> |
| <label class="block text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">1. Choose Target Model</label> |
| <select id="model-selector" class="w-full bg-gray-950 border border-gray-700 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-yellow-500 text-gray-200"> |
| <optgroup label="📝 Text Generation (Real-Time SSE Streaming)"> |
| <option value="meta-llama/Llama-3.3-70B-Instruct" selected>meta-llama/Llama-3.3-70B-Instruct</option> |
| <option value="deepseek-ai/DeepSeek-R1-Distill-Qwen-32B">deepseek-ai/DeepSeek-R1-Distill-Qwen-32B (CoT)</option> |
| </optgroup> |
| <optgroup label="🎨 Image Generation (Binary Blob Manipulation)"> |
| <option value="black-forest-labs/FLUX.1-schnell">black-forest-labs/FLUX.1-schnell (Ultra-Fast)</option> |
| <option value="stabilityai/stable-diffusion-3.5-large">stabilityai/stable-diffusion-3.5-large</option> |
| </optgroup> |
| <optgroup label="🎬 Video Generation (ZeroGPU Space Fallback Pools)"> |
| <option value="VIDEO_POOL">Public ZeroGPU Video Space Cluster</option> |
| </optgroup> |
| </select> |
| </div> |
|
|
| <div class="flex-1 flex flex-col"> |
| <label class="block text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">2. Enter Generation Prompt</label> |
| <textarea id="prompt-input" placeholder="Type instructions here..." class="w-full flex-1 min-h-[150px] bg-gray-950 border border-gray-700 rounded-lg p-3 text-sm focus:outline-none focus:border-yellow-500 resize-none custom-scrollbar text-gray-100"></textarea> |
| </div> |
|
|
| <button id="generate-btn" class="w-full bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600 text-gray-950 font-bold py-3 rounded-lg transition transform active:scale-[0.98] shadow-lg shadow-orange-500/20"> |
| Execute Request |
| </button> |
| </section> |
|
|
| <section class="lg:col-span-8 bg-gray-900 border border-gray-800 rounded-xl p-5 flex flex-col shadow-xl min-h-[400px]"> |
| <div class="flex items-center justify-between border-b border-gray-800 pb-3 mb-4"> |
| <span class="text-xs font-semibold uppercase tracking-wider text-gray-400">Live Execution Output Canvas</span> |
| <span id="status-indicator" class="text-xs text-gray-500 font-mono">Idle</span> |
| </div> |
| |
| <div class="flex-1 flex flex-col justify-center items-center relative overflow-hidden bg-gray-950 border border-gray-800 rounded-lg p-4"> |
| <div id="text-output" class="w-full h-full text-sm font-mono whitespace-pre-wrap overflow-y-auto custom-scrollbar text-gray-300 hidden select-text"></div> |
| |
| <img id="image-output" alt="AI Generation Output" class="max-w-full max-h-full object-contain rounded shadow-2xl hidden" /> |
| |
| <video id="video-output" controls class="max-w-full max-h-full rounded shadow-2xl hidden"></video> |
|
|
| <div id="placeholder-view" class="text-center space-y-2 pointer-events-none"> |
| <span class="text-4xl block opacity-40">🤖</span> |
| <p class="text-xs text-gray-500">Output will render in real time within this client container.</p> |
| </div> |
| </div> |
| </section> |
| </main> |
|
|
| <script type="module"> |
| const CONFIG = { |
| imageModels: [ |
| "black-forest-labs/FLUX.1-schnell", |
| "stabilityai/stable-diffusion-3.5-large" |
| ], |
| videoSpaces: [ |
| "zhipuai/CogVideoX-5b-Space", |
| "ali-vilab/InVideo-AnimateDiff" |
| ] |
| }; |
| |
| class ProductionHFEngine { |
| constructor() { |
| this.token = window.localStorage.getItem('hf_playground_token') || ""; |
| } |
| |
| setToken(newToken) { |
| this.token = newToken; |
| window.localStorage.setItem('hf_playground_token', newToken); |
| } |
| |
| getHeaders() { |
| const headers = { "Content-Type": "application/json" }; |
| if (this.token) headers["Authorization"] = `Bearer ${this.token}`; |
| return headers; |
| } |
| |
| |
| |
| |
| async runInference(modelId, prompt, UI) { |
| const isImage = CONFIG.imageModels.includes(modelId); |
| UI.onStatus("Connecting to Serverless Pipeline..."); |
| |
| try { |
| const payload = isImage |
| ? { inputs: prompt } |
| : { inputs: prompt, parameters: { max_new_tokens: 1024 }, stream: true }; |
| |
| const response = await fetch(`https://api-inference.huggingface.co/models/${modelId}`, { |
| method: "POST", |
| headers: this.getHeaders(), |
| body: JSON.stringify(payload) |
| }); |
| |
| if (response.status === 429) { |
| throw new Error("429 Rate Limit Hit. Provide your own free HF token or try again in 5 minutes."); |
| } |
| if (!response.ok) throw new Error(`Hugging Face Error: ${response.statusText}`); |
| |
| if (isImage) { |
| const blob = await response.blob(); |
| UI.onImage(URL.createObjectURL(blob)); |
| return; |
| } |
| |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder("utf-8"); |
| let buffer = ""; |
| UI.onTextStart(); |
| |
| while (true) { |
| const { value, done } = await reader.read(); |
| if (done) break; |
| |
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split("\n"); |
| buffer = lines.pop(); |
| |
| for (const line of lines) { |
| const cleanLine = line.trim(); |
| if (!cleanLine || cleanLine.startsWith(":")) continue; |
| |
| if (cleanLine.startsWith("data:")) { |
| const dataStr = cleanLine.replace(/^data:\s*/, ""); |
| if (dataStr === "[DONE]") break; |
| |
| try { |
| const parsed = JSON.parse(dataStr); |
| |
| const chunk = parsed.token?.text || parsed.choices?.[0]?.delta?.content || parsed.generated_text || ""; |
| UI.onTextChunk(chunk); |
| } catch (e) {} |
| } |
| } |
| } |
| UI.onStatus("Stream Complete"); |
| } catch (err) { |
| UI.onError(err.message); |
| } |
| } |
| |
| |
| |
| |
| async runVideo(prompt, UI, poolIndex = 0) { |
| if (poolIndex >= CONFIG.videoSpaces.length) { |
| UI.onError("All public ZeroGPU Space pools are saturated. Please try again shortly."); |
| return; |
| } |
| |
| const currentSpace = CONFIG.videoSpaces[poolIndex]; |
| UI.onStatus(`[Pool ${poolIndex + 1}/${CONFIG.videoSpaces.length}] Testing payload match: ${currentSpace}...`); |
| |
| try { |
| const { Client } = await import("https://cdn.jsdelivr.net/npm/@gradio/client/+esm"); |
| const app = await Client.connect(currentSpace, { hf_token: this.token || undefined }); |
| |
| UI.onStatus("Bypassing queues... waiting for hardware allocation context..."); |
| |
| |
| const predictEndpoint = app.config.api_info.named_endpoints["/predict"] || app.config.api_info.unnamed_endpoints["0"]; |
| const expectedInputLength = predictEndpoint.parameters.length; |
| |
| |
| const payloadArray = Array(expectedInputLength).fill(null); |
| payloadArray[0] = prompt; |
| |
| if (expectedInputLength > 1) { |
| payloadArray[1] = Math.floor(Math.random() * 100000); |
| } |
| |
| const result = await app.predict(predictEndpoint.endpoint || "/predict", payloadArray); |
| |
| if (result?.data?.[0]) { |
| const rawData = result.data[0]; |
| const videoUrl = typeof rawData === 'object' ? rawData.url : rawData; |
| UI.onVideo(videoUrl); |
| } else { |
| throw new Error("Payload mismatch on dynamic endpoint."); |
| } |
| } catch (error) { |
| console.warn(`Space [${currentSpace}] failed processing. Cascading execution downward...`, error); |
| await this.runVideo(prompt, UI, poolIndex + 1); |
| } |
| } |
| } |
| |
| |
| const engine = new ProductionHFEngine(); |
| const nodes = { |
| selector: document.getElementById("model-selector"), |
| prompt: document.getElementById("prompt-input"), |
| btn: document.getElementById("generate-btn"), |
| status: document.getElementById("status-indicator"), |
| authStatus: document.getElementById("auth-status"), |
| loginBtn: document.getElementById("login-btn"), |
| text: document.getElementById("text-output"), |
| img: document.getElementById("image-output"), |
| video: document.getElementById("video-output"), |
| placeholder: document.getElementById("placeholder-view") |
| }; |
| |
| |
| const activeView = (target) => { |
| [nodes.text, nodes.img, nodes.video, nodes.placeholder].forEach(n => n.classList.add("hidden")); |
| target.classList.remove("hidden"); |
| }; |
| |
| |
| const processOAuthCallback = () => { |
| |
| const hashParams = new URLSearchParams(window.location.hash.substring(1)); |
| const tokenFromUrl = hashParams.get("access_token"); |
| |
| if (tokenFromUrl) { |
| engine.setToken(tokenFromUrl); |
| |
| window.history.replaceState({}, document.title, window.location.pathname); |
| } |
| |
| if (engine.token) { |
| nodes.authStatus.innerText = "Authenticated via User Token Slot"; |
| nodes.authStatus.classList.replace("text-gray-400", "text-emerald-400"); |
| nodes.loginBtn.innerText = "Disconnect"; |
| } |
| }; |
| |
| nodes.loginBtn.addEventListener("click", () => { |
| if (engine.token) { |
| engine.setToken(""); |
| window.location.reload(); |
| } else { |
| |
| const hfSpaceId = window.location.host.split('.')[0].replace('-static', ''); |
| |
| window.location.href = `https://huggingface.co/oauth/authorize?client_id=${hfSpaceId}&response_type=token&scope=openid%20inference-api`; |
| } |
| }); |
| |
| |
| nodes.btn.addEventListener("click", async () => { |
| const model = nodes.selector.value; |
| const prompt = nodes.prompt.value.trim(); |
| if (!prompt) return alert("Please provide an action input prompt first."); |
| |
| nodes.btn.disabled = true; |
| nodes.btn.classList.add("opacity-50"); |
| |
| const UIHandlers = { |
| onStatus: (msg) => { nodes.status.innerText = msg; }, |
| onError: (err) => { |
| nodes.status.innerText = "Error encountered"; |
| activeView(nodes.text); |
| nodes.text.innerText = `⚠️ Execution halted:\n\n${err}`; |
| nodes.btn.disabled = false; |
| nodes.btn.classList.remove("opacity-50"); |
| }, |
| onTextStart: () => { |
| activeView(nodes.text); |
| nodes.text.innerText = ""; |
| }, |
| onTextChunk: (chunk) => { |
| nodes.text.innerText += chunk; |
| nodes.text.scrollTop = nodes.text.scrollHeight; |
| }, |
| onImage: (url) => { |
| activeView(nodes.img); |
| nodes.img.src = url; |
| nodes.status.innerText = "Success"; |
| nodes.btn.disabled = false; |
| nodes.btn.classList.remove("opacity-50"); |
| }, |
| onVideo: (url) => { |
| activeView(nodes.video); |
| nodes.video.src = url; |
| nodes.video.play(); |
| nodes.status.innerText = "Success"; |
| nodes.btn.disabled = false; |
| nodes.btn.classList.remove("opacity-50"); |
| } |
| }; |
| |
| if (model === "VIDEO_POOL") { |
| await engine.runVideo(prompt, UIHandlers); |
| } else { |
| await engine.runInference(model, prompt, UIHandlers); |
| nodes.btn.disabled = false; |
| nodes.btn.classList.remove("opacity-50"); |
| } |
| }); |
| |
| |
| processOAuthCallback(); |
| </script> |
| </body> |
| </html> |