From a8dd835c34b605ffb85ef81f9200ecc5bbec3d7a Mon Sep 17 00:00:00 2001 From: Alexopus Date: Sat, 19 Jul 2025 23:28:05 +0200 Subject: [PATCH] Add Lora buttons --- __init__.py | 46 +++++++++++++ js/extension.js | 155 ++++++++++++++++++++++++++++++++++++++++++- js/notes-sidebar.css | 12 ++++ 3 files changed, 210 insertions(+), 3 deletions(-) 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