Commit Graph

696 Commits

Author SHA1 Message Date
Josh Corbett af37c2330e Docs: Housekeeping + Remove docs-old (#9166)
* chore(docs): remove old docs directory

* chore(docs): transition tests, generators to new docs

Focuses on continuing the move away from the old docs configuration by
removing legacy packages, tests and generate scripts, and replacing them
with new scripts for the new docs.ma

- Adds docs commands to Makefile
- Removes old docs tests
- Removes old docs generate scripts
- Removes docstring and mkfile usage
- Regenerated UV lockfile

* feat(docs): improve config organization

Pulls out the long parts of the astro config into dedicated files
located in a new `config/` directory.

* feat(docs): add pages for contributing to docs

* feat(docs): add image section

describe how to add images to your docs pages in an organized fashion.

* feat(docs): add guide for docs translations

* feat(docs): add cover image

* fix(docs): add social image metadata

* fix(docs): pages in wrong locations

also updated some orderings and frontmatter

* chore(docs): upgrade deps

* feat(docs): add missing docs pages

* fix(docs): docs fixes

- fix redirects
- improve canvas projects docs
- improve models docs

---------

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-05-26 20:45:51 +00:00
Alexander Eichhorn 811103e1c3 fix(fp8): route ModelMixin through hook-based path to survive partial load (#9231)
Diffusers' enable_layerwise_casting() installs a LayerwiseCastingHook that
(a) only casts dtype in pre_forward, not device, and (b) replaces Linear.forward
with an instance-level wrapper that calls the original Linear.forward captured
before the hook was installed. ModelCache.put() later runs
apply_custom_layers_to_model, which constructs a new CustomLinear sharing the
original Linear's __dict__ — so the diffusers wrapper carries over and routes
calls to the captured original forward, silently bypassing CustomLinear.forward
and its cast_to_device autocast.

With partial loading (e.g. FLUX.2 Klein 9B on a constrained GPU), some Linear
weights stay on CPU. The diffusers pre_forward only casts dtype, so F.linear
then sees input on cuda:0 and weight on cpu and raises
"Expected all tensors to be on the same device".

Route every nn.Module — including ModelMixin — through _apply_fp8_to_nn_module,
which uses register_forward_pre_hook / register_forward_hook(always_call=True).
nn.Module._call_impl dispatches these around forward without replacing it, so
CustomLinear.forward is still reached and cast_to_device moves the weight to
the input device. Lose diffusers' _disable_peft_input_autocast in the process,
which is irrelevant — InvokeAI patches LoRAs through CustomLinear's
_patches_and_weights, not PEFT BaseTunerLayer.

Add regression test that asserts the ModelMixin branch calls
_apply_fp8_to_nn_module and not enable_layerwise_casting.

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-05-26 20:26:25 +00:00
Alexander Eichhorn 7859d3fc91 feat(nodes): add Save Image (Gallery + File Export) node (#9077)
* feat(nodes): add Save Image (Gallery + File Export) node

Saves an image to the gallery and additionally exports a copy to the
filesystem with a custom filename using prefix and suffix around the
gallery UUID.

- Filename pattern: {prefix}{uuid}{suffix}.{png|jpg|webp}
- UUID matches the gallery entry so exported files can be matched back
- Quality input (1-100) for JPG/WEBP, ignored for PNG
- Board and Metadata inputs behave like the standard Save Image node
- Output directory restricted to subfolders of the InvokeAI outputs
  folder; absolute paths and path traversal are rejected

* Chore Ruff

* Chore Typegen

* Chore Typegen

* Fix Linux CI Test

* update schema

---------

Co-authored-by: dunkeroni <dunkeroni@gmail.com>
2026-05-26 15:53:29 -04:00
Alexander Eichhorn 3e72b88839 Harden/router auth and bugfixes (#9200)
* harden(api): enforce auth on 4 routers, fix 4 router bugs, add route tests

Add auth dependencies to download_queue, style_presets, model_relationships,
and utilities routers, which previously had none — under multiuser=True these
endpoints were anonymously reachable. CurrentUserOrDefault for read/per-resource
routes; AdminUserOrDefault for global actions (prune/cancel-all downloads,
style preset import/export, model relationship add/remove). Single-user mode
behavior is unchanged.

Fix four bugs in the same routers:
- model_relationships: HTTPException(400) from the self-relationship check was
  caught by `except Exception` and re-raised as 500. Move the check before the
  try-block and drop the broad except so the 400 reaches the client.
- style_presets update_style_preset: image was persisted before json.loads
  could fail, leaving the preset image partially updated on bad payloads.
  Validate `data` first, then mutate image state.
- style_presets create/update: json.JSONDecodeError was not caught alongside
  pydantic.ValidationError; malformed JSON surfaced as 500. Catch both → 400.
- download_queue download(): `Path(dest)` with no validation. Reject absolute
  paths, '..' segments, and empty strings with 400 before the service is called.

Add shared multiuser test fixtures in tests/app/routers/conftest.py and new
route-level tests for download_queue, style_presets, model_relationships,
utilities, and virtual_boards covering anonymous-rejection, role gating, and
the four bug regressions.

Enable --cov=invokeai in pytest addopts so the existing fail_under = 85
threshold is actually enforced.

* Ruff

* chore: openapi.json

* harden(api): enforce auth on 4 routers, fix 4 router bugs, scope style presets per-user, add route tests

Add auth dependencies to download_queue, style_presets, model_relationships,
and utilities routers, which previously had none — under multiuser=True these
endpoints were anonymously reachable. CurrentUserOrDefault for read/per-resource
routes; AdminUserOrDefault for global actions (prune/cancel-all downloads,
style preset import/export, model relationship add/remove). Single-user mode
behavior is unchanged.

Fix four bugs in the same routers:
- model_relationships: HTTPException(400) from the self-relationship check was
  caught by `except Exception` and re-raised as 500. Move the check before the
  try-block and drop the broad except so the 400 reaches the client.
- style_presets update_style_preset: image was persisted before json.loads
  could fail, leaving the preset image partially updated on bad payloads.
  Validate `data` first, then mutate image state.
- style_presets create/update: json.JSONDecodeError was not caught alongside
  pydantic.ValidationError; malformed JSON surfaced as 500. Catch both -> 400.
- download_queue download(): `Path(dest)` with no validation. Reject absolute
  paths, '..' segments, and empty strings with 400 before the service is called.

Scope style presets per-user. Migration 27 added `user_id` and `is_public`
columns months ago, but the service layer never read or wrote them — every
authenticated user saw and could mutate every preset. This commit wires them up:

- StylePresetRecordDTO gains `user_id`; StylePresetWithoutId / StylePresetChanges
  / StylePresetFormData gain `is_public`.
- SqliteStylePresetRecordsStorage.create / create_many / _sync_default_style_presets
  write user_id (system for defaults). get_many takes user_id + is_admin and
  filters SQL to: admin sees all, otherwise own ∪ default ∪ public.
- style_presets router gains _assert_preset_read / _assert_preset_write helpers
  mirroring _assert_image_owner in routers/images.py. Get/update/delete/image
  load the record first, then enforce: 403 for non-owner on private presets,
  403 for non-admin attempting to mutate a Default preset, 403 for non-admin
  trying to create a Default preset.

Add shared multiuser test fixtures in tests/app/routers/conftest.py: enable_multiuser
patches ApiDependencies across the relevant router modules and swaps None-valued
services for MagicMock, plus wires a real SqliteStylePresetRecordsStorage on the
in-memory DB so cross-user SQL filtering is actually exercised. Add route-level
tests for download_queue, style_presets, model_relationships, utilities, and
virtual_boards covering anonymous-rejection, role gating, the four bug regressions,
and the full ownership matrix for style presets (own/public/default/admin) on
get, list, update, delete, image fetch, and is_public flip.

Rename tests/app/routers/test_download_queue.py to test_download_queue_router.py
to avoid a pytest collection collision with the long-standing
tests/app/services/download/test_download_queue.py.

Enable --cov=invokeai in pytest addopts so the existing fail_under = 85
threshold is actually enforced.

* chore(pytest): drop --cov=invokeai from this PR

Reviewer pointed out that current full-suite coverage is ~48% while
fail_under is set to 85, so activating --cov=invokeai on this PR would
trip CI the moment it lands.

Tests added in this PR remain (they cover the bug regressions and auth
gating). Coverage-gate activation will be bundled into a follow-up PR
together with whatever additional tests are needed to clear the
threshold.

* refactor(api): share access helpers, tighten conftest, polish review nits

Address remaining feedback from the code review (issues 9200#issuecomment-4480805221):

- Extract _assert_image_owner / _assert_image_read_access / _assert_board_read_access
  from routers/images.py into a new routers/_access.py module (functions exported
  without the underscore prefix). routers/images.py keeps local _assert_* aliases
  so its 13 internal call sites stay untouched. routers/utilities.py imports the
  helper at module top instead of reaching into routers/images.py privates from
  inside image_to_prompt.
- Reorder current_user to come before the body parameter in expand_prompt and
  image_to_prompt, matching the convention used by the rest of the routers in
  this PR and by routers/images.py.
- Simplify _validate_dest: '..' in posix.parts or '..' in windows.parts is more
  direct than the set-union allocation.
- Harden tests/app/routers/conftest.py: pull the eight monkeypatched module paths
  into a _PATCHED_API_DEPENDENCIES_MODULES tuple so adding a new router is one
  list entry instead of one more setattr call. Add two WARNING blocks to the
  module docstring — one calling out the unconditional MagicMock replacements
  (style_preset_records is the one real-SQLite exception), and one reminding
  future authors that new routers must be appended to the tuple.
- test_multiuser_authorization.py: its local enable_multiuser fixture now also
  patches routers/_access.ApiDependencies, since the image read-access check
  used by its image-mutation tests now resolves names in _access's globals.

* chore: openapi.json and typegen

* Chore Ruff

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
Co-authored-by: JPPhoto <jpollack@jpollackphoto.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-05-21 21:30:44 -04:00
Jonathan 19007a1f5c Add universal noise and optional denoiser noise inputs (#9044)
* add universal noise and optional denoiser noise inputs

* Document scheduler parity limitations

* Clarify external noise integration rules

* chore: typegen

* Fix external noise handling regressions

* chore: typegen

* Fix stale FLUX.2 scheduler mu test

* chore: typegen

* chore: typegen

* chore: typegen

* Fold universal noise into noise node

* Address PR review cleanup comments

* remove num_channels from noise shape validation

* restore internal piping for cogview and sd3 channels

* chore: ruff

---------

Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
2026-05-18 23:35:43 +00:00
Alexander Eichhorn 028c976ea4 fix(external-providers): admin guard, validation, locale keys (#9171)
* fix(external-providers): admin guard, validation, locale keys

- Require AdminUserOrDefault on POST/DELETE /external_providers/config/{id}
  so non-admins in multiuser mode can no longer set/reset shared credentials
- Reject Seedream batch requests where references + init + outputs > 15
  before posting, surfacing ExternalProviderCapabilityError instead of a
  provider-side 400
- Surface Seedream batch item errors via provider_metadata.partial_failures
  and raise when every item failed, instead of silently dropping filtered
  results
- Set max_reference_images=3 on Qwen Image Edit Max so the central validator
  enforces the documented limit before hitting DashScope
- Add missing parameters.* locale keys (quality, background, inputFidelity,
  temperature, thinkingLevel, watermark, optimizePrompt) so the OpenAI,
  Gemini, and Seedream option panels render their labels without fallbacks

* i18n(external): localise OpenAI/Gemini select option labels

Wrap the visible <option> text in OpenAIProviderOptions and
GeminiProviderOptions with t(...) so non-English locales translate
the values alongside their <FormLabel> (previously the labels
translated but Auto/High/Medium/Low/Transparent/Opaque/Default/
Minimal stayed English). Adds the matching
parameters.{quality,background,inputFidelity,thinkingLevel}Options
keys to en.json and a colocated vitest guard that fails if any
<FormLabel> or <option> in External/*ProviderOptions.tsx contains
a raw literal instead of a {t(...)} expression.

* fix(canvas): gate drop tiles by current model's supported entity types

The Canvas drop area registered every <DndDropTarget> as long as the
canvas wasn't busy, so dragging an image onto the "Regional Reference
Image" tile created a regional_guidance layer even when the active
model didn't support it (e.g. OpenAI/Gemini/Seedream). The
layer-creation menu already disables those entries via
useIsEntityTypeEnabled, but the drop tiles bypassed that check.

Pull useIsEntityTypeEnabled into CanvasDropArea and OR each tile's
isDisabled with !isEnabled for its entity type. Same fix closes the
symmetric gaps for SD3 / CogView4 / Flux Kontext on the control-layer
and inpaint-mask tiles, mirroring the menu in one place.

* Chore Ruff Format

* chore: openapi

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
Co-authored-by: JPPhoto <jpollack@jpollackphoto.com>
2026-05-18 19:11:29 +00:00
kappacommit 61d1eaa5c5 feat(anima): Make Anima respect the precision property in Invoke.yaml (#9183)
* feat(devices): add choose_anima_inference_dtype honoring config.precision

When precision='auto', delegates to the existing choose_bfloat16_safe_dtype
probe. When set to a specific value (float16/bfloat16/float32), returns that
dtype directly so the user's config choice is honored. Anima call sites will
migrate to this helper in a follow-up commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(anima): honor config.precision at the three Anima dtype call sites

Swaps choose_bfloat16_safe_dtype for choose_anima_inference_dtype at:
- _run_diffusion (transformer / latents / LoRA dtype)
- anima_text_encoder (text-encoder LoRA dtype)
- model_loaders/anima (transformer load dtype)

precision='auto' preserves the current bf16-probe fallback; specific values
are now honored end-to-end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-05-17 15:21:01 -04:00
Lincoln Stein 8f46d8bf13 fix(qwen): fix ghosting artifacts Qwen Image Edit (#9155)
* fix(qwen): use distinct img_shapes for reference latents in Qwen Image Edit

Qwen Image Edit was applying identical RoPE positions to the noisy
and reference latent segments (both packed at the noisy latent's
dimensions), so cross-attention couldn't disentangle them — reference
content bled into the generation as a faintly offset ghost across the
whole frame, outside the masked edit region.

The denoise now keeps reference latents at their own (H, W) and uses
those dims in the reference segment of img_shapes, matching diffusers'
QwenImageEditPipeline / QwenImageEditPlusPipeline.

The reference qwen_image_i2l is resized to ~1024² area preserving
aspect ratio (matching diffusers' VAE_IMAGE_SIZE) so the reference
token sequence stays in the distribution the model was trained on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(qwen): clamp reference latents to VAE_IMAGE_SIZE in denoise

The frontend resizes the reference image to ~1024² area before VAE
encoding, but direct API callers and older graph JSON can wire
qwen_image_i2l → qwen_image_denoise without explicit width/height,
sending a native-resolution reference latent into the transformer.
Without the clamp the model receives an out-of-distribution sequence
length (artifact returns, VRAM spikes).

Mirror diffusers' QwenImageEdit(Plus) VAE_IMAGE_SIZE behavior in
latent space: bilinear-downscale the reference latent to
calculate_dimensions(1024², aspect_ratio) snapped to multiples of 32
in pixel space (= multiples of 4 in latent space, so always packable).
In-budget latents pass through untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-05-13 00:53:39 -04:00
Alexander Eichhorn 6f42ad0ee7 feat: add per-model FP8 layerwise casting for VRAM reduction (#8945)
* feat: add per-model FP8 layerwise casting for VRAM reduction

Add fp8_storage option to model default settings that enables
diffusers' enable_layerwise_casting() to store weights in FP8
(float8_e4m3fn) while casting to fp16/bf16 during inference.
This reduces VRAM usage by ~50% per model with minimal quality loss.

Supported: SD1/SD2/SDXL/SD3, Flux, Flux2, CogView4, Z-Image,
VAE (diffusers-based), ControlNet, T2IAdapter.
Not applicable: Text Encoders, LoRA, GGUF, BnB, custom classes

* feat: add FP8 storage option to Model Manager UI

Add per-model FP8 storage toggle in Model Manager default settings for
both main models and control adapter models. When enabled, model weights
are stored in FP8 format in VRAM (~50% savings) and cast layer-by-layer
to compute precision during inference via diffusers' enable_layerwise_casting().

Backend: add fp8_storage field to MainModelDefaultSettings and
ControlAdapterDefaultSettings, apply FP8 layerwise casting in all
relevant model loaders (SD, SDXL, FLUX, CogView4, Z-Image, ControlNet,
T2IAdapter, VAE). Gracefully skips non-ModelMixin models (custom
checkpoint loaders, GGUF, BnB).

Frontend: add FP8 Storage switch to model default settings panels with
InformationalPopover, translation keys, and proper form handling.

* ruff format

* fix: enable FP8 layerwise casting for checkpoint Flux models

FluxCheckpointModel and Flux2CheckpointModel were missing the
_apply_fp8_layerwise_casting call. Additionally, the FP8 casting
only worked for diffusers ModelMixin models. Add manual layerwise
casting via forward hooks for plain nn.Module (custom Flux class).

Also simplify FP8 UI toggle from dual-slider to single switch,
matching the CPU-only toggle pattern per review feedback on #8945.

* fix: exclude Z-Image from FP8 due to diffusers layerwise casting bug

Z-Image's transformer has dtype mismatches with diffusers'
enable_layerwise_casting: skipped modules (t_embedder, cap_embedder)
stay in bf16 while hooked modules cast to fp16, causing crashes in
attention layers. Also hide the FP8 toggle in the UI for Z-Image models.

* fix: detect model dtype for FP8 compute instead of using global dtype

Models like Flux are loaded in bf16 but the global torch dtype is fp16,
causing dtype mismatches during FP8 layerwise casting. Detect the
model's actual parameter dtype and use it as compute_dtype for both
diffusers ModelMixin and plain nn.Module models.

* Remove call for _should_use_fp8 in z-image

* Merge branch 'main' + exclude VAEs from FP8 layerwise casting

Resolve merge conflict in vae.py by keeping upstream's Anima/QwenImage VAE
loader paths and dropping the FP8 call from the AutoencoderKL checkpoint
path.

Exclude VAEs from FP8 layerwise casting in _should_use_fp8 (both standalone
ModelType.VAE and the VAE/VAEDecoder/VAEEncoder submodel types of Main
models). FP8 storage causes noticeable quality degradation on VAE decode.

* fix(fp8): invalidate cache on settings change, exception-safe nn.Module fallback, hide ControlLoRA toggle

- Add ModelCache.drop_model() and call it from update_model_record when
  fp8_storage or cpu_only change. These settings are baked into the loaded
  nn.Module at load time, so toggling them was silently a no-op until the
  cache entry was evicted by other means.
- Replace the pre-hook/post-hook pair in _apply_fp8_to_nn_module with a
  forward wrapper using try/finally. register_forward_hook only fires on
  successful forward, so an exception left params in compute dtype and
  defeated the FP8 storage savings.
- Hide the FP8 toggle in the UI for ControlLoRA and exclude LoRA/ControlLoRA
  in _should_use_fp8. LoRAs are patched into base models rather than run as
  a standalone forward pass, so layerwise-casting hooks would never fire.
- Add tests for drop_model, the exception-safe FP8 wrapper, the
  ControlLoRA/LoRA exclusion, and the _load_settings_changed predicate.

* fix(fp8): honor class swap for LoRA patches, evict stale locked entries, skip precision-sensitive layers

- _wrap_forward_with_fp8_cast now dispatches via type(module).forward at
  call time instead of capturing the bound method. ModelCache.put() swaps
  nn.Linear.__class__ to CustomLinear (sharing __dict__), which would
  otherwise leave our instance forward shadowing CustomLinear.forward and
  silently bypass LoRA/ControlLoRA patch dispatch on FP8 checkpoints.
- drop_model() now marks locked entries is_stale instead of skipping them
  silently; unlock() evicts stale entries once the last lock releases.
  Without this, a setting toggled during an in-flight generation survived
  on the locked entry and the next generation reused the pre-change module.
- _apply_fp8_to_nn_module mirrors diffusers' apply_layerwise_casting: only
  the supported layer classes (Linear/Conv*/Embedding) get cast, and module
  paths matching norm/pos_embed/patch_embed/proj_in/proj_out are skipped.
  FLUX RMSNorm.scale and similar precision-sensitive scalars are no longer
  crushed to FP8.
- drop_model() and the unlock-stale path now update stats.cleared and fire
  on_cache_models_cleared callbacks, matching _make_room_internal so the
  UI stats panel and observers don't miss invalidations.
- Add 14 tests: class-swap dispatch, norm/pos_embed/proj_in_out skip,
  unsupported-type skip, stale-marking, multi-lock release, stats and
  callback firing for both paths, no-op silence.

* fix(fp8): switch nn.Module FP8 wrapper to hooks so CustomLinear dispatch survives apply_custom_layers_to_model

Previous fix was wrong. `apply_custom_layers_to_model` does not do
`module.__class__ = CustomLinear` — `wrap_custom_layer` constructs a NEW
CustomLinear via __new__ and shares the original Linear's __dict__, then
setattr installs the new object on the parent. The new object has
type() == CustomLinear, but our wrapped forward closed over the original
Linear instance, so `type(module).forward(module, ...)` resolved to
Linear.forward on the captured old object and silently bypassed
CustomLinear.forward — breaking LoRA/ControlLoRA patch dispatch for
FP8 checkpoint models. Reproduced on a fresh worktree.

Replace the instance-forward override with register_forward_pre_hook +
register_forward_hook(always_call=True). Hooks are dispatched by
nn.Module._call_impl with the actual called instance, so they fire on
the new CustomLinear and self.forward resolves normally via class lookup
— reaching CustomLinear.forward and its patch-aware branch.
always_call=True keeps the exception-safety guarantee (post-hook fires
even when forward raises).

Replace the simulated __class__-swap test with one that runs real
apply_custom_layers_to_model, attaches a sentinel _patches_and_weights,
and asserts the patch-aware branch in CustomLinear.forward is reached.
Verified the test fails under the old instance-forward implementation
with the reviewer-described symptom and passes under the hook fix.

* Add docs for fp8

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-05-12 02:43:38 +00:00
Jonathan f99858b89f Add OKLab and Oklch image utilities and nodes, refactor color space nodes (#8999)
* Add OKLab and Oklch image utilities and nodes

* refactor: unify oklab color conversions

* refactor: unify shared color conversions

* chore: typegen

* chore: typegen

---------

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-05-11 13:39:31 +00:00
Jonathan aa865f6e08 feat: add image subfolder strategy setting UI (#9133)
* feat: add image subfolder strategy setting UI

* fix: address image subfolder strategy review
2026-05-08 19:50:49 -04:00
kappacommit 25bbf32a65 feat(model): Add ER SDE / DPM++ 2M Scheduler Support For Anima (#9125)
* refactor(anima): reshape ANIMA_SCHEDULER_MAP to (class, kwargs) tuples

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(anima): address Task 1 review feedback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(anima): add dpmpp_2m and dpmpp_2m_sde schedulers

* refactor(anima): unify ANIMA_SHIFT in schedulers.py and add Literal-coverage test

* fix(anima): seed generator into scheduler.step for SDE reproducibility

* feat(anima): add pure ancestral-Euler step helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(anima): fix _anima_euler_ancestral_step docstring formula to match code

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(anima): address Task 4 review feedback

* feat(anima): add euler_a (rectified-flow ancestral Euler) scheduler

* fix(anima): sample euler_a noise in float32 to avoid bfloat16 quantization

* chore(anima): bump anima_denoise to v1.3.0 and regen schema

* fix(frontend): revert Windows path-separator drift in schema regen

* fix(anima): gate step_generator construction to schedulers that need it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(anima): apply ruff lint and format fixes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(frontend): expose new Anima schedulers in dropdown and metadata recall

* chore(frontend): apply prettier wrap to setAnimaScheduler PayloadAction

* fix(anima): correct euler_a math — variance-preserving noise mix, not biased Euler

* revert(anima): remove euler_a scheduler — quality not worth the complexity

* chore(anima): apply ruff format (trim trailing blank lines)

* feat(rectified-flow): add order-1 ER-SDE stepper for rectified flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(rectified-flow): document lambda_next>0 precondition; tighten terminal-step test

Address code review feedback on order-1 ER-SDE stepper:
- Add docstring preconditions to integral helpers noting the logarithmic
  singularity at lam=0 (callers must guard sigma_next>0).
- Tighten terminal-step test from atol=1e-5 to torch.equal — the algebra
  is exact when sigma_next=0, not approximate.

* feat(rectified-flow): add 2nd-order Taylor extension to ER-SDE stepper

* test(rectified-flow): tighten Task 2 state-mutation test with value assertion

Address code review feedback on the 2nd-order Taylor extension tests:
- Assert state.old_d_x0 equals the analytically-computed d_x0 = 0.2v / (1.5 - 4.0)
  rather than just checking it's non-None.
- Document that x_t is intentionally re-used across calls (state threading
  test, not trajectory correctness).
- Document the order-2 correction coefficient magnitude that justifies the
  atol=1e-3 threshold in the engagement test.

* feat(rectified-flow): add 3rd-order Taylor extension to ER-SDE stepper

* docs(rectified-flow): document have_two_back invariant + order-3 test margin

Address code review feedback on Task 3:
- Comment why have_two_back checks both old_d_x0 and sigma_prev_prev
  (the sigma~=1 boundary path can break the joint invariant).
- Document the analytically verified ~0.0004 per-element correction
  magnitude that justifies the atol=1e-3 threshold in the order-3 test.

* feat(anima): register er_sde scheduler choice

* docs(anima): document custom-code-path scheduler convention; tighten test

Address code review feedback on Task 4:
- Add an in-file comment above ANIMA_SCHEDULER_MAP explaining the
  convention: schedulers with custom code paths (er_sde) live in the
  Literal+labels only, not the map.
- Hoist `import typing` to module-level in test_anima_schedulers.py
  (was inline-imported in two test functions).
- Pin the er_sde label value (== "ER-SDE"), not just key existence.

* feat(anima): wire er_sde scheduler into denoise loop

* docs(anima): document float32 noise dtype and sigma_next/sigma_prev naming

Address code review feedback on Task 5:
- Comment explaining why fresh_noise is float32 (matches er_sde_rf_step's
  dtype contract with x_t.to(float32)).
- Bridging comment at the inpaint extension call clarifying that
  sigma_next here means the same thing as sigma_prev in the Euler branch
  and the AnimaInpaintExtension API.

* chore(anima): bump anima_denoise to v1.4.0 and regen schema

* feat(frontend): expose er_sde scheduler in dropdown and metadata recall

Address code review on Task 6 — er_sde was registered in the OpenAPI
schema but missing from the frontend's own Zod enums and Redux
PayloadAction types, so:
- The combobox dropdown didn't list it.
- setAnimaScheduler('er_sde') would fail TypeScript at the call site.
- Metadata recall for er_sde-generated images would silently no-op
  (the scheduler value couldn't pass zParameterScheduler validation).

Changes:
- Add er_sde to zAnimaSchedulerField (the per-Anima Zod enum).
- Add er_sde to the animaScheduler state-shape Zod enum.
- Widen setAnimaScheduler's PayloadAction union.
- Add ER-SDE option to the ParamAnimaScheduler combobox.
- Make the metadata Scheduler handler accept ParameterAnimaScheduler too,
  with a fallback parse and a narrowing guard before the SD/SDXL dispatch.

* fix(rectified-flow): guard 2nd-order branch against sigma_prev_curr=1.0

When step 0 goes through the sigma_curr=1 closed-form limit branch it
writes state.sigma_prev_curr=1.0. On step 1, have_one_back was True and
the 2nd-order path called _lambda(1.0) = 1.0/(1.0-1.0), crashing with
ZeroDivisionError in every real denoise run.

Fix: extend the have_one_back guard to require that sigma_prev_curr is
more than _SIGMA_ONE_TOLERANCE below 1.0. The finite-difference derivative
across the limit step is not meaningful, so skipping the 2nd-order term on
that transition is correct. Order-3 is already gated behind old_d_x0 being
set, which this path never sets, so no additional guard is needed there.

Adds a regression test that runs the full sigma=1.0->0.9->0.7->0.0
sequence and asserts no ZeroDivisionError and all-finite output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(scheduler): add ERSDEScheduler available to SD/SDXL

ER-SDE solver (Cui et al., arXiv:2309.06169) usable across SD/SDXL
(VP-SDE) and rectified-flow models. Anima migration follows in
subsequent commits.

- ERSDEScheduler(SchedulerMixin, ConfigMixin) with prediction_type
  (epsilon | v_prediction | flow_prediction), use_flow_sigmas,
  solver_order (1/2/3 with auto-warmup), and stochastic toggle
- set_timesteps(sigmas=) for pre-shifted Anima/FLUX/Z-Image schedules
- Closed-form limit at sigma=1 in flow mode
- Unit tests + VP smoke + 5/5 Anima parity vs er_sde_rf_step
  (worst delta 5.137e-07)
- Frontend wiring: zSchedulerField, SCHEDULER_OPTIONS, OpenAPI regen
- parsing.tsx cleanup: removes the AnyScheduler widening since
  er_sde is now a first-class general scheduler

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(anima): wire ERSDEScheduler into ANIMA_SCHEDULER_MAP

Adds er_sde to the standard scheduler dispatch map with rectified-flow
kwargs (flow_prediction, use_flow_sigmas=True, flow_shift=3.0,
solver_order=3, stochastic=True). Anima still routes through the
legacy elif is_er_sde: branch — that's removed in a follow-up commit.
This is the additive prerequisite that lets the cutover happen without
a window where Anima can't use ER-SDE.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(anima): add ER-SDE dispatch integration tests

Verifies the ANIMA_SCHEDULER_MAP['er_sde'] entry instantiates correctly,
accepts pre-shifted sigmas via set_timesteps(sigmas=...), and resets
multistep state. Catches wiring regressions that the algorithm-level
parity test cannot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(anima): remove elif is_er_sde branch, dispatch through ANIMA_SCHEDULER_MAP

Anima ER-SDE now flows through the same standard scheduler path as
dpmpp_2m_sde — pre-shifted sigmas via scheduler.set_timesteps(sigmas=...),
inpaint extension via inpaint_extension.merge_intermediate_latents_with_init_latents,
step_callback per-step. The custom code path was the only thing keeping
ER-SDE off the universal pipeline.

Bumps invocation version to 1.5.0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(er_sde): mark module as internal reference and parity oracle

ERSDEScheduler is now the production code path. er_sde_rf_step is kept
as the comparison oracle for the scheduler's parity test, and as a
self-contained mathematical reference for the rectified-flow algebra.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(rectified-flow): remove er_sde.py reference helper

ERSDEScheduler is the production code path. The pure-function helper
was retained as a parity oracle but YAGNI — keeping ~200 lines of code
purely as a regression net for hypothetical future drift isn't worth
the maintenance signal it generates.

Removes:
- invokeai/backend/rectified_flow/er_sde.py
- tests/backend/rectified_flow/test_er_sde.py
- tests/backend/rectified_flow/test_er_sde_scheduler_anima_parity.py

ERSDEScheduler's own tests (test_er_sde_scheduler.py) remain — they
exercise both VP-SDE and rectified-flow paths directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(schema): restore forward slashes in @default cache dir paths

Windows-side typegen run flipped these to backslashes. Restore the
canonical forward-slash form to match upstream.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style: apply ruff lint and format to ER-SDE files

Sort imports + format per project ruff config (line-length 120).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(state): use zParameterAnimaScheduler in state shape

The animaScheduler field inlined its enum (originally to add er_sde
when the shared schema didn't have it yet). Now that zAnimaSchedulerField
already includes er_sde, reference the shared zParameterAnimaScheduler
to match the pattern used by scheduler/fluxScheduler/zImageScheduler.
Drops the redundant .default('euler') — initial value comes from
getInitialParamsState.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style(types): sort imports per simple-import-sort

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(anima): honour clipped sigma schedule for DPM++ img2img/inpaint

DPMSolverMultistepScheduler doesn't accept sigmas= in diffusers 0.35.1,
so the fallback previously called set_timesteps(num_inference_steps=total_steps)
which regenerated a full schedule from sigma_max, ignoring denoising_start/end.

When the scheduler supports set_begin_index, call set_timesteps with the
full step count and offset into it, so the internal flow_shift applies
correctly and denoising starts at the right sigma.

Also fixes the inpaint sigma_prev lookup and the timestep loop to use the
same offset, and corrects the false parity-test reference in the ER-SDE
dispatch test docstring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: apply ruff format to anima_denoise dispatch block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(anima): extract scheduler driver and fix Heun progress/inpaint bugs

Encapsulate per-scheduler dispatch quirks (sigmas= vs num_inference_steps=,
Heun's doubled-array index, set_begin_index path) in AnimaSchedulerDriver
and tighten two latent bugs in the Heun path of anima_denoise:

* Heun's terminal first-order step never reported a user-step completion,
  so progress capped at N-1 of N. The driver now flags it via sigma_prev==0,
  and the <= total_steps clamp that papered over the off-by-one is gone.
* The inpaint mix ran after every Heun half-step, corrupting the second-order
  corrector's input (RectifiedFlowInpaintExtension's docstring says it should
  be called after each denoising step — i.e. once per user step). Mix is now
  gated on completes_user_step, which is unconditionally True for non-Heun.

Also: Heun shift kwarg switched to ANIMA_SHIFT (its set_timesteps doesn't
accept sigmas=, so it builds its own internal schedule); narrative comments
in scheduler_driver and er_sde_scheduler trimmed; new tests covering driver
iteration counts, terminal sigma_prev, seed determinism, and the begin_index
fallback for clipped DPM++ schedules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: apply ruff lint and format to anima scheduler driver

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: re-run after flaky token-expiration test

The 1-second JWT token-expiration test in test_token_service.py is timing
sensitive — passes locally on retry. Empty commit to retrigger CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-05-08 02:59:23 +00:00
kappacommit fcc0881811 fix(mm): support ComfyUI bundled checkpoint format for Anima model identification (#9113)
Anima finetunes packaged in ComfyUI format use `model.diffusion_model.*`
prefixed keys instead of bare or `net.*` prefixed keys. Update the probe
and loader to recognize and handle this format.

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 02:31:08 +00:00
Alexander Eichhorn 51f528c819 Feat:(model) qwen image vae checkpoint (#9108)
* feat(qwen-image): standalone VAE checkpoint and Qwen2.5-VL encoder support

Add standalone model types so Qwen Image can be run without downloading the
full ~40 GB Diffusers pipeline. The VAE and Qwen2.5-VL encoder can now each
come from their own model, with the Component Source (Diffusers) acting as a
fallback for any submodel not provided separately.

* feat(qwen-image): support ComfyUI single-file Qwen2.5-VL encoder

Add a checkpoint loader for ComfyUI-style consolidated Qwen2.5-VL encoder
files (e.g. qwen_2.5_vl_7b_fp8_scaled.safetensors), which bundle the language
model and visual tower into one safetensors with FP8 + per-tensor weight_scale
quantization. This drops the standalone encoder footprint from ~16 GB
(Diffusers folder, FP16) to ~7 GB.

* feat(qwen-image): register standalone components as starter models

Add three new starter models so users can install a complete GGUF Qwen Image
setup in one click without ever touching the full ~40 GB Diffusers pipeline:

- "Qwen Image VAE" — single-file VAE checkpoint pulled from the Qwen-Image
  repo (~250 MB).
- "Qwen2.5-VL Encoder (fp8 scaled)" — ComfyUI single-file FP8 encoder
  (~7 GB).
- "Qwen2.5-VL Encoder (Diffusers)" — full-precision encoder via multi-folder
  HF download (text_encoder+tokenizer+processor, ~16 GB).

The 8 GGUF main starters (Q2_K / Q4_K_M / Q6_K / Q8_0 for both Edit and
txt2img) now declare the VAE + fp8 encoder as dependencies, so installing
any of them automatically pulls in everything needed to generate. The
fp8 encoder is preferred as the default dependency since it's smaller and
the on-the-fly dequantization is essentially free at runtime.

The Qwen Image starter bundle gets the VAE and fp8 encoder prepended so
the bundled Lightning LoRA variants also benefit.

* Chore Ruff Format

* fix(qwen-image): backfill VAE/encoder fields on persisted state, recall in metadata, optimize scan

- bump params slice persisted state to v3 with a v2→v3 migration that
  backfills qwenImageVaeModel and qwenImageQwenVLEncoderModel to null,
  preventing existing users from losing all persisted params on upgrade
- emit qwen_image_vae and qwen_image_qwen_vl_encoder into graph metadata
  and add recall handlers so generations using standalone components are
  reproducible
- clear the two new fields in the modelSelected listener when switching
  away from qwen-image, matching the existing cleanup pattern
- identify single-file Qwen VL encoder checkpoints by reading only the
  safetensors key index via safe_open, instead of loading the full ~7GB
  state dict into RAM during model scan
- log a clear info message and raise an actionable RuntimeError when the
  first-time HuggingFace tokenizer/config download is needed but offline,
  pointing users to the diffusers folder layout as an offline alternative
- add unit tests for the migration, metadata recall, and identification

* fix(qwen-image): auto-select VAE/encoder, clarify GGUF tip, fix fp8 single-file encoder crash

- Auto-select first available standalone VAE and Qwen2.5-VL encoder when
  switching to a Qwen Image model, so GGUF users are ready-to-go without
  digging into Advanced. Prefers the diffusers-folder encoder over the
  single-file checkpoint.
- Update the "Required for GGUF models" placeholder to clarify that
  the diffusers source is only required when a standalone VAE & encoder
  is not installed.
- Fix QwenVLEncoderCheckpointLoader crash on ComfyUI fp8_scaled
  single-file encoders. Two issues: (1) handle the `.scale_weight` /
  `.scale_input` quantization key scheme alongside `.weight_scale`,
  and (2) apply Qwen2_5_VLForConditionalGeneration's
  _checkpoint_conversion_mapping before load_state_dict so legacy
  `visual.*` / `model.*` keys map onto the new `model.visual.*` /
  `model.language_model.*` layout expected by transformers ≥4.50.

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-05-08 02:07:24 +00:00
Alexander Eichhorn 8c17fbe9b4 feat: add configurable image subfolder strategies (#8969)
* feat: add configurable image subfolder strategies

Add support for organizing output images into subfolders instead of a
single flat directory. Four strategies are available via the
image_subfolder_strategy config setting: flat (default, current
behavior), date (YYYY/MM/DD), type (by image category), and hash (UUID
prefix for filesystem performance). The strategy can be changed at any
time - existing images keep their paths, only new images use the new
strategy.

* Chore ruff check

* Chore typegen

* Chore fix ts types

* make subfolder optional

* Add image_subfolder_strategy to InvokeAIAppConfig

* Chore typegen again

* Add test coverage for image subfolder feature

Cover the new subfolder path surface introduced in PR #8969:
subfolder validation and security checks, ImageService subfolder
forwarding for all strategies, delete_images_on_board silent-failure
contract, migration 28, config 4.0.2→4.0.3 upgrade, and
recall_parameters subfolder resolution regression.

* Uses a real in-memory SQLite database (same pattern as test_model_records_sql.py) to verify image_subfolder round-trips correctly through:

TestImageSubfolderRoundTrip (3 tests) — save() -> get() for default empty, custom, and deeply nested subfolders
TestGetManySubfolder (1 test) — get_many() deserializes image_subfolder on every row
TestDeleteIntermediatesSubfolder (2 tests) — delete_intermediates() returns correct (name, subfolder) pairs and actually removes intermediate rows

* Chore Docs

* test(recall_parameters): merge duplicate test files into single module

Two test files shared the basename test_recall_parameters.py
(tests/app/api/routers/ and tests/app/routers/), which broke pytest
collection due to module name conflict. Merge the load_image_file unit
tests into the main router test file and remove the duplicate.

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-05-06 22:35:14 -04:00
Alexander Eichhorn 18b98e7ac9 feat(apionly): seedream provider (#9054)
* feat: initial external model support

* feat: support reference images for external models

* fix: sorting lint error

* feat: add Seedream provider and capability-driven settings visibility

Add BytePlus Seedream as external image generation provider with four
models (seedream-4.5, seedream-4.0, seedream-3.0-t2i) including debug
dump support and batch generation.

Hide irrelevant canvas settings for external models by using
ExternalModelCapabilities as the source of truth for UI rendering.
Scheduler, LoRA, CFG Scale, and all Advanced settings (VAE, CLIP Skip,
Seamless, etc.) are now hidden for external models. Steps, Guidance,
and Seed controls are only shown when the model declares support via
its capability flags. Adds supports_steps capability field and gates
the graph builder accordingly.

* 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

* feat: initial external model support

* feat: add Seedream provider and capability-driven settings visibility

Add BytePlus Seedream as external image generation provider with four
models (seedream-4.5, seedream-4.0, seedream-3.0-t2i) including debug
dump support and batch generation.

Hide irrelevant canvas settings for external models by using
ExternalModelCapabilities as the source of truth for UI rendering.
Scheduler, LoRA, CFG Scale, and all Advanced settings (VAE, CLIP Skip,
Seamless, etc.) are now hidden for external models. Steps, Guidance,
and Seed controls are only shown when the model declares support via
its capability flags. Adds supports_steps capability field and gates
the graph builder accordingly.

* Add Seedream provider, starter models, and provider-specific UI options

- Add SeedreamProvider with support for 5.0 Lite, 4.5, 4.0, and 3.0 models
- Add Seedream starter models with correct 2K/1K resolution tables
- Add SeedreamImageGenerationInvocation with watermark and optimize_prompt fields
- Register seedream node type in frontend graph builder
- Add SeedreamProviderOptions UI component (watermark, optimize prompt checkboxes)
- Add Seedream state/reducers/selectors to paramsSlice
- Fix Seedream provider to use provider_options for guidance_scale and watermark
- Add 429 rate limit handling with retry_after support
- Update Seedream tests for new ExternalGenerationRequest interface

* 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

* docs: add Seedream provider page to External Models guide

* chore: ruff format seedream provider and starter_models

* chore(frontend): regenerate schema.ts to include Seedream config fields

* fix: remove reserved Windows device name "nul" from repo

* Chore windows path

* fix: update InvokeAIAppConfig docstring with Seedream config fields

* chore(frontend): regenerate schema.ts after docstring update

* 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(seedream): TSC errors + seedream provider follow-ups

- Export imageSizeChanged from paramsSlice so the metadata recall handler
  can import it.
- Build the external graph's metadata model entry via zModelIdentifierField
  (ExternalApiModelConfig is not in the AnyModelConfig union).
- Strip Seedream debug payload/image dumps.
- Regenerate schema.ts.

* chore pnpm fix

* Chore Docs

* Fix move the seedream to api_keys.yaml

* fix(seedream): consolidate API key storage, update model IDs, fix metadata recall and node fields

- Load external_seedream_api_key/base_url from api_keys.yaml like the other providers
- Update Seedream 5.0 Lite ID to seedream-5-0-lite-260128 and add Seedream 5.0 (seedream-5-0-260128)
- Remove deprecated Seedream 3.0 T2I (replaced by Seedream 4.0 per BytePlus)
- Add metadata recall handlers for watermark and optimize_prompt so Remix restores them
- Hide non-functional mode and mask_image fields on the Seedream node (Seedream API has no inpaint and infers mode from inputs); bump node to 1.1.0

* docs(mm): move external provider docs into location needed for astro

* fix(linear): respect iterations for models without a seed node

Linear UI dropped the iterations count when the graph builder returned no seed node (external API models without seed support like Seedream), so iterations > 1 produced only one image. Use batch.runs to repeat the graph in that case.

* Fix Docs

* Fix Docs 2

---------

Co-authored-by: CypherNaught-0x <9931495+CypherNaught-0x@users.noreply.github.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-05-06 20:24:47 -04:00
Alexander Eichhorn 73d46338af feat(apionly): add Alibaba Cloud Models (#9055)
* 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

* feat: add Alibaba Cloud DashScope external image generation provider

Add AlibabaCloudProvider supporting Qwen Image and Wan model families
via the DashScope API. Includes sync (multimodal-generation) and async
(image-generation with task polling) request modes, five starter models
(Qwen Image 2.0 Pro, 2.0, Max, Wan 2.6 T2I, Qwen Image Edit Max),
config fields for API key and base URL, and frontend registration.

* 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

* docs: add Alibaba Cloud DashScope provider page to External Models guide

* chore: ruff format alibabacloud provider

* chore(frontend): regenerate schema.ts to include Alibaba Cloud DashScope config fields

* fix: update InvokeAIAppConfig docstring with Alibaba Cloud DashScope config fields

* chore(frontend): regenerate schema.ts after docstring update

* 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

* feat: add AlibabaCloudImageGenerationInvocation node for DashScope provider

* 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

* Chore Fix typegen

* Chore Docs

* fix(alibabacloud): remove references to non-existent negative_prompt field

* fix(alibabacloud): address PR review — explicit routing, retries, fix double-counting, Qwen Edit Max as ref-image model

- Explicit sync/async lookup, raise on unknown model_id
- Move poll sleep to end of loop, info-log on first poll
- if/elif in async parser to prevent url+b64_image double-count
- 429/5xx retry with Retry-After, wrap RequestException into ExternalProviderRequestError
- 32 MiB streaming cap on image downloads
- Drop dead routing-table entries and the init_image edit path
- Disable supports_negative_prompt on all Alibaba starter models (request schema has no negative_prompt field)
- Switch Qwen Image Edit Max to txt2img + reference_images panel (up to 3 inputs)
- Update docs
- Add 8 unit tests covering parser, routing, retries, polling, and download cap

* Chore Ruff

* docs: fix info text on storage location of external model api keys

---------

Co-authored-by: CypherNaught-0x <9931495+CypherNaught-0x@users.noreply.github.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-05-05 03:00:20 +00:00
Alexander Eichhorn d50c898a7c fix(mm): identify Qwen2.5/Qwen3 causal LMs as Text LLM, not Qwen3 encoder (#9097)
* fix(mm): identify Qwen2.5/Qwen3 causal LMs as Text LLM, not Qwen3 encoder

`Qwen3Encoder_Qwen3Encoder_Config` matches any directory with `config.json`
at the root and a Qwen* class name, which also describes a complete causal
LM like `Qwen2.5-1.5B-Instruct`. With both configs matching and equal sort
keys, the encoder won the tie-break, blocking the model from being used as
a prompt expander.

A standalone text_encoder download has no tokenizer files; a complete causal
LM does. Use that to reject the encoder match when tokenizer files are
present alongside `config.json` at the root.

Also fixes the secondary failure when manually switching the type from
`Qwen3Encoder` to `Text LLM`: the existing record's `format=qwen3_encoder`
and `variant` were carried over and produced no matching discriminator under
`text_llm`. On `ValidationError` after a type change, retry with the stale
fields stripped so the new class can apply its defaults.

Closes #9090

* Chore Ruff

* feat(prompt-tools): show install hint when no LLM/vision model is present

The Expand Prompt (sparkle) and Image-to-Prompt buttons used to disappear
entirely when no Text LLM or LLaVA model was installed, leaving users no way
to discover the feature exists.

Both buttons now stay visible at all times. Clicking them with no suitable
model installed opens a popover that:

- explains what kind of model the feature needs
- recommends a default (Qwen2.5-1.5B-Instruct for prompt expansion, LLaVA
  Onevision 0.5B for image-to-prompt)
- offers an "Open Model Manager" button that jumps straight to the Starter
  Models sub-tab (mirrors the useStarterModelsToast flow, but skips the
  Launchpad detour since we already know which models the user needs).

Tooltip labels also reflect the missing-model state.

Adds four starter models so the recommended setups are one click away:
- Qwen2.5-1.5B-Instruct (~3 GB) — recommended Text LLM default
- Qwen2.5-3B-Instruct (~6 GB) — higher quality Text LLM
- SmolLM2-1.7B-Instruct (~3 GB) — Apache-2.0 Text LLM alternative
- LLaVA Onevision Qwen2 7B (~16 GB) — larger LLaVA option alongside
  the existing 0.5B starter
2026-05-05 02:41:44 +00:00
Lincoln Stein 47d0952aab fix(multiuser): redact other users' current-item identifiers from queue status (#9087)
* fix(multiuser): redact other users' current-item identifiers from queue status events

QueueItemStatusChangedEvent embeds the SessionQueueStatus, which includes the
currently-running item's item_id, session_id, and batch_id. The event ships to
user:{owner} and admin rooms. When user A's item changed status while user B's
item was the one in progress, owner A's frontend received the event with B's
identifiers exposed.

In _set_queue_item_status, scrub item_id/session_id/batch_id from the embedded
queue_status when the in-progress item belongs to a different user than the
changed item. Aggregate counts remain global (not user-sensitive).

Identified out-of-scope in the security audit of #127.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(session_queue): close race condition in session queue user_id redaction

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-04-30 17:19:34 -04:00
Alexander Eichhorn 6190bca6e9 feat: add Custom Node Manager (#9047)
* feat: add Custom Node Manager for installing and managing node packs from the UI

Adds a new "Nodes" tab (circuit icon) to the sidebar with a two-panel layout:
- Left: list of installed custom node packs with reload and uninstall
- Right: tabbed install UI (Git URL / Scan Folder) with install log

Backend API endpoints (POST install, DELETE uninstall, POST reload, GET list)
handle git clone, pip dependency install, runtime node loading/unloading,
and automatic workflow import from node pack repositories. Workflows are
tagged with node-pack:<name> and removed on uninstall.

Includes user and developer documentation, plus 31 tests (21 backend, 10 frontend).

* Chore typegen

* Chore Typegen without Node

* feat(custom-nodes): detect deps instead of auto-installing

Auto-running pip on requirements.txt could pull incompatible
packages into the running InvokeAI env and break the app. The
installer now detects requirements.txt or pyproject.toml,
returns requires_dependencies + dependency_file, and the UI
shows a persistent warning toast pointing the user to the
node pack's documentation.

* Chore ruff

* custom nodes: require admin auth, share imported workflows, and localize UI

- Gate install/uninstall/reload routes on AdminUserOrDefault so they respect multiuser auth
- Import pack workflows under the installing admin with is_public=True so all users see them
- Replace hardcoded English strings in CustomNodesList and CustomNodesInstallLog with translations
- Reuse existing common/queue keys for clear/status, drop duplicates in en.json

* test(custom_nodes): update _import_workflows_from_pack tests for owner_user_id

Pass owner_user_id="admin" in all call sites and assert that user_id and
is_public=True are forwarded to workflow_records.create().

* custom nodes: track imported workflows via manifest and harden pack listing

- Record imported workflow IDs in .invokeai_pack_manifest.json inside the pack
  directory; uninstall reads the manifest before rmtree and deletes only those
  IDs, so user-authored workflows sharing the pack tag are preserved
- Gate GET /v2/custom_nodes/ with AdminUserOrDefault to match install/uninstall
  /reload and prevent unauthenticated disclosure of absolute node pack paths
- Extract getParentDirectory() helper that handles both POSIX and Windows
  separators so the nodes-directory label renders on all platforms
- Add regression tests for manifest roundtrip, colliding-tag preservation, and
  parent-directory extraction across separator styles

* Chore typegen

* ui: hide Custom Nodes tab for non-admin users in multiuser mode

Add useIsCustomNodesEnabled hook (mirrors useIsModelManagerEnabled) and
conditionally render the tab in VerticalNavBar. Backend routes already
reject non-admin callers; this prevents the UI from advertising controls
that would 403.

* ui: guard Custom Nodes tab content for non-admin persisted state and add auth regression tests

- Suppress CustomNodesTabAutoLayout render and redirect to generate via
  navigationApi.switchToTab when a non-admin user lands on a persisted
  customNodes tab
- Add TestCustomNodesAuthorization with 10 route-level tests verifying
  unauthenticated (401), non-admin (403), and admin (200) for list,
  install, uninstall, and reload endpoints
- Add decision-matrix test for useIsCustomNodesEnabled covering
  single-user, multiuser admin, multiuser non-admin, and unloaded user

* test: add shared helper for custom-nodes gate + admin happy-path tests

Extract getIsCustomNodesEnabled so test imports the real logic
instead of a local reimplementation. Add install/uninstall
admin-success tests with mocked filesystem/subprocess.

* fix(custom-nodes): return optimistic default while setup status loads

Prevents redirect away from persisted customNodes tab on startup
in single-user mode when RTK Query hasn't resolved yet.

* ui: split custom nodes permission into isKnown/isAllowed to close loading window

useIsCustomNodesEnabled now returns { isKnown, isAllowed } so the navbar
hides the tab conservatively (isAllowed=false while loading) while the
redirect only fires once the decision is definitive (isKnown && !isAllowed),
preventing both the non-admin flash and the single-user kickout.

* refactor(custom-nodes): extract permission derivation into shared helper

Tests now import deriveCustomNodesPermission directly instead
of mirroring the hook logic in a local simulateHook, so hook
and tests can never drift.

* Chore Knit fix

* fix(custom-nodes): purge full module subtree on uninstall

Only removing sys.modules[pack_name] left submodules cached, so
reinstall reused them and the @invocation decorators never re-
registered the nodes — the pack loaded with 0 nodes until a
full process restart. Now _purge_pack_modules strips the root
and every pack_name.* key.

* fix(custom-nodes): purge full module subtree on uninstall

Only removing sys.modules[pack_name] left submodules cached, so
reinstall reused them and the @invocation decorators never re-
registered the nodes — the pack loaded with 0 nodes until a
full process restart. Now _purge_pack_modules strips the root
and every pack_name.* key.

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-04-27 20:36:34 +00:00
Lincoln Stein eb15a4e8c5 feat(recall): support direct model reference images in recall API (#9045)
* feat(recall): support model-free reference images in recall API

The recall parameters API previously exposed only `loras`, `control_layers`,
and `ip_adapters`. This meant reference images used by architectures that
feed images directly into the main model — FLUX.2 Klein, FLUX Kontext, and
Qwen Image Edit — could not be sent through the recall endpoint at all:
they have no adapter model to resolve, so they could not ride in the
`ip_adapters` list.

This change adds a new `reference_images` field on RecallParameter that
carries only an `image_name`. The backend validates the file exists in
outputs/images and forwards the resolved metadata (width/height) in the
broadcast event. The frontend's recall handler picks the right config type
(`flux2_reference_image` / `flux_kontext_reference_image` / `ip_adapter`
fallback) via getDefaultRefImageConfig() based on the currently-selected
main model, matching the behavior of a manual drag-and-drop, and dispatches
`refImagesRecalled` with replace:false so these append rather than clobber
any adapters already applied in the same event.

Also consolidates the two existing docs under docs/contributing/RECALL_PARAMETERS/
(RECALL_PARAMETERS_API.md and RECALL_API_LORAS_CONTROLNETS_IMAGES.md) into
a single RECALL_PARAMETERS_API.md that documents the full request schema
including the new field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(recall): cover loras, control layers, and ip_adapters paths

The original recall_parameters router (PR #8758) shipped without any
unit tests for its three collection fields. This commit backfills that
coverage alongside the reference_images tests added in the previous
commit.

The resolver helpers (resolve_model_name_to_key, load_image_file,
process_controlnet_image) are monkey-patched via module-level attribute
replacement so each test can pin down a specific resolution outcome
without spinning up the model manager or an image-files service. Two
small factory helpers (make_name_to_key_stub / make_load_image_file_stub)
make that ergonomic.

New coverage:

* LoRAs — multi-entry resolution + weight/is_enabled pass-through,
  silent drop on unresolvable names, is_enabled default of True.
* Control layers — ControlNet resolution precedence, fall-through to
  T2I Adapter and Control LoRA in order, missing image gracefully
  warned-and-continued, processed_image attached when the processor
  returns data, unresolvable entries dropped.
* IP Adapters — IPAdapter-before-FluxRedux lookup order, method /
  image_influence pass-through, missing image gracefully warned-and-
  continued, unresolvable entries dropped.
* Combined happy path — full request with prompts + model + all four
  collection fields, verifying every resolved value reaches the
  broadcast payload.
* Main-model drop — an unresolvable main model is scrubbed from the
  broadcast so the frontend never receives a stale model name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(frontend): typegen

* chore: fix lint errors and typegen

* fix(test): patch ApiDependencies in auth_dependencies to fix recall tests

The patched_dependencies fixture only monkeypatched ApiDependencies in
the recall_parameters module, but the endpoint also resolves
CurrentUserOrDefault via auth_dependencies, which accesses
ApiDependencies.invoker independently. Patch both import sites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(frontend): typegen

* feat(recall): add strict mode to clear unset parameters on recall

Add a `strict` query parameter (default false) to POST recall endpoint.
When true, parameters not in the request body are reset: list fields
(loras, control_layers, ip_adapters, reference_images) become [] and
scalar fields become null, so the frontend clears stale state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(frontend): eliminate ref image doubling from race between Promise.all chains

IP adapters and model-free reference images were dispatched via two
independent Promise.all chains — one with replace:true, the other with
replace:false.  When a previous recall's promises were still in-flight
they could resolve after the clear and re-append stale entries, doubling
the list.  Combine both into a single Promise.all with one replace:true
dispatch so the race is impossible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
2026-04-27 16:11:10 -04:00
Gohsuke Shimada 0a09452fa3 feat(ui): add canvas snapshot save/restore functionality (#8978)
* feat(ui): add canvas snapshot save/restore functionality

Add ability to save and restore canvas state snapshots, allowing users
to preserve their canvas layout at any point and restore it later.
This is useful when the canvas freezes or resets unexpectedly.

Backend:
- Add get_keys_by_prefix and delete_by_key to client_state persistence
- Add corresponding API endpoints

Frontend:
- Add canvasSnapshotRestored reducer to canvasSlice
- Add useCanvasSnapshots hook for snapshot CRUD operations
- Add CanvasToolbarSnapshotMenuButton with save/restore UI
- Add i18n keys for snapshot feature
- Regenerate API schema types

Tests:
- Add tests for new client_state endpoints (prefix search, key deletion)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ui): address review feedback for canvas snapshot feature

- Preserve current modelBase on snapshot restore to prevent bbox desync
  with the active model (mirrors resetState pattern)
- Exclude snapshot restore from undo history so it cannot be accidentally
  undone
- Migrate manual fetch calls to RTKQ endpoints (clientState.ts) so
  snapshots go through the shared API transport layer with proper auth,
  session-expiry handling and sliding-window token refresh
- Validate referenced images on restore and warn when some are missing
- Detect incompatible (schema-changed) snapshots and show a specific
  error message instead of a generic failure toast
- Disable snapshot restore while the canvas is staging to prevent entity
  ID conflicts with in-progress generations
- Sort snapshot list by updated_at instead of rowid so re-saved
  snapshots appear at the top
- Add pre-flight backend reachability check before image validation to
  avoid false "missing images" warnings when offline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(ui): consolidate collectImageNames to shared canvasProjectFile utility

Remove the local collectImageNames from useCanvasSnapshots and reuse
the shared, more comprehensive version from canvasProjectFile.ts that
was introduced by the canvas project save/load feature (#8917).

Snapshots don't include global ref images, so an empty array is passed
for that parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(canvas-snapshots): escape LIKE wildcards, warn on overwrite, fix default name chars

- Escape %, _, \ in client_state prefix query to prevent accidental wildcard matching
- Confirm before overwriting an existing snapshot instead of silently replacing it
- Use - instead of / and : in the default snapshot name to avoid key separator clashes

* fix(canvas): align canvasProjectRecalled with snapshot restore pattern

Preserve modelBase, call syncScaledSize, and exclude from undo history
to avoid bbox/model desync on project load — same pattern already used
by canvasSnapshotRestored.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
2026-04-27 19:15:03 +00:00
Jonathan 29741ddb06 Fix lazy If branch pruning and skipped-parent handling in graph runtime (#9079)
* Fix lazy If branch pruning and skipped-parent handling in graph runtime

* Tighten lazy If runtime edge-case handling

* Polish lazy If runtime diagnostics and idempotency

---------

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-04-27 18:53:15 +00:00
Jonathan 3bde35be10 Align DyPE with paper (#8980) 2026-04-27 14:40:47 -04:00
Alexander Eichhorn f9f2a32e96 Feat(UI): Add LLM-powered prompt expansion and image-to-prompt features (#8899)
* Add LLM-powered prompt expansion and image-to-prompt features

Adds two new buttons to the positive prompt area:
- "Expand Prompt" uses a local TextLLM model (AutoModelForCausalLM) to expand brief prompts into detailed image generation prompts
- "Image to Prompt" uses an existing LLaVA OneVision model to generate descriptive prompts from uploaded images

Backend: new TextLLM model type with config, loader, pipeline wrapper, workflow node, and two new API endpoints (expand-prompt, image-to-prompt). Also fixes HuggingFace metadata fetch assertion error when file size is None.

Frontend: ExpandPromptButton and ImageToPromptButton components with model picker popovers, RTK Query mutations, and model type hooks. Buttons only appear when compatible models are installed.

* chore fix windows paths

* Fix device mismatch for LLM inference and add CPU-only toggle for Text LLM models

Derive the execution device from the loaded model parameters instead of
the global TorchDevice chooser so that cpu_only models no longer receive
GPU-bound inputs. Also expose the existing cpu_only setting in the
frontend Model Manager for Text LLM models.

* Harden LLM endpoints and add tests

- Bound max_tokens to 1-2048 on ExpandPromptRequest to prevent OOM
- Replace asserts with explicit type checks and proper HTTP status codes
  (404 for unknown models, 422 for wrong model type, 500 for unexpected)
- Use float32 dtype for cpu_only TextLLM models instead of global fp16
- Add 16 tests for TextLLMPipeline and API request validation

* Add Ctrl+Z undo for LLM prompt changes

Saves the previous prompt before LLM overwrites it (Expand Prompt and
Image to Prompt). Pressing Ctrl+Z in the prompt textarea restores
the original prompt. Undo state auto-expires after 30 seconds and
is cleared when the user types manually.

* Add documentation and What's New entry for LLM prompt tools

- Add docs/features/prompt-tools.md covering Expand Prompt, Image to
  Prompt, compatible models, Ctrl+Z undo, and the workflow node
- Register new doc page in mkdocs.yml under Features
- Add What's New item in en.json for the LLM Prompt Tools feature

* fix: resolve merge conflict in mkdocs.yml nav

* feat(ui): allow dragging gallery images onto prompt box for Image to Prompt

Add drop target on the positive prompt textarea so users can drag images
from the gallery directly into the prompt area. When dropped, the Image
to Prompt popover opens automatically with the image pre-loaded, ready
for description generation.

* chore typegen

* Fix typo in Z-Image Turbo diversity description

* Fix three bugs in LLM/VLM utility endpoints

Move torch.no_grad() from async endpoint into worker functions where
inference actually runs, since the context manager does not carry across
the thread boundary used by asyncio.to_thread().

Add threading.Lock around load_model() calls to serialize access to the
thread-unsafe model loader, preventing race conditions from concurrent
HTTP requests.

Catch ImageFileNotFoundException in image_to_prompt and return 404
instead of letting it fall through to the blanket 500 handler.

* Fix tokenizer validation, drag-drop dead end, and i18n for LLM prompt tools

Validate tokenizer files at model probe time instead of deferring to runtime.
Guard image drag-drop on the prompt textarea behind LLaVA model availability.
Add missing modelManager.textLLM i18n key and replace all hardcoded strings
in ImageToPromptButton and ExpandPromptButton with translation calls.

* Add unit tests for promptUndo module

* Fix typo in Z-Image Turbo diversity description

* Chore fix typegen

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-04-26 22:03:16 -04:00
Alexander Eichhorn dd5758b53d fix: SDXL DoRA LoRA fails with enable_partial_loading=true (#9063)
* fix: SDXL DoRA LoRA fails with enable_partial_loading=true

cast_to_device returns plain torch.Tensor instead of torch.nn.Parameter,
causing _aggregate_patch_parameters to replace valid weights with meta
device dummies, falsely triggering DoRA's quantization guard.

Fixes invoke-ai/InvokeAI#8624

* test: regression coverage for DoRA + partial-loading + CPU→device autocast

Adds targeted coverage for the bug fixed in a0a87212 (#8624, PR #9063):

- test_aggregate_patch_parameters_preserves_plain_tensor_with_dora:
  CPU-only unit test that feeds a plain torch.Tensor (as handed in by
  _cast_weight_bias_for_input) into _aggregate_patch_parameters with a
  DoRA patch. Pre-fix, the tensor was replaced by a meta-device dummy,
  tripping DoRA's quantization guard.

- "single_dora" variant in the patch_under_test fixture: exercises the
  full CUDA/MPS autocast hot path via
  test_linear_sidecar_patches_with_autocast_from_cpu_to_device.

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-04-25 16:33:20 +00:00
Jonathan 0531108f32 Fix graph execution state resume after JSON round-trip (#9042)
* Fix graph execution state resume after JSON round-trip

* tightened tests, refactored

* chore: ruff

---------

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-04-25 15:41:07 +00:00
Jonathan 5a0818ab36 Prevent stale queue snapshots from regressing workflow completion state (#9043)
* fix queue item ordering across events and snapshots

* clean up queue item status sequencing

* trim white-box queue sequencing tests

* format sqlite migrator tests

* clarify queue status sequencing intent

---------

Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
2026-04-23 11:05:47 -04:00
skunkworxdark b2d79dc86c feat:(model-manager) add sorting capabilities for models (#9024)
* 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>
2026-04-20 20:08:09 -04:00
Jonathan 0d7205ff79 Handle mixed-dtype mismatches in autocast linear and conv wrappers (#9006)
* Handle CustomConv2d bias dtype mismatches

* Fix mixed-dtype autocast regressions

* Format custom_conv2d with ruff
2026-04-20 20:31:35 +00:00
CypherNaugh_0x 9deb545cc1 External models (Gemini Nano Banana & OpenAI GPT Image) (#8633) (#8884)
* 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>
2026-04-20 17:13:26 +00:00
Josh Corbett 9643b1385f Docs Overhaul (#8896)
* feat(docs): new docs scaffold

* feat(docs): update alternate launchers section

* feat(docs): add contributor section

* fix(docs): update description of lynxhub launcher mention

* feat(docs): add more docs

* feat(docs): setup index page

* feat(docs): add more docs, rewrote a few pages

* feat(docs): add todo

* feat(docs): set up internationalization

* fix(docs): admonition typo

* feat(docs): add invoke styles

* feat(docs): add more invoke styling, revamp splash page, remove theme
switcher

* fix(docs): expressive code sh styles without title

* chore(docs): cleanup readme

* chore(docs): add new github pages workflow

* fix(docs): remove base path

* chore(docs): add initial translations CI, powered by Crowdin

* feat(docs): upgrade astro

* feat(docs): enhance new contributor guide

* feat(docs): various enhancements

- improve homepage;
- enhance some docs pages;
- override some layout components;
- enhance interactivity and qol styling;
- create new download page + component;
- add llms.txt;
- remove unused logo component;

* feat(docs): isolate new docs

* style(docs): use md reference links over utility links

* chore(docs): specify package manager

* feat(docs): releases page

* feat(docs): add page context menus

* feat(docs): sort workflows sidebar items

* fix(docs): relative links on homepage

* feat(docs): add text tool and recall params api guides

* feat(docs): fix faq links, create models concept page

* chore(docs): set CI to new dir, update deployment url

* feat(docs): generate settings and api json for pages

- update deploy script
- add api and settings component to render generated json
- increase page content width

* style(docs): remove relative path for component import

* fix(docs): resolve tests by regenerating json

* fix(docs): fixing the test for real this time

- sorts openapi output map required field
- missing `__name__` attributes
- resolved components name keyerror

* feat(docs): finish 'adding nodes' page

* feat(docs): upgrade astro + starlight, add link tester

* chore(docs): upgrade astro

* feat(docs): add prompting guides

* fix(docs): generated openapi

* fix(docs): ci node version

* fix(docs): invalid links

* fix(docs): md aside formatting

* feat(docs): reorder 'configuration' category

* feat(docs): change contributor checklist to steps list

* chore(docs): upgrade deps

* feat(docs): splash page image styling

* feat(docs): add gallery marquee to homepage

* feat(docs): add splash page marquee gallery

* feat(docs): remove openapi generation

* fix(docs): regenerate settings json

* fix(docs): json generation test

---------

Co-authored-by: joshistoast <me@joshcorbett.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-04-16 22:03:05 -04:00
Lincoln Stein e252a5bb47 fix(multiuser): make preexisting workflows visible after migration (#9049) 2026-04-14 12:27:14 -04:00
Lincoln Stein 33ec16deb4 Feature: Shared/private workflows and image boards in multiuser mode (#9018)
* 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>
2026-04-13 17:27:20 -04:00
Lincoln Stein b42274a57e Feat[model support]: Qwen Image — full pipeline with edit, generate LoRA, GGUF, quantization, and UI (#9000) 2026-04-12 14:39:13 +02:00
Jonathan d4104be0b8 graph.py refactoring and If node optimization (#9030)
* test: add if-node execution coverage

* feat: short-circuit if-node branch execution

* test: cover iterated if-node pruning

* style: apply ruff fixes for if-node work

* refactor: track prepared exec node metadata

* fix: defer iterated if branches until resolution

* refactor: extract prepared exec registry

* refactor: extract if branch scheduler

* refactor: extract execution materializer

* refactor: extract execution scheduler

* refactor: extract execution runtime

* refactor: clarify if branch resolution

* refactor: clarify execution materialization

* docs: describe graph execution helpers

* refactor: clarify execution runtime

* refactor: clarify execution scheduling

* refactor: clarify iteration node selection

* docs: describe execution materializer flow

* refactor: clarify collector validation

* refactor: clarify iterator validation

* refactor: clarify graph validation flow

* docs: update shared graph design overview

* chore: typegen

* fix: harden if-node scheduler edge cases
2026-04-09 21:38:40 -04:00
Jonathan ee600973ed Broaden text encoder partial-load recovery (#9034) 2026-04-09 20:09:40 -04:00
4pointoh f0d09c34a8 feat: add Anima model support (#8961)
* 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>
2026-04-09 12:04:11 -04:00
Jonathan e6f2980d7c Added If node and ability to link an Any output to a node input if cardinality matches (#8869)
* Added If node

* Added stricter type checking on inputs

* feat(nodes): make if-node type checks cardinality-aware without loosening global AnyField

* chore: typegen
2026-04-06 03:26:26 +00:00
Lincoln Stein 01c67c5468 Fix (multiuser): Ask user to log back in when security token has expired (#9017)
* 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>
2026-04-05 23:11:44 -04:00
Jonathan 6963cd97ba Fix SIGINT shutdown during active inference (#8993) 2026-03-28 18:35:18 -04:00
Jonathan f7aa5fcbbf Add chaining to Collect node (#8933)
* Add chained collect node

* test(frontend): align parseSchema fixtures with collect v1.1 and normalize undefined fields in assertions

* fix(nodes): block collect-to-collect links when inferred item types differ

---------

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-03-24 01:39:52 +00:00
Jonathan dc5007fe95 Fix/model cache Qwen/CogView4 cancel repair (#8959)
* Repair partially loaded Qwen models after cancel to avoid device mismatches

* ruff

* Repair CogView4 text encoder after canceled partial loads

* Avoid MPS CI crash in repair regression test

* Fix MPS device assertion in repair test
2026-03-15 10:04:15 -04:00
Lincoln Stein cd47b3baf7 Feature: Make strict password checking optional (#8957)
* feat: add strict_password_checking config option to relax password requirements

- Add `strict_password_checking: bool = Field(default=False)` to InvokeAIAppConfig
- Add `get_password_strength()` function to password_utils.py (returns weak/moderate/strong)
- Add `strict_password_checking` field to SetupStatusResponse API endpoint
- Update users_base.py and users_default.py to accept `strict_password_checking` param
- Update auth.py router to pass config.strict_password_checking to all user service calls
- Create shared frontend utility passwordUtils.ts for password strength validation
- Update AdministratorSetup, UserProfile, UserManagement components to:
  - Fetch strict_password_checking from setup status endpoint
  - Show colored strength indicators (red/yellow/blue) in non-strict mode
  - Allow any non-empty password in non-strict mode
  - Maintain strict validation behavior when strict_password_checking=True
- Update SetupStatusResponse type in auth.ts endpoint
- Add passwordStrength and passwordHelperRelaxed translation keys to en.json
- Add tests for new get_password_strength() function

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Changes before error encountered

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(backend): docstrings

* chore(frontend): typegen

---------

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: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-03-10 18:22:47 -04:00
Jonathan 62b7c7a6e8 Added SQL injection tests (#8873)
* Added SQL injection tests

* Updated tests after multi-user merge

* ruff:format

---------

Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-03-07 02:07:14 +00:00
Lincoln Stein c7bdaf93b2 Fix: Shut down the server with one keyboard interrupt (#94) (#8936)
* Fix: Kill the server with one keyboard interrupt (#94)

* Initial plan

* Handle KeyboardInterrupt in run_app to allow single Ctrl+C shutdown

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Force os._exit(0) on KeyboardInterrupt to avoid hanging on background threads

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>

Fix graceful shutdown to wait for download/install worker threads (#102)

* Initial plan

* Replace os._exit(0) with ApiDependencies.shutdown() on KeyboardInterrupt

Instead of immediately force-exiting the process on CTRL+C, call
ApiDependencies.shutdown() to gracefully stop the download and install
manager services, allowing active work to complete or cancel cleanly
before the process exits.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Make stop() idempotent in download and model install services

When CTRL+C is pressed, uvicorn's graceful shutdown triggers the FastAPI
lifespan which calls ApiDependencies.shutdown(), then a KeyboardInterrupt
propagates from run_until_complete() hitting the except block which tries
to call ApiDependencies.shutdown() a second time.

Change both stop() methods to return silently (instead of raising) when
the service is not running. This handles:
- Double-shutdown: lifespan already stopped the services
- Early interrupt: services were never fully started

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>

Fix shutdown hang on session processor thread lock (#108)

* Initial plan

* Fix shutdown hang: wake session processor thread on stop() and mark daemon

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>

* Fix: shut down asyncio executor on KeyboardInterrupt to prevent post-generation hang (#112)

Fix: cancel pending asyncio tasks before loop.close() to suppress destroyed-task warnings
Fix: suppress stack trace when dispatching events after event loop is closed on shutdown
Fix: cancel in-progress generation on stop() to prevent core dump during mid-flight Ctrl+C

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: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
2026-03-05 22:01:40 -05:00
DustyShoe 6fe7910a90 fix(model-install): persist remote access_token for resume after restart (#8932)
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-03-02 02:44:21 +00:00
Lincoln Stein 146b936844 feat(multiuser mode): Support multiple isolated users on same backend (#8822)
* Add comprehensive multi-user support specification and implementation plan

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Clarify Python tooling transition state

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add executive summary for multi-user support specification

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Reorganize multiuser docs into subfolder and update with approved design decisions

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* docs: fix mkdocs syntax issues

* Fix Z-Image VAE operations not reserving working memory for OOM prevention (#8)

* Initial plan

* Fix Z-Image VAE encode/decode to request working memory

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add test for Z-Image working memory estimation

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix unit test: only set config attribute for AutoencoderKL, not FluxAutoEncoder

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* style: fix ruff errors

* Fix test: use model_construct to bypass Pydantic validation for mock fields

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(ruff): fix ruff errors

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* Phase 1: Add multi-user database schema and authentication foundation (#6)

* Initial plan

* Add Phase 1: Database schema, authentication utilities, and user service

- Added dependencies: passlib[bcrypt], python-jose[cryptography], email-validator
- Created migration_25 for multi-user support database schema
  - users table with authentication fields
  - user_sessions, user_invitations, shared_boards tables
  - Added user_id columns to boards, images, workflows, session_queue, style_presets
  - Created system user for backward compatibility
- Implemented authentication utilities
  - Password hashing and validation (password_utils.py)
  - JWT token generation and verification (token_service.py)
- Implemented user service
  - Abstract base class (users_base.py)
  - SQLite implementation (users_default.py)
  - Full CRUD operations for users
  - Authentication and admin management
- Added comprehensive tests for all components
- All code passes ruff linting and mypy type checking

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Refactor user service to use transaction pattern and improve security documentation

- Changed all database operations to use self._db.transaction() context manager
- Follows the same pattern as other services like board_records
- Improves thread safety and transaction management
- Added detailed security warnings for the hardcoded JWT secret key
- Documented that it must be moved to config before production
- Fixed whitespace linting issues
- All tests still pass with the refactored code

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix system user email to use valid domain

- Changed system user email from system@invokeai.local to system@system.invokeai
- .local domains are rejected by email-validator as special-use/reserved
- All end-to-end tests now pass successfully

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix test failures: password length, migration chain, and table existence checks

- Fixed bcrypt 72-byte password limit by truncating long passwords in hash_password and verify_password
- Fixed test_migration_25 to use callback directly instead of migrator to avoid fragmented chain error
- Updated system user email assertion in test from @invokeai.local to @system.invokeai
- Added table existence checks for boards, images, workflows, and session_queue before altering them
- This prevents "no such table" errors when migration runs in environments without all tables

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* (chore) fix ruff errors

* (chore) fix ruff errors (2)

* Fix test failures: use _conn instead of conn and improve password truncation

- Fixed test_user_service.py to use db._conn instead of db.conn (SqliteDatabase uses private _conn)
- Simplified password truncation logic in password_utils.py for better clarity
- Ensured UTF-8 decoding handles truncated bytes properly with errors="ignore"

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(uv): updated uv lockfile

* Fix bcrypt password length errors by configuring passlib properly

- Added bcrypt__relax_truncate_checks=True to CryptContext to prevent errors on long passwords
- Removed min_length=8 constraint from pydantic models to allow service-level validation
- Service-level validation provides better error messages and more control
- Manual truncation code kept as safety net for passwords >72 bytes

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix bcrypt parameter name: use truncate_error instead of relax_truncate_checks

- Changed bcrypt__relax_truncate_checks=True to bcrypt__truncate_error=False
- The correct passlib parameter is truncate_error (not relax_truncate_checks)
- Setting it to False allows passwords >72 bytes without raising an error

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* fix(passwords) downgrade bcrypt to work with current passlib

* chore(uv): update locks file

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* Implement Phase 2: Authentication Service with JWT and FastAPI Integration (#11)

* Initial plan

* Add Phase 2 authentication service - auth dependencies, router, and service integration

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Address code review feedback - add token expiration constants and improve documentation

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix email validation to allow special-use domains like .local for testing

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix system user to not be admin - allows /auth/setup to work on fresh database

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore: typegen

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* Add Phase 3 integration tests and documentation for authentication middleware (#15)

* Initial plan

* Add Phase 3 integration tests for auth router endpoints

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add Phase 3 completion documentation and testing guide

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore: 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: Lincoln Stein <lincoln.stein@gmail.com>

* chore: fix unresolved merged conflict markers

* Phase 4: Add multi-tenancy to boards and session queue services (#17)

* Initial plan

* Update boards service for multi-user support

- Add user_id parameter to board creation and filtering
- Update board_records base and SQLite implementation to filter by user
- Include shared boards and public boards in user queries
- Update boards router to require authentication
- Update invocation context to pass user_id from queue item
- Add user_id field to SessionQueueItem model

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Update session_queue service for multi-user support

- Add user_id parameter to enqueue_batch method
- Update ValueToInsertTuple to include user_id
- Update prepare_values_to_insert to accept and use user_id
- Update SQL INSERT statements to include user_id column
- Update session_queue router to require authentication
- Update retry_items_by_id to preserve user_id when retrying

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add authentication imports to images, workflows, and style_presets routers

- Import CurrentUser dependency in all three routers
- Add CurrentUser parameter to upload_image endpoint
- Prepare for further authentication requirements in these services

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add multiuser tests and Phase 4 documentation

- Create test_boards_multiuser.py with authentication tests
- Verify board operations require authentication
- Test board creation and listing with auth tokens
- Add comprehensive Phase 4 implementation summary
- Document all changes, data flows, and security considerations

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add authentication to remaining board endpoints

- Add CurrentUser to get_board endpoint
- Add CurrentUser to update_board endpoint
- Add CurrentUser to delete_board endpoint
- Ensures all board operations require authentication
- Addresses code review feedback

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Feature(image boards): Implement per-user board isolation

- Complete verification report with all checks passed
- Document code quality, security, and testing results
- List all achievements and sign-off criteria
- Mark phase as READY FOR MERGE

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore: ruff

* chore: resolve conflicts in z_image_working_memory test

* chore: 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: Lincoln Stein <lincoln.stein@gmail.com>

* feat: Implement Phase 5 of multiuser plan - Frontend authentication (#19)

* Phase 5: Implement frontend authentication infrastructure

- Created auth slice with Redux state management for authentication
- Created auth API endpoints (login, logout, setup, me)
- Created LoginPage component for user authentication
- Created AdministratorSetup component for initial admin setup
- Created ProtectedRoute wrapper for route authentication checking
- Updated API configuration to include Authorization headers
- Installed and configured react-router-dom for routing
- Updated App component with authentication routes
- All TypeScript checks passing

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(style): prettier, typegen and add convenience targets to makefile

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* feat: Implement Phase 6 frontend UI updates - UserMenu and admin restrictions

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

docs: Add comprehensive testing and verification documentation for Phase 6

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

docs: Add Phase 6 summary document

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* feat: Add user management script for testing multiuser features

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* feat: Implement read-only model manager access for non-admin users

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

feat: Add admin authorization to model management API endpoints

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

docs: Update specification and implementation plan for read-only model manager

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Phase 7: Comprehensive testing and security validation for multiuser authentication (#23)

* Initial plan

* Phase 7: Complete test suite with 88 comprehensive tests

- Add password utils tests (31 tests): hashing, verification, validation
- Add token service tests (20 tests): JWT creation, verification, security
- Add security tests (13 tests): SQL injection, XSS, auth bypass prevention
- Add data isolation tests (11 tests): multi-user data separation
- Add performance tests (13 tests): benchmarks and scalability
- Add comprehensive testing documentation
- Add phase 7 verification report

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* bugfix(backend): Fix issues with authentication token expiration handling

- Remove time.sleep from token uniqueness test (use different expiration instead)
- Increase token expiration test time from 1 microsecond to 10 milliseconds
- More reliable test timing to prevent flakiness

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Add Phase 7 summary documentation

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Fix test_performance.py missing logger fixture

Add missing logger fixture to test_performance.py that was causing test failures.
The fixture creates a Logger instance needed by the user_service fixture.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Add board isolation issue specification document

Document the three board isolation issues that need to be addressed:
1. Board list not updating when switching users
2. "Uncategorized" board shared among users
3. Admin cannot access all users' boards

Includes technical details, implementation plan, and acceptance criteria.
This document will be used to create a separate GitHub issue and PR.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Fix test failures in Phase 7 tests

- Fix board service API calls to use enum values (BoardRecordOrderBy, SQLiteDirection) instead of strings
- Fix board deletion test to use get_dto() instead of non-existent get() method
- Add exception handling to verify_password() for invalid hash formats
- Update SQL injection test to accept both 401 and 422 status codes (Pydantic validation)

All fixes ensure tests match actual API signatures and handle edge cases properly.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Fix token forgery test to properly decode and modify JWT payload

The test was attempting to modify the JWT payload by string replacement on the
base64-encoded data, which doesn't work since "false" doesn't appear literally
in the base64 encoding. Fixed to:
- Properly decode the base64 payload
- Parse the JSON
- Modify the is_admin field
- Re-encode the payload
- Create a forged token with the modified payload and original signature
- Verify it's rejected with 401 status

This properly tests that JWT signature verification prevents token forgery.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Fix test_verify_expired_token timing issue

Increased token expiration from 10ms to 100ms and wait time from 20ms to 200ms
to make the test more reliable. The previous timing was too tight and could
cause race conditions where the token hadn't actually expired yet when verified.

This ensures the test reliably validates that expired tokens are properly rejected.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Fix token verification to properly validate signatures and expiration

The verify_token() function was not explicitly enabling JWT verification options,
causing it to accept expired tokens and tokens with modified signatures/payloads.

Fixed by explicitly setting verification options:
- verify_signature: True (reject modified tokens)
- verify_exp: True (reject expired tokens)
- verify_nbf: True (verify "not before" claim)
- verify_iat: True (verify "issued at" claim)
- verify_aud: False (we don't use audience claims)

Also added catch-all exception handler for other validation errors.

This ensures all token security tests pass properly.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Revert to default python-jose verification behavior

Removed explicit options parameter from jwt.decode() call since python-jose
verifies signatures and expiration by default. The explicit options may have
been causing issues or conflicts with the library's default behavior.

Added comments clarifying that python-jose defaults are secure and handle
verification properly. Need user to test and provide debugging output if
issues persist.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Fix JWT expiration verification for python-jose 3.5.0

python-jose 3.5.0 has a bug where expiration verification doesn't work properly
by default. The jwt.decode() function is not rejecting expired tokens even when
they should be rejected.

Workaround implemented:
1. First, get unverified claims to extract the 'exp' timestamp
2. Manually check if current time >= exp time (token is expired)
3. Return None immediately if expired
4. Then verify signature with jwt.decode() for tokens that aren't expired

This ensures:
- Expired tokens are properly rejected
- Signature verification still happens for non-expired tokens
- Modified tokens are rejected due to signature mismatch

All three failing tests should now pass:
- test_verify_expired_token
- test_verify_token_with_modified_payload
- test_token_signature_verification

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix race condition in token verification - verify signature before expiration

Changed the order of verification in verify_token():
1. First verify signature with jwt.decode() - rejects modified/forged tokens
2. Then manually check expiration timestamp

Previous implementation checked expiration first using get_unverified_claims(),
which could cause a race condition where:
- Token with valid payload but INVALID signature would pass expiration check
- If expiration check happened to return None due to timing, signature was never verified
- Modified tokens could be accepted intermittently

New implementation ensures signature is ALWAYS verified first, preventing any
modified tokens from being accepted, while still working around the python-jose
3.5.0 expiration bug by manually checking expiration after signature verification.

This eliminates the non-deterministic test failures in test_verify_token_with_modified_payload.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(app): 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: Lincoln Stein <lincoln.stein@gmail.com>

* Backend: Add admin board filtering and uncategorized board isolation

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix intermittent token service test failures caused by Base64 padding (#32)

* Initial plan

* Fix intermittent token service test failures due to Base64 padding

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Address code review: add constants for magic numbers in tests

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(tests): 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: Lincoln Stein <lincoln.stein@gmail.com>

* Implement user isolation for session queue and socket events (WIP - debugging queue visibility) (#30)

* Add user isolation for queue events and field values filtering

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add user column to queue list UI

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add field values privacy indicator and implementation documentation

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Allow all users to see queue item status events while keeping invocation events private

Co-authored-by: lstein <111189+lstein@users.noreply.github.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: Lincoln Stein <lincoln.stein@gmail.com>

* Fix Queue tab not updating for other users in real-time (#34)

* Initial plan

* Add SessionQueueItemIdList invalidation to queue socket events

This ensures the queue item list updates in real-time for all users when
queue events occur (status changes, batch enqueued, queue cleared).

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add SessionQueueItemIdList invalidation to queue_items_retried event

Ensures queue list updates when items are retried.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Improve queue_items_retried event and mutation invalidation

- Add individual item invalidation to queue_items_retried event handler
- Add SessionQueueStatus and BatchStatus tags to retryItemsById mutation
- Ensure consistency between event handler and mutation invalidation patterns

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add privacy check for batch field values in Queue tab

Displays "Hidden for privacy" message for non-admin users viewing
queue items they don't own, instead of showing the actual field values.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* i18n(frontend): change wording of queue values suppressed message

* Add SessionQueueItemIdList cache invalidation to queue events

Ensures real-time queue updates for all users by invalidating the
SessionQueueItemIdList cache tag when queue events occur.

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>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>

* Fix multiuser information leakage in Queue panel detail view (#38)

* Initial plan

* Implement multiuser queue information leakage fix

- Backend: Update sanitize_queue_item_for_user to clear session graph and workflow
- Frontend: Add permission check to disable detail view for unauthorized users
- Add test for sanitization logic
- Add translation key for permission denied message

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix prettier formatting for QueueItemComponent

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Address code review feedback

- Move Graph and GraphExecutionState imports to top of file
- Remove dependency on test_nodes in sanitization test
- Create minimal test invocation directly in test file

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Address additional code review feedback

- Create shallow copy to avoid mutating original queue_item
- Extract 'system' user_id to constant (SYSTEM_USER_ID)
- Add constant to both backend and frontend for consistency

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix pydantic validation error in test fixture

Add required timestamp fields (created_at, updated_at, started_at, completed_at) to SessionQueueItem in test fixture

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>

* fix(queue): Enforce user permissions for queue operations in multiuser mode (#36)

* Initial plan

* Add backend authorization checks for queue operations

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix linting issues in authorization changes

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add frontend authorization checks for queue operations

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add access denied messages for cancel and clear operations

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix access denied messages for all cancel/delete operations

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix merge conflict duplicates in QueueItemComponent

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(frontend): typegen

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* fix(multiuser): Isolate client state per user to prevent data leakage (#40)

* Implement per-user client state storage to fix multiuser leakage

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix: Make authentication optional for client_state endpoints to support single-user mode

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Clear params state on logout/login to prevent user data leakage

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>

* feat(queue): show user/total pending jobs in multiuser mode badge (#43)

* Initial plan

* Add multiuser queue badge support - show X/Y format in multiuser mode

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Format openapi.json with Prettier

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Address code review feedback - optimize DB queries and improve code clarity

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* translationBot(ui): update translation files (#8767)

Updated by "Cleanup translation files" hook in Weblate.


Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI

* Limit automated issue closure to bug issues only (#8776)

* Initial plan

* Add only-labels parameter to limit automated issue closure to bugs only

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>

* fix(multiuser): Isolate client state per user to prevent data leakage (#40)

* Implement per-user client state storage to fix multiuser leakage

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix: Make authentication optional for client_state endpoints to support single-user mode

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Clear params state on logout/login to prevent user data leakage

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>

* Initial plan

* chore(backend) ruff & typegen

* Fix real-time badge updates by invalidating SessionQueueStatus on queue events

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>
Co-authored-by: Weblate (bot) <hosted@weblate.org>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>

* Convert session queue isolation logs from info to debug level

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add JWT secret storage in database and app_settings service

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add multiuser configuration option with default false

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Update token service tests to initialize JWT secret

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix app_settings_service to use proper database transaction pattern

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(backend): typegen and ruff

* chore(docs): update docstrings

* Fix frontend to bypass authentication in single-user mode

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix auth tests to enable multiuser mode

Auth tests were failing because the login and setup endpoints now return 403 when multiuser mode is disabled (the default). Updated test fixtures to enable multiuser mode for all auth-related tests.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix model manager UI visibility in single-user mode

Model manager UI for adding, deleting and modifying models is now:
- Visible in single-user mode (multiuser: false, the default)
- Hidden in multiuser mode for non-admin users
- Visible in multiuser mode for admin users

Created useIsModelManagerEnabled hook that checks multiuser_enabled status
and returns true when multiuser is disabled OR when user is admin.

Updated all model manager components to use this hook instead of direct
is_admin checks.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(backend): ruff

* chore(frontend): typegen

* Fix TypeScript lint errors

- Added multiuser_enabled field to SetupStatusResponse type in auth.ts
- Removed unused user variable reference in MainModelDefaultSettings.tsx

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix test_data_isolation to enable multiuser mode

Added fixture to enable multiuser mode for data isolation tests, similar to other auth tests.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Redirect login and setup pages to app in single-user mode

When multiuser mode is disabled, the LoginPage and AdministratorSetup components now redirect to /app instead of showing the login/setup forms. This prevents users from being stuck on the login page after browser refresh in single-user mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix test_auth.py to initialize JWT secret

Added setup_jwt_secret fixture to test_auth.py to initialize the JWT secret before running auth tests. This fixture was missing, causing token creation/verification to fail in auth router tests.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Prevent login form flash in single-user mode

Show loading spinner instead of login/setup forms when multiuser mode is disabled or when redirecting is about to happen. This prevents the unattractive flash of the login dialog when refreshing the page in single-user mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix board and queue operations in single-user mode

Changed boards, session_queue, and images routers to use CurrentUserOrDefault instead of CurrentUser. This allows these endpoints to work without authentication when multiuser mode is disabled (default), fixing the issue where users couldn't create boards or add jobs to the queue in single-user mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add user management utilities and rename add_user.py

Created three user management scripts in the scripts/ directory:
- useradd.py (renamed from add_user.py) - add users with admin privileges
- userdel.py - delete users by email address with confirmation
- usermod.py - modify user details (name, password, admin status)

All scripts support both CLI and interactive modes for flexibility.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix ESLint errors in frontend code

- Fixed brace-style issue in App.tsx (else-if on same line)
- Removed unused useAppSelector imports from model manager components
- Fixed import sorting in ControlAdapterModelDefaultSettings.tsx

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Add userlist.py script for viewing database users

Created scripts/userlist.py to display all users in the database. Supports:
- Table format (default): Shows ID, email, display name, admin status, and active status
- JSON format (--json flag): Outputs user data as JSON for scripting/automation

Example usage:
  python scripts/userlist.py          # Table view
  python scripts/userlist.py --json   # JSON output

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix test_boards_multiuser.py test failures

Fixed test failures caused by ApiDependencies.invoker not being set properly:
- Added setup_jwt_secret fixture to initialize JWT secret for token generation
- Added enable_multiuser_for_tests fixture that sets ApiDependencies.invoker as a class attribute
- Updated tests to use enable_multiuser_for_tests fixture to ensure ApiDependencies is properly configured
- Removed MockApiDependencies class approach in favor of directly setting the class attribute

This fixes the AttributeError and ensures all tests have the proper setup.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(backend): ruff

* Fix userlist.py SqliteDatabase initialization

Fixed AttributeError in userlist.py where SqliteDatabase was being passed the config object instead of config.db_path. The constructor expects a Path object (db_path) as the first argument, not the entire config object.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix test_boards_multiuser.py by adding app_settings service to mock

Added AppSettingsService initialization to the mock_services fixture in tests/conftest.py. The test was failing because setup_jwt_secret fixture expected mock_invoker.services.app_settings to exist, but it wasn't being initialized in the mock services.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* bugfix(scripts): fix crash in userlist.py script

* Fix test_boards_multiuser.py JWT secret initialization

Fixed setup_jwt_secret fixture to call set_jwt_secret() directly instead of trying to access non-existent app_settings service. Removed incorrect app_settings parameter from InvocationServices initialization in tests/conftest.py since app_settings is not an attribute of InvocationServices.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix CurrentUserOrDefault to require auth in multiuser mode

Changed get_current_user_or_default to raise HTTP 401 when multiuser mode is enabled and credentials are missing, invalid, or the user is inactive. This ensures that board/queue/image operations require authentication in multiuser mode while still working without authentication in single-user mode (default).

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(front & backend): ruff and lint

* Add AdminUserOrDefault and fix model settings in single-user mode

Created AdminUserOrDefault dependency that allows admin operations to work without authentication in single-user mode while requiring admin privileges in multiuser mode. Updated model_manager router to use AdminUserOrDefault for update_model_record, update_model_image, and reidentify_model endpoints. This fixes the "Missing authentication credentials" error when saving model default settings in single-user mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix model manager operations in single-user mode

Changed all model manager endpoints from AdminUser to AdminUserOrDefault to allow model installation, deletion, conversion, and cache management operations to work without authentication in single-user mode. This fixes the issue where users couldn't add or delete models in single-user mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix syntax error in model_manager.py

Added Depends(AdminUserOrDefault) to all AdminUserOrDefault dependency parameters to fix Python syntax error where parameters without defaults were following parameters with defaults. Imported Depends from fastapi.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix FastAPI dependency injection syntax error

Removed type annotations from AdminUserOrDefault dependency parameters. FastAPI doesn't allow both Annotated type hints and = Depends() default values together. Changed from `_: AdminUserOrDefault = Depends(AdminUserOrDefault)` to `_ = Depends(AdminUserOrDefault)` throughout model_manager.py.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix delete_model endpoint parameter annotation

Changed delete_model endpoint to use Annotated[str, Path(...)] instead of str = Path(...) to match FastAPI's preferred syntax and fix the 422 Unprocessable Entity error when deleting models in single-user mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix parameter annotations for all AdminUserOrDefault endpoints

Changed all endpoints using AdminUserOrDefault from old syntax (key: str = Path(...)) to FastAPI's preferred Annotated syntax (key: Annotated[str, Path(...)]). This fixes 422 Unprocessable Entity errors when updating model settings and deleting models in single-user mode. Updated endpoints: delete_model_image, install_model, install_hugging_face_model, and convert_model.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Revert to correct AdminUserOrDefault usage pattern

Reverted model_manager.py to state before commit c47af8f and reapplied AdminUserOrDefault correctly. Changed from `_: AdminUser` to `current_admin: AdminUserOrDefault` using the same pattern as boards.py (`current_user: CurrentUserOrDefault`). This fixes all 422 errors in single-user mode while maintaining proper admin authentication in multiuser mode.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix parameter order for AdminUserOrDefault in model manager

Moved current_admin: AdminUserOrDefault parameter before all parameters with default values in model_manager.py endpoints. Python requires parameters without defaults to come before parameters with defaults. Fixed 8 endpoints: delete_model, bulk_delete_models, delete_model_image, install_model, install_hugging_face_model, cancel_model_install_job, convert_model, and do_hf_login.

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(frontend): typegen

* chore(frontend): typegen again

* Docs(app): Comprehensive Documentation of Multiuser Features (#50)

* Implement Phase 8: Complete multiuser documentation (user, admin, and API guides)

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Update multiuser documentation for single-user mode and CLI scripts

- Document multiuser config option (true/false/absent)
- Explain single-user mode behavior (no login required)
- Document mode switching and legacy "system" user
- Update user management to reference CLI scripts (useradd, userdel, usermod, userlist)
- Note that web UI for user management is coming in future release
- Add adaptive API client example for both modes

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* docs(multiuser): bring user guide documentation up to date

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* docs(app): update multiuser documentation

* bugfix(app): fix misaligned database migration calls

* chore(tests): update migration test to accommodate resequencing of migrations

* fix(frontend): prevent caching of static pages

* chore(backend): ruff

* fix(backend): fix incorrect migration import

* Fix: Admin users can see image previews from other users' generations (#61)

* Initial plan

* Fix: strip image preview from InvocationProgressEvent sent to admin room

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore: ruff

* fix(backend): add migration_29 file

* chore(tests): fix migration_29 test

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* fix(queue): System user queue items show blank instead of `<hidden>` for non-admin users (#63)

* Initial plan

* fix(queue): System user queue items show blank instead of `<hidden>` for non-admin users

Co-authored-by: lstein <111189+lstein@users.noreply.github.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: Lincoln Stein <lincoln.stein@gmail.com>

* Hide "Use Cache" checkbox in node editor for non-admin users in multiuser mode (#65)

* Initial plan

* Hide use cache checkbox for non-admin users 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>

* Fix node loading hang when invoke URL ends with /app (#67)

* Initial plan

* Fix node loading hang when URL ends with /app

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>

* Move user management scripts to installable module with CLI entry points (#69)

* Initial plan

* Add user management module with invoke-useradd/userdel/userlist/usermod entry points

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* chore(util): remove superceded user administration scripts

---------

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: Lincoln Stein <lincoln.stein@gmail.com>

* chore(backend): reorganized migrations, but something still broken

* Fix migration 28 crash when `client_state.data` column is absent (#70)

* Initial plan

* Fix migration 28 to handle missing data column in client_state table

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>

* Consolidate multiuser DB migrations 27–29 into a single migration step (#71)

* Initial plan

* Consolidate migrations 27, 28, and 29 into a single migration step

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>

* Add `--root` option to user management CLI utilities (#81)

* Initial plan

* Add --root option to user management CLI utilities

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>

* Fix queue clear() endpoint to respect user_id for multi-tenancy (#75)

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

Add tests for session queue clear() user_id scoping

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

chore(frontend): rebuild typegen

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* fix: use AdminUserOrDefault for pause and resume queue endpoints (#77)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* fix: queue pause/resume buttons disabled in single-user mode (#83)

In single-user mode, currentUser is never populated (no auth), so
`currentUser?.is_admin ?? false` always returns false, disabling the buttons.

Follow the same pattern as useIsModelManagerEnabled: treat as admin
when multiuser mode is disabled, and check is_admin flag when enabled.

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* fix: enforce board ownership checks in multiuser mode (#84)

- get_board: verify current user owns the board (or is admin), return 403 otherwise
- update_board: verify ownership before updating, 404 if not found, 403 if unauthorized
- delete_board: verify ownership before deleting, 404 if not found, 403 if unauthorized
- list_all_board_image_names: add CurrentUserOrDefault auth and ownership check for non-'none' board IDs



test: add ownership enforcement tests for board endpoints in multiuser mode

- Auth requirement tests for get, update, delete, and list_image_names
- Cross-user 403 forbidden tests (non-owner cannot access/modify/delete)
- Admin bypass tests (admin can access/update/delete any user's board)
- Board listing isolation test (users only see their own boards)
- Refactored fixtures to use monkeypatch (consistent with other test files)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix: Clear auth state when switching from multiuser to single-user mode (#86)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix race conditions in download queue and model install service (#98)

* Initial plan

* Fix race conditions in download queue and model install service

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>

---------

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: Weblate (bot) <hosted@weblate.org>
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
2026-02-26 23:47:25 -05:00
Lincoln Stein dfc66b7142 Feature: Add FLUX.2 LOKR model support (detection and loading) (#8909)
* Add FLUX.2 LOKR model support (detection and loading) (#88)

Fix BFL LOKR models being misidentified as AIToolkit format



Fix alpha key warning in LOKR QKV split layers

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>

* Fix BFL→diffusers key mapping for non-block layers in FLUX.2 LoRA/LoKR

BFL's FLUX.2 model uses different names than diffusers' Flux2Transformer2DModel
for top-level modules (embedders, modulations, output layers). The existing
conversion only handled block-level renames (double_blocks→transformer_blocks),
causing "Failed to find module" warnings for non-block LoRA keys like img_in,
txt_in, modulation.lin, time_in, and final_layer.

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
2026-02-27 00:45:13 +00:00
Harikrishna KP ddaa12b0fd Fix bare except clauses and mutable default arguments (#8871)
* Fix bare except clauses and mutable default arguments

Replace bare `except:` with `except Exception:` in sqlite_database.py
and mlsd/utils.py to avoid catching KeyboardInterrupt and SystemExit,
which can prevent graceful shutdowns and mask critical errors (PEP 8
E722).

Replace mutable default arguments (lists) with None in
imwatermark/vendor.py to prevent shared state between calls, which
is a known Python gotcha that can cause subtle bugs when default
mutable objects are modified in place.

* add tests for mutable defaults and bare except fixes

* Simplify exception propagation tests

* Remove unused db initialization in error propagation tests

Removed unused database initialization in tests for KeyboardInterrupt and SystemExit.

---------

Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
2026-02-22 23:25:15 -05:00