import { showToast } from '../utils/uiHelpers.js'; import { state } from '../state/index.js'; export function showLoraModal(lora) { const escapedWords = lora.civitai?.trainedWords?.length ? lora.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : []; const content = `
`; modalManager.showModal('loraModal', content); setupEditableFields(); setupShowcaseScroll(); } // 添加复制文件名的函数 window.copyFileName = async function(fileName) { try { await navigator.clipboard.writeText(fileName); showToast('File name copied', 'success'); } catch (err) { console.error('Copy failed:', err); showToast('Copy failed', 'error'); } }; // Add function to save model name window.saveModelName = async function(filePath) { const modelNameElement = document.querySelector('.model-name-content'); const newModelName = modelNameElement.textContent.trim(); // Validate model name if (!newModelName) { showToast('Model name cannot be empty', 'error'); return; } // Check if model name is too long (limit to 100 characters) if (newModelName.length > 100) { showToast('Model name is too long (maximum 100 characters)', 'error'); // Truncate the displayed text modelNameElement.textContent = newModelName.substring(0, 100); return; } try { await saveModelMetadata(filePath, { model_name: newModelName }); // Update the corresponding lora card's dataset and display const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); if (loraCard) { loraCard.dataset.model_name = newModelName; const titleElement = loraCard.querySelector('.card-title'); if (titleElement) { titleElement.textContent = newModelName; } } showToast('Model name updated successfully', 'success'); // Reload the page to reflect the sorted order setTimeout(() => { window.location.reload(); }, 1500); } catch (error) { showToast('Failed to update model name', 'error'); } }; function setupEditableFields() { const editableFields = document.querySelectorAll('.editable-field [contenteditable]'); editableFields.forEach(field => { field.addEventListener('focus', function() { if (this.textContent === 'Add your notes here...' || this.textContent === 'Save usage tips here..') { this.textContent = ''; } }); field.addEventListener('blur', function() { if (this.textContent.trim() === '') { if (this.classList.contains('model-name-content')) { // Restore original model name if empty const filePath = document.querySelector('.modal-content') .querySelector('.file-path').textContent + document.querySelector('.modal-content') .querySelector('#file-name').textContent + '.safetensors'; const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); if (loraCard) { this.textContent = loraCard.dataset.model_name; } } else if (this.classList.contains('usage-tips-content')) { this.textContent = 'Save usage tips here..'; } else { this.textContent = 'Add your notes here...'; } } }); // Add input validation for model name if (field.classList.contains('model-name-content')) { field.addEventListener('input', function() { // Limit model name length 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('Model name is limited to 100 characters', 'warning'); } }); field.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); const filePath = document.querySelector('.modal-content') .querySelector('.file-path').textContent + document.querySelector('.modal-content') .querySelector('#file-name').textContent + '.safetensors'; saveModelName(filePath); } }); } }); const presetSelector = document.getElementById('preset-selector'); const presetValue = document.getElementById('preset-value'); const addPresetBtn = document.querySelector('.add-preset-btn'); const presetTags = document.querySelector('.preset-tags'); presetSelector.addEventListener('change', function() { const selected = this.value; if (selected) { presetValue.style.display = 'inline-block'; presetValue.min = selected.includes('strength') ? 0 : 1; presetValue.max = selected.includes('strength') ? 1 : 12; presetValue.step = selected.includes('strength') ? 0.01 : 1; if (selected === 'clip_skip') { presetValue.type = 'number'; presetValue.step = 1; } // Add auto-focus setTimeout(() => presetValue.focus(), 0); } else { presetValue.style.display = 'none'; } }); addPresetBtn.addEventListener('click', async function() { const key = presetSelector.value; const value = presetValue.value; if (!key || !value) return; const filePath = document.querySelector('.modal-content') .querySelector('.file-path').textContent + document.querySelector('.modal-content') .querySelector('#file-name').textContent + '.safetensors'; const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); const currentPresets = parsePresets(loraCard.dataset.usage_tips); currentPresets[key] = parseFloat(value); const newPresetsJson = JSON.stringify(currentPresets); await saveModelMetadata(filePath, { usage_tips: newPresetsJson }); loraCard.dataset.usage_tips = newPresetsJson; presetTags.innerHTML = renderPresetTags(currentPresets); presetSelector.value = ''; presetValue.value = ''; presetValue.style.display = 'none'; }); // Add keydown event listeners for notes const notesContent = document.querySelector('.notes-content'); if (notesContent) { notesContent.addEventListener('keydown', async function(e) { if (e.key === 'Enter') { if (e.shiftKey) { // Allow shift+enter for new line return; } e.preventDefault(); const filePath = document.querySelector('.modal-content') .querySelector('.file-path').textContent + document.querySelector('.modal-content') .querySelector('#file-name').textContent + '.safetensors'; await saveNotes(filePath); } }); } // Add keydown event for preset value presetValue.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); addPresetBtn.click(); } }); } window.saveNotes = async function(filePath) { const content = document.querySelector('.notes-content').textContent; try { await saveModelMetadata(filePath, { notes: content }); // Update the corresponding lora card's dataset const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); if (loraCard) { loraCard.dataset.notes = content; } showToast('Notes saved successfully', 'success'); } catch (error) { showToast('Failed to save notes', 'error'); } }; async function saveModelMetadata(filePath, data) { const response = await fetch('/loras/api/save-metadata', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ file_path: filePath, ...data }) }); if (!response.ok) { throw new Error('Failed to save metadata'); } } function renderTriggerWords(words) { if (!words.length) return `