1414from invokeai .app .invocations .fields import MetadataField
1515from invokeai .app .services .image_records .image_records_common import (
1616 ImageCategory ,
17+ ImageNamesResult ,
1718 ImageRecordChanges ,
1819 ResourceOrigin ,
1920)
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+ )
2128from invokeai .app .services .shared .pagination import OffsetPaginatedResults
2229from invokeai .app .services .shared .sqlite .sqlite_common import SQLiteDirection
2330from invokeai .app .util .controlnet_utils import heuristic_resize_fast
@@ -153,18 +160,30 @@ async def create_image_upload_entry(
153160 raise HTTPException (status_code = 501 , detail = "Not implemented" )
154161
155162
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 )
157164async def delete_image (
158165 image_name : str = Path (description = "The name of the image to delete" ),
159- ) -> None :
166+ ) -> DeleteImagesResult :
160167 """Deletes an image"""
161168
169+ deleted_images : set [str ] = set ()
170+ affected_boards : set [str ] = set ()
171+
162172 try :
173+ image_dto = ApiDependencies .invoker .services .images .get_dto (image_name )
174+ board_id = image_dto .board_id or "none"
163175 ApiDependencies .invoker .services .images .delete (image_name )
176+ deleted_images .add (image_name )
177+ affected_boards .add (board_id )
164178 except Exception :
165179 # TODO: Does this need any exception handling at all?
166180 pass
167181
182+ return DeleteImagesResult (
183+ deleted_images = list (deleted_images ),
184+ affected_boards = list (affected_boards ),
185+ )
186+
168187
169188@images_router .delete ("/intermediates" , operation_id = "clear_intermediates" )
170189async def clear_intermediates () -> int :
@@ -376,46 +395,52 @@ async def list_image_dtos(
376395 return image_dtos
377396
378397
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 )
384399async def delete_images_from_list (
385400 image_names : list [str ] = Body (description = "The list of names of images to delete" , embed = True ),
386- ) -> DeleteImagesFromListResult :
401+ ) -> DeleteImagesResult :
387402 try :
388- deleted_images : list [str ] = []
403+ deleted_images : set [str ] = set ()
404+ affected_boards : set [str ] = set ()
389405 for image_name in image_names :
390406 try :
407+ image_dto = ApiDependencies .invoker .services .images .get_dto (image_name )
408+ board_id = image_dto .board_id or "none"
391409 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 )
393412 except Exception :
394413 pass
395- return DeleteImagesFromListResult (deleted_images = deleted_images )
414+ return DeleteImagesResult (
415+ deleted_images = list (deleted_images ),
416+ affected_boards = list (affected_boards ),
417+ )
396418 except Exception :
397419 raise HTTPException (status_code = 500 , detail = "Failed to delete images" )
398420
399421
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 :
404424 """Deletes all images that are uncategorized"""
405425
406426 image_names = ApiDependencies .invoker .services .board_images .get_all_board_image_names_for_board (
407427 board_id = "none" , categories = None , is_intermediate = None
408428 )
409429
410430 try :
411- deleted_images : list [str ] = []
431+ deleted_images : set [str ] = set ()
432+ affected_boards : set [str ] = set ()
412433 for image_name in image_names :
413434 try :
414435 ApiDependencies .invoker .services .images .delete (image_name )
415- deleted_images .append (image_name )
436+ deleted_images .add (image_name )
437+ affected_boards .add ("none" )
416438 except Exception :
417439 pass
418- return DeleteImagesFromListResult (deleted_images = deleted_images )
440+ return DeleteImagesResult (
441+ deleted_images = list (deleted_images ),
442+ affected_boards = list (affected_boards ),
443+ )
419444 except Exception :
420445 raise HTTPException (status_code = 500 , detail = "Failed to delete images" )
421446
@@ -424,36 +449,50 @@ class ImagesUpdatedFromListResult(BaseModel):
424449 updated_image_names : list [str ] = Field (description = "The image names that were updated" )
425450
426451
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 )
428453async def star_images_in_list (
429454 image_names : list [str ] = Body (description = "The list of names of images to star" , embed = True ),
430- ) -> ImagesUpdatedFromListResult :
455+ ) -> StarredImagesResult :
431456 try :
432- updated_image_names : list [str ] = []
457+ starred_images : set [str ] = set ()
458+ affected_boards : set [str ] = set ()
433459 for image_name in image_names :
434460 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" )
437466 except Exception :
438467 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+ )
440472 except Exception :
441473 raise HTTPException (status_code = 500 , detail = "Failed to star images" )
442474
443475
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 )
445477async def unstar_images_in_list (
446478 image_names : list [str ] = Body (description = "The list of names of images to unstar" , embed = True ),
447- ) -> ImagesUpdatedFromListResult :
479+ ) -> UnstarredImagesResult :
448480 try :
449- updated_image_names : list [str ] = []
481+ unstarred_images : set [str ] = set ()
482+ affected_boards : set [str ] = set ()
450483 for image_name in image_names :
451484 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" )
454490 except Exception :
455491 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+ )
457496 except Exception :
458497 raise HTTPException (status_code = 500 , detail = "Failed to unstar images" )
459498
@@ -524,3 +563,61 @@ async def get_bulk_download_item(
524563 return response
525564 except Exception :
526565 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