641 lines
19 KiB
JavaScript
641 lines
19 KiB
JavaScript
import { state } from './state.js';
|
|
import { showStatus } from './utils.js';
|
|
import {
|
|
getImageUrl,
|
|
saveCharacters,
|
|
saveModels,
|
|
saveStyles,
|
|
saveTags,
|
|
renameCharacter,
|
|
deleteImage,
|
|
uploadImage,
|
|
} from './api.js';
|
|
import { setupAutocomplete } from './autocomplete.js';
|
|
import { setupModalDragAndDrop } from './dragdrop.js';
|
|
import { setupWeightAdjustment } from './weight-adjustment.js';
|
|
|
|
let pendingImage = null; // Store pending image for new characters
|
|
|
|
export function showEditModal(type, name) {
|
|
state.currentEditName = name;
|
|
state.currentEditType = type;
|
|
|
|
if (type === 'character') {
|
|
const data = state.characters[name] || {
|
|
character: '',
|
|
top: '',
|
|
bottom: '',
|
|
neg: '',
|
|
categories: ''
|
|
};
|
|
|
|
state.currentOriginalName = name;
|
|
document.getElementById('editCharNameInput').value = name;
|
|
document.getElementById('editCharacter').value = data.character || '';
|
|
document.getElementById('editTop').value = data.top || '';
|
|
document.getElementById('editBottom').value = data.bottom || '';
|
|
document.getElementById('editNeg').value = data.neg || '';
|
|
document.getElementById('editCategories').value = data.categories || '';
|
|
|
|
// Show modal first so elements exist
|
|
document.getElementById('editModal').classList.add('show');
|
|
|
|
// Now access the preview elements
|
|
const previewImg = document.getElementById('previewImg');
|
|
const dropZone = document.getElementById('charImageDropZone');
|
|
|
|
if (previewImg && dropZone) {
|
|
// Handle preview image
|
|
if (name && state.characterImages[name]) {
|
|
previewImg.src = getImageUrl(name);
|
|
previewImg.style.display = 'block';
|
|
dropZone.classList.add('has-image');
|
|
window.pendingCharacterImage = null;
|
|
} else if (window.pendingCharacterImage) {
|
|
previewImg.src = window.pendingCharacterImage;
|
|
previewImg.style.display = 'block';
|
|
dropZone.classList.add('has-image');
|
|
} else {
|
|
previewImg.style.display = 'none';
|
|
dropZone.classList.remove('has-image');
|
|
window.pendingCharacterImage = null;
|
|
}
|
|
}
|
|
|
|
if (name) {
|
|
setupModalDragAndDrop('editModal', name, 'character');
|
|
} else {
|
|
setupModalDragAndDrop('editModal', '', 'character');
|
|
}
|
|
|
|
setupAutocomplete(document.getElementById('editCharacter'));
|
|
setupAutocomplete(document.getElementById('editTop'));
|
|
setupAutocomplete(document.getElementById('editBottom'));
|
|
setupAutocomplete(document.getElementById('editNeg'));
|
|
setupAutocomplete(document.getElementById('editCategories'));
|
|
setupAutocomplete(document.getElementById('editCharNameInput'), false);
|
|
|
|
setupWeightAdjustment(document.getElementById('editCharacter'));
|
|
setupWeightAdjustment(document.getElementById('editTop'));
|
|
setupWeightAdjustment(document.getElementById('editBottom'));
|
|
setupWeightAdjustment(document.getElementById('editNeg'));
|
|
setupWeightAdjustment(document.getElementById('editCategories'));
|
|
setupWeightAdjustment(document.getElementById('editCharNameInput'));
|
|
|
|
// Don't show modal again - already shown above
|
|
}
|
|
else if (type === 'model') {
|
|
const data = state.models[name] || {
|
|
quality: { positive: '', negative: '' },
|
|
embeddings: { positive: '', negative: '' }
|
|
};
|
|
|
|
state.currentOriginalName = name;
|
|
document.getElementById('editModelNameInput').value = name;
|
|
document.getElementById('editModelQualityPos').value =
|
|
data.quality?.positive || '';
|
|
document.getElementById('editModelQualityNeg').value =
|
|
data.quality?.negative || '';
|
|
document.getElementById('editModelEmbedPos').value =
|
|
data.embeddings?.positive || '';
|
|
document.getElementById('editModelEmbedNeg').value =
|
|
data.embeddings?.negative || '';
|
|
|
|
setupAutocomplete(document.getElementById('editModelQualityPos'));
|
|
setupAutocomplete(document.getElementById('editModelQualityNeg'));
|
|
setupAutocomplete(document.getElementById('editModelEmbedPos'));
|
|
setupAutocomplete(document.getElementById('editModelEmbedNeg'));
|
|
|
|
setupWeightAdjustment(document.getElementById('editModelQualityPos'));
|
|
setupWeightAdjustment(document.getElementById('editModelQualityNeg'));
|
|
setupWeightAdjustment(document.getElementById('editModelEmbedPos'));
|
|
setupWeightAdjustment(document.getElementById('editModelEmbedNeg'));
|
|
|
|
document.getElementById('modelEditModal').classList.add('show');
|
|
}
|
|
else if (type === 'style') {
|
|
const data = state.styles[name] || {
|
|
positive: '',
|
|
negative: ''
|
|
};
|
|
|
|
state.currentOriginalName = name;
|
|
document.getElementById('editStyleNameInput').value = name;
|
|
document.getElementById('editStylePos').value = data.positive || '';
|
|
document.getElementById('editStyleNeg').value = data.negative || '';
|
|
|
|
// Show modal first so elements exist
|
|
document.getElementById('styleEditModal').classList.add('show');
|
|
|
|
// Now access the preview elements
|
|
const previewImg = document.getElementById('stylePreviewImg');
|
|
const dropZone = document.getElementById('styleImageDropZone');
|
|
|
|
if (previewImg && dropZone) {
|
|
if (name && state.styleImages[name]) {
|
|
previewImg.src = getImageUrl(name, 'style');
|
|
previewImg.style.display = 'block';
|
|
dropZone.classList.add('has-image');
|
|
window.pendingStyleImage = null;
|
|
} else if (window.pendingStyleImage) {
|
|
previewImg.src = window.pendingStyleImage;
|
|
previewImg.style.display = 'block';
|
|
dropZone.classList.add('has-image');
|
|
} else {
|
|
previewImg.style.display = 'none';
|
|
dropZone.classList.remove('has-image');
|
|
window.pendingStyleImage = null;
|
|
}
|
|
}
|
|
|
|
if (name) {
|
|
setupModalDragAndDrop('styleEditModal', name, 'style');
|
|
} else {
|
|
setupModalDragAndDrop('styleEditModal', '', 'style');
|
|
}
|
|
|
|
setupAutocomplete(document.getElementById('editStylePos'));
|
|
setupAutocomplete(document.getElementById('editStyleNeg'));
|
|
|
|
setupWeightAdjustment(document.getElementById('editStylePos'));
|
|
setupWeightAdjustment(document.getElementById('editStyleNeg'));
|
|
|
|
// Don't show modal again - already shown above
|
|
}
|
|
else if (type === 'tag') {
|
|
const data = state.tags[name] || {
|
|
positive: '',
|
|
negative: ''
|
|
};
|
|
|
|
state.currentOriginalName = name;
|
|
document.getElementById('editTagNameInput').value = name;
|
|
document.getElementById('editTagPos').value = data.positive || '';
|
|
document.getElementById('editTagNeg').value = data.negative || '';
|
|
|
|
setupAutocomplete(document.getElementById('editTagPos'));
|
|
setupAutocomplete(document.getElementById('editTagNeg'));
|
|
setupAutocomplete(document.getElementById('editTagNameInput'));
|
|
|
|
setupWeightAdjustment(document.getElementById('editTagPos'));
|
|
setupWeightAdjustment(document.getElementById('editTagNeg'));
|
|
setupWeightAdjustment(document.getElementById('editTagNameInput'));
|
|
|
|
document.getElementById('tagEditModal').classList.add('show');
|
|
}
|
|
else if (type === 'wildcard') {
|
|
const data = state.wildcards[name] || '';
|
|
|
|
state.currentOriginalName = name;
|
|
document.getElementById('editWildcardNameInput').value = name;
|
|
document.getElementById('editWildcardOptions').value = data;
|
|
|
|
setupAutocomplete(
|
|
document.getElementById('editWildcardOptions')
|
|
);
|
|
setupWeightAdjustment(
|
|
document.getElementById('editWildcardOptions')
|
|
);
|
|
|
|
document.getElementById('wildcardEditModal')
|
|
.classList.add('show');
|
|
}
|
|
}
|
|
|
|
export function hideEditModal(type) {
|
|
if (type === 'character' || !type) {
|
|
document.getElementById('editModal').classList.remove('show');
|
|
}
|
|
if (type === 'model') {
|
|
document.getElementById('modelEditModal').classList.remove('show');
|
|
}
|
|
if (type === 'style') {
|
|
document.getElementById('styleEditModal').classList.remove('show');
|
|
}
|
|
if (type === 'tag') {
|
|
document.getElementById('tagEditModal').classList.remove('show');
|
|
}
|
|
if (type === 'wildcard') {
|
|
document.getElementById('wildcardEditModal').classList.remove('show');
|
|
}
|
|
state.currentEditName = null;
|
|
state.currentEditType = null;
|
|
state.currentOriginalName = null;
|
|
}
|
|
|
|
|
|
export async function saveCharacter() {
|
|
const newName = document.getElementById('editCharNameInput').value.trim();
|
|
|
|
if (!newName) {
|
|
alert('Character name cannot be empty');
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName &&
|
|
state.currentOriginalName &&
|
|
state.characters[newName]) {
|
|
alert('A character with this name already exists');
|
|
return;
|
|
}
|
|
|
|
const characterData = {
|
|
character: document.getElementById('editCharacter').value,
|
|
top: document.getElementById('editTop').value,
|
|
bottom: document.getElementById('editBottom').value,
|
|
neg: document.getElementById('editNeg').value,
|
|
categories: document.getElementById('editCategories').value
|
|
};
|
|
|
|
// Handle image upload
|
|
const fileInput = document.getElementById('editImage');
|
|
let imageToUpload = null;
|
|
|
|
if (fileInput.files.length > 0) {
|
|
imageToUpload = fileInput.files[0];
|
|
} else if (window.pendingCharacterImage) {
|
|
// Convert base64 to blob for upload
|
|
const response = await fetch(window.pendingCharacterImage);
|
|
imageToUpload = await response.blob();
|
|
}
|
|
|
|
if (!state.currentOriginalName) {
|
|
if (state.characters[newName]) {
|
|
alert('A character with this name already exists');
|
|
return;
|
|
}
|
|
|
|
state.characters[newName] = characterData;
|
|
|
|
try {
|
|
await saveCharacters(state.characters);
|
|
|
|
// Upload image after character is created
|
|
if (imageToUpload) {
|
|
await uploadImage(imageToUpload, newName, 'character');
|
|
window.pendingCharacterImage = null;
|
|
}
|
|
|
|
showStatus('Character created successfully!', 'success');
|
|
if (window.renderAll) window.renderAll();
|
|
hideEditModal('character');
|
|
} catch (error) {
|
|
showStatus('Error creating character: ' + error.message, 'error');
|
|
delete state.characters[newName];
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName) {
|
|
try {
|
|
await renameCharacter(state.currentOriginalName, newName, characterData);
|
|
|
|
delete state.characters[state.currentOriginalName];
|
|
state.characters[newName] = characterData;
|
|
|
|
if (state.characterImages[state.currentOriginalName]) {
|
|
state.characterImages[newName] = true;
|
|
delete state.characterImages[state.currentOriginalName];
|
|
}
|
|
|
|
showStatus('Character renamed successfully!', 'success');
|
|
if (window.renderAll) window.renderAll();
|
|
hideEditModal('character');
|
|
} catch (error) {
|
|
showStatus('Error renaming character: ' + error.message, 'error');
|
|
}
|
|
} else {
|
|
state.characters[state.currentOriginalName] = characterData;
|
|
|
|
try {
|
|
await saveCharacters(state.characters);
|
|
showStatus('Character saved successfully!', 'success');
|
|
if (window.renderAll) window.renderAll();
|
|
hideEditModal('character');
|
|
} catch (error) {
|
|
showStatus('Error saving character: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
if (imageToUpload) {
|
|
await uploadImage(imageToUpload,
|
|
state.currentOriginalName || newName,
|
|
'character');
|
|
window.pendingCharacterImage = null;
|
|
}
|
|
}
|
|
|
|
export async function saveItem(type) {
|
|
if (!state.currentEditName && state.currentEditName !== '') return;
|
|
|
|
if (type === 'character') {
|
|
await saveCharacter();
|
|
} else if (type === 'model') {
|
|
const newName = document.getElementById('editModelNameInput').value.trim();
|
|
|
|
if (!newName) {
|
|
alert('Model name cannot be empty');
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName && state.currentOriginalName && state.models[newName]) {
|
|
alert('A model with this name already exists');
|
|
return;
|
|
}
|
|
|
|
const modelData = {
|
|
quality: {
|
|
positive: document.getElementById('editModelQualityPos').value,
|
|
negative: document.getElementById('editModelQualityNeg').value
|
|
},
|
|
embeddings: {
|
|
positive: document.getElementById('editModelEmbedPos').value,
|
|
negative: document.getElementById('editModelEmbedNeg').value
|
|
}
|
|
};
|
|
|
|
if (!state.currentOriginalName) {
|
|
if (state.models[newName]) {
|
|
alert('A model with this name already exists');
|
|
return;
|
|
}
|
|
state.models[newName] = modelData;
|
|
} else if (newName !== state.currentOriginalName) {
|
|
delete state.models[state.currentOriginalName];
|
|
state.models[newName] = modelData;
|
|
} else {
|
|
state.models[state.currentOriginalName] = modelData;
|
|
}
|
|
|
|
try {
|
|
await saveModels(state.models);
|
|
showStatus('Model saved successfully!', 'success');
|
|
if (window.renderModels) window.renderModels();
|
|
hideEditModal('model');
|
|
} catch (error) {
|
|
showStatus('Error saving model: ' + error.message, 'error');
|
|
}
|
|
} else if (type === 'style') {
|
|
const newName = document.getElementById('editStyleNameInput').value.trim();
|
|
|
|
if (!newName) {
|
|
alert('Style name cannot be empty');
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName && state.currentOriginalName && state.styles[newName]) {
|
|
alert('A style with this name already exists');
|
|
return;
|
|
}
|
|
|
|
const styleData = {
|
|
positive: document.getElementById('editStylePos').value,
|
|
negative: document.getElementById('editStyleNeg').value
|
|
};
|
|
|
|
// Handle image upload
|
|
const fileInput = document.getElementById('editStyleImage');
|
|
let imageToUpload = null;
|
|
|
|
if (fileInput.files.length > 0) {
|
|
imageToUpload = fileInput.files[0];
|
|
} else if (window.pendingStyleImage) {
|
|
// Convert base64 to blob for upload
|
|
const response = await fetch(window.pendingStyleImage);
|
|
imageToUpload = await response.blob();
|
|
}
|
|
|
|
if (!state.currentOriginalName) {
|
|
if (state.styles[newName]) {
|
|
alert('A style with this name already exists');
|
|
return;
|
|
}
|
|
state.styles[newName] = styleData;
|
|
|
|
try {
|
|
await saveStyles(state.styles);
|
|
|
|
// Upload image after style is created
|
|
if (imageToUpload) {
|
|
await uploadImage(imageToUpload, newName, 'style');
|
|
window.pendingStyleImage = null;
|
|
}
|
|
|
|
showStatus('Style created successfully!', 'success');
|
|
if (window.renderStyles) window.renderStyles();
|
|
hideEditModal('style');
|
|
} catch (error) {
|
|
showStatus('Error creating style: ' + error.message, 'error');
|
|
delete state.styles[newName];
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName) {
|
|
delete state.styles[state.currentOriginalName];
|
|
state.styles[newName] = styleData;
|
|
|
|
if (state.styleImages[state.currentOriginalName]) {
|
|
state.styleImages[newName] = true;
|
|
delete state.styleImages[state.currentOriginalName];
|
|
}
|
|
} else {
|
|
state.styles[state.currentOriginalName] = styleData;
|
|
}
|
|
|
|
try {
|
|
await saveStyles(state.styles);
|
|
showStatus('Style saved successfully!', 'success');
|
|
if (window.renderStyles) window.renderStyles();
|
|
hideEditModal('style');
|
|
} catch (error) {
|
|
showStatus('Error saving style: ' + error.message, 'error');
|
|
}
|
|
|
|
if (imageToUpload) {
|
|
await uploadImage(imageToUpload, state.currentOriginalName || newName, 'style');
|
|
window.pendingStyleImage = null;
|
|
}
|
|
} else if (type === 'tag') {
|
|
const newName = document.getElementById('editTagNameInput').value.trim();
|
|
|
|
if (!newName) {
|
|
alert('Tag name cannot be empty');
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName &&
|
|
state.currentOriginalName &&
|
|
state.tags[newName]) {
|
|
alert('A tag preset with this name already exists');
|
|
return;
|
|
}
|
|
|
|
const tagData = {
|
|
positive: document.getElementById('editTagPos').value,
|
|
negative: document.getElementById('editTagNeg').value
|
|
};
|
|
|
|
if (!state.currentOriginalName) {
|
|
if (state.tags[newName]) {
|
|
alert('A tag preset with this name already exists');
|
|
return;
|
|
}
|
|
state.tags[newName] = tagData;
|
|
} else if (newName !== state.currentOriginalName) {
|
|
delete state.tags[state.currentOriginalName];
|
|
state.tags[newName] = tagData;
|
|
} else {
|
|
state.tags[state.currentOriginalName] = tagData;
|
|
}
|
|
|
|
try {
|
|
await saveTags(state.tags);
|
|
showStatus('Tag preset saved successfully!', 'success');
|
|
if (window.renderTags) window.renderTags();
|
|
hideEditModal('tag');
|
|
} catch (error) {
|
|
showStatus('Error saving tag preset: ' + error.message, 'error');
|
|
}
|
|
} else if (type === 'wildcard') {
|
|
const newName = document.getElementById('editWildcardNameInput')
|
|
.value.trim();
|
|
|
|
if (!newName) {
|
|
alert('Wildcard name cannot be empty');
|
|
return;
|
|
}
|
|
|
|
if (newName !== state.currentOriginalName &&
|
|
state.currentOriginalName &&
|
|
state.wildcards[newName]) {
|
|
alert('A wildcard with this name already exists');
|
|
return;
|
|
}
|
|
|
|
const options = document.getElementById('editWildcardOptions')
|
|
.value;
|
|
|
|
if (!state.currentOriginalName) {
|
|
if (state.wildcards[newName]) {
|
|
alert('A wildcard with this name already exists');
|
|
return;
|
|
}
|
|
state.wildcards[newName] = options;
|
|
} else if (newName !== state.currentOriginalName) {
|
|
delete state.wildcards[state.currentOriginalName];
|
|
state.wildcards[newName] = options;
|
|
} else {
|
|
state.wildcards[state.currentOriginalName] = options;
|
|
}
|
|
|
|
try {
|
|
await saveWildcards(state.wildcards);
|
|
showStatus('Wildcard saved successfully!', 'success');
|
|
if (window.renderWildcards) window.renderWildcards();
|
|
hideEditModal('wildcard');
|
|
} catch (error) {
|
|
showStatus('Error saving wildcard: ' + error.message, 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function deleteCurrentItem(type) {
|
|
if (!state.currentEditName) return;
|
|
|
|
const typeNames = {
|
|
character: 'character',
|
|
model: 'model',
|
|
style: 'style',
|
|
tag: 'tag preset'
|
|
};
|
|
|
|
if (!confirm(`Delete ${typeNames[type]} "${state.currentEditName}"?`)) return;
|
|
|
|
if (type === 'character') {
|
|
delete state.characters[state.currentEditName];
|
|
|
|
try {
|
|
await saveCharacters(state.characters);
|
|
showStatus('Character deleted successfully!', 'success');
|
|
if (window.renderAll) window.renderAll();
|
|
hideEditModal('character');
|
|
} catch (error) {
|
|
showStatus('Error deleting character: ' + error.message, 'error');
|
|
}
|
|
} else if (type === 'model') {
|
|
delete state.models[state.currentEditName];
|
|
|
|
try {
|
|
await saveModels(state.models);
|
|
showStatus('Model deleted successfully!', 'success');
|
|
if (window.renderModels) window.renderModels();
|
|
hideEditModal('model');
|
|
} catch (error) {
|
|
showStatus('Error deleting model: ' + error.message, 'error');
|
|
}
|
|
} else if (type === 'style') {
|
|
delete state.styles[state.currentEditName];
|
|
|
|
try {
|
|
await saveStyles(state.styles);
|
|
showStatus('Style deleted successfully!', 'success');
|
|
if (window.renderStyles) window.renderStyles();
|
|
hideEditModal('style');
|
|
} catch (error) {
|
|
showStatus('Error deleting style: ' + error.message, 'error');
|
|
}
|
|
} else if (type === 'tag') {
|
|
delete state.tags[state.currentEditName];
|
|
|
|
try {
|
|
await saveTags(state.tags);
|
|
showStatus('Tag preset deleted successfully!', 'success');
|
|
if (window.renderTags) window.renderTags();
|
|
hideEditModal('tag');
|
|
} catch (error) {
|
|
showStatus('Error deleting tag preset: ' + error.message, 'error');
|
|
}
|
|
} else if (type === 'wildcard') {
|
|
delete state.wildcards[state.currentEditName];
|
|
|
|
try {
|
|
await saveWildcards(state.wildcards);
|
|
showStatus('Wildcard deleted successfully!', 'success');
|
|
if (window.renderWildcards) window.renderWildcards();
|
|
hideEditModal('wildcard');
|
|
} catch (error) {
|
|
showStatus(
|
|
'Error deleting wildcard: ' + error.message,
|
|
'error'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function removeImage(type = 'character') {
|
|
if (!state.currentEditName) return;
|
|
|
|
const typeLabel = type === 'character' ? 'character' : 'style';
|
|
if (!confirm(`Remove image from this ${typeLabel}?`)) return;
|
|
|
|
try {
|
|
await deleteImage(state.currentEditName, type);
|
|
|
|
if (type === 'character') {
|
|
state.characterImages[state.currentEditName] = false;
|
|
document.getElementById('previewImg').style.display = 'none';
|
|
document.getElementById('charImageDropZone').classList.remove('has-image');
|
|
if (window.renderCharacters) window.renderCharacters();
|
|
} else {
|
|
state.styleImages[state.currentEditName] = false;
|
|
document.getElementById('stylePreviewImg').style.display = 'none';
|
|
document.getElementById('styleImageDropZone').classList.remove('has-image');
|
|
if (window.renderStyles) window.renderStyles();
|
|
}
|
|
showStatus('Image removed!', 'success');
|
|
} catch (error) {
|
|
showStatus('Error removing image: ' + error.message, 'error');
|
|
}
|
|
}
|