Model load events are already emitted by the backend but were only
logged in the frontend. Track a loading counter via
model_load_started / model_load_complete and flip the existing
ProgressBar into indeterminate mode while any model is loading, so
users get visual feedback that something is happening.
Infer encoder and decoder block_out_channels independently from the
state dict and rebuild the decoder submodule when its channel widths
differ from the encoder, so the asymmetric full_encoder_small_decoder
checkpoint from black-forest-labs/FLUX.2-small-decoder loads correctly.
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
Add support for Kohya-format Z-Image LoRAs (lora_unet__ prefix) by
adding key detection and conversion to dot-notation module paths.
Fix ComfyUI-format Z-Image LoRAs being misidentified as main models
by ensuring LoRA-specific suffixes (including .alpha) are checked
before Z-Image key matching in _has_z_image_keys().
* feat(model-manager): add comprehensive sorting capabilities for models
dded the ability to sort models in the Model Manager by various attributes
including Name, Base, Type, Format, Size, Date Added, and Date Modified.
Supports both ascending and descending order.
- Backend: Added `order_by` and `direction` query parameters to the
``/api/v1/models`/` listing endpoint. Implemented case-insensitive
sorting in the SQLite model records service.
- Frontend: Introduced `<ModelSortControl />` UI, updated Redux slices
to manage sort state, removed client-side entity adapter sorting to
respect server-side ordering, and added i18n localization keys.
- Tests: Added test coverage for SQL-based sorting on size and name.
* feat(model-manager): add comprehensive sorting capabilities for models
dded the ability to sort models in the Model Manager by various attributes
including Name, Base, Type, Format, Size, Date Added, and Date Modified.
Supports both ascending and descending order.
- Backend: Added `order_by` and `direction` query parameters to the
``/api/v1/models`/` listing endpoint. Implemented case-insensitive
sorting in the SQLite model records service.
- Frontend: Introduced `<ModelSortControl />` UI, updated Redux slices
to manage sort state, removed client-side entity adapter sorting to
respect server-side ordering, and added i18n localization keys.
- Tests: Added test coverage for SQL-based sorting on size and name.
* ruff fix
* typegen fix
* typegen fix - this time without my custom nodes.
* another typegen fix
* refactor(ui): consolidate model filter and sort controls into a unified menu
- Replaced separate `ModelSortControl` and `ModelTypeFilter` components with a single, unified "Filtering" dropdown menu.
- Organised filtering options into categorised submenus in the following order: Direction, Sort By, and Model Type.
- Enhanced submenu labels to display the currently active selection inline for quick reference.
- Improved visual alignment within menus by using hidden checkmarks on unselected items, ensuring consistent indentation across all options.
- Resolved styling and linting issues (unused variables, JSX bind warnings) within the new component.
* Lint fix
* Addresses PR feedback to use translation strings directly within `ORDER_BY_OPTIONS`.
Previously, sort keys and their translated labels were maintained in separate constructs (`ORDER_BY_OPTIONS` array and `ORDER_BY_LABELS` map). This refactor converts `ORDER_BY_OPTIONS` into an array of objects containing both the `key` and its corresponding `i18nKey`, creating a single source of truth.
This change:
- Simplifies the `SortBySubMenu` component by removing the redundant `ORDER_BY_LABELS` lookup map.
- Improves maintainability by ensuring developers only need to update one place when adding or modifying sort options.
- Reduces the risk of mismatched keys and labels.
---------
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
Port SqliteSessionQueue to a SQLAlchemy Core / SQLModel hybrid that keeps the
existing public API and DB schema (migrations and triggers untouched). Hot
paths (enqueue bulk insert, dequeue, bulk cancel/delete, list with cursor
pagination, status aggregations) use Core to avoid ORM hydration overhead;
single-row reads stay ORM-style for clarity.
- Add SqlModelSessionQueue alongside the legacy SqliteSessionQueue
- Add the missing `workflow` column to SessionQueueTable (was added by
migration_2 but never declared on the SQLModel)
- Wire dependencies.py to the new implementation
- Add 36 unit tests covering enqueue/dequeue, status mutations, bulk
cancel/delete, prune-to-limit, retry, pagination and aggregations
- Avoid nested write sessions on the single StaticPool connection by reading
the current item before opening the outer write session
* fix(ui): FLUX.2 Klein VAE/Qwen3 readiness checks and diffusers source auto-detection
Fix several issues with FLUX.2 Klein model handling:
1. Add readiness validation for non-diffusers Klein models so the invoke
button is disabled when required VAE/Qwen3 submodels are missing.
2. Auto-detect installed diffusers flux2 models and pass them as
qwen3_source_model in the graph builder, so GGUF/safetensors models
can extract VAE and encoder from an available diffusers model.
3. Use variant-aware matching so Klein 9B models pick a 9B diffusers
source (not 4B), preventing Qwen3 encoder dimension mismatches.
4. Change placeholder text from "From main model" to "From diffusers
model" or "No diffusers model available" depending on availability.
5. Export readiness check functions and add comprehensive tests for both
the graph builder and readiness logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Chore Fix merge
* fix(ui): unify FLUX.2 Klein Qwen3 variant matching
Extract KLEIN_TO_QWEN3_VARIANT_MAP and isFlux2KleinQwen3Compatible into
features/parameters/util/flux2Klein so UI placeholder, readiness check, and
graph builder share one rule. Accepts klein_9b and klein_9b_base as mutual
Qwen3 sources (both use qwen3_8b) and guards against undefined === undefined
false positives.
Use zModelIdentifierField.parse for qwen3_source_model construction in
buildFLUXGraph, matching the pattern used for Z-Image.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
Add a "Lock Transparency" toggle to raster layers that preserves the
alpha channel when painting. When enabled, brush strokes use
'source-atop' compositing to only paint on existing non-transparent
pixels, similar to Photoshop's "Lock Transparent Pixels" feature.
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
- setVisibility now uses layer.remove()/stage.add() to detach/reattach
hidden canvas elements from the DOM, freeing browser compositing resources
- Wrap syncIsOnscreen and syncIntersectsBbox with rafThrottle to reduce
redundant calculations during pan/zoom
- Increase canvasElementCacheSize 32→128 and imageDataCacheSize 32→64
- Add 3-Layer-Flattening design document for future architectural optimization
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* feat: initial external model support
* feat: support reference images for external models
* fix: sorting lint error
* chore: hide Reidentify button for external models
* review: enable auto-install/remove fro external models
* feat: show external mode name during install
* review: model descriptions
* review: implemented review comments
* review: added optional seed control for external models
* chore: fix linter warning
* review: save api keys to a seperate file
* docs: updated external model docs
* chore: fix linter errors
* fix: sync configured external starter models on startup
* feat(ui): add provider-specific external generation nodes
* feat: expose external panel schemas in model configs
* feat(ui): drive external panels from panel schema
* docs: sync app config docstring order
* feat: add gemini 3.1 flash image preview starter model
* feat: update gemini image model limits
* fix: resolve TypeScript errors and move external provider config to api_keys.yaml
Add 'external', 'external_image_generator', and 'external_api' to Zod
enum schemas (zBaseModelType, zModelType, zModelFormat) to match the
generated OpenAPI types. Remove redundant union workarounds from
component prop types and Record definitions.
Fix type errors in ModelEdit (react-hook-form Control invariance),
parsing.tsx (model identifier narrowing), buildExternalGraph (edge
typing), and ModelSettings import/export buttons.
Move external_gemini_base_url and external_openai_base_url into
api_keys.yaml alongside the API keys so all external provider config
lives in one dedicated file, separate from invokeai.yaml.
* feat: add resolution presets and imageConfig support for Gemini 3 models
Add combined resolution preset selector for external models that maps
aspect ratio + image size to fixed dimensions. Gemini 3 Pro and 3.1 Flash
now send imageConfig (aspectRatio + imageSize) via generationConfig instead
of text-based aspect ratio hints used by Gemini 2.5 Flash.
Backend: ExternalResolutionPreset model, resolution_presets capability field,
image_size on ExternalGenerationRequest, and Gemini provider imageConfig logic.
Frontend: ExternalSettingsAccordion with combo resolution select, dimension
slider disabling for fixed-size models, and panel schema constraint wiring
for Steps/Guidance/Seed controls.
* Remove unused external model fields and add provider-specific parameters
- Remove negative_prompt, steps, guidance, reference_image_weights,
reference_image_modes from external model nodes (unused by any provider)
- Remove supports_negative_prompt, supports_steps, supports_guidance
from ExternalModelCapabilities
- Add provider_options dict to ExternalGenerationRequest for
provider-specific parameters
- Add OpenAI-specific fields: quality, background, input_fidelity
- Add Gemini-specific fields: temperature, thinking_level
- Add new OpenAI starter models: GPT Image 1.5, GPT Image 1 Mini,
DALL-E 3, DALL-E 2
- Fix OpenAI provider to use output_format (GPT Image) vs
response_format (DALL-E) and send model ID in requests
- Add fixed aspect ratio sizes for OpenAI models (bucketing)
- Add ExternalProviderRateLimitError with retry logic for 429 responses
- Add provider-specific UI components in ExternalSettingsAccordion
- Simplify ParamSteps/ParamGuidance by removing dead external overrides
- Update all backend and frontend tests
* Chore Ruff check & format
* Chore typegen
* feat: full canvas workflow integration for external models
- Add missing aspect ratios (4:5, 5:4, 8:1, 4:1, 1:4, 1:8) to type
system for external model support
- Sync canvas bbox when external model resolution preset is selected
- Use params preset dimensions in buildExternalGraph to prevent
"unsupported aspect ratio" errors
- Lock all bbox controls (resize handles, aspect ratio select,
width/height sliders, swap/optimal buttons) for external models
with fixed dimension presets
- Disable denoise strength slider for external models (not applicable)
- Sync bbox aspect ratio changes back to paramsSlice for external models
- Initialize bbox dimensions when switching to an external model
* Chore typegen Linux seperator
* feat: full canvas workflow integration for external models
- Update buildExternalGraph test to include dimensions in mock params
* Merge remote-tracking branch 'upstream/main' into external-models
* Chore pnpm fix
* add missing parameter
* docs: add External Models guide with Gemini and OpenAI provider pages
* fix(external-models): address PR review feedback
- Gemini recall: write temperature, thinking_level, image_size to image metadata;
wire external graph as metadata receiver; add recall handlers.
- Canvas: gate regional guidance, inpaint mask, and control layer for external models.
- Canvas: throw a clear error on outpainting for external models (was falling back to
inpaint and hitting an API-side mask/image size mismatch).
- Workflow editor: add ui_model_provider_id filter so OpenAI and Gemini nodes only
list their own provider's models.
- Workflow editor: silently drop seed when the selected model does not support it
instead of raising a capability error.
- Remove the legacy external_image_generation invocation and the graph-builder
fallback; providers must register a dedicated node.
- Regenerate schema.ts.
- remove Gemini debug dumps to outputs/external_debug
* fix(external-models): resolve TSC errors in metadata parsing and external graph
- Export imageSizeChanged from paramsSlice (required by the new ImageSize
recall handler).
- Emit the external graph's metadata model entry via zModelIdentifierField
since ExternalApiModelConfig is not part of the AnyModelConfig union.
* chore: prettier format ModelIdentifierFieldInputComponent
* fix: remove unsupported thinkingConfig from Gemini image models and restrict GPT Image models to txt2img
* chore typegen
* chore(docs): regenerate settings.json for external provider fields
* fix(external): fix mask handling and mode support for external providers
- Remove img2img and inpaint modes from Gemini models (Gemini has no
bitmap mask or dedicated edit API; image editing works via reference
images in the UI)
- Fix DALL-E 2 inpainting: convert grayscale mask to RGBA with alpha
channel transparency (OpenAI expects transparent=edit area) and
convert init image to RGBA when mask is present
* fix(external): update mode support and UI for external providers
- Remove DALL-E 2 from starter models (deprecated, shutdown May 12 2026)
- Enable img2img for GPT Image 1/1.5/1-mini (supports edits endpoint)
- Set Gemini models to txt2img only (no mask/edit API; editing via
ref images)
- Hide mode/init_image/mask_image fields on Gemini node (not usable)
- Hide mask_image field on OpenAI node (no model supports inpaint)
* Chore typegen
* fix(external): improve OpenAI node UX and disable cache by default
- Hide OpenAI node's mode and init_image fields: OpenAI's API has no
img2img/inpaint distinction (the edits endpoint is invoked
automatically when reference images are provided). init_image is
functionally identical to a reference image and was misleading users.
- Default use_cache to False for external image generation nodes:
external API calls are non-deterministic and incur usage costs.
Cache hits returned stale image references that did not produce new
gallery entries on repeat invokes.
* fix(external): duplicate cached images on cache hit instead of skipping
External image generation nodes use the standard invocation cache, but
returning the cached output (with stale image_name references) on cache
hits resulted in no new gallery entries — the Invoke button would spin
indefinitely on repeat invokes with identical parameters.
Override invoke_internal so that on cache hit, the cached images are
loaded and re-saved as new gallery entries. The expensive API call is
still skipped (cost saving), but the user sees a new image as expected.
* Chore typegen + ruff
* CHore ruff format
* fix(external): restore OpenAI advanced settings on Remix recall
Remix recall iterates through ImageMetadataHandlers but only Gemini's
temperature handler was wired up — OpenAI's quality, background, and
input_fidelity were stored in image metadata but never parsed back into
the params slice. Add the three missing handlers so Remix restores
these settings as expected.
---------
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
Co-authored-by: Alexander Eichhorn <alex@code-with.us>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
Introduces SQLModel (SQLAlchemy + Pydantic) as an ORM layer to enable
future database backend switching (PostgreSQL, MySQL). All services
except session_queue have been migrated to SQLModel-based implementations
while keeping the existing migration system and raw SQLite connection
intact for backwards compatibility.
Key changes:
- Add sqlmodel dependency
- Define SQLModel table models for all 14 database tables
- Extend SqliteDatabase with SQLAlchemy Engine and Session management
- Create SQLModel implementations for 10 services (boards, images,
workflows, models, users, style presets, app settings, etc.)
- Session queue remains on raw SQLite (Phase 3)
- Add 95 unit tests and 12 performance benchmarks
- Optimize with StaticPool, expire_on_commit=False, and read-only sessions
* feat(ui): add canvas project save/load (.invk format)
Add ZIP-based .invk file format to save and restore the entire canvas
state including all layers, masks, reference images, generation
parameters, LoRAs, and embedded image files. Images are deduplicated
on load - only missing images are re-uploaded from the project file.
- Always clear LoRAs on project load, even when project has none
- Fix jszip dependency ordering in package.json
- Add useAssertSingleton to SaveCanvasProjectDialog for consistency
- Add concurrency limit (max 5) for image fetch/upload requests
- Remove redundant deep-clone in remapCroppableImage (mutate in-place)
- Default project name to "Canvas Project" instead of empty string
* Chore pnpm fix
* feat(ui): group nodes by category in add-node dialog
Add collapsible category grouping to the node picker command palette.
Categories are parsed from the backend schema and displayed as
expandable sections with caret icons. All categories auto-expand
when searching.
* feat(ui): add toggle for category grouping in add-node dialog and prioritize exact matches
Add a persistent "Group Nodes by Category" setting to workflow editor settings,
allowing users to switch between grouped and flat node list views. Also sort
exact title matches to the top when searching.
* fix: update test schema categories to match expected templates
* feat: add expand/collapse all buttons to node picker and fix node categories
Add "Expand All" and "Collapse All" link-buttons above the grouped
category list in the add-node dialog so users can quickly open or
close all categories at once. Buttons are hidden during search since
categories auto-expand while searching.
Fix two miscategorized nodes: Z-Image ControlNet was in "Control"
instead of "Controlnet", and Upscale (RealESRGAN) was in "Esrgan"
instead of "Upscale".
* refactor(nodes): clean up node category taxonomy
Reorganize all built-in invocation categories into a consistent set of
18 groups (model, prompt, conditioning, controlnet_preprocessors,
latents, image, mask, inpaint, tiles, upscale, segmentation, math,
strings, primitives, batch, metadata, multimodal, canvas).
- Move denoise/i2l/l2i nodes consistently into "latents"
- Move all mask creation/manipulation nodes into "mask"
- Split ControlNet preprocessors out of "controlnet" into their own group
- Fold "unet", "vllm", "string", "ip_adapter", "t2i_adapter" into larger
groups
- Move metadata_linked denoise wrappers from "latents" to "metadata"
- Add missing category to ideal_size
- Introduce dedicated "canvas" group for canvas/output/panel nodes
Also adds the now-required `category` field to invocation template
fixtures in validateConnection.test.ts.
* Chore Ruff Format
---------
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* Feat(Canvas): Add Lasso tool with Freehand and Polygon modes
* Refine Lasso modes behavior and optimisation.
* Fix: Pettier
* added docs/features/Lasso_tool.md
* Fix: Removed restrictions mentioned in PR's conversation:
1. Disabled when there is no visible raster content
2. Lasso is blocked when all inpaint masks are globally hidden.
---------
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* Add more settings to invokeai.yaml for improved queue management.
* Adjusted description
* More logic tweaking
* chore(api): update generated schema types
* chore(api): update generated schema types
* Add: UI element for max_queue_history to 'Settings' modal.
Now it is possible to set Max queue history in both places: .yaml and UI.
* chore(api): regenerate schema types
* chore(api): normalize generated schema path defaults
---------
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* feat: Per-user workflow libraries in multiuser mode (#114)
* Add per-user workflow isolation: migration 28, service updates, router ownership checks, is_public endpoint, schema regeneration, frontend UI
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* feat: add shared workflow checkbox to Details panel, auto-tag, gate edit/delete, fix tests
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* Restrict model sync to admin users only (#118)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* feat: distinct splash screens for admin/non-admin users in multiuser mode (#116)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* Disable Save when editing another user's shared workflow in multiuser mode (#120)
* Disable Save when editing another user's shared workflow in multiuser mode
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* chore(app): ruff
* Add board visibility (private/shared/public) feature with tests and UI
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* Enforce read-only access for non-owners of shared/public boards in UI
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* Fix remaining board access enforcement: invoke icon, drag-out, change-board filter, archive
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* fix: allow drag from shared boards to non-board targets (viewer, ref image, etc.)
Previously, images in shared boards owned by another user could not be
dragged at all — the draggable setup was completely skipped in
GalleryImage.tsx when canWriteImages was false. This blocked ALL drop
targets including the viewer, reference image pane, and canvas.
Now images are always draggable. The board-move restriction is enforced
in the dnd target isValid functions instead:
- addImageToBoardDndTarget: rejects moves from shared boards the user
doesn't own (unless admin or board is public)
- removeImageFromBoardDndTarget: same check
Other drop targets (viewer, reference images, canvas, comparison, etc.)
remain fully functional for shared board images.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(security): add auth requirement to all sensitive routes in multimodal mode
* chore(backend): ruff
* fix (backend): improve user isolation for session queue and recall parameters
- Sanitize session queue information of all cross-user fields except for the timestamps and status.
- Recall parameters are now user-scoped.
- Queue status endpoints now report user-scoped activity rather than global activity
- Tests added:
TestSessionQueueSanitization (4 tests):
1. test_owner_sees_all_fields - Owner sees complete queue item data
2. test_admin_sees_all_fields - Admin sees complete queue item data
3. test_non_owner_sees_only_status_timestamps_errors -
Non-owner sees only item_id, queue_id, status, and timestamps; everything else is redacted
4. test_sanitization_does_not_mutate_original - Sanitization doesn't modify the original object
TestRecallParametersIsolation (2 tests):
5. test_user1_write_does_not_leak_to_user2 - User1's recall params are not visible in user2's client state
6. test_two_users_independent_state - Both users can write recall params independently without overwriting each other
fix(backend): queue status endpoints report user-scoped stats rather than global stats
* fix(workflow): do not filter default workflows in multiuser mode
Problem: When categories=['user', 'default'] (or no category filter)
and user_id was set for multiuser scoping, the SQL query became
WHERE category IN ('user', 'default') AND user_id = ?,
which excluded default workflows (owned by "system").
Fix: Changed user_id = ? to (user_id = ? OR category = 'default') in
all 6 occurrences across workflow_records_sqlite.py — in get_many,
counts_by_category, counts_by_tag, and get_all_tags. Default
workflows are now always visible regardless of user scoping.
Tests added (2):
- test_default_workflows_visible_when_listing_user_and_default — categories=['user','default'] includes both
- test_default_workflows_visible_when_no_category_filter — no filter still shows defaults
* fix(multiuser): scope queue/recall/intermediates endpoints to current user
Several read-only and event-emitting endpoints were leaking aggregate
cross-user activity in multiuser mode:
- recall_parameters_updated event was broadcast to every queue
subscriber. Added user_id to the event and routed it to the owner +
admin rooms only.
- get_queue_status, get_batch_status, counts_by_destination and
get_intermediates_count now scope counts to the calling user
(admins still see global state). Removed the now-redundant
user_pending/user_in_progress fields and simplified QueueCountBadge.
- get_queue_status hides current item_id/session_id/batch_id when the
current item belongs to another user.
Also fixes test_session_queue_sanitization assertions that lagged
behind the recently expanded redaction set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(backend): ruff
* fix(multiuser): reject anonymous websockets and scope queue item events
Close three cross-user leaks in the websocket layer:
- _handle_connect() now rejects connections without a valid JWT in
multiuser mode (previously fell through to user_id="system"), so
anonymous clients can no longer subscribe to queue rooms and observe
other users' activity. In single-user mode it still accepts as system
admin.
- _handle_sub_queue() no longer silently falls back to the system user
for an unknown sid in multiuser mode; it refuses the subscription.
- QueueItemStatusChangedEvent and BatchEnqueuedEvent are now routed to
user:{user_id} + admin rooms instead of the full queue room. Both
events carry unsanitized user_id, batch_id, origin, destination,
session_id, and error metadata and must not be broadcast.
- BatchEnqueuedEvent gains a user_id field; emit_batch_enqueued and
enqueue_batch thread it through.
New TestWebSocketAuth suite covers connect accept/reject for both
modes, sub_queue refusal, and private routing of the queue item and
batch events (plus a QueueClearedEvent sanity check).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): verify user record on websocket connect
A deleted or deactivated user with an unexpired JWT could still open a
websocket and subscribe to queue rooms. Now _handle_connect() checks the
backing user record (exists + is_active) in multiuser mode, mirroring
the REST auth path in auth_dependencies.py. Fails closed if the user
service is unavailable.
Tests: added deleted-user and inactive-user rejection tests; updated
valid-token test to create the user in the database first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): close bulk download cross-user exfiltration path
Backend:
- POST /download now validates image read access (per-image) and board
read access (per-board) before queuing the download.
- GET /download/{name} is intentionally unauthenticated because the
browser triggers it via <a download> which cannot carry Authorization
headers. Access control relies on POST-time checks, UUID filename
unguessability, private socket event routing, and single-fetch deletion.
- Added _assert_board_read_access() helper to images router.
- Threaded user_id through bulk download handler, base class, event
emission, and BulkDownloadEventBase so events carry the initiator.
- Bulk download service now tracks download ownership via _download_owners
dict (cleaned up on delete).
- Socket bulk_download room subscription restricted to authenticated
sockets in multiuser mode.
- Added error-catching in FastAPIEventService._dispatch_from_queue to
prevent silent event dispatch failures.
Frontend:
- Fixed pre-existing race condition where the "Preparing Download" toast
from the POST response overwrote the "Ready to Download" toast from the
socket event (background task completes in ~17ms, so the socket event
can arrive before Redux processes the HTTP response). Toast IDs are now
distinct: "preparing:{name}" vs "{name}".
- bulk_download_complete/error handlers now dismiss the preparing toast.
Tests (8 new):
- Bulk download by image names rejected for non-owner (403)
- Bulk download by image names allowed for owner (202)
- Bulk download from private board rejected (403)
- Bulk download from shared board allowed (202)
- Admin can bulk download any images (202)
- Bulk download events carry user_id
- Bulk download event emitted to download room
- GET /download unauthenticated returns 404 for unknown files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): enforce board visibility on image listing endpoints
GET /api/v1/images?board_id=... and GET /api/v1/images/names?board_id=...
passed board_id directly to the SQL layer without checking board
visibility. The SQL only applied user_id filtering for board_id="none"
(uncategorized images), so any authenticated user who knew a private
board ID could enumerate its images.
Both endpoints now call _assert_board_read_access() before querying,
returning 403 unless the caller is the board owner, an admin, or the
board is Shared/Public.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(backend): ruff
* fix(multiuser): require image ownership when adding images to boards
add_image_to_board and add_images_to_board only checked write access to
the destination board, never verifying that the caller owned the source
image. An attacker could add a victim's image to their own board, then
exploit the board-ownership fallback in _assert_image_owner to gain
delete/patch/star/unstar rights on the image.
Both endpoints now call _assert_image_direct_owner which requires direct
image ownership (image_records.user_id) or admin — board ownership is
intentionally not sufficient, preventing the escalation chain.
Also fixed a pre-existing bug where HTTPException from the inner loop in
add_images_to_board was caught by the outer except-Exception and returned
as 500 instead of propagating the correct status code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(backend): ruff
* fix(multiuser): validate image access in recall parameter resolution
The recall endpoint loaded image files and ran ControlNet preprocessors
on any image_name supplied in control_layers or ip_adapters without
checking that the caller could read the image. An attacker who knew
another user's image UUID could extract dimensions and, for supported
preprocessors, mint a derived processed image they could then fetch.
Added _assert_recall_image_access() which validates read access for every
image referenced in the request before any resolution or processing
occurs. Access is granted to the image owner, admins, or when the image
sits on a Shared/Public board.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): require admin auth on model install job endpoints
list_model_installs, get_model_install_job, pause, resume,
restart_failed, and restart_file were unauthenticated — any caller who
could reach the API could view sensitive install job fields (source,
local_path, error_traceback) and interfere with installation state.
All six endpoints now require AdminUserOrDefault, consistent with the
neighboring cancel and prune routes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): close bulk download exfiltration and additional review findings
Bulk download capability token exfiltration:
- Socket events now route to user:{user_id} + admin rooms instead of the
shared 'default' room (the earlier toast race that blocked this approach
was fixed in a prior commit).
- GET /download/{name} re-requires CurrentUserOrDefault and enforces
ownership via get_owner().
- Frontend download handler replaced <a download> (which cannot carry auth
headers) with fetch() + Authorization header + programmatic blob download.
Additional fixes from reviewer tests:
- Public boards now grant write access in _assert_board_write_access and
mutation rights in _assert_image_owner (BoardVisibility.Public).
- Uncategorized image listing (GET /boards/none/image_names) now filters
to the caller's images only, preventing cross-user enumeration.
- board_images router uses board_image_records.get_board_for_image()
instead of images.get_dto() to avoid dependency on image_files service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): add user_id scoping to workflow SQL mutations
Defense-in-depth: the route layer already checks ownership before
calling update/delete/update_is_public/update_opened_at, but the SQL
statements did not include AND user_id = ?, so a bypass of the route
check would allow cross-user mutations.
All four methods now accept an optional user_id parameter. When
provided, the SQL WHERE clause is scoped to that user. The route layer
passes current_user.user_id for non-admin callers and None for admins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(multiuser): allow non-owner uploads to public boards
upload_image() blocked non-owner uploads even to public boards. The
board write check now allows uploads when board_visibility is Public,
consistent with the public-board semantics in _assert_board_write_access
and _assert_image_owner.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
* feat: add configurable shift parameter for Z-Image sigma schedule
Add a shift (mu) override to the Z-Image denoise invocation and expose
it in the UI. When left blank, shift is auto-calculated from image
dimensions (existing behavior). Users can override to fine-tune the
timestep schedule, with an inline X button to reset back to auto.
* refactor: switch Z-Image sigma schedule from exponential to linear time shift
Use shift directly as a linear multiplier instead of exp(mu), giving
more predictable and uniform control over the timestep schedule.
Auto-calculated values are converted via exp(mu) to preserve identical
default behavior.
* feat: recall Z-Image shift parameter from metadata
Write z_image_shift into graph metadata and add a ZImageShift recall
handler so the shift override can be restored from previously generated
images. Auto-mode (null) is omitted from metadata to avoid persisting a
stale value.
---------
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
openapi-typescript computes enum types from `const` usage in
discriminated unions rather than from the enum definition itself,
dropping values that only appear in some union members (e.g. "anima"
from BaseModelType). Add a post-processing step that patches generated
string enum types to match the actual OpenAPI schema definitions.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
* fix(ui): replace all hardcoded frontend strings with i18n translation keys
Remove fallback/defaultValue strings from t() calls, replace hardcoded
English text in labels, tooltips, aria-labels, placeholders and JSX content
with proper t() calls, and add ~50 missing keys to en.json. Fix incorrect
i18n key paths in CanvasObjectImage.ts and a Zoom button aria-label bug
in CanvasToolbarScale.tsx.
* chore pnpm run fix
---------
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
* feat(frontend): suppress tooltips on touch devices
* fix(frontend): change selector to role="tooltip" because .chakra-tooltip does not match
* chore(frontend): lint:prettier
* feat: add Anima model support
* schema
* image to image
* regional guidance
* loras
* last fixes
* tests
* fix attributions
* fix attributions
* refactor to use diffusers reference
* fix an additional lora type
* some adjustments to follow flux 2 paper implementation
* use t5 from model manager instead of downloading
* make lora identification more reliable
* fix: resolve lint errors in anima module
Remove unused variable, fix import ordering, inline dict() call,
and address minor lint issues across anima-related files.
* Chore Ruff format again
* fix regional guidance error
* fix(anima): validate unexpected keys after strict=False checkpoint loading
Capture the load_state_dict result and raise RuntimeError on unexpected
keys (indicating a corrupted or incompatible checkpoint), while logging
a warning for missing keys (expected for inv_freq buffers regenerated
at runtime).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): make model loader submodel fields required instead of Optional
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): add Classification.Prototype to LoRA loaders, fix exception types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): fix replace-all in key conversion, warn on DoRA+LoKR, unify grouping functions
- Use key.replace(old, new, 1) in _convert_kohya_unet_key and _convert_kohya_te_key to avoid replacing multiple occurrences
- Upgrade DoRA+LoKR dora_scale strip from logger.debug to logger.warning since it represents data loss
- Replace _group_kohya_keys and _group_by_layer with a single _group_keys_by_layer function parameterized by extra_suffixes, with _KOHYA_KNOWN_SUFFIXES and _PEFT_EXTRA_SUFFIXES constants
- Add test_empty_state_dict_returns_empty_model to verify empty input produces a model with no layers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): add safety cap for Qwen3 sequence length to prevent OOM
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): add denoising range validation, fix closure capture, add edge case tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): add T5 to metadata, fix dead code, decouple scheduler type guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(anima): update VAE field description for required field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: regenerate frontend types after upstream merge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: ruff format anima_denoise.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(anima): add T5 encoder metadata recall handler
The T5 encoder was added to generation metadata but had no recall
handler, so it wasn't restored when recalling from metadata.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(frontend): add regression test for buildAnimaGraph
Add tests for CFG gating (negative conditioning omitted when cfgScale <= 1)
and basic graph structure (model loader, text encoder, denoise nodes).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* only show 0.6b for anima
* dont show 0.6b for other models
* schema
* Anima preview 3
* fix ci
---------
Co-authored-by: Your Name <you@example.com>
Co-authored-by: kappacommit <samwolfe40@gmail.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
* feat: Add canvas-workflow integration feature
This commit implements a new feature that allows users to run workflows
directly from the unified canvas. Users can now:
- Access a "Run Workflow" option from the canvas layer context menu
- Select a workflow with image parameters from a modal dialog
- Customize workflow parameters (non-image fields)
- Execute the workflow with the current canvas layer as input
- Have the result automatically added back to the canvas
Key changes:
- Added canvasWorkflowIntegrationSlice for state management
- Created CanvasWorkflowIntegrationModal and related UI components
- Added context menu item to raster layers
- Integrated workflow execution with canvas image extraction
- Added modal to global modal isolator
This integration enhances the canvas by allowing users to leverage
custom workflows for advanced image processing directly within the
canvas workspace.
Implements feature request for deeper workflow-canvas integration.
* refactor(ui): simplify canvas workflow integration field rendering
- Extract WorkflowFieldRenderer component for individual field rendering
- Add WorkflowFormPreview component to handle workflow parameter display
- Remove workflow compatibility filtering - allow all workflows
- Simplify workflow selector to use flattened workflow list
- Add comprehensive field type support (String, Integer, Float, Boolean, Enum, Scheduler, Board, Model, Image, Color)
- Implement image field selection UI with radio
* feat(ui): add canvas-workflow-integration logging namespace
* feat(ui): add workflow filtering for canvas-workflow integration
- Add useFilteredWorkflows hook to filter workflows with ImageField inputs
- Add workflowHasImageField utility to check for ImageField in Form Builder
- Only show workflows that have Form Builder with at least one ImageField
- Add loading state while filtering workflows
- Improve error messages to clarify Form Builder requirement
- Update modal description to mention Form Builder and parameter adjustment
- Add fallback error message for workflows without Form Builder
* feat(ui): add persistence and migration for canvas workflow integration state
- Add _version field (v1) to canvasWorkflowIntegrationState for future migrations
- Add persistConfig with migration function to handle version upgrades
- Add persistDenylist to exclude transient state (isOpen, isProcessing, sourceEntityIdentifier)
- Use es-toolkit isPlainObject and tsafe assert for type-safe migration
- Persist selectedWorkflowId and fieldValues across sessions
* pnpm fix imports
* fix(ui): handle workflow errors in canvas staging area and improve form UX
- Clear processing state when workflow execution fails at enqueue time
or during invocation, so the modal doesn't get stuck
- Optimistically update listAllQueueItems cache on queue item status
changes so the staging area immediately exits on failure
- Clear processing state on invocation_error for canvas workflow origin
- Auto-select the only unfilled ImageField in workflow form
- Fix image field overflow and thumbnail sizing in workflow form
* feat(ui): add canvas_output node and entry-based staging area
Add a dedicated `canvas_output` backend invocation node that explicitly
marks which images go to the canvas staging area, replacing the fragile
board-based heuristic. Each `canvas_output` node produces a separate
navigable entry in the staging area, allowing workflows with multiple
outputs to be individually previewed and accepted.
Key changes:
- New `CanvasOutputInvocation` backend node (canvas.py)
- Entry-based staging area model where each output image is a separate
navigable entry with flat next/prev cycling across all items
- Frontend execute hook uses `canvas_output` type detection instead of
board field heuristic, with proper board field value translation
- Workflow filtering requires both Form Builder and canvas_output node
- Updated QueueItemPreviewMini and StagingAreaItemsList for entries
- Tests for entry-based navigation, multi-output, and race conditions
* Chore pnp run fix
* Chore eslint fix
* Remove unused useOutputImageDTO export to fix knip lint
* Update invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useCanvasWorkflowIntegrationExecute.tsx
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* move UI text to en.json
* fix conflicts merge with main
* generate schema
* Chore typegen
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
Klein 9B Base (undistilled) and Klein 9B (distilled) have identical
architectures and cannot be distinguished from the state dict alone.
Use a filename heuristic ("base" in the name) to detect the Base
variant for checkpoint, GGUF, and diffusers format models.
Also fixes the incorrect guidance_embeds-based detection for diffusers
format, since both variants have guidance_embeds=False.
* feat: add support for OneTrainer BFL Flux LoRA format
Newer versions of OneTrainer export Flux LoRAs using BFL internal key
names (double_blocks, single_blocks, img_attn, etc.) with a
'transformer.' prefix and split QKV projections (qkv.0/1/2, linear1.0/1/2/3).
This format was not recognized by any existing detector.
Add detection and conversion for this format, merging split QKV and
linear1 layers into MergedLayerPatch instances for the fused BFL model.
* chore ruff
OneTrainer exports Z-Image LoRAs with 'transformer.layers.' key prefix
instead of 'diffusion_model.layers.'. Add this prefix (and the
PEFT-wrapped 'base_model.model.transformer.layers.' variant) to the
Z-Image LoRA probe so these models are correctly identified and loaded.
* Added If node
* Added stricter type checking on inputs
* feat(nodes): make if-node type checks cardinality-aware without loosening global AnyField
* chore: typegen
* Initial plan
* Warn user when credentials have expired in multiuser mode
Agent-Logs-Url: https://github.com/lstein/InvokeAI/sessions/f0947cda-b15c-475d-b7f4-2d553bdf2cd6
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* Address code review: avoid multiple localStorage reads in base query
Agent-Logs-Url: https://github.com/lstein/InvokeAI/sessions/f0947cda-b15c-475d-b7f4-2d553bdf2cd6
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
* bugfix(multiuser): ask user to log back in when authentication token expires
* feat: sliding window session expiry with token refresh
Backend:
- SlidingWindowTokenMiddleware refreshes JWT on each mutating request
(POST/PUT/PATCH/DELETE), returning a new token in X-Refreshed-Token
response header. GET requests don't refresh (they're often background
fetches that shouldn't reset the inactivity timer).
- CORS expose_headers updated to allow X-Refreshed-Token.
Frontend:
- dynamicBaseQuery picks up X-Refreshed-Token from responses and
updates localStorage so subsequent requests use the fresh expiry.
- 401 handler only triggers sessionExpiredLogout when a token was
actually sent (not for unauthenticated background requests).
- ProtectedRoute polls localStorage every 5s and listens for storage
events to detect token removal (e.g. manual deletion, other tabs).
Result: session expires after TOKEN_EXPIRATION_NORMAL (1 day) of
inactivity, not a fixed time after login. Any user-initiated action
resets the clock.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(backend): ruff
* fix: address review feedback on auth token handling
Bug fixes:
- ProtectedRoute: only treat 401 errors as session expiry, not
transient 500/network errors that should not force logout
- Token refresh: use explicit remember_me claim in JWT instead of
inferring from remaining lifetime, preventing silent downgrade of
7-day tokens to 1-day when <24h remains
- TokenData: add remember_me field, set during login
Tests (6 new):
- Mutating requests (POST/PUT/DELETE) return X-Refreshed-Token
- GET requests do not return X-Refreshed-Token
- Unauthenticated requests do not return X-Refreshed-Token
- Remember-me token refreshes to 7-day duration even near expiry
- Normal token refreshes to 1-day duration
- remember_me claim preserved through refresh cycle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(backend): ruff
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
* feat: add bulk reidentify action for models (#8951)
Add a "Reidentify Models" bulk action to the model manager, allowing
users to re-probe multiple models at once instead of one by one.
- Backend: POST /api/v2/models/i/bulk_reidentify endpoint with partial
failure handling (returns succeeded/failed lists)
- Frontend: bulk reidentify mutation, confirmation modal with warning
about custom settings reset, toast notifications for all outcomes
- i18n: new translation keys for bulk reidentify UI strings
* fix typgen
* Fix bulk reidentify failing for models without trigger_phrases
The bulk reidentify endpoint was directly assigning trigger_phrases
without checking if the config type supports it, causing an
AttributeError for ControlNet models. Added the same hasattr guard
used by the individual reidentify endpoint. Also restored the
missing path preservation that the individual endpoint has.
@@ -58,15 +58,39 @@ Invoke offers a fully featured workflow management solution, enabling users to c
Invoke features an organized gallery system for easily storing, accessing, and remixing your content in the Invoke workspace. Images can be dragged/dropped onto any Image-base UI element in the application, and rich metadata within the Image allows for easy recall of key prompts or settings used in your workflow.
### Model Support
- SD 1.5
- SD 2.0
- SDXL
- SD 3.5 Medium
- SD 3.5 Large
- CogView 4
- Flux.1 Dev
- Flux.1 Schnell
- Flux.1 Kontext
- Flux.1 Krea
- Flux Redux
- Flux Fill
- Flux.2 Klein 4B
- Flux.2 Klein 9B
- Z-Image Turbo
- Z-Image Base
- Anima
- Qwen Image
- Qwen Image Edit
- Nano Banana (API Only)
- GPT Image (API Only)
- Wan (API Only)
### Other features
- Support for both ckpt and diffusers models
- SD1.5, SD2.0, SDXL, and FLUX support
- Support for ckpt, diffusers, and some gguf models
Any environment variables supported by InvokeAI can be set here. See the [Configuration docs](https://invoke-ai.github.io/InvokeAI/features/CONFIGURATION/) for further detail.
Any environment variables supported by InvokeAI can be set here. See the [Configuration docs](https://invoke.ai/configuration/invokeai-yaml/) for further detail.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.