distinguish between usage columns and feature columns, added tooling

This commit is contained in:
Michael Peter Christen committed 2025-11-11 23:15:40 +01:00
1 parent 576d3b2657
commit 0887619a1f
2 files changed
+44 -59

No files matched your search

+42 -59
View File
@@ -17,10 +17,12 @@
event.preventDefault();
event.returnValue = "Model downloads are still running. Please wait until they finish.";
};
const PRODUCTION_MODEL_TOTAL_COLUMNS = 13;
const PRODUCTION_MODEL_TOTAL_COLUMNS = 14;
const PRODUCTION_MODEL_MODEL_COLUMN_INDEX = 1;
const PRODUCTION_MODEL_FEATURE_COLUMN_START = 5;
const PRODUCTION_MODEL_FEATURE_COLUMN_END = 11;
const PRODUCTION_MODEL_USAGE_COLUMN_START = 5;
const PRODUCTION_MODEL_USAGE_COLUMN_END = 10; // including
const PRODUCTION_MODEL_FEATURE_COLUMN_START = 11;
const PRODUCTION_MODEL_FEATURE_COLUMN_END = 12; // including
const PRODUCTION_MODEL_ACTION_COLUMN_INDEX = PRODUCTION_MODEL_TOTAL_COLUMNS - 1;
const PRODUCTION_MODEL_COLUMN_NAMES = [
"service",
@@ -34,6 +36,7 @@
"qa-generation",
"classification",
"tldr-shortener",
"tooling",
"vision"
];
const PRODUCTION_MODEL_SUBMIT_URL = "LLMSelection_p.html";
@@ -79,10 +82,6 @@
return response.json();
}
async function fetchOllamaModels(hoststub) {
return fetchJsonOrThrow(`${hoststub}/api/tags`);
}
async function deleteOllamaModel(hoststub, modelName) {
const response = await fetch(`${hoststub}/api/delete`, {
method: "DELETE",
@@ -117,14 +116,8 @@
return payload;
}
async function fetchOpenAICompatibleModels(hoststub) {
return fetchJsonOrThrow(`${hoststub}/v1/models`);
}
async function requestModelsForService(service, hoststub) {
return service === "OLLAMA"
? fetchOllamaModels(hoststub)
: fetchOpenAICompatibleModels(hoststub);
return service === "OLLAMA" ? fetchJsonOrThrow(`${hoststub}/api/tags`) : fetchJsonOrThrow(`${hoststub}/v1/models`);
}
function handleModelLoadError(service, error) {
@@ -278,7 +271,7 @@
async function handleModelDelete(modelName, deleteButton) {
if (!modelName) return;
if (isModelInProduction(modelName)) {
if (getProductionModelNames().has(modelName)) {
alert(`Model "${modelName}" is currently used in the production table and cannot be deleted.`);
updateAvailableModelButtons();
return;
@@ -450,11 +443,6 @@
return modelNames;
}
function isModelInProduction(modelName) {
if (!modelName) return false;
return getProductionModelNames().has(modelName);
}
function updateAvailableModelButtons() {
const productionModels = getProductionModelNames();
document.querySelectorAll('button[data-action="delete-model"]').forEach(button => {
@@ -572,8 +560,7 @@
}
});
const shouldEnableFeatures = !hadRowsBeforeInsert;
ensureProductionRowFeatureCells(targetRow, shouldEnableFeatures);
ensureProductionRowUsageCells(targetRow, !hadRowsBeforeInsert);
ensureProductionRowActionButton(targetRow);
persistProductionModels();
}
@@ -591,15 +578,16 @@
for (let i = 0; i < missingCells; i += 1) {
row.appendChild(document.createElement("td"));
}
ensureProductionRowFeatureCells(row, false);
ensureProductionRowUsageCells(row, false);
ensureProductionRowActionButton(row);
});
updateAvailableModelButtons();
}
function ensureProductionRowFeatureCells(row, defaultChecked) {
function ensureProductionRowUsageCells(row, defaultChecked) {
if (!row) return;
for (let col = PRODUCTION_MODEL_FEATURE_COLUMN_START; col <= PRODUCTION_MODEL_FEATURE_COLUMN_END; col += 1) {
// ensure existence of checkboxes
for (let col = PRODUCTION_MODEL_USAGE_COLUMN_START; col <= PRODUCTION_MODEL_FEATURE_COLUMN_END; col += 1) {
const cell = row.cells[col];
if (!cell) continue;
let checkbox = cell.querySelector('input[type="checkbox"]');
@@ -609,28 +597,40 @@
cell.textContent = "";
cell.appendChild(checkbox);
checkbox.checked = !!defaultChecked;
if (col >= PRODUCTION_MODEL_FEATURE_COLUMN_START) {
// disable the checkbox
checkbox.disabled = true
}
}
initializeFeatureCheckbox(checkbox, col);
initializeUsageCheckbox(checkbox, col);
}
if (defaultChecked) {
enforceFeatureExclusivityForRow(row);
// enforce feature exclusivity for row
if (!row) return;
for (let col = PRODUCTION_MODEL_USAGE_COLUMN_START; col <= PRODUCTION_MODEL_USAGE_COLUMN_END; col += 1) {
const cell = row.cells[col];
if (!cell) continue;
const checkbox = cell.querySelector('input[type="checkbox"]');
if (!checkbox || !checkbox.checked) continue;
handleUsageCheckboxToggle(col, checkbox);
}
}
updateUndeployButtonState(row);
}
function initializeFeatureCheckbox(checkbox, columnIndex) {
function initializeUsageCheckbox(checkbox, col) {
if (!checkbox) return;
checkbox.dataset.featureColumn = String(columnIndex);
checkbox.dataset.featureColumn = String(col);
if (checkbox.dataset.listenerAttached === "true") {
return;
}
checkbox.addEventListener("change", event => {
handleFeatureCheckboxToggle(columnIndex, event.currentTarget);
handleUsageCheckboxToggle(col, event.currentTarget);
});
checkbox.dataset.listenerAttached = "true";
}
function handleFeatureCheckboxToggle(columnIndex, checkbox) {
function handleUsageCheckboxToggle(columnIndex, checkbox) {
if (!checkbox) return;
const tbody = getProductionTableBody();
if (!tbody) return;
@@ -655,17 +655,6 @@
persistProductionModels();
}
function enforceFeatureExclusivityForRow(row) {
if (!row) return;
for (let col = PRODUCTION_MODEL_FEATURE_COLUMN_START; col <= PRODUCTION_MODEL_FEATURE_COLUMN_END; col += 1) {
const cell = row.cells[col];
if (!cell) continue;
const checkbox = cell.querySelector('input[type="checkbox"]');
if (!checkbox || !checkbox.checked) continue;
handleFeatureCheckboxToggle(col, checkbox);
}
}
function ensureProductionRowActionButton(row) {
if (!row || row.cells.length < PRODUCTION_MODEL_TOTAL_COLUMNS) {
return;
@@ -691,23 +680,18 @@
if (!row) return;
const button = row.querySelector('button[data-action="undeploy"]');
if (!button) return;
const hasFeatureChecked = isAnyFeatureCheckboxChecked(row);
button.disabled = hasFeatureChecked;
button.title = hasFeatureChecked
? "Disable the assigned features before undeploying."
: "";
}
function isAnyFeatureCheckboxChecked(row) {
for (let col = PRODUCTION_MODEL_FEATURE_COLUMN_START; col <= PRODUCTION_MODEL_FEATURE_COLUMN_END; col += 1) {
hasFeatureChecked = false;
for (let col = PRODUCTION_MODEL_USAGE_COLUMN_START; col <= PRODUCTION_MODEL_USAGE_COLUMN_END; col += 1) {
const cell = row.cells[col];
if (!cell) continue;
const checkbox = cell.querySelector('input[type="checkbox"]');
if (checkbox && checkbox.checked) {
return true;
hasFeatureChecked = true;
break;
}
}
return false;
button.disabled = hasFeatureChecked;
button.title = hasFeatureChecked ? "Disable the assigned features before undeploying." : "";
}
function persistProductionModels() {
@@ -721,10 +705,8 @@
}
const rowData = {};
PRODUCTION_MODEL_COLUMN_NAMES.forEach((columnName, index) => {
if (index >= PRODUCTION_MODEL_FEATURE_COLUMN_START && index <= PRODUCTION_MODEL_FEATURE_COLUMN_END) {
const checkbox = row.cells[index]
? row.cells[index].querySelector('input[type="checkbox"]')
: null;
if (index >= PRODUCTION_MODEL_USAGE_COLUMN_START && index <= PRODUCTION_MODEL_FEATURE_COLUMN_END) {
const checkbox = row.cells[index] ? row.cells[index].querySelector('input[type="checkbox"]') : null;
rowData[columnName] = checkbox ? checkbox.checked : false;
} else {
rowData[columnName] = row.cells[index] ? row.cells[index].textContent.trim() : "";
@@ -845,7 +827,7 @@
<fieldset id="productionModelsContainer" style="display: block;"><legend>Production Models</legend>
<table class="table table-striped" id="productionModelsTable">
<thead class="thead-dark">
<tr><td>service</td><td>model</td><td>hoststub</td><td>api_key</td><td>max_tokens</td><td>answers</td><td>chat</td><td>translation</td><td>qa-generation</td><td>classification</td><td>shortener</td><td>vision</td><td>Actions</td></tr>
<tr><td>service</td><td>model</td><td>hoststub</td><td>api_key</td><td>max_tokens</td><td>answers</td><td>chat</td><td>translation</td><td>qa-generation</td><td>classification</td><td>shortener</td><td><i>tooling</i></td><td><i>vision</i></td><td>Actions</td></tr>
</thead>
<tbody>
#{productionmodels}#
@@ -857,7 +839,8 @@
<td><input type="checkbox" #(qa-generation)#::checked=true#(/qa-generation)#></td>
<td><input type="checkbox" #(classification)#::checked=true#(/classification)#></td>
<td><input type="checkbox" #(tldr-shortener)#::checked=true#(/tldr-shortener)#></td>
<td><input type="checkbox" #(vision)#::checked=true#(/vision)#></td>
<td><input type="checkbox" #(tooling)#::checked=true#(/tooling)# disabled=true></td>
<td><input type="checkbox" #(vision)#::checked=true#(/vision)# disabled=true></td>
<td></td>
</tr>
#{/productionmodels}#
@@ -71,6 +71,7 @@ public class LLMSelection_p {
"qa-generation":true,
"classification":true,
"tldr-shortener":true,
"tooling":true,
"vision":true
}]}
*/
@@ -92,6 +93,7 @@ public class LLMSelection_p {
prop.put("productionmodels_" + i + "_qa-generation", row.optBoolean("qa-generation", false));
prop.put("productionmodels_" + i + "_classification", row.optBoolean("classification", false));
prop.put("productionmodels_" + i + "_tldr-shortener", row.optBoolean("tldr-shortener", false));
prop.put("productionmodels_" + i + "_tooling", row.optBoolean("tooling", false));
prop.put("productionmodels_" + i + "_vision", row.optBoolean("vision", false));
}
prop.put("productionmodels", production_models.length());