Add extra prompts, endless generation, random character default, and small fixes
- Add extra positive/negative prompt textareas to all 9 detail pages with session persistence - Add Endless generation button to all detail pages (continuous preview generation until stopped) - Default character selector to "Random Character" on all secondary detail pages - Fix queue clear endpoint (remove spurious auth check) - Refactor app.py into routes/ and services/ modules - Update CLAUDE.md with new architecture documentation - Various data file updates and cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
571
plans/APP_REFACTOR.md
Normal file
571
plans/APP_REFACTOR.md
Normal file
@@ -0,0 +1,571 @@
|
||||
# APP_REFACTOR.md — Split app.py into Modules
|
||||
|
||||
## Goal
|
||||
|
||||
Split the 8,478-line `app.py` into a clean module structure using Flask Blueprints for routes and plain Python modules for services/utilities. The JSON files and DB remain the source of truth — no data migration needed.
|
||||
|
||||
---
|
||||
|
||||
## Target Structure
|
||||
|
||||
```
|
||||
app.py # ~80 lines: Flask init, config, register blueprints, startup
|
||||
models.py # Unchanged
|
||||
utils.py # Pure helpers (no Flask/DB deps beyond current_app)
|
||||
services/
|
||||
__init__.py
|
||||
comfyui.py # ComfyUI HTTP client
|
||||
workflow.py # Workflow building + checkpoint settings
|
||||
prompts.py # Prompt building + dedup
|
||||
llm.py # LLM integration + MCP tool calls
|
||||
mcp.py # MCP/Docker server lifecycle
|
||||
sync.py # All sync_*() functions
|
||||
job_queue.py # Background job queue + worker thread
|
||||
file_io.py # LoRA/checkpoint scanning, file uploads, image saving
|
||||
routes/
|
||||
__init__.py # register_blueprints() helper
|
||||
characters.py # Character CRUD + generate + outfit management
|
||||
outfits.py # Outfit routes
|
||||
actions.py # Action routes
|
||||
styles.py # Style routes
|
||||
scenes.py # Scene routes
|
||||
detailers.py # Detailer routes
|
||||
checkpoints.py # Checkpoint routes
|
||||
looks.py # Look routes
|
||||
presets.py # Preset routes
|
||||
generator.py # Generator mix-and-match page
|
||||
gallery.py # Gallery browsing + image deletion
|
||||
settings.py # Settings page + status API + context processors
|
||||
strengths.py # Strengths gallery system
|
||||
transfer.py # Resource transfer system
|
||||
queue_api.py # /api/queue/* endpoints
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Contents — Exact Function Mapping
|
||||
|
||||
### `app.py` (entry point, ~80 lines)
|
||||
|
||||
Keep only:
|
||||
- Flask app creation, config, extensions (SQLAlchemy, Session)
|
||||
- Logging setup
|
||||
- `from routes import register_blueprints; register_blueprints(app)`
|
||||
- `with app.app_context():` block (DB init, migrations, sync calls, worker start)
|
||||
|
||||
### `utils.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `parse_orientation()` | 768 | Pure logic |
|
||||
| `_resolve_lora_weight()` | 833 | Pure logic |
|
||||
| `allowed_file()` | 765 | Pure logic |
|
||||
| `_IDENTITY_KEYS` | 850 | Constant |
|
||||
| `_WARDROBE_KEYS` | 851 | Constant |
|
||||
| `ALLOWED_EXTENSIONS` | 728 | Constant |
|
||||
| `_LORA_DEFAULTS` | 730 | Constant |
|
||||
|
||||
### `services/job_queue.py`
|
||||
|
||||
| Function/Global | Current Line | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `_job_queue_lock` | 75 | Global |
|
||||
| `_job_queue` | 76 | Global |
|
||||
| `_job_history` | 77 | Global |
|
||||
| `_queue_worker_event` | 78 | Global |
|
||||
| `_enqueue_job()` | 81 | Needs `comfyui.queue_prompt`, `comfyui.get_history` |
|
||||
| `_queue_worker()` | 102 | The background thread loop |
|
||||
| `_make_finalize()` | 227 | Needs `comfyui.get_history`, `comfyui.get_image`, DB models |
|
||||
| `_prune_job_history()` | 292 | Pure logic on globals |
|
||||
|
||||
### `services/comfyui.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `queue_prompt()` | 1083 | HTTP POST to ComfyUI |
|
||||
| `get_history()` | 1116 | HTTP GET + polling |
|
||||
| `get_image()` | 1148 | HTTP GET for image bytes |
|
||||
| `_ensure_checkpoint_loaded()` | 1056 | HTTP POST to load checkpoint |
|
||||
|
||||
All use `current_app.config['COMFYUI_URL']` — pass URL as param or read from config.
|
||||
|
||||
### `services/workflow.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `_prepare_workflow()` | 3119 | Core workflow wiring |
|
||||
| `_apply_checkpoint_settings()` | 6445 | Checkpoint-specific settings |
|
||||
| `_log_workflow_prompts()` | 3030 | Logging helper |
|
||||
| `_build_style_workflow()` | 5089 | Style-specific workflow builder |
|
||||
| `_build_checkpoint_workflow()` | 6501 | Checkpoint-specific workflow builder |
|
||||
| `_queue_scene_generation()` | 5603 | Scene generation helper |
|
||||
| `_queue_detailer_generation()` | 6091 | Detailer generation helper |
|
||||
| `_get_default_checkpoint()` | 3273 | Reads session + DB |
|
||||
|
||||
### `services/prompts.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `build_prompt()` | 933 | Core prompt builder |
|
||||
| `build_extras_prompt()` | 2104 | Generator page prompt builder |
|
||||
| `_dedup_tags()` | 797 | Tag deduplication |
|
||||
| `_cross_dedup_prompts()` | 808 | Cross-dedup positive/negative |
|
||||
| `_build_strengths_prompts()` | 7577 | Strengths-specific prompt builder |
|
||||
| `_get_character_data_without_lora()` | 7563 | Helper for strengths |
|
||||
| `_resolve_character()` | 853 | Resolve slug → Character |
|
||||
| `_ensure_character_fields()` | 861 | Mutate selected_fields |
|
||||
| `_append_background()` | 889 | Add background tag |
|
||||
|
||||
### `services/llm.py`
|
||||
|
||||
| Function/Constant | Current Line | Notes |
|
||||
|-------------------|-------------|-------|
|
||||
| `DANBOORU_TOOLS` | 1844 | Tool definitions constant |
|
||||
| `call_mcp_tool()` | 1904 | Sync MCP tool call |
|
||||
| `load_prompt()` | 1911 | Load system prompt from file |
|
||||
| `call_llm()` | 1918 | LLM chat completion + tool loop |
|
||||
|
||||
### `services/mcp.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `_ensure_mcp_repo()` | 423 | Clone danbooru-mcp repo |
|
||||
| `ensure_mcp_server_running()` | 457 | Start danbooru-mcp container |
|
||||
| `_ensure_character_mcp_repo()` | 496 | Clone character-mcp repo |
|
||||
| `ensure_character_mcp_server_running()` | 530 | Start character-mcp container |
|
||||
|
||||
### `services/sync.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `sync_characters()` | 1158 | |
|
||||
| `sync_outfits()` | 1218 | |
|
||||
| `ensure_default_outfit()` | 1276 | |
|
||||
| `sync_looks()` | 1360 | |
|
||||
| `sync_presets()` | 1415 | |
|
||||
| `sync_actions()` | 1534 | |
|
||||
| `sync_styles()` | 1591 | |
|
||||
| `sync_detailers()` | 1648 | |
|
||||
| `sync_scenes()` | 1705 | |
|
||||
| `sync_checkpoints()` | 1776 | |
|
||||
| `_default_checkpoint_data()` | 1762 | |
|
||||
| `_resolve_preset_entity()` | 1484 | Used by preset routes too |
|
||||
| `_resolve_preset_fields()` | 1494 | Used by preset routes too |
|
||||
| `_PRESET_ENTITY_MAP` | 1472 | Constant |
|
||||
|
||||
### `services/file_io.py`
|
||||
|
||||
| Function | Current Line | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `get_available_loras()` | 739 | Reads Settings + filesystem |
|
||||
| `get_available_checkpoints()` | 750 | Reads Settings + filesystem |
|
||||
| `_count_look_assignments()` | 895 | DB query |
|
||||
| `_count_outfit_lora_assignments()` | 907 | DB query |
|
||||
| `_scan_gallery_images()` | 7233 | Filesystem scan |
|
||||
| `_enrich_with_names()` | 7276 | DB lookups for gallery |
|
||||
| `_parse_comfy_png_metadata()` | 7340 | PNG metadata parsing |
|
||||
|
||||
### `routes/__init__.py`
|
||||
|
||||
```python
|
||||
def register_blueprints(app):
|
||||
from routes.characters import bp as characters_bp
|
||||
from routes.outfits import bp as outfits_bp
|
||||
# ... etc for all route modules
|
||||
app.register_blueprint(characters_bp)
|
||||
app.register_blueprint(outfits_bp)
|
||||
# ...
|
||||
```
|
||||
|
||||
### `routes/characters.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /` (index) | 2093 |
|
||||
| `POST /rescan` | 2098 |
|
||||
| `GET /character/<slug>` | 2289 |
|
||||
| `GET/POST /character/<slug>/transfer` | 2305 |
|
||||
| `GET/POST /create` | 2465 |
|
||||
| `GET/POST /character/<slug>/edit` | 2718 |
|
||||
| `POST /character/<slug>/outfit/*` (6 routes) | 2800–2988 |
|
||||
| `POST /character/<slug>/upload` | 2989 |
|
||||
| `POST /character/<slug>/replace_cover_from_preview` | 3018 |
|
||||
| `POST /character/<slug>/generate` | 3344 |
|
||||
| `POST /character/<slug>/save_defaults` | 3379 |
|
||||
| `GET /get_missing_characters` | 3303 |
|
||||
| `POST /clear_all_covers` | 3308 |
|
||||
| `POST /generate_missing` | 3316 |
|
||||
|
||||
### `routes/outfits.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /outfits` | 3844 |
|
||||
| `POST /outfits/rescan` | 3850 |
|
||||
| `POST /outfits/bulk_create` | 3856 |
|
||||
| `GET /outfit/<slug>` | 3966 |
|
||||
| `GET/POST /outfit/<slug>/edit` | 3991 |
|
||||
| `POST /outfit/<slug>/upload` | 4062 |
|
||||
| `POST /outfit/<slug>/generate` | 4091 |
|
||||
| `POST /outfit/<slug>/replace_cover_from_preview` | 4187 |
|
||||
| `GET/POST /outfit/create` | 4199 |
|
||||
| `POST /outfit/<slug>/save_defaults` | 4321 |
|
||||
| `POST /outfit/<slug>/clone` | 4330 |
|
||||
| `POST /outfit/<slug>/save_json` | 4380 |
|
||||
| Helper: `_get_linked_characters_for_outfit()` | 3956 |
|
||||
|
||||
### `routes/actions.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /actions` | 4398 |
|
||||
| `POST /actions/rescan` | 4403 |
|
||||
| `GET /action/<slug>` | 4409 |
|
||||
| `GET/POST /action/<slug>/edit` | 4430 |
|
||||
| `POST /action/<slug>/upload` | 4501 |
|
||||
| `POST /action/<slug>/generate` | 4530 |
|
||||
| `POST /action/<slug>/replace_cover_from_preview` | 4706 |
|
||||
| `POST /action/<slug>/save_defaults` | 4718 |
|
||||
| `POST /actions/bulk_create` | 4727 |
|
||||
| `GET/POST /action/create` | 4831 |
|
||||
| `POST /action/<slug>/clone` | 4905 |
|
||||
| `POST /action/<slug>/save_json` | 4947 |
|
||||
| `GET /get_missing_actions` | 3401 |
|
||||
| `POST /clear_all_action_covers` | 3406 |
|
||||
|
||||
### `routes/styles.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /styles` | 4965 |
|
||||
| `POST /styles/rescan` | 4970 |
|
||||
| `GET /style/<slug>` | 4976 |
|
||||
| `GET/POST /style/<slug>/edit` | 4997 |
|
||||
| `POST /style/<slug>/upload` | 5060 |
|
||||
| `POST /style/<slug>/generate` | 5142 |
|
||||
| `POST /style/<slug>/save_defaults` | 5185 |
|
||||
| `POST /style/<slug>/replace_cover_from_preview` | 5194 |
|
||||
| `GET /get_missing_styles` | 5206 |
|
||||
| `POST /clear_all_style_covers` | 5224 |
|
||||
| `POST /styles/generate_missing` | 5232 |
|
||||
| `POST /styles/bulk_create` | 5262 |
|
||||
| `GET/POST /style/create` | 5358 |
|
||||
| `POST /style/<slug>/clone` | 5412 |
|
||||
| `POST /style/<slug>/save_json` | 5453 |
|
||||
|
||||
### `routes/scenes.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /scenes` | 5471 |
|
||||
| `POST /scenes/rescan` | 5476 |
|
||||
| `GET /scene/<slug>` | 5482 |
|
||||
| `GET/POST /scene/<slug>/edit` | 5503 |
|
||||
| `POST /scene/<slug>/upload` | 5574 |
|
||||
| `POST /scene/<slug>/generate` | 5680 |
|
||||
| `POST /scene/<slug>/save_defaults` | 5721 |
|
||||
| `POST /scene/<slug>/replace_cover_from_preview` | 5730 |
|
||||
| `POST /scenes/bulk_create` | 5742 |
|
||||
| `GET/POST /scene/create` | 5844 |
|
||||
| `POST /scene/<slug>/clone` | 5902 |
|
||||
| `POST /scene/<slug>/save_json` | 5943 |
|
||||
| `GET /get_missing_scenes` | 3414 |
|
||||
| `POST /clear_all_scene_covers` | 3419 |
|
||||
|
||||
### `routes/detailers.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /detailers` | 5961 |
|
||||
| `POST /detailers/rescan` | 5966 |
|
||||
| `GET /detailer/<slug>` | 5972 |
|
||||
| `GET/POST /detailer/<slug>/edit` | 5999 |
|
||||
| `POST /detailer/<slug>/upload` | 6062 |
|
||||
| `POST /detailer/<slug>/generate` | 6154 |
|
||||
| `POST /detailer/<slug>/save_defaults` | 6206 |
|
||||
| `POST /detailer/<slug>/replace_cover_from_preview` | 6215 |
|
||||
| `POST /detailer/<slug>/save_json` | 6227 |
|
||||
| `POST /detailers/bulk_create` | 6243 |
|
||||
| `GET/POST /detailer/create` | 6340 |
|
||||
| `GET /get_missing_detailers` | 5211 |
|
||||
| `POST /clear_all_detailer_covers` | 5216 |
|
||||
|
||||
### `routes/checkpoints.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /checkpoints` | 6396 |
|
||||
| `POST /checkpoints/rescan` | 6401 |
|
||||
| `GET /checkpoint/<slug>` | 6407 |
|
||||
| `POST /checkpoint/<slug>/upload` | 6425 |
|
||||
| `POST /checkpoint/<slug>/generate` | 6531 |
|
||||
| `POST /checkpoint/<slug>/replace_cover_from_preview` | 6558 |
|
||||
| `POST /checkpoint/<slug>/save_json` | 6570 |
|
||||
| `GET /get_missing_checkpoints` | 6586 |
|
||||
| `POST /clear_all_checkpoint_covers` | 6591 |
|
||||
| `POST /checkpoints/bulk_create` | 6598 |
|
||||
|
||||
### `routes/looks.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /looks` | 6702 |
|
||||
| `POST /looks/rescan` | 6708 |
|
||||
| `GET /look/<slug>` | 6714 |
|
||||
| `GET/POST /look/<slug>/edit` | 6748 |
|
||||
| `POST /look/<slug>/upload` | 6803 |
|
||||
| `POST /look/<slug>/generate` | 6820 |
|
||||
| `POST /look/<slug>/replace_cover_from_preview` | 6898 |
|
||||
| `POST /look/<slug>/save_defaults` | 6910 |
|
||||
| `POST /look/<slug>/generate_character` | 6919 |
|
||||
| `POST /look/<slug>/save_json` | 7043 |
|
||||
| `GET/POST /look/create` | 7060 |
|
||||
| `GET /get_missing_looks` | 7103 |
|
||||
| `POST /clear_all_look_covers` | 7108 |
|
||||
| `POST /looks/bulk_create` | 7116 |
|
||||
|
||||
### `routes/presets.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /presets` | 3429 |
|
||||
| `GET /preset/<slug>` | 3435 |
|
||||
| `POST /preset/<slug>/generate` | 3442 |
|
||||
| `POST /preset/<slug>/replace_cover_from_preview` | 3595 |
|
||||
| `POST /preset/<slug>/upload` | 3608 |
|
||||
| `GET/POST /preset/<slug>/edit` | 3628 |
|
||||
| `POST /preset/<slug>/save_json` | 3710 |
|
||||
| `POST /preset/<slug>/clone` | 3728 |
|
||||
| `POST /presets/rescan` | 3757 |
|
||||
| `GET/POST /preset/create` | 3764 |
|
||||
| `GET /get_missing_presets` | 3836 |
|
||||
|
||||
### `routes/generator.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET/POST /generator` | 2168 |
|
||||
| `POST /generator/preview_prompt` | 2256 |
|
||||
|
||||
### `routes/gallery.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /gallery` | 7296 |
|
||||
| `GET /gallery/prompt-data` | 7414 |
|
||||
| `POST /gallery/delete` | 7434 |
|
||||
| `POST /resource/<category>/<slug>/delete` | 7457 |
|
||||
|
||||
Constants: `GALLERY_CATEGORIES`, `_MODEL_MAP`
|
||||
|
||||
### `routes/settings.py`
|
||||
|
||||
| Route/Function | Current Line |
|
||||
|----------------|-------------|
|
||||
| `GET/POST /settings` | 2066 |
|
||||
| `POST /set_default_checkpoint` | 588 |
|
||||
| `GET /api/status/comfyui` | 625 |
|
||||
| `GET /api/comfyui/loaded_checkpoint` | 638 |
|
||||
| `GET /api/status/mcp` | 659 |
|
||||
| `GET /api/status/llm` | 674 |
|
||||
| `GET /api/status/character-mcp` | 712 |
|
||||
| `POST /get_openrouter_models` | 2035 |
|
||||
| `POST /get_local_models` | 2051 |
|
||||
| Context processor: `inject_comfyui_ws_url()` | 569 |
|
||||
| Context processor: `inject_default_checkpoint()` | 582 |
|
||||
|
||||
### `routes/strengths.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `POST /strengths/<category>/<slug>/generate` | 7796 |
|
||||
| `GET /strengths/<category>/<slug>/list` | 7892 |
|
||||
| `POST /strengths/<category>/<slug>/clear` | 7917 |
|
||||
| `POST /strengths/<category>/<slug>/save_range` | 7939 |
|
||||
|
||||
Constants: `_STRENGTHS_MODEL_MAP`, `_CATEGORY_LORA_NODES`, `_STRENGTHS_DATA_DIRS`
|
||||
Helper: `_prepare_strengths_workflow()` (7690)
|
||||
|
||||
### `routes/transfer.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET/POST /resource/<category>/<slug>/transfer` | 8118 |
|
||||
|
||||
Constants: `_RESOURCE_TRANSFER_MAP`, `_TRANSFER_TARGET_CATEGORIES`
|
||||
Helper: `_create_minimal_template()` (8045)
|
||||
|
||||
### `routes/queue_api.py`
|
||||
|
||||
| Route | Current Line |
|
||||
|-------|-------------|
|
||||
| `GET /api/queue` | 306 |
|
||||
| `GET /api/queue/count` | 323 |
|
||||
| `POST /api/queue/<job_id>/remove` | 331 |
|
||||
| `POST /api/queue/<job_id>/pause` | 348 |
|
||||
| `POST /api/queue/clear` | 365 |
|
||||
| `GET /api/queue/<job_id>/status` | 396 |
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
app.py
|
||||
├── models.py (unchanged)
|
||||
├── utils.py (no deps except stdlib)
|
||||
├── services/
|
||||
│ ├── comfyui.py ← utils (for config)
|
||||
│ ├── prompts.py ← utils, models
|
||||
│ ├── workflow.py ← comfyui, prompts, utils, models
|
||||
│ ├── llm.py ← mcp (for tool calls)
|
||||
│ ├── mcp.py ← (stdlib only: subprocess, docker)
|
||||
│ ├── sync.py ← models, utils
|
||||
│ ├── job_queue.py ← comfyui, models
|
||||
│ └── file_io.py ← models, utils
|
||||
└── routes/
|
||||
├── All route modules ← services/*, utils, models
|
||||
└── (routes never import from other routes)
|
||||
```
|
||||
|
||||
**No circular imports**: routes → services → utils/models. Services never import routes. Utils never imports services.
|
||||
|
||||
---
|
||||
|
||||
## Migration Phases
|
||||
|
||||
### Phase 1 — Extract services (no route changes)
|
||||
|
||||
**Order matters** — extract leaf dependencies first:
|
||||
|
||||
1. **`utils.py`** — Pure constants and helpers. Zero risk. Cut `parse_orientation`, `_resolve_lora_weight`, `allowed_file`, `ALLOWED_EXTENSIONS`, `_LORA_DEFAULTS`, `_IDENTITY_KEYS`, `_WARDROBE_KEYS` out of app.py. Add imports in app.py to keep everything working.
|
||||
|
||||
2. **`services/comfyui.py`** — `queue_prompt`, `get_history`, `get_image`, `_ensure_checkpoint_loaded`. These only use `requests` + config URL. Accept `comfyui_url` as parameter or read `current_app.config`.
|
||||
|
||||
3. **`services/mcp.py`** — `_ensure_mcp_repo`, `ensure_mcp_server_running`, `_ensure_character_mcp_repo`, `ensure_character_mcp_server_running`. Only uses `subprocess`/`os`/`logging`.
|
||||
|
||||
4. **`services/llm.py`** — `DANBOORU_TOOLS`, `call_mcp_tool`, `load_prompt`, `call_llm`. Depends on `services/mcp` for tool calls and `models.Settings` for config.
|
||||
|
||||
5. **`services/prompts.py`** — `build_prompt`, `build_extras_prompt`, `_dedup_tags`, `_cross_dedup_prompts`, `_resolve_character`, `_ensure_character_fields`, `_append_background`, `_build_strengths_prompts`, `_get_character_data_without_lora`. Depends on `utils` and `models`.
|
||||
|
||||
6. **`services/workflow.py`** — `_prepare_workflow`, `_apply_checkpoint_settings`, `_log_workflow_prompts`, `_build_style_workflow`, `_build_checkpoint_workflow`, `_queue_scene_generation`, `_queue_detailer_generation`, `_get_default_checkpoint`. Depends on `comfyui`, `prompts`, `utils`, `models`.
|
||||
|
||||
7. **`services/sync.py`** — All `sync_*()` + `_default_checkpoint_data`, `_resolve_preset_entity`, `_resolve_preset_fields`, `_PRESET_ENTITY_MAP`. Depends on `models`, `utils`.
|
||||
|
||||
8. **`services/file_io.py`** — `get_available_loras`, `get_available_checkpoints`, `_count_look_assignments`, `_count_outfit_lora_assignments`, `_scan_gallery_images`, `_enrich_with_names`, `_parse_comfy_png_metadata`. Depends on `models`, `utils`.
|
||||
|
||||
9. **`services/job_queue.py`** — Queue globals, `_enqueue_job`, `_queue_worker`, `_make_finalize`, `_prune_job_history`. Depends on `comfyui`, `models`. Extract last because the worker thread references many things.
|
||||
|
||||
**After Phase 1**: `app.py` still has all routes, but imports helpers from `services/*` and `utils`. Each service module is independently testable. The app should work identically.
|
||||
|
||||
### Phase 2 — Extract routes into Blueprints
|
||||
|
||||
**Order**: Start with the smallest/most isolated, work toward characters (largest).
|
||||
|
||||
1. **`routes/queue_api.py`** — 6 routes, only depends on `job_queue` globals. Blueprint prefix: none (keeps `/api/queue/*`).
|
||||
|
||||
2. **`routes/settings.py`** — Settings page + status APIs + context processors. Register context processors on the app via `app.context_processor` in `register_blueprints()`.
|
||||
|
||||
3. **`routes/gallery.py`** — Gallery + resource delete. Depends on `file_io`, `models`.
|
||||
|
||||
4. **`routes/transfer.py`** — Transfer system. Self-contained with its own constants.
|
||||
|
||||
5. **`routes/strengths.py`** — Strengths system. Self-contained with its own constants + workflow helper.
|
||||
|
||||
6. **`routes/generator.py`** — Generator page. Depends on `prompts.build_extras_prompt`.
|
||||
|
||||
7. **`routes/checkpoints.py`** — Smallest category. Good test case for the category pattern.
|
||||
|
||||
8. **`routes/presets.py`** — Preset CRUD. Depends on `sync._resolve_preset_*`.
|
||||
|
||||
9. **`routes/looks.py`** — Look CRUD + generate_character.
|
||||
|
||||
10. **`routes/detailers.py`** — Detailer CRUD.
|
||||
|
||||
11. **`routes/scenes.py`** — Scene CRUD.
|
||||
|
||||
12. **`routes/styles.py`** — Style CRUD.
|
||||
|
||||
13. **`routes/actions.py`** — Action CRUD.
|
||||
|
||||
14. **`routes/outfits.py`** — Outfit CRUD + linked characters helper.
|
||||
|
||||
15. **`routes/characters.py`** — Character CRUD + outfit management + transfer. Largest blueprint (~1300 lines). Do last since it has the most cross-cutting concerns.
|
||||
|
||||
**After Phase 2**: `app.py` is ~80 lines. All routes live in blueprints. Full functionality preserved.
|
||||
|
||||
### Phase 3 — Verification & cleanup
|
||||
|
||||
1. Run the app, test every page manually (index, detail, generate, edit, clone, delete for each category).
|
||||
2. Test batch generation, generator page, gallery, settings, strengths, transfer.
|
||||
3. Remove any dead imports from `app.py`.
|
||||
4. Update `CLAUDE.md` to reflect new file structure.
|
||||
|
||||
---
|
||||
|
||||
## Blueprint Pattern Template
|
||||
|
||||
Each route blueprint follows this pattern:
|
||||
|
||||
```python
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from models import db, Outfit # only what's needed
|
||||
from services.workflow import _prepare_workflow
|
||||
from services.prompts import build_prompt, _resolve_character, _ensure_character_fields, _append_background
|
||||
from services.job_queue import _enqueue_job, _make_finalize
|
||||
from services.file_io import get_available_loras
|
||||
from services.llm import call_llm, load_prompt
|
||||
from utils import allowed_file
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('gaze')
|
||||
|
||||
bp = Blueprint('outfits', __name__)
|
||||
|
||||
@bp.route('/outfits')
|
||||
def outfits_index():
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
- **Circular imports**: Enforced by the dependency graph — routes → services → utils/models. If a service needs something from another service, import it at function level if needed.
|
||||
- **`current_app` vs `app`**: Routes already use `request`, `session`, etc. which are context-local. Services that need app config use `current_app.config[...]` inside function bodies (not at module level).
|
||||
- **Thread safety**: `job_queue.py` keeps the same threading globals. The worker thread is started in `app.py`'s startup block, same as before.
|
||||
- **Session access**: Only route functions access `session`. Services that currently read session (like `_get_default_checkpoint`) will stay in `services/workflow.py` and import `session` from flask — this is fine since they're only called from within request context.
|
||||
- **Testing**: After each phase-1 extraction, verify the app starts and the affected functionality works before proceeding to the next module.
|
||||
|
||||
---
|
||||
|
||||
## Lines of Code Estimate (per module)
|
||||
|
||||
| Module | Approx Lines |
|
||||
|--------|-------------|
|
||||
| `app.py` (final) | ~100 |
|
||||
| `utils.py` | ~120 |
|
||||
| `services/comfyui.py` | ~120 |
|
||||
| `services/mcp.py` | ~150 |
|
||||
| `services/llm.py` | ~200 |
|
||||
| `services/prompts.py` | ~350 |
|
||||
| `services/workflow.py` | ~400 |
|
||||
| `services/sync.py` | ~800 |
|
||||
| `services/job_queue.py` | ~250 |
|
||||
| `services/file_io.py` | ~250 |
|
||||
| `routes/characters.py` | ~1300 |
|
||||
| `routes/outfits.py` | ~550 |
|
||||
| `routes/actions.py` | ~570 |
|
||||
| `routes/styles.py` | ~500 |
|
||||
| `routes/scenes.py` | ~500 |
|
||||
| `routes/detailers.py` | ~450 |
|
||||
| `routes/checkpoints.py` | ~350 |
|
||||
| `routes/looks.py` | ~550 |
|
||||
| `routes/presets.py` | ~450 |
|
||||
| `routes/generator.py` | ~150 |
|
||||
| `routes/gallery.py` | ~300 |
|
||||
| `routes/settings.py` | ~250 |
|
||||
| `routes/strengths.py` | ~400 |
|
||||
| `routes/transfer.py` | ~200 |
|
||||
| `routes/queue_api.py` | ~120 |
|
||||
| **Total** | **~8,430** |
|
||||
1006
plans/OUTFIT_LOOKS_REFACTOR.md
Normal file
1006
plans/OUTFIT_LOOKS_REFACTOR.md
Normal file
File diff suppressed because it is too large
Load Diff
149
plans/TRANSFER.md
Normal file
149
plans/TRANSFER.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Resource Transfer (Non-Character) — Implementation Plan
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
- Add **Transfer** support and UI to these resource types:
|
||||
- **Looks**
|
||||
- **Outfits**
|
||||
- **Actions**
|
||||
- **Styles**
|
||||
- **Scenes**
|
||||
- **Detailers**
|
||||
|
||||
### Out of scope / explicitly excluded
|
||||
- **Characters** (existing flow may remain separate)
|
||||
- **Checkpoints**
|
||||
- **Presets**
|
||||
|
||||
## User experience requirements
|
||||
|
||||
### Entry points
|
||||
- A **Transfer** button should appear on **all supported resource detail pages**.
|
||||
- Clicking **Transfer** opens a transfer form for the currently viewed resource.
|
||||
|
||||
### Transfer form fields
|
||||
The form must allow the user to:
|
||||
- Select **target category** (destination resource type)
|
||||
- Edit **new name**
|
||||
- Edit **new id/slug**
|
||||
- Optionally choose **Use LLM** to regenerate JSON (recommended default: enabled)
|
||||
|
||||
All of these fields should be **pre-populated using the existing resource’s information**.
|
||||
|
||||
### Post-submit behavior
|
||||
- After transfer completes, redirect to the **target category index page** (e.g. `/looks`).
|
||||
- **Highlight** the newly created item on the index page.
|
||||
- Do **not** auto-generate previews/covers.
|
||||
|
||||
## Current state (reference implementation)
|
||||
|
||||
- Character-only transfer route exists: [`transfer_character()`](../app.py:2216)
|
||||
- Character detail page shows Transfer in header: [`templates/detail.html`](../templates/detail.html:79)
|
||||
- Resource detail templates do not currently show Transfer (examples):
|
||||
- Looks: [`templates/looks/detail.html`](../templates/looks/detail.html:123)
|
||||
- Outfits: [`templates/outfits/detail.html`](../templates/outfits/detail.html:118)
|
||||
- Actions: [`templates/actions/detail.html`](../templates/actions/detail.html:129)
|
||||
|
||||
## Proposed backend design
|
||||
|
||||
### 1) Add a generic resource transfer route
|
||||
Implement a category-based route:
|
||||
|
||||
- `GET|POST /resource/<category>/<slug>/transfer`
|
||||
|
||||
Responsibilities:
|
||||
- Validate `category` is in allowlist: `looks,outfits,actions,styles,scenes,detailers`
|
||||
- Load the appropriate SQLAlchemy model instance by `slug`
|
||||
- Render a resource-agnostic transfer form on GET
|
||||
- On POST:
|
||||
- Validate new name/id constraints
|
||||
- Generate a safe/unique slug for the destination category
|
||||
- Create destination JSON (LLM path or non-LLM fallback)
|
||||
- Write JSON file to correct `data/<category>/` directory
|
||||
- Create DB row for the new resource
|
||||
- Redirect to destination index with highlight
|
||||
|
||||
### 2) Shared mapping table
|
||||
Create a mapping table/dict:
|
||||
- `category → model_class`
|
||||
- `category → target_dir`
|
||||
- `category → id_field` (e.g. `look_id`, `outfit_id`, ...)
|
||||
- `category → index_route` (e.g. `looks_index`)
|
||||
|
||||
This mirrors the `target_type` mapping approach in [`transfer_character()`](../app.py:2251).
|
||||
|
||||
## JSON generation strategy
|
||||
|
||||
### LLM path (primary)
|
||||
- Use an explicit transfer prompt (existing code loads `transfer_system.txt` in [`transfer_character()`](../app.py:2289)).
|
||||
- Provide the source resource JSON as input context.
|
||||
- Instruct the LLM:
|
||||
- to output **JSON only** (no markdown)
|
||||
- to conform to the **target resource schema**
|
||||
- After parsing:
|
||||
- enforce required fields: `<target>_id` and `<target>_name` (pattern used in [`transfer_character()`](../app.py:2316))
|
||||
|
||||
### Non-LLM fallback
|
||||
- Create a minimal template JSON for the target type.
|
||||
- Copy safe common fields where present (e.g. `tags`, `lora`).
|
||||
- Add a provenance description like “Transferred from <source>”.
|
||||
|
||||
## Frontend changes
|
||||
|
||||
### 1) Add Transfer button to supported resource detail pages
|
||||
Add a Transfer button near existing header actions, pointing to `/resource/<category>/<slug>/transfer`.
|
||||
|
||||
Targets:
|
||||
- [`templates/looks/detail.html`](../templates/looks/detail.html:123)
|
||||
- [`templates/outfits/detail.html`](../templates/outfits/detail.html:118)
|
||||
- [`templates/actions/detail.html`](../templates/actions/detail.html:129)
|
||||
- [`templates/styles/detail.html`](../templates/styles/detail.html:1)
|
||||
- [`templates/scenes/detail.html`](../templates/scenes/detail.html:1)
|
||||
- [`templates/detailers/detail.html`](../templates/detailers/detail.html:1)
|
||||
|
||||
### 2) Highlight the new item on the destination index
|
||||
Redirect scheme:
|
||||
- `/looks?highlight=<slug>` (similar for each category)
|
||||
|
||||
Index templates to update:
|
||||
- [`templates/looks/index.html`](../templates/looks/index.html:1)
|
||||
- [`templates/outfits/index.html`](../templates/outfits/index.html:1)
|
||||
- [`templates/actions/index.html`](../templates/actions/index.html:1)
|
||||
- [`templates/styles/index.html`](../templates/styles/index.html:1)
|
||||
- [`templates/scenes/index.html`](../templates/scenes/index.html:1)
|
||||
- [`templates/detailers/index.html`](../templates/detailers/index.html:1)
|
||||
|
||||
Implementation idea:
|
||||
- Add `data-slug="..."` to each item card/container and a small script that:
|
||||
- reads `highlight` query param
|
||||
- finds the element
|
||||
- scrolls into view
|
||||
- applies a temporary CSS class (e.g. `highlight-pulse`)
|
||||
|
||||
## Validation & error handling
|
||||
|
||||
- Reject invalid/unsupported categories.
|
||||
- Enforce name length limits (existing character transfer uses `<= 100` in [`transfer_character()`](../app.py:2228)).
|
||||
- Sanitize id/slug to safe filename characters.
|
||||
- Ensure destination JSON filename is unique; if exists, auto-suffix `_2`, `_3`, ...
|
||||
- LLM error handling:
|
||||
- JSON parse failures should show a user-facing flash message
|
||||
- preserve form inputs on redirect if possible
|
||||
|
||||
## Testing
|
||||
|
||||
Add tests covering:
|
||||
- Happy path transfer for one representative resource (e.g. look → outfit)
|
||||
- Invalid category rejected
|
||||
- Duplicate slug resolution works
|
||||
- LLM returns invalid JSON → proper failure handling
|
||||
|
||||
## Deliverables checklist
|
||||
|
||||
- New transfer route and supporting helper mapping in app backend
|
||||
- Resource-agnostic transfer template
|
||||
- Transfer button on all supported detail pages
|
||||
- Index page highlight behavior
|
||||
- Prompt file verified/added for transfer conversions
|
||||
- Tests + minimal docs update
|
||||
775
plans/gallery-enhancement-plan.md
Normal file
775
plans/gallery-enhancement-plan.md
Normal file
@@ -0,0 +1,775 @@
|
||||
# GAZE Gallery Enhancement Plan
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This plan outlines a comprehensive enhancement to the GAZE Image Gallery, transforming it from a basic grid-and-lightbox viewer into an immersive, multi-modal viewing experience. The enhancement introduces **three major feature categories**: Slideshow Modes, Image Wall Layouts, and Interactive Viewing Tools.
|
||||
|
||||
---
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Existing Implementation
|
||||
|
||||
| Component | Current State | File Location |
|
||||
|-----------|--------------|---------------|
|
||||
| Grid View | Auto-fill grid, 160-210px cards, hover overlays | [`templates/gallery.html`](templates/gallery.html:88) |
|
||||
| Lightbox | Bootstrap modal, arrow navigation, keyboard support | [`templates/layout.html`](templates/layout.html:123) |
|
||||
| Filtering | Category, item, sort, pagination | [`templates/gallery.html`](templates/gallery.html:11) |
|
||||
| Metadata | Prompt modal with LoRA chips, generation params | [`templates/gallery.html`](templates/gallery.html:194) |
|
||||
|
||||
### Design System Variables
|
||||
|
||||
```css
|
||||
/* From static/style.css — these will guide our new components */
|
||||
--bg-base: #07070f;
|
||||
--bg-card: #0c0c1c;
|
||||
--accent: #8b7eff;
|
||||
--accent-glow: rgba(139, 126, 255, 0.14);
|
||||
--border: #16163a;
|
||||
--text: #e8e8f5;
|
||||
--text-muted: #6a6a9a;
|
||||
--radius: 12px;
|
||||
--font-display: 'Space Grotesk';
|
||||
--font-body: 'Inter';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Gallery Page
|
||||
A[View Mode Selector] --> B[Grid View]
|
||||
A --> C[Masonry View]
|
||||
A --> D[Justified View]
|
||||
A --> E[Collage View]
|
||||
end
|
||||
|
||||
subgraph Viewer Modes
|
||||
F[Cinema Slideshow]
|
||||
G[Classic Slideshow]
|
||||
H[Showcase Frame]
|
||||
I[Comparison Mode]
|
||||
J[Discovery Mode]
|
||||
K[Ambient Screensaver]
|
||||
end
|
||||
|
||||
subgraph Core Engine
|
||||
L[GalleryController]
|
||||
M[ImageLoader - lazy/preload]
|
||||
N[TransitionEngine]
|
||||
O[MetadataProvider]
|
||||
end
|
||||
|
||||
B & C & D & E --> L
|
||||
L --> F & G & H & I & J & K
|
||||
L --> M
|
||||
L --> N
|
||||
L --> O
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Feature Specifications
|
||||
|
||||
### 1. View Mode Selector
|
||||
|
||||
A floating toolbar that allows switching between gallery layouts.
|
||||
|
||||
**UI Design:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [≡ Grid] [◫ Masonry] [▬ Justified] [◱ Collage] │ [▶ Slideshow ▾] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Slideshow Dropdown:**
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ 🎬 Cinema │
|
||||
│ ⏯ Classic │
|
||||
│ 🖼 Showcase │
|
||||
│ ⚡ Discovery │
|
||||
│ 🌌 Ambient │
|
||||
│ ⚖ Comparison │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Image Wall Layouts
|
||||
|
||||
#### 2.1 Masonry Layout (Pinterest-style)
|
||||
|
||||
Variable height columns that create a waterfall effect.
|
||||
|
||||
```
|
||||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
||||
│ │ │ │ │ │ │ │
|
||||
│ 1 │ │ 2 │ │ │ │ 4 │
|
||||
│ │ └──────┘ │ 3 │ │ │
|
||||
└──────┘ ┌──────┐ │ │ └──────┘
|
||||
┌──────┐ │ │ │ │ ┌──────┐
|
||||
│ 5 │ │ 6 │ └──────┘ │ │
|
||||
└──────┘ │ │ ┌──────┐ │ 8 │
|
||||
┌──────┐ └──────┘ │ 7 │ │ │
|
||||
│ 9 │ ┌──────┐ └──────┘ └──────┘
|
||||
└──────┘ │ 10 │
|
||||
└──────┘
|
||||
```
|
||||
|
||||
**Technical Approach:**
|
||||
- CSS columns or CSS Grid with `grid-auto-rows: 1px` and `span` calculation
|
||||
- JavaScript to calculate optimal column count based on viewport
|
||||
- Preserve natural aspect ratios of images
|
||||
- Lazy loading with Intersection Observer
|
||||
|
||||
**CSS Variables:**
|
||||
```css
|
||||
--masonry-column-width: 280px;
|
||||
--masonry-gap: 12px;
|
||||
--masonry-min-columns: 2;
|
||||
--masonry-max-columns: 6;
|
||||
```
|
||||
|
||||
#### 2.2 Justified Layout (Google Photos-style)
|
||||
|
||||
Rows with perfect edge alignment, variable widths.
|
||||
|
||||
```
|
||||
┌────────────┬──────────┬─────────────────┐
|
||||
│ 1 │ 2 │ 3 │
|
||||
└────────────┴──────────┴─────────────────┘
|
||||
┌─────────────────┬────────────┬─────────┐
|
||||
│ 4 │ 5 │ 6 │
|
||||
└─────────────────┴────────────┴─────────┘
|
||||
┌──────────┬─────────────────┬───────────┐
|
||||
│ 7 │ 8 │ 9 │
|
||||
└──────────┴─────────────────┴───────────┘
|
||||
```
|
||||
|
||||
**Technical Approach:**
|
||||
- Linear partition algorithm to distribute images across rows
|
||||
- Target row height with flex-grow distribution
|
||||
- Smart breakpoints based on aspect ratios
|
||||
- Each row is a flex container
|
||||
|
||||
**Configuration:**
|
||||
```javascript
|
||||
{
|
||||
targetRowHeight: 240,
|
||||
minRowHeight: 180,
|
||||
maxRowHeight: 320,
|
||||
containerPadding: 16,
|
||||
imageSpacing: 8
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Collage Layout (Magazine-style)
|
||||
|
||||
Creative mixed-size layout with featured images.
|
||||
|
||||
```
|
||||
┌───────────────────┬─────────┐
|
||||
│ │ 2 │
|
||||
│ 1 ├─────────┤
|
||||
│ FEATURED │ 3 │
|
||||
│ ├─────────┤
|
||||
├─────────┬─────────┤ 4 │
|
||||
│ 5 │ 6 │ │
|
||||
└─────────┴─────────┴─────────┘
|
||||
```
|
||||
|
||||
**Technical Approach:**
|
||||
- CSS Grid with named areas
|
||||
- Template patterns that rotate/cycle
|
||||
- Special "featured" designation based on:
|
||||
- User favorites (if implemented)
|
||||
- Most recent
|
||||
- Random selection
|
||||
- Subtle rotation transforms (-2° to +2°) on some tiles
|
||||
- Optional overlap with z-index layering
|
||||
|
||||
**Template Patterns:**
|
||||
```javascript
|
||||
const collagePatterns = [
|
||||
'hero-right', // Large left, stack right
|
||||
'hero-left', // Stack left, large right
|
||||
'hero-center', // Center featured, corners fill
|
||||
'magazine', // Asymmetric editorial style
|
||||
'scattered' // Random positions with overlap
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Slideshow Modes
|
||||
|
||||
#### 3.1 Cinema Mode
|
||||
|
||||
Full-screen immersive experience with cinematic effects.
|
||||
|
||||
**Features:**
|
||||
- **Ambient glow**: Extract dominant colors from image, project as soft backdrop glow
|
||||
- **Ken Burns effect**: Subtle pan/zoom animation on images
|
||||
- **Vignette overlay**: Soft dark edges for focus
|
||||
- **Particle system**: Optional floating light particles/orbs
|
||||
- **Audio reactive**: Optional ambient music visualization (future)
|
||||
|
||||
**Visual Design:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
|
||||
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
|
||||
│░░░░░░┌─────────────────────────────────────────────┐░░░░░░░░░░░░│
|
||||
│░░░░░░│ │░░░░░░░░░░░░│
|
||||
│░░░░░░│ [Image with Ken Burns] │░░░░░░░░░░░░│
|
||||
│░░░░░░│ │░░░░░░░░░░░░│
|
||||
│░░░░░░│ │░░░░░░░░░░░░│
|
||||
│░░░░░░└─────────────────────────────────────────────┘░░░░░░░░░░░░│
|
||||
│░░░░░░░░░░░░░░░░░░░ ● ● ● ○ ○ ○ ○ ○ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
|
||||
│░░░░░░░░░░░░░░░░░░░░░ 4 / 24 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
|
||||
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
|
||||
│░░░░ = Ambient glow from image colors ░░░░░░░░░░░░░░░░░░░░░░░░░░░│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Controls (appear on hover/touch):**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ⏮ ⏯ ⏭ │ ⏱ 5s ▾ │ 🔀 Shuffle │ ℹ Info │ ✕ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Ken Burns Patterns:**
|
||||
```javascript
|
||||
const kenBurnsPatterns = [
|
||||
{ start: 'scale(1) translate(0, 0)', end: 'scale(1.15) translate(-3%, -2%)' },
|
||||
{ start: 'scale(1.1) translate(-5%, 0)', end: 'scale(1) translate(0, 0)' },
|
||||
{ start: 'scale(1) translate(0, 5%)', end: 'scale(1.12) translate(2%, -3%)' },
|
||||
// ... more patterns
|
||||
];
|
||||
```
|
||||
|
||||
**Ambient Glow Implementation:**
|
||||
```javascript
|
||||
// Use canvas to extract dominant colors
|
||||
async function extractAmbientColors(imageSrc) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = await loadImage(imageSrc);
|
||||
|
||||
canvas.width = 50; // Downscale for performance
|
||||
canvas.height = 50;
|
||||
ctx.drawImage(img, 0, 0, 50, 50);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, 50, 50);
|
||||
// Quantize colors and find dominant clusters
|
||||
return dominantColors(imageData); // Returns [primary, secondary, accent]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 Classic Slideshow
|
||||
|
||||
Clean, professional presentation mode.
|
||||
|
||||
**Features:**
|
||||
- Multiple transition types: fade, slide, zoom, cube
|
||||
- Progress bar or dot indicators
|
||||
- Timer controls (3s, 5s, 8s, 15s, manual)
|
||||
- Loop toggle
|
||||
- Random/sequential order
|
||||
|
||||
**UI:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ │
|
||||
│ ┌───────────────┐ │
|
||||
│ ◀ │ │ ▶ │
|
||||
│ │ IMAGE │ │
|
||||
│ │ │ │
|
||||
│ └───────────────┘ │
|
||||
│ │
|
||||
│ ● ● ● ○ ○ ○ ○ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ ⏮ ⏯ ⏭ 🔀 Shuffle ⏱ 5s ✕ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Transitions CSS:**
|
||||
```css
|
||||
.slide-transition-fade { transition: opacity 0.8s ease; }
|
||||
.slide-transition-slide { transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
|
||||
.slide-transition-zoom { transition: transform 0.7s ease, opacity 0.7s ease; }
|
||||
.slide-transition-cube {
|
||||
transform-style: preserve-3d;
|
||||
perspective: 1000px;
|
||||
transition: transform 0.8s ease;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 Showcase Frame Mode
|
||||
|
||||
Digital picture frame aesthetic with metadata.
|
||||
|
||||
**Features:**
|
||||
- Image displayed with decorative frame/mat
|
||||
- Metadata panel (character name, category, date)
|
||||
- Ambient room simulation (optional)
|
||||
- Clock widget (optional)
|
||||
- Subtle animations
|
||||
|
||||
**Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ╔══════════════════════════════════════════════╗ │
|
||||
│ ║ ┌──────────────────────────────────────────┐ ║ │
|
||||
│ ║ │ │ ║ │
|
||||
│ ║ │ │ ║ │
|
||||
│ ║ │ IMAGE │ ║ │
|
||||
│ ║ │ │ ║ │
|
||||
│ ║ │ │ ║ │
|
||||
│ ║ └──────────────────────────────────────────┘ ║ │
|
||||
│ ╚══════════════════════════════════════════════╝ │
|
||||
│ │
|
||||
│ Character: Luna Eclipse │
|
||||
│ Category: characters • Style: anime │
|
||||
│ Created: March 8, 2026 │
|
||||
│ 12:34 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Frame Styles:**
|
||||
```javascript
|
||||
const frameStyles = [
|
||||
'minimal', // Thin elegant border
|
||||
'classic', // Traditional wooden frame look
|
||||
'modern', // Thick black mat, thin metal frame
|
||||
'gallery', // White mat, thin black frame
|
||||
'floating', // Shadow box effect
|
||||
'none' // No frame, edge-to-edge
|
||||
];
|
||||
```
|
||||
|
||||
#### 3.4 Discovery Mode (Random Shuffle)
|
||||
|
||||
Explore your collection with serendipity.
|
||||
|
||||
**Features:**
|
||||
- Random image selection with no repeats until all shown
|
||||
- "Like" button to save favorites (session-based)
|
||||
- Quick category jump
|
||||
- Surprise effects (occasional zoom, pan variations)
|
||||
- Stats display ("You've discovered 47 of 234 images")
|
||||
|
||||
**Controls:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ⚡ DISCOVERY MODE │
|
||||
│ │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ IMAGE │ │
|
||||
│ │ │ │
|
||||
│ └───────────────────┘ │
|
||||
│ │
|
||||
│ ❤️ Like 🔀 Next Random ⏯ Auto │
|
||||
│ │
|
||||
│ Discovered: 47 / 234 ━━━━━━━░░░░░░░░░░░░ │
|
||||
│ │
|
||||
│ Quick jump: [Characters] [Actions] [Outfits] [Scenes] [All] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 3.5 Ambient Screensaver Mode
|
||||
|
||||
Artistic background display with effects.
|
||||
|
||||
**Features:**
|
||||
- Fullscreen with auto-hide UI
|
||||
- Particle effects (floating light orbs, subtle sparkles)
|
||||
- Slow crossfade transitions (10-30 seconds)
|
||||
- Color-matched gradient backgrounds
|
||||
- Clock overlay option
|
||||
- Wake on mouse move
|
||||
|
||||
**Particle System:**
|
||||
```javascript
|
||||
const particleConfig = {
|
||||
count: 50,
|
||||
colors: ['#8b7eff', '#c084fc', '#60a5fa', '#ffffff'],
|
||||
minSize: 2,
|
||||
maxSize: 8,
|
||||
speed: 0.3,
|
||||
opacity: [0.1, 0.6],
|
||||
blur: [0, 4],
|
||||
drift: true // Slight random movement
|
||||
};
|
||||
```
|
||||
|
||||
**Visual Effect:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ · · · │
|
||||
│ · · · │
|
||||
│ · ┌─────────────────┐ · │
|
||||
│ · │ │ · │
|
||||
│ · │ IMAGE │ · · │
|
||||
│ │ │ · │
|
||||
│ · └─────────────────┘ · │
|
||||
│ · · · · · │
|
||||
│ · · · │
|
||||
│ 12:34 │
|
||||
│ · = floating light particles │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 3.6 Comparison Mode
|
||||
|
||||
Side-by-side or overlay comparison.
|
||||
|
||||
**Features:**
|
||||
- Two image selection
|
||||
- Slider overlay (before/after style)
|
||||
- Side-by-side split
|
||||
- Onion skin overlay (opacity blend)
|
||||
- Difference highlight mode
|
||||
- Zoom sync (both images zoom together)
|
||||
|
||||
**Layouts:**
|
||||
```
|
||||
SLIDER MODE: SPLIT MODE:
|
||||
┌─────────────────────────────┐ ┌───────────┬───────────┐
|
||||
│ │ │ │ │ │
|
||||
│ IMAGE A │ IMAGE B │ │ IMAGE A │ IMAGE B │
|
||||
│ │ │ │ │ │
|
||||
│ ◄═══|═══► │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
└─────────────────────────────┘ └───────────┴───────────┘
|
||||
|
||||
ONION SKIN: DIFFERENCE:
|
||||
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
||||
│ │ │ │
|
||||
│ IMAGE A + B BLENDED │ │ PIXEL DIFFERENCE VIEW │
|
||||
│ │ │ (highlights changes) │
|
||||
│ Opacity: ●━━━━━━━○ │ │ │
|
||||
│ │ │ │
|
||||
└─────────────────────────────┘ └─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Enhanced Lightbox Viewer
|
||||
|
||||
Upgrade the existing lightbox with professional features.
|
||||
|
||||
#### 4.1 Zoom & Pan Controls
|
||||
|
||||
**Features:**
|
||||
- Mouse wheel zoom
|
||||
- Double-click to fit/fill toggle
|
||||
- Click-and-drag panning when zoomed
|
||||
- Pinch-to-zoom on touch devices
|
||||
- Zoom level indicator
|
||||
- Reset button
|
||||
|
||||
**Implementation:**
|
||||
```javascript
|
||||
class ZoomPanController {
|
||||
constructor(element) {
|
||||
this.scale = 1;
|
||||
this.translateX = 0;
|
||||
this.translateY = 0;
|
||||
this.minScale = 0.5;
|
||||
this.maxScale = 5;
|
||||
}
|
||||
|
||||
handleWheel(e) {
|
||||
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
||||
const newScale = Math.min(this.maxScale, Math.max(this.minScale, this.scale * delta));
|
||||
// Zoom toward cursor position
|
||||
this.zoomToPoint(newScale, e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
handleDrag(e) {
|
||||
if (this.scale > 1) {
|
||||
this.translateX += e.movementX;
|
||||
this.translateY += e.movementY;
|
||||
this.applyTransform();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 Info Overlay Panel
|
||||
|
||||
Slide-out panel with image details.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ┌───────────┐│
|
||||
│ │ℹ INFO ││
|
||||
│ ├───────────┤│
|
||||
│ ┌─────────────────────┐ │ ││
|
||||
│ │ │ │ Luna ││
|
||||
│ │ IMAGE │ │ Eclipse ││
|
||||
│ │ │ │ ││
|
||||
│ │ │ │ Category: ││
|
||||
│ └─────────────────────┘ │ characters││
|
||||
│ │ ││
|
||||
│ │ Actions: ││
|
||||
│ │ standing ││
|
||||
│ │ ││
|
||||
│ │ [Prompt] ││
|
||||
│ │ [Generate]││
|
||||
│ └───────────┘│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 4.3 Thumbnail Strip
|
||||
|
||||
Quick navigation with visual preview.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ MAIN │ │
|
||||
│ │ IMAGE │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ▢ ▢ ▢ [▣] ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Keyboard Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `←` / `→` | Previous / Next image |
|
||||
| `↑` / `↓` | Zoom in / out |
|
||||
| `Space` | Toggle slideshow play/pause |
|
||||
| `F` | Toggle fullscreen |
|
||||
| `I` | Toggle info panel |
|
||||
| `G` | Open grid view |
|
||||
| `M` | Toggle masonry view |
|
||||
| `S` | Start slideshow |
|
||||
| `C` | Enter comparison mode |
|
||||
| `R` | Random image |
|
||||
| `Esc` | Close viewer / exit fullscreen |
|
||||
| `1-5` | Set slideshow speed preset |
|
||||
| `+` / `-` | Zoom in / out |
|
||||
| `0` | Reset zoom |
|
||||
|
||||
---
|
||||
|
||||
### 6. Mobile & Touch Optimization
|
||||
|
||||
**Gestures:**
|
||||
- **Swipe left/right**: Navigate images
|
||||
- **Swipe up**: Show info panel
|
||||
- **Swipe down**: Close viewer
|
||||
- **Pinch**: Zoom
|
||||
- **Double-tap**: Toggle fit/fill
|
||||
- **Long press**: Show context menu
|
||||
|
||||
**Responsive Breakpoints:**
|
||||
```css
|
||||
/* Mobile - single column, touch optimized */
|
||||
@media (max-width: 576px) { }
|
||||
|
||||
/* Tablet - 2-3 columns */
|
||||
@media (min-width: 577px) and (max-width: 991px) { }
|
||||
|
||||
/* Desktop - 4-6 columns */
|
||||
@media (min-width: 992px) { }
|
||||
|
||||
/* Large displays - up to 8 columns */
|
||||
@media (min-width: 1400px) { }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
static/
|
||||
├── style.css # Add gallery enhancement styles
|
||||
├── js/
|
||||
│ └── gallery/
|
||||
│ ├── gallery-core.js # Main controller
|
||||
│ ├── layout-masonry.js # Masonry layout engine
|
||||
│ ├── layout-justified.js # Justified layout engine
|
||||
│ ├── layout-collage.js # Collage layout engine
|
||||
│ ├── slideshow-cinema.js # Cinema mode
|
||||
│ ├── slideshow-classic.js # Classic slideshow
|
||||
│ ├── slideshow-showcase.js # Showcase frame
|
||||
│ ├── mode-discovery.js # Discovery mode
|
||||
│ ├── mode-ambient.js # Ambient screensaver
|
||||
│ ├── mode-comparison.js # Comparison mode
|
||||
│ ├── viewer-zoom.js # Zoom/pan controller
|
||||
│ ├── effects-particles.js # Particle system
|
||||
│ ├── effects-ambient.js # Ambient color extraction
|
||||
│ └── effects-kenburns.js # Ken Burns animations
|
||||
│
|
||||
templates/
|
||||
├── gallery.html # Enhanced with view mode selector
|
||||
└── partials/
|
||||
└── gallery-viewer.html # New standalone viewer partial
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Foundation
|
||||
- [ ] Create gallery core controller class
|
||||
- [ ] Implement view mode selector UI
|
||||
- [ ] Add masonry layout with lazy loading
|
||||
- [ ] Enhanced lightbox with zoom/pan
|
||||
- [ ] Keyboard shortcut system
|
||||
|
||||
### Phase 2: Layouts
|
||||
- [ ] Justified gallery layout
|
||||
- [ ] Collage layout with templates
|
||||
- [ ] Thumbnail strip navigation
|
||||
- [ ] Mobile swipe gestures
|
||||
|
||||
### Phase 3: Slideshows
|
||||
- [ ] Classic slideshow with transitions
|
||||
- [ ] Cinema mode with ambient glow
|
||||
- [ ] Ken Burns effect engine
|
||||
- [ ] Showcase frame mode
|
||||
|
||||
### Phase 4: Advanced Features
|
||||
- [ ] Discovery mode with favorites
|
||||
- [ ] Comparison mode slider
|
||||
- [ ] Ambient screensaver with particles
|
||||
- [ ] Info panel with metadata
|
||||
|
||||
### Phase 5: Polish
|
||||
- [ ] Performance optimization
|
||||
- [ ] Accessibility improvements
|
||||
- [ ] Settings persistence
|
||||
- [ ] Documentation
|
||||
|
||||
---
|
||||
|
||||
## Technical Considerations
|
||||
|
||||
### Performance
|
||||
- Use `IntersectionObserver` for lazy loading
|
||||
- Preload next 2 images in slideshow
|
||||
- Use `requestAnimationFrame` for animations
|
||||
- Debounce resize handlers
|
||||
- Use CSS transforms instead of layout properties
|
||||
- Consider web workers for color extraction
|
||||
|
||||
### Browser Support
|
||||
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
- CSS Grid and Flexbox required
|
||||
- Fullscreen API
|
||||
- Intersection Observer API
|
||||
- Web Animations API preferred
|
||||
|
||||
### Accessibility
|
||||
- ARIA labels on all controls
|
||||
- Focus management in modals
|
||||
- Reduced motion preference respected
|
||||
- Screen reader announcements for image changes
|
||||
- Keyboard navigation throughout
|
||||
|
||||
---
|
||||
|
||||
## CSS Variables for Gallery Enhancement
|
||||
|
||||
```css
|
||||
/* Gallery Enhancement Variables */
|
||||
:root {
|
||||
/* Layouts */
|
||||
--gallery-gap: 8px;
|
||||
--gallery-card-radius: 12px;
|
||||
--gallery-masonry-width: 280px;
|
||||
--gallery-justified-height: 240px;
|
||||
|
||||
/* Viewer */
|
||||
--viewer-bg: rgba(0, 0, 0, 0.97);
|
||||
--viewer-accent: var(--accent);
|
||||
--viewer-transition: 0.3s ease;
|
||||
|
||||
/* Slideshow */
|
||||
--slideshow-interval: 5000;
|
||||
--kenburns-duration: 8000;
|
||||
--transition-duration: 800;
|
||||
|
||||
/* Effects */
|
||||
--ambient-blur: 100px;
|
||||
--ambient-opacity: 0.3;
|
||||
--particle-count: 50;
|
||||
--vignette-opacity: 0.4;
|
||||
|
||||
/* Frames */
|
||||
--frame-color-light: #f5f5f5;
|
||||
--frame-color-dark: #1a1a2e;
|
||||
--mat-width: 40px;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mockup: Cinema Mode
|
||||
|
||||
```
|
||||
╔═══════════════════════════════════════════════════════════════════════╗
|
||||
║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓║
|
||||
║▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓║
|
||||
║▓▓▓▓▓░░░░░░ ░░░░░▓▓▓▓▓▓║
|
||||
║▓▓░░░░░ ░░░░▓▓▓║
|
||||
║▓░░░░ ╭─────────────────────────────╮ ░░░▓║
|
||||
║░░░░ │ │ ░░║
|
||||
║░░ │ │ ░║
|
||||
║░ │ ★ IMAGE ★ │ ░║
|
||||
║░ │ │ ░║
|
||||
║░ │ │ ░║
|
||||
║░░ │ │ ░░║
|
||||
║░░░ ╰─────────────────────────────╯ ░░░║
|
||||
║▓░░░░ ░░▓▓║
|
||||
║▓▓░░░░░ • • • ○ ○ ░░░░▓▓▓║
|
||||
║▓▓▓▓░░░░░░░ 4 / 24 ░░░░░▓▓▓▓▓▓║
|
||||
║▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓║
|
||||
║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓║
|
||||
╚═══════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
Legend:
|
||||
▓ = Darkest vignette
|
||||
░ = Ambient glow (color-matched to image)
|
||||
★ = Image with Ken Burns animation
|
||||
• = Active indicator, ○ = Inactive
|
||||
|
||||
Controls appear on hover:
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ⏮ │ ⏸ │ ⏭ │ ⏱ 5s ▾ │ 🔀 Shuffle │ ℹ️ Info │ ✕ │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review and approve** this plan
|
||||
2. **Prioritize features** for MVP
|
||||
3. **Begin Phase 1** implementation
|
||||
4. **Iterate** based on feedback
|
||||
|
||||
Would you like me to proceed with implementation, or would you like to adjust any aspects of this plan?
|
||||
Reference in New Issue
Block a user