| <!DOCTYPE html>
|
| <html lang="en">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Flux2 Klein Generator</title>
|
| <style>
|
| :root {
|
| --bg-color: #121212;
|
| --surface-color: #1e1e1e;
|
| --primary-color: #6200ea;
|
| --text-color: #e0e0e0;
|
| --border-color: #333;
|
| }
|
| body {
|
| font-family: system-ui, -apple-system, sans-serif;
|
| background-color: var(--bg-color);
|
| color: var(--text-color);
|
| display: flex;
|
| justify-content: center;
|
| padding: 2rem;
|
| margin: 0;
|
| }
|
| .container {
|
| background-color: var(--surface-color);
|
| padding: 2rem;
|
| border-radius: 12px;
|
| width: 100%;
|
| max-width: 800px;
|
| box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
| }
|
| h2 {
|
| margin-top: 0;
|
| font-weight: 500;
|
| }
|
| textarea {
|
| width: 100%;
|
| height: 120px;
|
| background-color: var(--bg-color);
|
| color: var(--text-color);
|
| border: 1px solid var(--border-color);
|
| border-radius: 8px;
|
| padding: 1rem;
|
| font-family: inherit;
|
| font-size: 1rem;
|
| box-sizing: border-box;
|
| resize: vertical;
|
| margin-bottom: 1rem;
|
| }
|
| textarea:focus {
|
| outline: none;
|
| border-color: var(--primary-color);
|
| }
|
| button {
|
| background-color: var(--primary-color);
|
| color: white;
|
| border: none;
|
| padding: 0.75rem 1.5rem;
|
| font-size: 1rem;
|
| border-radius: 8px;
|
| cursor: pointer;
|
| transition: opacity 0.2s;
|
| width: 100%;
|
| font-weight: bold;
|
| }
|
| button:hover {
|
| opacity: 0.9;
|
| }
|
| button:disabled {
|
| background-color: var(--border-color);
|
| cursor: not-allowed;
|
| }
|
| .status {
|
| margin-top: 1rem;
|
| font-size: 0.9rem;
|
| color: #aaa;
|
| text-align: center;
|
| min-height: 1.5em;
|
| }
|
| .result-container {
|
| margin-top: 1.5rem;
|
| text-align: center;
|
| }
|
| img {
|
| max-width: 100%;
|
| border-radius: 8px;
|
| display: none;
|
| border: 1px solid var(--border-color);
|
| }
|
| </style>
|
| </head>
|
| <body>
|
|
|
| <div class="container">
|
| <h2>🎨 Flux 2 Klein Generator</h2>
|
| <textarea id="promptInput" placeholder="Enter your image prompt here...">Photoreal studio product photo of a matte black ceramic coffee mug sitting centered on a raw, weathered oak wooden block. Clear, highly legible text printed on the mug reads: "FAST 4-STEP". Below it, a smaller line reads: "KLEIN 4B". Wisps of realistic steam rise from hot coffee inside the mug. Studio softbox lighting from camera left creates a subtle golden rim highlight, casting soft, smooth shadows on the wood. Crisp focus with razor-sharp details, shallow depth of field, minimalist clean beige background.</textarea>
|
| <button id="generateBtn" onclick="generateImage()">Generate Image</button>
|
| <div id="status" class="status"></div>
|
|
|
| <div class="result-container">
|
| <img id="resultImage" alt="Generated Output" />
|
| </div>
|
| </div>
|
|
|
| <script>
|
| const workflowTemplate = {
|
| "76": { "inputs": { "value": "" }, "class_type": "PrimitiveStringMultiline" },
|
| "97": { "inputs": { "filename_prefix": "ComfyUI", "images": [ "77:82", 0 ] }, "class_type": "SaveImage" },
|
| "77:80": { "inputs": { "sampler_name": "euler" }, "class_type": "KSamplerSelect" },
|
| "77:81": { "inputs": { "noise": [ "77:86", 0 ], "guider": [ "77:90", 0 ], "sampler": [ "77:80", 0 ], "sigmas": [ "77:93", 0 ], "latent_image": [ "77:83", 0 ] }, "class_type": "SamplerCustomAdvanced" },
|
| "77:82": { "inputs": { "samples": [ "77:81", 0 ], "vae": [ "77:89", 0 ] }, "class_type": "VAEDecode" },
|
| "77:83": { "inputs": { "width": [ "77:84", 0 ], "height": [ "77:85", 0 ], "batch_size": 1 }, "class_type": "EmptyFlux2LatentImage" },
|
| "77:84": { "inputs": { "value": 1024 }, "class_type": "PrimitiveInt" },
|
| "77:85": { "inputs": { "value": 1024 }, "class_type": "PrimitiveInt" },
|
| "77:86": { "inputs": { "noise_seed": 0 }, "class_type": "RandomNoise" },
|
| "77:87": { "inputs": { "unet_name": "flux-2-klein-4b-fp8.safetensors", "weight_dtype": "default" }, "class_type": "UNETLoader" },
|
| "77:88": { "inputs": { "clip_name": "qwen_3_4b.safetensors", "type": "flux2", "device": "default" }, "class_type": "CLIPLoader" },
|
| "77:89": { "inputs": { "vae_name": "flux2-vae.safetensors" }, "class_type": "VAELoader" },
|
| "77:90": { "inputs": { "cfg": 1, "model": [ "77:87", 0 ], "positive": [ "77:92", 0 ], "negative": [ "77:91", 0 ] }, "class_type": "CFGGuider" },
|
| "77:91": { "inputs": { "conditioning": [ "77:92", 0 ] }, "class_type": "ConditioningZeroOut" },
|
| "77:92": { "inputs": { "text": [ "76", 0 ], "clip": [ "77:88", 0 ] }, "class_type": "CLIPTextEncode" },
|
| "77:93": { "inputs": { "steps": 4, "width": [ "77:84", 0 ], "height": [ "77:85", 0 ] }, "class_type": "Flux2Scheduler" }
|
| };
|
|
|
| const COMFY_URL = "http://127.0.0.1:8188";
|
|
|
| async function generateImage() {
|
| const promptText = document.getElementById('promptInput').value.trim();
|
| if (!promptText) return;
|
|
|
| const btn = document.getElementById('generateBtn');
|
| const statusEl = document.getElementById('status');
|
| const imgEl = document.getElementById('resultImage');
|
|
|
| btn.disabled = true;
|
| statusEl.innerText = "Queuing workflow to ComfyUI...";
|
| imgEl.style.display = 'none';
|
|
|
|
|
| const workflow = JSON.parse(JSON.stringify(workflowTemplate));
|
| workflow["76"].inputs.value = promptText;
|
|
|
|
|
| workflow["77:86"].inputs.noise_seed = Math.floor(Math.random() * 999999999999999);
|
|
|
| try {
|
|
|
| const response = await fetch(`${COMFY_URL}/prompt`, {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ prompt: workflow })
|
| });
|
|
|
| if (!response.ok) throw new Error("Failed to reach ComfyUI.");
|
|
|
| const data = await response.json();
|
| const promptId = data.prompt_id;
|
|
|
| statusEl.innerText = "Generating... (Pushing through the GPU)";
|
|
|
|
|
| const pollInterval = setInterval(async () => {
|
| try {
|
| const historyRes = await fetch(`${COMFY_URL}/history/${promptId}`);
|
| const historyData = await historyRes.json();
|
|
|
|
|
| if (historyData[promptId]) {
|
| clearInterval(pollInterval);
|
| statusEl.innerText = "Generation complete!";
|
|
|
|
|
| const outputs = historyData[promptId].outputs;
|
| if (outputs && outputs["97"] && outputs["97"].images) {
|
| const filename = outputs["97"].images[0].filename;
|
|
|
| imgEl.src = `${COMFY_URL}/view?filename=${filename}&type=output`;
|
| imgEl.style.display = 'block';
|
| } else {
|
| statusEl.innerText = "Error: Couldn't find image output in Node 97.";
|
| }
|
| btn.disabled = false;
|
| }
|
| } catch (pollErr) {
|
| console.error("Polling error:", pollErr);
|
| }
|
| }, 2000);
|
|
|
| } catch (err) {
|
| console.error(err);
|
| statusEl.innerText = "Error: Make sure ComfyUI is running and CORS is enabled.";
|
| btn.disabled = false;
|
| }
|
| }
|
| </script>
|
| </body>
|
| </html> |