Initial commit
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} ## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# OSX useful to ignore
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# IntelliJ Idea
|
||||
.idea
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Cookiecutter
|
||||
output/
|
||||
python_boilerplate/
|
||||
cookiecutter-pypackage-env/
|
||||
|
||||
# vscode settings
|
||||
.history/
|
||||
*.code-workspace
|
||||
|
||||
# Frontend extension
|
||||
node_modules/
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
node.zip
|
||||
.vscode/
|
||||
.claude/
|
||||
@@ -0,0 +1,147 @@
|
||||
# ComfyUI-AutoNotes
|
||||
|
||||
An intelligent note-taking extension for ComfyUI that automatically displays relevant notes based on your workflow context.
|
||||
|
||||
## Features
|
||||
|
||||
- **Smart Note Display**: Notes automatically appear based on trigger conditions like selected nodes, node attributes, or workflow names
|
||||
- **Sidebar Interface**: Non-intrusive sidebar that doesn't interfere with your workflow
|
||||
- **Multiple Display Modes**:
|
||||
- "All" mode shows all notes
|
||||
- "Automatic" mode shows only relevant notes based on triggers
|
||||
- **Rich Formatting**: Support for both plaintext and Markdown formatting
|
||||
- **Trigger Conditions**: Configure when notes should appear:
|
||||
- **Node Selected**: Show notes when specific node types are selected
|
||||
- **Node Attribute**: Show notes when nodes have specific attribute values
|
||||
- **Workflow Name**: Show notes when workflow filename contains specific text
|
||||
- **Pinnable Notes**: Pin important notes to always display regardless of context
|
||||
- **Organized Storage**: Notes stored in your ComfyUI user directory with UUID-based organization
|
||||
- **Edit Dialog**: Full-featured editor with tree view and trigger condition management
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone or download this repository into your ComfyUI `custom_nodes` directory:
|
||||
|
||||
```
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/yourusername/ComfyUI-AutoNotes.git
|
||||
```
|
||||
|
||||
2. Restart ComfyUI
|
||||
|
||||
3. The AutoNotes sidebar will appear on the right side of your ComfyUI interface
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Operations
|
||||
|
||||
1. **Creating Notes**: Click the "Add" button in the sidebar control panel
|
||||
2. **Editing Notes**: Click the "Edit" button to open the full editor dialog
|
||||
3. **Display Modes**: Switch between "All" and "Automatic" in the dropdown
|
||||
|
||||
### Setting Up Trigger Conditions
|
||||
|
||||
In the Edit dialog, you can configure when notes should automatically appear:
|
||||
|
||||
#### Node Selected Trigger
|
||||
|
||||
- Displays the note when any of the specified node types is selected
|
||||
- Example: `CheckpointLoaderSimple, LoraLoader`
|
||||
|
||||
#### Node Attribute Trigger
|
||||
|
||||
- Displays the note when a specific node has certain attribute values
|
||||
- Useful for showing notes about specific models or settings
|
||||
- Example:
|
||||
- Node Type: `CheckpointLoaderSimple`
|
||||
- Attribute Name: `ckpt_name`
|
||||
- Attribute Values: `mymodel.safetensors, specialmodel.ckpt`
|
||||
|
||||
#### Workflow Name Trigger
|
||||
|
||||
- Displays the note when the workflow filename contains specific text
|
||||
- Example: `portrait, character, anime`
|
||||
|
||||
### Example Use Cases
|
||||
|
||||
1. **Model-Specific Notes**: Create notes with tips about specific checkpoints that appear when you select them
|
||||
2. **Node Documentation**: Add usage instructions that appear when you select certain node types
|
||||
3. **Workflow Reminders**: Set up notes that remind you of important settings for specific types of workflows
|
||||
4. **Quick Reference**: Pin frequently-used information to always stay visible
|
||||
|
||||
### Data Storage
|
||||
|
||||
Notes are stored in your ComfyUI user directory under `autonotes/`:
|
||||
|
||||
- `notes.json`: Contains all note data and metadata
|
||||
- `folders.json`: Contains folder organization (future feature)
|
||||
|
||||
## Technical Details
|
||||
|
||||
### File Structure
|
||||
|
||||
```text
|
||||
ComfyUI-AutoNotes/
|
||||
├── __init__.py # Extension entry point
|
||||
├── autonotes.py # Main Python backend
|
||||
├── web/
|
||||
│ └── autonotes.js # Frontend JavaScript
|
||||
├── README.md # This file
|
||||
└── requirements.md # Original requirements document
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
The extension exposes REST API endpoints for note management:
|
||||
|
||||
- `GET /autonotes/notes` - Retrieve notes with filtering
|
||||
- `POST /autonotes/notes` - Create new note
|
||||
- `PUT /autonotes/notes/{uuid}` - Update existing note
|
||||
- `DELETE /autonotes/notes/{uuid}` - Delete note
|
||||
- `GET /autonotes/folders` - Retrieve folders
|
||||
- `POST /autonotes/folders` - Create new folder
|
||||
|
||||
### Data Models
|
||||
|
||||
**Note Structure**:
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "unique-identifier",
|
||||
"folder_uuid": "parent-folder-id",
|
||||
"name": "Note Title",
|
||||
"content": "Note content...",
|
||||
"format_style": "plaintext|markdown",
|
||||
"pinned": true|false,
|
||||
"trigger_conditions": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**Trigger Condition Structure**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "node_selected|node_attribute|workflow_name",
|
||||
"node_types": ["NodeType1", "NodeType2"],
|
||||
"node_type": "SpecificNodeType",
|
||||
"attribute_name": "attribute_name",
|
||||
"attribute_values": ["value1", "value2"],
|
||||
"workflow_names": ["name1", "name2"]
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
The extension follows ComfyUI's standard custom node structure:
|
||||
|
||||
- Python backend handles data management and API endpoints
|
||||
- JavaScript frontend provides the user interface
|
||||
- Uses ComfyUI's built-in web server and extension system
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Test thoroughly with ComfyUI
|
||||
5. Submit a pull request
|
||||
@@ -0,0 +1,3 @@
|
||||
from .autonotes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS, WEB_DIRECTORY
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
from typing import Dict, List, Optional, Any
|
||||
from dataclasses import dataclass, asdict
|
||||
import folder_paths
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
@dataclass
|
||||
class TriggerCondition:
|
||||
type: str # "node_selected", "node_attribute", "workflow_name"
|
||||
node_types: Optional[List[str]] = None
|
||||
node_type: Optional[str] = None
|
||||
attribute_name: Optional[str] = None
|
||||
attribute_values: Optional[List[str]] = None
|
||||
workflow_names: Optional[List[str]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Note:
|
||||
uuid: str
|
||||
folder_uuid: Optional[str]
|
||||
content: str
|
||||
format_style: str # "plaintext" or "markdown"
|
||||
trigger_conditions: List[TriggerCondition]
|
||||
pinned: bool = False
|
||||
name: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Folder:
|
||||
uuid: str
|
||||
parent_uuid: Optional[str]
|
||||
name: str
|
||||
|
||||
|
||||
class AutoNotesManager:
|
||||
def __init__(self):
|
||||
self.user_dir = folder_paths.get_user_directory()
|
||||
self.data_dir = os.path.join(self.user_dir, "autonotes")
|
||||
self.notes_file = os.path.join(self.data_dir, "notes.json")
|
||||
self.folders_file = os.path.join(self.data_dir, "folders.json")
|
||||
|
||||
# Ensure data directory exists
|
||||
os.makedirs(self.data_dir, exist_ok=True)
|
||||
|
||||
# Load existing data
|
||||
self.notes: Dict[str, Note] = self._load_notes()
|
||||
self.folders: Dict[str, Folder] = self._load_folders()
|
||||
|
||||
def _load_notes(self) -> Dict[str, Note]:
|
||||
if os.path.exists(self.notes_file):
|
||||
try:
|
||||
with open(self.notes_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
notes = {}
|
||||
for note_data in data:
|
||||
# Convert trigger conditions
|
||||
trigger_conditions = []
|
||||
for tc_data in note_data.get('trigger_conditions', []):
|
||||
trigger_conditions.append(TriggerCondition(**tc_data))
|
||||
|
||||
note_data['trigger_conditions'] = trigger_conditions
|
||||
note = Note(**note_data)
|
||||
notes[note.uuid] = note
|
||||
return notes
|
||||
except Exception as e:
|
||||
print(f"Error loading notes: {e}")
|
||||
return {}
|
||||
|
||||
def _load_folders(self) -> Dict[str, Folder]:
|
||||
if os.path.exists(self.folders_file):
|
||||
try:
|
||||
with open(self.folders_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
folders = {}
|
||||
for folder_data in data:
|
||||
folder = Folder(**folder_data)
|
||||
folders[folder.uuid] = folder
|
||||
return folders
|
||||
except Exception as e:
|
||||
print(f"Error loading folders: {e}")
|
||||
return {}
|
||||
|
||||
def _save_notes(self):
|
||||
try:
|
||||
data = []
|
||||
for note in self.notes.values():
|
||||
note_dict = asdict(note)
|
||||
# Convert trigger conditions to dict
|
||||
note_dict['trigger_conditions'] = [asdict(tc) for tc in note.trigger_conditions]
|
||||
data.append(note_dict)
|
||||
|
||||
with open(self.notes_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving notes: {e}")
|
||||
|
||||
def _save_folders(self):
|
||||
try:
|
||||
data = [asdict(folder) for folder in self.folders.values()]
|
||||
with open(self.folders_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving folders: {e}")
|
||||
|
||||
def create_note(self, name: str, folder_uuid: Optional[str] = None) -> str:
|
||||
note_uuid = str(uuid.uuid4())
|
||||
note = Note(
|
||||
uuid=note_uuid,
|
||||
folder_uuid=folder_uuid,
|
||||
content="",
|
||||
format_style="plaintext",
|
||||
trigger_conditions=[],
|
||||
name=name
|
||||
)
|
||||
self.notes[note_uuid] = note
|
||||
self._save_notes()
|
||||
return note_uuid
|
||||
|
||||
def update_note(self, note_uuid: str, **kwargs) -> bool:
|
||||
if note_uuid not in self.notes:
|
||||
return False
|
||||
|
||||
note = self.notes[note_uuid]
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(note, key):
|
||||
setattr(note, key, value)
|
||||
|
||||
self._save_notes()
|
||||
return True
|
||||
|
||||
def delete_note(self, note_uuid: str) -> bool:
|
||||
if note_uuid in self.notes:
|
||||
del self.notes[note_uuid]
|
||||
self._save_notes()
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_folder(self, name: str, parent_uuid: Optional[str] = None) -> str:
|
||||
folder_uuid = str(uuid.uuid4())
|
||||
folder = Folder(
|
||||
uuid=folder_uuid,
|
||||
parent_uuid=parent_uuid,
|
||||
name=name
|
||||
)
|
||||
self.folders[folder_uuid] = folder
|
||||
self._save_folders()
|
||||
return folder_uuid
|
||||
|
||||
def get_notes_for_display(self, mode: str = "all", selected_node_type: Optional[str] = None,
|
||||
selected_node_attributes: Optional[Dict[str, Any]] = None,
|
||||
workflow_name: Optional[str] = None,
|
||||
workflow_nodes: Optional[Dict[str, Any]] = None) -> List[Note]:
|
||||
if mode == "all":
|
||||
return list(self.notes.values())
|
||||
|
||||
# Automatic mode - filter by trigger conditions
|
||||
matching_notes = []
|
||||
|
||||
for note in self.notes.values():
|
||||
if note.pinned:
|
||||
matching_notes.append(note)
|
||||
continue
|
||||
|
||||
for condition in note.trigger_conditions:
|
||||
if self._check_trigger_condition(condition, selected_node_type,
|
||||
selected_node_attributes, workflow_name,
|
||||
workflow_nodes):
|
||||
matching_notes.append(note)
|
||||
break
|
||||
|
||||
return matching_notes
|
||||
|
||||
def _check_trigger_condition(self, condition: TriggerCondition,
|
||||
selected_node_type: Optional[str],
|
||||
selected_node_attributes: Optional[Dict[str, Any]],
|
||||
workflow_name: Optional[str],
|
||||
workflow_nodes: Optional[Dict[str, Any]] = None) -> bool:
|
||||
if condition.type == "node_selected":
|
||||
return (selected_node_type is not None and
|
||||
condition.node_types is not None and
|
||||
selected_node_type in condition.node_types)
|
||||
|
||||
elif condition.type == "node_attribute":
|
||||
if (selected_node_type == condition.node_type and
|
||||
selected_node_attributes is not None and
|
||||
condition.attribute_name in selected_node_attributes and
|
||||
condition.attribute_values is not None):
|
||||
|
||||
attr_value = str(selected_node_attributes[condition.attribute_name])
|
||||
return any(val in attr_value for val in condition.attribute_values)
|
||||
|
||||
elif condition.type == "node_in_workflow":
|
||||
if workflow_nodes is not None and condition.node_types is not None:
|
||||
# Check if any of the specified node types exist in the workflow
|
||||
return any(node_type in workflow_nodes for node_type in condition.node_types)
|
||||
|
||||
elif condition.type == "attribute_in_workflow":
|
||||
if (workflow_nodes is not None and
|
||||
condition.node_type is not None and
|
||||
condition.attribute_name is not None):
|
||||
|
||||
# Check if the node type exists and has the specified attribute
|
||||
if condition.node_type in workflow_nodes:
|
||||
node_attrs = workflow_nodes[condition.node_type]
|
||||
if condition.attribute_name in node_attrs:
|
||||
# If attribute_values is specified, check if any match
|
||||
if condition.attribute_values is not None and len(condition.attribute_values) > 0:
|
||||
attr_value = str(node_attrs[condition.attribute_name])
|
||||
return any(val in attr_value for val in condition.attribute_values)
|
||||
else:
|
||||
# No specific values required, just presence is enough
|
||||
return True
|
||||
|
||||
elif condition.type == "workflow_name":
|
||||
if workflow_name is not None and condition.workflow_names is not None:
|
||||
return any(name in workflow_name for name in condition.workflow_names)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# Global manager instance
|
||||
autonotes_manager = AutoNotesManager()
|
||||
|
||||
|
||||
class AutoNotesNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "process"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "AutoNotes"
|
||||
|
||||
def process(self):
|
||||
# This node doesn't actually process anything in the workflow
|
||||
# It's just here to provide a presence in the node list
|
||||
return {}
|
||||
|
||||
|
||||
# Web API routes
|
||||
@PromptServer.instance.routes.get("/autonotes/notes")
|
||||
async def get_notes(request):
|
||||
try:
|
||||
mode = request.query.get('mode', 'all')
|
||||
selected_node_type = request.query.get('node_type')
|
||||
workflow_name = request.query.get('workflow_name')
|
||||
|
||||
# Parse node attributes if provided
|
||||
selected_node_attributes = None
|
||||
if 'node_attributes' in request.query:
|
||||
try:
|
||||
selected_node_attributes = json.loads(request.query['node_attributes'])
|
||||
except:
|
||||
pass
|
||||
|
||||
# Parse workflow nodes if provided
|
||||
workflow_nodes = None
|
||||
if 'workflow_nodes' in request.query:
|
||||
try:
|
||||
workflow_nodes = json.loads(request.query['workflow_nodes'])
|
||||
except:
|
||||
pass
|
||||
|
||||
notes = autonotes_manager.get_notes_for_display(
|
||||
mode=mode,
|
||||
selected_node_type=selected_node_type,
|
||||
selected_node_attributes=selected_node_attributes,
|
||||
workflow_name=workflow_name,
|
||||
workflow_nodes=workflow_nodes
|
||||
)
|
||||
|
||||
# Convert notes to JSON-serializable format
|
||||
notes_data = []
|
||||
for note in notes:
|
||||
note_dict = asdict(note)
|
||||
note_dict['trigger_conditions'] = [asdict(tc) for tc in note.trigger_conditions]
|
||||
notes_data.append(note_dict)
|
||||
|
||||
return web.json_response(notes_data)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/autonotes/notes")
|
||||
async def create_note(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
name = data.get('name', 'New Note')
|
||||
folder_uuid = data.get('folder_uuid')
|
||||
|
||||
note_uuid = autonotes_manager.create_note(name, folder_uuid)
|
||||
return web.json_response({"uuid": note_uuid})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.put("/autonotes/notes/{note_uuid}")
|
||||
async def update_note(request):
|
||||
try:
|
||||
note_uuid = request.match_info['note_uuid']
|
||||
data = await request.json()
|
||||
|
||||
# Handle trigger conditions conversion
|
||||
if 'trigger_conditions' in data:
|
||||
trigger_conditions = []
|
||||
for tc_data in data['trigger_conditions']:
|
||||
trigger_conditions.append(TriggerCondition(**tc_data))
|
||||
data['trigger_conditions'] = trigger_conditions
|
||||
|
||||
success = autonotes_manager.update_note(note_uuid, **data)
|
||||
return web.json_response({"success": success})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.delete("/autonotes/notes/{note_uuid}")
|
||||
async def delete_note(request):
|
||||
try:
|
||||
note_uuid = request.match_info['note_uuid']
|
||||
success = autonotes_manager.delete_note(note_uuid)
|
||||
return web.json_response({"success": success})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/autonotes/folders")
|
||||
async def get_folders(request):
|
||||
try:
|
||||
folders_data = [asdict(folder) for folder in autonotes_manager.folders.values()]
|
||||
return web.json_response(folders_data)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/autonotes/folders")
|
||||
async def create_folder(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
name = data.get('name', 'New Folder')
|
||||
parent_uuid = data.get('parent_uuid')
|
||||
|
||||
folder_uuid = autonotes_manager.create_folder(name, parent_uuid)
|
||||
return web.json_response({"uuid": folder_uuid})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.put("/autonotes/folders/{folder_uuid}")
|
||||
async def update_folder(request):
|
||||
try:
|
||||
folder_uuid = request.match_info['folder_uuid']
|
||||
data = await request.json()
|
||||
|
||||
if folder_uuid not in autonotes_manager.folders:
|
||||
return web.json_response({"success": False, "error": "Folder not found"}, status=404)
|
||||
|
||||
folder = autonotes_manager.folders[folder_uuid]
|
||||
if 'name' in data:
|
||||
folder.name = data['name']
|
||||
if 'parent_uuid' in data:
|
||||
folder.parent_uuid = data['parent_uuid']
|
||||
|
||||
autonotes_manager._save_folders()
|
||||
return web.json_response({"success": True})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.delete("/autonotes/folders/{folder_uuid}")
|
||||
async def delete_folder(request):
|
||||
try:
|
||||
folder_uuid = request.match_info['folder_uuid']
|
||||
|
||||
if folder_uuid not in autonotes_manager.folders:
|
||||
return web.json_response({"success": False, "error": "Folder not found"}, status=404)
|
||||
|
||||
# Remove folder_uuid from any notes that reference it
|
||||
for note in autonotes_manager.notes.values():
|
||||
if note.folder_uuid == folder_uuid:
|
||||
note.folder_uuid = None
|
||||
autonotes_manager._save_notes()
|
||||
|
||||
# Delete the folder
|
||||
del autonotes_manager.folders[folder_uuid]
|
||||
autonotes_manager._save_folders()
|
||||
|
||||
return web.json_response({"success": True})
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
# Node mappings
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AutoNotesNode": AutoNotesNode
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"AutoNotesNode": "Auto Notes"
|
||||
}
|
||||
|
||||
# Web directory for frontend files
|
||||
WEB_DIRECTORY = "./web"
|
||||
@@ -0,0 +1,73 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "ComfyUI-AutoNotes"
|
||||
version = "0.0.1"
|
||||
description = "Automatically display context-aware notes based on workflow state, selected nodes, and node attributes in ComfyUI."
|
||||
authors = [
|
||||
{name = "John Cantu", email = "jfcantu@gmail.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
license = {text = "MIT license"}
|
||||
requires-python = ">=3.10"
|
||||
classifiers = []
|
||||
dependencies = []
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/jfcantu/ComfyUI-AutoNotes"
|
||||
BugTracker = "https://github.com/jfcantu/ComfyUI-AutoNotes/issues"
|
||||
Documentation = "https://github.com/jfcantu/ComfyUI-AutoNotes/wiki"
|
||||
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "jfcantu"
|
||||
DisplayName = "ComfyUI AutoNotes"
|
||||
Icon = ""
|
||||
Tags = []
|
||||
Repository = "https://github.com/jfcantu/ComfyUI-AutoNotes"
|
||||
|
||||
includes = []
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["*.*"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "8.0"
|
||||
testpaths = [
|
||||
"tests",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
files = "."
|
||||
|
||||
# Use strict defaults
|
||||
strict = true
|
||||
warn_unreachable = true
|
||||
warn_no_return = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
# Don't require test functions to include types
|
||||
module = "tests.*"
|
||||
allow_untyped_defs = true
|
||||
disable_error_code = "attr-defined"
|
||||
|
||||
[tool.ruff]
|
||||
# extend-exclude = ["static", "ci/templates"]
|
||||
line-length = 140
|
||||
src = ["src", "tests"]
|
||||
target-version = "py39"
|
||||
|
||||
# Add rules to ban exec/eval
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"S102", # exec-builtin
|
||||
"S307", # eval-used
|
||||
"W293",
|
||||
"F", # The "F" series in Ruff stands for "Pyflakes" rules, which catch various Python syntax errors and undefined names.
|
||||
# See all rules here: https://docs.astral.sh/ruff/rules/#pyflakes-f
|
||||
]
|
||||
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
inline-quotes = "double"
|
||||
+2983
File diff suppressed because it is too large
Load Diff
Vendored
+6
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user