Skip to content

refactor(ui): canvas flow #8069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 210 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
210 commits
Select commit Hold shift + click to select a range
53a3dc5
feat(ui): viewer is a modal (wip)
psychedelicious May 16, 2025
3419107
chore(ui): bump @reduxjs/toolkit to latest
psychedelicious May 16, 2025
668c475
feat(ui): default canvas tool is move
psychedelicious May 16, 2025
c9cd0a8
refactor(ui): image viewer & comparison convolutedness
psychedelicious May 21, 2025
5f2f12f
refactor(ui): org state in prep for new flow
psychedelicious May 21, 2025
5c4cbc7
refactor(ui): zod-ify params slice state
psychedelicious May 21, 2025
a0b0c30
refactor(ui): move params state to big file of canvas zod stuff
psychedelicious May 21, 2025
02e4a3a
refactor(ui): params state zodification
psychedelicious May 22, 2025
c4d1e78
fix(ui): circular import issue
psychedelicious May 22, 2025
cf2d67e
refactor(ui): canvas flow (wip)
psychedelicious May 22, 2025
aa3b210
refactor(ui): canvas flow (wip)
psychedelicious May 23, 2025
c0428ee
refactor(ui): canvas flow (wip)
psychedelicious May 23, 2025
ce5ae83
refactor(ui): canvas flow (wip)
psychedelicious May 27, 2025
d985dfe
refactor(ui): canvas flow events (wip)
psychedelicious May 27, 2025
faeb5f0
refactor(ui): canvas flow (wip)
psychedelicious May 29, 2025
4dc3f1b
refactor(ui): canvas flow (wip)
psychedelicious May 29, 2025
7308428
wip
psychedelicious May 30, 2025
650809e
feat(ui): images always added to gallery in simple session
psychedelicious May 30, 2025
b3f3020
fix(ui): ensure images are added to gallery in simple sessions
psychedelicious May 30, 2025
7a5fa25
feat(ui): support bookmarking an entity when adding it
psychedelicious May 30, 2025
2a92524
feat(ui): bookmark new inpaint masks
psychedelicious May 30, 2025
57bfae6
fix(ui): ensure all args are passed to handler when creating new canv…
psychedelicious May 30, 2025
579318a
fix(ui): remove unused sessionId field from type
psychedelicious May 30, 2025
1446d34
fix(ui): merge refs when forwardingin DndImage
psychedelicious May 30, 2025
eb45a45
fix(ui): ref goes undefined in GalleryImage
psychedelicious May 30, 2025
a3851e0
refactor(ui): canvas flow (wip)
psychedelicious May 30, 2025
5e93f58
wip progress events
psychedelicious May 30, 2025
8a78e37
feat: canvas flow rework (wip)
psychedelicious Jun 3, 2025
c9042e5
feat: canvas flow rework (wip)
psychedelicious Jun 3, 2025
e80f0b2
fix(ui): unstable selector results in lora drop down
psychedelicious Jun 3, 2025
0e9b718
feat: canvas flow rework (wip)
psychedelicious Jun 3, 2025
ad736bc
feat: canvas flow rework (wip)
psychedelicious Jun 3, 2025
5dbc2a7
feat: canvas flow rework (wip)
psychedelicious Jun 3, 2025
e16414b
tidy(ui): app layout components
psychedelicious Jun 4, 2025
0af20b0
feat(api): remove status from list all queue items query
psychedelicious Jun 4, 2025
84f7094
chore(ui): typegen
psychedelicious Jun 4, 2025
db4220f
feat: canvas flow rework (wip)
psychedelicious Jun 4, 2025
2e2ac71
feat: canvas flow rework (wip)
psychedelicious Jun 4, 2025
cd13619
fix(ui): prevent drag of progress images
psychedelicious Jun 4, 2025
985cd82
tidy(ui): component organization
psychedelicious Jun 4, 2025
7ec511d
feat(app): do not prune queue on startup
psychedelicious Jun 4, 2025
d2155e9
feat(ui): remove clear queue ui components
psychedelicious Jun 4, 2025
5088e70
fix(ui): cursor on staging area preview image
psychedelicious Jun 4, 2025
b05de86
perf(ui): queue actions menu is lazy
psychedelicious Jun 4, 2025
0028166
feat(ui): modularize all staging area logic so it can be shared w/ ca…
psychedelicious Jun 4, 2025
628367b
feat(ui): move socket events handling into ctx component
psychedelicious Jun 4, 2025
c8df7cd
feat(ui): prevent flicker of image action buttons
psychedelicious Jun 5, 2025
ea34690
feat: canvas flow rework (wip)
psychedelicious Jun 5, 2025
86e1a37
docs(ui): add comment about auto-switch not being quite right yet
psychedelicious Jun 5, 2025
da4b084
feat(ui): tweak canvas scroll to zoom feel
psychedelicious Jun 5, 2025
5d80642
feat(app): support deleting queue items by id or destination
psychedelicious Jun 5, 2025
56938ca
feat(ui): rough out canvas staging area
psychedelicious Jun 5, 2025
e3fc244
chore(ui): lint (partial cleanup)
psychedelicious Jun 5, 2025
3a08ea7
feat(ui): update canvas session state handling for new staging strat
psychedelicious Jun 5, 2025
6570c0c
feat(ui): more staging fixes
psychedelicious Jun 5, 2025
1412c07
feat(ui): improved staging placeholders
psychedelicious Jun 5, 2025
526e633
feat(ui): improved staging placeholders
psychedelicious Jun 5, 2025
9bbc31b
fix(ui): reset layers when changing session type
psychedelicious Jun 5, 2025
3038a79
fix(ui): ensure canvas tool modules are destroyed
psychedelicious Jun 5, 2025
bf5ed61
feat(ui): add staging area toolbar to simple session
psychedelicious Jun 5, 2025
cd0668d
feat(ui): tweak staging image display
psychedelicious Jun 5, 2025
23511d6
feat(ui): when discarding last item, select new last instead of first
psychedelicious Jun 5, 2025
bc3550f
feat(ui): finish generation when discarding last item
psychedelicious Jun 5, 2025
592c842
build(ui): temporarily ignore all knip issues
psychedelicious Jun 5, 2025
2431060
fix(ui): hide layers when not on canvas tab
psychedelicious Jun 5, 2025
cc50835
fix(ui): mini preview bg color
psychedelicious Jun 5, 2025
2ddcde1
refactor(ui): migrate from canceling queue items to deleteing, make q…
psychedelicious Jun 6, 2025
9df6949
feat(ui): remove vary and edit as control buttons
psychedelicious Jun 6, 2025
2531366
feat(ui): simple session initial state
psychedelicious Jun 6, 2025
9f392c8
feat(ui): remove technical progress message from full preview
psychedelicious Jun 6, 2025
e81dde0
feat(ui): fiddle w/ staging area header
psychedelicious Jun 6, 2025
c316f07
feat(ui): add startover button to canvas toolbar
psychedelicious Jun 6, 2025
ac206f4
feat(ui): make main panel styling and title consistent
psychedelicious Jun 6, 2025
6754fde
chore(ui): lint
psychedelicious Jun 6, 2025
2f26657
feat(ui): move canvas-specific staging subscriptions to CanvasStaging…
psychedelicious Jun 10, 2025
711fe91
feat(ui): add on first progress autoswitch mode
psychedelicious Jun 10, 2025
d640a90
fix(ui): switch only on first progress image
psychedelicious Jun 10, 2025
94afc13
feat(ui): close viewer on escape
psychedelicious Jun 10, 2025
a71a0e1
feat(ui): add AppGetState type
psychedelicious Jun 11, 2025
01784fb
feat(ui): store output image DTO in session context instead of just t…
psychedelicious Jun 11, 2025
ba082cc
fix(ui): wait until last queue item deleted before flagging canvas se…
psychedelicious Jun 11, 2025
8c17bde
fix(ui): use imageDTO in staging area
psychedelicious Jun 11, 2025
c7ed351
refactor(ui): async modal pattern; use for deleting images
psychedelicious Jun 11, 2025
baa9141
chore(ui): dpdm
psychedelicious Jun 11, 2025
a5e5cbd
feat(ui): simple session initial state cards are buttons
psychedelicious Jun 11, 2025
aa93e95
feat(ui): split out ref images into own slice (WIP)
psychedelicious Jun 12, 2025
5a2f5c1
refactor(ui): `refImage.ipAdapter` -> `refImage.config`
psychedelicious Jun 13, 2025
48e2e7e
refactor(ui): ref images (WIP)
psychedelicious Jun 13, 2025
8d1ab0a
refactor(ui): ref images (WIP)
psychedelicious Jun 13, 2025
3bb446c
experiment(ui): add generate tab
psychedelicious Jun 12, 2025
45b1ef6
tweak(ui): ref image header
psychedelicious Jun 13, 2025
d08e2fb
fix(ui): hack to close chakra tooltips on drag
psychedelicious Jun 11, 2025
1b1e198
fix(ui): update queue item preview images on init of queue items context
psychedelicious Jun 11, 2025
a28c15d
chore: bump version to v6.0.0a2
psychedelicious Jun 13, 2025
450a0bf
fix(ui): remove old isSelected from refImageAdded call
psychedelicious Jun 16, 2025
0f1a69a
feat(ui): toggleable negative prompt
psychedelicious Jun 16, 2025
ed05bf2
feat(ui): refine ref images UI
psychedelicious Jun 16, 2025
2e0824a
feat(ui): make autoswitch on/off
psychedelicious Jun 16, 2025
893f7a8
fix(ui): progress image fixes
psychedelicious Jun 16, 2025
c31cb0b
fix(ui): invoke button tooltip on generate tab
psychedelicious Jun 16, 2025
161624c
feat(ui): rework simple session initial state
psychedelicious Jun 16, 2025
5ac5115
chore(ui): lint
psychedelicious Jun 16, 2025
2f9ea91
feat(ui): tweak splash screen layout
psychedelicious Jun 17, 2025
32aa3e6
feat(ui): switch tab on drag over tab button
psychedelicious Jun 17, 2025
a7e4573
feat(ui): ref images feel more like buttons
psychedelicious Jun 17, 2025
a5baf0c
fix(ui): overflow on ref image model
psychedelicious Jun 17, 2025
903776b
feat(ui): represent IP adapter weight in ref image thumbnail
psychedelicious Jun 17, 2025
18775e8
fix(ui): only show weight for IP adapters
psychedelicious Jun 17, 2025
d5c238e
feat(ui): port UI slice to zod
psychedelicious Jun 17, 2025
abaa33e
wip
psychedelicious Jun 11, 2025
7f44da4
fix(ui): wonky stage sizing on first visibility
psychedelicious Jun 18, 2025
6eecdca
wip
psychedelicious Jun 19, 2025
fcaeba2
feat(ui): canvas launchpad
psychedelicious Jun 19, 2025
e7e1142
feat(ui): get layouts working
psychedelicious Jun 19, 2025
e0ed56f
fix(ui): inverted logic for resume queue button
psychedelicious Jun 20, 2025
7f222ff
fix(ui): unnecessary dependency on tab selection in
psychedelicious Jun 20, 2025
16993cd
feat(ui): get all tabs working w/ new layout
psychedelicious Jun 20, 2025
df87800
feat(ui): restore floating panel buttons
psychedelicious Jun 20, 2025
f4794e4
fix(ui): generate tab hotkey
psychedelicious Jun 20, 2025
553d1a6
feat(ui): restore all panel hotkeys
psychedelicious Jun 20, 2025
7c4550c
chore(ui): lint
psychedelicious Jun 20, 2025
33a28ad
chore: bump version to v6.0.0a3
psychedelicious Jun 20, 2025
241844b
refactor(ui): rip out image viewer as modal
psychedelicious Jun 20, 2025
01953cf
feat(ui): tweak dockview tabs
psychedelicious Jun 20, 2025
852badc
feat(ui): standardize auto layout structure
psychedelicious Jun 20, 2025
b06f76c
fix(ui): unable to resize prompt box bc negative prompt button is over
psychedelicious Jun 20, 2025
041023d
feat(ui): tweak vertical tab bar layout
psychedelicious Jun 20, 2025
3984b34
fix(ui): don't use layers when generating on generate tab
psychedelicious Jun 20, 2025
3264188
fix(ui): launchpad layouts
psychedelicious Jun 20, 2025
a30933b
feat(ui): clean up image view components & code
psychedelicious Jun 20, 2025
81341de
feat(ui): mini metadata viewer
psychedelicious Jun 20, 2025
8d0fe55
feat(ui): no model error state for ref images
psychedelicious Jun 20, 2025
399d6e7
chore: bump version to v6.0.0a4
psychedelicious Jun 20, 2025
e10afe3
feat(ui): re-implement multiple auto-switch modes
psychedelicious Jun 23, 2025
7208373
fix(ui): reset last started item id when doing autoswitch
psychedelicious Jun 23, 2025
36ec101
feat(ui): double-click staging area image to disable auto-switch
psychedelicious Jun 23, 2025
5d8061b
fix(ui): staging area does not show placeholder on first render
psychedelicious Jun 23, 2025
34aa131
feat(ui): show last progress message & placeholder in generation prog…
psychedelicious Jun 23, 2025
214005d
feat(ui): generation progress tab improvements
psychedelicious Jun 23, 2025
f0ba693
feat(ui): switch to viewer/canvas on invoke
psychedelicious Jun 23, 2025
d23cdfd
feat(ui): viewer integrates progress (wip)
psychedelicious Jun 23, 2025
4028cad
feat(api): return more data when doing image/board mutations
psychedelicious Jun 23, 2025
7038229
chore(ui): typegen
psychedelicious Jun 23, 2025
4665f0d
refactor(ui): use image names for selection instead of dtos
psychedelicious Jun 23, 2025
a294e8e
chore(ui): lint
psychedelicious Jun 23, 2025
ac81ec4
chore: bump version to v6.0.0a5
psychedelicious Jun 23, 2025
049a8d8
fix(ui): fix metadata toggle stuck disabled
psychedelicious Jun 23, 2025
bee4cf4
refactor: gallery scroll
psychedelicious Jun 24, 2025
2c8ce6f
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
87909a0
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
32a5e96
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
0a8f647
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
c825471
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
8327d86
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
f55c593
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
434d8a2
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
d45197e
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
f68d8ed
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
adea983
refactor: gallery scroll (improved impl)
psychedelicious Jun 24, 2025
7080889
feat(ui): scrollbar styles
psychedelicious Jun 24, 2025
bf5fc95
fix(ui): minor jank when siwtching images rapidly
psychedelicious Jun 24, 2025
6e3e316
chore: bump version to v6.0.0a6
psychedelicious Jun 24, 2025
b204fb6
chore: ruff
psychedelicious Jun 24, 2025
89c609f
feat(ui): calculate gridTemplateColumns in selector
psychedelicious Jun 25, 2025
1218f49
fix(ui): remove context from DOM props
psychedelicious Jun 25, 2025
b5eb3d9
fix(ui): gallery updates on image completion
psychedelicious Jun 25, 2025
98368b0
feat(ui): restore gallery hotkeys (except delete)
psychedelicious Jun 25, 2025
b2b42be
refactor: remove unused methods/routes, fix some gallery invalidation…
psychedelicious Jun 25, 2025
f7b2492
fix(ui): issues with progress viewer
psychedelicious Jun 25, 2025
937c03f
chore(ui): disable debug logger
psychedelicious Jun 25, 2025
0eb4360
fix(ui): debounce gallery min width value
psychedelicious Jun 25, 2025
d74d079
fix(ui): restore gallery selection count tag
psychedelicious Jun 25, 2025
e164451
chore: ruff
psychedelicious Jun 25, 2025
a928ed0
chore(ui): dpdm
psychedelicious Jun 25, 2025
2367b9f
chore: bump version to v6.0.0a7
psychedelicious Jun 25, 2025
a92ba25
feat(ui): switch to canvas tab when using launchpad
psychedelicious Jun 25, 2025
6c8cf99
feat(ui): revised ref image panel
psychedelicious Jun 25, 2025
2c7fa90
chore: bump version to v6.0.0a8
psychedelicious Jun 25, 2025
1aedc26
feat(ui): handle ref image deletion autoswitch
psychedelicious Jun 25, 2025
242da9e
fix(ui): hide ref panel when last one is deleted
psychedelicious Jun 25, 2025
7aefa8f
fix(ui): invalidate image name list cache on mutation
psychedelicious Jun 25, 2025
b5acc20
feat(ui): migrate from lodash.isEqual to objectEquals
psychedelicious Jun 25, 2025
9424271
revert(ui): undo accidental downgrade of rtk
psychedelicious Jun 25, 2025
1a39d22
feat(ui): migrate from lodash-es to es-toolkit
psychedelicious Jun 25, 2025
7948bca
build(ui): adopt sonda over rollup-plugin-visualizer to examine bundle
psychedelicious Jun 25, 2025
ab39305
chore(ui): upgrade zod to v4
psychedelicious Jun 25, 2025
14f7c98
chore(ui): bump package version
psychedelicious Jun 25, 2025
504daa0
Revert "build(ui): adopt sonda over rollup-plugin-visualizer to exami…
psychedelicious Jun 25, 2025
ab5cb2c
refactor: optimistic gallery updates
psychedelicious Jun 26, 2025
3243853
fix(ui): prevent duplicate initial galler yfetches
psychedelicious Jun 26, 2025
386a932
feat(ui): clean up GalleryImage
psychedelicious Jun 26, 2025
4612f0a
fix(ui): tab bar shrinkage
psychedelicious Jun 26, 2025
df6e67c
fix(ui): queue count badge showing up multiple times
psychedelicious Jun 26, 2025
175c014
fix(ui): auto image selection on invocation complete, board change
psychedelicious Jun 26, 2025
29462e6
fix(ui): handle selecting images/boards on invocation complete
psychedelicious Jun 26, 2025
0e84382
fix(ui): ensure image selected on first load
psychedelicious Jun 26, 2025
394a14c
fix(ui): progress in viewer bg color
psychedelicious Jun 26, 2025
1c778bd
fix(ui): some progress image jank
psychedelicious Jun 26, 2025
966dd88
feat(ui): boards and gallery panel collapse
psychedelicious Jun 26, 2025
82f31f2
feat(ui): tweak canvas entity group list button layout
psychedelicious Jun 26, 2025
0bf84ab
feat(ui): gallery scrollbars autohide
psychedelicious Jun 26, 2025
5385282
feat(ui): use consistent gallery scrollseek placeholder component
psychedelicious Jun 26, 2025
ea8da0b
chore: ruff
psychedelicious Jun 26, 2025
6358f39
chore(ui): lint
psychedelicious Jun 26, 2025
f4a4113
chore: bump version to v6.0.0a9
psychedelicious Jun 26, 2025
418ad0d
fix(ui): rebase conflicts
psychedelicious Jun 26, 2025
62fa4f4
fix(ui): more viewer progress nonsense
psychedelicious Jun 26, 2025
9fc51c7
fix(ui): optimistic updates when sorting by oldest first
psychedelicious Jun 26, 2025
7759b16
fix(ui): dnd on images
psychedelicious Jun 26, 2025
38ccd8e
chore: bump version to v6.0.0a10
psychedelicious Jun 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ cython_debug/
# Scratch folder
.scratch/
.vscode/
.zed/

# source installer files
installer/*zip
Expand Down
2 changes: 1 addition & 1 deletion docs/contributing/frontend/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ Migration logic is in [migrations.ts].
<!-- links -->

[pydantic]: https://github.com/pydantic/pydantic 'pydantic'
[zod]: https://github.com/colinhacks/zod 'zod'
[zod]: https://github.com/colinhacks/zod 'zod/v4'
[openapi-types]: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-types 'openapi-types'
[reactflow]: https://github.com/xyflow/xyflow 'reactflow'
[reactflow-concepts]: https://reactflow.dev/learn/concepts/terms-and-definitions
Expand Down
75 changes: 51 additions & 24 deletions invokeai/app/api/routers/board_images.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
from fastapi import Body, HTTPException
from fastapi.routing import APIRouter
from pydantic import BaseModel, Field

from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.services.images.images_common import AddImagesToBoardResult, RemoveImagesFromBoardResult

board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"])


class AddImagesToBoardResult(BaseModel):
board_id: str = Field(description="The id of the board the images were added to")
added_image_names: list[str] = Field(description="The image names that were added to the board")


class RemoveImagesFromBoardResult(BaseModel):
removed_image_names: list[str] = Field(description="The image names that were removed from their board")


@board_images_router.post(
"/",
operation_id="add_image_to_board",
responses={
201: {"description": "The image was added to a board successfully"},
},
status_code=201,
response_model=AddImagesToBoardResult,
)
async def add_image_to_board(
board_id: str = Body(description="The id of the board to add to"),
image_name: str = Body(description="The name of the image to add"),
):
) -> AddImagesToBoardResult:
"""Creates a board_image"""
try:
result = ApiDependencies.invoker.services.board_images.add_image_to_board(
board_id=board_id, image_name=image_name
added_images: set[str] = set()
affected_boards: set[str] = set()
old_board_id = ApiDependencies.invoker.services.images.get_dto(image_name).board_id or "none"
ApiDependencies.invoker.services.board_images.add_image_to_board(board_id=board_id, image_name=image_name)
added_images.add(image_name)
affected_boards.add(board_id)
affected_boards.add(old_board_id)

return AddImagesToBoardResult(
added_images=list(added_images),
affected_boards=list(affected_boards),
)
return result
except Exception:
raise HTTPException(status_code=500, detail="Failed to add image to board")

Expand All @@ -45,14 +45,25 @@ async def add_image_to_board(
201: {"description": "The image was removed from the board successfully"},
},
status_code=201,
response_model=RemoveImagesFromBoardResult,
)
async def remove_image_from_board(
image_name: str = Body(description="The name of the image to remove", embed=True),
):
) -> RemoveImagesFromBoardResult:
"""Removes an image from its board, if it had one"""
try:
result = ApiDependencies.invoker.services.board_images.remove_image_from_board(image_name=image_name)
return result
removed_images: set[str] = set()
affected_boards: set[str] = set()
old_board_id = ApiDependencies.invoker.services.images.get_dto(image_name).board_id or "none"
ApiDependencies.invoker.services.board_images.remove_image_from_board(image_name=image_name)
removed_images.add(image_name)
affected_boards.add("none")
affected_boards.add(old_board_id)
return RemoveImagesFromBoardResult(
removed_images=list(removed_images),
affected_boards=list(affected_boards),
)

except Exception:
raise HTTPException(status_code=500, detail="Failed to remove image from board")

Expand All @@ -72,16 +83,25 @@ async def add_images_to_board(
) -> AddImagesToBoardResult:
"""Adds a list of images to a board"""
try:
added_image_names: list[str] = []
added_images: set[str] = set()
affected_boards: set[str] = set()
for image_name in image_names:
try:
old_board_id = ApiDependencies.invoker.services.images.get_dto(image_name).board_id or "none"
ApiDependencies.invoker.services.board_images.add_image_to_board(
board_id=board_id, image_name=image_name
board_id=board_id,
image_name=image_name,
)
added_image_names.append(image_name)
added_images.add(image_name)
affected_boards.add(board_id)
affected_boards.add(old_board_id)

except Exception:
pass
return AddImagesToBoardResult(board_id=board_id, added_image_names=added_image_names)
return AddImagesToBoardResult(
added_images=list(added_images),
affected_boards=list(affected_boards),
)
except Exception:
raise HTTPException(status_code=500, detail="Failed to add images to board")

Expand All @@ -100,13 +120,20 @@ async def remove_images_from_board(
) -> RemoveImagesFromBoardResult:
"""Removes a list of images from their board, if they had one"""
try:
removed_image_names: list[str] = []
removed_images: set[str] = set()
affected_boards: set[str] = set()
for image_name in image_names:
try:
old_board_id = ApiDependencies.invoker.services.images.get_dto(image_name).board_id or "none"
ApiDependencies.invoker.services.board_images.remove_image_from_board(image_name=image_name)
removed_image_names.append(image_name)
removed_images.add(image_name)
affected_boards.add("none")
affected_boards.add(old_board_id)
except Exception:
pass
return RemoveImagesFromBoardResult(removed_image_names=removed_image_names)
return RemoveImagesFromBoardResult(
removed_images=list(removed_images),
affected_boards=list(affected_boards),
)
except Exception:
raise HTTPException(status_code=500, detail="Failed to remove images from board")
159 changes: 128 additions & 31 deletions invokeai/app/api/routers/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
from invokeai.app.invocations.fields import MetadataField
from invokeai.app.services.image_records.image_records_common import (
ImageCategory,
ImageNamesResult,
ImageRecordChanges,
ResourceOrigin,
)
from invokeai.app.services.images.images_common import ImageDTO, ImageUrlsDTO
from invokeai.app.services.images.images_common import (
DeleteImagesResult,
ImageDTO,
ImageUrlsDTO,
StarredImagesResult,
UnstarredImagesResult,
)
from invokeai.app.services.shared.pagination import OffsetPaginatedResults
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
from invokeai.app.util.controlnet_utils import heuristic_resize_fast
Expand Down Expand Up @@ -153,18 +160,30 @@ async def create_image_upload_entry(
raise HTTPException(status_code=501, detail="Not implemented")


@images_router.delete("/i/{image_name}", operation_id="delete_image")
@images_router.delete("/i/{image_name}", operation_id="delete_image", response_model=DeleteImagesResult)
async def delete_image(
image_name: str = Path(description="The name of the image to delete"),
) -> None:
) -> DeleteImagesResult:
"""Deletes an image"""

deleted_images: set[str] = set()
affected_boards: set[str] = set()

try:
image_dto = ApiDependencies.invoker.services.images.get_dto(image_name)
board_id = image_dto.board_id or "none"
ApiDependencies.invoker.services.images.delete(image_name)
deleted_images.add(image_name)
affected_boards.add(board_id)
except Exception:
# TODO: Does this need any exception handling at all?
pass

return DeleteImagesResult(
deleted_images=list(deleted_images),
affected_boards=list(affected_boards),
)


@images_router.delete("/intermediates", operation_id="clear_intermediates")
async def clear_intermediates() -> int:
Expand Down Expand Up @@ -376,46 +395,52 @@ async def list_image_dtos(
return image_dtos


class DeleteImagesFromListResult(BaseModel):
deleted_images: list[str]


@images_router.post("/delete", operation_id="delete_images_from_list", response_model=DeleteImagesFromListResult)
@images_router.post("/delete", operation_id="delete_images_from_list", response_model=DeleteImagesResult)
async def delete_images_from_list(
image_names: list[str] = Body(description="The list of names of images to delete", embed=True),
) -> DeleteImagesFromListResult:
) -> DeleteImagesResult:
try:
deleted_images: list[str] = []
deleted_images: set[str] = set()
affected_boards: set[str] = set()
for image_name in image_names:
try:
image_dto = ApiDependencies.invoker.services.images.get_dto(image_name)
board_id = image_dto.board_id or "none"
ApiDependencies.invoker.services.images.delete(image_name)
deleted_images.append(image_name)
deleted_images.add(image_name)
affected_boards.add(board_id)
except Exception:
pass
return DeleteImagesFromListResult(deleted_images=deleted_images)
return DeleteImagesResult(
deleted_images=list(deleted_images),
affected_boards=list(affected_boards),
)
except Exception:
raise HTTPException(status_code=500, detail="Failed to delete images")


@images_router.delete(
"/uncategorized", operation_id="delete_uncategorized_images", response_model=DeleteImagesFromListResult
)
async def delete_uncategorized_images() -> DeleteImagesFromListResult:
@images_router.delete("/uncategorized", operation_id="delete_uncategorized_images", response_model=DeleteImagesResult)
async def delete_uncategorized_images() -> DeleteImagesResult:
"""Deletes all images that are uncategorized"""

image_names = ApiDependencies.invoker.services.board_images.get_all_board_image_names_for_board(
board_id="none", categories=None, is_intermediate=None
)

try:
deleted_images: list[str] = []
deleted_images: set[str] = set()
affected_boards: set[str] = set()
for image_name in image_names:
try:
ApiDependencies.invoker.services.images.delete(image_name)
deleted_images.append(image_name)
deleted_images.add(image_name)
affected_boards.add("none")
except Exception:
pass
return DeleteImagesFromListResult(deleted_images=deleted_images)
return DeleteImagesResult(
deleted_images=list(deleted_images),
affected_boards=list(affected_boards),
)
except Exception:
raise HTTPException(status_code=500, detail="Failed to delete images")

Expand All @@ -424,36 +449,50 @@ class ImagesUpdatedFromListResult(BaseModel):
updated_image_names: list[str] = Field(description="The image names that were updated")


@images_router.post("/star", operation_id="star_images_in_list", response_model=ImagesUpdatedFromListResult)
@images_router.post("/star", operation_id="star_images_in_list", response_model=StarredImagesResult)
async def star_images_in_list(
image_names: list[str] = Body(description="The list of names of images to star", embed=True),
) -> ImagesUpdatedFromListResult:
) -> StarredImagesResult:
try:
updated_image_names: list[str] = []
starred_images: set[str] = set()
affected_boards: set[str] = set()
for image_name in image_names:
try:
ApiDependencies.invoker.services.images.update(image_name, changes=ImageRecordChanges(starred=True))
updated_image_names.append(image_name)
updated_image_dto = ApiDependencies.invoker.services.images.update(
image_name, changes=ImageRecordChanges(starred=True)
)
starred_images.add(image_name)
affected_boards.add(updated_image_dto.board_id or "none")
except Exception:
pass
return ImagesUpdatedFromListResult(updated_image_names=updated_image_names)
return StarredImagesResult(
starred_images=list(starred_images),
affected_boards=list(affected_boards),
)
except Exception:
raise HTTPException(status_code=500, detail="Failed to star images")


@images_router.post("/unstar", operation_id="unstar_images_in_list", response_model=ImagesUpdatedFromListResult)
@images_router.post("/unstar", operation_id="unstar_images_in_list", response_model=UnstarredImagesResult)
async def unstar_images_in_list(
image_names: list[str] = Body(description="The list of names of images to unstar", embed=True),
) -> ImagesUpdatedFromListResult:
) -> UnstarredImagesResult:
try:
updated_image_names: list[str] = []
unstarred_images: set[str] = set()
affected_boards: set[str] = set()
for image_name in image_names:
try:
ApiDependencies.invoker.services.images.update(image_name, changes=ImageRecordChanges(starred=False))
updated_image_names.append(image_name)
updated_image_dto = ApiDependencies.invoker.services.images.update(
image_name, changes=ImageRecordChanges(starred=False)
)
unstarred_images.add(image_name)
affected_boards.add(updated_image_dto.board_id or "none")
except Exception:
pass
return ImagesUpdatedFromListResult(updated_image_names=updated_image_names)
return UnstarredImagesResult(
unstarred_images=list(unstarred_images),
affected_boards=list(affected_boards),
)
except Exception:
raise HTTPException(status_code=500, detail="Failed to unstar images")

Expand Down Expand Up @@ -524,3 +563,61 @@ async def get_bulk_download_item(
return response
except Exception:
raise HTTPException(status_code=404)


@images_router.get("/names", operation_id="get_image_names")
async def get_image_names(
image_origin: Optional[ResourceOrigin] = Query(default=None, description="The origin of images to list."),
categories: Optional[list[ImageCategory]] = Query(default=None, description="The categories of image to include."),
is_intermediate: Optional[bool] = Query(default=None, description="Whether to list intermediate images."),
board_id: Optional[str] = Query(
default=None,
description="The board id to filter by. Use 'none' to find images without a board.",
),
order_dir: SQLiteDirection = Query(default=SQLiteDirection.Descending, description="The order of sort"),
starred_first: bool = Query(default=True, description="Whether to sort by starred images first"),
search_term: Optional[str] = Query(default=None, description="The term to search for"),
) -> ImageNamesResult:
"""Gets ordered list of image names with metadata for optimistic updates"""

try:
result = ApiDependencies.invoker.services.images.get_image_names(
starred_first=starred_first,
order_dir=order_dir,
image_origin=image_origin,
categories=categories,
is_intermediate=is_intermediate,
board_id=board_id,
search_term=search_term,
)
return result
except Exception:
raise HTTPException(status_code=500, detail="Failed to get image names")


@images_router.post(
"/images_by_names",
operation_id="get_images_by_names",
responses={200: {"model": list[ImageDTO]}},
)
async def get_images_by_names(
image_names: list[str] = Body(embed=True, description="Object containing list of image names to fetch DTOs for"),
) -> list[ImageDTO]:
"""Gets image DTOs for the specified image names. Maintains order of input names."""

try:
image_service = ApiDependencies.invoker.services.images

# Fetch DTOs preserving the order of requested names
image_dtos: list[ImageDTO] = []
for name in image_names:
try:
dto = image_service.get_dto(name)
image_dtos.append(dto)
except Exception:
# Skip missing images - they may have been deleted between name fetch and DTO fetch
continue

return image_dtos
except Exception:
raise HTTPException(status_code=500, detail="Failed to get image DTOs")
Loading
Loading