Skip to content
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

feat(ui, api): gallery search #6561

Merged
merged 11 commits into from
Jul 1, 2024
10 changes: 2 additions & 8 deletions invokeai/app/api/routers/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,12 @@ async def list_image_dtos(
limit: int = Query(default=10, description="The number of images per page"),
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"),
) -> OffsetPaginatedResults[ImageDTO]:
"""Gets a list of image DTOs"""

image_dtos = ApiDependencies.invoker.services.images.get_many(
offset,
limit,
starred_first,
order_dir,
image_origin,
categories,
is_intermediate,
board_id,
offset, limit, starred_first, order_dir, image_origin, categories, is_intermediate, board_id, search_term
)

return image_dtos
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def get_many(
categories: Optional[list[ImageCategory]] = None,
is_intermediate: Optional[bool] = None,
board_id: Optional[str] = None,
search_term: Optional[str] = None,
) -> OffsetPaginatedResults[ImageRecord]:
"""Gets a page of image records."""
pass
Expand Down
8 changes: 8 additions & 0 deletions invokeai/app/services/image_records/image_records_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def get_many(
categories: Optional[list[ImageCategory]] = None,
is_intermediate: Optional[bool] = None,
board_id: Optional[str] = None,
search_term: Optional[str] = None,
) -> OffsetPaginatedResults[ImageRecord]:
try:
self._lock.acquire()
Expand Down Expand Up @@ -211,6 +212,13 @@ def get_many(
"""
query_params.append(board_id)

# Search term condition
if search_term:
query_conditions += """--sql
AND images.metadata LIKE ?
"""
query_params.append(f"%{search_term.lower()}%")

if starred_first:
query_pagination = f"""--sql
ORDER BY images.starred DESC, images.created_at {order_dir.value} LIMIT ? OFFSET ?
Expand Down
1 change: 1 addition & 0 deletions invokeai/app/services/images/images_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def get_many(
categories: Optional[list[ImageCategory]] = None,
is_intermediate: Optional[bool] = None,
board_id: Optional[str] = None,
search_term: Optional[str] = None,
) -> OffsetPaginatedResults[ImageDTO]:
"""Gets a paginated list of image DTOs."""
pass
Expand Down
2 changes: 2 additions & 0 deletions invokeai/app/services/images/images_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def get_many(
categories: Optional[list[ImageCategory]] = None,
is_intermediate: Optional[bool] = None,
board_id: Optional[str] = None,
search_term: Optional[str] = None,
) -> OffsetPaginatedResults[ImageDTO]:
try:
results = self.__invoker.services.image_records.get_many(
Expand All @@ -225,6 +226,7 @@ def get_many(
categories,
is_intermediate,
board_id,
search_term,
)

image_dtos = [
Expand Down
1 change: 1 addition & 0 deletions invokeai/frontend/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@
"viewerImage": "Viewer Image",
"compareImage": "Compare Image",
"openInViewer": "Open in Viewer",
"searchImages": "Search by Metadata",
"selectAllOnPage": "Select All On Page",
"selectAllOnBoard": "Select All On Board",
"showArchivedBoards": "Show Archived Boards",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -81,6 +82,7 @@ const ImageGalleryContent = () => {
</Tabs>
</Flex>

<GallerySearch />
psychedelicious marked this conversation as resolved.
Show resolved Hide resolved
<GalleryImageGrid />
<GalleryPagination />
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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<HTMLInputElement>) => {
setSearchTermInput(e.target.value);
debouncedSetSearchTerm(e.target.value);
},
[debouncedSetSearchTerm]
);

const handleClearInput = useCallback(() => {
setSearchTermInput('');
dispatch(searchTermChanged(''));
}, [dispatch]);

return (
<InputGroup>
<Input
placeholder={t('gallery.searchImages')}
value={searchTermInput}
onChange={handleChangeInput}
data-testid="image-search-input"
/>
{searchTermInput.length && (
<InputRightElement h="full" pe={2}>
<IconButton
onClick={handleClearInput}
size="sm"
variant="link"
aria-label={t('boards.clearSearch')}
icon={<PiXBold />}
/>
</InputRightElement>
)}
</InputGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const selectListImagesQueryArgs = createMemoizedSelector(
is_intermediate: false,
starred_first: gallery.starredFirst,
order_dir: gallery.orderDir,
search_term: gallery.searchTerm,
}
: skipToken
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const initialGalleryState: GalleryState = {
offset: 0,
starredFirst: true,
orderDir: 'ASC',
searchTerm: '',
isImageViewerOpen: true,
imageToCompare: null,
comparisonMode: 'slider',
Expand Down Expand Up @@ -118,6 +119,9 @@ export const gallerySlice = createSlice({
orderDirChanged: (state, action: PayloadAction<OrderDir>) => {
state.orderDir = action.payload;
},
searchTermChanged: (state, action: PayloadAction<string>) => {
state.searchTerm = action.payload;
},
},
});

Expand All @@ -143,6 +147,7 @@ export const {
orderDirChanged,
starredFirstChanged,
shouldShowArchivedBoardsChanged,
searchTermChanged,
} = gallerySlice.actions;

export const selectGallerySlice = (state: RootState) => state.gallery;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type GalleryState = {
limit: number;
starredFirst: boolean;
orderDir: OrderDir;
searchTerm: string;
alwaysShowImageSizeBadge: boolean;
imageToCompare: ImageDTO | null;
comparisonMode: ComparisonMode;
Expand Down
Loading
Loading