Skip to content

Add board visibility (private/shared/public) for multiuser mode#122

Open
Copilot wants to merge 30 commits intolstein/feature/workflow-isolation-in-multiuser-modefrom
copilot/enhancement-allow-shared-boards
Open

Add board visibility (private/shared/public) for multiuser mode#122
Copilot wants to merge 30 commits intolstein/feature/workflow-isolation-in-multiuser-modefrom
copilot/enhancement-allow-shared-boards

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 9, 2026

Adds three visibility levels for boards in multiuser mode: private (owner/admin only), shared (all users can view, owner/admin can modify), and public (all users can view and write; only owner/admin can rename or delete).

Summary

Backend

  • BoardVisibility enum (private | shared | public) added to board_records_common.py; board_visibility field added to BoardRecord and BoardChanges
  • Migration 29: adds board_visibility TEXT NOT NULL DEFAULT 'private' column to boards table; migrates existing is_public=1 rows to 'public'
  • board_records_sqlite.py: update() handles board_visibility; get_many()/get_all() queries use board_visibility IN ('shared', 'public') instead of is_public = 1
  • boards.py router: get_board and list_all_board_image_names allow non-owner access for shared/public boards; update_board and delete_board remain owner/admin-only

Frontend

  • schema.ts: BoardVisibility enum, board_visibility field on BoardDTO and BoardChanges
  • BoardContextMenu.tsx: "Set Private / Set Shared / Set Public" menu items (owner and admins only); visibility handlers extracted into named useCallback hooks to comply with react/jsx-no-bind; Delete Board, Archive, and Unarchive menu items are disabled (greyed out) for non-owners of shared and public boards
  • BoardEditableTitle.tsx: pencil icon and double-click rename hidden for non-owners of shared and public boards
  • GalleryBoard.tsx: blue share icon badge for shared boards, green globe badge for public boards; DnD drop target disabled for non-owners of shared boards
  • GalleryImage.tsx: drag-out disabled for non-owners viewing a shared board, preventing images from being moved out
  • GalleryItemDeleteIconButton.tsx: shift+hover trash icon hidden when viewing a shared board as a non-owner
  • ContextMenuItemDeleteImage.tsx: delete image menu item hidden when viewing a shared board as a non-owner
  • ContextMenuItemChangeBoard.tsx: "Change Board" menu item disabled when viewing a shared board as a non-owner
  • MultipleSelectionMenuItems.tsx: "Change Board" and "Delete Selection" disabled when viewing a shared board as a non-owner
  • InvokeQueueBackButton.tsx: main Invoke/generate button disabled when the auto-add board is a shared board the current user does not own
  • FloatingLeftPanelButtons.tsx: floating invoke icon button also disabled when the auto-add board is a shared board the current user does not own
  • ChangeBoardModal.tsx: destination board list filtered to exclude shared boards the current user does not own, preventing moves into read-only boards
  • New hooksuseBoardAccess(board) returns { canWriteImages, canRenameBoard, canDeleteBoard }; useSelectedBoard() and useAutoAddBoard() look up the relevant BoardDTO from the RTK Query cache
  • en.json: i18n strings for all new visibility UI

Tests

10 new tests in test_boards_multiuser.py covering default visibility, setting each level, cross-user access enforcement, reversion to private, non-owner restriction, and admin override. All 33 tests pass.

Related Issues / Discussions

QA Instructions

  1. Enable multiuser mode (multiuser: true in config)
  2. Create a board as User A — verify it defaults to private (User B cannot see it)
  3. Right-click board → Set Shared — verify User B can now view it but:
    • The pencil icon is hidden and double-clicking the name does nothing
    • The Delete Board, Archive, and Unarchive options in the context menu are greyed out
    • The trash icon on images and "Delete Image" context menu item are hidden
    • Both the main Invoke button and the floating invoke icon button are disabled if this shared board is the auto-add target
    • Dragging images onto the board is disabled
    • Dragging images out of the board is disabled
    • The "Change Board" destination list does not include this shared board
  4. Set to Public — verify User B can view and write images (including generating into it), but cannot rename, delete, or archive the board
  5. Set back to Private — verify User B loses access again
  6. Verify shared/public boards show the appropriate icon badge in the boards list
  7. Verify admins retain full access to all boards regardless of visibility

Merge Plan

Migration 29 adds a new column with a safe default ('private'), so existing databases upgrade non-destructively. No redux slice changes.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • ❗Changes to a redux slice have a corresponding migration
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)
Original prompt

This section details on the original issue you should resolve

<issue_title>[enhancement]: Allow shared boards in multiuser mode</issue_title>
<issue_description>### Is there an existing issue for this?

  • I have searched the existing issues

Contact Details

No response

What should this feature add?

Currently when multiuser mode is active, all user-created boards are strictly private. They are entirely isolated from each other.

I would like it to be possible to designate a board as one of "Private," "Shared," or "Public".

  • Private board -- Only visible to the user who created it.
  • Shared board -- Visible to all users. Read/Write for the user who created it (they can add images to it and delete images from it). Read-only for other users.
  • Public board -- Visible and Read/Write to all users.

The Administrator should continue to be able to see all boards created by all users and have read/write access to them.

There is code in the lstein/feature/workflow-isolation-in-multiuser-mode that enables sharing of workflows. I have designated this branch as the base for this pull request.

In terms of user interface, try adding a discrete pulldown menu to the right of the board's name can be used to choose among the three board types. Something small like a caret (^) mark, and badges that appear when the board is shared or public.

Alternatives

No response

Additional Content

No response</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Add support for shared boards in multiuser mode Add board visibility (private/shared/public) for multiuser mode Mar 9, 2026
@lstein
Copy link
Copy Markdown
Owner

lstein commented Mar 9, 2026

@copilot I'm getting the following eslint errors:

/home/lstein/Projects/InvokeAI-lstein/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
  130:17  error  JSX props should not use arrow functions  react/jsx-no-bind
  137:17  error  JSX props should not use arrow functions  react/jsx-no-bind
  144:17  error  JSX props should not use arrow functions  react/jsx-no-bind

Copy link
Copy Markdown
Author

Copilot AI commented Mar 9, 2026

@copilot I'm getting the following eslint errors:

/home/lstein/Projects/InvokeAI-lstein/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx

...

Fixed in 0d7f7ea. Replaced the three inline arrow functions with dedicated useCallback handlers (handleSetVisibilityPrivate, handleSetVisibilityShared, handleSetVisibilityPublic) that each delegate to the shared handleSetVisibility callback.

@lstein lstein marked this pull request as ready for review March 9, 2026 22:24
@lstein lstein self-requested a review as a code owner March 9, 2026 22:24
Copy link
Copy Markdown
Owner

@lstein lstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few glitches to correct. In these examples, "Board A Public" and "Board A Shared" are owned by user A and are public and shared respectively. User B is a non-administrative user.

  1. Shared boards are read-only for non-owners. So when B is logged in he should not be able to generate images and store them in Board A Shared, to move images in and out of Board A Shared, or delete images from Board A Shared. As much as possible, user interfaces that attempt to modify the board should be disabled, so that if Board A Shared is the auto save target for image generation, the generation buttons should be disabled.
  2. Disable the option to delete a public or shared board when a non-owner tries to do so. So when B is logged in and right clicks on Board A Shared or Board A Public, the Delete menu option is greyed out.
  3. Similarly, the trash-can icon to delete individual image files, and the delete image menu item should be deleted when B is logged in and working with Board A Shared.
  4. The pencil icon should be hidden from Board A Shared and Board A Public when B is logged in. Only the owner (or the administrator) should be able to edit the name of a shared or public board.

Copy link
Copy Markdown
Author

Copilot AI commented Mar 9, 2026

A few glitches to correct. In these examples, "Board A Public" and "Board A Shared" are owned by user A and are public and shared respectively. User B is a non-administrative user.

  1. Shared boards a...

Addressed in a0b90b1. Here's what was changed:

  1. Shared board read-only: GalleryItemDeleteIconButton (shift+hover trash icon) and ContextMenuItemDeleteImage hide when a non-owner views a shared board. ContextMenuItemChangeBoard and multi-select "Delete Selection"/"Change Board" are disabled too. The DnD drop target on shared boards is disabled for non-owners, preventing drag-in. The Invoke button is disabled when the auto-add board is shared and the user is not the owner.

  2. Delete board greyed out: BoardContextMenu now sets isDisabled={!canDeleteBoard} on the Delete Board menu item — greys it out for non-owners of both shared and public boards.

  3. Trash icon / delete image hidden for shared: Items 6–9 above all use the new useSelectedBoard()useBoardAccess() chain. When the selected board is shared and the current user is not the owner, canWriteImages is false and the delete controls are hidden/disabled.

  4. Pencil icon hidden for shared and public: BoardEditableTitle uses useBoardAccess(board).canRenameBoard; when false (non-owner of shared or public board), the pencil icon is not rendered and double-clicking the title has no effect.

New helpers: useBoardAccess(board) returns { canWriteImages, canRenameBoard, canDeleteBoard }. Logic: owner or admin → full access; public board non-owner → can write images but not rename/delete; shared board non-owner → read-only.

@lstein lstein self-requested a review March 10, 2026 02:19
Copy link
Copy Markdown
Owner

@lstein lstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still some issues:

  1. When a read-only shared board is selected, and I am not its owner, the "Add to Queue" button is active and lets me generate into the shared board. This button should be disabled when the target is a shared board and I am not the owner of the board or the administrator.
  2. I am able to drag an image out of a shared board and into a private board, thereby deleting it from the shared board, even when I am not the owner of the shared board. Dragging from a shared board when I am not the owner (or administrator) should be disabled.
  3. I can right click on an image in a private or public board and select "Change Board" to move it into a shared board, even when I am not the owner of the shared board. The drop target for a shared board should be disabled unless I am the owner (or administrator).
  4. I can archive a public or shared board, even when I am not the owner of that board. If I am not the owner, the archive menu selection should be disabled.

Copy link
Copy Markdown
Author

Copilot AI commented Mar 10, 2026

There are still some issues:

  1. When a read-only shared board is selected, and I am not its owner, the "Add to Queue" button is active and lets me generate into the shared board. This button should b...

Fixed in ae9cca7. Here's what was addressed:

  1. "Add to Queue" icon button still active — The floating InvokeIconButton in FloatingLeftPanelButtons.tsx was a second invoke entry point that wasn't covered. Now it also checks useBoardAccess(autoAddBoard).canWriteImages and is disabled for non-owners of shared boards.

  2. Dragging images out of shared boardGalleryImage.tsx now reads useSelectedBoard() + useBoardAccess() and conditionally registers the draggable() binding. When canDragFromBoard is false (non-owner viewing a shared board), the draggable is not attached, so the image cannot be picked up. The monitorForElements monitor is always registered so multi-image drag indicators still work.

  3. "Change Board" destination includes shared boardsChangeBoardModal.tsx now applies a canWriteToBoard filter to the options list. Boards where the current user is not the owner/admin and which are shared are excluded. Public boards and the user's own boards remain available.

  4. Archive/Unarchive accessible to non-owners — Both the Archive and Unarchive menu items in BoardContextMenu.tsx now carry isDisabled={!canDeleteBoard}, so non-owners of shared and public boards see them greyed out.

Copilot AI and others added 3 commits March 9, 2026 22:56
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
…-board filter, archive

Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
@lstein lstein force-pushed the copilot/enhancement-allow-shared-boards branch from 1fd217f to 9f8f7a1 Compare March 10, 2026 02:56
@github-actions github-actions bot added the docs label Apr 4, 2026
…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>
@lstein
Copy link
Copy Markdown
Owner

lstein commented Apr 4, 2026

Fix: Drag-and-drop from shared boards

Problem: Images in shared boards owned by another user couldn't be dragged at all. The GalleryImage component skipped the entire draggable() setup when canWriteImages was false, which disabled ALL drop targets — including the viewer, reference image pane, canvas, and comparison.

Fix: Moved the ownership check from the drag source to the drop targets:

  1. GalleryImage.tsx: Images are now always draggable regardless of board ownership. Removed the canDragFromBoard gate and the useBoardAccess dependency.

  2. dnd.ts: Added canMoveFromSourceBoard() helper that checks the boards RTK Query cache to determine if the current user owns the source board (or is admin, or the board is public).

  3. addImageToBoardDndTarget.isValid and removeImageFromBoardDndTarget.isValid: Now reject board-to-board moves when the source board is a shared board the user doesn't own. Other drop targets (viewer, reference images, canvas entities, comparison, upscale, node fields) are unaffected.

Result: Users can drag images from shared boards to the viewer, reference image pane, canvas, etc. Only board-move drop targets are disabled for non-owned shared boards.

Copy link
Copy Markdown
Owner

@lstein lstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as advertised.

DustyShoe and others added 11 commits April 4, 2026 23:58
…8963)

* Feat(Canvas): Add button to hide preview thumbnails in staging area.

* Code clean up. Added tests.

* Fix: Removed redundant Icon aliases
* Upgrade spandrel to 0.4.2 in uv.lock

* Fixed typos
* Fix workflow copy hotkeys in info view

* Fix Makefile help target copy

* Fix workflow info view copy handling

* Fix workflow edge delete hotkeys
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* Run vitest during frontend build

* Add frontend-test Make target
…red (invoke-ai#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>
…f cardinality matches (invoke-ai#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
weblate and others added 13 commits April 5, 2026 23:54
* translationBot(ui): update translation (Italian)

Currently translated at 98.0% (2205 of 2250 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI

* translationBot(ui): update translation files

Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/
Translation: InvokeAI/Web UI

* translationBot(ui): update translation (Italian)

Currently translated at 97.8% (2210 of 2259 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.8% (2224 of 2272 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 98.1% (2252 of 2295 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 98.0% (2264 of 2309 strings)

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

* translationBot(ui): update translation (Russian)

Currently translated at 60.7% (1419 of 2334 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 98.1% (2290 of 2334 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.7% (2319 of 2372 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.7% (2327 of 2380 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.7% (2328 of 2382 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.5% (2370 of 2429 strings)

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

* translationBot(ui): update translation (Finnish)

Currently translated at 1.5% (37 of 2429 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.5% (2373 of 2433 strings)

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

* translationBot(ui): update translation (Japanese)

Currently translated at 87.1% (2120 of 2433 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.5% (2374 of 2433 strings)

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

* translationBot(ui): update translation (Japanese)

Currently translated at 92.2% (2244 of 2433 strings)

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

* translationBot(ui): update translation (Italian)

Currently translated at 97.5% (2374 of 2433 strings)

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

* translationBot(ui): update translation (Spanish)

Currently translated at 29.4% (720 of 2444 strings)

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

---------

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Co-authored-by: DustyShoe <warukeichi@gmail.com>
Co-authored-by: Ilmari Laakkonen <ilmarille@gmail.com>
Co-authored-by: 嶋田豪介 <shimada_gosuke@cyberagent.co.jp>
Co-authored-by: Lucas Prone <sac2087@gmail.com>
)

OneTrainer exports Z-Image LoRAs with 'transformer.layers.' key prefix
instead of 'diffusion_model.layers.'. Add this prefix (and the
PEFT-wrapped 'base_model.model.transformer.layers.' variant) to the
Z-Image LoRA probe so these models are correctly identified and loaded.
* feat: add support for OneTrainer BFL Flux LoRA format

Newer versions of OneTrainer export Flux LoRAs using BFL internal key
names (double_blocks, single_blocks, img_attn, etc.) with a
'transformer.' prefix and split QKV projections (qkv.0/1/2, linear1.0/1/2/3).
This format was not recognized by any existing detector.

Add detection and conversion for this format, merging split QKV and
linear1 layers into MergedLayerPatch instances for the fused BFL model.

* chore ruff
…ke-ai#9011)

Klein 9B Base (undistilled) and Klein 9B (distilled) have identical
architectures and cannot be distinguished from the state dict alone.
Use a filename heuristic ("base" in the name) to detect the Base
variant for checkpoint, GGUF, and diffusers format models.

Also fixes the incorrect guidance_embeds-based detection for diffusers
format, since both variants have guidance_embeds=False.
…-allow-shared-boards' into copilot/enhancement-allow-shared-boards
…voke-ai#8968)

Verified model sizes against Hugging Face repositories and corrected
11 descriptions that had wrong or outdated download size estimates.

Key corrections:
- T5-XXL base encoder: ~8GB → ~9.5GB
- FLUX.2 VAE: ~335MB → ~168MB (was confused with FLUX.1 VAE)
- FLUX.1 Krea dev: ~33GB → ~29GB (uses quantized T5, not full)
- FLUX.2 Klein 4B/9B Diffusers: ~10GB/~20GB → ~16GB/~35GB
- SD3.5 Medium/Large: ~15GB/~19G → ~16GB/~28GB
- CogView4: ~29GB → ~31GB
- Z-Image Turbo: ~30.6GB → ~33GB
- FLUX.1 Kontext/Krea quantized: ~14GB → ~12GB
…er (invoke-ai#8665)

* feat: Add canvas-workflow integration feature

This commit implements a new feature that allows users to run workflows
directly from the unified canvas. Users can now:

- Access a "Run Workflow" option from the canvas layer context menu
- Select a workflow with image parameters from a modal dialog
- Customize workflow parameters (non-image fields)
- Execute the workflow with the current canvas layer as input
- Have the result automatically added back to the canvas

Key changes:
- Added canvasWorkflowIntegrationSlice for state management
- Created CanvasWorkflowIntegrationModal and related UI components
- Added context menu item to raster layers
- Integrated workflow execution with canvas image extraction
- Added modal to global modal isolator

This integration enhances the canvas by allowing users to leverage
custom workflows for advanced image processing directly within the
canvas workspace.

Implements feature request for deeper workflow-canvas integration.

* refactor(ui): simplify canvas workflow integration field rendering

- Extract WorkflowFieldRenderer component for individual field rendering
- Add WorkflowFormPreview component to handle workflow parameter display
- Remove workflow compatibility filtering - allow all workflows
- Simplify workflow selector to use flattened workflow list
- Add comprehensive field type support (String, Integer, Float, Boolean, Enum, Scheduler, Board, Model, Image, Color)
- Implement image field selection UI with radio

* feat(ui): add canvas-workflow-integration logging namespace

* feat(ui): add workflow filtering for canvas-workflow integration

- Add useFilteredWorkflows hook to filter workflows with ImageField inputs
- Add workflowHasImageField utility to check for ImageField in Form Builder
- Only show workflows that have Form Builder with at least one ImageField
- Add loading state while filtering workflows
- Improve error messages to clarify Form Builder requirement
- Update modal description to mention Form Builder and parameter adjustment
- Add fallback error message for workflows without Form Builder

* feat(ui): add persistence and migration for canvas workflow integration state

- Add _version field (v1) to canvasWorkflowIntegrationState for future migrations
- Add persistConfig with migration function to handle version upgrades
- Add persistDenylist to exclude transient state (isOpen, isProcessing, sourceEntityIdentifier)
- Use es-toolkit isPlainObject and tsafe assert for type-safe migration
- Persist selectedWorkflowId and fieldValues across sessions

* pnpm fix imports

* fix(ui): handle workflow errors in canvas staging area and improve form UX

- Clear processing state when workflow execution fails at enqueue time
  or during invocation, so the modal doesn't get stuck
- Optimistically update listAllQueueItems cache on queue item status
  changes so the staging area immediately exits on failure
- Clear processing state on invocation_error for canvas workflow origin
- Auto-select the only unfilled ImageField in workflow form
- Fix image field overflow and thumbnail sizing in workflow form

* feat(ui): add canvas_output node and entry-based staging area

Add a dedicated `canvas_output` backend invocation node that explicitly
marks which images go to the canvas staging area, replacing the fragile
board-based heuristic. Each `canvas_output` node produces a separate
navigable entry in the staging area, allowing workflows with multiple
outputs to be individually previewed and accepted.

Key changes:
- New `CanvasOutputInvocation` backend node (canvas.py)
- Entry-based staging area model where each output image is a separate
  navigable entry with flat next/prev cycling across all items
- Frontend execute hook uses `canvas_output` type detection instead of
  board field heuristic, with proper board field value translation
- Workflow filtering requires both Form Builder and canvas_output node
- Updated QueueItemPreviewMini and StagingAreaItemsList for entries
- Tests for entry-based navigation, multi-output, and race conditions

* Chore pnp run fix

* Chore eslint fix

* Remove unused useOutputImageDTO export to fix knip lint

* Update invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useCanvasWorkflowIntegrationExecute.tsx

Co-authored-by: dunkeroni <dunkeroni@gmail.com>

* move UI text to en.json

* fix conflicts merge with main

* generate schema

* Chore typegen

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
Co-authored-by: dunkeroni <dunkeroni@gmail.com>
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants