| <!DOCTYPE html> |
| <html> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <link rel="shortcut icon" |
| href="data:image/png;base64,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" /> |
| <title>SDCPP Server</title> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| height: 100vh; |
| margin: 0; |
| background-color: #f0f0f0; |
| color: #333; |
| } |
| |
| .container { |
| display: flex; |
| flex-direction: column; |
| justify-content: space-between; |
| align-items: center; |
| width: 90%; |
| height: auto; |
| min-height: auto; |
| background: white; |
| padding-inline: 20px; |
| border-radius: 10px; |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| gap: 1rem; |
| } |
| |
| .header { |
| text-align: center; |
| } |
| |
| .section { |
| width: 100%; |
| } |
| |
| .section h2 { |
| margin-bottom: 10px; |
| } |
| |
| .input-group { |
| display: flex; |
| flex-direction: column; |
| margin-bottom: 10px; |
| } |
| |
| .input-group label { |
| margin-bottom: 5px; |
| font-weight: bold; |
| } |
| |
| |
| .prompt-input { |
| width: 100%; |
| padding: 10px; |
| border: 1px solid #ccc; |
| border-radius: 5px; |
| margin-bottom: 10px; |
| box-sizing: border-box; |
| resize: none; |
| } |
| |
| .param-input { |
| width: 100%; |
| padding: 10px; |
| border: 1px solid #ccc; |
| border-radius: 5px; |
| margin-bottom: 10px; |
| box-sizing: border-box; |
| } |
| |
| .line { |
| display: flex; |
| justify-content: space-between; |
| width: 100%; |
| } |
| |
| .line .input-group { |
| width: 48%; |
| } |
| |
| canvas { |
| width: 100%; |
| height: 100%; |
| } |
| |
| |
| |
| |
| |
| |
| .collapsible { |
| background-color: #eee; |
| color: #444; |
| cursor: pointer; |
| padding: 10px; |
| width: 100%; |
| border: none; |
| text-align: left; |
| outline: none; |
| font-size: 15px; |
| border-radius: 5px; |
| margin-bottom: 10px; |
| } |
| |
| .content { |
| padding: 0 18px; |
| max-height: 0; |
| overflow: hidden; |
| transition: max-height 0.2s ease-out; |
| } |
| |
| #model-id { |
| display: inline-block; |
| background: lightgray; |
| margin-bottom: 1rem; |
| padding: 5px 10px; |
| border-radius: 5px; |
| } |
| |
| button { |
| background-color: #4CAF50; |
| color: white; |
| padding: 10px 20px; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| font-size: 16px; |
| height: 3rem; |
| } |
| |
| progress { |
| width: 100%; |
| } |
| |
| button:hover { |
| background-color: #45a049; |
| } |
| |
| .status { |
| margin-top: 5px; |
| font-size: 18px; |
| } |
| |
| |
| .left-section { |
| width: 100%; |
| height: auto; |
| padding: 15px; |
| box-sizing: border-box; |
| } |
| |
| .right-section { |
| height: 100%; |
| width: 100%; |
| padding: 15px; |
| box-sizing: border-box; |
| } |
| |
| @media (min-width: 768px) { |
| .container { |
| max-height: 95%; |
| min-height: 512px; |
| height: 95%; |
| flex-direction: row; |
| justify-content: space-between; |
| } |
| |
| .left-section { |
| overflow-y: auto; |
| max-width: 100%; |
| max-height: 100%; |
| } |
| |
| |
| } |
| |
| .image-container { |
| width: 100%; |
| height: 100%; |
| overflow-y: auto; |
| border: 1px solid #ccc; |
| display: flex; |
| flex-wrap: wrap; |
| gap: 10px; |
| padding: 10px; |
| background-color: #f5f5f5; |
| box-sizing: border-box; |
| } |
| |
| .image-container img { |
| flex: 1 1 calc(33.333% - 10px); |
| min-width: 10px; |
| height: 150px; |
| object-fit: contain; |
| cursor: pointer; |
| display: inline-block; |
| vertical-align: top; |
| } |
| |
| |
| .fullscreen-overlay { |
| display: none; |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.9); |
| z-index: 1000; |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .fullscreen-content { |
| position: relative; |
| width: 90%; |
| height: 90%; |
| } |
| |
| #fullscreenImg { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| max-width: 90%; |
| max-height: 90%; |
| display: block; |
| margin: auto; |
| } |
| |
| .nav-btn { |
| position: absolute; |
| top: 50%; |
| transform: translateY(-50%); |
| font-size: 40px; |
| color: white; |
| background: none; |
| border: none; |
| cursor: pointer; |
| padding: 10px; |
| } |
| |
| .prev-btn { |
| left: 20px; |
| width: 30%; |
| height: 100%; |
| } |
| |
| .next-btn { |
| right: 20px; |
| width: 30%; |
| height: 100%; |
| } |
| |
| |
| .close-btn { |
| position: absolute; |
| top: 20px; |
| right: 30px; |
| font-size: 50px; |
| color: white; |
| cursor: pointer; |
| } |
| </style> |
| </head> |
|
|
|
|
| <body> |
| <div class="header"> |
| <p>Model:<span id="model-id">Z-Image-Turbo</span></p> |
| </div> |
| <div class="container"> |
| <div class="left-section"> |
| <div class="section"> |
| <div class="input-group"> |
| <label for="prompt">Prompt:</label> |
| <textarea id="prompt" rows="4" class="prompt-input"></textarea> |
| </div> |
| <div class="input-group"> |
| <label for="neg_prompt">Negative Prompt:</label> |
| <textarea id="neg_prompt" rows="4" |
| class="prompt-input">blurry, ugly, bad vagina, deformed, unrealistic, multiple penises, extra arms, extra limbs, overexposed, dribbling, flat colours, labia, vagina</textarea> |
| </div> |
| <div class="line"> |
| <button onclick="generateImage()">Generate</button> |
| <button onclick="update_queue()">status</button> |
| <button onclick="renderImages()">Gallery</button> |
| </div> |
| </div> |
| <div class="section"> |
| <h2>Settings</h2> |
| <button class="collapsible">Loras</button> |
| <div id="lora_model" class="content"> |
| </div> |
| <div class="line"> |
| <div class="input-group"> |
| <label for="gennum">Generate num:</label> |
| <input type="number" id="gennum" class="param-input" value=5> |
| </div> |
| |
| </div> |
| <div class="line"> |
| <div class="input-group"> |
| <label for="width">Width:</label> |
| <input type="number" id="width" class="param-input" value=960> |
| </div> |
| <div class="input-group"> |
| <label for="height">Height:</label> |
| <input type="number" id="height" class="param-input" value=1280> |
| </div> |
| </div> |
| <div class="line"> |
| <div class="input-group"> |
| <label for="seed">Seed:</label> |
| <input type="number" id="seed" class="param-input" value=-1> |
| </div> |
| <div class="input-group"> |
| <label for="batch_count">Batch Count:</label> |
| <input type="number" id="batch_count" class="param-input" value=2> |
| </div> |
| </div> |
| <div class="line"> |
| <div class="input-group"> |
| <label for="cfg_scale">CFG Scale:</label> |
| <input type="number" id="cfg_scale" class="param-input" value=1.0> |
| </div> |
| <div class="input-group"> |
| <label for="steps">Steps:</label> |
| <input type="number" id="steps" class="param-input" value=4> |
| </div> |
| </div> |
| <div class="line"> |
| <div class="input-group"> |
| <label for="sample_method">Sample Method:</label> |
| <select id="sample_method" class="param-input"> |
| <option value="euler">euler</option> |
| <option value="euler_a">euler_a</option> |
| <option value="heun">heun</option> |
| <option value="dpm++2s_a">dpm++2s_a</option> |
| <option value="dpm++2mv2">dpm++2mv2</option> |
| <option value="dpm++2m">dpm++2m</option> |
| <option value="dpm2">dpm2</option> |
| </select> |
| </div> |
| <div class="input-group"> |
| <label for="scheduler">scheduler:</label> |
| <select id="scheduler" class="param-input"> |
| <option value="discrete">discrete</option> |
| <option value="karras">karras</option> |
| <option value="simple">simple</option> |
| </select> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="right-section"> |
| <div class="image-container" id="imageContainer"> |
| </div> |
| </div> |
| </div> |
| <div class="fullscreen-overlay" id="fullscreenOverlay"> |
| <span class="close-btn" id="closeBtn">×</span> |
| <div class="fullscreen-content" id="fullscreenContent"> |
| <img id="fullscreenImg" src="" alt="fullscreenImg"> |
| <button class="nav-btn prev-btn" id="prevBtn">‹</button> |
| <button class="nav-btn next-btn" id="nextBtn">›</button> |
| </div> |
| </div> |
| <div class="status"> |
| <p>Current task status: <span id="status"> -- </span> | Queue: <span id="queued_tasks">0</span></p> |
| </div> |
|
|
| <script> |
| async function addLora() { |
| const response = await fetch('/addLora', { |
| method: 'GET', |
| }); |
| const data = await response.json(); |
| |
| const lora_str = document.getElementById('lora_model'); |
| lora_str.innerHTML = ''; |
| |
| data.loras.forEach((itemText, index) => { |
| const html = ` <label><input type="checkbox" name="loraitem" value=${index}>${itemText["title"]}</label><br>`; |
| lora_str.insertAdjacentHTML('beforeend', html); |
| }); |
| |
| } |
| async function update_queue() { |
| const response = await fetch('/status', { |
| method: 'GET', |
| }); |
| const data = await response.json(); |
| const display = document.getElementById('queued_tasks'); |
| display.innerHTML = data.num; |
| const status = data.status; |
| document.getElementById('status').innerHTML = status; |
| } |
| async function generateImage() { |
| const prompt = document.getElementById('prompt').value; |
| const neg_prompt = document.getElementById('neg_prompt').value; |
| const width = document.getElementById('width').value; |
| const height = document.getElementById('height').value; |
| const cfg_scale = document.getElementById('cfg_scale').value; |
| const steps = document.getElementById('steps').value; |
| const sample_method = document.getElementById('sample_method').value; |
| const seed = document.getElementById('seed').value; |
| const batch_count = document.getElementById('batch_count').value; |
| const gennum = document.getElementById('gennum').value; |
| const scheduler = document.getElementById('scheduler').value; |
| const requestBody = { |
| prompt: prompt, |
| negative_prompt: neg_prompt, |
| width: width, |
| height: height, |
| cfg_scale: cfg_scale, |
| steps: steps, |
| sample_method: sample_method, |
| seed: seed, |
| batch_count: batch_count, |
| schedule: scheduler, |
| gennum: parseInt(gennum), |
| loras: getSelectedloras(), |
| }; |
| const response = await fetch('/gen', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify(requestBody) |
| }); |
| const data = await response.json(); |
| const status = data.status; |
| document.getElementById('status').innerHTML = status; |
| const display = document.getElementById('queued_tasks'); |
| display.innerHTML = data.num; |
| } |
| var coll = document.getElementsByClassName("collapsible"); |
| for (let i = 0; i < coll.length; i++) { |
| coll[i].addEventListener("click", function () { |
| this.classList.toggle("active"); |
| var content = this.nextElementSibling; |
| content.style.transition = "max-height 0.3s ease-out"; |
| if (content.style.maxHeight) { |
| content.style.maxHeight = null; |
| } else { |
| content.style.maxHeight = content.scrollHeight + "px"; |
| |
| content.scrollTop = 0; |
| } |
| }); |
| } |
| function getSelectedloras() { |
| const checkboxes = document.querySelectorAll('input[name="loraitem"]:checked'); |
| const selectedValues = []; |
| checkboxes.forEach(checkbox => { |
| selectedValues.push(parseInt(checkbox.value)); |
| }); |
| return selectedValues; |
| } |
| const imageContainer = document.getElementById('imageContainer'); |
| const fullscreenOverlay = document.getElementById('fullscreenOverlay'); |
| const fullscreenImg = document.getElementById('fullscreenImg'); |
| const closeBtn = document.getElementById('closeBtn'); |
| const prevBtn = document.getElementById('prevBtn'); |
| const nextBtn = document.getElementById('nextBtn'); |
| const fullscreenContent = document.getElementById('fullscreenContent'); |
| |
| |
| async function renderImages() { |
| const response = await fetch('getimages', { |
| method: 'GET', |
| }); |
| const base64Images = await response.json().images; |
| imageContainer.innerHTML = ''; |
| base64Images.forEach((src) => { |
| const img = document.createElement('img'); |
| img.src = src; |
| img.onclick = () => openFullscreen(img); |
| imageContainer.appendChild(img); |
| }); |
| } |
| |
| function openFullscreen(index) { |
| index.id = "nowfullscreenImg" |
| fullscreenImg.src = index.src; |
| fullscreenOverlay.style.display = 'flex'; |
| } |
| |
| function closeFullscreen() { |
| document.getElementById('nowfullscreenImg').removeAttribute('id'); |
| fullscreenOverlay.style.display = 'none'; |
| } |
| |
| function changeImage(direction) { |
| let con = document.getElementById('nowfullscreenImg'); |
| let nextElement; |
| if (direction === 'next') { |
| |
| nextElement = con.nextElementSibling; |
| } else if (direction === 'prev') { |
| nextElement = con.previousElementSibling; |
| } |
| if (nextElement) { |
| con.removeAttribute('id'); |
| nextElement.id = "nowfullscreenImg" |
| fullscreenImg.src = nextElement.src; |
| } |
| |
| } |
| |
| |
| closeBtn.onclick = closeFullscreen; |
| prevBtn.onclick = () => changeImage('prev'); |
| nextBtn.onclick = () => changeImage('next'); |
| |
| addLora(); |
| |
| </script> |
| </body> |
|
|
| </html> |