diff --git a/locales/de.json b/locales/de.json index 0efd37ad..4a849568 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1180,6 +1180,7 @@ "editModelName": "Modellname bearbeiten", "editFileName": "Dateiname bearbeiten", "editBaseModel": "Basis-Modell bearbeiten", + "editVersionName": "Versionsname bearbeiten", "viewOnCivitai": "Auf Civitai anzeigen", "viewOnCivitaiText": "Auf Civitai anzeigen", "viewCreatorProfile": "Ersteller-Profil anzeigen", diff --git a/locales/en.json b/locales/en.json index b4255602..076725ae 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1180,6 +1180,7 @@ "editModelName": "Edit model name", "editFileName": "Edit file name", "editBaseModel": "Edit base model", + "editVersionName": "Edit version name", "viewOnCivitai": "View on Civitai", "viewOnCivitaiText": "View on Civitai", "viewCreatorProfile": "View Creator Profile", diff --git a/locales/es.json b/locales/es.json index a63ceb78..f8f42993 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1180,6 +1180,7 @@ "editModelName": "Editar nombre del modelo", "editFileName": "Editar nombre de archivo", "editBaseModel": "Editar modelo base", + "editVersionName": "Editar nombre de versión", "viewOnCivitai": "Ver en Civitai", "viewOnCivitaiText": "Ver en Civitai", "viewCreatorProfile": "Ver perfil del creador", diff --git a/locales/fr.json b/locales/fr.json index cd219f73..658ba92f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1180,6 +1180,7 @@ "editModelName": "Modifier le nom du modèle", "editFileName": "Modifier le nom de fichier", "editBaseModel": "Modifier le modèle de base", + "editVersionName": "Modifier le nom de la version", "viewOnCivitai": "Voir sur Civitai", "viewOnCivitaiText": "Voir sur Civitai", "viewCreatorProfile": "Voir le profil du créateur", diff --git a/locales/he.json b/locales/he.json index f52eec25..22dad233 100644 --- a/locales/he.json +++ b/locales/he.json @@ -1180,6 +1180,7 @@ "editModelName": "ערוך שם מודל", "editFileName": "ערוך שם קובץ", "editBaseModel": "ערוך מודל בסיס", + "editVersionName": "ערוך שם גרסה", "viewOnCivitai": "הצג ב-Civitai", "viewOnCivitaiText": "הצג ב-Civitai", "viewCreatorProfile": "הצג פרופיל יוצר", diff --git a/locales/ja.json b/locales/ja.json index d1f7d25e..1d6147af 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1180,6 +1180,7 @@ "editModelName": "モデル名を編集", "editFileName": "ファイル名を編集", "editBaseModel": "ベースモデルを編集", + "editVersionName": "バージョン名を編集", "viewOnCivitai": "Civitaiで表示", "viewOnCivitaiText": "Civitaiで表示", "viewCreatorProfile": "作成者プロフィールを表示", diff --git a/locales/ko.json b/locales/ko.json index 9b5d4d62..fb75a1fe 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1180,6 +1180,7 @@ "editModelName": "모델명 편집", "editFileName": "파일명 편집", "editBaseModel": "베이스 모델 편집", + "editVersionName": "버전명 편집", "viewOnCivitai": "Civitai에서 보기", "viewOnCivitaiText": "Civitai에서 보기", "viewCreatorProfile": "제작자 프로필 보기", diff --git a/locales/ru.json b/locales/ru.json index ddedadfe..a3a269af 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1180,6 +1180,7 @@ "editModelName": "Редактировать название модели", "editFileName": "Редактировать имя файла", "editBaseModel": "Редактировать базовую модель", + "editVersionName": "Редактировать название версии", "viewOnCivitai": "Посмотреть на Civitai", "viewOnCivitaiText": "Посмотреть на Civitai", "viewCreatorProfile": "Посмотреть профиль создателя", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index e9c92562..48a19f9d 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -1180,6 +1180,7 @@ "editModelName": "编辑模型名称", "editFileName": "编辑文件名", "editBaseModel": "编辑基础模型", + "editVersionName": "编辑版本名称", "viewOnCivitai": "在 Civitai 查看", "viewOnCivitaiText": "在 Civitai 查看", "viewCreatorProfile": "查看创作者主页", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index a601fc3c..1caf9f71 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1180,6 +1180,7 @@ "editModelName": "編輯模型名稱", "editFileName": "編輯檔案名稱", "editBaseModel": "編輯基礎模型", + "editVersionName": "編輯版本名稱", "viewOnCivitai": "在 Civitai 查看", "viewOnCivitaiText": "在 Civitai 查看", "viewCreatorProfile": "查看創作者個人檔案", diff --git a/static/css/components/lora-modal/lora-modal.css b/static/css/components/lora-modal/lora-modal.css index b64e83f5..51094319 100644 --- a/static/css/components/lora-modal/lora-modal.css +++ b/static/css/components/lora-modal/lora-modal.css @@ -255,25 +255,28 @@ transform: translateY(-2px); } -/* File name copy styles */ -.file-name-wrapper { +/* Editable inline field styles (file name, version name, etc.) */ +.file-name-wrapper, +.version-name-wrapper { display: flex; align-items: center; gap: 8px; - padding: 4px; + padding: 4px 0; border-radius: var(--border-radius-xs); transition: background-color 0.2s; position: relative; } -.file-name-content { - padding: 2px 4px; +.file-name-content, +.version-name-content { + padding: 2px 4px 2px 0; border-radius: var(--border-radius-xs); border: 1px solid transparent; flex: 1; } -.file-name-wrapper.editing .file-name-content { +.file-name-wrapper.editing .file-name-content, +.version-name-wrapper.editing .version-name-content { border: 1px solid var(--lora-accent); background: var(--bg-color); outline: none; @@ -283,7 +286,8 @@ .edit-model-name-btn, .edit-file-name-btn, .edit-base-model-btn, -.edit-model-description-btn { +.edit-model-description-btn, +.edit-version-name-btn { background: transparent; border: none; color: var(--text-color); @@ -299,9 +303,11 @@ .edit-file-name-btn.visible, .edit-base-model-btn.visible, .edit-model-description-btn.visible, +.edit-version-name-btn.visible, .model-name-header:hover .edit-model-name-btn, .file-name-wrapper:hover .edit-file-name-btn, .base-model-display:hover .edit-base-model-btn, +.version-name-wrapper:hover .edit-version-name-btn, .model-name-header:hover .edit-model-description-btn { opacity: 0.5; } @@ -309,14 +315,16 @@ .edit-model-name-btn:hover, .edit-file-name-btn:hover, .edit-base-model-btn:hover, -.edit-model-description-btn:hover { +.edit-model-description-btn:hover, +.edit-version-name-btn:hover { opacity: 0.8 !important; background: rgba(0, 0, 0, 0.05); } [data-theme="dark"] .edit-model-name-btn:hover, [data-theme="dark"] .edit-file-name-btn:hover, -[data-theme="dark"] .edit-base-model-btn:hover { +[data-theme="dark"] .edit-base-model-btn:hover, +[data-theme="dark"] .edit-version-name-btn:hover { background: rgba(255, 255, 255, 0.05); } @@ -338,7 +346,7 @@ } .base-model-content { - padding: 2px 4px; + padding: 2px 4px 2px 0; border-radius: var(--border-radius-xs); border: 1px solid transparent; color: var(--text-color); diff --git a/static/js/components/shared/ModelMetadata.js b/static/js/components/shared/ModelMetadata.js index d0d90a9d..f7c4e0ab 100644 --- a/static/js/components/shared/ModelMetadata.js +++ b/static/js/components/shared/ModelMetadata.js @@ -66,6 +66,12 @@ function updateModalFilePathReferences(newFilePath) { fileNameContent.setAttribute('data-file-path', newFilePath); } + const versionNameContent = scopedQuery('.version-name-content'); + if (versionNameContent && versionNameContent.dataset) { + versionNameContent.dataset.filePath = newFilePath; + versionNameContent.setAttribute('data-file-path', newFilePath); + } + const editTagsBtn = scopedQuery('.edit-tags-btn'); if (editTagsBtn) { editTagsBtn.dataset.filePath = newFilePath; @@ -516,3 +522,127 @@ export function setupFileNameEditing(filePath) { editBtn.classList.remove('visible'); } } + +/** + * Set up version name editing functionality + * @param {string} filePath - File path + */ +export function setupVersionNameEditing(filePath) { + const versionNameContent = document.querySelector('.version-name-content'); + const editBtn = document.querySelector('.edit-version-name-btn'); + + if (!versionNameContent || !editBtn) return; + + // Store the file path in a data attribute for later use + versionNameContent.dataset.filePath = filePath; + + // Show edit button on hover + const versionNameWrapper = document.querySelector('.version-name-wrapper'); + versionNameWrapper.addEventListener('mouseenter', () => { + editBtn.classList.add('visible'); + }); + + versionNameWrapper.addEventListener('mouseleave', () => { + if (!versionNameWrapper.classList.contains('editing')) { + editBtn.classList.remove('visible'); + } + }); + + // Handle edit button click + editBtn.addEventListener('click', () => { + versionNameWrapper.classList.add('editing'); + versionNameContent.setAttribute('contenteditable', 'true'); + // Store original value for comparison later + versionNameContent.dataset.originalValue = versionNameContent.textContent.trim(); + versionNameContent.focus(); + + // Place cursor at the end + const range = document.createRange(); + const sel = window.getSelection(); + if (versionNameContent.childNodes.length > 0) { + range.setStart(versionNameContent.childNodes[0], versionNameContent.textContent.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + + editBtn.classList.add('visible'); + }); + + // Handle keyboard events in edit mode + versionNameContent.addEventListener('keydown', function(e) { + if (!this.getAttribute('contenteditable')) return; + + if (e.key === 'Enter') { + e.preventDefault(); + this.blur(); // Trigger save on Enter + } else if (e.key === 'Escape') { + e.preventDefault(); + // Restore original value + this.textContent = this.dataset.originalValue; + exitEditMode(); + } + }); + + // Limit version name length + versionNameContent.addEventListener('input', function() { + if (!this.getAttribute('contenteditable')) return; + + if (this.textContent.length > 100) { + this.textContent = this.textContent.substring(0, 100); + // Place cursor at the end + const range = document.createRange(); + const sel = window.getSelection(); + range.setStart(this.childNodes[0], 100); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + + showToast('toast.models.nameTooLong', {}, 'warning'); + } + }); + + // Handle focus out - save changes + versionNameContent.addEventListener('blur', async function() { + if (!this.getAttribute('contenteditable')) return; + + const newVersionName = this.textContent.trim(); + const originalValue = this.dataset.originalValue; + + // Basic validation + if (!newVersionName) { + // Restore original value if empty + this.textContent = originalValue; + showToast('toast.models.nameCannotBeEmpty', {}, 'error'); + exitEditMode(); + return; + } + + if (newVersionName === originalValue) { + // No changes, just exit edit mode + exitEditMode(); + return; + } + + try { + // Resolve current file path from modal state + const filePath = getActiveModalFilePath(this.dataset.filePath); + + await getModelApiClient().saveModelMetadata(filePath, { civitai: { name: newVersionName } }); + + showToast('toast.models.nameUpdatedSuccessfully', {}, 'success'); + } catch (error) { + console.error('Error updating version name:', error); + this.textContent = originalValue; // Restore original version name + showToast('toast.models.nameUpdateFailed', {}, 'error'); + } finally { + exitEditMode(); + } + }); + + function exitEditMode() { + versionNameContent.removeAttribute('contenteditable'); + versionNameWrapper.classList.remove('editing'); + editBtn.classList.remove('visible'); + } +} diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js index 5b1a92b7..eabc1101 100644 --- a/static/js/components/shared/ModelModal.js +++ b/static/js/components/shared/ModelModal.js @@ -11,7 +11,8 @@ import { setupTabSwitching } from './ModelDescription.js'; import { setupModelNameEditing, setupBaseModelEditing, - setupFileNameEditing + setupFileNameEditing, + setupVersionNameEditing } from './ModelMetadata.js'; import { setupTagEditMode } from './ModelTags.js'; import { getModelApiClient } from '../../api/modelApiFactory.js'; @@ -466,7 +467,12 @@ export async function showModelModal(model, modelType) {
- ${modelWithFullData.civitai?.name || 'N/A'} +
+ ${modelWithFullData.civitai?.name || 'N/A'} + +
@@ -660,6 +666,7 @@ export async function showModelModal(model, modelType) { setupTagTooltip(); setupTagEditMode(modelType); setupModelNameEditing(modelWithFullData.file_path); + setupVersionNameEditing(modelWithFullData.file_path); setupBaseModelEditing(modelWithFullData.file_path); setupFileNameEditing(modelWithFullData.file_path); setupEventHandlers(modelWithFullData.file_path, modelType);