diff --git a/CLAUDE.md b/CLAUDE.md
index f9bf81c..30dc39b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -35,11 +35,17 @@ This is a ComfyUI extension that adds a server-side notes sidebar for managing m
## API Endpoints
The extension exposes these REST endpoints:
+
+### Notes Management
- `GET /notes_sidebar/list` - List all notes
- `GET /notes_sidebar/note/{note_name}` - Get specific note content
- `POST /notes_sidebar/note/{note_name}` - Save/create note
- `DELETE /notes_sidebar/note/{note_name}` - Delete note
+### LoRA Integration
+- `GET /notes_sidebar/loras` - List all available LoRA files
+- `GET /notes_sidebar/loras_with_metadata` - List LoRAs with associated metadata from `.txt` files
+
## Development Notes
### Extension Structure
@@ -56,10 +62,11 @@ The extension exposes these REST endpoints:
- Active note tracking with tab highlighting
- Content persistence across sidebar open/close cycles
- Real-time tab rendering on note operations
-- Button layout: Save/Rename on left, Refresh/Delete on right
+- Button layout: Save/Rename/LoRA submenu on left, Refresh/Delete on right
- **Unsaved Changes Tracking**: Visual indicators (*) for modified notes
- **Content Preservation**: Tab switching preserves unsaved content
- **Change Detection**: Automatic tracking of modifications vs saved state
+- **LoRA Integration**: Submenu with three formatting options for appending LoRA lists
### Unsaved Changes Behavior
- Notes with modifications show asterisk (*) in tab name
@@ -74,6 +81,7 @@ The extension exposes these REST endpoints:
- **API Design**: RESTful endpoints with JSON responses and consistent error handling
- **UI Updates**: Event-driven architecture prevents manual DOM manipulation
- **Extension Integration**: Follows ComfyUI's extension patterns (NODE_CLASS_MAPPINGS, registerExtension)
+- **ComfyUI Integration**: Leverages `folder_paths` module for LoRA discovery and metadata loading
## Development Commands
@@ -88,6 +96,8 @@ No automated tests defined. Manual testing workflow:
2. Verify sidebar tab appears with "Notes" label and book icon
3. Test CRUD operations: create, edit, save, rename, delete notes
4. Verify tab switching and content persistence
+5. Test LoRA integration: verify submenu appears on click with three format options
+6. Verify unsaved changes tracking with asterisk (*) indicators
### Development Workflow
1. Modify Python code in `__init__.py` for backend changes
diff --git a/js/extension.js b/js/extension.js
index c320103..b4d13d3 100644
--- a/js/extension.js
+++ b/js/extension.js
@@ -173,7 +173,7 @@ class NotesSidebar {
}
updateButtonState() {
- if (!this.saveBtn || !this.renameBtn || !this.deleteBtn || !this.appendLorasBtn || !this.appendLorasTagsBtn || !this.appendLorasMetaBtn) return;
+ if (!this.saveBtn || !this.renameBtn || !this.deleteBtn || !this.appendLorasBtn) return;
const hasActiveNote = !!this.activeNote;
const hasUnsavedChanges = hasActiveNote && this.unsavedChanges.get(this.activeNote);
@@ -185,8 +185,6 @@ class NotesSidebar {
this.renameBtn.disabled = !hasActiveNote;
this.deleteBtn.disabled = !hasActiveNote;
this.appendLorasBtn.disabled = !hasActiveNote;
- this.appendLorasTagsBtn.disabled = !hasActiveNote;
- this.appendLorasMetaBtn.disabled = !hasActiveNote;
}
async init() {
@@ -290,32 +288,48 @@ class NotesSidebar {
this.deleteBtn.onclick = () => this.deleteActiveNote();
this.deleteBtn.disabled = true;
+ // LoRA submenu container
+ this.loraMenuContainer = document.createElement('div');
+ this.loraMenuContainer.className = 'notes-submenu-container';
+
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.title = 'Append';
+ this.appendLorasBtn.onclick = () => this.toggleLoraMenu();
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;
+ // Create submenu
+ this.loraSubmenu = document.createElement('div');
+ this.loraSubmenu.className = 'notes-submenu';
+ this.loraSubmenu.style.display = 'none';
- 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;
+ // Create submenu items
+ const rawOption = document.createElement('div');
+ rawOption.className = 'notes-submenu-item';
+ rawOption.textContent = 'Append raw LoRA filenames list';
+ rawOption.onclick = () => this.selectLoraOption('raw');
+
+ const tagOption = document.createElement('div');
+ tagOption.className = 'notes-submenu-item';
+ tagOption.textContent = "Append LoRAs in '' format";
+ tagOption.onclick = () => this.selectLoraOption('tag');
+
+ const metaOption = document.createElement('div');
+ metaOption.className = 'notes-submenu-item';
+ metaOption.textContent = "Append LoRAs in ', metadata' format";
+ metaOption.onclick = () => this.selectLoraOption('meta');
+
+ this.loraSubmenu.appendChild(rawOption);
+ this.loraSubmenu.appendChild(tagOption);
+ this.loraSubmenu.appendChild(metaOption);
+
+ this.loraMenuContainer.appendChild(this.appendLorasBtn);
+ this.loraMenuContainer.appendChild(this.loraSubmenu);
leftButtonContainer.appendChild(this.saveBtn);
leftButtonContainer.appendChild(this.renameBtn);
- leftButtonContainer.appendChild(this.appendLorasBtn);
- leftButtonContainer.appendChild(this.appendLorasTagsBtn);
- leftButtonContainer.appendChild(this.appendLorasMetaBtn);
+ leftButtonContainer.appendChild(this.loraMenuContainer);
rightButtonContainer.appendChild(this.deleteBtn);
buttonContainer.appendChild(leftButtonContainer);
@@ -579,6 +593,41 @@ class NotesSidebar {
this.store.setUnsavedChanges(this.activeNote, hasUnsavedChanges);
}
+ toggleLoraMenu() {
+ if (this.loraSubmenu.style.display === 'none') {
+ this.loraSubmenu.style.display = 'block';
+ // Add click listener to close menu when clicking outside
+ document.addEventListener('click', this.closeLoraMenuHandler);
+ } else {
+ this.loraSubmenu.style.display = 'none';
+ document.removeEventListener('click', this.closeLoraMenuHandler);
+ }
+ }
+
+ closeLoraMenuHandler = (event) => {
+ if (!this.loraMenuContainer.contains(event.target)) {
+ this.loraSubmenu.style.display = 'none';
+ document.removeEventListener('click', this.closeLoraMenuHandler);
+ }
+ }
+
+ selectLoraOption(type) {
+ this.loraSubmenu.style.display = 'none';
+ document.removeEventListener('click', this.closeLoraMenuHandler);
+
+ switch(type) {
+ case 'raw':
+ this.appendLorasList();
+ break;
+ case 'tag':
+ this.appendLorasTags();
+ break;
+ case 'meta':
+ this.appendLorasWithMetadata();
+ break;
+ }
+ }
+
async appendLorasList() {
try {
const response = await fetch('/notes_sidebar/loras');
diff --git a/js/notes-sidebar.css b/js/notes-sidebar.css
index a95be16..a0887d3 100644
--- a/js/notes-sidebar.css
+++ b/js/notes-sidebar.css
@@ -152,10 +152,38 @@
background: #9C27B0;
}
-.notes-btn.append-loras-tags {
- background: #673AB7;
+/* Submenu styles */
+.notes-submenu-container {
+ position: relative;
+ display: inline-block;
}
-.notes-btn.append-loras-meta {
- background: #3F51B5;
+.notes-submenu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ background: #2a2a2a;
+ border: 1px solid #444;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ z-index: 1000;
+ min-width: 200px;
+ margin-top: 2px;
+}
+
+.notes-submenu-item {
+ padding: 8px 12px;
+ color: #fff;
+ cursor: pointer;
+ border-bottom: 1px solid #444;
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.notes-submenu-item:last-child {
+ border-bottom: none;
+}
+
+.notes-submenu-item:hover {
+ background: #444;
}
\ No newline at end of file