263 lines
7.5 KiB
JavaScript
263 lines
7.5 KiB
JavaScript
import snarkdown from "https://cdn.skypack.dev/snarkdown";
|
|
import hljs from "https://cdn.skypack.dev/highlight.js";
|
|
// models base url
|
|
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) => {
|
|
// if (isEmbedding || isSegmenting) {
|
|
// return;
|
|
// }
|
|
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();
|
|
});
|
|
|
|
//add event listener to file input
|
|
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);
|
|
}
|
|
});
|
|
// add event listener to drop-area
|
|
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;
|
|
});
|