mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 13:12:12 -03:00
feat(docs): update AGENTS.md with improved testing and development instructions
- Simplify pytest coverage command by consolidating --cov flags - Remove redundant JSON coverage report - Reorganize frontend section into "Frontend Development (Standalone Web UI)" and "Vue Widget Development" - Add npm commands for Vue widget development (dev, build, typecheck, tests) - Consolidate Python code style sections (imports, formatting, naming, error handling, async) - Update architecture overview with clearer service descriptions - Fix truncated line in API endpoints note - Improve readability and remove redundant information
This commit is contained in:
181
AGENTS.md
181
AGENTS.md
@@ -25,168 +25,127 @@ pytest tests/test_recipes.py::test_function_name
|
|||||||
|
|
||||||
# Run backend tests with coverage
|
# Run backend tests with coverage
|
||||||
COVERAGE_FILE=coverage/backend/.coverage pytest \
|
COVERAGE_FILE=coverage/backend/.coverage pytest \
|
||||||
--cov=py \
|
--cov=py --cov=standalone \
|
||||||
--cov=standalone \
|
|
||||||
--cov-report=term-missing \
|
--cov-report=term-missing \
|
||||||
--cov-report=html:coverage/backend/html \
|
--cov-report=html:coverage/backend/html \
|
||||||
--cov-report=xml:coverage/backend/coverage.xml \
|
--cov-report=xml:coverage/backend/coverage.xml
|
||||||
--cov-report=json:coverage/backend/coverage.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend Development
|
### Frontend Development (Standalone Web UI)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install frontend dependencies
|
|
||||||
npm install
|
npm install
|
||||||
|
npm test # Run all tests (JS + Vue)
|
||||||
|
npm run test:js # Run JS tests only
|
||||||
|
npm run test:watch # Watch mode
|
||||||
|
npm run test:coverage # Generate coverage report
|
||||||
|
```
|
||||||
|
|
||||||
# Run frontend tests
|
### Vue Widget Development
|
||||||
npm test
|
|
||||||
|
|
||||||
# Run frontend tests in watch mode
|
```bash
|
||||||
npm run test:watch
|
cd vue-widgets
|
||||||
|
npm install
|
||||||
# Run frontend tests with coverage
|
npm run dev # Build in watch mode
|
||||||
npm run test:coverage
|
npm run build # Build production bundle
|
||||||
|
npm run typecheck # Run TypeScript type checking
|
||||||
|
npm test # Run Vue widget tests
|
||||||
|
npm run test:watch # Watch mode
|
||||||
|
npm run test:coverage # Generate coverage report
|
||||||
```
|
```
|
||||||
|
|
||||||
## Python Code Style
|
## Python Code Style
|
||||||
|
|
||||||
### Imports
|
### Imports & Formatting
|
||||||
|
|
||||||
- Use `from __future__ import annotations` for forward references in type hints
|
- Use `from __future__ import annotations` for forward references
|
||||||
- Group imports: standard library, third-party, local (separated by blank lines)
|
- Group imports: standard library, third-party, local (blank line separated)
|
||||||
- Use absolute imports within `py/` package: `from ..services import X`
|
- Absolute imports within `py/`: `from ..services import X`
|
||||||
- Mock ComfyUI dependencies in tests using `tests/conftest.py` patterns
|
- PEP 8 with 4-space indentation, type hints required
|
||||||
|
|
||||||
### Formatting & Types
|
|
||||||
|
|
||||||
- PEP 8 with 4-space indentation
|
|
||||||
- Type hints required for function signatures and class attributes
|
|
||||||
- Use `TYPE_CHECKING` guard for type-checking-only imports
|
|
||||||
- Prefer dataclasses for simple data containers
|
|
||||||
- Use `Optional[T]` for nullable types, `Union[T, None]` only when necessary
|
|
||||||
|
|
||||||
### Naming Conventions
|
### Naming Conventions
|
||||||
|
|
||||||
- Files: `snake_case.py` (e.g., `model_scanner.py`, `lora_service.py`)
|
- Files: `snake_case.py`, Classes: `PascalCase`, Functions/vars: `snake_case`
|
||||||
- Classes: `PascalCase` (e.g., `ModelScanner`, `LoraService`)
|
- Constants: `UPPER_SNAKE_CASE`, Private: `_protected`, `__mangled`
|
||||||
- Functions/variables: `snake_case` (e.g., `get_instance`, `model_type`)
|
|
||||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `VALID_LORA_TYPES`)
|
|
||||||
- Private members: `_single_underscore` (protected), `__double_underscore` (name-mangled)
|
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling & Async
|
||||||
|
|
||||||
- Use `logging.getLogger(__name__)` for module-level loggers
|
- Use `logging.getLogger(__name__)`, define custom exceptions in `py/services/errors.py`
|
||||||
- Define custom exceptions in `py/services/errors.py`
|
- `async def` for I/O, `@pytest.mark.asyncio` for async tests
|
||||||
- Use `asyncio.Lock` for thread-safe singleton patterns
|
- Singleton with `asyncio.Lock`: see `ModelScanner.get_instance()`
|
||||||
- Raise specific exceptions with descriptive messages
|
- Return `aiohttp.web.json_response` or `web.Response`
|
||||||
- Log errors at appropriate levels (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
||||||
|
|
||||||
### Async Patterns
|
### Testing
|
||||||
|
|
||||||
- Use `async def` for I/O-bound operations
|
- `pytest` with `--import-mode=importlib`
|
||||||
- Mark async tests with `@pytest.mark.asyncio`
|
- Fixtures in `tests/conftest.py`, use `tmp_path_factory` for isolation
|
||||||
- Use `async with` for context managers
|
- Mark tests needing real paths: `@pytest.mark.no_settings_dir_isolation`
|
||||||
- Singleton pattern with class-level locks: see `ModelScanner.get_instance()`
|
- Mock ComfyUI dependencies via conftest patterns
|
||||||
- Use `aiohttp.web.Response` for HTTP responses
|
|
||||||
|
|
||||||
### Testing Patterns
|
## JavaScript/TypeScript Code Style
|
||||||
|
|
||||||
- Use `pytest` with `--import-mode=importlib`
|
|
||||||
- Fixtures in `tests/conftest.py` handle ComfyUI mocking
|
|
||||||
- Use `@pytest.mark.no_settings_dir_isolation` for tests needing real paths
|
|
||||||
- Test files: `tests/test_*.py`
|
|
||||||
- Use `tmp_path_factory` for temporary directory isolation
|
|
||||||
|
|
||||||
## JavaScript Code Style
|
|
||||||
|
|
||||||
### Imports & Modules
|
### Imports & Modules
|
||||||
|
|
||||||
- ES modules with `import`/`export`
|
- ES modules: `import { app } from "../../scripts/app.js"` for ComfyUI
|
||||||
- Use `import { app } from "../../scripts/app.js"` for ComfyUI integration
|
- Vue: `import { ref, computed } from 'vue'`, type imports: `import type { Foo }`
|
||||||
- Export named functions/classes: `export function foo() {}`
|
- Export named functions: `export function foo() {}`
|
||||||
- Widget files use `*_widget.js` suffix
|
|
||||||
|
|
||||||
### Naming & Formatting
|
### Naming & Formatting
|
||||||
|
|
||||||
- camelCase for functions, variables, object properties
|
- camelCase for functions/vars/props, PascalCase for classes
|
||||||
- PascalCase for classes/constructors
|
- Constants: `UPPER_SNAKE_CASE`, Files: `snake_case.js` or `kebab-case.js`
|
||||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `CONVERTED_TYPE`)
|
|
||||||
- Files: `snake_case.js` or `kebab-case.js`
|
|
||||||
- 2-space indentation preferred (follow existing file conventions)
|
- 2-space indentation preferred (follow existing file conventions)
|
||||||
|
- Vue Single File Components: `<script setup lang="ts">` preferred
|
||||||
|
|
||||||
### Widget Development
|
### Widget Development
|
||||||
|
|
||||||
- Use `app.registerExtension()` to register ComfyUI extensions
|
- ComfyUI: `app.registerExtension()`, `node.addDOMWidget(name, type, element, options)`
|
||||||
- Use `node.addDOMWidget(name, type, element, options)` for custom widgets
|
- Event handlers via `addEventListener` or widget callbacks
|
||||||
- Event handlers attached via `addEventListener` or widget callbacks
|
- Shared utilities: `web/comfyui/utils.js`
|
||||||
- See `web/comfyui/utils.js` for shared utilities
|
|
||||||
|
### Vue Composables Pattern
|
||||||
|
|
||||||
|
- Use composition API: `useXxxState(widget)`, return reactive refs and methods
|
||||||
|
- Guard restoration loops with flag: `let isRestoring = false`
|
||||||
|
- Build config from state: `const buildConfig = (): Config => { ... }`
|
||||||
|
|
||||||
## Architecture Patterns
|
## Architecture Patterns
|
||||||
|
|
||||||
### Service Layer
|
### Service Layer
|
||||||
|
|
||||||
- Use `ServiceRegistry` singleton for dependency injection
|
- `ServiceRegistry` singleton for DI, services use `get_instance()` classmethod
|
||||||
- Services follow singleton pattern via `get_instance()` class method
|
|
||||||
- Separate scanners (discovery) from services (business logic)
|
- Separate scanners (discovery) from services (business logic)
|
||||||
- Handlers in `py/routes/handlers/` implement route logic
|
- Handlers in `py/routes/handlers/` are pure functions with deps as params
|
||||||
|
|
||||||
### Model Types
|
### Model Types & Routes
|
||||||
|
|
||||||
- BaseModelService is abstract base for LoRA, Checkpoint, Embedding services
|
- `BaseModelService` base for LoRA, Checkpoint, Embedding
|
||||||
- ModelScanner provides file discovery and hash-based deduplication
|
- `ModelScanner` for file discovery, hash deduplication
|
||||||
- Persistent cache in SQLite via `PersistentModelCache`
|
- `PersistentModelCache` (SQLite) for persistence
|
||||||
- Metadata sync from CivitAI/CivArchive via `MetadataSyncService`
|
- Route registrars: `ModelRouteRegistrar`, endpoints: `/loras/*`, `/checkpoints/*`, `/embeddings/*`
|
||||||
|
- WebSocket via `WebSocketManager` for real-time updates
|
||||||
### Routes & Handlers
|
|
||||||
|
|
||||||
- Route registrars organize endpoints by domain: `ModelRouteRegistrar`, etc.
|
|
||||||
- Handlers are pure functions taking dependencies as parameters
|
|
||||||
- Use `WebSocketManager` for real-time progress updates
|
|
||||||
- Return `aiohttp.web.json_response` or `web.Response`
|
|
||||||
|
|
||||||
### Recipe System
|
### Recipe System
|
||||||
|
|
||||||
- Base metadata in `py/recipes/base.py`
|
- Base: `py/recipes/base.py`, Enrichment: `RecipeEnrichmentService`
|
||||||
- Enrichment adds model metadata: `RecipeEnrichmentService`
|
- Parsers: `py/recipes/parsers/`
|
||||||
- Parsers for different formats in `py/recipes/parsers/`
|
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
- Always use English for comments (per copilot-instructions.md)
|
- ALWAYS use English for comments (per copilot-instructions.md)
|
||||||
- Dual mode: ComfyUI plugin (uses folder_paths) vs standalone (reads settings.json)
|
- Dual mode: ComfyUI plugin (folder_paths) vs standalone (settings.json)
|
||||||
- Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"`
|
- Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"`
|
||||||
- Settings auto-saved in user directory or portable mode
|
|
||||||
- WebSocket broadcasts for real-time updates (downloads, scans)
|
|
||||||
- Symlink handling requires normalized paths
|
|
||||||
- API endpoints follow `/loras/*`, `/checkpoints/*`, `/embeddings/*` patterns
|
|
||||||
- Run `python scripts/sync_translation_keys.py` after UI string updates
|
- Run `python scripts/sync_translation_keys.py` after UI string updates
|
||||||
|
- Symlinks require normalized paths
|
||||||
|
|
||||||
## Frontend UI Architecture
|
## Frontend UI Architecture
|
||||||
|
|
||||||
This project has two distinct UI systems:
|
### 1. Standalone Web UI
|
||||||
|
|
||||||
### 1. Standalone Lora Manager Web UI
|
|
||||||
- Location: `./static/` and `./templates/`
|
- Location: `./static/` and `./templates/`
|
||||||
- Purpose: Full-featured web application for managing LoRA models
|
- Tech: Vanilla JS + CSS, served by standalone server
|
||||||
- Tech stack: Vanilla JS + CSS, served by the standalone server
|
- Tests via npm in root directory
|
||||||
- Development: Uses npm for frontend testing (`npm test`, `npm run test:watch`, etc.)
|
|
||||||
|
|
||||||
### 2. ComfyUI Custom Node Widgets
|
### 2. ComfyUI Custom Node Widgets
|
||||||
- Location: `./web/comfyui/`
|
- Location: `./web/comfyui/` (Vanilla JS) + `./vue-widgets/` (Vue)
|
||||||
- Purpose: Widgets and UI logic that ComfyUI loads as custom node extensions
|
- Primary styles: `./web/comfyui/lm_styles.css` (NOT `./static/css/`)
|
||||||
- Tech stack: Vanilla JS + Vue.js widgets (in `./vue-widgets/` and built to `./web/comfyui/vue-widgets/`)
|
- Vue builds to `./web/comfyui/vue-widgets/`, typecheck via `vue-tsc`
|
||||||
- Widget styling: Primary styles in `./web/comfyui/lm_styles.css` (NOT `./static/css/`)
|
|
||||||
- Development: No npm build step for these widgets (Vue widgets use build system)
|
|
||||||
|
|
||||||
### Widget Development Guidelines
|
|
||||||
- Use `app.registerExtension()` to register ComfyUI extensions (ComfyUI integration layer)
|
|
||||||
- Use `node.addDOMWidget()` for custom DOM widgets
|
|
||||||
- Widget styles should follow the patterns in `./web/comfyui/lm_styles.css`
|
|
||||||
- Selected state: `rgba(66, 153, 225, 0.3)` background, `rgba(66, 153, 225, 0.6)` border
|
|
||||||
- Hover state: `rgba(66, 153, 225, 0.2)` background
|
|
||||||
- Color palette matches the Lora Manager accent color (blue #4299e1)
|
|
||||||
- Use oklch() for color values when possible (defined in `./static/css/base.css`)
|
|
||||||
- Vue widget components are in `./vue-widgets/src/components/` and built to `./web/comfyui/vue-widgets/`
|
|
||||||
- When modifying widget styles, check `./web/comfyui/lm_styles.css` for consistency with other ComfyUI widgets
|
|
||||||
|
|
||||||
|
|||||||
276
CLAUDE.md
276
CLAUDE.md
@@ -8,17 +8,22 @@ ComfyUI LoRA Manager is a comprehensive LoRA management system for ComfyUI that
|
|||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
### Backend Development
|
### Backend
|
||||||
```bash
|
|
||||||
# Install dependencies
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Install development dependencies (for testing)
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
|
|
||||||
# Run standalone server (port 8188 by default)
|
# Run standalone server (port 8188 by default)
|
||||||
python standalone.py --port 8188
|
python standalone.py --port 8188
|
||||||
|
|
||||||
|
# Run all backend tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run specific test file or function
|
||||||
|
pytest tests/test_recipes.py
|
||||||
|
pytest tests/test_recipes.py::test_function_name
|
||||||
|
|
||||||
# Run backend tests with coverage
|
# Run backend tests with coverage
|
||||||
COVERAGE_FILE=coverage/backend/.coverage pytest \
|
COVERAGE_FILE=coverage/backend/.coverage pytest \
|
||||||
--cov=py \
|
--cov=py \
|
||||||
@@ -27,185 +32,158 @@ COVERAGE_FILE=coverage/backend/.coverage pytest \
|
|||||||
--cov-report=html:coverage/backend/html \
|
--cov-report=html:coverage/backend/html \
|
||||||
--cov-report=xml:coverage/backend/coverage.xml \
|
--cov-report=xml:coverage/backend/coverage.xml \
|
||||||
--cov-report=json:coverage/backend/coverage.json
|
--cov-report=json:coverage/backend/coverage.json
|
||||||
|
|
||||||
# Run specific test file
|
|
||||||
pytest tests/test_recipes.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend Development
|
### Frontend
|
||||||
```bash
|
|
||||||
# Install frontend dependencies
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Run frontend tests
|
There are three test suites run by `npm test`: vanilla JS tests (vitest at root) and Vue widget tests (`vue-widgets/` vitest).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
cd vue-widgets && npm install && cd ..
|
||||||
|
|
||||||
|
# Run all frontend tests (JS + Vue)
|
||||||
npm test
|
npm test
|
||||||
|
|
||||||
# Run frontend tests in watch mode
|
# Run only vanilla JS tests
|
||||||
|
npm run test:js
|
||||||
|
|
||||||
|
# Run only Vue widget tests
|
||||||
|
npm run test:vue
|
||||||
|
|
||||||
|
# Watch mode (JS tests only)
|
||||||
npm run test:watch
|
npm run test:watch
|
||||||
|
|
||||||
# Run frontend tests with coverage
|
# Frontend coverage
|
||||||
npm run test:coverage
|
npm run test:coverage
|
||||||
|
|
||||||
|
# Build Vue widgets (output to web/comfyui/vue-widgets/)
|
||||||
|
cd vue-widgets && npm run build
|
||||||
|
|
||||||
|
# Vue widget dev mode (watch + rebuild)
|
||||||
|
cd vue-widgets && npm run dev
|
||||||
|
|
||||||
|
# Typecheck Vue widgets
|
||||||
|
cd vue-widgets && npm run typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
### Localization
|
### Localization
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Sync translation keys after UI string updates
|
# Sync translation keys after UI string updates
|
||||||
python scripts/sync_translation_keys.py
|
python scripts/sync_translation_keys.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Locale files are in `locales/` (en, zh-CN, zh-TW, ja, ko, fr, de, es, ru, he).
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Backend Structure (Python)
|
### Dual Mode Operation
|
||||||
|
|
||||||
**Core Entry Points:**
|
The system runs in two modes:
|
||||||
- `__init__.py` - ComfyUI plugin entry point, registers nodes and routes
|
- **ComfyUI plugin mode**: Integrates with ComfyUI's PromptServer, uses `folder_paths` for model discovery
|
||||||
- `standalone.py` - Standalone server that mocks ComfyUI dependencies
|
- **Standalone mode**: `standalone.py` mocks ComfyUI dependencies, reads paths from `settings.json`
|
||||||
- `py/lora_manager.py` - Main LoraManager class that registers HTTP routes
|
|
||||||
|
|
||||||
**Service Layer** (`py/services/`):
|
|
||||||
- `ServiceRegistry` - Singleton service registry for dependency management
|
|
||||||
- `ModelServiceFactory` - Factory for creating model services (LoRA, Checkpoint, Embedding)
|
|
||||||
- Scanner services (`lora_scanner.py`, `checkpoint_scanner.py`, `embedding_scanner.py`) - Model file discovery and indexing
|
|
||||||
- `model_scanner.py` - Base scanner with hash-based deduplication and metadata extraction
|
|
||||||
- `persistent_model_cache.py` - SQLite-based cache for model metadata
|
|
||||||
- `metadata_sync_service.py` - Syncs metadata from CivitAI/CivArchive APIs
|
|
||||||
- `civitai_client.py` / `civarchive_client.py` - API clients for external services
|
|
||||||
- `downloader.py` / `download_manager.py` - Model download orchestration
|
|
||||||
- `recipe_scanner.py` - Recipe file management and image association
|
|
||||||
- `settings_manager.py` - Application settings with migration support
|
|
||||||
- `websocket_manager.py` - WebSocket broadcasting for real-time updates
|
|
||||||
- `use_cases/` - Business logic orchestration (auto-organize, bulk refresh, downloads)
|
|
||||||
|
|
||||||
**Routes Layer** (`py/routes/`):
|
|
||||||
- Route registrars organize endpoints by domain (models, recipes, previews, example images, updates)
|
|
||||||
- `handlers/` - Request handlers implementing business logic
|
|
||||||
- Routes use aiohttp and integrate with ComfyUI's PromptServer
|
|
||||||
|
|
||||||
**Recipe System** (`py/recipes/`):
|
|
||||||
- `base.py` - Base recipe metadata structure
|
|
||||||
- `enrichment.py` - Enriches recipes with model metadata
|
|
||||||
- `merger.py` - Merges recipe data from multiple sources
|
|
||||||
- `parsers/` - Parsers for different recipe formats (PNG, JSON, workflow)
|
|
||||||
|
|
||||||
**Custom Nodes** (`py/nodes/`):
|
|
||||||
- `lora_loader.py` - LoRA loader nodes with preset support
|
|
||||||
- `save_image.py` - Enhanced save image with pattern-based filenames
|
|
||||||
- `trigger_word_toggle.py` - Toggle trigger words in prompts
|
|
||||||
- `lora_stacker.py` - Stack multiple LoRAs
|
|
||||||
- `prompt.py` - Prompt node with autocomplete
|
|
||||||
- `wanvideo_lora_select.py` - WanVideo-specific LoRA selection
|
|
||||||
|
|
||||||
**Configuration** (`py/config.py`):
|
|
||||||
- Manages folder paths for models, checkpoints, embeddings
|
|
||||||
- Handles symlink mappings for complex directory structures
|
|
||||||
- Auto-saves paths to settings.json in ComfyUI mode
|
|
||||||
|
|
||||||
### Frontend Structure (JavaScript)
|
|
||||||
|
|
||||||
**ComfyUI Widgets** (`web/comfyui/`):
|
|
||||||
- Vanilla JavaScript ES modules extending ComfyUI's LiteGraph-based UI
|
|
||||||
- `loras_widget.js` - Main LoRA selection widget with preview
|
|
||||||
- `loras_widget_events.js` - Event handling for widget interactions
|
|
||||||
- `autocomplete.js` - Autocomplete for trigger words and embeddings
|
|
||||||
- `preview_tooltip.js` - Preview tooltip for model cards
|
|
||||||
- `top_menu_extension.js` - Adds "Launch LoRA Manager" menu item
|
|
||||||
- `trigger_word_highlight.js` - Syntax highlighting for trigger words
|
|
||||||
- `utils.js` - Shared utilities and API helpers
|
|
||||||
|
|
||||||
**Widget Development:**
|
|
||||||
- Widgets use `app.registerExtension` and `getCustomWidgets` hooks
|
|
||||||
- `node.addDOMWidget(name, type, element, options)` embeds HTML in nodes
|
|
||||||
- See `docs/dom_widget_dev_guide.md` for complete DOMWidget development guide
|
|
||||||
|
|
||||||
**Web Source** (`web-src/`):
|
|
||||||
- Modern frontend components (if migrating from static)
|
|
||||||
- `components/` - Reusable UI components
|
|
||||||
- `styles/` - CSS styling
|
|
||||||
|
|
||||||
### Key Patterns
|
|
||||||
|
|
||||||
**Dual Mode Operation:**
|
|
||||||
- ComfyUI plugin mode: Integrates with ComfyUI's PromptServer, uses folder_paths
|
|
||||||
- Standalone mode: Mocks ComfyUI dependencies via `standalone.py`, reads paths from settings.json
|
|
||||||
- Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"`
|
- Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"`
|
||||||
|
|
||||||
**Settings Management:**
|
### Backend (Python)
|
||||||
- Settings stored in user directory (via `platformdirs`) or portable mode (in repo)
|
|
||||||
- Migration system tracks settings schema version
|
|
||||||
- Template in `settings.json.example` defines defaults
|
|
||||||
|
|
||||||
**Model Scanning Flow:**
|
**Entry points:**
|
||||||
1. Scanner walks folder paths, computes file hashes
|
- `__init__.py` — ComfyUI plugin entry: registers nodes via `NODE_CLASS_MAPPINGS`, sets `WEB_DIRECTORY`, calls `LoraManager.add_routes()`
|
||||||
2. Hash-based deduplication prevents duplicate processing
|
- `standalone.py` — Standalone server: mocks `folder_paths` and node modules, starts aiohttp server
|
||||||
3. Metadata extracted from safetensors headers
|
- `py/lora_manager.py` — Main `LoraManager` class that registers all HTTP routes
|
||||||
4. Persistent cache stores results in SQLite
|
|
||||||
5. Background sync fetches CivitAI/CivArchive metadata
|
|
||||||
6. WebSocket broadcasts updates to connected clients
|
|
||||||
|
|
||||||
**Recipe System:**
|
**Service layer** (`py/services/`):
|
||||||
- Recipes store LoRA combinations with parameters
|
- `ServiceRegistry` singleton for dependency injection; services follow `get_instance()` singleton pattern
|
||||||
- Supports import from workflow JSON, PNG metadata
|
- `BaseModelService` abstract base → `LoraService`, `CheckpointService`, `EmbeddingService`
|
||||||
- Images associated with recipes via sibling file detection
|
- `ModelScanner` base → `LoraScanner`, `CheckpointScanner`, `EmbeddingScanner` for file discovery with hash-based deduplication
|
||||||
- Enrichment adds model metadata for display
|
- `PersistentModelCache` — SQLite-based metadata cache
|
||||||
|
- `MetadataSyncService` — Background sync from CivitAI/CivArchive APIs
|
||||||
|
- `SettingsManager` — Settings with schema migration support
|
||||||
|
- `WebSocketManager` — Real-time progress broadcasting
|
||||||
|
- `ModelServiceFactory` — Creates the right service for each model type
|
||||||
|
- Use cases in `py/services/use_cases/` orchestrate complex business logic (auto-organize, bulk refresh, downloads)
|
||||||
|
|
||||||
**Frontend-Backend Communication:**
|
**Routes** (`py/routes/`):
|
||||||
- REST API for CRUD operations
|
- Route registrars organize endpoints by domain: `ModelRouteRegistrar`, `RecipeRouteRegistrar`, etc.
|
||||||
- WebSocket for real-time progress updates (downloads, scans)
|
- Request handlers in `py/routes/handlers/` implement route logic
|
||||||
- API endpoints follow `/loras/*` pattern
|
- API endpoints follow `/loras/*`, `/checkpoints/*`, `/embeddings/*` patterns
|
||||||
|
- All routes use aiohttp, return `web.json_response` or `web.Response`
|
||||||
|
|
||||||
|
**Recipe system** (`py/recipes/`):
|
||||||
|
- `base.py` — Recipe metadata structure
|
||||||
|
- `enrichment.py` — Enriches recipes with model metadata
|
||||||
|
- `parsers/` — Parsers for PNG metadata, JSON, and workflow formats
|
||||||
|
|
||||||
|
**Custom nodes** (`py/nodes/`):
|
||||||
|
- Each node class has a `NAME` class attribute used as key in `NODE_CLASS_MAPPINGS`
|
||||||
|
- Standard ComfyUI node pattern: `INPUT_TYPES()` classmethod, `RETURN_TYPES`, `FUNCTION`
|
||||||
|
- All nodes registered in `__init__.py`
|
||||||
|
|
||||||
|
**Configuration** (`py/config.py`):
|
||||||
|
- Manages folder paths for models, handles symlink mappings
|
||||||
|
- Auto-saves paths to settings.json in ComfyUI mode
|
||||||
|
|
||||||
|
### Frontend — Two Distinct UI Systems
|
||||||
|
|
||||||
|
#### 1. Standalone Manager Web UI
|
||||||
|
- **Location:** `static/` (JS/CSS) and `templates/` (HTML)
|
||||||
|
- **Tech:** Vanilla JS + CSS, served by standalone server
|
||||||
|
- **Structure:** `static/js/core.js` (shared), `loras.js`, `checkpoints.js`, `embeddings.js`, `recipes.js`, `statistics.js`
|
||||||
|
- **Tests:** `tests/frontend/**/*.test.js` (vitest + jsdom)
|
||||||
|
|
||||||
|
#### 2. ComfyUI Custom Node Widgets
|
||||||
|
- **Vanilla JS widgets:** `web/comfyui/*.js` — ES modules extending ComfyUI's LiteGraph UI
|
||||||
|
- `loras_widget.js` / `loras_widget_events.js` — Main LoRA selection widget
|
||||||
|
- `autocomplete.js` — Trigger word and embedding autocomplete
|
||||||
|
- `preview_tooltip.js` — Model card preview tooltips
|
||||||
|
- `top_menu_extension.js` — "Launch LoRA Manager" menu item
|
||||||
|
- `utils.js` — Shared utilities and API helpers
|
||||||
|
- Widget styling in `web/comfyui/lm_styles.css` (NOT `static/css/`)
|
||||||
|
- **Vue widgets:** `vue-widgets/src/` → built to `web/comfyui/vue-widgets/`
|
||||||
|
- Vue 3 + TypeScript + PrimeVue + vue-i18n
|
||||||
|
- Vite build with CSS-injected-by-JS plugin
|
||||||
|
- Components: `LoraPoolWidget`, `LoraRandomizerWidget`, `LoraCyclerWidget`, `AutocompleteTextWidget`
|
||||||
|
- Auto-built on ComfyUI startup via `py/vue_widget_builder.py`
|
||||||
|
- Tests: `vue-widgets/tests/**/*.test.ts` (vitest)
|
||||||
|
|
||||||
|
**Widget registration pattern:**
|
||||||
|
- Widgets use `app.registerExtension()` and `getCustomWidgets` hooks
|
||||||
|
- `node.addDOMWidget(name, type, element, options)` embeds HTML in LiteGraph nodes
|
||||||
|
- See `docs/dom_widget_dev_guide.md` for DOMWidget development guide
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
**Python:**
|
**Python:**
|
||||||
- PEP 8 with 4-space indentation
|
- PEP 8, 4-space indentation, English comments only
|
||||||
- snake_case for files, functions, variables
|
- Use `from __future__ import annotations` for forward references
|
||||||
- PascalCase for classes
|
- Use `TYPE_CHECKING` guard for type-checking-only imports
|
||||||
- Type hints preferred
|
|
||||||
- English comments only (per copilot-instructions.md)
|
|
||||||
- Loggers via `logging.getLogger(__name__)`
|
- Loggers via `logging.getLogger(__name__)`
|
||||||
|
- Custom exceptions in `py/services/errors.py`
|
||||||
|
- Async patterns: `async def` for I/O, `@pytest.mark.asyncio` for async tests
|
||||||
|
- Singleton pattern with class-level `asyncio.Lock` (see `ModelScanner.get_instance()`)
|
||||||
|
|
||||||
**JavaScript:**
|
**JavaScript:**
|
||||||
- ES modules with camelCase
|
- ES modules, camelCase functions/variables, PascalCase classes
|
||||||
- Files use `*_widget.js` suffix for ComfyUI widgets
|
- Widget files use `*_widget.js` suffix
|
||||||
- Prefer vanilla JS, avoid framework dependencies
|
- Prefer vanilla JS for `web/comfyui/` widgets, avoid framework dependencies (except Vue widgets)
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
**Backend Tests:**
|
**Backend (pytest):**
|
||||||
- pytest with `--import-mode=importlib`
|
- Config in `pytest.ini`: `--import-mode=importlib`, testpaths=`tests`
|
||||||
- Test files: `tests/test_*.py`
|
- Fixtures in `tests/conftest.py` handle ComfyUI dependency mocking
|
||||||
- Fixtures in `tests/conftest.py`
|
- Markers: `@pytest.mark.asyncio`, `@pytest.mark.no_settings_dir_isolation`
|
||||||
- Mock ComfyUI dependencies using standalone.py patterns
|
- Uses `tmp_path_factory` for directory isolation
|
||||||
- Markers: `@pytest.mark.asyncio` for async tests, `@pytest.mark.no_settings_dir_isolation` for real paths
|
|
||||||
|
|
||||||
**Frontend Tests:**
|
**Frontend (vitest):**
|
||||||
- Vitest with jsdom environment
|
- Vanilla JS tests: `tests/frontend/**/*.test.js` with jsdom
|
||||||
- Test files: `tests/frontend/**/*.test.js`
|
- Vue widget tests: `vue-widgets/tests/**/*.test.ts` with jsdom + @vue/test-utils
|
||||||
- Setup in `tests/frontend/setup.js`
|
- Setup in `tests/frontend/setup.js`
|
||||||
- Coverage via `npm run test:coverage`
|
|
||||||
|
|
||||||
## Important Notes
|
## Key Integration Points
|
||||||
|
|
||||||
**Settings Location:**
|
- **Settings:** Stored in user directory (via `platformdirs`) or portable mode (`"use_portable_settings": true`)
|
||||||
- ComfyUI mode: Auto-saves folder paths to user settings directory
|
- **CivitAI/CivArchive:** API clients for metadata sync and model downloads; CivitAI API key in settings
|
||||||
- Standalone mode: Use `settings.json` (copy from `settings.json.example`)
|
- **Symlink handling:** Config scans symlinks to map virtual→physical paths; fingerprinting prevents redundant rescans
|
||||||
- Portable mode: Set `"use_portable_settings": true` in settings.json
|
- **WebSocket:** Broadcasts real-time progress for downloads, scans, and metadata sync
|
||||||
|
- **Model scanning flow:** Walk folders → compute hashes → deduplicate → extract safetensors metadata → cache in SQLite → background CivitAI sync → WebSocket broadcast
|
||||||
**API Integration:**
|
|
||||||
- CivitAI API key required for downloads (add to settings)
|
|
||||||
- CivArchive API used as fallback for deleted models
|
|
||||||
- Metadata archive database available for offline metadata
|
|
||||||
|
|
||||||
**Symlink Handling:**
|
|
||||||
- Config scans symlinks to map virtual paths to physical locations
|
|
||||||
- Preview validation uses normalized preview root paths
|
|
||||||
- Fingerprinting prevents redundant symlink rescans
|
|
||||||
|
|
||||||
**ComfyUI Node Development:**
|
|
||||||
- Nodes defined in `py/nodes/`, registered in `__init__.py`
|
|
||||||
- Frontend widgets in `web/comfyui/`, matched by node type
|
|
||||||
- Use `WEB_DIRECTORY = "./web/comfyui"` convention
|
|
||||||
|
|
||||||
**Recipe Image Association:**
|
|
||||||
- Recipes scan for sibling images in same directory
|
|
||||||
- Supports repair/migration of recipe image paths
|
|
||||||
- See `py/services/recipe_scanner.py` for implementation details
|
|
||||||
|
|||||||
Reference in New Issue
Block a user