fix(ui): add disabled state and feedback to usage tips Add button

The Add button silently returned when no parameter or value was
provided, looking clickable but doing nothing. Keep it disabled until
both inputs are filled, validate the numeric value, surface save
failures via toast without clearing user input, and confirm additions
vs overwrites with success toasts. Includes translations for all
locales.
This commit is contained in:
Will Miao
2026-09-02 23:15:23 +08:00
parent 77109b3cf8
commit 7c4c8b8f30
12 changed files with 95 additions and 15 deletions
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "Wert",
"add": "Hinzufügen",
"invalidRange": "Ungültiges Bereichsformat. Verwenden Sie x.x-y.y"
"invalidRange": "Ungültiges Bereichsformat. Verwenden Sie x.x-y.y",
"invalidValue": "Bitte geben Sie eine gültige Zahl ein",
"saveFailed": "Fehler beim Speichern des voreingestellten Parameters",
"added": "Voreingestellter Parameter hinzugefügt",
"updated": "Voreingestellter Parameter aktualisiert"
},
"triggerWords": {
"label": "Trigger Words",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "Value",
"add": "Add",
"invalidRange": "Invalid range format. Use x.x-y.y"
"invalidRange": "Invalid range format. Use x.x-y.y",
"invalidValue": "Please enter a valid number",
"saveFailed": "Failed to save preset parameter",
"added": "Preset parameter added",
"updated": "Preset parameter updated"
},
"triggerWords": {
"label": "Trigger Words",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "Valor",
"add": "Añadir",
"invalidRange": "Formato de rango inválido. Use x.x-y.y"
"invalidRange": "Formato de rango inválido. Use x.x-y.y",
"invalidValue": "Introduce un número válido",
"saveFailed": "Error al guardar el parámetro preajustado",
"added": "Parámetro preajustado añadido",
"updated": "Parámetro preajustado actualizado"
},
"triggerWords": {
"label": "Palabras clave",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "Valeur",
"add": "Ajouter",
"invalidRange": "Format de plage invalide. Utilisez x.x-y.y"
"invalidRange": "Format de plage invalide. Utilisez x.x-y.y",
"invalidValue": "Veuillez saisir un nombre valide",
"saveFailed": "Échec de l'enregistrement du paramètre préréglé",
"added": "Paramètre préréglé ajouté",
"updated": "Paramètre préréglé mis à jour"
},
"triggerWords": {
"label": "Mots-clés",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "ערך",
"add": "הוסף",
"invalidRange": "פורמט טווח לא תקין. השתמש ב-x.x-y.y"
"invalidRange": "פורמט טווח לא תקין. השתמש ב-x.x-y.y",
"invalidValue": "נא להזין מספר תקין",
"saveFailed": "שמירת הפרמטר הקבוע מראש נכשלה",
"added": "הפרמטר הקבוע מראש נוסף",
"updated": "הפרמטר הקבוע מראש עודכן"
},
"triggerWords": {
"label": "מילות טריגר",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "値",
"add": "追加",
"invalidRange": "無効な範囲形式です。x.x-y.y を使用してください"
"invalidRange": "無効な範囲形式です。x.x-y.y を使用してください",
"invalidValue": "有効な数値を入力してください",
"saveFailed": "プリセットパラメータの保存に失敗しました",
"added": "プリセットパラメータを追加しました",
"updated": "プリセットパラメータを更新しました"
},
"triggerWords": {
"label": "トリガーワード",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "클립 스킵",
"valuePlaceholder": "값",
"add": "추가",
"invalidRange": "잘못된 범위 형식입니다. x.x-y.y를 사용하세요"
"invalidRange": "잘못된 범위 형식입니다. x.x-y.y를 사용하세요",
"invalidValue": "유효한 숫자를 입력하세요",
"saveFailed": "프리셋 매개변수 저장에 실패했습니다",
"added": "프리셋 매개변수가 추가되었습니다",
"updated": "프리셋 매개변수가 업데이트되었습니다"
},
"triggerWords": {
"label": "트리거 단어",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "Значение",
"add": "Добавить",
"invalidRange": "Неверный формат диапазона. Используйте x.x-y.y"
"invalidRange": "Неверный формат диапазона. Используйте x.x-y.y",
"invalidValue": "Введите корректное число",
"saveFailed": "Не удалось сохранить предустановленный параметр",
"added": "Предустановленный параметр добавлен",
"updated": "Предустановленный параметр обновлён"
},
"triggerWords": {
"label": "Триггерные слова",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "数值",
"add": "添加",
"invalidRange": "无效的范围格式。请使用 x.x-y.y"
"invalidRange": "无效的范围格式。请使用 x.x-y.y",
"invalidValue": "请输入有效的数值",
"saveFailed": "保存预设参数失败",
"added": "已添加预设参数",
"updated": "已更新预设参数"
},
"triggerWords": {
"label": "触发词",
+5 -1
View File
@@ -1593,7 +1593,11 @@
"clipSkip": "Clip Skip",
"valuePlaceholder": "數值",
"add": "新增",
"invalidRange": "無效的範圍格式。請使用 x.x-y.y"
"invalidRange": "無效的範圍格式。請使用 x.x-y.y",
"invalidValue": "請輸入有效的數值",
"saveFailed": "儲存預設參數失敗",
"added": "已新增預設參數",
"updated": "已更新預設參數"
},
"triggerWords": {
"label": "觸發詞",
@@ -66,3 +66,12 @@
.add-preset-btn:hover {
opacity: 0.9;
}
.add-preset-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.add-preset-btn:hover:disabled {
opacity: 0.5;
}
+36 -5
View File
@@ -877,8 +877,9 @@ function renderLoraSpecificContent(lora, escapedWords) {
<option value="clip_strength">${translate('modals.model.usageTips.clipStrength', {}, 'Clip Strength')}</option>
<option value="clip_skip">${translate('modals.model.usageTips.clipSkip', {}, 'Clip Skip')}</option>
</select>
<input type="number" id="preset-value" step="0.01" placeholder="${translate('modals.model.usageTips.valuePlaceholder', {}, 'Value')}" style="display:none;">
<button class="add-preset-btn">${translate('modals.model.usageTips.add', {}, 'Add')}</button>
<!-- autofill opt-out attrs prevent password managers / email-alias extensions from attaching popups -->
<input type="number" id="preset-value" step="0.01" placeholder="${translate('modals.model.usageTips.valuePlaceholder', {}, 'Value')}" style="display:none;" autocomplete="off" data-1p-ignore data-lpignore="true" data-bwignore data-form-type="other">
<button class="add-preset-btn" disabled>${translate('modals.model.usageTips.add', {}, 'Add')}</button>
</div>
<div class="preset-tags">
${renderPresetTags(parsePresets(lora.usage_tips))}
@@ -1086,6 +1087,11 @@ function setupLoraSpecificFields(filePath) {
if (!presetSelector || !presetValue || !addPresetBtn || !presetTags) return;
// Add button stays disabled until both a parameter and a value are provided
const updateAddPresetButtonState = () => {
addPresetBtn.disabled = !(presetSelector.value && presetValue.value.trim());
};
presetSelector.addEventListener('change', function () {
const selected = this.value;
if (selected) {
@@ -1111,12 +1117,16 @@ function setupLoraSpecificFields(filePath) {
} else {
presetValue.style.display = 'none';
}
updateAddPresetButtonState();
});
presetValue.addEventListener('input', updateAddPresetButtonState);
addPresetBtn.addEventListener('click', async function () {
const key = presetSelector.value;
const value = presetValue.value;
const value = presetValue.value.trim();
// Unreachable via UI while the button is disabled; kept as a safety net
if (!key || !value) return;
const currentPath = resolveFilePath();
@@ -1131,9 +1141,11 @@ function setupLoraSpecificFields(filePath) {
document.querySelector(`.model-card[data-filepath="${escapedFilePath}"]`);
const currentPresets = parsePresets(loraCard?.dataset.usage_tips);
let isUpdate;
if (key === 'strength_range') {
const rangeMatch = value.match(/^(-?\d*\.?\d+)\s*[-~]\s*(-?\d*\.?\d+)$/);
if (rangeMatch) {
isUpdate = 'strength_min' in currentPresets || 'strength_max' in currentPresets;
currentPresets['strength_min'] = parseFloat(rangeMatch[1]);
currentPresets['strength_max'] = parseFloat(rangeMatch[2]);
} else {
@@ -1141,17 +1153,36 @@ function setupLoraSpecificFields(filePath) {
return;
}
} else {
currentPresets[key] = parseFloat(value);
const numericValue = parseFloat(value);
if (!Number.isFinite(numericValue)) {
showToast('modals.model.usageTips.invalidValue', {}, 'error', 'Please enter a valid number');
return;
}
isUpdate = key in currentPresets;
currentPresets[key] = numericValue;
}
const newPresetsJson = JSON.stringify(currentPresets);
await getModelApiClient().saveModelMetadata(currentPath, { usage_tips: newPresetsJson });
try {
await getModelApiClient().saveModelMetadata(currentPath, { usage_tips: newPresetsJson });
} catch (error) {
console.error('Failed to save preset parameter:', error);
showToast('modals.model.usageTips.saveFailed', {}, 'error', 'Failed to save preset parameter');
return;
}
presetTags.innerHTML = renderPresetTags(currentPresets);
showToast(
isUpdate ? 'modals.model.usageTips.updated' : 'modals.model.usageTips.added',
{},
'success',
isUpdate ? 'Preset parameter updated' : 'Preset parameter added'
);
presetSelector.value = '';
presetValue.value = '';
presetValue.style.display = 'none';
addPresetBtn.disabled = true;
});
// Add keydown event for preset value