mvp gallery search

This commit is contained in:
Mary Hipp 2024-06-25 20:26:54 -04:00
parent 9d86c2e2c1
commit e3e8d689d7
8 changed files with 266 additions and 152 deletions

View File

@ -384,7 +384,9 @@
"problemDeletingImagesDesc": "One or more images could not be deleted",
"viewerImage": "Viewer Image",
"compareImage": "Compare Image",
"noActiveSearch": "No active search",
"openInViewer": "Open in Viewer",
"searchingBy": "Searching by",
"selectAllOnPage": "Select All On Page",
"selectAllOnBoard": "Select All On Board",
"selectForCompare": "Select for Compare",

View File

@ -6,6 +6,8 @@ import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { BiSelectMultiple } from 'react-icons/bi';
import { GallerySearch } from './GallerySearch';
export const GalleryBulkSelect = () => {
const dispatch = useAppDispatch();
const { selection } = useAppSelector((s) => s.gallery);
@ -22,28 +24,32 @@ export const GalleryBulkSelect = () => {
return (
<Flex alignItems="center" justifyContent="space-between">
{selection.length > 0 ? (
<Tag>
<TagLabel>
{selection.length} {t('common.selected')}
</TagLabel>
<Tooltip label="Clear selection">
<TagCloseButton onClick={onClickClearSelection} />
</Tooltip>
</Tag>
) : (
<Spacer />
)}
<Flex>
{selection.length > 0 ? (
<Tag>
<TagLabel>
{selection.length} {t('common.selected')}
</TagLabel>
<Tooltip label="Clear selection">
<TagCloseButton onClick={onClickClearSelection} />
</Tooltip>
</Tag>
) : (
<Spacer />
)}
<Tooltip label={t('gallery.selectAllOnPage')}>
<IconButton
variant="outline"
size="sm"
icon={<BiSelectMultiple />}
aria-label="Bulk select"
onClick={onClickSelectAllPage}
/>
</Tooltip>
<Tooltip label={t('gallery.selectAllOnPage')}>
<IconButton
variant="outline"
size="sm"
icon={<BiSelectMultiple />}
aria-label="Bulk select"
onClick={onClickSelectAllPage}
/>
</Tooltip>
</Flex>
<GallerySearch />
</Flex>
);
};

View File

@ -0,0 +1,97 @@
import { Flex, IconButton, Input, InputGroup, InputRightElement, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { searchTermChanged } from 'features/gallery/store/gallerySlice';
import { motion } from 'framer-motion';
import { debounce } from 'lodash-es';
import type { ChangeEvent } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiMagnifyingGlassBold, PiXBold } from 'react-icons/pi';
export const GallerySearch = () => {
const dispatch = useAppDispatch();
const { searchTerm } = useAppSelector((s) => s.gallery);
const { t } = useTranslation();
const [expanded, setExpanded] = useState(false);
const [searchTermInput, setSearchTermInput] = useState('');
const debouncedSetSearchTerm = useMemo(() => {
return debounce((value: string) => {
dispatch(searchTermChanged(value));
}, 1000);
}, [dispatch]);
const onChangeInput = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setSearchTermInput(e.target.value);
debouncedSetSearchTerm(e.target.value);
},
[debouncedSetSearchTerm]
);
const onClearInput = useCallback(() => {
setSearchTermInput('');
debouncedSetSearchTerm('');
}, [debouncedSetSearchTerm]);
const toggleExpanded = useCallback((newState: boolean) => {
setExpanded(newState);
}, []);
return (
<Flex>
{!expanded && (
<Tooltip
label={
searchTerm && searchTerm.length ? `${t('gallery.searchingBy')} ${searchTerm}` : t('gallery.noActiveSearch')
}
>
<IconButton
aria-label="Close"
icon={<PiMagnifyingGlassBold />}
onClick={toggleExpanded.bind(null, true)}
variant="outline"
size="sm"
/>
</Tooltip>
)}
<motion.div
initial={false}
animate={{ width: expanded ? '200px' : '0px' }}
transition={{ duration: 0.3 }}
style={{ overflow: 'hidden' }}
>
<InputGroup size="sm">
<IconButton
aria-label="Close"
icon={<PiMagnifyingGlassBold />}
onClick={toggleExpanded.bind(null, false)}
variant="ghost"
size="sm"
/>
<Input
type="text"
placeholder="Search..."
size="sm"
variant="outline"
onChange={onChangeInput}
value={searchTermInput}
/>
{searchTermInput && searchTermInput.length && (
<InputRightElement h="full" pe={2}>
<IconButton
onClick={onClearInput}
size="sm"
variant="link"
aria-label={t('boards.clearSearch')}
icon={<PiXBold />}
/>
</InputRightElement>
)}
</InputGroup>
</motion.div>
</Flex>
);
};

View File

@ -18,12 +18,13 @@ const GalleryImageGrid = () => {
useGalleryHotkeys();
const { t } = useTranslation();
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const { imageDTOs, isLoading, isError } = useListImagesQuery(queryArgs, {
selectFromResult: ({ data, isLoading, isSuccess, isError }) => ({
const { imageDTOs, isLoading, isError, isFetching } = useListImagesQuery(queryArgs, {
selectFromResult: ({ data, isLoading, isSuccess, isError, isFetching }) => ({
imageDTOs: data?.items ?? EMPTY_ARRAY,
isLoading,
isSuccess,
isError,
isFetching,
}),
});
@ -35,7 +36,7 @@ const GalleryImageGrid = () => {
);
}
if (isLoading) {
if (isLoading || isFetching) {
return (
<Flex w="full" h="full" alignItems="center" justifyContent="center">
<IAINoContentFallback label={t('gallery.loading')} icon={PiImageBold} />

View File

@ -20,6 +20,7 @@ export const selectListImagesQueryArgs = createMemoizedSelector(
offset: gallery.offset,
limit: gallery.limit,
is_intermediate: false,
search_term: gallery.searchTerm,
}
: skipToken
);

View File

@ -110,6 +110,9 @@ export const gallerySlice = createSlice({
limitChanged: (state, action: PayloadAction<number>) => {
state.limit = action.payload;
},
searchTermChanged: (state, action: PayloadAction<string | undefined>) => {
state.searchTerm = action.payload;
},
},
extraReducers: (builder) => {
builder.addMatcher(isAnyBoardDeleted, (state, action) => {
@ -154,6 +157,7 @@ export const {
comparisonModeCycled,
offsetChanged,
limitChanged,
searchTermChanged,
} = gallerySlice.actions;
const isAnyBoardDeleted = isAnyOf(

View File

@ -20,6 +20,7 @@ export type GalleryState = {
boardSearchText: string;
offset: number;
limit: number;
searchTerm?: string;
alwaysShowImageSizeBadge: boolean;
imageToCompare: ImageDTO | null;
comparisonMode: ComparisonMode;

View File

@ -7283,144 +7283,144 @@ export type components = {
project_id: string | null;
};
InvocationOutputMap: {
midas_depth_image_processor: components["schemas"]["ImageOutput"];
lscale: components["schemas"]["LatentsOutput"];
string_split: components["schemas"]["String2Output"];
mask_edge: components["schemas"]["ImageOutput"];
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
color_correct: components["schemas"]["ImageOutput"];
save_image: components["schemas"]["ImageOutput"];
show_image: components["schemas"]["ImageOutput"];
segment_anything_processor: components["schemas"]["ImageOutput"];
latents: components["schemas"]["LatentsOutput"];
lineart_image_processor: components["schemas"]["ImageOutput"];
hed_image_processor: components["schemas"]["ImageOutput"];
infill_lama: components["schemas"]["ImageOutput"];
infill_patchmatch: components["schemas"]["ImageOutput"];
float_collection: components["schemas"]["FloatCollectionOutput"];
denoise_latents: components["schemas"]["LatentsOutput"];
metadata: components["schemas"]["MetadataOutput"];
compel: components["schemas"]["ConditioningOutput"];
img_blur: components["schemas"]["ImageOutput"];
img_crop: components["schemas"]["ImageOutput"];
image_mask_to_tensor: components["schemas"]["MaskOutput"];
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
img_ilerp: components["schemas"]["ImageOutput"];
img_paste: components["schemas"]["ImageOutput"];
core_metadata: components["schemas"]["MetadataOutput"];
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
lora_selector: components["schemas"]["LoRASelectorOutput"];
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
rectangle_mask: components["schemas"]["MaskOutput"];
noise: components["schemas"]["NoiseOutput"];
float_to_int: components["schemas"]["IntegerOutput"];
esrgan: components["schemas"]["ImageOutput"];
merge_tiles_to_image: components["schemas"]["ImageOutput"];
prompt_from_file: components["schemas"]["StringCollectionOutput"];
infill_rgba: components["schemas"]["ImageOutput"];
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
lora_loader: components["schemas"]["LoRALoaderOutput"];
iterate: components["schemas"]["IterateInvocationOutput"];
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
color_map_image_processor: components["schemas"]["ImageOutput"];
blank_image: components["schemas"]["ImageOutput"];
normalbae_image_processor: components["schemas"]["ImageOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
string_split_neg: components["schemas"]["StringPosNegOutput"];
img_channel_offset: components["schemas"]["ImageOutput"];
face_mask_detection: components["schemas"]["FaceMaskOutput"];
cv_inpaint: components["schemas"]["ImageOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
latents_collection: components["schemas"]["LatentsCollectionOutput"];
metadata: components["schemas"]["MetadataOutput"];
invert_tensor_mask: components["schemas"]["MaskOutput"];
tomask: components["schemas"]["ImageOutput"];
main_model_loader: components["schemas"]["ModelLoaderOutput"];
img_watermark: components["schemas"]["ImageOutput"];
img_pad_crop: components["schemas"]["ImageOutput"];
random_range: components["schemas"]["IntegerCollectionOutput"];
mlsd_image_processor: components["schemas"]["ImageOutput"];
merge_metadata: components["schemas"]["MetadataOutput"];
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
string_split: components["schemas"]["String2Output"];
integer_collection: components["schemas"]["IntegerCollectionOutput"];
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
noise: components["schemas"]["NoiseOutput"];
float_math: components["schemas"]["FloatOutput"];
seamless: components["schemas"]["SeamlessModeOutput"];
img_lerp: components["schemas"]["ImageOutput"];
img_blur: components["schemas"]["ImageOutput"];
string_join: components["schemas"]["StringOutput"];
vae_loader: components["schemas"]["VAEOutput"];
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
mask_from_id: components["schemas"]["ImageOutput"];
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
img_resize: components["schemas"]["ImageOutput"];
string_replace: components["schemas"]["StringOutput"];
face_identifier: components["schemas"]["ImageOutput"];
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
mul: components["schemas"]["IntegerOutput"];
l2i: components["schemas"]["ImageOutput"];
img_chan: components["schemas"]["ImageOutput"];
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
blank_image: components["schemas"]["ImageOutput"];
ip_adapter: components["schemas"]["IPAdapterOutput"];
tile_image_processor: components["schemas"]["ImageOutput"];
integer_math: components["schemas"]["IntegerOutput"];
infill_tile: components["schemas"]["ImageOutput"];
color_correct: components["schemas"]["ImageOutput"];
show_image: components["schemas"]["ImageOutput"];
float: components["schemas"]["FloatOutput"];
prompt_from_file: components["schemas"]["StringCollectionOutput"];
merge_metadata: components["schemas"]["MetadataOutput"];
img_scale: components["schemas"]["ImageOutput"];
string_join_three: components["schemas"]["StringOutput"];
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
freeu: components["schemas"]["UNetOutput"];
img_channel_multiply: components["schemas"]["ImageOutput"];
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
img_conv: components["schemas"]["ImageOutput"];
latents: components["schemas"]["LatentsOutput"];
face_mask_detection: components["schemas"]["FaceMaskOutput"];
canny_image_processor: components["schemas"]["ImageOutput"];
collect: components["schemas"]["CollectInvocationOutput"];
infill_tile: components["schemas"]["ImageOutput"];
integer_collection: components["schemas"]["IntegerCollectionOutput"];
img_lerp: components["schemas"]["ImageOutput"];
step_param_easing: components["schemas"]["FloatCollectionOutput"];
lresize: components["schemas"]["LatentsOutput"];
img_mul: components["schemas"]["ImageOutput"];
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
img_scale: components["schemas"]["ImageOutput"];
rand_float: components["schemas"]["FloatOutput"];
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
range_of_size: components["schemas"]["IntegerCollectionOutput"];
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
heuristic_resize: components["schemas"]["ImageOutput"];
controlnet: components["schemas"]["ControlOutput"];
string: components["schemas"]["StringOutput"];
tile_image_processor: components["schemas"]["ImageOutput"];
metadata_item: components["schemas"]["MetadataItemOutput"];
freeu: components["schemas"]["UNetOutput"];
round_float: components["schemas"]["FloatOutput"];
conditioning: components["schemas"]["ConditioningOutput"];
ideal_size: components["schemas"]["IdealSizeOutput"];
float: components["schemas"]["FloatOutput"];
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
integer_math: components["schemas"]["IntegerOutput"];
string_collection: components["schemas"]["StringCollectionOutput"];
img_conv: components["schemas"]["ImageOutput"];
img_channel_multiply: components["schemas"]["ImageOutput"];
lblend: components["schemas"]["LatentsOutput"];
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
color: components["schemas"]["ColorOutput"];
image: components["schemas"]["ImageOutput"];
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
image_collection: components["schemas"]["ImageCollectionOutput"];
model_identifier: components["schemas"]["ModelIdentifierOutput"];
l2i: components["schemas"]["ImageOutput"];
seamless: components["schemas"]["SeamlessModeOutput"];
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
string_join_three: components["schemas"]["StringOutput"];
ip_adapter: components["schemas"]["IPAdapterOutput"];
add: components["schemas"]["IntegerOutput"];
crop_latents: components["schemas"]["LatentsOutput"];
float_range: components["schemas"]["FloatCollectionOutput"];
mul: components["schemas"]["IntegerOutput"];
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
boolean: components["schemas"]["BooleanOutput"];
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
mediapipe_face_processor: components["schemas"]["ImageOutput"];
i2l: components["schemas"]["LatentsOutput"];
latents_collection: components["schemas"]["LatentsCollectionOutput"];
integer: components["schemas"]["IntegerOutput"];
img_chan: components["schemas"]["ImageOutput"];
pair_tile_image: components["schemas"]["PairTileImageOutput"];
unsharp_mask: components["schemas"]["ImageOutput"];
img_hue_adjust: components["schemas"]["ImageOutput"];
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
face_off: components["schemas"]["FaceOffOutput"];
mask_combine: components["schemas"]["ImageOutput"];
leres_image_processor: components["schemas"]["ImageOutput"];
image_mask_to_tensor: components["schemas"]["MaskOutput"];
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
scheduler: components["schemas"]["SchedulerOutput"];
sub: components["schemas"]["IntegerOutput"];
pidi_image_processor: components["schemas"]["ImageOutput"];
infill_cv2: components["schemas"]["ImageOutput"];
div: components["schemas"]["IntegerOutput"];
img_nsfw: components["schemas"]["ImageOutput"];
depth_anything_image_processor: components["schemas"]["ImageOutput"];
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
range: components["schemas"]["IntegerCollectionOutput"];
range_of_size: components["schemas"]["IntegerCollectionOutput"];
img_resize: components["schemas"]["ImageOutput"];
img_watermark: components["schemas"]["ImageOutput"];
esrgan: components["schemas"]["ImageOutput"];
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
img_paste: components["schemas"]["ImageOutput"];
face_identifier: components["schemas"]["ImageOutput"];
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
round_float: components["schemas"]["FloatOutput"];
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
lscale: components["schemas"]["LatentsOutput"];
rand_int: components["schemas"]["IntegerOutput"];
float_math: components["schemas"]["FloatOutput"];
infill_cv2: components["schemas"]["ImageOutput"];
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
img_nsfw: components["schemas"]["ImageOutput"];
main_model_loader: components["schemas"]["ModelLoaderOutput"];
tomask: components["schemas"]["ImageOutput"];
string_replace: components["schemas"]["StringOutput"];
face_off: components["schemas"]["FaceOffOutput"];
string: components["schemas"]["StringOutput"];
heuristic_resize: components["schemas"]["ImageOutput"];
midas_depth_image_processor: components["schemas"]["ImageOutput"];
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
mask_combine: components["schemas"]["ImageOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
image: components["schemas"]["ImageOutput"];
infill_rgba: components["schemas"]["ImageOutput"];
img_hue_adjust: components["schemas"]["ImageOutput"];
vae_loader: components["schemas"]["VAEOutput"];
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
segment_anything_processor: components["schemas"]["ImageOutput"];
sub: components["schemas"]["IntegerOutput"];
iterate: components["schemas"]["IterateInvocationOutput"];
img_mul: components["schemas"]["ImageOutput"];
denoise_latents: components["schemas"]["LatentsOutput"];
lineart_image_processor: components["schemas"]["ImageOutput"];
rand_float: components["schemas"]["FloatOutput"];
rectangle_mask: components["schemas"]["MaskOutput"];
lora_selector: components["schemas"]["LoRASelectorOutput"];
pair_tile_image: components["schemas"]["PairTileImageOutput"];
cv_inpaint: components["schemas"]["ImageOutput"];
hed_image_processor: components["schemas"]["ImageOutput"];
range: components["schemas"]["IntegerCollectionOutput"];
img_pad_crop: components["schemas"]["ImageOutput"];
string_split_neg: components["schemas"]["StringPosNegOutput"];
string_collection: components["schemas"]["StringCollectionOutput"];
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
save_image: components["schemas"]["ImageOutput"];
img_ilerp: components["schemas"]["ImageOutput"];
compel: components["schemas"]["ConditioningOutput"];
unsharp_mask: components["schemas"]["ImageOutput"];
image_collection: components["schemas"]["ImageCollectionOutput"];
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
float_to_int: components["schemas"]["IntegerOutput"];
random_range: components["schemas"]["IntegerCollectionOutput"];
ideal_size: components["schemas"]["IdealSizeOutput"];
i2l: components["schemas"]["LatentsOutput"];
infill_patchmatch: components["schemas"]["ImageOutput"];
depth_anything_image_processor: components["schemas"]["ImageOutput"];
infill_lama: components["schemas"]["ImageOutput"];
mask_from_id: components["schemas"]["ImageOutput"];
conditioning: components["schemas"]["ConditioningOutput"];
lresize: components["schemas"]["LatentsOutput"];
step_param_easing: components["schemas"]["FloatCollectionOutput"];
metadata_item: components["schemas"]["MetadataItemOutput"];
controlnet: components["schemas"]["ControlOutput"];
merge_tiles_to_image: components["schemas"]["ImageOutput"];
boolean: components["schemas"]["BooleanOutput"];
core_metadata: components["schemas"]["MetadataOutput"];
img_channel_offset: components["schemas"]["ImageOutput"];
model_identifier: components["schemas"]["ModelIdentifierOutput"];
scheduler: components["schemas"]["SchedulerOutput"];
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
color_map_image_processor: components["schemas"]["ImageOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
mask_edge: components["schemas"]["ImageOutput"];
lora_loader: components["schemas"]["LoRALoaderOutput"];
float_collection: components["schemas"]["FloatCollectionOutput"];
float_range: components["schemas"]["FloatCollectionOutput"];
normalbae_image_processor: components["schemas"]["ImageOutput"];
lblend: components["schemas"]["LatentsOutput"];
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
leres_image_processor: components["schemas"]["ImageOutput"];
add: components["schemas"]["IntegerOutput"];
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
img_crop: components["schemas"]["ImageOutput"];
integer: components["schemas"]["IntegerOutput"];
crop_latents: components["schemas"]["LatentsOutput"];
mlsd_image_processor: components["schemas"]["ImageOutput"];
};
/**
* InvocationStartedEvent
@ -14108,7 +14108,7 @@ export type operations = {
install_hugging_face_model: {
parameters: {
query: {
/** @description Hugging Face repo_id to install */
/** @description HuggingFace repo_id to install */
source: string;
};
};
@ -14698,6 +14698,8 @@ export type operations = {
offset?: number;
/** @description The number of images per page */
limit?: number;
/** @description The term to search for */
search_term?: string | null;
};
};
responses: {