refactoring - both model lists now tables

This commit is contained in:
Michael Peter Christen committed 2025-11-07 01:57:05 +01:00
1 parent 2b9d6413a7
commit fa037b467f
1 file changed
+121 -107
+121 -107
View File
@@ -35,7 +35,15 @@
["qwen3-vl:30b-a3b-instruct-q4_K_M", "17.33", "22GB", "Very fast, exceptional good 30B model, ranking above GPT-4-turbo, GPT-4.1-nano, GPT-o1, GPT-4o-mini", "Alibaba", "apache-2.0"]
];
const MODEL_TABLE_HEADERS = ["Model", "Ranking", "Size", "Description", "Provider", "License", "Actions"];
const RECOMMENDED_MODEL_MAP = new Map(
RECOMMENDED_MODELS.map(([name, ranking, size, description, provider, license]) => [
name,
{ ranking, size, description, provider, license }
])
);
/***
*** API functions to access Ollama or OpenAI endpoints (list/load/delete models)
***/
@@ -284,83 +292,64 @@
}
function renderAvailableModels(provider, payload) {
// called when the model list was loaded
const availableModelsContainer = document.getElementById("availableModelsContainer");
if (!availableModelsContainer) return;
availableModelsContainer.innerHTML = "<legend>Available Models</legend>";
availableModelsContainer.style.display = "none";
const container = document.getElementById("availableModelsContainer");
if (!container) return;
const models = provider === "OLLAMA" ? (payload.models || []) : (payload.data || []);
const getId = provider === "OLLAMA" ? m => m.model : m => m.id;
const frag = document.createDocumentFragment();
const table = document.createElement("table");
table.className = "table table-striped";
const thead = document.createElement("thead");
thead.className = "thead-dark";
const headerRow = document.createElement("tr");
["Model", "Actions"].forEach(h => {
const th = document.createElement("th");
th.textContent = h;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
const tbody = document.createElement("tbody");
const rows = [];
models.forEach(m => {
const id = getId(m);
if (!id) return;
availableModels.push(id);
const tr = document.createElement("tr");
const tdMain = document.createElement("td");
tdMain.textContent = id;
tr.appendChild(tdMain);
const actionsTd = document.createElement("td");
const deleteBtn = document.createElement("button");
deleteBtn.type = "button";
deleteBtn.className = "btn btn-danger btn-sm";
deleteBtn.textContent = "Delete";
deleteBtn.style.padding = "2px 8px";
deleteBtn.style.lineHeight = "1.2";
if (provider === "OLLAMA") {
deleteBtn.addEventListener("click", () => handleModelDelete(id, deleteBtn));
} else {
deleteBtn.disabled = true;
deleteBtn.title = "Model management is only supported for Ollama.";
}
actionsTd.appendChild(deleteBtn);
tr.appendChild(actionsTd);
tbody.appendChild(tr);
const info = getRecommendedModelInfo(id) || {};
rows.push({
model: id,
ranking: info.ranking || "",
size: info.size || "",
description: info.description || "",
provider: info.provider || "",
license: info.license || "",
renderActions: () => createDeleteButton(provider, id)
});
});
if (tbody.hasChildNodes()) {
table.appendChild(tbody);
frag.appendChild(table);
}
const hasModels = tbody.hasChildNodes();
availableModelsContainer.appendChild(frag);
availableModelsContainer.style.display = hasModels ? "block" : "none";
renderModelTable(container, "Available Models", rows);
}
function renderRecommendedModels(hoststub) {
// called when the model list was loaded and the diff to the recommended model list is displayed
const loadModelContainer = document.getElementById("loadModelContainer");
if (!loadModelContainer) return;
loadModelContainer.innerHTML = "<legend>Recommended Models</legend>";
loadModelContainer.style.display = "none";
const downloadableModels = RECOMMENDED_MODELS.filter(m => m && !availableModels.includes(m[0]));
if (!downloadableModels.length) return;
const rows = downloadableModels.map(m => {
const info = getRecommendedModelInfo(m[0]) || {};
return {
model: m[0],
ranking: info.ranking || "",
size: info.size || "",
description: info.description || "",
provider: info.provider || "",
license: info.license || "",
renderActions: () => createDownloadButton(hoststub, m[0])
};
});
renderModelTable(loadModelContainer, "Recommended Models", rows);
}
function getRecommendedModelInfo(modelName) {
return RECOMMENDED_MODEL_MAP.get(modelName) || null;
}
function renderModelTable(container, title, rows) {
if (!container) return;
container.innerHTML = `<legend>${title}</legend>`;
if (!rows || !rows.length) {
container.style.display = "none";
return;
}
const table = document.createElement("table");
table.className = "table table-striped";
@@ -368,7 +357,7 @@
const thead = document.createElement("thead");
thead.className = "thead-dark";
const headerRow = document.createElement("tr");
["Model", "Ranking", "Size", "Description", "Provider", "License", "Actions"].forEach(h => {
MODEL_TABLE_HEADERS.forEach(h => {
const th = document.createElement("th");
th.textContent = h;
headerRow.appendChild(th);
@@ -376,63 +365,88 @@
thead.appendChild(headerRow);
table.appendChild(thead);
const frag = document.createDocumentFragment();
downloadableModels.forEach(m => {
const modelName = m[0];
const tbody = document.createElement("tbody");
rows.forEach(row => {
const tr = document.createElement("tr");
const tdMain = document.createElement("td");
tdMain.textContent = modelName;
tr.appendChild(tdMain);
[m[1], m[2], m[3], m[4], m[5]].forEach(text => {
const columnValues = [
row.model || "",
row.ranking || "",
row.size || "",
row.description || "",
row.provider || "",
row.license || ""
];
columnValues.forEach(value => {
const td = document.createElement("td");
td.textContent = text;
td.textContent = value;
tr.appendChild(td);
});
const actionsTd = document.createElement("td");
const downloadBtn = document.createElement("button");
downloadBtn.type = "button";
downloadBtn.className = "btn btn-primary btn-sm";
downloadBtn.textContent = "Download";
downloadBtn.addEventListener("click", async () => {
const activityId = addDownloadActivity(modelName);
downloadBtn.disabled = true;
try {
await downloadOllamaModel(hoststub, modelName);
console.log(`Model ${modelName} is now available on server ${hoststub}.`);
} catch (err) {
const status = err && typeof err.status === "number" ? err.status : null;
const message = status
? `Failed to download model ${modelName}. HTTP status: ${status}`
: `Error pulling model ${modelName} from server ${hoststub}. Check the console for details.`;
alert(message);
console.error("Error during model pull request:", err);
} finally {
downloadBtn.disabled = false;
if (activityId) {
removeDownloadActivity(activityId);
}
try {
await loadModelList();
} catch (refreshError) {
console.error("Failed to refresh models after download:", refreshError);
}
if (typeof row.renderActions === "function") {
const actionContent = row.renderActions();
if (Array.isArray(actionContent)) {
actionContent.forEach(node => node && actionsTd.appendChild(node));
} else if (actionContent instanceof Node) {
actionsTd.appendChild(actionContent);
}
});
actionsTd.appendChild(downloadBtn);
}
tr.appendChild(actionsTd);
frag.appendChild(tr);
tbody.appendChild(tr);
});
const tbody = document.createElement("tbody");
tbody.appendChild(frag);
table.appendChild(tbody);
container.appendChild(table);
container.style.display = "block";
}
loadModelContainer.appendChild(table);
loadModelContainer.style.display = "block";
function createDeleteButton(provider, modelId) {
const deleteBtn = document.createElement("button");
deleteBtn.type = "button";
deleteBtn.className = "btn btn-danger btn-sm";
deleteBtn.textContent = "Delete";
deleteBtn.style.padding = "2px 8px";
deleteBtn.style.lineHeight = "1.2";
if (provider === "OLLAMA") {
deleteBtn.addEventListener("click", () => handleModelDelete(modelId, deleteBtn));
} else {
deleteBtn.disabled = true;
deleteBtn.title = "Model management is only supported for Ollama.";
}
return deleteBtn;
}
function createDownloadButton(hoststub, modelName) {
const downloadBtn = document.createElement("button");
downloadBtn.type = "button";
downloadBtn.className = "btn btn-primary btn-sm";
downloadBtn.textContent = "Download";
downloadBtn.addEventListener("click", async () => {
const activityId = addDownloadActivity(modelName);
downloadBtn.disabled = true;
try {
await downloadOllamaModel(hoststub, modelName);
console.log(`Model ${modelName} is now available on server ${hoststub}.`);
} catch (err) {
const status = err && typeof err.status === "number" ? err.status : null;
const message = status
? `Failed to download model ${modelName}. HTTP status: ${status}`
: `Error pulling model ${modelName} from server ${hoststub}. Check the console for details.`;
alert(message);
console.error("Error during model pull request:", err);
} finally {
downloadBtn.disabled = false;
if (activityId) {
removeDownloadActivity(activityId);
}
try {
await loadModelList();
} catch (refreshError) {
console.error("Failed to refresh models after download:", refreshError);
}
}
});
return downloadBtn;
}
</script>