mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
Add trigger words editting
This commit is contained in:
@@ -384,7 +384,7 @@ class ApiRoutes:
|
|||||||
# 准备要处理的 loras
|
# 准备要处理的 loras
|
||||||
to_process = [
|
to_process = [
|
||||||
lora for lora in cache.raw_data
|
lora for lora in cache.raw_data
|
||||||
if lora.get('sha256') and not lora.get('civitai') and lora.get('from_civitai')
|
if lora.get('sha256') and (not lora.get('civitai') or 'id' not in lora.get('civitai')) and lora.get('from_civitai') # TODO: for lora not from CivitAI but added traineWords
|
||||||
]
|
]
|
||||||
total_to_process = len(to_process)
|
total_to_process = len(to_process)
|
||||||
|
|
||||||
@@ -617,8 +617,15 @@ class ApiRoutes:
|
|||||||
else:
|
else:
|
||||||
metadata = {}
|
metadata = {}
|
||||||
|
|
||||||
# Update metadata with new values
|
# Handle nested updates (for civitai.trainedWords)
|
||||||
metadata.update(metadata_updates)
|
for key, value in metadata_updates.items():
|
||||||
|
if isinstance(value, dict) and key in metadata and isinstance(metadata[key], dict):
|
||||||
|
# Deep update for nested dictionaries
|
||||||
|
for nested_key, nested_value in value.items():
|
||||||
|
metadata[key][nested_key] = nested_value
|
||||||
|
else:
|
||||||
|
# Regular update for top-level keys
|
||||||
|
metadata[key] = value
|
||||||
|
|
||||||
# Save updated metadata
|
# Save updated metadata
|
||||||
with open(metadata_path, 'w', encoding='utf-8') as f:
|
with open(metadata_path, 'w', encoding='utf-8') as f:
|
||||||
|
|||||||
@@ -121,6 +121,15 @@ async def load_metadata(file_path: str) -> Optional[LoraMetadata]:
|
|||||||
elif preview_url != normalize_path(preview_url):
|
elif preview_url != normalize_path(preview_url):
|
||||||
data['preview_url'] = normalize_path(preview_url)
|
data['preview_url'] = normalize_path(preview_url)
|
||||||
needs_update = True
|
needs_update = True
|
||||||
|
|
||||||
|
# Ensure all fields are present, due to updates adding new fields
|
||||||
|
if 'tags' not in data:
|
||||||
|
data['tags'] = []
|
||||||
|
needs_update = True
|
||||||
|
|
||||||
|
if 'modelDescription' not in data:
|
||||||
|
data['modelDescription'] = ""
|
||||||
|
needs_update = True
|
||||||
|
|
||||||
if needs_update:
|
if needs_update:
|
||||||
with open(metadata_path, 'w', encoding='utf-8') as f:
|
with open(metadata_path, 'w', encoding='utf-8') as f:
|
||||||
|
|||||||
@@ -163,12 +163,57 @@
|
|||||||
border: 1px solid var(--lora-border);
|
border: 1px solid var(--lora-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* New header style for trigger words */
|
||||||
|
.trigger-words-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-trigger-words-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-trigger-words-btn:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .edit-trigger-words-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit mode active state */
|
||||||
|
.trigger-words.edit-mode .edit-trigger-words-btn {
|
||||||
|
opacity: 0.8;
|
||||||
|
color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-words-content {
|
||||||
|
margin-bottom: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
.trigger-words-tags {
|
.trigger-words-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-top: var(--space-1);
|
}
|
||||||
|
|
||||||
|
/* No trigger words message */
|
||||||
|
.no-trigger-words {
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.7;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update Trigger Words styles */
|
/* Update Trigger Words styles */
|
||||||
@@ -182,6 +227,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update trigger word content color to use theme accent */
|
/* Update trigger word content color to use theme accent */
|
||||||
@@ -207,6 +253,123 @@
|
|||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Delete button for trigger word */
|
||||||
|
.delete-trigger-word-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--lora-error);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 9px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-trigger-word-btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit controls */
|
||||||
|
.trigger-words-edit-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-words-edit-controls button {
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.85em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-words-edit-controls button:hover {
|
||||||
|
background: oklch(var(--lora-accent) / 0.1);
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-words-edit-controls button i {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-trigger-words-btn {
|
||||||
|
background: var(--lora-accent) !important;
|
||||||
|
color: white !important;
|
||||||
|
border-color: var(--lora-accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-trigger-words-btn:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add trigger word form */
|
||||||
|
.add-trigger-word-form {
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-trigger-word-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-trigger-word-input:focus {
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-add-trigger-word-btn,
|
||||||
|
.cancel-add-trigger-word-btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.85em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-add-trigger-word-btn {
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-add-trigger-word-btn:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-add-trigger-word-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .cancel-add-trigger-word-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
/* Editable Fields */
|
/* Editable Fields */
|
||||||
.editable-field {
|
.editable-field {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -724,7 +887,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.model-description-content blockquote {
|
.model-description-content blockquote {
|
||||||
border-left: 3px solid var(--lora-accent);
|
border-left: 3px solid var (--lora-accent);
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function showLoraModal(lora) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${renderTriggerWords(escapedWords)}
|
${renderTriggerWords(escapedWords, lora.file_path)}
|
||||||
<div class="info-item notes">
|
<div class="info-item notes">
|
||||||
<label>Additional Notes</label>
|
<label>Additional Notes</label>
|
||||||
<div class="editable-field">
|
<div class="editable-field">
|
||||||
@@ -120,6 +120,7 @@ export function showLoraModal(lora) {
|
|||||||
setupShowcaseScroll();
|
setupShowcaseScroll();
|
||||||
setupTabSwitching();
|
setupTabSwitching();
|
||||||
setupTagTooltip();
|
setupTagTooltip();
|
||||||
|
setupTriggerWordsEditMode();
|
||||||
|
|
||||||
// If we have a model ID but no description, fetch it
|
// If we have a model ID but no description, fetch it
|
||||||
if (lora.civitai?.modelId && !lora.modelDescription) {
|
if (lora.civitai?.modelId && !lora.modelDescription) {
|
||||||
@@ -488,35 +489,75 @@ async function saveModelMetadata(filePath, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTriggerWords(words) {
|
function renderTriggerWords(words, filePath) {
|
||||||
if (!words.length) return `
|
if (!words.length) return `
|
||||||
<div class="info-item full-width trigger-words">
|
<div class="info-item full-width trigger-words">
|
||||||
<label>Trigger Words</label>
|
<div class="trigger-words-header">
|
||||||
<span>No trigger word needed</span>
|
<label>Trigger Words</label>
|
||||||
|
<button class="edit-trigger-words-btn" data-file-path="${filePath}" title="Edit trigger words">
|
||||||
|
<i class="fas fa-pencil-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="trigger-words-content">
|
||||||
|
<span class="no-trigger-words">No trigger word needed</span>
|
||||||
|
<div class="trigger-words-tags" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="trigger-words-edit-controls" style="display:none;">
|
||||||
|
<button class="add-trigger-word-btn" title="Add a trigger word">
|
||||||
|
<i class="fas fa-plus"></i> Add
|
||||||
|
</button>
|
||||||
|
<button class="save-trigger-words-btn" title="Save changes">
|
||||||
|
<i class="fas fa-save"></i> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="add-trigger-word-form" style="display:none;">
|
||||||
|
<input type="text" class="new-trigger-word-input" placeholder="Enter trigger word">
|
||||||
|
<button class="confirm-add-trigger-word-btn">Add</button>
|
||||||
|
<button class="cancel-add-trigger-word-btn">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="info-item full-width trigger-words">
|
<div class="info-item full-width trigger-words">
|
||||||
<label>Trigger Words</label>
|
<div class="trigger-words-header">
|
||||||
<div class="trigger-words-tags">
|
<label>Trigger Words</label>
|
||||||
${words.map(word => `
|
<button class="edit-trigger-words-btn" data-file-path="${filePath}" title="Edit trigger words">
|
||||||
<div class="trigger-word-tag" onclick="copyTriggerWord('${word}')">
|
<i class="fas fa-pencil-alt"></i>
|
||||||
<span class="trigger-word-content">${word}</span>
|
</button>
|
||||||
<span class="trigger-word-copy">
|
</div>
|
||||||
<i class="fas fa-copy"></i>
|
<div class="trigger-words-content">
|
||||||
</span>
|
<div class="trigger-words-tags">
|
||||||
</div>
|
${words.map(word => `
|
||||||
`).join('')}
|
<div class="trigger-word-tag" data-word="${word}" onclick="copyTriggerWord('${word}')">
|
||||||
|
<span class="trigger-word-content">${word}</span>
|
||||||
|
<span class="trigger-word-copy">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</span>
|
||||||
|
<button class="delete-trigger-word-btn" style="display:none;" onclick="event.stopPropagation();">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="trigger-words-edit-controls" style="display:none;">
|
||||||
|
<button class="add-trigger-word-btn" title="Add a trigger word">
|
||||||
|
<i class="fas fa-plus"></i> Add
|
||||||
|
</button>
|
||||||
|
<button class="save-trigger-words-btn" title="Save changes">
|
||||||
|
<i class="fas fa-save"></i> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="add-trigger-word-form" style="display:none;">
|
||||||
|
<input type="text" class="new-trigger-word-input" placeholder="Enter trigger word">
|
||||||
|
<button class="confirm-add-trigger-word-btn">Add</button>
|
||||||
|
<button class="cancel-add-trigger-word-btn">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderShowcaseImages(images) {
|
|
||||||
return renderShowcaseContent(images);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleShowcase(element) {
|
export function toggleShowcase(element) {
|
||||||
const carousel = element.nextElementSibling;
|
const carousel = element.nextElementSibling;
|
||||||
const isCollapsed = carousel.classList.contains('collapsed');
|
const isCollapsed = carousel.classList.contains('collapsed');
|
||||||
@@ -738,4 +779,243 @@ function setupTagTooltip() {
|
|||||||
tooltip.classList.remove('visible');
|
tooltip.classList.remove('visible');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up trigger words edit mode
|
||||||
|
function setupTriggerWordsEditMode() {
|
||||||
|
const editBtn = document.querySelector('.edit-trigger-words-btn');
|
||||||
|
if (!editBtn) return;
|
||||||
|
|
||||||
|
editBtn.addEventListener('click', function() {
|
||||||
|
const triggerWordsSection = this.closest('.trigger-words');
|
||||||
|
const isEditMode = triggerWordsSection.classList.toggle('edit-mode');
|
||||||
|
|
||||||
|
// Toggle edit mode UI elements
|
||||||
|
const triggerWordTags = triggerWordsSection.querySelectorAll('.trigger-word-tag');
|
||||||
|
const editControls = triggerWordsSection.querySelector('.trigger-words-edit-controls');
|
||||||
|
const noTriggerWords = triggerWordsSection.querySelector('.no-trigger-words');
|
||||||
|
const tagsContainer = triggerWordsSection.querySelector('.trigger-words-tags');
|
||||||
|
|
||||||
|
if (isEditMode) {
|
||||||
|
this.innerHTML = '<i class="fas fa-times"></i>'; // Change to cancel icon
|
||||||
|
this.title = "Cancel editing";
|
||||||
|
editControls.style.display = 'flex';
|
||||||
|
|
||||||
|
// If we have no trigger words yet, hide the "No trigger word needed" text
|
||||||
|
// and show the empty tags container
|
||||||
|
if (noTriggerWords) {
|
||||||
|
noTriggerWords.style.display = 'none';
|
||||||
|
if (tagsContainer) tagsContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable click-to-copy and show delete buttons
|
||||||
|
triggerWordTags.forEach(tag => {
|
||||||
|
tag.onclick = null;
|
||||||
|
tag.querySelector('.trigger-word-copy').style.display = 'none';
|
||||||
|
tag.querySelector('.delete-trigger-word-btn').style.display = 'block';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.innerHTML = '<i class="fas fa-pencil-alt"></i>'; // Change back to edit icon
|
||||||
|
this.title = "Edit trigger words";
|
||||||
|
editControls.style.display = 'none';
|
||||||
|
|
||||||
|
// If we have no trigger words, show the "No trigger word needed" text
|
||||||
|
// and hide the empty tags container
|
||||||
|
const currentTags = triggerWordsSection.querySelectorAll('.trigger-word-tag');
|
||||||
|
if (noTriggerWords && currentTags.length === 0) {
|
||||||
|
noTriggerWords.style.display = '';
|
||||||
|
if (tagsContainer) tagsContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original state
|
||||||
|
triggerWordTags.forEach(tag => {
|
||||||
|
const word = tag.dataset.word;
|
||||||
|
tag.onclick = () => copyTriggerWord(word);
|
||||||
|
tag.querySelector('.trigger-word-copy').style.display = 'flex';
|
||||||
|
tag.querySelector('.delete-trigger-word-btn').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide add form if open
|
||||||
|
triggerWordsSection.querySelector('.add-trigger-word-form').style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up add trigger word button
|
||||||
|
const addBtn = document.querySelector('.add-trigger-word-btn');
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.addEventListener('click', function() {
|
||||||
|
const triggerWordsSection = this.closest('.trigger-words');
|
||||||
|
const addForm = triggerWordsSection.querySelector('.add-trigger-word-form');
|
||||||
|
addForm.style.display = 'flex';
|
||||||
|
addForm.querySelector('input').focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up confirm and cancel add buttons
|
||||||
|
const confirmAddBtn = document.querySelector('.confirm-add-trigger-word-btn');
|
||||||
|
const cancelAddBtn = document.querySelector('.cancel-add-trigger-word-btn');
|
||||||
|
const triggerWordInput = document.querySelector('.new-trigger-word-input');
|
||||||
|
|
||||||
|
if (confirmAddBtn && triggerWordInput) {
|
||||||
|
confirmAddBtn.addEventListener('click', function() {
|
||||||
|
addNewTriggerWord(triggerWordInput.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add keydown event to input
|
||||||
|
triggerWordInput.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addNewTriggerWord(this.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelAddBtn) {
|
||||||
|
cancelAddBtn.addEventListener('click', function() {
|
||||||
|
const addForm = this.closest('.add-trigger-word-form');
|
||||||
|
addForm.style.display = 'none';
|
||||||
|
addForm.querySelector('input').value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up save button
|
||||||
|
const saveBtn = document.querySelector('.save-trigger-words-btn');
|
||||||
|
if (saveBtn) {
|
||||||
|
saveBtn.addEventListener('click', saveTriggerWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up delete buttons
|
||||||
|
document.querySelectorAll('.delete-trigger-word-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
const tag = this.closest('.trigger-word-tag');
|
||||||
|
tag.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to add a new trigger word
|
||||||
|
function addNewTriggerWord(word) {
|
||||||
|
word = word.trim();
|
||||||
|
if (!word) return;
|
||||||
|
|
||||||
|
const triggerWordsSection = document.querySelector('.trigger-words');
|
||||||
|
let tagsContainer = document.querySelector('.trigger-words-tags');
|
||||||
|
|
||||||
|
// Ensure tags container exists and is visible
|
||||||
|
if (tagsContainer) {
|
||||||
|
tagsContainer.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
// Create tags container if it doesn't exist
|
||||||
|
const contentDiv = triggerWordsSection.querySelector('.trigger-words-content');
|
||||||
|
if (contentDiv) {
|
||||||
|
tagsContainer = document.createElement('div');
|
||||||
|
tagsContainer.className = 'trigger-words-tags';
|
||||||
|
contentDiv.appendChild(tagsContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tagsContainer) return;
|
||||||
|
|
||||||
|
// Hide "no trigger words" message if it exists
|
||||||
|
const noTriggerWordsMsg = triggerWordsSection.querySelector('.no-trigger-words');
|
||||||
|
if (noTriggerWordsMsg) {
|
||||||
|
noTriggerWordsMsg.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation: Check length
|
||||||
|
if (word.split(/\s+/).length > 30) {
|
||||||
|
showToast('Trigger word should not exceed 30 words', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation: Check total number
|
||||||
|
const currentTags = tagsContainer.querySelectorAll('.trigger-word-tag');
|
||||||
|
if (currentTags.length >= 10) {
|
||||||
|
showToast('Maximum 10 trigger words allowed', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation: Check for duplicates
|
||||||
|
const existingWords = Array.from(currentTags).map(tag => tag.dataset.word);
|
||||||
|
if (existingWords.includes(word)) {
|
||||||
|
showToast('This trigger word already exists', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new tag
|
||||||
|
const newTag = document.createElement('div');
|
||||||
|
newTag.className = 'trigger-word-tag';
|
||||||
|
newTag.dataset.word = word;
|
||||||
|
newTag.innerHTML = `
|
||||||
|
<span class="trigger-word-content">${word}</span>
|
||||||
|
<span class="trigger-word-copy" style="display:none;">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</span>
|
||||||
|
<button class="delete-trigger-word-btn" onclick="event.stopPropagation();">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add event listener to delete button
|
||||||
|
const deleteBtn = newTag.querySelector('.delete-trigger-word-btn');
|
||||||
|
deleteBtn.addEventListener('click', function() {
|
||||||
|
newTag.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
tagsContainer.appendChild(newTag);
|
||||||
|
|
||||||
|
// Clear and hide the input form
|
||||||
|
const triggerWordInput = document.querySelector('.new-trigger-word-input');
|
||||||
|
triggerWordInput.value = '';
|
||||||
|
document.querySelector('.add-trigger-word-form').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to save updated trigger words
|
||||||
|
async function saveTriggerWords() {
|
||||||
|
const filePath = document.querySelector('.edit-trigger-words-btn').dataset.filePath;
|
||||||
|
const triggerWordTags = document.querySelectorAll('.trigger-word-tag');
|
||||||
|
const words = Array.from(triggerWordTags).map(tag => tag.dataset.word);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Special format for updating nested civitai.trainedWords
|
||||||
|
await saveModelMetadata(filePath, {
|
||||||
|
civitai: { trainedWords: words }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
const editBtn = document.querySelector('.edit-trigger-words-btn');
|
||||||
|
editBtn.click(); // Exit edit mode
|
||||||
|
|
||||||
|
// Update the LoRA card's dataset
|
||||||
|
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||||
|
if (loraCard && loraCard.dataset.civitai) {
|
||||||
|
const civitaiData = JSON.parse(loraCard.dataset.civitai);
|
||||||
|
civitaiData.trainedWords = words;
|
||||||
|
loraCard.dataset.civitai = JSON.stringify(civitaiData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we saved an empty array and there's a no-trigger-words element, show it
|
||||||
|
const noTriggerWords = document.querySelector('.no-trigger-words');
|
||||||
|
const tagsContainer = document.querySelector('.trigger-words-tags');
|
||||||
|
if (words.length === 0 && noTriggerWords) {
|
||||||
|
noTriggerWords.style.display = '';
|
||||||
|
if (tagsContainer) tagsContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('Trigger words updated successfully', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
showToast('Failed to update trigger words', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add copy trigger word function
|
||||||
|
window.copyTriggerWord = async function(word) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(word);
|
||||||
|
showToast('Trigger word copied', 'success');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Copy failed:', err);
|
||||||
|
showToast('Copy failed', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user