diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 6d3829bc71..2435ee2766 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -392,6 +392,7 @@ "viewerImage": "Viewer Image", "compareImage": "Compare Image", "openInViewer": "Open in Viewer", + "searchImages": "Search Images by Metadata...", "selectAllOnPage": "Select All On Page", "selectAllOnBoard": "Select All On Board", "showArchivedBoards": "Show Archived Boards", diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 8f173965a9..b0b147b510 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -13,6 +13,7 @@ import GalleryBoardName from './GalleryBoardName'; import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; import { GalleryPagination } from './ImageGrid/GalleryPagination'; +import { GallerySearch } from './ImageGrid/GallerySearch'; const ImageGalleryContent = () => { const { t } = useTranslation(); @@ -81,6 +82,7 @@ const ImageGalleryContent = () => { + diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index 0be30505d5..cfcf527c6e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -19,10 +19,11 @@ const GalleryImageGrid = () => { useGalleryHotkeys(); const { t } = useTranslation(); const queryArgs = useAppSelector(selectListImagesQueryArgs); - const { imageDTOs, isLoading, isError } = useListImagesQuery(queryArgs, { - selectFromResult: ({ data, isLoading, isSuccess, isError }) => ({ + const { imageDTOs, isLoading, isFetching, isError } = useListImagesQuery(queryArgs, { + selectFromResult: ({ data, isLoading, isFetching, isSuccess, isError }) => ({ imageDTOs: data?.items ?? EMPTY_ARRAY, isLoading, + isFetching, isSuccess, isError, }), @@ -36,7 +37,7 @@ const GalleryImageGrid = () => { ); } - if (isLoading) { + if (isLoading || isFetching) { return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx new file mode 100644 index 0000000000..0221a53533 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySearch.tsx @@ -0,0 +1,57 @@ +import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { searchTermChanged } from 'features/gallery/store/gallerySlice'; +import { debounce } from 'lodash-es'; +import type { ChangeEvent } from 'react'; +import { useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiXBold } from 'react-icons/pi'; + +export const GallerySearch = () => { + const dispatch = useAppDispatch(); + const { searchTerm } = useAppSelector((s) => s.gallery); + const { t } = useTranslation(); + + const [searchTermInput, setSearchTermInput] = useState(searchTerm); + + const debouncedSetSearchTerm = useMemo(() => { + return debounce((value: string) => { + dispatch(searchTermChanged(value)); + }, 1000); + }, [dispatch]); + + const handleChangeInput = useCallback( + (e: ChangeEvent) => { + setSearchTermInput(e.target.value); + debouncedSetSearchTerm(e.target.value); + }, + [debouncedSetSearchTerm] + ); + + const handleClearInput = useCallback(() => { + setSearchTermInput(''); + dispatch(searchTermChanged('')); + }, [dispatch]); + + return ( + + + {searchTermInput && searchTermInput.length && ( + + } + /> + + )} + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts index 832da474ca..76ed4c6659 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts @@ -22,6 +22,7 @@ export const selectListImagesQueryArgs = createMemoizedSelector( is_intermediate: false, starred_first: gallery.starredFirst, order_dir: gallery.orderDir, + search_term: gallery.searchTerm, } : skipToken ); diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index d5d83e53e8..9ae7be7606 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -118,6 +118,9 @@ export const gallerySlice = createSlice({ orderDirChanged: (state, action: PayloadAction) => { state.orderDir = action.payload; }, + searchTermChanged: (state, action: PayloadAction) => { + state.searchTerm = action.payload; + }, }, }); @@ -143,6 +146,7 @@ export const { orderDirChanged, starredFirstChanged, shouldShowArchivedBoardsChanged, + searchTermChanged, } = gallerySlice.actions; export const selectGallerySlice = (state: RootState) => state.gallery; diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts index 16c899e6a7..03d2082d10 100644 --- a/invokeai/frontend/web/src/features/gallery/store/types.ts +++ b/invokeai/frontend/web/src/features/gallery/store/types.ts @@ -22,6 +22,7 @@ export type GalleryState = { limit: number; starredFirst: boolean; orderDir: OrderDir; + searchTerm?: string; alwaysShowImageSizeBadge: boolean; imageToCompare: ImageDTO | null; comparisonMode: ComparisonMode; diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 07f1cdb34b..f64fba6e77 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -7293,145 +7293,145 @@ export type components = { project_id: string | null; }; InvocationOutputMap: { - img_channel_offset: components["schemas"]["ImageOutput"]; - create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; - round_float: components["schemas"]["FloatOutput"]; - img_blur: components["schemas"]["ImageOutput"]; - sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; - lblend: components["schemas"]["LatentsOutput"]; - img_lerp: components["schemas"]["ImageOutput"]; - zoe_depth_image_processor: components["schemas"]["ImageOutput"]; - range_of_size: components["schemas"]["IntegerCollectionOutput"]; - freeu: components["schemas"]["UNetOutput"]; - alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; - latents: components["schemas"]["LatentsOutput"]; - controlnet: components["schemas"]["ControlOutput"]; - canny_image_processor: components["schemas"]["ImageOutput"]; - color_map_image_processor: components["schemas"]["ImageOutput"]; - l2i: components["schemas"]["ImageOutput"]; - string_join_three: components["schemas"]["StringOutput"]; - crop_latents: components["schemas"]["LatentsOutput"]; - img_resize: components["schemas"]["ImageOutput"]; - img_crop: components["schemas"]["ImageOutput"]; - face_off: components["schemas"]["FaceOffOutput"]; - seamless: components["schemas"]["SeamlessModeOutput"]; - string: components["schemas"]["StringOutput"]; - img_watermark: components["schemas"]["ImageOutput"]; - img_paste: components["schemas"]["ImageOutput"]; - ideal_size: components["schemas"]["IdealSizeOutput"]; - img_scale: components["schemas"]["ImageOutput"]; - mul: components["schemas"]["IntegerOutput"]; - conditioning: components["schemas"]["ConditioningOutput"]; - add: components["schemas"]["IntegerOutput"]; - conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; - create_gradient_mask: components["schemas"]["GradientMaskOutput"]; - float: components["schemas"]["FloatOutput"]; - sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; - string_collection: components["schemas"]["StringCollectionOutput"]; - image_mask_to_tensor: components["schemas"]["MaskOutput"]; - infill_cv2: components["schemas"]["ImageOutput"]; - boolean_collection: components["schemas"]["BooleanCollectionOutput"]; - color: components["schemas"]["ColorOutput"]; - lresize: components["schemas"]["LatentsOutput"]; - image: components["schemas"]["ImageOutput"]; - esrgan: components["schemas"]["ImageOutput"]; - image_collection: components["schemas"]["ImageCollectionOutput"]; - metadata: components["schemas"]["MetadataOutput"]; - scheduler: components["schemas"]["SchedulerOutput"]; - img_pad_crop: components["schemas"]["ImageOutput"]; - integer: components["schemas"]["IntegerOutput"]; - boolean: components["schemas"]["BooleanOutput"]; - float_math: components["schemas"]["FloatOutput"]; - sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; - tile_image_processor: components["schemas"]["ImageOutput"]; - img_channel_multiply: components["schemas"]["ImageOutput"]; - tomask: components["schemas"]["ImageOutput"]; - sub: components["schemas"]["IntegerOutput"]; - img_mul: components["schemas"]["ImageOutput"]; - string_split: components["schemas"]["String2Output"]; - model_identifier: components["schemas"]["ModelIdentifierOutput"]; - pair_tile_image: components["schemas"]["PairTileImageOutput"]; - heuristic_resize: components["schemas"]["ImageOutput"]; - infill_lama: components["schemas"]["ImageOutput"]; - lineart_anime_image_processor: components["schemas"]["ImageOutput"]; - float_range: components["schemas"]["FloatCollectionOutput"]; - infill_patchmatch: components["schemas"]["ImageOutput"]; - mediapipe_face_processor: components["schemas"]["ImageOutput"]; - cv_inpaint: components["schemas"]["ImageOutput"]; - denoise_latents: components["schemas"]["LatentsOutput"]; - range: components["schemas"]["IntegerCollectionOutput"]; - content_shuffle_image_processor: components["schemas"]["ImageOutput"]; - compel: components["schemas"]["ConditioningOutput"]; - leres_image_processor: components["schemas"]["ImageOutput"]; - prompt_from_file: components["schemas"]["StringCollectionOutput"]; - noise: components["schemas"]["NoiseOutput"]; - i2l: components["schemas"]["LatentsOutput"]; - pidi_image_processor: components["schemas"]["ImageOutput"]; - merge_metadata: components["schemas"]["MetadataOutput"]; - img_conv: components["schemas"]["ImageOutput"]; - ip_adapter: components["schemas"]["IPAdapterOutput"]; - div: components["schemas"]["IntegerOutput"]; - dw_openpose_image_processor: components["schemas"]["ImageOutput"]; - rand_int: components["schemas"]["IntegerOutput"]; - calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; - show_image: components["schemas"]["ImageOutput"]; - calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; - float_to_int: components["schemas"]["IntegerOutput"]; - infill_rgba: components["schemas"]["ImageOutput"]; - face_mask_detection: components["schemas"]["FaceMaskOutput"]; - sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; - iterate: components["schemas"]["IterateInvocationOutput"]; - rectangle_mask: components["schemas"]["MaskOutput"]; - merge_tiles_to_image: components["schemas"]["ImageOutput"]; - lineart_image_processor: components["schemas"]["ImageOutput"]; - lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; - lora_selector: components["schemas"]["LoRASelectorOutput"]; - midas_depth_image_processor: components["schemas"]["ImageOutput"]; - face_identifier: components["schemas"]["ImageOutput"]; save_image: components["schemas"]["ImageOutput"]; + integer_math: components["schemas"]["IntegerOutput"]; + segment_anything_processor: components["schemas"]["ImageOutput"]; + sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; + zoe_depth_image_processor: components["schemas"]["ImageOutput"]; + collect: components["schemas"]["CollectInvocationOutput"]; + range: components["schemas"]["IntegerCollectionOutput"]; unsharp_mask: components["schemas"]["ImageOutput"]; - string_split_neg: components["schemas"]["StringPosNegOutput"]; - img_chan: components["schemas"]["ImageOutput"]; - float_collection: components["schemas"]["FloatCollectionOutput"]; - lora_loader: components["schemas"]["LoRALoaderOutput"]; - random_range: components["schemas"]["IntegerCollectionOutput"]; - invert_tensor_mask: components["schemas"]["MaskOutput"]; - clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; + string_replace: components["schemas"]["StringOutput"]; + face_identifier: components["schemas"]["ImageOutput"]; + heuristic_resize: components["schemas"]["ImageOutput"]; + range_of_size: components["schemas"]["IntegerCollectionOutput"]; + latents_collection: components["schemas"]["LatentsCollectionOutput"]; + color_map_image_processor: components["schemas"]["ImageOutput"]; + img_ilerp: components["schemas"]["ImageOutput"]; + infill_patchmatch: components["schemas"]["ImageOutput"]; + face_off: components["schemas"]["FaceOffOutput"]; + string_collection: components["schemas"]["StringCollectionOutput"]; + sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"]; string_join: components["schemas"]["StringOutput"]; - img_hue_adjust: components["schemas"]["ImageOutput"]; - img_nsfw: components["schemas"]["ImageOutput"]; + lblend: components["schemas"]["LatentsOutput"]; + conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; + string_split_neg: components["schemas"]["StringPosNegOutput"]; + img_watermark: components["schemas"]["ImageOutput"]; + infill_lama: components["schemas"]["ImageOutput"]; + div: components["schemas"]["IntegerOutput"]; + show_image: components["schemas"]["ImageOutput"]; tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; - calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; - mask_combine: components["schemas"]["ImageOutput"]; - main_model_loader: components["schemas"]["ModelLoaderOutput"]; - img_ilerp: components["schemas"]["ImageOutput"]; - string_replace: components["schemas"]["StringOutput"]; - sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; - hed_image_processor: components["schemas"]["ImageOutput"]; - latents_collection: components["schemas"]["LatentsCollectionOutput"]; - infill_tile: components["schemas"]["ImageOutput"]; - vae_loader: components["schemas"]["VAEOutput"]; - depth_anything_image_processor: components["schemas"]["ImageOutput"]; - lscale: components["schemas"]["LatentsOutput"]; - t2i_adapter: components["schemas"]["T2IAdapterOutput"]; - metadata_item: components["schemas"]["MetadataItemOutput"]; - blank_image: components["schemas"]["ImageOutput"]; - tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; - canvas_paste_back: components["schemas"]["ImageOutput"]; - rand_float: components["schemas"]["FloatOutput"]; - mask_from_id: components["schemas"]["ImageOutput"]; - segment_anything_processor: components["schemas"]["ImageOutput"]; + sub: components["schemas"]["IntegerOutput"]; normalbae_image_processor: components["schemas"]["ImageOutput"]; - mask_edge: components["schemas"]["ImageOutput"]; - dynamic_prompt: components["schemas"]["StringCollectionOutput"]; - color_correct: components["schemas"]["ImageOutput"]; - integer_math: components["schemas"]["IntegerOutput"]; - core_metadata: components["schemas"]["MetadataOutput"]; - integer_collection: components["schemas"]["IntegerCollectionOutput"]; - collect: components["schemas"]["CollectInvocationOutput"]; - mlsd_image_processor: components["schemas"]["ImageOutput"]; + invert_tensor_mask: components["schemas"]["MaskOutput"]; + create_gradient_mask: components["schemas"]["GradientMaskOutput"]; + string_split: components["schemas"]["String2Output"]; step_param_easing: components["schemas"]["FloatCollectionOutput"]; + metadata: components["schemas"]["MetadataOutput"]; + img_pad_crop: components["schemas"]["ImageOutput"]; + integer: components["schemas"]["IntegerOutput"]; + img_mul: components["schemas"]["ImageOutput"]; + calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; + color: components["schemas"]["ColorOutput"]; + infill_rgba: components["schemas"]["ImageOutput"]; + t2i_adapter: components["schemas"]["T2IAdapterOutput"]; + denoise_latents: components["schemas"]["LatentsOutput"]; + img_lerp: components["schemas"]["ImageOutput"]; + img_channel_offset: components["schemas"]["ImageOutput"]; + img_crop: components["schemas"]["ImageOutput"]; + alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; + color_correct: components["schemas"]["ImageOutput"]; + calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; + img_hue_adjust: components["schemas"]["ImageOutput"]; + lresize: components["schemas"]["LatentsOutput"]; + img_blur: components["schemas"]["ImageOutput"]; + compel: components["schemas"]["ConditioningOutput"]; + sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + float_to_int: components["schemas"]["IntegerOutput"]; + boolean: components["schemas"]["BooleanOutput"]; + string_join_three: components["schemas"]["StringOutput"]; + add: components["schemas"]["IntegerOutput"]; + merge_tiles_to_image: components["schemas"]["ImageOutput"]; + core_metadata: components["schemas"]["MetadataOutput"]; + lscale: components["schemas"]["LatentsOutput"]; + mlsd_image_processor: components["schemas"]["ImageOutput"]; + image_collection: components["schemas"]["ImageCollectionOutput"]; + crop_latents: components["schemas"]["LatentsOutput"]; + image_mask_to_tensor: components["schemas"]["MaskOutput"]; + lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; + ip_adapter: components["schemas"]["IPAdapterOutput"]; + pidi_image_processor: components["schemas"]["ImageOutput"]; + rand_int: components["schemas"]["IntegerOutput"]; + img_conv: components["schemas"]["ImageOutput"]; + scheduler: components["schemas"]["SchedulerOutput"]; + img_paste: components["schemas"]["ImageOutput"]; + noise: components["schemas"]["NoiseOutput"]; + img_scale: components["schemas"]["ImageOutput"]; + i2l: components["schemas"]["LatentsOutput"]; + main_model_loader: components["schemas"]["ModelLoaderOutput"]; + blank_image: components["schemas"]["ImageOutput"]; + mask_edge: components["schemas"]["ImageOutput"]; + seamless: components["schemas"]["SeamlessModeOutput"]; + esrgan: components["schemas"]["ImageOutput"]; + canvas_paste_back: components["schemas"]["ImageOutput"]; + mul: components["schemas"]["IntegerOutput"]; + dynamic_prompt: components["schemas"]["StringCollectionOutput"]; + controlnet: components["schemas"]["ControlOutput"]; + l2i: components["schemas"]["ImageOutput"]; + ideal_size: components["schemas"]["IdealSizeOutput"]; + latents: components["schemas"]["LatentsOutput"]; + midas_depth_image_processor: components["schemas"]["ImageOutput"]; + tomask: components["schemas"]["ImageOutput"]; + float_math: components["schemas"]["FloatOutput"]; + round_float: components["schemas"]["FloatOutput"]; + cv_inpaint: components["schemas"]["ImageOutput"]; + create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; + model_identifier: components["schemas"]["ModelIdentifierOutput"]; + pair_tile_image: components["schemas"]["PairTileImageOutput"]; + lineart_image_processor: components["schemas"]["ImageOutput"]; + img_nsfw: components["schemas"]["ImageOutput"]; + infill_cv2: components["schemas"]["ImageOutput"]; + clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; + dw_openpose_image_processor: components["schemas"]["ImageOutput"]; + img_resize: components["schemas"]["ImageOutput"]; + iterate: components["schemas"]["IterateInvocationOutput"]; + rectangle_mask: components["schemas"]["MaskOutput"]; + canny_image_processor: components["schemas"]["ImageOutput"]; + calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; + mask_from_id: components["schemas"]["ImageOutput"]; + metadata_item: components["schemas"]["MetadataItemOutput"]; + infill_tile: components["schemas"]["ImageOutput"]; + tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; + img_channel_multiply: components["schemas"]["ImageOutput"]; + boolean_collection: components["schemas"]["BooleanCollectionOutput"]; + lora_loader: components["schemas"]["LoRALoaderOutput"]; + float_collection: components["schemas"]["FloatCollectionOutput"]; + string: components["schemas"]["StringOutput"]; + freeu: components["schemas"]["UNetOutput"]; + lineart_anime_image_processor: components["schemas"]["ImageOutput"]; + depth_anything_image_processor: components["schemas"]["ImageOutput"]; + image: components["schemas"]["ImageOutput"]; + face_mask_detection: components["schemas"]["FaceMaskOutput"]; + rand_float: components["schemas"]["FloatOutput"]; + float: components["schemas"]["FloatOutput"]; + random_range: components["schemas"]["IntegerCollectionOutput"]; + integer_collection: components["schemas"]["IntegerCollectionOutput"]; + sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; + mask_combine: components["schemas"]["ImageOutput"]; + tile_image_processor: components["schemas"]["ImageOutput"]; + img_chan: components["schemas"]["ImageOutput"]; + vae_loader: components["schemas"]["VAEOutput"]; + prompt_from_file: components["schemas"]["StringCollectionOutput"]; + float_range: components["schemas"]["FloatCollectionOutput"]; + merge_metadata: components["schemas"]["MetadataOutput"]; + sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; + hed_image_processor: components["schemas"]["ImageOutput"]; + lora_selector: components["schemas"]["LoRASelectorOutput"]; + conditioning: components["schemas"]["ConditioningOutput"]; + leres_image_processor: components["schemas"]["ImageOutput"]; + mediapipe_face_processor: components["schemas"]["ImageOutput"]; + content_shuffle_image_processor: components["schemas"]["ImageOutput"]; }; /** * InvocationStartedEvent @@ -14836,6 +14836,8 @@ export type operations = { order_dir?: components["schemas"]["SQLiteDirection"]; /** @description Whether to sort by starred images first */ starred_first?: boolean; + /** @description The term to search for */ + search_term?: string | null; }; }; responses: {