Sorry, but I am too busy and tired to dive into the code to do pull requests. So, I’m making this post.
Fix the misleading behavior
I have found two misleading behaviors with Llama.cpp.
- When we load a model with the specified parameters from the command line (llama-server), these parameters are not reflected in the UI.
- When we switch to another model, the old parameters in the UI are still applied, while we would expect the command-line parameters to be used. This behavior causes bad experiences, as the model can become very disappointing.
To “fix” these behaviors, I use a hack with the help of Tampermonkey. And after switching models, I usually do a “hard refresh” of the page (Ctrl+Shift+R or Cmd+Shift+R).
const props = fetch('/props')
.then((response) ...
Sorry, but I am too busy and tired to dive into the code to do pull requests. So, I’m making this post.
Fix the misleading behavior
I have found two misleading behaviors with Llama.cpp.
- When we load a model with the specified parameters from the command line (llama-server), these parameters are not reflected in the UI.
- When we switch to another model, the old parameters in the UI are still applied, while we would expect the command-line parameters to be used. This behavior causes bad experiences, as the model can become very disappointing.
To “fix” these behaviors, I use a hack with the help of Tampermonkey. And after switching models, I usually do a “hard refresh” of the page (Ctrl+Shift+R or Cmd+Shift+R).
const props = fetch('/props')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then((props) => {
console.log(props);
localStorage.setItem('config', JSON.stringify({
"apiKey":"",
"systemMessage":"You are a helpful assistant.",
"showTokensPerSecond":true,
"showThoughtInProgress":true,
"excludeThoughtOnReq":true,
"pasteLongTextToFileLen":15500,
"samplers":"edkypmxt",
"temperature":props['default_generation_settings']['params']['temperature'],
"dynatemp_range":0,
"dynatemp_exponent":1,
"top_k":props['default_generation_settings']['params']['top_k'],
"top_p":props['default_generation_settings']['params']['top_p'],
"min_p":props['default_generation_settings']['params']['min_p'],
"xtc_probability":0,
"xtc_threshold":0.1,
"typical_p":1,
"repeat_last_n":64,
"repeat_penalty":1,
"presence_penalty":1.5,
"frequency_penalty":0,
"dry_multiplier":0,
"dry_base":1.75,
"dry_allowed_length":2,
"dry_penalty_last_n":-1,
"max_tokens":-1,
"custom":"",
"pyIntepreterEnabled":false
}));
})
.catch((error) => {
console.error('Failed to fetch /props:', error);
});
Add import/export db feature
As the data are stored in the browser, it could be very useful to export / import easily from the UI the db.
Again, I use hacks with the help of Tampermonkey.
Export db
// import feature
// Create export button and add to DOM
let exportButton = document.createElement('BUTTON');
exportButton.classList.add("btn");
exportButton.textContent = "📤";
let divForButton = document.createElement('DIV');
divForButton.classList.add("tooltip", "tooltip-bottom");
divForButton.setAttribute("id", "export");
divForButton.dataset.tip = "Export";
divForButton.appendChild(exportButton);
const observer = new MutationObserver(() => {
const header = document.querySelector("header");
if (header) {
header.children[0].appendChild(divForButton);
document.getElementById('export').addEventListener('click', exportDb);
header.children[0].appendChild(input);
observer.disconnect(); // stop watching once done
}
});
observer.observe(document.body, { childList: true, subtree: true });
async function exportIndexedDB(dbName) {
const request = indexedDB.open(dbName);
return new Promise((resolve, reject) => {
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
const exportData = {};
const tx = db.transaction(db.objectStoreNames, "readonly");
const promises = [];
for (const storeName of db.objectStoreNames) {
const store = tx.objectStore(storeName);
const getAllRequest = store.getAll();
const p = new Promise((res, rej) => {
getAllRequest.onsuccess = () => {
exportData[storeName] = getAllRequest.result;
res();
};
getAllRequest.onerror = () => rej(getAllRequest.error);
});
promises.push(p);
}
tx.oncomplete = async () => {
try {
await Promise.all(promises);
resolve(exportData);
} catch (err) {
reject(err);
}
};
tx.onerror = () => reject(tx.error);
};
});
}
function exportDb(){
exportIndexedDB('LlamacppWebui').then(data => {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement("a"), {
href: url,
download: "indexeddb-export.json"
});
a.click();
URL.revokeObjectURL(url);
});
}
// end export
Import db
// import function
async function importIndexedDB(dbName, jsonData) {
const request = indexedDB.open(dbName);
return new Promise((resolve, reject) => {
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
const validStoreNames = Array.from(db.objectStoreNames).filter(name => name in jsonData);
const tx = db.transaction(validStoreNames, "readwrite");
for (const storeName of validStoreNames) {
const store = tx.objectStore(storeName);
for (const item of jsonData[storeName]) {
store.put(item);
}
}
tx.oncomplete = () => resolve("Import successful");
tx.onerror = () => reject(tx.error);
};
});
}
// Create file input and add to DOM
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json';
input.style.display = 'block'; // Make it visible for user interaction
// Handle file selection
input.addEventListener('change', () => {
const file = input.files[0];
const reader = new FileReader();
reader.onload = async () => {
const jsonData = JSON.parse(reader.result);
try {
const result = await importIndexedDB("LlamacppWebui", jsonData);
location.reload(); // Reload page after successful import
} catch (err) {
console.error("Import failed:", err);
}
};
reader.onerror = () => console.error("File read error:", reader.error);
reader.readAsText(file);
});
// end import
Here the full code:
// ==UserScript==
// @name llama.cpp server
// @namespace http://tampermonkey.net/
// @version 2025-09-19
// @description try to take over the world!
// @author You
// @match http://localhost:8090/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=undefined.localhost
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// import feature
// Create export button and add to DOM
let exportButton = document.createElement('BUTTON');
exportButton.classList.add("btn");
exportButton.textContent = "📤";
let divForButton = document.createElement('DIV');
divForButton.classList.add("tooltip", "tooltip-bottom");
divForButton.setAttribute("id", "export");
divForButton.dataset.tip = "Export";
divForButton.appendChild(exportButton);
const observer = new MutationObserver(() => {
const header = document.querySelector("header");
if (header) {
header.children[0].appendChild(divForButton);
document.getElementById('export').addEventListener('click', exportDb);
header.children[0].appendChild(input);
observer.disconnect(); // stop watching once done
}
});
observer.observe(document.body, { childList: true, subtree: true });
async function exportIndexedDB(dbName) {
const request = indexedDB.open(dbName);
return new Promise((resolve, reject) => {
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
const exportData = {};
const tx = db.transaction(db.objectStoreNames, "readonly");
const promises = [];
for (const storeName of db.objectStoreNames) {
const store = tx.objectStore(storeName);
const getAllRequest = store.getAll();
const p = new Promise((res, rej) => {
getAllRequest.onsuccess = () => {
exportData[storeName] = getAllRequest.result;
res();
};
getAllRequest.onerror = () => rej(getAllRequest.error);
});
promises.push(p);
}
tx.oncomplete = async () => {
try {
await Promise.all(promises);
resolve(exportData);
} catch (err) {
reject(err);
}
};
tx.onerror = () => reject(tx.error);
};
});
}
function exportDb(){
exportIndexedDB('LlamacppWebui').then(data => {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement("a"), {
href: url,
download: "indexeddb-export.json"
});
a.click();
URL.revokeObjectURL(url);
});
}
// end export
// import function
async function importIndexedDB(dbName, jsonData) {
const request = indexedDB.open(dbName);
return new Promise((resolve, reject) => {
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
const validStoreNames = Array.from(db.objectStoreNames).filter(name => name in jsonData);
const tx = db.transaction(validStoreNames, "readwrite");
for (const storeName of validStoreNames) {
const store = tx.objectStore(storeName);
for (const item of jsonData[storeName]) {
store.put(item);
}
}
tx.oncomplete = () => resolve("Import successful");
tx.onerror = () => reject(tx.error);
};
});
}
// Create file input and add to DOM
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json';
input.style.display = 'block'; // Make it visible for user interaction
// Handle file selection
input.addEventListener('change', () => {
const file = input.files[0];
const reader = new FileReader();
reader.onload = async () => {
const jsonData = JSON.parse(reader.result);
try {
const result = await importIndexedDB("LlamacppWebui", jsonData);
location.reload(); // Reload page after successful import
} catch (err) {
console.error("Import failed:", err);
}
};
reader.onerror = () => console.error("File read error:", reader.error);
reader.readAsText(file);
});
// end import
// Fetch model params from server
const props = fetch('/props')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then((props) => {
console.log(props);
localStorage.setItem('config', JSON.stringify({
"apiKey":"",
"systemMessage":"You are a helpful assistant.",
"showTokensPerSecond":true,
"showThoughtInProgress":true,
"excludeThoughtOnReq":true,
"pasteLongTextToFileLen":15500,
"samplers":"edkypmxt",
"temperature":props['default_generation_settings']['params']['temperature'],
"dynatemp_range":0,
"dynatemp_exponent":1,
"top_k":props['default_generation_settings']['params']['top_k'],
"top_p":props['default_generation_settings']['params']['top_p'],
"min_p":props['default_generation_settings']['params']['min_p'],
"xtc_probability":0,
"xtc_threshold":0.1,
"typical_p":1,
"repeat_last_n":64,
"repeat_penalty":1,
"presence_penalty":1.5,
"frequency_penalty":0,
"dry_multiplier":0,
"dry_base":1.75,
"dry_allowed_length":2,
"dry_penalty_last_n":-1,
"max_tokens":-1,
"custom":"",
"pyIntepreterEnabled":false
}));
})
.catch((error) => {
console.error('Failed to fetch /props:', error);
});
})();