reki / app.js
ythj's picture
Upload 4 files
bed1b7d verified
Raw History Blame Contribute Delete
3.2 kB
import { LlamaCppWasm } from "https://esm.sh/llama-cpp-wasm@0.0.4";
const C = window.GGUF_CONFIG;
const $ = id => document.getElementById(id);
let engine = null;
let active = false;
function status(s) {
$("status").textContent = s;
}
function info(s) {
$("info").textContent = s;
}
function progress(v) {
$("bar").style.width = Math.max(0, Math.min(100, v)) + "%";
}
async function createEngine() {
if (engine) return engine;
engine = await LlamaCppWasm.create({
moduleUrl: new URL("https://esm.sh/llama-cpp-wasm@0.0.3/wasm/llama-cpp-wasm.js").href,
wasmUrl: new URL("https://esm.sh/llama-cpp-wasm@0.0.3/wasm/llama-cpp-wasm.wasm").href
});
return engine;
}
async function loadModel(source) {
$("load").disabled = true;
status("loading runtime");
info("Starting WebAssembly runtime…");
progress(0);
const e = await createEngine();
status("downloading model");
info("Downloading GGUF into browser memory…");
await e.load(source, {
contextSize: C.contextSize,
batchSize: C.batchSize,
threads: C.threads
}, p => {
if (p && p.totalBytes) {
progress((p.loadedBytes / p.totalBytes) * 100);
}
});
progress(100);
status("ready");
info("Model loaded locally in this browser.");
$("send").disabled = false;
$("load").disabled = false;
}
$("load").onclick = async () => {
try {
const f = $("file").files[0];
await loadModel(f ? {file: f} : {url: C.modelUrl});
} catch (e) {
console.error(e);
status("error");
info(String(e?.message || e));
$("load").disabled = false;
}
};
$("file").onchange = () => {
if ($("file").files[0]) info("Selected local GGUF: " + $("file").files[0].name);
};
$("send").onclick = async () => {
if (!engine || active) return;
active = true;
$("send").disabled = true;
$("stop").disabled = false;
$("output").textContent = "";
const temperature = Number($("temp").value) || C.defaultTemperature;
const maxTokens = Number($("max").value) || C.defaultMaxTokens;
const prompt = $("prompt").value;
try {
for await (const text of engine.completion(prompt, {
maxTokens,
temperature,
topK: C.defaultTopK,
topP: C.defaultTopP
})) {
$("output").textContent += text;
}
} catch (e) {
if (!String(e?.message || e).toLowerCase().includes("cancel")) {
$("output").textContent += "\n\n[ERROR] " + String(e?.message || e);
}
} finally {
active = false;
$("send").disabled = false;
$("stop").disabled = true;
}
};
$("stop").onclick = () => {
try { engine?.cancel(); } catch {}
};
window.MyGGUFAPI = {
async chat(prompt, options = {}) {
if (!engine) throw new Error("Model is not loaded.");
let text = "";
for await (const chunk of engine.completion(String(prompt), {
maxTokens: options.maxTokens ?? C.defaultMaxTokens,
temperature: options.temperature ?? C.defaultTemperature,
topK: options.topK ?? C.defaultTopK,
topP: options.topP ?? C.defaultTopP,
seed: options.seed
})) text += chunk;
return {
text,
model: C.modelName
};
},
model() {
return C.modelName;
},
loaded() {
return !!engine;
}
};