diff --git a/__init__.py b/__init__.py
index d9f8711..fbf9c02 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,6 +1,7 @@
import os
from aiohttp import web
from server import PromptServer
+import folder_paths
WEB_DIRECTORY = "./js"
@@ -76,4 +77,49 @@ async def delete_note(request):
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
+@PromptServer.instance.routes.get("/notes_sidebar/loras")
+async def get_loras_list(request):
+ try:
+ loras = folder_paths.get_filename_list("loras")
+ return web.json_response({"loras": loras})
+ except Exception as e:
+ return web.json_response({"error": str(e)}, status=500)
+
+@PromptServer.instance.routes.get("/notes_sidebar/loras_with_metadata")
+async def get_loras_with_metadata(request):
+ try:
+ loras = folder_paths.get_filename_list("loras")
+
+ loras_with_metadata = []
+ for lora in loras:
+ # Get the full path to the lora file
+ lora_full_path = folder_paths.get_full_path("loras", lora)
+
+ metadata = ""
+ if lora_full_path:
+ # Get the directory and base name
+ lora_dir = os.path.dirname(lora_full_path)
+ base_name = os.path.splitext(os.path.basename(lora_full_path))[0]
+ txt_file_path = os.path.join(lora_dir, f"{base_name}.txt")
+
+ if os.path.exists(txt_file_path):
+ try:
+ with open(txt_file_path, 'r', encoding='utf-8') as f:
+ lines = f.read().strip().split('\n')
+ # Join multiple lines with " | "
+ metadata = " | ".join(line.strip() for line in lines if line.strip())
+ except Exception as e:
+ print(f"Error reading metadata for {lora}: {e}")
+
+ loras_with_metadata.append({
+ "name": lora,
+ "metadata": metadata
+ })
+
+ print(f"Processed Lora: {lora}, Metadata: {metadata}")
+
+ return web.json_response({"loras": loras_with_metadata})
+ except Exception as e:
+ return web.json_response({"error": str(e)}, status=500)
+
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
\ No newline at end of file
diff --git a/js/extension.js b/js/extension.js
index 260eddd..c320103 100644
--- a/js/extension.js
+++ b/js/extension.js
@@ -108,6 +108,9 @@ class NotesSidebar {
this.saveBtn = null;
this.renameBtn = null;
this.deleteBtn = null;
+ this.appendLorasBtn = null;
+ this.appendLorasTagsBtn = null;
+ this.appendLorasMetaBtn = null;
this.isInitialized = false;
this.sidebarElement = null;
@@ -170,7 +173,7 @@ class NotesSidebar {
}
updateButtonState() {
- if (!this.saveBtn || !this.renameBtn || !this.deleteBtn) return;
+ if (!this.saveBtn || !this.renameBtn || !this.deleteBtn || !this.appendLorasBtn || !this.appendLorasTagsBtn || !this.appendLorasMetaBtn) return;
const hasActiveNote = !!this.activeNote;
const hasUnsavedChanges = hasActiveNote && this.unsavedChanges.get(this.activeNote);
@@ -178,9 +181,12 @@ class NotesSidebar {
// Save button: enabled only when there's an active note AND unsaved changes
this.saveBtn.disabled = !hasActiveNote || !hasUnsavedChanges;
- // Rename and delete buttons: enabled only when there's an active note
+ // Rename, delete, and append buttons: enabled only when there's an active note
this.renameBtn.disabled = !hasActiveNote;
this.deleteBtn.disabled = !hasActiveNote;
+ this.appendLorasBtn.disabled = !hasActiveNote;
+ this.appendLorasTagsBtn.disabled = !hasActiveNote;
+ this.appendLorasMetaBtn.disabled = !hasActiveNote;
}
async init() {
@@ -284,8 +290,32 @@ class NotesSidebar {
this.deleteBtn.onclick = () => this.deleteActiveNote();
this.deleteBtn.disabled = true;
+ this.appendLorasBtn = document.createElement('button');
+ this.appendLorasBtn.innerHTML = '';
+ this.appendLorasBtn.className = 'notes-btn append-loras';
+ this.appendLorasBtn.title = 'Append LoRAs List';
+ this.appendLorasBtn.onclick = () => this.appendLorasList();
+ this.appendLorasBtn.disabled = true;
+
+ this.appendLorasTagsBtn = document.createElement('button');
+ this.appendLorasTagsBtn.innerHTML = '';
+ this.appendLorasTagsBtn.className = 'notes-btn append-loras-tags';
+ this.appendLorasTagsBtn.title = 'Append LoRAs as Tags';
+ this.appendLorasTagsBtn.onclick = () => this.appendLorasTags();
+ this.appendLorasTagsBtn.disabled = true;
+
+ this.appendLorasMetaBtn = document.createElement('button');
+ this.appendLorasMetaBtn.innerHTML = '';
+ this.appendLorasMetaBtn.className = 'notes-btn append-loras-meta';
+ this.appendLorasMetaBtn.title = 'Append LoRAs with Metadata';
+ this.appendLorasMetaBtn.onclick = () => this.appendLorasWithMetadata();
+ this.appendLorasMetaBtn.disabled = true;
+
leftButtonContainer.appendChild(this.saveBtn);
- rightButtonContainer.appendChild(this.renameBtn);
+ leftButtonContainer.appendChild(this.renameBtn);
+ leftButtonContainer.appendChild(this.appendLorasBtn);
+ leftButtonContainer.appendChild(this.appendLorasTagsBtn);
+ leftButtonContainer.appendChild(this.appendLorasMetaBtn);
rightButtonContainer.appendChild(this.deleteBtn);
buttonContainer.appendChild(leftButtonContainer);
@@ -548,6 +578,125 @@ class NotesSidebar {
const hasUnsavedChanges = currentContent !== savedContent;
this.store.setUnsavedChanges(this.activeNote, hasUnsavedChanges);
}
+
+ async appendLorasList() {
+ try {
+ const response = await fetch('/notes_sidebar/loras');
+ const data = await response.json();
+
+ if (response.ok && data.loras) {
+ // Normalize backslashes to forward slashes
+ const normalizedLoras = data.loras.map(lora => lora.replace(/\\/g, '/'));
+ const lorasText = normalizedLoras.join('\n');
+
+ // 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 + lorasText;
+
+ // 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
+ this.onTextAreaChange();
+ } else {
+ console.error('Error fetching LoRAs:', data.error || 'Unknown error');
+ alert('Failed to fetch LoRAs list');
+ }
+ } catch (error) {
+ console.error('Error appending LoRAs:', error);
+ alert('Failed to append LoRAs list');
+ }
+ }
+
+ async appendLorasTags() {
+ try {
+ const response = await fetch('/notes_sidebar/loras');
+ const data = await response.json();
+
+ if (response.ok && data.loras) {
+ // Format each LoRA as , stripping .safetensors extension and normalizing slashes
+ const lorasTags = data.loras.map(lora => {
+ const nameWithoutExt = lora.replace(/\.safetensors$/i, '');
+ const normalizedPath = nameWithoutExt.replace(/\\/g, '/');
+ return ``;
+ });
+ const lorasText = lorasTags.join('\n');
+
+ // 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 + lorasText;
+
+ // 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
+ this.onTextAreaChange();
+ } else {
+ console.error('Error fetching LoRAs:', data.error || 'Unknown error');
+ alert('Failed to fetch LoRAs list');
+ }
+ } catch (error) {
+ console.error('Error appending LoRAs tags:', error);
+ alert('Failed to append LoRAs tags');
+ }
+ }
+
+ async appendLorasWithMetadata() {
+ try {
+ const response = await fetch('/notes_sidebar/loras_with_metadata');
+ const data = await response.json();
+
+ if (response.ok && data.loras) {
+ // Format each LoRA with metadata as , metadata
+ const lorasWithMeta = data.loras.map(lora => {
+ const nameWithoutExt = lora.name.replace(/\.safetensors$/i, '');
+ const normalizedPath = nameWithoutExt.replace(/\\/g, '/');
+ const baseTag = ``;
+
+ if (lora.metadata) {
+ return `${baseTag}, ${lora.metadata}`;
+ } else {
+ return baseTag;
+ }
+ });
+ const lorasText = lorasWithMeta.join('\n');
+
+ // 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 + lorasText;
+
+ // 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
+ this.onTextAreaChange();
+ } else {
+ console.error('Error fetching LoRAs with metadata:', data.error || 'Unknown error');
+ alert('Failed to fetch LoRAs with metadata');
+ }
+ } catch (error) {
+ console.error('Error appending LoRAs with metadata:', error);
+ alert('Failed to append LoRAs with metadata');
+ }
+ }
}
// Helper function to validate filename safety
diff --git a/js/notes-sidebar.css b/js/notes-sidebar.css
index ff6bf19..a95be16 100644
--- a/js/notes-sidebar.css
+++ b/js/notes-sidebar.css
@@ -146,4 +146,16 @@
.notes-btn.rename {
background: #FF9800;
+}
+
+.notes-btn.append-loras {
+ background: #9C27B0;
+}
+
+.notes-btn.append-loras-tags {
+ background: #673AB7;
+}
+
+.notes-btn.append-loras-meta {
+ background: #3F51B5;
}
\ No newline at end of file