|
import snarkdown from "https://cdn.skypack.dev/snarkdown"; |
|
import hljs from "https://cdn.skypack.dev/highlight.js"; |
|
|
|
const MODELS = { |
|
moondream2_q4k: { |
|
base_url: |
|
"https://huggingface.co/santiagomed/candle-moondream/resolve/main/", |
|
model: "model-q4_0.gguf", |
|
tokenizer: "tokenizer.json", |
|
quantized: true, |
|
size: "1.51 GB", |
|
}, |
|
}; |
|
|
|
const moodreamWorker = new Worker("./moondreamWorker.js", { |
|
type: "module", |
|
}); |
|
|
|
async function generateSequence(controller) { |
|
const getValue = (id) => document.querySelector(`#${id}`).value; |
|
const modelID = getValue("model"); |
|
const model = MODELS[modelID]; |
|
const weightsURL = |
|
model.model instanceof Array |
|
? model.model.map((m) => model.base_url + m) |
|
: model.base_url + model.model; |
|
const tokenizerURL = model.base_url + model.tokenizer; |
|
|
|
const prompt = getValue("prompt").trim(); |
|
const temperature = getValue("temperature"); |
|
const topP = getValue("top-p"); |
|
const repeatPenalty = getValue("repeat_penalty"); |
|
const seed = getValue("seed"); |
|
const maxSeqLen = getValue("max-seq"); |
|
|
|
if (prompt?.value?.trim() === "") { |
|
return; |
|
} |
|
|
|
function updateStatus(data) { |
|
const outStatus = document.querySelector("#output-status"); |
|
const outGen = document.querySelector("#output-generation"); |
|
const outCounter = document.querySelector("#output-counter"); |
|
|
|
switch (data.status) { |
|
case "loading": |
|
outStatus.hidden = false; |
|
outStatus.textContent = data.message; |
|
outGen.hidden = true; |
|
outCounter.hidden = true; |
|
break; |
|
case "generating": |
|
const { message, prompt, sentence, tokensSec, totalTime } = data; |
|
outStatus.hidden = true; |
|
outCounter.hidden = false; |
|
outGen.hidden = false; |
|
outGen.innerHTML = snarkdown(prompt + sentence); |
|
outCounter.innerHTML = `${(totalTime / 1000).toFixed( |
|
2 |
|
)}s (${tokensSec.toFixed(2)} tok/s)`; |
|
hljs.highlightAll(); |
|
break; |
|
case "complete": |
|
outStatus.hidden = true; |
|
outGen.hidden = false; |
|
break; |
|
} |
|
} |
|
|
|
return new Promise((resolve, reject) => { |
|
moodreamWorker.postMessage({ |
|
weightsURL, |
|
modelID, |
|
tokenizerURL, |
|
quantized: model.quantized, |
|
imageURL: currentImageURL, |
|
prompt, |
|
temp: temperature, |
|
top_p: topP, |
|
repeatPenalty, |
|
seed: seed, |
|
maxSeqLen, |
|
verbose_prompt: false, |
|
command: "start", |
|
}); |
|
|
|
const handleAbort = () => { |
|
moodreamWorker.postMessage({ command: "abort" }); |
|
}; |
|
const handleMessage = (event) => { |
|
const { status, error, message, prompt, sentence } = event.data; |
|
if (status) updateStatus(event.data); |
|
if (error) { |
|
moodreamWorker.removeEventListener("message", handleMessage); |
|
reject(new Error(error)); |
|
} |
|
if (status === "aborted") { |
|
moodreamWorker.removeEventListener("message", handleMessage); |
|
resolve(event.data); |
|
} |
|
if (status === "complete") { |
|
moodreamWorker.removeEventListener("message", handleMessage); |
|
resolve(event.data); |
|
} |
|
}; |
|
|
|
controller.signal.addEventListener("abort", handleAbort); |
|
moodreamWorker.addEventListener("message", handleMessage); |
|
}); |
|
} |
|
|
|
const form = document.querySelector("#form"); |
|
const prompt = document.querySelector("#prompt"); |
|
const runBtn = document.querySelector("#run"); |
|
const modelSelect = document.querySelector("#model"); |
|
const dropArea = document.querySelector("#drop-area"); |
|
const canvas = document.querySelector("#canvas"); |
|
const ctxCanvas = canvas.getContext("2d"); |
|
const fileUpload = document.querySelector("#file-upload"); |
|
const clearImgBtn = document.querySelector("#clear-img-btn"); |
|
const imagesExamples = document.querySelector("#image-select"); |
|
|
|
let currentImageURL = null; |
|
let runController = new AbortController(); |
|
let isRunning = false; |
|
|
|
document.addEventListener("DOMContentLoaded", () => { |
|
for (const [id, model] of Object.entries(MODELS)) { |
|
const option = document.createElement("option"); |
|
option.value = id; |
|
option.innerText = `${id} (${model.size})`; |
|
modelSelect.appendChild(option); |
|
} |
|
const query = new URLSearchParams(window.location.search); |
|
const modelID = query.get("model"); |
|
if (modelID) { |
|
modelSelect.value = modelID; |
|
} else { |
|
modelSelect.value = "moondream2_q4k"; |
|
} |
|
}); |
|
|
|
imagesExamples.addEventListener("click", (e) => { |
|
|
|
|
|
|
|
const target = e.target; |
|
if (target.nodeName === "IMG") { |
|
const href = target.src; |
|
clearImageCanvas(); |
|
currentImageURL = href; |
|
drawImageCanvas(href); |
|
} |
|
}); |
|
modelSelect.addEventListener("change", (e) => { |
|
const query = new URLSearchParams(window.location.search); |
|
query.set("model", e.target.value); |
|
window.history.replaceState({}, "", `${window.location.pathname}?${query}`); |
|
window.parent.postMessage({ queryString: "?" + query }, "*"); |
|
const model = MODELS[e.target.value]; |
|
document.querySelector("#max-seq").max = model.seq_len; |
|
document.querySelector("#max-seq").nextElementSibling.value = 200; |
|
}); |
|
|
|
clearImgBtn.addEventListener("click", () => { |
|
clearImageCanvas(); |
|
}); |
|
|
|
|
|
fileUpload.addEventListener("input", async (e) => { |
|
const target = e.target; |
|
if (target.files.length > 0 && !target.files[0].type.includes("svg")) { |
|
const href = URL.createObjectURL(target.files[0]); |
|
clearImageCanvas(); |
|
await drawImageCanvas(href); |
|
} |
|
}); |
|
|
|
dropArea.addEventListener("dragenter", (e) => { |
|
e.preventDefault(); |
|
dropArea.classList.add("border-blue-700"); |
|
}); |
|
dropArea.addEventListener("dragleave", (e) => { |
|
e.preventDefault(); |
|
dropArea.classList.remove("border-blue-700"); |
|
}); |
|
dropArea.addEventListener("dragover", (e) => { |
|
e.preventDefault(); |
|
}); |
|
dropArea.addEventListener("drop", async (e) => { |
|
e.preventDefault(); |
|
dropArea.classList.remove("border-blue-700"); |
|
const url = e.dataTransfer.getData("text/uri-list"); |
|
const files = e.dataTransfer.files; |
|
if (files.length > 0) { |
|
const href = URL.createObjectURL(files[0]); |
|
clearImageCanvas(); |
|
await drawImageCanvas(href); |
|
} else if (url) { |
|
clearImageCanvas(); |
|
await drawImageCanvas(url); |
|
} |
|
}); |
|
|
|
form.addEventListener("submit", async (e) => { |
|
e.preventDefault(); |
|
if (isRunning) { |
|
stopRunning(); |
|
} else { |
|
startRunning(); |
|
await generateSequence(runController); |
|
stopRunning(); |
|
} |
|
}); |
|
|
|
async function drawImageCanvas(imgURL) { |
|
if (!imgURL) { |
|
throw new Error("No image URL provided"); |
|
} |
|
return new Promise((resolve, reject) => { |
|
ctxCanvas.clearRect(0, 0, canvas.width, canvas.height); |
|
ctxCanvas.clearRect(0, 0, canvas.width, canvas.height); |
|
const img = new Image(); |
|
img.crossOrigin = "anonymous"; |
|
img.onload = () => { |
|
canvas.width = img.width; |
|
canvas.height = img.height; |
|
ctxCanvas.drawImage(img, 0, 0); |
|
clearImgBtn.disabled = false; |
|
resolve(img); |
|
}; |
|
img.src = imgURL; |
|
currentImageURL = imgURL; |
|
}); |
|
} |
|
|
|
function clearImageCanvas() { |
|
ctxCanvas.clearRect(0, 0, canvas.width, canvas.height); |
|
clearImgBtn.disabled = true; |
|
canvas.parentElement.style.height = "auto"; |
|
currentImageURL = null; |
|
canvas.width = 0; |
|
canvas.height = 0; |
|
} |
|
|
|
function startRunning() { |
|
isRunning = true; |
|
runBtn.textContent = "Stop"; |
|
prompt.disabled = true; |
|
} |
|
|
|
function stopRunning() { |
|
runController.abort(); |
|
runController = new AbortController(); |
|
runBtn.textContent = "Run"; |
|
isRunning = false; |
|
prompt.disabled = false; |
|
} |
|
|
|
prompt.addEventListener("input", (e) => { |
|
runBtn.disabled = false; |
|
}); |
|
|