Spaces:
Running
on
Zero
Running
on
Zero
<!-- Copyright (c) Meta Platforms, Inc. and affiliates. --> | |
<!-- This source code is licensed under the Chameleon License found in the --> | |
<!-- LICENSE file in the root directory of this source tree. --> | |
<h1> | |
<div id="connection-status"></div> | |
MiniViewer: | |
</h1> | |
<div class="container"> | |
<div class="sidebar with-padding"> | |
<h4>Input Controls</h4> | |
<div class="input-controls-container"> | |
<button class="button" onclick="addInput('text')">Add text input</button> | |
<button class="button" onclick="addInput('image')"> | |
Add image input | |
</button> | |
<button class="button" onclick="addInput('<END-OF-TURN>')"> | |
Add end-of-turn token | |
</button> | |
</div> | |
<hr /> | |
<h4>General Options</h4> | |
<div class="option"> | |
<label for="seed">seed</label> | |
<input type="number" id="seed" value="0" /> | |
</div> | |
<div class="option"> | |
<label for="max-seq-len">max sequence length</label> | |
<input type="number" id="max-seq-len" value="4096" /> | |
</div> | |
<div class="option"> | |
<label for="max-gen-len">max generation length</label> | |
<input type="number" id="max-gen-len" value="4096" /> | |
</div> | |
<h4> | |
<input type="checkbox" id="enable-text" name="enable-text" checked /> | |
<label for="enable-text">Text Decoder Options</label> | |
</h4> | |
<div class="option"> | |
<label for="text-rep-penalty">repetition penalty</label> | |
<input type="number" id="text-rep-penalty" value="1.2" step="0.01" /> | |
</div> | |
<div class="option"> | |
<label for="text-temp">temperature</label> | |
<input type="number" id="text-temp" value="0.7" step="0.01" /> | |
</div> | |
<div class="option"> | |
<label for="text-top-p">top-p</label> | |
<input type="number" id="text-top-p" value="0.9" step="0.01" /> | |
</div> | |
<h4> | |
<input type="checkbox" id="enable-image" name="enable-image" checked /> | |
<label for="enable-image">Image Decoder Options</label> | |
</h4> | |
<div class="option"> | |
<label for="img-cfg-gstext">cfg text</label> | |
<input type="number" id="img-cfg-gstext" value="3.0" step="0.01" /> | |
</div> | |
<div class="option"> | |
<label for="img-cfg-gsimage">cfg image</label> | |
<input type="number" id="img-cfg-gsimage" value="1.2" step="0.01" /> | |
</div> | |
<div class="option"> | |
<label for="img-temp">temperature</label> | |
<input type="number" id="img-temp" value="0.7" step="0.01" /> | |
</div> | |
<div class="option"> | |
<label for="img-top-p">top-p</label> | |
<input type="number" id="img-top-p" value="0.9" step="0.01" /> | |
</div> | |
</div> | |
<div class="content with-padding"> | |
<div class="input-wrapper"> | |
Inputs: | |
<div id="inputs" class="with-padding"></div> | |
</div> | |
<h4> | |
<button id="generate" class="button" onclick="generate()"> | |
Generate | |
</button> | |
<button | |
id="cancel" | |
class="button" | |
onclick="cancel()" | |
style="display: none" | |
> | |
Cancel | |
</button> | |
</h4> | |
Results: | |
<pre id="results" class="with-padding"></pre> | |
<div id="timing" class="with-padding"></div> | |
<div id="queue" class="with-padding"></div> | |
</div> | |
</div> | |
<style> | |
.container { | |
display: inline-flex; | |
} | |
.sidebar { | |
flex: 0 0 200px; | |
border-right: 2px solid #ddd; | |
} | |
#connection-status { | |
width: 20px; | |
height: 20px; | |
border-radius: 10px; | |
background-color: grey; | |
display: inline-block; | |
} | |
.input-controls-container { | |
display: inline-grid; | |
} | |
.option { | |
display: flex; | |
margin-bottom: 5px; | |
} | |
.option label { | |
white-space: nowrap; | |
margin-right: 10px; | |
} | |
.option input { | |
flex-grow: 1; | |
text-align: right; | |
} | |
.content { | |
width: 100%; | |
} | |
.with-padding { | |
padding: 10px; | |
} | |
.input-wrapper { | |
border: dotted; | |
} | |
.input-container { | |
display: flex; | |
align-items: center; | |
} | |
.input-controls { | |
display: inline-flex; | |
padding: 2px; | |
} | |
#results { | |
background: lightgray; | |
} | |
button { | |
text-align: left; | |
} | |
img { | |
width: 200px; | |
height: 200px; | |
} | |
</style> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.6.0/socket.io.min.js"></script> | |
<script> | |
var active_key; | |
var socket; | |
function createButton(text, onClick) { | |
var button = document.createElement("button"); | |
button.textContent = text; | |
button.onclick = onClick; | |
return button; | |
} | |
function removeInput(evt) { | |
var inputWrapper = evt.target.parentNode.parentNode; | |
inputWrapper.parentNode.removeChild(inputWrapper); | |
} | |
function moveInputUp(evt) { | |
var inputWrapper = evt.target.parentNode.parentNode; | |
var prev = inputWrapper.previousElementSibling; | |
if (prev) { | |
inputWrapper.parentNode.insertBefore(inputWrapper, prev); | |
} | |
} | |
function moveInputDown(evt) { | |
var inputWrapper = evt.target.parentNode.parentNode; | |
var next = inputWrapper.nextElementSibling; | |
if (next) { | |
inputWrapper.parentNode.insertBefore(next, inputWrapper); | |
} | |
} | |
function readFileAsync(file) { | |
return new Promise((resolve, reject) => { | |
let reader = new FileReader(); | |
reader.onload = () => resolve(reader.result); | |
reader.onerror = reject; | |
reader.readAsDataURL(file); | |
}); | |
} | |
async function loadImageSource(dataTransfer) { | |
if (dataTransfer.files.length > 0) { | |
return await readFileAsync(dataTransfer.files[0]); | |
} | |
let htmlContent = dataTransfer.getData("text/html"); | |
if (htmlContent) { | |
const div = document.createElement("div"); | |
div.innerHTML = htmlContent; | |
return div.querySelector("img").src; | |
} | |
return ( | |
dataTransfer.getData("text/uri-list") || | |
dataTransfer.getData("text/plain") | |
); | |
} | |
async function showPreview(evt) { | |
var wrapper = evt.target.parentElement; | |
wrapper.querySelector("img").src = await loadImageSource(evt.target); | |
wrapper.querySelector("img").style.display = "block"; | |
wrapper.querySelector("p").style.display = "none"; | |
} | |
async function handleDrop(evt) { | |
evt.preventDefault(); | |
var wrapper = evt.target.parentElement; | |
var file = evt.dataTransfer.files[0]; | |
var fileInput = wrapper.querySelector('input[type="file"]'); | |
fileInput.files = evt.dataTransfer.files; | |
wrapper.querySelector("img").src = await loadImageSource(evt.dataTransfer); | |
wrapper.querySelector("img").style.display = "block"; | |
wrapper.querySelector("p").style.display = "none"; | |
} | |
function addInput(input_kind) { | |
var inputs_div = document.getElementById("inputs"); | |
var wrapper = document.createElement("div"); | |
wrapper.kind = input_kind; | |
wrapper.className = "input-container"; | |
var new_inputs = []; | |
if (input_kind === "text") { | |
new_inputs.push(document.createElement("textarea")); | |
} else if (input_kind === "image") { | |
wrapper.setAttribute("draggable", true); | |
wrapper.ondragover = (evt) => evt.preventDefault(); | |
wrapper.ondrop = handleDrop; | |
var hiddenImageFromFile = document.createElement("input"); | |
hiddenImageFromFile.type = "file"; | |
hiddenImageFromFile.accept = "image/*"; | |
hiddenImageFromFile.addEventListener("change", showPreview); | |
hiddenImageFromFile.style.display = "none"; | |
wrapper.onclick = function () { | |
hiddenImageFromFile.click(); | |
}; | |
new_inputs.push(hiddenImageFromFile); | |
var description = document.createElement("p"); | |
description.textContent = | |
"Drag and drop your image here, or click to select."; | |
new_inputs.push(description); | |
var preview = document.createElement("img"); | |
preview.style.display = "none"; | |
new_inputs.push(preview); | |
} else { | |
var span = document.createElement("span"); | |
span.textContent = input_kind; | |
new_inputs.push(span); | |
} | |
const input_controls = document.createElement("div"); | |
input_controls.className = "input-controls"; | |
input_controls.appendChild(createButton("-", removeInput)); | |
input_controls.appendChild(createButton("β", moveInputDown)); | |
input_controls.appendChild(createButton("β", moveInputUp)); | |
wrapper.appendChild(input_controls); | |
for (var new_input of new_inputs) { | |
wrapper.appendChild(new_input); | |
} | |
wrapper.appendChild(document.createElement("br")); | |
inputs_div.appendChild(wrapper); | |
} | |
async function generate() { | |
document.getElementById("generate").style.display = "none"; | |
document.getElementById("cancel").style.display = "block"; | |
document.getElementById("results").innerHTML = ""; | |
document.getElementById("timing").innerHTML = ""; | |
document.getElementById("queue").innerHTML = ""; | |
active_key = `key_${Math.random() | |
.toString(36) | |
.substring(2, 11)}_${Date.now()}`; | |
const user_options = {}; | |
for (const option of document.getElementsByClassName("option")) { | |
const input = option.querySelector("input"); | |
user_options[input.id] = Number(input.value); | |
} | |
user_options["enable-text"] = | |
document.getElementById("enable-text").checked; | |
user_options["enable-image"] = | |
document.getElementById("enable-image").checked; | |
const user_inputs = []; | |
const inputs_div = document.getElementById("inputs"); | |
const input_elems = Array.from(inputs_div.children).map((wrapper) => | |
wrapper.querySelector("textarea, input, span") | |
); | |
const image_promises = Array.from(inputs_div.children) | |
.filter((wrapper) => wrapper.kind === "image") | |
.map((wrapper) => { | |
const file_input = wrapper.querySelector('input[type="file"]'); | |
return file_input.files[0] | |
? readFileAsync(file_input.files[0]) | |
: Promise.resolve(null); | |
}); | |
const images = await Promise.all(image_promises); | |
for (const wrapper of inputs_div.children) { | |
if (wrapper.kind === "text") { | |
user_inputs.push({ | |
type: "text", | |
value: wrapper.querySelector("textarea").value, | |
}); | |
} else if (wrapper.kind === "image") { | |
user_inputs.push({ type: "image", value: images.shift() }); | |
} else { | |
user_inputs.push({ type: "sentinel", value: wrapper.kind }); | |
} | |
} | |
socket.emit("generate", active_key, user_options, user_inputs); | |
} | |
function cancel() { | |
document.getElementById("generate").style.display = "block"; | |
document.getElementById("cancel").style.display = "none"; | |
document.getElementById("queue").innerHTML = ""; | |
socket.emit("cancel", active_key); | |
active_key = null; | |
} | |
function connectSocket() { | |
socket = io(); | |
socket.on("connect", function() { | |
document.getElementById("connection-status").style.backgroundColor = 'green'; | |
}); | |
socket.on("disconnect", function(reason) { | |
cancel(); | |
document.getElementById("connection-status").style.backgroundColor = 'red'; | |
}); | |
socket.on("progress", function (data) { | |
if (data.key != active_key) { | |
return; | |
} | |
document.getElementById("queue").innerHTML = ""; | |
if (data.type == "queue") { | |
document.getElementById( | |
"queue" | |
).innerHTML = `queue position ${data.value}`; | |
} | |
if (data.type == "text") { | |
document.getElementById("results").innerHTML += data.value; | |
} else if (data.type == "image_start") { | |
document.getElementById("results").appendChild(new Image()); | |
} else if (data.type == "image") { | |
document.getElementById("results").lastElementChild.src = data.value; | |
} else if (data.type == "image_end") { | |
} else if (data.type == "done") { | |
document.getElementById( | |
"timing" | |
).innerHTML = `Generation time: ${data.value.toFixed(2)} sec`; | |
document.getElementById("generate").style.display = "block"; | |
document.getElementById("cancel").style.display = "none"; | |
active_key = null; | |
} | |
}); | |
} | |
window.onload = (evt) => { | |
connectSocket(); | |
}; | |
</script> | |