summaryrefslogtreecommitdiff
path: root/htroot/LLMSelection_p.html
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2025-11-07 01:57:05 +0100
committerMichael Peter Christen <mc@yacy.net>2025-11-07 01:57:05 +0100
commitfa037b467f183fcc74dcbad2d5b0663dfca8f2cf (patch)
tree9d447c3f19ea9c855f392fb14c2e49d07104271d /htroot/LLMSelection_p.html
parent2b9d6413a7340f91b84694edc008ed2d784ea8ae (diff)
refactoring - both model lists now tables
Diffstat (limited to 'htroot/LLMSelection_p.html')
-rw-r--r--htroot/LLMSelection_p.html228
1 files changed, 121 insertions, 107 deletions
diff --git a/htroot/LLMSelection_p.html b/htroot/LLMSelection_p.html
index b3d4f53cd..cc183b571 100644
--- a/htroot/LLMSelection_p.html
+++ b/htroot/LLMSelection_p.html
@@ -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";
+ }
+
+ 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;
+ }
- loadModelContainer.appendChild(table);
- loadModelContainer.style.display = "block";
+ 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>