mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-19 08:52:05 -03:00
feat(tags): unify recipe modal tag UI with model modal
- Replace recipe modal's custom tag display/edit with shared renderCompactTags/setupTagEditMode from ModelTags and utils - Remove 300+ lines of duplicated tag display and editing code - Parameterize setupTagEditMode with saveHandler/onSaved/showSuggestions options for recipe-specific save flow (updateRecipeMetadata + dirty state) - Scope all DOM queries in ModelTags.js via options.container / this.closest to prevent cross-modal element conflicts - Fix edit button alignment (justify-content: flex-start) - Fix tag tooltip selector scoping in setupTagTooltip - Add width: 100% to #recipeTagsContainer for edit container full width
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-tag-compact {
|
.model-tag-compact {
|
||||||
@@ -28,6 +30,9 @@
|
|||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
max-width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for empty tags placeholder */
|
/* Style for empty tags placeholder */
|
||||||
@@ -118,8 +123,9 @@
|
|||||||
/* Model Tags Edit Mode */
|
/* Model Tags Edit Mode */
|
||||||
.model-tags-header {
|
.model-tags-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-tags-btn {
|
.edit-tags-btn {
|
||||||
@@ -132,6 +138,7 @@
|
|||||||
border-radius: var(--border-radius-xs);
|
border-radius: var(--border-radius-xs);
|
||||||
transition: var(--transition-base);
|
transition: var(--transition-base);
|
||||||
margin-left: var(--space-1);
|
margin-left: var(--space-1);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-tags-btn.visible,
|
.edit-tags-btn.visible,
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#recipeTagsContainer {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.recipe-modal-header h2 {
|
.recipe-modal-header h2 {
|
||||||
margin: 0 0 var(--space-1);
|
margin: 0 0 var(--space-1);
|
||||||
padding: var(--space-1);
|
padding: var(--space-1);
|
||||||
@@ -95,127 +99,11 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-editor.tags-editor input {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove obsolete button styles */
|
/* Remove obsolete button styles */
|
||||||
.editor-actions {
|
.editor-actions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Special styling for tags content */
|
|
||||||
.tags-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags-display {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 6px;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-tags {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--text-color);
|
|
||||||
opacity: 0.6;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Recipe Tags styles */
|
|
||||||
.recipe-tags-container {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe-tags-compact {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 6px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe-tag-compact {
|
|
||||||
background: var(--surface-subtle);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
padding: 2px 8px;
|
|
||||||
font-size: 0.75em;
|
|
||||||
color: var(--text-color);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .recipe-tag-compact {
|
|
||||||
background: var(--surface-subtle);
|
|
||||||
border: 1px solid var(--lora-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe-tag-more {
|
|
||||||
background: var(--lora-accent);
|
|
||||||
color: var(--lora-text);
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
padding: 2px 8px;
|
|
||||||
font-size: 0.75em;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe-tags-tooltip {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 8px);
|
|
||||||
left: 0;
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
box-shadow: var(--shadow-dropdown);
|
|
||||||
padding: 10px 14px;
|
|
||||||
max-width: 400px;
|
|
||||||
z-index: 10;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transform: translateY(-4px);
|
|
||||||
transition: var(--transition-base);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recipe-tags-tooltip.visible {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
transform: translateY(0);
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-tag {
|
|
||||||
background: var(--surface-hover);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
padding: 3px 8px;
|
|
||||||
font-size: 0.75em;
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .tooltip-tag {
|
|
||||||
background: var(--surface-hover);
|
|
||||||
border: 1px solid var(--lora-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
#recipeModal .modal-content {
|
#recipeModal .modal-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1153,7 +1041,7 @@
|
|||||||
max-height: 2.4em;
|
max-height: 2.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recipe-tags-container {
|
#recipeTagsContainer {
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { fetchRecipeDetails, updateRecipeMetadata } from '../api/recipeApi.js';
|
|||||||
import { downloadManager } from '../managers/DownloadManager.js';
|
import { downloadManager } from '../managers/DownloadManager.js';
|
||||||
import { MODEL_TYPES } from '../api/apiConfig.js';
|
import { MODEL_TYPES } from '../api/apiConfig.js';
|
||||||
import { openMediaViewer } from './shared/MediaViewer.js';
|
import { openMediaViewer } from './shared/MediaViewer.js';
|
||||||
|
import { renderCompactTags, setupTagTooltip } from './shared/utils.js';
|
||||||
|
import { setupTagEditMode } from './shared/ModelTags.js';
|
||||||
|
|
||||||
const ALLOWED_GEN_PARAM_KEYS = new Set([
|
const ALLOWED_GEN_PARAM_KEYS = new Set([
|
||||||
'prompt',
|
'prompt',
|
||||||
@@ -139,14 +141,6 @@ class RecipeModal {
|
|||||||
this.saveTitleEdit();
|
this.saveTitleEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tags edit
|
|
||||||
const tagsEditor = document.getElementById('recipeTagsEditor');
|
|
||||||
if (tagsEditor && tagsEditor.classList.contains('active') &&
|
|
||||||
!tagsEditor.contains(event.target) &&
|
|
||||||
!event.target.closest('.edit-icon')) {
|
|
||||||
this.saveTagsEdit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle reconnect input
|
// Handle reconnect input
|
||||||
const reconnectContainers = document.querySelectorAll('.lora-reconnect-container');
|
const reconnectContainers = document.querySelectorAll('.lora-reconnect-container');
|
||||||
reconnectContainers.forEach(container => {
|
reconnectContainers.forEach(container => {
|
||||||
@@ -236,98 +230,10 @@ class RecipeModal {
|
|||||||
this.filePath = hydratedRecipe.file_path;
|
this.filePath = hydratedRecipe.file_path;
|
||||||
this.listFilePath = hydratedRecipe.file_path;
|
this.listFilePath = hydratedRecipe.file_path;
|
||||||
|
|
||||||
// Set recipe tags if they exist
|
// Render tags using shared utility
|
||||||
const tagsCompactElement = document.getElementById('recipeTagsCompact');
|
const tagsContainer = document.getElementById('recipeTagsContainer');
|
||||||
const tagsTooltipContent = document.getElementById('recipeTagsTooltipContent');
|
if (tagsContainer) {
|
||||||
|
this.updateTagsDisplay(tagsContainer, hydratedRecipe.tags || []);
|
||||||
if (tagsCompactElement) {
|
|
||||||
// Add tags container with edit functionality
|
|
||||||
tagsCompactElement.innerHTML = `
|
|
||||||
<div class="editable-content tags-content">
|
|
||||||
<div class="tags-display"></div>
|
|
||||||
<button class="edit-icon" title="Edit tags"><i class="fas fa-pencil-alt"></i></button>
|
|
||||||
</div>
|
|
||||||
<div id="recipeTagsEditor" class="content-editor tags-editor">
|
|
||||||
<input type="text" class="tags-input" placeholder="Enter tags separated by commas">
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const tagsDisplay = tagsCompactElement.querySelector('.tags-display');
|
|
||||||
|
|
||||||
if (hydratedRecipe.tags && hydratedRecipe.tags.length > 0) {
|
|
||||||
// Limit displayed tags to 5, show a "+X more" button if needed
|
|
||||||
const maxVisibleTags = 5;
|
|
||||||
const visibleTags = hydratedRecipe.tags.slice(0, maxVisibleTags);
|
|
||||||
const remainingTags = hydratedRecipe.tags.length > maxVisibleTags ? hydratedRecipe.tags.slice(maxVisibleTags) : [];
|
|
||||||
|
|
||||||
// Add visible tags
|
|
||||||
visibleTags.forEach(tag => {
|
|
||||||
const tagElement = document.createElement('div');
|
|
||||||
tagElement.className = 'recipe-tag-compact';
|
|
||||||
tagElement.textContent = tag;
|
|
||||||
tagsDisplay.appendChild(tagElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add "more" button if needed
|
|
||||||
if (remainingTags.length > 0) {
|
|
||||||
const moreButton = document.createElement('div');
|
|
||||||
moreButton.className = 'recipe-tag-more';
|
|
||||||
moreButton.textContent = `+${remainingTags.length} more`;
|
|
||||||
tagsDisplay.appendChild(moreButton);
|
|
||||||
|
|
||||||
// Add tooltip functionality
|
|
||||||
moreButton.addEventListener('mouseenter', () => {
|
|
||||||
document.getElementById('recipeTagsTooltip').classList.add('visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
moreButton.addEventListener('mouseleave', () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!document.getElementById('recipeTagsTooltip').matches(':hover')) {
|
|
||||||
document.getElementById('recipeTagsTooltip').classList.remove('visible');
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('recipeTagsTooltip').addEventListener('mouseleave', () => {
|
|
||||||
document.getElementById('recipeTagsTooltip').classList.remove('visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add all tags to tooltip
|
|
||||||
if (tagsTooltipContent) {
|
|
||||||
tagsTooltipContent.innerHTML = '';
|
|
||||||
hydratedRecipe.tags.forEach(tag => {
|
|
||||||
const tooltipTag = document.createElement('div');
|
|
||||||
tooltipTag.className = 'tooltip-tag';
|
|
||||||
tooltipTag.textContent = tag;
|
|
||||||
tagsTooltipContent.appendChild(tooltipTag);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tagsDisplay.innerHTML = '<div class="no-tags">No tags</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add event listeners for tags editing
|
|
||||||
const editTagsIcon = tagsCompactElement.querySelector('.edit-icon');
|
|
||||||
const tagsInput = tagsCompactElement.querySelector('.tags-input');
|
|
||||||
|
|
||||||
// Set current tags in the input
|
|
||||||
if (hydratedRecipe.tags && hydratedRecipe.tags.length > 0) {
|
|
||||||
tagsInput.value = hydratedRecipe.tags.join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
editTagsIcon.addEventListener('click', () => this.showTagsEditor());
|
|
||||||
|
|
||||||
// Add key event listener for Enter key
|
|
||||||
tagsInput.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
this.saveTagsEdit();
|
|
||||||
} else if (e.key === 'Escape') {
|
|
||||||
e.preventDefault();
|
|
||||||
this.cancelTagsEdit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set recipe image
|
// Set recipe image
|
||||||
@@ -609,17 +515,35 @@ class RecipeModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
syncTagsDisplay(tags) {
|
syncTagsDisplay(tags) {
|
||||||
const tagsContainer = document.getElementById('recipeTagsCompact');
|
const container = document.getElementById('recipeTagsContainer');
|
||||||
if (!tagsContainer) {
|
if (!container) return;
|
||||||
return;
|
this.updateTagsDisplay(container, tags || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTagsDisplay(tagsContainer, tags || []);
|
// Re-render tags display using shared utility, wire edit mode with ModelTags
|
||||||
|
updateTagsDisplay(container, tags) {
|
||||||
|
const filePath = this.filePath || '';
|
||||||
|
|
||||||
const tagsInput = tagsContainer.querySelector('.tags-input');
|
container.innerHTML = renderCompactTags(tags, filePath);
|
||||||
if (tagsInput) {
|
|
||||||
tagsInput.value = tags && tags.length > 0 ? tags.join(', ') : '';
|
// Setup tooltip for all tags
|
||||||
}
|
setupTagTooltip(container);
|
||||||
|
|
||||||
|
// Wire edit button using shared tag editing (no suggestions for recipes)
|
||||||
|
setupTagEditMode(null, {
|
||||||
|
container: container,
|
||||||
|
showSuggestions: false,
|
||||||
|
normalizeTag: false,
|
||||||
|
saveHandler: async (filePath, tags) => {
|
||||||
|
await updateRecipeMetadata(filePath, { tags }, this.getMetadataUpdateOptions());
|
||||||
|
},
|
||||||
|
onSaved: (tags) => {
|
||||||
|
this.currentRecipe.tags = tags;
|
||||||
|
this.commitField('tags');
|
||||||
|
const c = document.getElementById('recipeTagsContainer');
|
||||||
|
if (c) this.updateTagsDisplay(c, tags);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
syncPromptField(field, value, placeholder) {
|
syncPromptField(field, value, placeholder) {
|
||||||
@@ -976,139 +900,6 @@ class RecipeModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tags editing methods
|
|
||||||
showTagsEditor() {
|
|
||||||
const tagsContainer = document.getElementById('recipeTagsCompact');
|
|
||||||
if (tagsContainer) {
|
|
||||||
tagsContainer.querySelector('.editable-content').classList.add('hide');
|
|
||||||
const editor = tagsContainer.querySelector('#recipeTagsEditor');
|
|
||||||
editor.classList.add('active');
|
|
||||||
const input = editor.querySelector('input');
|
|
||||||
input.oninput = () => this.markFieldDirty('tags');
|
|
||||||
input.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveTagsEdit() {
|
|
||||||
const tagsContainer = document.getElementById('recipeTagsCompact');
|
|
||||||
if (tagsContainer) {
|
|
||||||
const editor = tagsContainer.querySelector('#recipeTagsEditor');
|
|
||||||
const input = editor.querySelector('input');
|
|
||||||
const tagsText = input.value.trim();
|
|
||||||
|
|
||||||
// Parse tags
|
|
||||||
let newTags = [];
|
|
||||||
if (tagsText) {
|
|
||||||
newTags = tagsText.split(',')
|
|
||||||
.map(tag => tag.trim())
|
|
||||||
.filter(tag => tag.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if tags changed
|
|
||||||
const oldTags = this.currentRecipe.tags || [];
|
|
||||||
const tagsChanged =
|
|
||||||
newTags.length !== oldTags.length ||
|
|
||||||
newTags.some((tag, index) => tag !== oldTags[index]);
|
|
||||||
|
|
||||||
if (tagsChanged) {
|
|
||||||
// Update the recipe on the server
|
|
||||||
updateRecipeMetadata(this.filePath, { tags: newTags }, this.getMetadataUpdateOptions())
|
|
||||||
.then(data => {
|
|
||||||
// Show success toast
|
|
||||||
showToast('toast.recipes.tagsUpdated', {}, 'success');
|
|
||||||
|
|
||||||
// Update the current recipe object
|
|
||||||
this.currentRecipe.tags = newTags;
|
|
||||||
this.commitField('tags');
|
|
||||||
|
|
||||||
// Update tags in the UI
|
|
||||||
this.updateTagsDisplay(tagsContainer, newTags);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Error is handled in the API function
|
|
||||||
this.clearFieldDirty('tags');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.clearFieldDirty('tags');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide editor
|
|
||||||
editor.classList.remove('active');
|
|
||||||
tagsContainer.querySelector('.editable-content').classList.remove('hide');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method to update tags display
|
|
||||||
updateTagsDisplay(tagsContainer, tags) {
|
|
||||||
const tagsDisplay = tagsContainer.querySelector('.tags-display');
|
|
||||||
tagsDisplay.innerHTML = '';
|
|
||||||
|
|
||||||
if (tags.length > 0) {
|
|
||||||
// Limit displayed tags to 5, show a "+X more" button if needed
|
|
||||||
const maxVisibleTags = 5;
|
|
||||||
const visibleTags = tags.slice(0, maxVisibleTags);
|
|
||||||
const remainingTags = tags.length > maxVisibleTags ? tags.slice(maxVisibleTags) : [];
|
|
||||||
|
|
||||||
// Add visible tags
|
|
||||||
visibleTags.forEach(tag => {
|
|
||||||
const tagElement = document.createElement('div');
|
|
||||||
tagElement.className = 'recipe-tag-compact';
|
|
||||||
tagElement.textContent = tag;
|
|
||||||
tagsDisplay.appendChild(tagElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add "more" button if needed
|
|
||||||
if (remainingTags.length > 0) {
|
|
||||||
const moreButton = document.createElement('div');
|
|
||||||
moreButton.className = 'recipe-tag-more';
|
|
||||||
moreButton.textContent = `+${remainingTags.length} more`;
|
|
||||||
tagsDisplay.appendChild(moreButton);
|
|
||||||
|
|
||||||
// Update tooltip content
|
|
||||||
const tooltipContent = document.getElementById('recipeTagsTooltipContent');
|
|
||||||
if (tooltipContent) {
|
|
||||||
tooltipContent.innerHTML = '';
|
|
||||||
tags.forEach(tag => {
|
|
||||||
const tooltipTag = document.createElement('div');
|
|
||||||
tooltipTag.className = 'tooltip-tag';
|
|
||||||
tooltipTag.textContent = tag;
|
|
||||||
tooltipContent.appendChild(tooltipTag);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-add tooltip functionality
|
|
||||||
moreButton.addEventListener('mouseenter', () => {
|
|
||||||
document.getElementById('recipeTagsTooltip').classList.add('visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
moreButton.addEventListener('mouseleave', () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!document.getElementById('recipeTagsTooltip').matches(':hover')) {
|
|
||||||
document.getElementById('recipeTagsTooltip').classList.remove('visible');
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tagsDisplay.innerHTML = '<div class="no-tags">No tags</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelTagsEdit() {
|
|
||||||
const tagsContainer = document.getElementById('recipeTagsCompact');
|
|
||||||
if (tagsContainer) {
|
|
||||||
// Reset input value
|
|
||||||
const editor = tagsContainer.querySelector('#recipeTagsEditor');
|
|
||||||
const input = editor.querySelector('input');
|
|
||||||
input.value = this.currentRecipe.tags ? this.currentRecipe.tags.join(', ') : '';
|
|
||||||
this.clearFieldDirty('tags');
|
|
||||||
|
|
||||||
// Hide editor
|
|
||||||
editor.classList.remove('active');
|
|
||||||
tagsContainer.querySelector('.editable-content').classList.remove('hide');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPromptEditors() {
|
setupPromptEditors() {
|
||||||
const promptConfigs = [
|
const promptConfigs = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ let priorityTagSuggestionsLoaded = false;
|
|||||||
let priorityTagSuggestionsPromise = null;
|
let priorityTagSuggestionsPromise = null;
|
||||||
let activeTagDragState = null;
|
let activeTagDragState = null;
|
||||||
|
|
||||||
|
// Configurable options for tag editing (set by setupTagEditMode)
|
||||||
|
let tagEditOptions = {
|
||||||
|
showSuggestions: true,
|
||||||
|
saveHandler: null,
|
||||||
|
onSaved: null,
|
||||||
|
normalizeTag: true,
|
||||||
|
};
|
||||||
|
|
||||||
function normalizeModelTypeKey(modelType) {
|
function normalizeModelTypeKey(modelType) {
|
||||||
if (!modelType) {
|
if (!modelType) {
|
||||||
return '';
|
return '';
|
||||||
@@ -140,13 +148,30 @@ let saveTagsHandler = null;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up tag editing mode
|
* Set up tag editing mode
|
||||||
|
* @param {string|null} modelType - Model type for suggestions (e.g. 'loras', 'checkpoints')
|
||||||
|
* @param {Object} [options] - Optional configuration
|
||||||
|
* @param {boolean} [options.showSuggestions=true] - Show priority tag suggestions dropdown
|
||||||
|
* @param {Function} [options.saveHandler] - Custom save function, async (filePath, tags) => {}
|
||||||
|
* @param {Function} [options.onSaved] - Called after successful save, (tags) => {}
|
||||||
|
* @param {boolean} [options.normalizeTag=true] - Lowercase tag on add
|
||||||
*/
|
*/
|
||||||
export function setupTagEditMode(modelType = null) {
|
export function setupTagEditMode(modelType = null, options = {}) {
|
||||||
const editBtn = document.querySelector('.edit-tags-btn');
|
// Store options for use by saveTags and addNewTag
|
||||||
|
tagEditOptions = {
|
||||||
|
showSuggestions: options.showSuggestions !== false,
|
||||||
|
saveHandler: options.saveHandler || null,
|
||||||
|
onSaved: options.onSaved || null,
|
||||||
|
normalizeTag: options.normalizeTag !== false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = options.container || document;
|
||||||
|
const editBtn = root.querySelector('.edit-tags-btn');
|
||||||
if (!editBtn) return;
|
if (!editBtn) return;
|
||||||
|
|
||||||
|
if (tagEditOptions.showSuggestions) {
|
||||||
setActiveModelTypeKey(modelType);
|
setActiveModelTypeKey(modelType);
|
||||||
ensurePriorityTagSuggestions();
|
ensurePriorityTagSuggestions();
|
||||||
|
}
|
||||||
|
|
||||||
// Store original tags for restoring on cancel
|
// Store original tags for restoring on cancel
|
||||||
let originalTags = [];
|
let originalTags = [];
|
||||||
@@ -158,7 +183,8 @@ export function setupTagEditMode(modelType = null) {
|
|||||||
|
|
||||||
// Create new handler and store reference
|
// Create new handler and store reference
|
||||||
const editBtnClickHandler = function() {
|
const editBtnClickHandler = function() {
|
||||||
const tagsSection = document.querySelector('.model-tags-container');
|
const tagsSection = this.closest('.model-tags-container');
|
||||||
|
if (!tagsSection) return;
|
||||||
const isEditMode = tagsSection.classList.toggle('edit-mode');
|
const isEditMode = tagsSection.classList.toggle('edit-mode');
|
||||||
const filePath = this.dataset.filePath;
|
const filePath = this.dataset.filePath;
|
||||||
|
|
||||||
@@ -193,16 +219,18 @@ export function setupTagEditMode(modelType = null) {
|
|||||||
tagsSection.appendChild(editContainer);
|
tagsSection.appendChild(editContainer);
|
||||||
|
|
||||||
// Setup the tag input field behavior
|
// Setup the tag input field behavior
|
||||||
setupTagInput();
|
setupTagInput(tagsSection);
|
||||||
|
|
||||||
// Create and add preset suggestions dropdown
|
// Create and add preset suggestions dropdown
|
||||||
|
if (tagEditOptions.showSuggestions) {
|
||||||
const tagForm = editContainer.querySelector('.metadata-add-form');
|
const tagForm = editContainer.querySelector('.metadata-add-form');
|
||||||
const suggestionsDropdown = createSuggestionsDropdown(originalTags);
|
const suggestionsDropdown = createSuggestionsDropdown(originalTags);
|
||||||
tagForm.appendChild(suggestionsDropdown);
|
tagForm.appendChild(suggestionsDropdown);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup delete buttons for existing tags
|
// Setup delete buttons for existing tags
|
||||||
setupDeleteButtons();
|
setupDeleteButtons();
|
||||||
setupTagDragAndDrop();
|
setupTagDragAndDrop(tagsSection);
|
||||||
|
|
||||||
// Transfer click event from original button to the cloned one
|
// Transfer click event from original button to the cloned one
|
||||||
const newEditBtn = editContainer.querySelector('.metadata-header-btn');
|
const newEditBtn = editContainer.querySelector('.metadata-header-btn');
|
||||||
@@ -218,7 +246,7 @@ export function setupTagEditMode(modelType = null) {
|
|||||||
// Just show the existing edit container
|
// Just show the existing edit container
|
||||||
tagsEditContainer.style.display = 'block';
|
tagsEditContainer.style.display = 'block';
|
||||||
editBtn.style.display = 'none';
|
editBtn.style.display = 'none';
|
||||||
setupTagDragAndDrop();
|
setupTagDragAndDrop(tagsSection);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Exit edit mode
|
// Exit edit mode
|
||||||
@@ -255,7 +283,7 @@ export function setupTagEditMode(modelType = null) {
|
|||||||
saveTagsHandler = function(e) {
|
saveTagsHandler = function(e) {
|
||||||
if (e.target.classList.contains('save-tags-btn') ||
|
if (e.target.classList.contains('save-tags-btn') ||
|
||||||
e.target.closest('.save-tags-btn')) {
|
e.target.closest('.save-tags-btn')) {
|
||||||
saveTags();
|
saveTags(e.target);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,19 +295,28 @@ export function setupTagEditMode(modelType = null) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Save tags
|
* Save tags
|
||||||
|
* @param {Element} [triggerElement] - The element that triggered the save (e.g. save button)
|
||||||
*/
|
*/
|
||||||
async function saveTags() {
|
async function saveTags(triggerElement = null) {
|
||||||
const editBtn = document.querySelector('.edit-tags-btn');
|
let editBtn;
|
||||||
if (!editBtn) return;
|
let scope;
|
||||||
|
if (triggerElement) {
|
||||||
|
scope = triggerElement.closest('.model-tags-container');
|
||||||
|
editBtn = scope ? scope.querySelector('.edit-tags-btn') : document.querySelector('.edit-tags-btn');
|
||||||
|
} else {
|
||||||
|
scope = document.querySelector('.model-tags-container');
|
||||||
|
editBtn = scope ? scope.querySelector('.edit-tags-btn') : null;
|
||||||
|
}
|
||||||
|
if (!editBtn || !scope) return;
|
||||||
|
|
||||||
const filePath = editBtn.dataset.filePath;
|
const filePath = editBtn.dataset.filePath;
|
||||||
const tagElements = document.querySelectorAll('.metadata-item');
|
const tagElements = scope.querySelectorAll('.metadata-item');
|
||||||
let tags = Array.from(tagElements).map(tag => tag.dataset.tag);
|
let tags = Array.from(tagElements).map(tag => tag.dataset.tag);
|
||||||
|
|
||||||
// Flush uncommitted input as a tag so it's not silently lost on save
|
// Flush uncommitted input as a tag so it's not silently lost on save
|
||||||
const tagInput = document.querySelector('.metadata-input');
|
const tagInput = scope.querySelector('.metadata-input');
|
||||||
if (tagInput) {
|
if (tagInput) {
|
||||||
const pendingTag = tagInput.value.trim().toLowerCase();
|
const pendingTag = tagEditOptions.normalizeTag ? tagInput.value.trim().toLowerCase() : tagInput.value.trim();
|
||||||
if (pendingTag && !tags.includes(pendingTag)) {
|
if (pendingTag && !tags.includes(pendingTag)) {
|
||||||
tags.push(pendingTag);
|
tags.push(pendingTag);
|
||||||
}
|
}
|
||||||
@@ -287,7 +324,7 @@ async function saveTags() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get original tags to compare
|
// Get original tags to compare
|
||||||
const originalTagElements = document.querySelectorAll('.tooltip-tag');
|
const originalTagElements = scope.querySelectorAll('.tooltip-tag');
|
||||||
const originalTags = Array.from(originalTagElements).map(tag => tag.textContent);
|
const originalTags = Array.from(originalTagElements).map(tag => tag.textContent);
|
||||||
|
|
||||||
// Check if tags have actually changed
|
// Check if tags have actually changed
|
||||||
@@ -301,14 +338,22 @@ async function saveTags() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save tags metadata
|
// Use custom save handler if provided, otherwise default model API
|
||||||
|
if (tagEditOptions.saveHandler) {
|
||||||
|
await tagEditOptions.saveHandler(filePath, tags);
|
||||||
|
} else {
|
||||||
await getModelApiClient().saveModelMetadata(filePath, { tags: tags });
|
await getModelApiClient().saveModelMetadata(filePath, { tags: tags });
|
||||||
|
}
|
||||||
|
|
||||||
// Set flag to skip restoring original tags when exiting edit mode
|
// Set flag to skip restoring original tags when exiting edit mode
|
||||||
editBtn.dataset.skipRestore = "true";
|
editBtn.dataset.skipRestore = "true";
|
||||||
|
|
||||||
|
// Use custom onSaved if provided (e.g. for recipe dirty state + re-render)
|
||||||
|
if (tagEditOptions.onSaved) {
|
||||||
|
tagEditOptions.onSaved(tags);
|
||||||
|
} else {
|
||||||
// Update the compact tags display
|
// Update the compact tags display
|
||||||
const compactTagsContainer = document.querySelector('.model-tags-container');
|
const compactTagsContainer = scope;
|
||||||
if (compactTagsContainer) {
|
if (compactTagsContainer) {
|
||||||
// Generate new compact tags HTML
|
// Generate new compact tags HTML
|
||||||
const compactTagsDisplay = compactTagsContainer.querySelector('.model-tags-compact');
|
const compactTagsDisplay = compactTagsContainer.querySelector('.model-tags-compact');
|
||||||
@@ -353,6 +398,7 @@ async function saveTags() {
|
|||||||
|
|
||||||
// Exit edit mode
|
// Exit edit mode
|
||||||
editBtn.click();
|
editBtn.click();
|
||||||
|
}
|
||||||
|
|
||||||
showToast('modelTags.messages.updated', {}, 'success');
|
showToast('modelTags.messages.updated', {}, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -470,16 +516,19 @@ function renderPriorityTagSuggestions(container, existingTags = []) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up tag input behavior
|
* Set up tag input behavior
|
||||||
|
* @param {Element} scopeContainer - The .model-tags-container element
|
||||||
*/
|
*/
|
||||||
function setupTagInput() {
|
function setupTagInput(scopeContainer) {
|
||||||
const tagInput = document.querySelector('.metadata-input');
|
const tagInput = scopeContainer
|
||||||
|
? scopeContainer.querySelector('.metadata-input')
|
||||||
|
: document.querySelector('.metadata-input');
|
||||||
|
|
||||||
if (tagInput) {
|
if (tagInput) {
|
||||||
tagInput.focus();
|
tagInput.focus();
|
||||||
tagInput.addEventListener('keydown', function(e) {
|
tagInput.addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addNewTag(this.value);
|
addNewTag(this.value, this);
|
||||||
this.value = ''; // Clear input after adding
|
this.value = ''; // Clear input after adding
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -504,9 +553,12 @@ function setupDeleteButtons() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable drag-and-drop sorting for tag items
|
* Enable drag-and-drop sorting for tag items
|
||||||
|
* @param {Element} [scopeContainer] - Optional scoped .model-tags-container element
|
||||||
*/
|
*/
|
||||||
function setupTagDragAndDrop() {
|
function setupTagDragAndDrop(scopeContainer) {
|
||||||
const container = document.querySelector(METADATA_ITEMS_CONTAINER_SELECTOR);
|
const container = scopeContainer
|
||||||
|
? scopeContainer.querySelector(METADATA_ITEMS_CONTAINER_SELECTOR)
|
||||||
|
: document.querySelector(METADATA_ITEMS_CONTAINER_SELECTOR);
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -712,12 +764,14 @@ function finishPointerDrag() {
|
|||||||
/**
|
/**
|
||||||
* Add a new tag
|
* Add a new tag
|
||||||
* @param {string} tag - Tag to add
|
* @param {string} tag - Tag to add
|
||||||
|
* @param {Element} [scopeElement] - Element within the correct .model-tags-container for scoping
|
||||||
*/
|
*/
|
||||||
function addNewTag(tag) {
|
function addNewTag(tag, scopeElement = null) {
|
||||||
tag = tag.trim().toLowerCase();
|
tag = tagEditOptions.normalizeTag ? tag.trim().toLowerCase() : tag.trim();
|
||||||
if (!tag) return;
|
if (!tag) return;
|
||||||
|
|
||||||
const tagsContainer = document.querySelector('.metadata-items');
|
const scope = scopeElement ? scopeElement.closest('.model-tags-container') : document;
|
||||||
|
const tagsContainer = scope.querySelector('.metadata-items');
|
||||||
if (!tagsContainer) return;
|
if (!tagsContainer) return;
|
||||||
|
|
||||||
// Validation: Check length
|
// Validation: Check length
|
||||||
@@ -762,7 +816,7 @@ function addNewTag(tag) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tagsContainer.appendChild(newTag);
|
tagsContainer.appendChild(newTag);
|
||||||
setupTagDragAndDrop();
|
setupTagDragAndDrop(scope);
|
||||||
|
|
||||||
// Update status of items in the suggestions dropdown
|
// Update status of items in the suggestions dropdown
|
||||||
updateSuggestionsDropdown();
|
updateSuggestionsDropdown();
|
||||||
|
|||||||
@@ -78,10 +78,12 @@ export function renderCompactTags(tags, filePath = '') {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up tag tooltip functionality
|
* Set up tag tooltip functionality
|
||||||
|
* @param {Element} [scopeContainer] - Optional container to scope the querySelector
|
||||||
*/
|
*/
|
||||||
export function setupTagTooltip() {
|
export function setupTagTooltip(scopeContainer = null) {
|
||||||
const tagsContainer = document.querySelector('.model-tags-container');
|
const root = scopeContainer || document;
|
||||||
const tooltip = document.querySelector('.model-tags-tooltip');
|
const tagsContainer = root.querySelector('.model-tags-container');
|
||||||
|
const tooltip = root.querySelector('.model-tags-tooltip');
|
||||||
|
|
||||||
if (tagsContainer && tooltip) {
|
if (tagsContainer && tooltip) {
|
||||||
tagsContainer.addEventListener('mouseenter', () => {
|
tagsContainer.addEventListener('mouseenter', () => {
|
||||||
|
|||||||
@@ -6,13 +6,8 @@
|
|||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<!-- Header Actions: populated dynamically in RecipeModal.js -->
|
<!-- Header Actions: populated dynamically in RecipeModal.js -->
|
||||||
<div class="recipe-header-actions" id="recipeHeaderActions"></div>
|
<div class="recipe-header-actions" id="recipeHeaderActions"></div>
|
||||||
<!-- Recipe Tags Container -->
|
<!-- Recipe Tags Container (rendered by renderCompactTags) -->
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|||||||
@@ -246,12 +246,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -375,12 +370,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -474,12 +464,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -588,12 +573,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -682,12 +662,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -790,12 +765,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -873,12 +843,10 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
recipeModal.markFieldDirty('title');
|
recipeModal.markFieldDirty('title');
|
||||||
recipeModal.markFieldDirty('tags');
|
|
||||||
recipeModal.markFieldDirty('prompt');
|
recipeModal.markFieldDirty('prompt');
|
||||||
recipeModal.markFieldDirty('negative_prompt');
|
recipeModal.markFieldDirty('negative_prompt');
|
||||||
|
|
||||||
document.querySelector('#recipeTitleEditor .title-input').value = 'Local Title';
|
document.querySelector('#recipeTitleEditor .title-input').value = 'Local Title';
|
||||||
document.querySelector('#recipeTagsEditor .tags-input').value = 'local-tag-1, local-tag-2';
|
|
||||||
document.getElementById('recipePromptInput').value = 'local prompt';
|
document.getElementById('recipePromptInput').value = 'local prompt';
|
||||||
document.getElementById('recipeNegativePromptInput').value = 'local negative';
|
document.getElementById('recipeNegativePromptInput').value = 'local negative';
|
||||||
|
|
||||||
@@ -899,7 +867,6 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
await flushAsyncTasks();
|
await flushAsyncTasks();
|
||||||
|
|
||||||
expect(document.querySelector('#recipeTitleEditor .title-input').value).toBe('Local Title');
|
expect(document.querySelector('#recipeTitleEditor .title-input').value).toBe('Local Title');
|
||||||
expect(document.querySelector('#recipeTagsEditor .tags-input').value).toBe('local-tag-1, local-tag-2');
|
|
||||||
expect(document.getElementById('recipePromptInput').value).toBe('local prompt');
|
expect(document.getElementById('recipePromptInput').value).toBe('local prompt');
|
||||||
expect(document.getElementById('recipeNegativePromptInput').value).toBe('local negative');
|
expect(document.getElementById('recipeNegativePromptInput').value).toBe('local negative');
|
||||||
expect(recipeModal.currentRecipe.title).toBe('Hydrated Title');
|
expect(recipeModal.currentRecipe.title).toBe('Hydrated Title');
|
||||||
@@ -918,12 +885,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1057,12 +1019,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1170,8 +1127,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div id="recipeModal" class="modal">
|
<div id="recipeModal" class="modal">
|
||||||
<div id="recipeModalTitle"></div>
|
<div id="recipeModalTitle"></div>
|
||||||
<div id="recipePreviewContainer"></div>
|
<div id="recipePreviewContainer"></div>
|
||||||
<div id="recipeTagsCompact"></div>
|
<div id="recipeTagsContainer"></div>
|
||||||
<div id="recipeTagsTooltip"><div id="recipeTagsTooltipContent"></div></div>
|
|
||||||
<div id="recipePrompt"></div>
|
<div id="recipePrompt"></div>
|
||||||
<textarea id="recipePromptInput"></textarea>
|
<textarea id="recipePromptInput"></textarea>
|
||||||
<div id="recipeNegativePrompt"></div>
|
<div id="recipeNegativePrompt"></div>
|
||||||
@@ -1224,8 +1180,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div id="recipeModal" class="modal">
|
<div id="recipeModal" class="modal">
|
||||||
<div id="recipeModalTitle"></div>
|
<div id="recipeModalTitle"></div>
|
||||||
<div id="recipePreviewContainer"></div>
|
<div id="recipePreviewContainer"></div>
|
||||||
<div id="recipeTagsCompact"></div>
|
<div id="recipeTagsContainer"></div>
|
||||||
<div id="recipeTagsTooltip"><div id="recipeTagsTooltipContent"></div></div>
|
|
||||||
<div id="recipePrompt"></div>
|
<div id="recipePrompt"></div>
|
||||||
<textarea id="recipePromptInput"></textarea>
|
<textarea id="recipePromptInput"></textarea>
|
||||||
<div id="recipeNegativePrompt"></div>
|
<div id="recipeNegativePrompt"></div>
|
||||||
@@ -1300,12 +1255,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1418,12 +1368,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1541,12 +1486,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1654,12 +1594,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1776,12 +1711,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -1878,12 +1808,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
@@ -2007,12 +1932,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<header class="recipe-modal-header">
|
<header class="recipe-modal-header">
|
||||||
<h2 id="recipeModalTitle">Recipe Details</h2>
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
<div class="recipe-tags-container">
|
<div id="recipeTagsContainer"></div>
|
||||||
<div class="recipe-tags-compact" id="recipeTagsCompact"></div>
|
|
||||||
<div class="recipe-tags-tooltip" id="recipeTagsTooltip">
|
|
||||||
<div class="tooltip-content" id="recipeTagsTooltipContent"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="recipe-top-section">
|
<div class="recipe-top-section">
|
||||||
|
|||||||
Reference in New Issue
Block a user