14
14
from invokeai .app .invocations .fields import MetadataField
15
15
from invokeai .app .services .image_records .image_records_common import (
16
16
ImageCategory ,
17
+ ImageNamesResult ,
17
18
ImageRecordChanges ,
18
19
ResourceOrigin ,
19
20
)
20
- from invokeai .app .services .images .images_common import ImageDTO , ImageUrlsDTO
21
+ from invokeai .app .services .images .images_common import (
22
+ DeleteImagesResult ,
23
+ ImageDTO ,
24
+ ImageUrlsDTO ,
25
+ StarredImagesResult ,
26
+ UnstarredImagesResult ,
27
+ )
21
28
from invokeai .app .services .shared .pagination import OffsetPaginatedResults
22
29
from invokeai .app .services .shared .sqlite .sqlite_common import SQLiteDirection
23
30
from invokeai .app .util .controlnet_utils import heuristic_resize_fast
@@ -153,18 +160,30 @@ async def create_image_upload_entry(
153
160
raise HTTPException (status_code = 501 , detail = "Not implemented" )
154
161
155
162
156
- @images_router .delete ("/i/{image_name}" , operation_id = "delete_image" )
163
+ @images_router .delete ("/i/{image_name}" , operation_id = "delete_image" , response_model = DeleteImagesResult )
157
164
async def delete_image (
158
165
image_name : str = Path (description = "The name of the image to delete" ),
159
- ) -> None :
166
+ ) -> DeleteImagesResult :
160
167
"""Deletes an image"""
161
168
169
+ deleted_images : set [str ] = set ()
170
+ affected_boards : set [str ] = set ()
171
+
162
172
try :
173
+ image_dto = ApiDependencies .invoker .services .images .get_dto (image_name )
174
+ board_id = image_dto .board_id or "none"
163
175
ApiDependencies .invoker .services .images .delete (image_name )
176
+ deleted_images .add (image_name )
177
+ affected_boards .add (board_id )
164
178
except Exception :
165
179
# TODO: Does this need any exception handling at all?
166
180
pass
167
181
182
+ return DeleteImagesResult (
183
+ deleted_images = list (deleted_images ),
184
+ affected_boards = list (affected_boards ),
185
+ )
186
+
168
187
169
188
@images_router .delete ("/intermediates" , operation_id = "clear_intermediates" )
170
189
async def clear_intermediates () -> int :
@@ -376,46 +395,52 @@ async def list_image_dtos(
376
395
return image_dtos
377
396
378
397
379
- class DeleteImagesFromListResult (BaseModel ):
380
- deleted_images : list [str ]
381
-
382
-
383
- @images_router .post ("/delete" , operation_id = "delete_images_from_list" , response_model = DeleteImagesFromListResult )
398
+ @images_router .post ("/delete" , operation_id = "delete_images_from_list" , response_model = DeleteImagesResult )
384
399
async def delete_images_from_list (
385
400
image_names : list [str ] = Body (description = "The list of names of images to delete" , embed = True ),
386
- ) -> DeleteImagesFromListResult :
401
+ ) -> DeleteImagesResult :
387
402
try :
388
- deleted_images : list [str ] = []
403
+ deleted_images : set [str ] = set ()
404
+ affected_boards : set [str ] = set ()
389
405
for image_name in image_names :
390
406
try :
407
+ image_dto = ApiDependencies .invoker .services .images .get_dto (image_name )
408
+ board_id = image_dto .board_id or "none"
391
409
ApiDependencies .invoker .services .images .delete (image_name )
392
- deleted_images .append (image_name )
410
+ deleted_images .add (image_name )
411
+ affected_boards .add (board_id )
393
412
except Exception :
394
413
pass
395
- return DeleteImagesFromListResult (deleted_images = deleted_images )
414
+ return DeleteImagesResult (
415
+ deleted_images = list (deleted_images ),
416
+ affected_boards = list (affected_boards ),
417
+ )
396
418
except Exception :
397
419
raise HTTPException (status_code = 500 , detail = "Failed to delete images" )
398
420
399
421
400
- @images_router .delete (
401
- "/uncategorized" , operation_id = "delete_uncategorized_images" , response_model = DeleteImagesFromListResult
402
- )
403
- async def delete_uncategorized_images () -> DeleteImagesFromListResult :
422
+ @images_router .delete ("/uncategorized" , operation_id = "delete_uncategorized_images" , response_model = DeleteImagesResult )
423
+ async def delete_uncategorized_images () -> DeleteImagesResult :
404
424
"""Deletes all images that are uncategorized"""
405
425
406
426
image_names = ApiDependencies .invoker .services .board_images .get_all_board_image_names_for_board (
407
427
board_id = "none" , categories = None , is_intermediate = None
408
428
)
409
429
410
430
try :
411
- deleted_images : list [str ] = []
431
+ deleted_images : set [str ] = set ()
432
+ affected_boards : set [str ] = set ()
412
433
for image_name in image_names :
413
434
try :
414
435
ApiDependencies .invoker .services .images .delete (image_name )
415
- deleted_images .append (image_name )
436
+ deleted_images .add (image_name )
437
+ affected_boards .add ("none" )
416
438
except Exception :
417
439
pass
418
- return DeleteImagesFromListResult (deleted_images = deleted_images )
440
+ return DeleteImagesResult (
441
+ deleted_images = list (deleted_images ),
442
+ affected_boards = list (affected_boards ),
443
+ )
419
444
except Exception :
420
445
raise HTTPException (status_code = 500 , detail = "Failed to delete images" )
421
446
@@ -424,36 +449,50 @@ class ImagesUpdatedFromListResult(BaseModel):
424
449
updated_image_names : list [str ] = Field (description = "The image names that were updated" )
425
450
426
451
427
- @images_router .post ("/star" , operation_id = "star_images_in_list" , response_model = ImagesUpdatedFromListResult )
452
+ @images_router .post ("/star" , operation_id = "star_images_in_list" , response_model = StarredImagesResult )
428
453
async def star_images_in_list (
429
454
image_names : list [str ] = Body (description = "The list of names of images to star" , embed = True ),
430
- ) -> ImagesUpdatedFromListResult :
455
+ ) -> StarredImagesResult :
431
456
try :
432
- updated_image_names : list [str ] = []
457
+ starred_images : set [str ] = set ()
458
+ affected_boards : set [str ] = set ()
433
459
for image_name in image_names :
434
460
try :
435
- ApiDependencies .invoker .services .images .update (image_name , changes = ImageRecordChanges (starred = True ))
436
- updated_image_names .append (image_name )
461
+ updated_image_dto = ApiDependencies .invoker .services .images .update (
462
+ image_name , changes = ImageRecordChanges (starred = True )
463
+ )
464
+ starred_images .add (image_name )
465
+ affected_boards .add (updated_image_dto .board_id or "none" )
437
466
except Exception :
438
467
pass
439
- return ImagesUpdatedFromListResult (updated_image_names = updated_image_names )
468
+ return StarredImagesResult (
469
+ starred_images = list (starred_images ),
470
+ affected_boards = list (affected_boards ),
471
+ )
440
472
except Exception :
441
473
raise HTTPException (status_code = 500 , detail = "Failed to star images" )
442
474
443
475
444
- @images_router .post ("/unstar" , operation_id = "unstar_images_in_list" , response_model = ImagesUpdatedFromListResult )
476
+ @images_router .post ("/unstar" , operation_id = "unstar_images_in_list" , response_model = UnstarredImagesResult )
445
477
async def unstar_images_in_list (
446
478
image_names : list [str ] = Body (description = "The list of names of images to unstar" , embed = True ),
447
- ) -> ImagesUpdatedFromListResult :
479
+ ) -> UnstarredImagesResult :
448
480
try :
449
- updated_image_names : list [str ] = []
481
+ unstarred_images : set [str ] = set ()
482
+ affected_boards : set [str ] = set ()
450
483
for image_name in image_names :
451
484
try :
452
- ApiDependencies .invoker .services .images .update (image_name , changes = ImageRecordChanges (starred = False ))
453
- updated_image_names .append (image_name )
485
+ updated_image_dto = ApiDependencies .invoker .services .images .update (
486
+ image_name , changes = ImageRecordChanges (starred = False )
487
+ )
488
+ unstarred_images .add (image_name )
489
+ affected_boards .add (updated_image_dto .board_id or "none" )
454
490
except Exception :
455
491
pass
456
- return ImagesUpdatedFromListResult (updated_image_names = updated_image_names )
492
+ return UnstarredImagesResult (
493
+ unstarred_images = list (unstarred_images ),
494
+ affected_boards = list (affected_boards ),
495
+ )
457
496
except Exception :
458
497
raise HTTPException (status_code = 500 , detail = "Failed to unstar images" )
459
498
@@ -524,3 +563,61 @@ async def get_bulk_download_item(
524
563
return response
525
564
except Exception :
526
565
raise HTTPException (status_code = 404 )
566
+
567
+
568
+ @images_router .get ("/names" , operation_id = "get_image_names" )
569
+ async def get_image_names (
570
+ image_origin : Optional [ResourceOrigin ] = Query (default = None , description = "The origin of images to list." ),
571
+ categories : Optional [list [ImageCategory ]] = Query (default = None , description = "The categories of image to include." ),
572
+ is_intermediate : Optional [bool ] = Query (default = None , description = "Whether to list intermediate images." ),
573
+ board_id : Optional [str ] = Query (
574
+ default = None ,
575
+ description = "The board id to filter by. Use 'none' to find images without a board." ,
576
+ ),
577
+ order_dir : SQLiteDirection = Query (default = SQLiteDirection .Descending , description = "The order of sort" ),
578
+ starred_first : bool = Query (default = True , description = "Whether to sort by starred images first" ),
579
+ search_term : Optional [str ] = Query (default = None , description = "The term to search for" ),
580
+ ) -> ImageNamesResult :
581
+ """Gets ordered list of image names with metadata for optimistic updates"""
582
+
583
+ try :
584
+ result = ApiDependencies .invoker .services .images .get_image_names (
585
+ starred_first = starred_first ,
586
+ order_dir = order_dir ,
587
+ image_origin = image_origin ,
588
+ categories = categories ,
589
+ is_intermediate = is_intermediate ,
590
+ board_id = board_id ,
591
+ search_term = search_term ,
592
+ )
593
+ return result
594
+ except Exception :
595
+ raise HTTPException (status_code = 500 , detail = "Failed to get image names" )
596
+
597
+
598
+ @images_router .post (
599
+ "/images_by_names" ,
600
+ operation_id = "get_images_by_names" ,
601
+ responses = {200 : {"model" : list [ImageDTO ]}},
602
+ )
603
+ async def get_images_by_names (
604
+ image_names : list [str ] = Body (embed = True , description = "Object containing list of image names to fetch DTOs for" ),
605
+ ) -> list [ImageDTO ]:
606
+ """Gets image DTOs for the specified image names. Maintains order of input names."""
607
+
608
+ try :
609
+ image_service = ApiDependencies .invoker .services .images
610
+
611
+ # Fetch DTOs preserving the order of requested names
612
+ image_dtos : list [ImageDTO ] = []
613
+ for name in image_names :
614
+ try :
615
+ dto = image_service .get_dto (name )
616
+ image_dtos .append (dto )
617
+ except Exception :
618
+ # Skip missing images - they may have been deleted between name fetch and DTO fetch
619
+ continue
620
+
621
+ return image_dtos
622
+ except Exception :
623
+ raise HTTPException (status_code = 500 , detail = "Failed to get image DTOs" )
0 commit comments