172 lines
5.9 KiB
JavaScript
172 lines
5.9 KiB
JavaScript
import { NotesApi } from "../api/notesApi.js";
|
|
|
|
export class AppendMenu {
|
|
constructor(textArea, onContentChange) {
|
|
this.textArea = textArea;
|
|
this.onContentChange = onContentChange;
|
|
this.container = null;
|
|
this.button = null;
|
|
this.submenu = null;
|
|
this.isDisabled = true;
|
|
}
|
|
|
|
createMenuElements() {
|
|
// Append submenu container
|
|
this.container = document.createElement('div');
|
|
this.container.className = 'notes-submenu-container';
|
|
|
|
this.button = document.createElement('button');
|
|
this.button.innerHTML = '<i class="pi pi-plus-circle"></i>';
|
|
this.button.className = 'notes-btn';
|
|
this.button.title = 'Append';
|
|
this.button.onclick = () => this.toggleMenu();
|
|
this.button.disabled = this.isDisabled;
|
|
|
|
// Create submenu
|
|
this.submenu = document.createElement('div');
|
|
this.submenu.className = 'notes-submenu';
|
|
this.submenu.style.display = 'none';
|
|
|
|
// Create submenu items
|
|
const rawOption = document.createElement('div');
|
|
rawOption.className = 'notes-submenu-item';
|
|
rawOption.textContent = 'Append list of LoRA filenames';
|
|
rawOption.onclick = () => this.selectOption('raw');
|
|
|
|
const tagOption = document.createElement('div');
|
|
tagOption.className = 'notes-submenu-item';
|
|
tagOption.textContent = "Append list of LoRAs in '<lora:name>' format";
|
|
tagOption.onclick = () => this.selectOption('tag');
|
|
|
|
const metaOption = document.createElement('div');
|
|
metaOption.className = 'notes-submenu-item';
|
|
metaOption.textContent = "Append list of LoRAs in '<lora:name>, metadata' format";
|
|
metaOption.onclick = () => this.selectOption('meta');
|
|
|
|
this.submenu.appendChild(rawOption);
|
|
this.submenu.appendChild(tagOption);
|
|
this.submenu.appendChild(metaOption);
|
|
|
|
this.container.appendChild(this.button);
|
|
this.container.appendChild(this.submenu);
|
|
|
|
return this.container;
|
|
}
|
|
|
|
setDisabled(disabled) {
|
|
this.isDisabled = disabled;
|
|
if (this.button) {
|
|
this.button.disabled = disabled;
|
|
}
|
|
}
|
|
|
|
toggleMenu() {
|
|
if (this.submenu.style.display === 'none') {
|
|
this.submenu.style.display = 'block';
|
|
// Add click listener to close menu when clicking outside
|
|
document.addEventListener('click', this.closeMenuHandler);
|
|
} else {
|
|
this.submenu.style.display = 'none';
|
|
document.removeEventListener('click', this.closeMenuHandler);
|
|
}
|
|
}
|
|
|
|
closeMenuHandler = (event) => {
|
|
if (!this.container.contains(event.target)) {
|
|
this.submenu.style.display = 'none';
|
|
document.removeEventListener('click', this.closeMenuHandler);
|
|
}
|
|
}
|
|
|
|
selectOption(type) {
|
|
this.submenu.style.display = 'none';
|
|
document.removeEventListener('click', this.closeMenuHandler);
|
|
|
|
this.appendLoras(type);
|
|
}
|
|
|
|
async appendLoras(formatType) {
|
|
try {
|
|
let formattedText;
|
|
|
|
switch(formatType) {
|
|
case 'raw':
|
|
formattedText = await this.formatLorasRaw();
|
|
break;
|
|
case 'tag':
|
|
formattedText = await this.formatLorasTags();
|
|
break;
|
|
case 'meta':
|
|
formattedText = await this.formatLorasWithMetadata();
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown format type: ${formatType}`);
|
|
}
|
|
|
|
this.insertTextIntoTextarea(formattedText);
|
|
} catch (error) {
|
|
console.error('Error appending LoRAs:', error);
|
|
alert(`Failed to append LoRAs ${formatType === 'raw' ? 'list' : formatType === 'tag' ? 'tags' : 'with metadata'}`);
|
|
}
|
|
}
|
|
|
|
async formatLorasRaw() {
|
|
const loras = await NotesApi.getLoras();
|
|
|
|
// Normalize backslashes to forward slashes
|
|
const normalizedLoras = loras.map(lora => lora.replace(/\\/g, '/'));
|
|
return normalizedLoras.join('\n');
|
|
}
|
|
|
|
async formatLorasTags() {
|
|
const loras = await NotesApi.getLoras();
|
|
|
|
// Format each LoRA as <lora:name>, stripping .safetensors extension and normalizing slashes
|
|
const lorasTags = loras.map(lora => {
|
|
const nameWithoutExt = lora.replace(/\.safetensors$/i, '');
|
|
const normalizedPath = nameWithoutExt.replace(/\\/g, '/');
|
|
return `<lora:${normalizedPath}>`;
|
|
});
|
|
return lorasTags.join('\n');
|
|
}
|
|
|
|
async formatLorasWithMetadata() {
|
|
const loras = await NotesApi.getLorasWithMetadata();
|
|
|
|
// Format each LoRA with metadata as <lora:name>, metadata
|
|
const lorasWithMeta = loras.map(lora => {
|
|
const nameWithoutExt = lora.name.replace(/\.safetensors$/i, '');
|
|
const normalizedPath = nameWithoutExt.replace(/\\/g, '/');
|
|
const baseTag = `<lora:${normalizedPath}>`;
|
|
|
|
if (lora.metadata) {
|
|
return `${baseTag}, ${lora.metadata}`;
|
|
} else {
|
|
return baseTag;
|
|
}
|
|
});
|
|
return lorasWithMeta.join('\n');
|
|
}
|
|
|
|
insertTextIntoTextarea(text) {
|
|
if (!this.textArea) return;
|
|
|
|
// Add newline before appending if current content doesn't end with one
|
|
const separator = this.textArea.value && !this.textArea.value.endsWith('\n') ? '\n' : '';
|
|
const textToInsert = separator + text;
|
|
|
|
// Focus textarea to ensure it's active
|
|
this.textArea.focus();
|
|
|
|
// Move cursor to end
|
|
this.textArea.setSelectionRange(this.textArea.value.length, this.textArea.value.length);
|
|
|
|
// Use document.execCommand to preserve undo history
|
|
document.execCommand('insertText', false, textToInsert);
|
|
|
|
// Trigger change handling
|
|
if (this.onContentChange) {
|
|
this.onContentChange();
|
|
}
|
|
}
|
|
} |