diff options
| author | Michael Peter Christen <mc@yacy.net> | 2025-11-07 01:57:05 +0100 |
|---|---|---|
| committer | Michael Peter Christen <mc@yacy.net> | 2025-11-07 01:57:05 +0100 |
| commit | fa037b467f183fcc74dcbad2d5b0663dfca8f2cf (patch) | |
| tree | 9d447c3f19ea9c855f392fb14c2e49d07104271d | |
| parent | 2b9d6413a7340f91b84694edc008ed2d784ea8ae (diff) | |
refactoring - both model lists now tables
| -rw-r--r-- | htroot/LLMSelection_p.html | 228 |
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> |
