From 5578660ccbdc472e2447c6ec0849e8d809e5ce03 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Jul 2024 07:17:29 +1000 Subject: [PATCH 01/37] fix(ui): reset page when search term changes --- invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index c94115ecfc..8c0c467218 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -122,6 +122,7 @@ export const gallerySlice = createSlice({ }, searchTermChanged: (state, action: PayloadAction) => { state.searchTerm = action.payload; + state.offset = 0; }, }, }); From 7c01b69c12d6e7162d940925e354d656fb8eaee8 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:52:44 +1000 Subject: [PATCH 02/37] fix(ui): revise image selection after deletion - For single image deletion, select the image in the same slot as the deleted image - For multiple image deletion, empty selection - On list images, if no images are currently selected, select the first image --- .../middleware/listenerMiddleware/index.ts | 6 +- .../addFirstListImagesListener.ts.ts | 27 ----- ...geDeleted.ts => imageDeletionListeners.ts} | 104 +++++++++--------- 3 files changed, 55 insertions(+), 82 deletions(-) delete mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts rename invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/{imageDeleted.ts => imageDeletionListeners.ts} (70%) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 2d3db05bf7..9698f85219 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -1,7 +1,6 @@ import type { TypedStartListening } from '@reduxjs/toolkit'; import { createListenerMiddleware } from '@reduxjs/toolkit'; import { addCommitStagingAreaImageListener } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener'; -import { addFirstListImagesListener } from 'app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts'; import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued'; import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived'; import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted'; @@ -26,7 +25,7 @@ import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMid import { addGalleryOffsetChangedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryOffsetChanged'; import { addGetOpenAPISchemaListener } from 'app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema'; import { addImageAddedToBoardFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard'; -import { addRequestedSingleImageDeletionListener } from 'app/store/middleware/listenerMiddleware/listeners/imageDeleted'; +import { addImageDeletionListeners } from 'app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners'; import { addImageDroppedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped'; import { addImageRemovedFromBoardFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard'; import { addImagesStarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesStarred'; @@ -70,7 +69,7 @@ const startAppListening = listenerMiddleware.startListening as AppStartListening addImageUploadedFulfilledListener(startAppListening); // Image deleted -addRequestedSingleImageDeletionListener(startAppListening); +addImageDeletionListeners(startAppListening); addDeleteBoardAndImagesFulfilledListener(startAppListening); addImageToDeleteSelectedListener(startAppListening); @@ -139,7 +138,6 @@ addModelSelectedListener(startAppListening); addAppStartedListener(startAppListening); addModelsLoadedListener(startAppListening); addAppConfigReceivedListener(startAppListening); -addFirstListImagesListener(startAppListening); // Ad-hoc upscale workflwo addUpscaleRequestedListener(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts deleted file mode 100644 index 5db5f687a1..0000000000 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { imageSelected } from 'features/gallery/store/gallerySlice'; -import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; -import { imagesApi } from 'services/api/endpoints/images'; -import { getListImagesUrl } from 'services/api/util'; - -export const addFirstListImagesListener = (startAppListening: AppStartListening) => { - startAppListening({ - matcher: imagesApi.endpoints.listImages.matchFulfilled, - effect: async (action, { dispatch, unsubscribe, cancelActiveListeners }) => { - // Only run this listener on the first listImages request for no-board images - if (action.meta.arg.queryCacheKey !== getListImagesUrl({ board_id: 'none', categories: IMAGE_CATEGORIES })) { - return; - } - - // this should only run once - cancelActiveListeners(); - unsubscribe(); - - const data = action.payload; - - if (data.items.length > 0) { - dispatch(imageSelected(data.items[0] ?? null)); - } - }, - }); -}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts similarity index 70% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts index 916ec2c47f..056346cb68 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts @@ -22,11 +22,11 @@ import { imageSelected } from 'features/gallery/store/gallerySlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { isImageFieldInputInstance } from 'features/nodes/types/field'; import { isInvocationNode } from 'features/nodes/types/invocation'; -import { forEach } from 'lodash-es'; -import { api } from 'services/api'; +import { forEach, intersectionBy } from 'lodash-es'; import { imagesApi } from 'services/api/endpoints/images'; import type { ImageDTO } from 'services/api/types'; +// Some utils to delete images from different parts of the app const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { state.nodes.present.nodes.forEach((node) => { if (!isInvocationNode(node)) { @@ -97,10 +97,11 @@ const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, image }); }; -export const addRequestedSingleImageDeletionListener = (startAppListening: AppStartListening) => { +export const addImageDeletionListeners = (startAppListening: AppStartListening) => { + // Handle single image deletion startAppListening({ actionCreator: imageDeletionConfirmed, - effect: async (action, { dispatch, getState, condition }) => { + effect: async (action, { dispatch, getState }) => { const { imageDTOs, imagesUsage } = action.payload; if (imageDTOs.length !== 1 || imagesUsage.length !== 1) { @@ -116,49 +117,46 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt return; } - dispatch(isModalOpenChanged(false)); - const state = getState(); + try { + const state = getState(); + await dispatch(imagesApi.endpoints.deleteImage.initiate(imageDTO)).unwrap(); - // We need to reset the features where the image is in use - none of these work if their image(s) don't exist - if (imageUsage.isCanvasImage) { - dispatch(resetCanvas()); - } + if (state.gallery.selection.some((i) => i.image_name === imageDTO.image_name)) { + // The deleted image was a selected image, we need to select the next image + const newSelection = state.gallery.selection.filter((i) => i.image_name !== imageDTO.image_name); + + if (newSelection.length > 0) { + return; + } + + // Get the current list of images and select the same index + const baseQueryArgs = selectListImagesQueryArgs(state); + const data = imagesApi.endpoints.listImages.select(baseQueryArgs)(state).data; + + if (data) { + const deletedImageIndex = data.items.findIndex((i) => i.image_name === imageDTO.image_name); + const nextImage = data.items[deletedImageIndex + 1] ?? data.items[0] ?? null; + dispatch(imageSelected(nextImage)); + } + } + + // We need to reset the features where the image is in use - none of these work if their image(s) don't exist + if (imageUsage.isCanvasImage) { + dispatch(resetCanvas()); + } - imageDTOs.forEach((imageDTO) => { deleteControlAdapterImages(state, dispatch, imageDTO); deleteNodesImages(state, dispatch, imageDTO); deleteControlLayerImages(state, dispatch, imageDTO); - }); - - // Delete from server - const { requestId } = dispatch(imagesApi.endpoints.deleteImage.initiate(imageDTO)); - - // Wait for successful deletion, then trigger boards to re-fetch - const wasImageDeleted = await condition( - (action) => imagesApi.endpoints.deleteImage.matchFulfilled(action) && action.meta.requestId === requestId, - 30000 - ); - - if (wasImageDeleted) { - dispatch(api.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }])); - } - - const lastSelectedImage = state.gallery.selection[state.gallery.selection.length - 1]?.image_name; - - if (imageDTO && imageDTO?.image_name === lastSelectedImage) { - const baseQueryArgs = selectListImagesQueryArgs(state); - const { data } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state); - - if (data && data.items) { - const newlySelectedImage = data?.items.find((img) => img.image_name !== imageDTO?.image_name); - dispatch(imageSelected(newlySelectedImage || null)); - } else { - dispatch(imageSelected(null)); - } + } catch { + // no-op + } finally { + dispatch(isModalOpenChanged(false)); } }, }); + // Handle multiple image deletion startAppListening({ actionCreator: imageDeletionConfirmed, effect: async (action, { dispatch, getState }) => { @@ -170,20 +168,18 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt } try { - // Delete from server - await dispatch(imagesApi.endpoints.deleteImages.initiate({ imageDTOs })).unwrap(); const state = getState(); - const queryArgs = selectListImagesQueryArgs(state); - const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state); + await dispatch(imagesApi.endpoints.deleteImages.initiate({ imageDTOs })).unwrap(); - if (data && data.items[0]) { - dispatch(imageSelected(data.items[0])); - } else { - dispatch(imageSelected(null)); + if (intersectionBy(state.gallery.selection, imageDTOs, 'image_name').length > 0) { + // Some selected images were deleted, need to select the next image + const queryArgs = selectListImagesQueryArgs(state); + const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state); + if (data) { + dispatch(imageSelected(null)); + } } - dispatch(isModalOpenChanged(false)); - // We need to reset the features where the image is in use - none of these work if their image(s) don't exist if (imagesUsage.some((i) => i.isCanvasImage)) { @@ -197,14 +193,20 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt }); } catch { // no-op + } finally { + dispatch(isModalOpenChanged(false)); } }, }); + // When we list images, if no images is selected, select the first one. startAppListening({ - matcher: imagesApi.endpoints.deleteImage.matchPending, - effect: () => { - // + matcher: imagesApi.endpoints.listImages.matchFulfilled, + effect: (action, { dispatch, getState }) => { + const selection = getState().gallery.selection; + if (selection.length === 0) { + dispatch(imageSelected(action.payload.items[0] ?? null)); + } }, }); From a11dc62c2e9955423a278dd126188c614001c2ba Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Tue, 2 Jul 2024 20:37:26 -0400 Subject: [PATCH 03/37] fix access token lookup --- invokeai/app/services/download/download_default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/app/services/download/download_default.py b/invokeai/app/services/download/download_default.py index f6c7c1a1a0..73022e7d93 100644 --- a/invokeai/app/services/download/download_default.py +++ b/invokeai/app/services/download/download_default.py @@ -185,7 +185,7 @@ class DownloadQueueService(DownloadQueueServiceBase): job = DownloadJob( source=url, dest=path, - access_token=access_token, + access_token=access_token or self._lookup_access_token(url), ) mfdj.download_parts.add(job) self._download_part2parent[job.source] = mfdj From e719018ba1c7814d0527bc2e2cff89bdb384dec5 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 3 Jul 2024 11:57:44 -0400 Subject: [PATCH 04/37] fix sort order --- .../frontend/web/src/features/gallery/store/gallerySlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 8c0c467218..487c07dbc2 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -19,7 +19,7 @@ const initialGalleryState: GalleryState = { limit: 20, offset: 0, starredFirst: true, - orderDir: 'ASC', + orderDir: 'DESC', searchTerm: '', isImageViewerOpen: true, imageToCompare: null, From bb6ff4cf37db3d0dac2c260e662c0009c8b27bdb Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Wed, 3 Jul 2024 13:00:24 -0400 Subject: [PATCH 05/37] chore(ci): update pnpm github action --- .github/actions/install-frontend-deps/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/install-frontend-deps/action.yml b/.github/actions/install-frontend-deps/action.yml index 32b4987249..6152da80c6 100644 --- a/.github/actions/install-frontend-deps/action.yml +++ b/.github/actions/install-frontend-deps/action.yml @@ -9,9 +9,9 @@ runs: node-version: '18' - name: setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 with: - version: 8 + version: 8.15.6 run_install: false - name: get pnpm store directory From 6437ef3f8204f627084c230c4b58894542e8dc6b Mon Sep 17 00:00:00 2001 From: chainchompa Date: Wed, 3 Jul 2024 14:25:36 -0400 Subject: [PATCH 06/37] add view that displays private boards with shared boards --- invokeai/app/api/routers/boards.py | 1 + .../frontend/web/src/app/types/invokeai.ts | 1 + .../Boards/BoardsList/NoBoardBoard.tsx | 46 ++--- .../BoardsListWithPrivate/AddBoardButton.tsx | 32 +++ .../BoardTotalsTooltip.tsx | 22 ++ .../BoardsListWithPrivate.tsx | 120 +++++++++++ .../BoardsListWithPrivate/BoardsSearch.tsx | 66 ++++++ .../BoardsListWithPrivate/GalleryBoard.tsx | 194 ++++++++++++++++++ .../BoardsListWithPrivate/NoBoardBoard.tsx | 96 +++++++++ .../components/ImageGalleryContent.tsx | 22 +- .../src/features/system/store/configSlice.ts | 1 + .../web/src/services/api/endpoints/boards.ts | 8 +- .../frontend/web/src/services/api/types.ts | 4 + 13 files changed, 571 insertions(+), 42 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 19c2b330f0..fb09c0b463 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -32,6 +32,7 @@ class DeleteBoardResult(BaseModel): ) async def create_board( board_name: str = Query(description="The name of the board to create"), + private_board: bool = Query(default=False, description="Whether the board is private"), ) -> BoardDTO: """Creates a board""" try: diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index 21636ada49..6d7416d95d 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -65,6 +65,7 @@ export type AppConfig = { */ shouldUpdateImagesOnConnect: boolean; shouldFetchMetadataFromApi: boolean; + allowPrivateBoards: boolean; disabledTabs: InvokeTabName[]; disabledFeatures: AppFeature[]; disabledSDFeatures: SDFeature[]; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 2e823ea25b..3f60dabf70 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -46,11 +46,10 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { ); const { t } = useTranslation(); return ( - + { ref={ref} onClick={handleSelectBoard} w="full" - h="full" position="relative" - justifyContent="center" alignItems="center" borderRadius="base" cursor="pointer" bg="base.800" > - - invoke-ai-logo - + invoke-ai-logo {autoAddBoardId === 'none' && } - {boardName} - + {t('unifiedCanvas.move')}} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx new file mode 100644 index 0000000000..67e133ba22 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx @@ -0,0 +1,32 @@ +import { Icon } from '@invoke-ai/ui-library'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiPlusBold } from 'react-icons/pi'; +import { useCreateBoardMutation } from 'services/api/endpoints/boards'; + +type Props = { + privateBoard: boolean; +}; + +const AddBoardButton = ({ privateBoard }: Props) => { + const { t } = useTranslation(); + const [createBoard] = useCreateBoardMutation(); + const DEFAULT_BOARD_NAME = t('boards.myBoard'); + const handleCreateBoard = useCallback(() => { + createBoard({ DEFAULT_BOARD_NAME, privateBoard }); + }, [createBoard, DEFAULT_BOARD_NAME, privateBoard]); + + return ( + + ); +}; + +export default memo(AddBoardButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx new file mode 100644 index 0000000000..b4c89a002d --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx @@ -0,0 +1,22 @@ +import { useTranslation } from 'react-i18next'; +import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; + +type Props = { + board_id: string; + isArchived: boolean; +}; + +export const BoardTotalsTooltip = ({ board_id, isArchived }: Props) => { + const { t } = useTranslation(); + const { imagesTotal } = useGetBoardImagesTotalQuery(board_id, { + selectFromResult: ({ data }) => { + return { imagesTotal: data?.total ?? 0 }; + }, + }); + const { assetsTotal } = useGetBoardAssetsTotalQuery(board_id, { + selectFromResult: ({ data }) => { + return { assetsTotal: data?.total ?? 0 }; + }, + }); + return `${t('boards.imagesWithCount', { count: imagesTotal })}, ${t('boards.assetsWithCount', { count: assetsTotal })}${isArchived ? ` (${t('boards.archived')})` : ''}`; +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx new file mode 100644 index 0000000000..b87a96f272 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx @@ -0,0 +1,120 @@ +import { Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; +import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; +import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover'; +import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import type { CSSProperties } from 'react'; +import { memo, useState } from 'react'; +import { PiCaretUpBold, PiPlusBold } from 'react-icons/pi'; +import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; +import type { BoardDTO } from 'services/api/types'; + +import AddBoardButton from './AddBoardButton'; +import BoardsSearch from './BoardsSearch'; +import GalleryBoard from './GalleryBoard'; +import NoBoardBoard from './NoBoardBoard'; + +const overlayScrollbarsStyles: CSSProperties = { + height: '100%', + width: '100%', +}; + +const BoardsListWithPrivate = () => { + const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); + const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); + const queryArgs = useAppSelector(selectListBoardsQueryArgs); + const { data: boards } = useListAllBoardsQuery(queryArgs); + const filteredPrivateBoards = boardSearchText + ? boards?.filter((board) => board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) + : boards?.filter((board) => board.is_private); + const filteredSharedBoards = boardSearchText + ? boards?.filter((board) => !board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) + : boards?.filter((board) => !board.is_private); + const [boardToDelete, setBoardToDelete] = useState(); + const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true); + const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true); + + return ( + <> + + + + + + + + setIsPrivateBoardsOpen(!isPrivateBoardsOpen)} + gap={2} + alignItems="center" + cursor="pointer" + > + + Private + + + + + + + {filteredPrivateBoards && + filteredPrivateBoards.map((board) => ( + + ))} + + + + setIsSharedBoardsOpen(!isSharedBoardsOpen)} + gap={2} + alignItems="center" + cursor="pointer" + > + + + Shared + + + + + + {filteredSharedBoards && + filteredSharedBoards.map((board) => ( + + ))} + + + + + + + ); +}; + +export default memo(BoardsListWithPrivate); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx new file mode 100644 index 0000000000..931c1e6cbb --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx @@ -0,0 +1,66 @@ +import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; +import type { ChangeEvent, KeyboardEvent } from 'react'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiXBold } from 'react-icons/pi'; + +const BoardsSearch = () => { + const dispatch = useAppDispatch(); + const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); + const { t } = useTranslation(); + + const handleBoardSearch = useCallback( + (searchTerm: string) => { + dispatch(boardSearchTextChanged(searchTerm)); + }, + [dispatch] + ); + + const clearBoardSearch = useCallback(() => { + dispatch(boardSearchTextChanged('')); + }, [dispatch]); + + const handleKeydown = useCallback( + (e: KeyboardEvent) => { + // exit search mode on escape + if (e.key === 'Escape') { + clearBoardSearch(); + } + }, + [clearBoardSearch] + ); + + const handleChange = useCallback( + (e: ChangeEvent) => { + handleBoardSearch(e.target.value); + }, + [handleBoardSearch] + ); + + return ( + + + {boardSearchText && boardSearchText.length && ( + + } + /> + + )} + + ); +}; + +export default memo(BoardsSearch); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx new file mode 100644 index 0000000000..7eaae8ee62 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx @@ -0,0 +1,194 @@ +import type { SystemStyleObject } from '@invoke-ai/ui-library'; +import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library'; +import { skipToken } from '@reduxjs/toolkit/query'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIDroppable from 'common/components/IAIDroppable'; +import SelectionOverlay from 'common/components/SelectionOverlay'; +import type { AddToBoardDropData } from 'features/dnd/types'; +import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; +import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; +import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; +import { memo, useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi'; +import { useUpdateBoardMutation } from 'services/api/endpoints/boards'; +import { useGetImageDTOQuery } from 'services/api/endpoints/images'; +import type { BoardDTO } from 'services/api/types'; + +const editableInputStyles: SystemStyleObject = { + p: 0, + fontSize: 'md', + w: '100%', +}; + +const ArchivedIcon = () => { + return ( + + + + ); +}; + +interface GalleryBoardProps { + board: BoardDTO; + isSelected: boolean; + setBoardToDelete: (board?: BoardDTO) => void; +} + +const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); + + const [isHovered, setIsHovered] = useState(false); + const handleMouseOver = useCallback(() => { + setIsHovered(true); + }, []); + const handleMouseOut = useCallback(() => { + setIsHovered(false); + }, []); + + const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken); + + const { board_name, board_id } = board; + const [localBoardName, setLocalBoardName] = useState(board_name); + + const handleSelectBoard = useCallback(() => { + dispatch(boardIdSelected({ boardId: board_id })); + if (autoAssignBoardOnClick && !board.archived) { + dispatch(autoAddBoardIdChanged(board_id)); + } + }, [board_id, autoAssignBoardOnClick, dispatch, board.archived]); + + const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation(); + + const droppableData: AddToBoardDropData = useMemo( + () => ({ + id: board_id, + actionType: 'ADD_TO_BOARD', + context: { boardId: board_id }, + }), + [board_id] + ); + + const handleSubmit = useCallback( + async (newBoardName: string) => { + // empty strings are not allowed + if (!newBoardName.trim()) { + setLocalBoardName(board_name); + return; + } + + // don't updated the board name if it hasn't changed + if (newBoardName === board_name) { + return; + } + + try { + const { board_name } = await updateBoard({ + board_id, + changes: { board_name: newBoardName }, + }).unwrap(); + + // update local state + setLocalBoardName(board_name); + } catch { + // revert on error + setLocalBoardName(board_name); + } + }, + [board_id, board_name, updateBoard] + ); + + const handleChange = useCallback((newBoardName: string) => { + setLocalBoardName(newBoardName); + }, []); + + return ( + + + + {(ref) => ( + } + openDelay={1000} + > + + + {board.archived && } + {coverImage?.thumbnail_url ? ( + + ) : ( + + + + )} + + + + + + + + + + {board.image_count} images + + {t('unifiedCanvas.move')}} /> + + + )} + + + + ); +}; + +export default memo(GalleryBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx new file mode 100644 index 0000000000..77563859a7 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx @@ -0,0 +1,96 @@ +import { Box, Flex, Image, Text, Tooltip } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIDroppable from 'common/components/IAIDroppable'; +import SelectionOverlay from 'common/components/SelectionOverlay'; +import type { RemoveFromBoardDropData } from 'features/dnd/types'; +import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; +import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; +import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; +import InvokeLogoSVG from 'public/assets/images/invoke-symbol-wht-lrg.svg'; +import { memo, useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useBoardName } from 'services/api/hooks/useBoardName'; + +interface Props { + isSelected: boolean; +} + +const NoBoardBoard = memo(({ isSelected }: Props) => { + const dispatch = useAppDispatch(); + const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); + const boardName = useBoardName('none'); + const handleSelectBoard = useCallback(() => { + dispatch(boardIdSelected({ boardId: 'none' })); + if (autoAssignBoardOnClick) { + dispatch(autoAddBoardIdChanged('none')); + } + }, [dispatch, autoAssignBoardOnClick]); + const [isHovered, setIsHovered] = useState(false); + + const handleMouseOver = useCallback(() => { + setIsHovered(true); + }, []); + + const handleMouseOut = useCallback(() => { + setIsHovered(false); + }, []); + + const droppableData: RemoveFromBoardDropData = useMemo( + () => ({ + id: 'no_board', + actionType: 'REMOVE_FROM_BOARD', + }), + [] + ); + const { t } = useTranslation(); + return ( + + + + {(ref) => ( + } openDelay={1000}> + + invoke-ai-logo + + {boardName} + + + {t('unifiedCanvas.move')}} /> + + + )} + + + + ); +}); + +NoBoardBoard.displayName = 'HoverableBoard'; + +export default memo(NoBoardBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index b0b147b510..e21e7508fd 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -9,6 +9,7 @@ import { PiImagesBold } from 'react-icons/pi'; import { RiServerLine } from 'react-icons/ri'; import BoardsList from './Boards/BoardsList/BoardsList'; +import BoardsListWithPrivate from './Boards/BoardsListWithPrivate/BoardsListWithPrivate'; import GalleryBoardName from './GalleryBoardName'; import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; @@ -18,6 +19,7 @@ import { GallerySearch } from './ImageGrid/GallerySearch'; const ImageGalleryContent = () => { const { t } = useTranslation(); const galleryView = useAppSelector((s) => s.gallery.galleryView); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const dispatch = useAppDispatch(); const galleryHeader = useStore($galleryHeader); const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = useDisclosure({ defaultIsOpen: true }); @@ -42,15 +44,21 @@ const ImageGalleryContent = () => { gap={2} > {galleryHeader} - - - - - + {true ? ( - + + + ) : ( + + + + + + + + - + )} diff --git a/invokeai/frontend/web/src/features/system/store/configSlice.ts b/invokeai/frontend/web/src/features/system/store/configSlice.ts index 7d26dbd34c..8901365556 100644 --- a/invokeai/frontend/web/src/features/system/store/configSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/configSlice.ts @@ -18,6 +18,7 @@ const initialConfigState: AppConfig = { isLocal: true, shouldUpdateImagesOnConnect: false, shouldFetchMetadataFromApi: false, + allowPrivateBoards: false, disabledTabs: [], disabledFeatures: ['lightbox', 'faceRestore', 'batches'], disabledSDFeatures: ['variation', 'symmetry', 'hires', 'perlinNoise', 'noiseThreshold'], diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts index 177aa0e340..026ffdfa56 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts @@ -1,5 +1,5 @@ import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types'; -import type { BoardDTO, ListBoardsArgs, OffsetPaginatedResults_ImageDTO_, UpdateBoardArg } from 'services/api/types'; +import type { BoardDTO, CreateBoardArg, ListBoardsArgs, OffsetPaginatedResults_ImageDTO_, UpdateBoardArg } from 'services/api/types'; import { getListImagesUrl } from 'services/api/util'; import type { ApiTagDescription } from '..'; @@ -87,11 +87,11 @@ export const boardsApi = api.injectEndpoints({ * Boards Mutations */ - createBoard: build.mutation({ - query: (board_name) => ({ + createBoard: build.mutation({ + query: ({ board_name, private_board }) => ({ url: buildBoardsUrl(), method: 'POST', - params: { board_name }, + params: { board_name, private_board }, }), invalidatesTags: [{ type: 'Board', id: LIST_TAG }], }), diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 162bdf6abc..d5c857cefd 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -11,6 +11,10 @@ export type ListBoardsArgs = NonNullable Date: Wed, 3 Jul 2024 22:44:34 -0400 Subject: [PATCH 07/37] [MM bugfix] Put model install errors on the event bus (#6578) * fix access token lookup * fix bug preventing model install error events from being reported --------- Co-authored-by: Lincoln Stein --- .../app/services/model_install/model_install_default.py | 2 +- tests/app/services/model_install/test_model_install.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/invokeai/app/services/model_install/model_install_default.py b/invokeai/app/services/model_install/model_install_default.py index dd1b44d899..9ad6424053 100644 --- a/invokeai/app/services/model_install/model_install_default.py +++ b/invokeai/app/services/model_install/model_install_default.py @@ -848,7 +848,7 @@ class ModelInstallService(ModelInstallServiceBase): with self._lock: if install_job := self._download_cache.pop(download_job.id, None): assert excp is not None - install_job.set_error(excp) + self._set_error(install_job, excp) self._download_queue.cancel_job(download_job) # Let other threads know that the number of downloads has changed diff --git a/tests/app/services/model_install/test_model_install.py b/tests/app/services/model_install/test_model_install.py index 0c212cca76..5e4f2a389f 100644 --- a/tests/app/services/model_install/test_model_install.py +++ b/tests/app/services/model_install/test_model_install.py @@ -18,6 +18,7 @@ from invokeai.app.services.events.events_common import ( ModelInstallDownloadProgressEvent, ModelInstallDownloadsCompleteEvent, ModelInstallDownloadStartedEvent, + ModelInstallErrorEvent, ModelInstallStartedEvent, ) from invokeai.app.services.model_install import ( @@ -339,7 +340,13 @@ def test_404_download(mm2_installer: ModelInstallServiceBase, mm2_app_config: In assert job.error_type == "HTTPError" assert job.error assert "NOT FOUND" in job.error + assert job.error_traceback is not None assert job.error_traceback.startswith("Traceback") + bus = mm2_installer.event_bus + assert bus is not None + assert hasattr(bus, "events") # the dummyeventservice has this + event_types = [type(x) for x in bus.events] + assert ModelInstallErrorEvent in event_types def test_other_error_during_install( From 414750a45d721d291ad342f29a80a010f2b7d8c3 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Tue, 2 Jul 2024 21:14:12 -0400 Subject: [PATCH 08/37] Update calc_model_size_by_data(...) to handle all expected model types, and to log an error if an unexpected model type is received. --- invokeai/backend/ip_adapter/ip_adapter.py | 8 ++--- .../load/model_cache/model_cache_default.py | 2 +- .../backend/model_manager/load/model_util.py | 32 ++++++++++++++++--- invokeai/backend/textual_inversion.py | 8 +++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index c33cb3f4ab..abd6ca655a 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -136,11 +136,11 @@ class IPAdapter(RawModel): self._image_proj_model.to(device=self.device, dtype=self.dtype, non_blocking=non_blocking) self.attn_weights.to(device=self.device, dtype=self.dtype, non_blocking=non_blocking) - def calc_size(self): - # workaround for circular import - from invokeai.backend.model_manager.load.model_util import calc_model_size_by_data + def calc_size(self) -> int: + # HACK(ryand): Fix this issue with circular imports. + from invokeai.backend.model_manager.load.model_util import calc_module_size - return calc_model_size_by_data(self._image_proj_model) + calc_model_size_by_data(self.attn_weights) + return calc_module_size(self._image_proj_model) + calc_module_size(self.attn_weights) def _init_image_proj_model( self, state_dict: dict[str, torch.Tensor] diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py index 697d3daf9b..c9e68a926a 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py @@ -160,7 +160,7 @@ class ModelCache(ModelCacheBase[AnyModel]): key = self._make_cache_key(key, submodel_type) if key in self._cached_models: return - size = calc_model_size_by_data(model) + size = calc_model_size_by_data(self.logger, model) self.make_room(size) state_dict = model.state_dict() if isinstance(model, torch.nn.Module) else None diff --git a/invokeai/backend/model_manager/load/model_util.py b/invokeai/backend/model_manager/load/model_util.py index c55eee48fa..c798b92d8c 100644 --- a/invokeai/backend/model_manager/load/model_util.py +++ b/invokeai/backend/model_manager/load/model_util.py @@ -2,25 +2,46 @@ """Various utility functions needed by the loader and caching system.""" import json +import logging from pathlib import Path from typing import Optional import torch -from diffusers import DiffusionPipeline +from diffusers.pipelines.pipeline_utils import DiffusionPipeline +from diffusers.schedulers.scheduling_utils import SchedulerMixin +from transformers import CLIPTokenizer +from invokeai.backend.ip_adapter.ip_adapter import IPAdapter +from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager.config import AnyModel from invokeai.backend.onnx.onnx_runtime import IAIOnnxRuntimeModel +from invokeai.backend.textual_inversion import TextualInversionModelRaw -def calc_model_size_by_data(model: AnyModel) -> int: +def calc_model_size_by_data(logger: logging.Logger, model: AnyModel) -> int: """Get size of a model in memory in bytes.""" + # TODO(ryand): We should create a CacheableModel interface for all models, and move the size calculations down to + # the models themselves. if isinstance(model, DiffusionPipeline): return _calc_pipeline_by_data(model) elif isinstance(model, torch.nn.Module): - return _calc_model_by_data(model) + return calc_module_size(model) elif isinstance(model, IAIOnnxRuntimeModel): return _calc_onnx_model_by_data(model) + elif isinstance(model, SchedulerMixin): + return 0 + elif isinstance(model, CLIPTokenizer): + # TODO(ryand): Accurately calculate the tokenizer's size. It's small enough that it shouldn't matter for now. + return 0 + elif isinstance(model, (TextualInversionModelRaw, IPAdapter, LoRAModelRaw)): + return model.calc_size() else: + # TODO(ryand): Promote this from a log to an exception once we are confident that we are handling all of the + # supported model types. + logger.error( + f"Failed to calculate model size for unexpected model type: {type(model)}. The model will be treated as " + "having size 0." + ) return 0 @@ -30,11 +51,12 @@ def _calc_pipeline_by_data(pipeline: DiffusionPipeline) -> int: for submodel_key in pipeline.components.keys(): submodel = getattr(pipeline, submodel_key) if submodel is not None and isinstance(submodel, torch.nn.Module): - res += _calc_model_by_data(submodel) + res += calc_module_size(submodel) return res -def _calc_model_by_data(model: torch.nn.Module) -> int: +def calc_module_size(model: torch.nn.Module) -> int: + """Calculate the size (in bytes) of a torch.nn.Module.""" mem_params = sum([param.nelement() * param.element_size() for param in model.parameters()]) mem_bufs = sum([buf.nelement() * buf.element_size() for buf in model.buffers()]) mem: int = mem_params + mem_bufs # in bytes diff --git a/invokeai/backend/textual_inversion.py b/invokeai/backend/textual_inversion.py index 0408176edb..4c7625ea37 100644 --- a/invokeai/backend/textual_inversion.py +++ b/invokeai/backend/textual_inversion.py @@ -77,6 +77,14 @@ class TextualInversionModelRaw(RawModel): if emb is not None: emb.to(device=device, dtype=dtype, non_blocking=non_blocking) + def calc_size(self) -> int: + """Get the size of this model in bytes.""" + embedding_size = self.embedding.element_size() * self.embedding.nelement() + embedding_2_size = 0 + if self.embedding_2 is not None: + embedding_2_size = self.embedding_2.element_size() * self.embedding_2.nelement() + return embedding_size + embedding_2_size + class TextualInversionManager(BaseTextualInversionManager): """TextualInversionManager implements the BaseTextualInversionManager ABC from the compel library.""" From 9da592528727753116a265fc52fb50476ffb3c91 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Wed, 3 Jul 2024 12:04:22 -0400 Subject: [PATCH 09/37] Add ruff rule to disallow relative parent imports. --- invokeai/app/api/dependencies.py | 50 ++++++++++--------- invokeai/app/api/routers/app_info.py | 3 +- invokeai/app/api/routers/board_images.py | 2 +- invokeai/app/api/routers/boards.py | 3 +- invokeai/app/api/routers/download_queue.py | 3 +- invokeai/app/api/routers/images.py | 3 +- invokeai/app/api/routers/model_manager.py | 3 +- invokeai/app/api/routers/session_queue.py | 3 +- invokeai/app/api_app.py | 2 +- invokeai/app/invocations/baseinvocation.py | 2 +- invokeai/app/invocations/metadata.py | 3 +- invokeai/app/invocations/noise.py | 2 +- invokeai/app/services/boards/boards_common.py | 2 +- .../app/services/images/images_default.py | 12 ++--- .../model_manager/model_manager_base.py | 13 +++-- .../model_manager/model_manager_default.py | 14 +++--- .../model_records/model_records_sql.py | 2 +- .../session_processor_default.py | 2 +- invokeai/app/util/step_callback.py | 5 +- invokeai/backend/ip_adapter/ip_adapter.py | 2 +- invokeai/backend/model_manager/config.py | 3 +- .../model_manager/load/memory_snapshot.py | 2 +- .../load/model_loader_registry.py | 3 +- .../load/model_loaders/controlnet.py | 2 +- .../load/model_loaders/generic_diffusers.py | 4 +- .../model_manager/load/model_loaders/lora.py | 4 +- .../model_manager/load/model_loaders/onnx.py | 2 +- .../load/model_loaders/stable_diffusion.py | 2 +- .../load/model_loaders/textual_inversion.py | 4 +- .../model_manager/load/model_loaders/vae.py | 2 +- .../metadata/fetch/fetch_base.py | 7 ++- .../metadata/fetch/huggingface.py | 4 +- .../model_manager/metadata/metadata_base.py | 7 +-- .../model_manager/util/select_hf_files.py | 2 +- invokeai/backend/onnx/onnx_runtime.py | 2 +- pyproject.toml | 7 ++- 36 files changed, 94 insertions(+), 94 deletions(-) diff --git a/invokeai/app/api/dependencies.py b/invokeai/app/api/dependencies.py index 19a7bb083d..27ab030d4c 100644 --- a/invokeai/app/api/dependencies.py +++ b/invokeai/app/api/dependencies.py @@ -4,37 +4,39 @@ from logging import Logger import torch +from invokeai.app.services.board_image_records.board_image_records_sqlite import SqliteBoardImageRecordStorage +from invokeai.app.services.board_images.board_images_default import BoardImagesService +from invokeai.app.services.board_records.board_records_sqlite import SqliteBoardRecordStorage +from invokeai.app.services.boards.boards_default import BoardService +from invokeai.app.services.bulk_download.bulk_download_default import BulkDownloadService +from invokeai.app.services.config.config_default import InvokeAIAppConfig +from invokeai.app.services.download.download_default import DownloadQueueService +from invokeai.app.services.events.events_fastapievents import FastAPIEventService +from invokeai.app.services.image_files.image_files_disk import DiskImageFileStorage +from invokeai.app.services.image_records.image_records_sqlite import SqliteImageRecordStorage +from invokeai.app.services.images.images_default import ImageService +from invokeai.app.services.invocation_cache.invocation_cache_memory import MemoryInvocationCache +from invokeai.app.services.invocation_services import InvocationServices +from invokeai.app.services.invocation_stats.invocation_stats_default import InvocationStatsService +from invokeai.app.services.invoker import Invoker +from invokeai.app.services.model_images.model_images_default import ModelImageFileStorageDisk +from invokeai.app.services.model_manager.model_manager_default import ModelManagerService +from invokeai.app.services.model_records.model_records_sql import ModelRecordServiceSQL +from invokeai.app.services.names.names_default import SimpleNameService from invokeai.app.services.object_serializer.object_serializer_disk import ObjectSerializerDisk from invokeai.app.services.object_serializer.object_serializer_forward_cache import ObjectSerializerForwardCache +from invokeai.app.services.session_processor.session_processor_default import ( + DefaultSessionProcessor, + DefaultSessionRunner, +) +from invokeai.app.services.session_queue.session_queue_sqlite import SqliteSessionQueue from invokeai.app.services.shared.sqlite.sqlite_util import init_db +from invokeai.app.services.urls.urls_default import LocalUrlService +from invokeai.app.services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningFieldData from invokeai.backend.util.logging import InvokeAILogger from invokeai.version.invokeai_version import __version__ -from ..services.board_image_records.board_image_records_sqlite import SqliteBoardImageRecordStorage -from ..services.board_images.board_images_default import BoardImagesService -from ..services.board_records.board_records_sqlite import SqliteBoardRecordStorage -from ..services.boards.boards_default import BoardService -from ..services.bulk_download.bulk_download_default import BulkDownloadService -from ..services.config import InvokeAIAppConfig -from ..services.download import DownloadQueueService -from ..services.events.events_fastapievents import FastAPIEventService -from ..services.image_files.image_files_disk import DiskImageFileStorage -from ..services.image_records.image_records_sqlite import SqliteImageRecordStorage -from ..services.images.images_default import ImageService -from ..services.invocation_cache.invocation_cache_memory import MemoryInvocationCache -from ..services.invocation_services import InvocationServices -from ..services.invocation_stats.invocation_stats_default import InvocationStatsService -from ..services.invoker import Invoker -from ..services.model_images.model_images_default import ModelImageFileStorageDisk -from ..services.model_manager.model_manager_default import ModelManagerService -from ..services.model_records import ModelRecordServiceSQL -from ..services.names.names_default import SimpleNameService -from ..services.session_processor.session_processor_default import DefaultSessionProcessor, DefaultSessionRunner -from ..services.session_queue.session_queue_sqlite import SqliteSessionQueue -from ..services.urls.urls_default import LocalUrlService -from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage - # TODO: is there a better way to achieve this? def check_internet() -> bool: diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py index c3bc98a038..3206adb242 100644 --- a/invokeai/app/api/routers/app_info.py +++ b/invokeai/app/api/routers/app_info.py @@ -10,14 +10,13 @@ from fastapi import Body from fastapi.routing import APIRouter from pydantic import BaseModel, Field +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.invocations.upscale import ESRGAN_MODELS from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch from invokeai.backend.util.logging import logging from invokeai.version import __version__ -from ..dependencies import ApiDependencies - class LogLevel(int, Enum): NotSet = logging.NOTSET diff --git a/invokeai/app/api/routers/board_images.py b/invokeai/app/api/routers/board_images.py index 8e36a682d2..eb193f6585 100644 --- a/invokeai/app/api/routers/board_images.py +++ b/invokeai/app/api/routers/board_images.py @@ -2,7 +2,7 @@ from fastapi import Body, HTTPException from fastapi.routing import APIRouter from pydantic import BaseModel, Field -from ..dependencies import ApiDependencies +from invokeai.app.api.dependencies import ApiDependencies board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"]) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index 19c2b330f0..77d6956033 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -4,12 +4,11 @@ from fastapi import Body, HTTPException, Path, Query from fastapi.routing import APIRouter from pydantic import BaseModel, Field +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.services.board_records.board_records_common import BoardChanges from invokeai.app.services.boards.boards_common import BoardDTO from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from ..dependencies import ApiDependencies - boards_router = APIRouter(prefix="/v1/boards", tags=["boards"]) diff --git a/invokeai/app/api/routers/download_queue.py b/invokeai/app/api/routers/download_queue.py index a6e53c7a5c..2633b28bca 100644 --- a/invokeai/app/api/routers/download_queue.py +++ b/invokeai/app/api/routers/download_queue.py @@ -8,13 +8,12 @@ from fastapi.routing import APIRouter from pydantic.networks import AnyHttpUrl from starlette.exceptions import HTTPException +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.services.download import ( DownloadJob, UnknownJobIDException, ) -from ..dependencies import ApiDependencies - download_queue_router = APIRouter(prefix="/v1/download_queue", tags=["download_queue"]) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index d540fd3b55..8e3824ce93 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -8,6 +8,7 @@ from fastapi.routing import APIRouter from PIL import Image from pydantic import BaseModel, Field, JsonValue +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.invocations.fields import MetadataField from invokeai.app.services.image_records.image_records_common import ( ImageCategory, @@ -18,8 +19,6 @@ from invokeai.app.services.images.images_common import ImageDTO, ImageUrlsDTO from invokeai.app.services.shared.pagination import OffsetPaginatedResults from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection -from ..dependencies import ApiDependencies - images_router = APIRouter(prefix="/v1/images", tags=["images"]) diff --git a/invokeai/app/api/routers/model_manager.py b/invokeai/app/api/routers/model_manager.py index 298756d175..f73b7a86b1 100644 --- a/invokeai/app/api/routers/model_manager.py +++ b/invokeai/app/api/routers/model_manager.py @@ -16,6 +16,7 @@ from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field from starlette.exceptions import HTTPException from typing_extensions import Annotated +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.services.model_images.model_images_common import ModelImageFileNotFoundException from invokeai.app.services.model_install.model_install_common import ModelInstallJob from invokeai.app.services.model_records import ( @@ -35,8 +36,6 @@ from invokeai.backend.model_manager.metadata.metadata_base import ModelMetadataW from invokeai.backend.model_manager.search import ModelSearch from invokeai.backend.model_manager.starter_models import STARTER_MODELS, StarterModel, StarterModelWithoutDependencies -from ..dependencies import ApiDependencies - model_manager_router = APIRouter(prefix="/v2/models", tags=["model_manager"]) # images are immutable; set a high max-age diff --git a/invokeai/app/api/routers/session_queue.py b/invokeai/app/api/routers/session_queue.py index 7161e54a41..5dd4693795 100644 --- a/invokeai/app/api/routers/session_queue.py +++ b/invokeai/app/api/routers/session_queue.py @@ -4,6 +4,7 @@ from fastapi import Body, Path, Query from fastapi.routing import APIRouter from pydantic import BaseModel +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.services.session_processor.session_processor_common import SessionProcessorStatus from invokeai.app.services.session_queue.session_queue_common import ( QUEUE_ITEM_STATUS, @@ -19,8 +20,6 @@ from invokeai.app.services.session_queue.session_queue_common import ( ) from invokeai.app.services.shared.pagination import CursorPaginatedResults -from ..dependencies import ApiDependencies - session_queue_router = APIRouter(prefix="/v1/queue", tags=["queue"]) diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index e69d95af71..654279536c 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -24,8 +24,8 @@ from invokeai.app.api.no_cache_staticfiles import NoCacheStaticFiles from invokeai.app.services.config.config_default import get_config from invokeai.app.util.custom_openapi import get_openapi_func from invokeai.backend.util.devices import TorchDevice +from invokeai.backend.util.logging import InvokeAILogger -from ..backend.util.logging import InvokeAILogger from .api.dependencies import ApiDependencies from .api.routers import ( app_info, diff --git a/invokeai/app/invocations/baseinvocation.py b/invokeai/app/invocations/baseinvocation.py index 1d169f0a82..b527de41bc 100644 --- a/invokeai/app/invocations/baseinvocation.py +++ b/invokeai/app/invocations/baseinvocation.py @@ -40,7 +40,7 @@ from invokeai.app.util.misc import uuid_string from invokeai.backend.util.logging import InvokeAILogger if TYPE_CHECKING: - from ..services.invocation_services import InvocationServices + from invokeai.app.services.invocation_services import InvocationServices logger = InvokeAILogger.get_logger() diff --git a/invokeai/app/invocations/metadata.py b/invokeai/app/invocations/metadata.py index 9c7264a9bb..17b68ffc0b 100644 --- a/invokeai/app/invocations/metadata.py +++ b/invokeai/app/invocations/metadata.py @@ -14,8 +14,7 @@ from invokeai.app.invocations.fields import ( from invokeai.app.invocations.model import ModelIdentifierField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.controlnet_utils import CONTROLNET_MODE_VALUES, CONTROLNET_RESIZE_VALUES - -from ...version import __version__ +from invokeai.version.invokeai_version import __version__ class MetadataItemField(BaseModel): diff --git a/invokeai/app/invocations/noise.py b/invokeai/app/invocations/noise.py index 931e639106..3e628ef1ba 100644 --- a/invokeai/app/invocations/noise.py +++ b/invokeai/app/invocations/noise.py @@ -8,8 +8,8 @@ from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR from invokeai.app.invocations.fields import FieldDescriptions, InputField, LatentsField, OutputField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.misc import SEED_MAX +from invokeai.backend.util.devices import TorchDevice -from ...backend.util.devices import TorchDevice from .baseinvocation import ( BaseInvocation, BaseInvocationOutput, diff --git a/invokeai/app/services/boards/boards_common.py b/invokeai/app/services/boards/boards_common.py index 0cb54102bb..15d0b3c37f 100644 --- a/invokeai/app/services/boards/boards_common.py +++ b/invokeai/app/services/boards/boards_common.py @@ -2,7 +2,7 @@ from typing import Optional from pydantic import Field -from ..board_records.board_records_common import BoardRecord +from invokeai.app.services.board_records.board_records_common import BoardRecord class BoardDTO(BoardRecord): diff --git a/invokeai/app/services/images/images_default.py b/invokeai/app/services/images/images_default.py index 4e78375034..6cf702cf62 100644 --- a/invokeai/app/services/images/images_default.py +++ b/invokeai/app/services/images/images_default.py @@ -3,16 +3,12 @@ from typing import Optional from PIL.Image import Image as PILImageType from invokeai.app.invocations.fields import MetadataField -from invokeai.app.services.invoker import Invoker -from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection - -from ..image_files.image_files_common import ( +from invokeai.app.services.image_files.image_files_common import ( ImageFileDeleteException, ImageFileNotFoundException, ImageFileSaveException, ) -from ..image_records.image_records_common import ( +from invokeai.app.services.image_records.image_records_common import ( ImageCategory, ImageRecord, ImageRecordChanges, @@ -23,6 +19,10 @@ from ..image_records.image_records_common import ( InvalidOriginException, ResourceOrigin, ) +from invokeai.app.services.invoker import Invoker +from invokeai.app.services.shared.pagination import OffsetPaginatedResults +from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection + from .images_base import ImageServiceABC from .images_common import ImageDTO, image_record_to_dto diff --git a/invokeai/app/services/model_manager/model_manager_base.py b/invokeai/app/services/model_manager/model_manager_base.py index af1b68e1ec..a906076b16 100644 --- a/invokeai/app/services/model_manager/model_manager_base.py +++ b/invokeai/app/services/model_manager/model_manager_base.py @@ -5,14 +5,13 @@ from abc import ABC, abstractmethod import torch from typing_extensions import Self +from invokeai.app.services.config.config_default import InvokeAIAppConfig +from invokeai.app.services.download.download_base import DownloadQueueServiceBase +from invokeai.app.services.events.events_base import EventServiceBase from invokeai.app.services.invoker import Invoker - -from ..config import InvokeAIAppConfig -from ..download import DownloadQueueServiceBase -from ..events.events_base import EventServiceBase -from ..model_install import ModelInstallServiceBase -from ..model_load import ModelLoadServiceBase -from ..model_records import ModelRecordServiceBase +from invokeai.app.services.model_install.model_install_base import ModelInstallServiceBase +from invokeai.app.services.model_load.model_load_base import ModelLoadServiceBase +from invokeai.app.services.model_records.model_records_base import ModelRecordServiceBase class ModelManagerServiceBase(ABC): diff --git a/invokeai/app/services/model_manager/model_manager_default.py b/invokeai/app/services/model_manager/model_manager_default.py index f695c3c8c1..7353b98bb4 100644 --- a/invokeai/app/services/model_manager/model_manager_default.py +++ b/invokeai/app/services/model_manager/model_manager_default.py @@ -6,17 +6,19 @@ from typing import Optional import torch from typing_extensions import Self +from invokeai.app.services.config.config_default import InvokeAIAppConfig +from invokeai.app.services.download.download_base import DownloadQueueServiceBase +from invokeai.app.services.events.events_base import EventServiceBase from invokeai.app.services.invoker import Invoker +from invokeai.app.services.model_install.model_install_base import ModelInstallServiceBase +from invokeai.app.services.model_install.model_install_default import ModelInstallService +from invokeai.app.services.model_load.model_load_base import ModelLoadServiceBase +from invokeai.app.services.model_load.model_load_default import ModelLoadService +from invokeai.app.services.model_records.model_records_base import ModelRecordServiceBase from invokeai.backend.model_manager.load import ModelCache, ModelLoaderRegistry from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import InvokeAILogger -from ..config import InvokeAIAppConfig -from ..download import DownloadQueueServiceBase -from ..events.events_base import EventServiceBase -from ..model_install import ModelInstallService, ModelInstallServiceBase -from ..model_load import ModelLoadService, ModelLoadServiceBase -from ..model_records import ModelRecordServiceBase from .model_manager_base import ModelManagerServiceBase diff --git a/invokeai/app/services/model_records/model_records_sql.py b/invokeai/app/services/model_records/model_records_sql.py index 16abf4c523..3abbc662ee 100644 --- a/invokeai/app/services/model_records/model_records_sql.py +++ b/invokeai/app/services/model_records/model_records_sql.py @@ -46,6 +46,7 @@ from pathlib import Path from typing import List, Optional, Union from invokeai.app.services.shared.pagination import PaginatedResults +from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase from invokeai.backend.model_manager.config import ( AnyModelConfig, BaseModelType, @@ -54,7 +55,6 @@ from invokeai.backend.model_manager.config import ( ModelType, ) -from ..shared.sqlite.sqlite_database import SqliteDatabase from .model_records_base import ( DuplicateModelException, ModelRecordChanges, diff --git a/invokeai/app/services/session_processor/session_processor_default.py b/invokeai/app/services/session_processor/session_processor_default.py index 3f348fb239..336a17d3d5 100644 --- a/invokeai/app/services/session_processor/session_processor_default.py +++ b/invokeai/app/services/session_processor/session_processor_default.py @@ -13,6 +13,7 @@ from invokeai.app.services.events.events_common import ( register_events, ) from invokeai.app.services.invocation_stats.invocation_stats_common import GESStatsNotFoundError +from invokeai.app.services.invoker import Invoker from invokeai.app.services.session_processor.session_processor_base import ( OnAfterRunNode, OnAfterRunSession, @@ -27,7 +28,6 @@ from invokeai.app.services.shared.graph import NodeInputError from invokeai.app.services.shared.invocation_context import InvocationContextData, build_invocation_context from invokeai.app.util.profiler import Profiler -from ..invoker import Invoker from .session_processor_base import InvocationServices, SessionProcessorBase, SessionRunnerBase from .session_processor_common import SessionProcessorStatus diff --git a/invokeai/app/util/step_callback.py b/invokeai/app/util/step_callback.py index 8992e59ace..c0c101cd75 100644 --- a/invokeai/app/util/step_callback.py +++ b/invokeai/app/util/step_callback.py @@ -5,9 +5,8 @@ from PIL import Image from invokeai.app.services.session_processor.session_processor_common import CanceledException, ProgressImage from invokeai.backend.model_manager.config import BaseModelType - -from ...backend.stable_diffusion import PipelineIntermediateState -from ...backend.util.util import image_to_dataURL +from invokeai.backend.stable_diffusion.diffusers_pipeline import PipelineIntermediateState +from invokeai.backend.util.util import image_to_dataURL if TYPE_CHECKING: from invokeai.app.services.events.events_base import EventServiceBase diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index abd6ca655a..ed3d70d832 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -11,8 +11,8 @@ from PIL import Image from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights +from invokeai.backend.raw_model import RawModel -from ..raw_model import RawModel from .resampler import Resampler diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index d788012dc7..a8eb13d339 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -33,8 +33,7 @@ from typing_extensions import Annotated, Any, Dict from invokeai.app.invocations.constants import SCHEDULER_NAME_VALUES from invokeai.app.util.misc import uuid_string from invokeai.backend.model_hash.hash_validator import validate_hash - -from ..raw_model import RawModel +from invokeai.backend.raw_model import RawModel # ModelMixin is the base class for all diffusers and transformers models # RawModel is the InvokeAI wrapper class for ip_adapters, loras, textual_inversion and onnx runtime diff --git a/invokeai/backend/model_manager/load/memory_snapshot.py b/invokeai/backend/model_manager/load/memory_snapshot.py index 195e39361b..66dd070963 100644 --- a/invokeai/backend/model_manager/load/memory_snapshot.py +++ b/invokeai/backend/model_manager/load/memory_snapshot.py @@ -5,7 +5,7 @@ import psutil import torch from typing_extensions import Self -from ..util.libc_util import LibcUtil, Struct_mallinfo2 +from invokeai.backend.model_manager.util.libc_util import LibcUtil, Struct_mallinfo2 GB = 2**30 # 1 GB diff --git a/invokeai/backend/model_manager/load/model_loader_registry.py b/invokeai/backend/model_manager/load/model_loader_registry.py index bb6bd18d7f..e44d1bd41f 100644 --- a/invokeai/backend/model_manager/load/model_loader_registry.py +++ b/invokeai/backend/model_manager/load/model_loader_registry.py @@ -18,7 +18,7 @@ Use like this: from abc import ABC, abstractmethod from typing import Callable, Dict, Optional, Tuple, Type, TypeVar -from ..config import ( +from invokeai.backend.model_manager.config import ( AnyModelConfig, BaseModelType, ModelConfigBase, @@ -26,6 +26,7 @@ from ..config import ( ModelType, SubModelType, ) + from . import ModelLoaderBase diff --git a/invokeai/backend/model_manager/load/model_loaders/controlnet.py b/invokeai/backend/model_manager/load/model_loaders/controlnet.py index b2fae37d29..6edcc48760 100644 --- a/invokeai/backend/model_manager/load/model_loaders/controlnet.py +++ b/invokeai/backend/model_manager/load/model_loaders/controlnet.py @@ -13,8 +13,8 @@ from invokeai.backend.model_manager import ( ModelType, ) from invokeai.backend.model_manager.config import ControlNetCheckpointConfig, SubModelType +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry -from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader diff --git a/invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py b/invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py index 6320797b8a..dfe38aa79c 100644 --- a/invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py +++ b/invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py @@ -18,8 +18,8 @@ from invokeai.backend.model_manager import ( SubModelType, ) from invokeai.backend.model_manager.config import DiffusersConfigBase - -from .. import ModelLoader, ModelLoaderRegistry +from invokeai.backend.model_manager.load.load_default import ModelLoader +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.CLIPVision, format=ModelFormat.Diffusers) diff --git a/invokeai/backend/model_manager/load/model_loaders/lora.py b/invokeai/backend/model_manager/load/model_loaders/lora.py index aa0acab6bc..367107c662 100644 --- a/invokeai/backend/model_manager/load/model_loaders/lora.py +++ b/invokeai/backend/model_manager/load/model_loaders/lora.py @@ -15,9 +15,9 @@ from invokeai.backend.model_manager import ( ModelType, SubModelType, ) +from invokeai.backend.model_manager.load.load_default import ModelLoader from invokeai.backend.model_manager.load.model_cache.model_cache_base import ModelCacheBase - -from .. import ModelLoader, ModelLoaderRegistry +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.LoRA, format=ModelFormat.Diffusers) diff --git a/invokeai/backend/model_manager/load/model_loaders/onnx.py b/invokeai/backend/model_manager/load/model_loaders/onnx.py index b43e0a1bdf..de4b4f0e2b 100644 --- a/invokeai/backend/model_manager/load/model_loaders/onnx.py +++ b/invokeai/backend/model_manager/load/model_loaders/onnx.py @@ -13,8 +13,8 @@ from invokeai.backend.model_manager import ( ModelType, SubModelType, ) +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry -from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 95caf848e5..46b527acc9 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -25,9 +25,9 @@ from invokeai.backend.model_manager.config import ( DiffusersConfigBase, MainCheckpointConfig, ) +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry from invokeai.backend.util.silence_warnings import SilenceWarnings -from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader VARIANT_TO_IN_CHANNEL_MAP = { diff --git a/invokeai/backend/model_manager/load/model_loaders/textual_inversion.py b/invokeai/backend/model_manager/load/model_loaders/textual_inversion.py index cfdc689cc8..8d0f08f91a 100644 --- a/invokeai/backend/model_manager/load/model_loaders/textual_inversion.py +++ b/invokeai/backend/model_manager/load/model_loaders/textual_inversion.py @@ -12,10 +12,10 @@ from invokeai.backend.model_manager import ( ModelType, SubModelType, ) +from invokeai.backend.model_manager.load.load_default import ModelLoader +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry from invokeai.backend.textual_inversion import TextualInversionModelRaw -from .. import ModelLoader, ModelLoaderRegistry - @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.TextualInversion, format=ModelFormat.EmbeddingFile) @ModelLoaderRegistry.register( diff --git a/invokeai/backend/model_manager/load/model_loaders/vae.py b/invokeai/backend/model_manager/load/model_loaders/vae.py index 3c496f59ab..e01d4c9251 100644 --- a/invokeai/backend/model_manager/load/model_loaders/vae.py +++ b/invokeai/backend/model_manager/load/model_loaders/vae.py @@ -12,8 +12,8 @@ from invokeai.backend.model_manager import ( ModelType, ) from invokeai.backend.model_manager.config import AnyModel, SubModelType, VAECheckpointConfig +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry -from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader diff --git a/invokeai/backend/model_manager/metadata/fetch/fetch_base.py b/invokeai/backend/model_manager/metadata/fetch/fetch_base.py index f84479404e..b86a029b3e 100644 --- a/invokeai/backend/model_manager/metadata/fetch/fetch_base.py +++ b/invokeai/backend/model_manager/metadata/fetch/fetch_base.py @@ -18,8 +18,11 @@ from pydantic.networks import AnyHttpUrl from requests.sessions import Session from invokeai.backend.model_manager import ModelRepoVariant - -from ..metadata_base import AnyModelRepoMetadata, AnyModelRepoMetadataValidator, BaseMetadata +from invokeai.backend.model_manager.metadata.metadata_base import ( + AnyModelRepoMetadata, + AnyModelRepoMetadataValidator, + BaseMetadata, +) class ModelMetadataFetchBase(ABC): diff --git a/invokeai/backend/model_manager/metadata/fetch/huggingface.py b/invokeai/backend/model_manager/metadata/fetch/huggingface.py index ab78b3e064..49b942c7d7 100644 --- a/invokeai/backend/model_manager/metadata/fetch/huggingface.py +++ b/invokeai/backend/model_manager/metadata/fetch/huggingface.py @@ -25,13 +25,13 @@ from pydantic.networks import AnyHttpUrl from requests.sessions import Session from invokeai.backend.model_manager.config import ModelRepoVariant - -from ..metadata_base import ( +from invokeai.backend.model_manager.metadata.metadata_base import ( AnyModelRepoMetadata, HuggingFaceMetadata, RemoteModelFile, UnknownMetadataException, ) + from .fetch_base import ModelMetadataFetchBase HF_MODEL_RE = r"https?://huggingface.co/([\w\-.]+/[\w\-.]+)" diff --git a/invokeai/backend/model_manager/metadata/metadata_base.py b/invokeai/backend/model_manager/metadata/metadata_base.py index f9f5335d17..97fc598380 100644 --- a/invokeai/backend/model_manager/metadata/metadata_base.py +++ b/invokeai/backend/model_manager/metadata/metadata_base.py @@ -24,8 +24,7 @@ from requests.sessions import Session from typing_extensions import Annotated from invokeai.backend.model_manager import ModelRepoVariant - -from ..util import select_hf_files +from invokeai.backend.model_manager.util.select_hf_files import filter_files class UnknownMetadataException(Exception): @@ -112,9 +111,7 @@ class HuggingFaceMetadata(ModelMetadataWithFiles): session = session or Session() configure_http_backend(backend_factory=lambda: session) # used in testing - paths = select_hf_files.filter_files( - [x.path for x in self.files], variant, subfolder - ) # all files in the model + paths = filter_files([x.path for x in self.files], variant, subfolder) # all files in the model prefix = f"{subfolder}/" if subfolder else "" # the next step reads model_index.json to determine which subdirectories belong # to the model diff --git a/invokeai/backend/model_manager/util/select_hf_files.py b/invokeai/backend/model_manager/util/select_hf_files.py index 4a63ab27b7..b0a9551437 100644 --- a/invokeai/backend/model_manager/util/select_hf_files.py +++ b/invokeai/backend/model_manager/util/select_hf_files.py @@ -17,7 +17,7 @@ from dataclasses import dataclass from pathlib import Path from typing import Dict, List, Optional, Set -from ..config import ModelRepoVariant +from invokeai.backend.model_manager.config import ModelRepoVariant def filter_files( diff --git a/invokeai/backend/onnx/onnx_runtime.py b/invokeai/backend/onnx/onnx_runtime.py index 9fcd4d093f..d562a46dff 100644 --- a/invokeai/backend/onnx/onnx_runtime.py +++ b/invokeai/backend/onnx/onnx_runtime.py @@ -10,7 +10,7 @@ import torch from onnx import numpy_helper from onnxruntime import InferenceSession, SessionOptions, get_available_providers -from ..raw_model import RawModel +from invokeai.backend.raw_model import RawModel ONNX_WEIGHTS_NAME = "model.onnx" diff --git a/pyproject.toml b/pyproject.toml index fcc0aff60c..ec215230f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -206,7 +206,12 @@ ignore = [ "B008", # https://docs.astral.sh/ruff/rules/function-call-in-default-argument/ "B904", # https://docs.astral.sh/ruff/rules/raise-without-from-inside-except/ ] -select = ["B", "C", "E", "F", "W", "I"] +select = ["B", "C", "E", "F", "W", "I", "TID"] + +[tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports. +ban-relative-imports = "parents" + #=== End: Ruff #=== Begin: MyPy From 1d449097cca8f79a99edf2f9d0df980229895c65 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Wed, 3 Jul 2024 12:20:35 -0400 Subject: [PATCH 10/37] Apply ruff rule to disallow all relative imports. --- invokeai/app/api_app.py | 15 ++++--- invokeai/app/invocations/collections.py | 5 +-- invokeai/app/invocations/compel.py | 5 +-- .../controlnet_image_processors.py | 9 +++- invokeai/app/invocations/cv.py | 6 +-- invokeai/app/invocations/image.py | 3 +- invokeai/app/invocations/infill.py | 8 ++-- invokeai/app/invocations/math.py | 3 +- invokeai/app/invocations/model.py | 11 +++-- invokeai/app/invocations/noise.py | 8 +--- invokeai/app/invocations/param_easing.py | 5 +-- invokeai/app/invocations/primitives.py | 8 +--- invokeai/app/invocations/prompt.py | 5 +-- invokeai/app/invocations/sdxl.py | 10 +---- invokeai/app/invocations/strings.py | 12 ++---- invokeai/app/invocations/upscale.py | 6 +-- .../board_image_records_sqlite.py | 3 +- .../board_images/board_images_default.py | 3 +- .../board_records/board_records_base.py | 3 +- .../board_records/board_records_sqlite.py | 11 +++-- invokeai/app/services/boards/boards_base.py | 3 +- .../app/services/boards/boards_default.py | 6 +-- .../bulk_download/bulk_download_default.py | 3 +- invokeai/app/services/config/__init__.py | 3 +- invokeai/app/services/download/__init__.py | 4 +- .../app/services/download/download_default.py | 11 +++-- .../services/events/events_fastapievents.py | 3 +- .../services/image_files/image_files_disk.py | 9 ++-- .../image_records/image_records_base.py | 8 +++- .../image_records/image_records_sqlite.py | 11 +++-- .../app/services/images/images_default.py | 5 +-- invokeai/app/services/invocation_services.py | 41 +++++++++---------- .../invocation_stats_default.py | 9 ++-- invokeai/app/services/invoker.py | 2 +- .../model_images/model_images_default.py | 9 ++-- .../app/services/model_install/__init__.py | 8 ++-- .../model_install/model_install_default.py | 21 +++++----- invokeai/app/services/model_load/__init__.py | 4 +- .../services/model_load/model_load_default.py | 3 +- .../app/services/model_manager/__init__.py | 3 +- .../model_manager/model_manager_default.py | 3 +- .../model_records/model_records_sql.py | 17 ++++---- invokeai/app/services/names/names_default.py | 3 +- .../session_processor_default.py | 8 ++-- invokeai/app/services/urls/urls_default.py | 2 +- invokeai/backend/image_util/__init__.py | 11 +++-- .../image_util/basicsr/rrdbnet_arch.py | 2 +- .../image_util/depth_anything/model/dpt.py | 2 +- .../image_util/dw_openpose/wholebody.py | 5 +-- invokeai/backend/ip_adapter/ip_adapter.py | 3 +- invokeai/backend/lora.py | 3 +- invokeai/backend/model_manager/__init__.py | 8 ++-- .../backend/model_manager/load/__init__.py | 8 ++-- .../load/model_cache/model_cache_default.py | 10 +++-- .../load/model_cache/model_locker.py | 7 +++- .../load/model_loader_registry.py | 3 +- .../load/model_loaders/controlnet.py | 3 +- .../model_manager/load/model_loaders/onnx.py | 3 +- .../load/model_loaders/stable_diffusion.py | 3 +- .../model_manager/load/model_loaders/vae.py | 3 +- invokeai/backend/model_manager/merge.py | 10 +---- .../model_manager/metadata/__init__.py | 4 +- .../model_manager/metadata/fetch/__init__.py | 4 +- .../metadata/fetch/huggingface.py | 3 +- invokeai/backend/model_manager/probe.py | 7 ++-- invokeai/backend/model_patcher.py | 9 ++-- invokeai/backend/stable_diffusion/__init__.py | 9 ++-- .../stable_diffusion/diffusion/__init__.py | 4 +- .../stable_diffusion/schedulers/__init__.py | 2 +- invokeai/backend/textual_inversion.py | 2 +- invokeai/backend/util/__init__.py | 4 +- invokeai/version/__init__.py | 2 +- pyproject.toml | 2 +- tests/test_graph_execution_state.py | 15 ++++--- tests/test_node_graph.py | 3 +- tests/test_session_queue.py | 3 +- 76 files changed, 221 insertions(+), 274 deletions(-) diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py index 654279536c..dca0bc139d 100644 --- a/invokeai/app/api_app.py +++ b/invokeai/app/api_app.py @@ -20,14 +20,9 @@ from torch.backends.mps import is_available as is_mps_available # noinspection PyUnresolvedReferences import invokeai.backend.util.hotfixes # noqa: F401 (monkeypatching on import) import invokeai.frontend.web as web_dir +from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.api.no_cache_staticfiles import NoCacheStaticFiles -from invokeai.app.services.config.config_default import get_config -from invokeai.app.util.custom_openapi import get_openapi_func -from invokeai.backend.util.devices import TorchDevice -from invokeai.backend.util.logging import InvokeAILogger - -from .api.dependencies import ApiDependencies -from .api.routers import ( +from invokeai.app.api.routers import ( app_info, board_images, boards, @@ -38,7 +33,11 @@ from .api.routers import ( utilities, workflows, ) -from .api.sockets import SocketIO +from invokeai.app.api.sockets import SocketIO +from invokeai.app.services.config.config_default import get_config +from invokeai.app.util.custom_openapi import get_openapi_func +from invokeai.backend.util.devices import TorchDevice +from invokeai.backend.util.logging import InvokeAILogger app_config = get_config() diff --git a/invokeai/app/invocations/collections.py b/invokeai/app/invocations/collections.py index e02291980f..bd3dedb3f8 100644 --- a/invokeai/app/invocations/collections.py +++ b/invokeai/app/invocations/collections.py @@ -4,13 +4,12 @@ import numpy as np from pydantic import ValidationInfo, field_validator +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import InputField from invokeai.app.invocations.primitives import IntegerCollectionOutput from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.misc import SEED_MAX -from .baseinvocation import BaseInvocation, invocation -from .fields import InputField - @invocation( "range", title="Integer Range", tags=["collection", "integer", "range"], category="collections", version="1.0.0" diff --git a/invokeai/app/invocations/compel.py b/invokeai/app/invocations/compel.py index 1e78e10d38..fffb09e654 100644 --- a/invokeai/app/invocations/compel.py +++ b/invokeai/app/invocations/compel.py @@ -5,6 +5,7 @@ from compel import Compel, ReturnedEmbeddingsType from compel.prompt_parser import Blend, Conjunction, CrossAttentionControlSubstitute, FlattenedPrompt, Fragment from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from invokeai.app.invocations.fields import ( ConditioningField, FieldDescriptions, @@ -14,6 +15,7 @@ from invokeai.app.invocations.fields import ( TensorField, UIComponent, ) +from invokeai.app.invocations.model import CLIPField from invokeai.app.invocations.primitives import ConditioningOutput from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.ti_utils import generate_ti_list @@ -26,9 +28,6 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( ) from invokeai.backend.util.devices import TorchDevice -from .baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output -from .model import CLIPField - # unconditioned: Optional[torch.Tensor] diff --git a/invokeai/app/invocations/controlnet_image_processors.py b/invokeai/app/invocations/controlnet_image_processors.py index c0b332f27b..497d07e7cf 100644 --- a/invokeai/app/invocations/controlnet_image_processors.py +++ b/invokeai/app/invocations/controlnet_image_processors.py @@ -22,6 +22,13 @@ from controlnet_aux.util import HWC3, ade_palette from PIL import Image from pydantic import BaseModel, Field, field_validator, model_validator +from invokeai.app.invocations.baseinvocation import ( + BaseInvocation, + BaseInvocationOutput, + Classification, + invocation, + invocation_output, +) from invokeai.app.invocations.fields import ( FieldDescriptions, ImageField, @@ -45,8 +52,6 @@ from invokeai.backend.image_util.lineart_anime import LineartAnimeProcessor from invokeai.backend.image_util.util import np_to_pil, pil_to_np from invokeai.backend.util.devices import TorchDevice -from .baseinvocation import BaseInvocation, BaseInvocationOutput, Classification, invocation, invocation_output - class ControlField(BaseModel): image: ImageField = Field(description="The control image") diff --git a/invokeai/app/invocations/cv.py b/invokeai/app/invocations/cv.py index a7c394deb2..f7951ccfeb 100644 --- a/invokeai/app/invocations/cv.py +++ b/invokeai/app/invocations/cv.py @@ -5,13 +5,11 @@ import cv2 as cv import numpy from PIL import Image, ImageOps -from invokeai.app.invocations.fields import ImageField +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import ImageField, InputField, WithBoard, WithMetadata from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from .baseinvocation import BaseInvocation, invocation -from .fields import InputField, WithBoard, WithMetadata - @invocation("cv_inpaint", title="OpenCV Inpaint", tags=["opencv", "inpaint"], category="inpaint", version="1.3.1") class CvInpaintInvocation(BaseInvocation, WithMetadata, WithBoard): diff --git a/invokeai/app/invocations/image.py b/invokeai/app/invocations/image.py index 65e7ce5e06..a551f8df8a 100644 --- a/invokeai/app/invocations/image.py +++ b/invokeai/app/invocations/image.py @@ -6,6 +6,7 @@ import cv2 import numpy from PIL import Image, ImageChops, ImageFilter, ImageOps +from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, invocation from invokeai.app.invocations.constants import IMAGE_MODES from invokeai.app.invocations.fields import ( ColorField, @@ -21,8 +22,6 @@ from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark from invokeai.backend.image_util.safety_checker import SafetyChecker -from .baseinvocation import BaseInvocation, Classification, invocation - @invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.1") class ShowImageInvocation(BaseInvocation): diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 7e1a2ee322..3314d72620 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -3,7 +3,9 @@ from typing import Literal, get_args from PIL import Image -from invokeai.app.invocations.fields import ColorField, ImageField +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import ColorField, ImageField, InputField, WithBoard, WithMetadata +from invokeai.app.invocations.image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.misc import SEED_MAX @@ -14,10 +16,6 @@ from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch, in from invokeai.backend.image_util.infill_methods.tile import infill_tile from invokeai.backend.util.logging import InvokeAILogger -from .baseinvocation import BaseInvocation, invocation -from .fields import InputField, WithBoard, WithMetadata -from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES - logger = InvokeAILogger.get_logger() diff --git a/invokeai/app/invocations/math.py b/invokeai/app/invocations/math.py index dad000d411..5d3988031b 100644 --- a/invokeai/app/invocations/math.py +++ b/invokeai/app/invocations/math.py @@ -5,12 +5,11 @@ from typing import Literal import numpy as np from pydantic import ValidationInfo, field_validator +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation from invokeai.app.invocations.fields import FieldDescriptions, InputField from invokeai.app.invocations.primitives import FloatOutput, IntegerOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from .baseinvocation import BaseInvocation, invocation - @invocation("add", title="Add Integers", tags=["math", "add"], category="math", version="1.0.1") class AddInvocation(BaseInvocation): diff --git a/invokeai/app/invocations/model.py b/invokeai/app/invocations/model.py index 94a6136fcb..c0d067c0a7 100644 --- a/invokeai/app/invocations/model.py +++ b/invokeai/app/invocations/model.py @@ -3,18 +3,17 @@ from typing import List, Optional from pydantic import BaseModel, Field -from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField, UIType -from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.app.shared.models import FreeUConfig -from invokeai.backend.model_manager.config import AnyModelConfig, BaseModelType, ModelType, SubModelType - -from .baseinvocation import ( +from invokeai.app.invocations.baseinvocation import ( BaseInvocation, BaseInvocationOutput, Classification, invocation, invocation_output, ) +from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField, UIType +from invokeai.app.services.shared.invocation_context import InvocationContext +from invokeai.app.shared.models import FreeUConfig +from invokeai.backend.model_manager.config import AnyModelConfig, BaseModelType, ModelType, SubModelType class ModelIdentifierField(BaseModel): diff --git a/invokeai/app/invocations/noise.py b/invokeai/app/invocations/noise.py index 3e628ef1ba..1d3ff3a29c 100644 --- a/invokeai/app/invocations/noise.py +++ b/invokeai/app/invocations/noise.py @@ -4,19 +4,13 @@ import torch from pydantic import field_validator +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR from invokeai.app.invocations.fields import FieldDescriptions, InputField, LatentsField, OutputField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.app.util.misc import SEED_MAX from invokeai.backend.util.devices import TorchDevice -from .baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) - """ Utilities """ diff --git a/invokeai/app/invocations/param_easing.py b/invokeai/app/invocations/param_easing.py index 0e590f4e2b..3e785ef545 100644 --- a/invokeai/app/invocations/param_easing.py +++ b/invokeai/app/invocations/param_easing.py @@ -39,12 +39,11 @@ from easing_functions import ( ) from matplotlib.ticker import MaxNLocator +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import InputField from invokeai.app.invocations.primitives import FloatCollectionOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from .baseinvocation import BaseInvocation, invocation -from .fields import InputField - @invocation( "float_range", diff --git a/invokeai/app/invocations/primitives.py b/invokeai/app/invocations/primitives.py index 28f72fb377..e5056e3775 100644 --- a/invokeai/app/invocations/primitives.py +++ b/invokeai/app/invocations/primitives.py @@ -4,6 +4,7 @@ from typing import Optional import torch +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR from invokeai.app.invocations.fields import ( ColorField, @@ -21,13 +22,6 @@ from invokeai.app.invocations.fields import ( from invokeai.app.services.images.images_common import ImageDTO from invokeai.app.services.shared.invocation_context import InvocationContext -from .baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) - """ Primitives: Boolean, Integer, Float, String, Image, Latents, Conditioning, Color - primitive nodes diff --git a/invokeai/app/invocations/prompt.py b/invokeai/app/invocations/prompt.py index 64a06d2f18..48eec0ac0e 100644 --- a/invokeai/app/invocations/prompt.py +++ b/invokeai/app/invocations/prompt.py @@ -5,12 +5,11 @@ import numpy as np from dynamicprompts.generators import CombinatorialPromptGenerator, RandomPromptGenerator from pydantic import field_validator +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import InputField, UIComponent from invokeai.app.invocations.primitives import StringCollectionOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from .baseinvocation import BaseInvocation, invocation -from .fields import InputField, UIComponent - @invocation( "dynamic_prompt", diff --git a/invokeai/app/invocations/sdxl.py b/invokeai/app/invocations/sdxl.py index 1c0817cb92..8eed158a61 100644 --- a/invokeai/app/invocations/sdxl.py +++ b/invokeai/app/invocations/sdxl.py @@ -1,15 +1,9 @@ +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output from invokeai.app.invocations.fields import FieldDescriptions, InputField, OutputField, UIType +from invokeai.app.invocations.model import CLIPField, ModelIdentifierField, UNetField, VAEField from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.backend.model_manager import SubModelType -from .baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) -from .model import CLIPField, ModelIdentifierField, UNetField, VAEField - @invocation_output("sdxl_model_loader_output") class SDXLModelLoaderOutput(BaseInvocationOutput): diff --git a/invokeai/app/invocations/strings.py b/invokeai/app/invocations/strings.py index 46ef35cbbf..2b6bf300b9 100644 --- a/invokeai/app/invocations/strings.py +++ b/invokeai/app/invocations/strings.py @@ -2,17 +2,11 @@ import re +from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output +from invokeai.app.invocations.fields import InputField, OutputField, UIComponent +from invokeai.app.invocations.primitives import StringOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from .baseinvocation import ( - BaseInvocation, - BaseInvocationOutput, - invocation, - invocation_output, -) -from .fields import InputField, OutputField, UIComponent -from .primitives import StringOutput - @invocation_output("string_pos_neg_output") class StringPosNegOutput(BaseInvocationOutput): diff --git a/invokeai/app/invocations/upscale.py b/invokeai/app/invocations/upscale.py index f93060f8d3..e7b3968aec 100644 --- a/invokeai/app/invocations/upscale.py +++ b/invokeai/app/invocations/upscale.py @@ -6,15 +6,13 @@ import numpy as np from PIL import Image from pydantic import ConfigDict -from invokeai.app.invocations.fields import ImageField +from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation +from invokeai.app.invocations.fields import ImageField, InputField, WithBoard, WithMetadata from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.services.shared.invocation_context import InvocationContext from invokeai.backend.image_util.basicsr.rrdbnet_arch import RRDBNet from invokeai.backend.image_util.realesrgan.realesrgan import RealESRGAN -from .baseinvocation import BaseInvocation, invocation -from .fields import InputField, WithBoard, WithMetadata - # TODO: Populate this from disk? # TODO: Use model manager to load? ESRGAN_MODELS = Literal[ diff --git a/invokeai/app/services/board_image_records/board_image_records_sqlite.py b/invokeai/app/services/board_image_records/board_image_records_sqlite.py index cde810a739..33ac76b06f 100644 --- a/invokeai/app/services/board_image_records/board_image_records_sqlite.py +++ b/invokeai/app/services/board_image_records/board_image_records_sqlite.py @@ -2,12 +2,11 @@ import sqlite3 import threading from typing import Optional, cast +from invokeai.app.services.board_image_records.board_image_records_base import BoardImageRecordStorageBase from invokeai.app.services.image_records.image_records_common import ImageRecord, deserialize_image_record from invokeai.app.services.shared.pagination import OffsetPaginatedResults from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase -from .board_image_records_base import BoardImageRecordStorageBase - class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase): _conn: sqlite3.Connection diff --git a/invokeai/app/services/board_images/board_images_default.py b/invokeai/app/services/board_images/board_images_default.py index 85e478619c..6a564f5a91 100644 --- a/invokeai/app/services/board_images/board_images_default.py +++ b/invokeai/app/services/board_images/board_images_default.py @@ -1,9 +1,8 @@ from typing import Optional +from invokeai.app.services.board_images.board_images_base import BoardImagesServiceABC from invokeai.app.services.invoker import Invoker -from .board_images_base import BoardImagesServiceABC - class BoardImagesService(BoardImagesServiceABC): __invoker: Invoker diff --git a/invokeai/app/services/board_records/board_records_base.py b/invokeai/app/services/board_records/board_records_base.py index 9d065b3750..9d16dacf60 100644 --- a/invokeai/app/services/board_records/board_records_base.py +++ b/invokeai/app/services/board_records/board_records_base.py @@ -1,9 +1,8 @@ from abc import ABC, abstractmethod +from invokeai.app.services.board_records.board_records_common import BoardChanges, BoardRecord from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from .board_records_common import BoardChanges, BoardRecord - class BoardRecordStorageBase(ABC): """Low-level service responsible for interfacing with the board record store.""" diff --git a/invokeai/app/services/board_records/board_records_sqlite.py b/invokeai/app/services/board_records/board_records_sqlite.py index 9d81e2f1e7..c64e060b95 100644 --- a/invokeai/app/services/board_records/board_records_sqlite.py +++ b/invokeai/app/services/board_records/board_records_sqlite.py @@ -2,12 +2,8 @@ import sqlite3 import threading from typing import Union, cast -from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase -from invokeai.app.util.misc import uuid_string - -from .board_records_base import BoardRecordStorageBase -from .board_records_common import ( +from invokeai.app.services.board_records.board_records_base import BoardRecordStorageBase +from invokeai.app.services.board_records.board_records_common import ( BoardChanges, BoardRecord, BoardRecordDeleteException, @@ -15,6 +11,9 @@ from .board_records_common import ( BoardRecordSaveException, deserialize_board_record, ) +from invokeai.app.services.shared.pagination import OffsetPaginatedResults +from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase +from invokeai.app.util.misc import uuid_string class SqliteBoardRecordStorage(BoardRecordStorageBase): diff --git a/invokeai/app/services/boards/boards_base.py b/invokeai/app/services/boards/boards_base.py index fddd5a7954..3c2d148cb9 100644 --- a/invokeai/app/services/boards/boards_base.py +++ b/invokeai/app/services/boards/boards_base.py @@ -1,10 +1,9 @@ from abc import ABC, abstractmethod from invokeai.app.services.board_records.board_records_common import BoardChanges +from invokeai.app.services.boards.boards_common import BoardDTO from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from .boards_common import BoardDTO - class BoardServiceABC(ABC): """High-level service for board management.""" diff --git a/invokeai/app/services/boards/boards_default.py b/invokeai/app/services/boards/boards_default.py index 6457aee1d2..97fd3059a9 100644 --- a/invokeai/app/services/boards/boards_default.py +++ b/invokeai/app/services/boards/boards_default.py @@ -1,11 +1,9 @@ from invokeai.app.services.board_records.board_records_common import BoardChanges -from invokeai.app.services.boards.boards_common import BoardDTO +from invokeai.app.services.boards.boards_base import BoardServiceABC +from invokeai.app.services.boards.boards_common import BoardDTO, board_record_to_dto from invokeai.app.services.invoker import Invoker from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from .boards_base import BoardServiceABC -from .boards_common import board_record_to_dto - class BoardService(BoardServiceABC): __invoker: Invoker diff --git a/invokeai/app/services/bulk_download/bulk_download_default.py b/invokeai/app/services/bulk_download/bulk_download_default.py index d4bf059b8f..4ebbd10d4f 100644 --- a/invokeai/app/services/bulk_download/bulk_download_default.py +++ b/invokeai/app/services/bulk_download/bulk_download_default.py @@ -4,6 +4,7 @@ from typing import Optional, Union from zipfile import ZipFile from invokeai.app.services.board_records.board_records_common import BoardRecordNotFoundException +from invokeai.app.services.bulk_download.bulk_download_base import BulkDownloadBase from invokeai.app.services.bulk_download.bulk_download_common import ( DEFAULT_BULK_DOWNLOAD_ID, BulkDownloadException, @@ -15,8 +16,6 @@ from invokeai.app.services.images.images_common import ImageDTO from invokeai.app.services.invoker import Invoker from invokeai.app.util.misc import uuid_string -from .bulk_download_base import BulkDownloadBase - class BulkDownloadService(BulkDownloadBase): def start(self, invoker: Invoker) -> None: diff --git a/invokeai/app/services/config/__init__.py b/invokeai/app/services/config/__init__.py index 126692f08a..df1acbf104 100644 --- a/invokeai/app/services/config/__init__.py +++ b/invokeai/app/services/config/__init__.py @@ -1,7 +1,6 @@ """Init file for InvokeAI configure package.""" from invokeai.app.services.config.config_common import PagingArgumentParser - -from .config_default import InvokeAIAppConfig, get_config +from invokeai.app.services.config.config_default import InvokeAIAppConfig, get_config __all__ = ["InvokeAIAppConfig", "get_config", "PagingArgumentParser"] diff --git a/invokeai/app/services/download/__init__.py b/invokeai/app/services/download/__init__.py index 33b0025809..48ded7d549 100644 --- a/invokeai/app/services/download/__init__.py +++ b/invokeai/app/services/download/__init__.py @@ -1,13 +1,13 @@ """Init file for download queue.""" -from .download_base import ( +from invokeai.app.services.download.download_base import ( DownloadJob, DownloadJobStatus, DownloadQueueServiceBase, MultiFileDownloadJob, UnknownJobIDException, ) -from .download_default import DownloadQueueService, TqdmProgress +from invokeai.app.services.download.download_default import DownloadQueueService, TqdmProgress __all__ = [ "DownloadJob", diff --git a/invokeai/app/services/download/download_default.py b/invokeai/app/services/download/download_default.py index 73022e7d93..b97f61657c 100644 --- a/invokeai/app/services/download/download_default.py +++ b/invokeai/app/services/download/download_default.py @@ -16,12 +16,7 @@ from requests import HTTPError from tqdm import tqdm from invokeai.app.services.config import InvokeAIAppConfig, get_config -from invokeai.app.services.events.events_base import EventServiceBase -from invokeai.app.util.misc import get_iso_timestamp -from invokeai.backend.model_manager.metadata import RemoteModelFile -from invokeai.backend.util.logging import InvokeAILogger - -from .download_base import ( +from invokeai.app.services.download.download_base import ( DownloadEventHandler, DownloadExceptionHandler, DownloadJob, @@ -33,6 +28,10 @@ from .download_base import ( ServiceInactiveException, UnknownJobIDException, ) +from invokeai.app.services.events.events_base import EventServiceBase +from invokeai.app.util.misc import get_iso_timestamp +from invokeai.backend.model_manager.metadata import RemoteModelFile +from invokeai.backend.util.logging import InvokeAILogger # Maximum number of bytes to download during each call to requests.iter_content() DOWNLOAD_CHUNK_SIZE = 100000 diff --git a/invokeai/app/services/events/events_fastapievents.py b/invokeai/app/services/events/events_fastapievents.py index 8279d3bb34..d514a06b67 100644 --- a/invokeai/app/services/events/events_fastapievents.py +++ b/invokeai/app/services/events/events_fastapievents.py @@ -6,12 +6,11 @@ from queue import Empty, Queue from fastapi_events.dispatcher import dispatch +from invokeai.app.services.events.events_base import EventServiceBase from invokeai.app.services.events.events_common import ( EventBase, ) -from .events_base import EventServiceBase - class FastAPIEventService(EventServiceBase): def __init__(self, event_handler_id: int) -> None: diff --git a/invokeai/app/services/image_files/image_files_disk.py b/invokeai/app/services/image_files/image_files_disk.py index 15d0be31f8..95ab052520 100644 --- a/invokeai/app/services/image_files/image_files_disk.py +++ b/invokeai/app/services/image_files/image_files_disk.py @@ -7,12 +7,15 @@ from PIL import Image, PngImagePlugin from PIL.Image import Image as PILImageType from send2trash import send2trash +from invokeai.app.services.image_files.image_files_base import ImageFileStorageBase +from invokeai.app.services.image_files.image_files_common import ( + ImageFileDeleteException, + ImageFileNotFoundException, + ImageFileSaveException, +) from invokeai.app.services.invoker import Invoker from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail -from .image_files_base import ImageFileStorageBase -from .image_files_common import ImageFileDeleteException, ImageFileNotFoundException, ImageFileSaveException - class DiskImageFileStorage(ImageFileStorageBase): """Stores images on disk""" diff --git a/invokeai/app/services/image_records/image_records_base.py b/invokeai/app/services/image_records/image_records_base.py index 94e884e549..1211c9762c 100644 --- a/invokeai/app/services/image_records/image_records_base.py +++ b/invokeai/app/services/image_records/image_records_base.py @@ -3,11 +3,15 @@ from datetime import datetime from typing import Optional from invokeai.app.invocations.fields import MetadataField +from invokeai.app.services.image_records.image_records_common import ( + ImageCategory, + ImageRecord, + ImageRecordChanges, + ResourceOrigin, +) from invokeai.app.services.shared.pagination import OffsetPaginatedResults from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection -from .image_records_common import ImageCategory, ImageRecord, ImageRecordChanges, ResourceOrigin - class ImageRecordStorageBase(ABC): """Low-level service responsible for interfacing with the image record store.""" diff --git a/invokeai/app/services/image_records/image_records_sqlite.py b/invokeai/app/services/image_records/image_records_sqlite.py index 82e7ffae9d..b0c2155a18 100644 --- a/invokeai/app/services/image_records/image_records_sqlite.py +++ b/invokeai/app/services/image_records/image_records_sqlite.py @@ -4,12 +4,8 @@ from datetime import datetime from typing import Optional, Union, cast from invokeai.app.invocations.fields import MetadataField, MetadataFieldValidator -from invokeai.app.services.shared.pagination import OffsetPaginatedResults -from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection -from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - -from .image_records_base import ImageRecordStorageBase -from .image_records_common import ( +from invokeai.app.services.image_records.image_records_base import ImageRecordStorageBase +from invokeai.app.services.image_records.image_records_common import ( IMAGE_DTO_COLS, ImageCategory, ImageRecord, @@ -20,6 +16,9 @@ from .image_records_common import ( ResourceOrigin, deserialize_image_record, ) +from invokeai.app.services.shared.pagination import OffsetPaginatedResults +from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection +from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase class SqliteImageRecordStorage(ImageRecordStorageBase): diff --git a/invokeai/app/services/images/images_default.py b/invokeai/app/services/images/images_default.py index 6cf702cf62..15d950bab8 100644 --- a/invokeai/app/services/images/images_default.py +++ b/invokeai/app/services/images/images_default.py @@ -19,13 +19,12 @@ from invokeai.app.services.image_records.image_records_common import ( InvalidOriginException, ResourceOrigin, ) +from invokeai.app.services.images.images_base import ImageServiceABC +from invokeai.app.services.images.images_common import ImageDTO, image_record_to_dto from invokeai.app.services.invoker import Invoker from invokeai.app.services.shared.pagination import OffsetPaginatedResults from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection -from .images_base import ImageServiceABC -from .images_common import ImageDTO, image_record_to_dto - class ImageService(ImageServiceABC): __invoker: Invoker diff --git a/invokeai/app/services/invocation_services.py b/invokeai/app/services/invocation_services.py index f4fce6098f..90ca613074 100644 --- a/invokeai/app/services/invocation_services.py +++ b/invokeai/app/services/invocation_services.py @@ -10,29 +10,28 @@ if TYPE_CHECKING: import torch + from invokeai.app.services.board_image_records.board_image_records_base import BoardImageRecordStorageBase + from invokeai.app.services.board_images.board_images_base import BoardImagesServiceABC + from invokeai.app.services.board_records.board_records_base import BoardRecordStorageBase + from invokeai.app.services.boards.boards_base import BoardServiceABC + from invokeai.app.services.bulk_download.bulk_download_base import BulkDownloadBase + from invokeai.app.services.config import InvokeAIAppConfig + from invokeai.app.services.download import DownloadQueueServiceBase + from invokeai.app.services.events.events_base import EventServiceBase + from invokeai.app.services.image_files.image_files_base import ImageFileStorageBase + from invokeai.app.services.image_records.image_records_base import ImageRecordStorageBase + from invokeai.app.services.images.images_base import ImageServiceABC + from invokeai.app.services.invocation_cache.invocation_cache_base import InvocationCacheBase + from invokeai.app.services.invocation_stats.invocation_stats_base import InvocationStatsServiceBase + from invokeai.app.services.model_images.model_images_base import ModelImageFileStorageBase + from invokeai.app.services.model_manager.model_manager_base import ModelManagerServiceBase + from invokeai.app.services.names.names_base import NameServiceBase + from invokeai.app.services.session_processor.session_processor_base import SessionProcessorBase + from invokeai.app.services.session_queue.session_queue_base import SessionQueueBase + from invokeai.app.services.urls.urls_base import UrlServiceBase + from invokeai.app.services.workflow_records.workflow_records_base import WorkflowRecordsStorageBase from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningFieldData - from .board_image_records.board_image_records_base import BoardImageRecordStorageBase - from .board_images.board_images_base import BoardImagesServiceABC - from .board_records.board_records_base import BoardRecordStorageBase - from .boards.boards_base import BoardServiceABC - from .bulk_download.bulk_download_base import BulkDownloadBase - from .config import InvokeAIAppConfig - from .download import DownloadQueueServiceBase - from .events.events_base import EventServiceBase - from .image_files.image_files_base import ImageFileStorageBase - from .image_records.image_records_base import ImageRecordStorageBase - from .images.images_base import ImageServiceABC - from .invocation_cache.invocation_cache_base import InvocationCacheBase - from .invocation_stats.invocation_stats_base import InvocationStatsServiceBase - from .model_images.model_images_base import ModelImageFileStorageBase - from .model_manager.model_manager_base import ModelManagerServiceBase - from .names.names_base import NameServiceBase - from .session_processor.session_processor_base import SessionProcessorBase - from .session_queue.session_queue_base import SessionQueueBase - from .urls.urls_base import UrlServiceBase - from .workflow_records.workflow_records_base import WorkflowRecordsStorageBase - class InvocationServices: """Services that can be used by invocations""" diff --git a/invokeai/app/services/invocation_stats/invocation_stats_default.py b/invokeai/app/services/invocation_stats/invocation_stats_default.py index 5a41f1f5d6..5533657dc7 100644 --- a/invokeai/app/services/invocation_stats/invocation_stats_default.py +++ b/invokeai/app/services/invocation_stats/invocation_stats_default.py @@ -9,11 +9,8 @@ import torch import invokeai.backend.util.logging as logger from invokeai.app.invocations.baseinvocation import BaseInvocation -from invokeai.app.services.invoker import Invoker -from invokeai.backend.model_manager.load.model_cache import CacheStats - -from .invocation_stats_base import InvocationStatsServiceBase -from .invocation_stats_common import ( +from invokeai.app.services.invocation_stats.invocation_stats_base import InvocationStatsServiceBase +from invokeai.app.services.invocation_stats.invocation_stats_common import ( GESStatsNotFoundError, GraphExecutionStats, GraphExecutionStatsSummary, @@ -22,6 +19,8 @@ from .invocation_stats_common import ( NodeExecutionStats, NodeExecutionStatsSummary, ) +from invokeai.app.services.invoker import Invoker +from invokeai.backend.model_manager.load.model_cache import CacheStats # Size of 1GB in bytes. GB = 2**30 diff --git a/invokeai/app/services/invoker.py b/invokeai/app/services/invoker.py index 527afb37f4..64f83725a1 100644 --- a/invokeai/app/services/invoker.py +++ b/invokeai/app/services/invoker.py @@ -1,7 +1,7 @@ # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) -from .invocation_services import InvocationServices +from invokeai.app.services.invocation_services import InvocationServices class Invoker: diff --git a/invokeai/app/services/model_images/model_images_default.py b/invokeai/app/services/model_images/model_images_default.py index 0ab79df3ed..36f04a93b5 100644 --- a/invokeai/app/services/model_images/model_images_default.py +++ b/invokeai/app/services/model_images/model_images_default.py @@ -5,15 +5,14 @@ from PIL.Image import Image as PILImageType from send2trash import send2trash from invokeai.app.services.invoker import Invoker -from invokeai.app.util.misc import uuid_string -from invokeai.app.util.thumbnails import make_thumbnail - -from .model_images_base import ModelImageFileStorageBase -from .model_images_common import ( +from invokeai.app.services.model_images.model_images_base import ModelImageFileStorageBase +from invokeai.app.services.model_images.model_images_common import ( ModelImageFileDeleteException, ModelImageFileNotFoundException, ModelImageFileSaveException, ) +from invokeai.app.util.misc import uuid_string +from invokeai.app.util.thumbnails import make_thumbnail class ModelImageFileStorageDisk(ModelImageFileStorageBase): diff --git a/invokeai/app/services/model_install/__init__.py b/invokeai/app/services/model_install/__init__.py index 941485a134..d96e86cbfe 100644 --- a/invokeai/app/services/model_install/__init__.py +++ b/invokeai/app/services/model_install/__init__.py @@ -1,9 +1,7 @@ """Initialization file for model install service package.""" -from .model_install_base import ( - ModelInstallServiceBase, -) -from .model_install_common import ( +from invokeai.app.services.model_install.model_install_base import ModelInstallServiceBase +from invokeai.app.services.model_install.model_install_common import ( HFModelSource, InstallStatus, LocalModelSource, @@ -12,7 +10,7 @@ from .model_install_common import ( UnknownInstallJobException, URLModelSource, ) -from .model_install_default import ModelInstallService +from invokeai.app.services.model_install.model_install_default import ModelInstallService __all__ = [ "ModelInstallServiceBase", diff --git a/invokeai/app/services/model_install/model_install_default.py b/invokeai/app/services/model_install/model_install_default.py index 9ad6424053..5e19a349ad 100644 --- a/invokeai/app/services/model_install/model_install_default.py +++ b/invokeai/app/services/model_install/model_install_default.py @@ -23,6 +23,16 @@ from invokeai.app.services.download import DownloadQueueServiceBase, MultiFileDo from invokeai.app.services.events.events_base import EventServiceBase from invokeai.app.services.invoker import Invoker from invokeai.app.services.model_install.model_install_base import ModelInstallServiceBase +from invokeai.app.services.model_install.model_install_common import ( + MODEL_SOURCE_TO_TYPE_MAP, + HFModelSource, + InstallStatus, + LocalModelSource, + ModelInstallJob, + ModelSource, + StringLikeSource, + URLModelSource, +) from invokeai.app.services.model_records import DuplicateModelException, ModelRecordServiceBase from invokeai.app.services.model_records.model_records_base import ModelRecordChanges from invokeai.backend.model_manager.config import ( @@ -47,17 +57,6 @@ from invokeai.backend.util.catch_sigint import catch_sigint from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.util import slugify -from .model_install_common import ( - MODEL_SOURCE_TO_TYPE_MAP, - HFModelSource, - InstallStatus, - LocalModelSource, - ModelInstallJob, - ModelSource, - StringLikeSource, - URLModelSource, -) - TMPDIR_PREFIX = "tmpinstall_" diff --git a/invokeai/app/services/model_load/__init__.py b/invokeai/app/services/model_load/__init__.py index b4a86e9348..4c7e40c8c7 100644 --- a/invokeai/app/services/model_load/__init__.py +++ b/invokeai/app/services/model_load/__init__.py @@ -1,6 +1,6 @@ """Initialization file for model load service module.""" -from .model_load_base import ModelLoadServiceBase -from .model_load_default import ModelLoadService +from invokeai.app.services.model_load.model_load_base import ModelLoadServiceBase +from invokeai.app.services.model_load.model_load_default import ModelLoadService __all__ = ["ModelLoadServiceBase", "ModelLoadService"] diff --git a/invokeai/app/services/model_load/model_load_default.py b/invokeai/app/services/model_load/model_load_default.py index 8eb94616be..be2cc2478a 100644 --- a/invokeai/app/services/model_load/model_load_default.py +++ b/invokeai/app/services/model_load/model_load_default.py @@ -10,6 +10,7 @@ from torch import load as torch_load from invokeai.app.services.config import InvokeAIAppConfig from invokeai.app.services.invoker import Invoker +from invokeai.app.services.model_load.model_load_base import ModelLoadServiceBase from invokeai.backend.model_manager import AnyModel, AnyModelConfig, SubModelType from invokeai.backend.model_manager.load import ( LoadedModel, @@ -22,8 +23,6 @@ from invokeai.backend.model_manager.load.model_loaders.generic_diffusers import from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import InvokeAILogger -from .model_load_base import ModelLoadServiceBase - class ModelLoadService(ModelLoadServiceBase): """Wrapper around ModelLoaderRegistry.""" diff --git a/invokeai/app/services/model_manager/__init__.py b/invokeai/app/services/model_manager/__init__.py index 5455577266..07c27cee31 100644 --- a/invokeai/app/services/model_manager/__init__.py +++ b/invokeai/app/services/model_manager/__init__.py @@ -1,10 +1,9 @@ """Initialization file for model manager service.""" +from invokeai.app.services.model_manager.model_manager_default import ModelManagerService, ModelManagerServiceBase from invokeai.backend.model_manager import AnyModel, AnyModelConfig, BaseModelType, ModelType, SubModelType from invokeai.backend.model_manager.load import LoadedModel -from .model_manager_default import ModelManagerService, ModelManagerServiceBase - __all__ = [ "ModelManagerServiceBase", "ModelManagerService", diff --git a/invokeai/app/services/model_manager/model_manager_default.py b/invokeai/app/services/model_manager/model_manager_default.py index 7353b98bb4..78f8e09e74 100644 --- a/invokeai/app/services/model_manager/model_manager_default.py +++ b/invokeai/app/services/model_manager/model_manager_default.py @@ -14,13 +14,12 @@ from invokeai.app.services.model_install.model_install_base import ModelInstallS from invokeai.app.services.model_install.model_install_default import ModelInstallService from invokeai.app.services.model_load.model_load_base import ModelLoadServiceBase from invokeai.app.services.model_load.model_load_default import ModelLoadService +from invokeai.app.services.model_manager.model_manager_base import ModelManagerServiceBase from invokeai.app.services.model_records.model_records_base import ModelRecordServiceBase from invokeai.backend.model_manager.load import ModelCache, ModelLoaderRegistry from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import InvokeAILogger -from .model_manager_base import ModelManagerServiceBase - class ModelManagerService(ModelManagerServiceBase): """ diff --git a/invokeai/app/services/model_records/model_records_sql.py b/invokeai/app/services/model_records/model_records_sql.py index 3abbc662ee..2f9829dad4 100644 --- a/invokeai/app/services/model_records/model_records_sql.py +++ b/invokeai/app/services/model_records/model_records_sql.py @@ -45,6 +45,14 @@ from math import ceil from pathlib import Path from typing import List, Optional, Union +from invokeai.app.services.model_records.model_records_base import ( + DuplicateModelException, + ModelRecordChanges, + ModelRecordOrderBy, + ModelRecordServiceBase, + ModelSummary, + UnknownModelException, +) from invokeai.app.services.shared.pagination import PaginatedResults from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase from invokeai.backend.model_manager.config import ( @@ -55,15 +63,6 @@ from invokeai.backend.model_manager.config import ( ModelType, ) -from .model_records_base import ( - DuplicateModelException, - ModelRecordChanges, - ModelRecordOrderBy, - ModelRecordServiceBase, - ModelSummary, - UnknownModelException, -) - class ModelRecordServiceSQL(ModelRecordServiceBase): """Implementation of the ModelConfigStore ABC using a SQL database.""" diff --git a/invokeai/app/services/names/names_default.py b/invokeai/app/services/names/names_default.py index 104268c8bd..5804a937d6 100644 --- a/invokeai/app/services/names/names_default.py +++ b/invokeai/app/services/names/names_default.py @@ -1,7 +1,6 @@ +from invokeai.app.services.names.names_base import NameServiceBase from invokeai.app.util.misc import uuid_string -from .names_base import NameServiceBase - class SimpleNameService(NameServiceBase): """Creates image names from UUIDs.""" diff --git a/invokeai/app/services/session_processor/session_processor_default.py b/invokeai/app/services/session_processor/session_processor_default.py index 336a17d3d5..e4faaeb911 100644 --- a/invokeai/app/services/session_processor/session_processor_default.py +++ b/invokeai/app/services/session_processor/session_processor_default.py @@ -15,22 +15,22 @@ from invokeai.app.services.events.events_common import ( from invokeai.app.services.invocation_stats.invocation_stats_common import GESStatsNotFoundError from invokeai.app.services.invoker import Invoker from invokeai.app.services.session_processor.session_processor_base import ( + InvocationServices, OnAfterRunNode, OnAfterRunSession, OnBeforeRunNode, OnBeforeRunSession, OnNodeError, OnNonFatalProcessorError, + SessionProcessorBase, + SessionRunnerBase, ) -from invokeai.app.services.session_processor.session_processor_common import CanceledException +from invokeai.app.services.session_processor.session_processor_common import CanceledException, SessionProcessorStatus from invokeai.app.services.session_queue.session_queue_common import SessionQueueItem, SessionQueueItemNotFoundError from invokeai.app.services.shared.graph import NodeInputError from invokeai.app.services.shared.invocation_context import InvocationContextData, build_invocation_context from invokeai.app.util.profiler import Profiler -from .session_processor_base import InvocationServices, SessionProcessorBase, SessionRunnerBase -from .session_processor_common import SessionProcessorStatus - class DefaultSessionRunner(SessionRunnerBase): """Processes a single session's invocations.""" diff --git a/invokeai/app/services/urls/urls_default.py b/invokeai/app/services/urls/urls_default.py index ff5071333f..d570521fb8 100644 --- a/invokeai/app/services/urls/urls_default.py +++ b/invokeai/app/services/urls/urls_default.py @@ -1,6 +1,6 @@ import os -from .urls_base import UrlServiceBase +from invokeai.app.services.urls.urls_base import UrlServiceBase class LocalUrlService(UrlServiceBase): diff --git a/invokeai/backend/image_util/__init__.py b/invokeai/backend/image_util/__init__.py index f45af9feb4..bc5eed7ddd 100644 --- a/invokeai/backend/image_util/__init__.py +++ b/invokeai/backend/image_util/__init__.py @@ -2,6 +2,11 @@ Initialization file for invokeai.backend.image_util methods. """ -from .infill_methods.patchmatch import PatchMatch # noqa: F401 -from .pngwriter import PngWriter, PromptFormatter, retrieve_metadata, write_metadata # noqa: F401 -from .util import InitImageResizer, make_grid # noqa: F401 +from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch # noqa: F401 +from invokeai.backend.image_util.pngwriter import ( # noqa: F401 + PngWriter, + PromptFormatter, + retrieve_metadata, + write_metadata, +) +from invokeai.backend.image_util.util import InitImageResizer, make_grid # noqa: F401 diff --git a/invokeai/backend/image_util/basicsr/rrdbnet_arch.py b/invokeai/backend/image_util/basicsr/rrdbnet_arch.py index cdb77f3c21..a99a697123 100644 --- a/invokeai/backend/image_util/basicsr/rrdbnet_arch.py +++ b/invokeai/backend/image_util/basicsr/rrdbnet_arch.py @@ -2,7 +2,7 @@ import torch from torch import nn as nn from torch.nn import functional as F -from .arch_util import default_init_weights, make_layer, pixel_unshuffle +from invokeai.backend.image_util.basicsr.arch_util import default_init_weights, make_layer, pixel_unshuffle class ResidualDenseBlock(nn.Module): diff --git a/invokeai/backend/image_util/depth_anything/model/dpt.py b/invokeai/backend/image_util/depth_anything/model/dpt.py index e1101b3c39..9b1e84c7bd 100644 --- a/invokeai/backend/image_util/depth_anything/model/dpt.py +++ b/invokeai/backend/image_util/depth_anything/model/dpt.py @@ -4,7 +4,7 @@ import torch import torch.nn as nn import torch.nn.functional as F -from .blocks import FeatureFusionBlock, _make_scratch +from invokeai.backend.image_util.depth_anything.model.blocks import FeatureFusionBlock, _make_scratch torchhub_path = Path(__file__).parent.parent / "torchhub" diff --git a/invokeai/backend/image_util/dw_openpose/wholebody.py b/invokeai/backend/image_util/dw_openpose/wholebody.py index 3f77f20b9c..ce028df1fe 100644 --- a/invokeai/backend/image_util/dw_openpose/wholebody.py +++ b/invokeai/backend/image_util/dw_openpose/wholebody.py @@ -8,11 +8,10 @@ import numpy as np import onnxruntime as ort from invokeai.app.services.config.config_default import get_config +from invokeai.backend.image_util.dw_openpose.onnxdet import inference_detector +from invokeai.backend.image_util.dw_openpose.onnxpose import inference_pose from invokeai.backend.util.devices import TorchDevice -from .onnxdet import inference_detector -from .onnxpose import inference_pose - config = get_config() diff --git a/invokeai/backend/ip_adapter/ip_adapter.py b/invokeai/backend/ip_adapter/ip_adapter.py index ed3d70d832..75286f4733 100644 --- a/invokeai/backend/ip_adapter/ip_adapter.py +++ b/invokeai/backend/ip_adapter/ip_adapter.py @@ -11,10 +11,9 @@ from PIL import Image from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection from invokeai.backend.ip_adapter.ip_attention_weights import IPAttentionWeights +from invokeai.backend.ip_adapter.resampler import Resampler from invokeai.backend.raw_model import RawModel -from .resampler import Resampler - class IPAdapterStateDict(TypedDict): ip_adapter: dict[str, torch.Tensor] diff --git a/invokeai/backend/lora.py b/invokeai/backend/lora.py index 8d17de0837..9c669a4c78 100644 --- a/invokeai/backend/lora.py +++ b/invokeai/backend/lora.py @@ -10,10 +10,9 @@ from safetensors.torch import load_file from typing_extensions import Self from invokeai.backend.model_manager import BaseModelType +from invokeai.backend.raw_model import RawModel from invokeai.backend.util.devices import TorchDevice -from .raw_model import RawModel - class LoRALayerBase: # rank: Optional[int] diff --git a/invokeai/backend/model_manager/__init__.py b/invokeai/backend/model_manager/__init__.py index 98cc5054c7..199c0c01f7 100644 --- a/invokeai/backend/model_manager/__init__.py +++ b/invokeai/backend/model_manager/__init__.py @@ -1,6 +1,6 @@ """Re-export frequently-used symbols from the Model Manager backend.""" -from .config import ( +from invokeai.backend.model_manager.config import ( AnyModel, AnyModelConfig, BaseModelType, @@ -13,9 +13,9 @@ from .config import ( SchedulerPredictionType, SubModelType, ) -from .load import LoadedModel -from .probe import ModelProbe -from .search import ModelSearch +from invokeai.backend.model_manager.load import LoadedModel +from invokeai.backend.model_manager.probe import ModelProbe +from invokeai.backend.model_manager.search import ModelSearch __all__ = [ "AnyModel", diff --git a/invokeai/backend/model_manager/load/__init__.py b/invokeai/backend/model_manager/load/__init__.py index 3d34f02727..d9a07bc250 100644 --- a/invokeai/backend/model_manager/load/__init__.py +++ b/invokeai/backend/model_manager/load/__init__.py @@ -6,10 +6,10 @@ Init file for the model loader. from importlib import import_module from pathlib import Path -from .load_base import LoadedModel, LoadedModelWithoutConfig, ModelLoaderBase -from .load_default import ModelLoader -from .model_cache.model_cache_default import ModelCache -from .model_loader_registry import ModelLoaderRegistry, ModelLoaderRegistryBase +from invokeai.backend.model_manager.load.load_base import LoadedModel, LoadedModelWithoutConfig, ModelLoaderBase +from invokeai.backend.model_manager.load.load_default import ModelLoader +from invokeai.backend.model_manager.load.model_cache.model_cache_default import ModelCache +from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry, ModelLoaderRegistryBase # This registers the subclasses that implement loaders of specific model types loaders = [x.stem for x in Path(Path(__file__).parent, "model_loaders").glob("*.py") if x.stem != "__init__"] diff --git a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py index c9e68a926a..9027b7b5b7 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_cache_default.py +++ b/invokeai/backend/model_manager/load/model_cache/model_cache_default.py @@ -29,13 +29,17 @@ import torch from invokeai.backend.model_manager import AnyModel, SubModelType from invokeai.backend.model_manager.load.memory_snapshot import MemorySnapshot, get_pretty_snapshot_diff +from invokeai.backend.model_manager.load.model_cache.model_cache_base import ( + CacheRecord, + CacheStats, + ModelCacheBase, + ModelLockerBase, +) +from invokeai.backend.model_manager.load.model_cache.model_locker import ModelLocker from invokeai.backend.model_manager.load.model_util import calc_model_size_by_data from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import InvokeAILogger -from .model_cache_base import CacheRecord, CacheStats, ModelCacheBase, ModelLockerBase -from .model_locker import ModelLocker - # Maximum size of the cache, in gigs # Default is roughly enough to hold three fp16 diffusers models in RAM simultaneously DEFAULT_MAX_CACHE_SIZE = 6.0 diff --git a/invokeai/backend/model_manager/load/model_cache/model_locker.py b/invokeai/backend/model_manager/load/model_cache/model_locker.py index 9de17ca5f5..efbfc726f7 100644 --- a/invokeai/backend/model_manager/load/model_cache/model_locker.py +++ b/invokeai/backend/model_manager/load/model_cache/model_locker.py @@ -7,8 +7,11 @@ from typing import Dict, Optional import torch from invokeai.backend.model_manager import AnyModel - -from .model_cache_base import CacheRecord, ModelCacheBase, ModelLockerBase +from invokeai.backend.model_manager.load.model_cache.model_cache_base import ( + CacheRecord, + ModelCacheBase, + ModelLockerBase, +) class ModelLocker(ModelLockerBase): diff --git a/invokeai/backend/model_manager/load/model_loader_registry.py b/invokeai/backend/model_manager/load/model_loader_registry.py index e44d1bd41f..0ce8f8a6b4 100644 --- a/invokeai/backend/model_manager/load/model_loader_registry.py +++ b/invokeai/backend/model_manager/load/model_loader_registry.py @@ -26,8 +26,7 @@ from invokeai.backend.model_manager.config import ( ModelType, SubModelType, ) - -from . import ModelLoaderBase +from invokeai.backend.model_manager.load import ModelLoaderBase class ModelLoaderRegistryBase(ABC): diff --git a/invokeai/backend/model_manager/load/model_loaders/controlnet.py b/invokeai/backend/model_manager/load/model_loaders/controlnet.py index 6edcc48760..82091874df 100644 --- a/invokeai/backend/model_manager/load/model_loaders/controlnet.py +++ b/invokeai/backend/model_manager/load/model_loaders/controlnet.py @@ -14,8 +14,7 @@ from invokeai.backend.model_manager import ( ) from invokeai.backend.model_manager.config import ControlNetCheckpointConfig, SubModelType from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry - -from .generic_diffusers import GenericDiffusersLoader +from invokeai.backend.model_manager.load.model_loaders.generic_diffusers import GenericDiffusersLoader @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.ControlNet, format=ModelFormat.Diffusers) diff --git a/invokeai/backend/model_manager/load/model_loaders/onnx.py b/invokeai/backend/model_manager/load/model_loaders/onnx.py index de4b4f0e2b..0a5d8477c4 100644 --- a/invokeai/backend/model_manager/load/model_loaders/onnx.py +++ b/invokeai/backend/model_manager/load/model_loaders/onnx.py @@ -14,8 +14,7 @@ from invokeai.backend.model_manager import ( SubModelType, ) from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry - -from .generic_diffusers import GenericDiffusersLoader +from invokeai.backend.model_manager.load.model_loaders.generic_diffusers import GenericDiffusersLoader @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.ONNX, format=ModelFormat.ONNX) diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 46b527acc9..d90352f0e6 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -26,10 +26,9 @@ from invokeai.backend.model_manager.config import ( MainCheckpointConfig, ) from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry +from invokeai.backend.model_manager.load.model_loaders.generic_diffusers import GenericDiffusersLoader from invokeai.backend.util.silence_warnings import SilenceWarnings -from .generic_diffusers import GenericDiffusersLoader - VARIANT_TO_IN_CHANNEL_MAP = { ModelVariantType.Normal: 4, ModelVariantType.Depth: 5, diff --git a/invokeai/backend/model_manager/load/model_loaders/vae.py b/invokeai/backend/model_manager/load/model_loaders/vae.py index e01d4c9251..bae29ea773 100644 --- a/invokeai/backend/model_manager/load/model_loaders/vae.py +++ b/invokeai/backend/model_manager/load/model_loaders/vae.py @@ -13,8 +13,7 @@ from invokeai.backend.model_manager import ( ) from invokeai.backend.model_manager.config import AnyModel, SubModelType, VAECheckpointConfig from invokeai.backend.model_manager.load.model_loader_registry import ModelLoaderRegistry - -from .generic_diffusers import GenericDiffusersLoader +from invokeai.backend.model_manager.load.model_loaders.generic_diffusers import GenericDiffusersLoader @ModelLoaderRegistry.register(base=BaseModelType.Any, type=ModelType.VAE, format=ModelFormat.Diffusers) diff --git a/invokeai/backend/model_manager/merge.py b/invokeai/backend/model_manager/merge.py index 125e99be93..b00bc99f3e 100644 --- a/invokeai/backend/model_manager/merge.py +++ b/invokeai/backend/model_manager/merge.py @@ -17,16 +17,10 @@ from diffusers.utils import logging as dlogging from invokeai.app.services.model_install import ModelInstallServiceBase from invokeai.app.services.model_records.model_records_base import ModelRecordChanges +from invokeai.backend.model_manager import AnyModelConfig, BaseModelType, ModelType, ModelVariantType +from invokeai.backend.model_manager.config import MainDiffusersConfig from invokeai.backend.util.devices import TorchDevice -from . import ( - AnyModelConfig, - BaseModelType, - ModelType, - ModelVariantType, -) -from .config import MainDiffusersConfig - class MergeInterpolationMethod(str, Enum): WeightedSum = "weighted_sum" diff --git a/invokeai/backend/model_manager/metadata/__init__.py b/invokeai/backend/model_manager/metadata/__init__.py index 1fd080b679..76da268153 100644 --- a/invokeai/backend/model_manager/metadata/__init__.py +++ b/invokeai/backend/model_manager/metadata/__init__.py @@ -16,8 +16,8 @@ data = HuggingFaceMetadataFetch().from_id("") assert isinstance(data, HuggingFaceMetadata) """ -from .fetch import HuggingFaceMetadataFetch, ModelMetadataFetchBase -from .metadata_base import ( +from invokeai.backend.model_manager.metadata.fetch import HuggingFaceMetadataFetch, ModelMetadataFetchBase +from invokeai.backend.model_manager.metadata.metadata_base import ( AnyModelRepoMetadata, AnyModelRepoMetadataValidator, BaseMetadata, diff --git a/invokeai/backend/model_manager/metadata/fetch/__init__.py b/invokeai/backend/model_manager/metadata/fetch/__init__.py index 652a3cf6b7..62b3dc4d54 100644 --- a/invokeai/backend/model_manager/metadata/fetch/__init__.py +++ b/invokeai/backend/model_manager/metadata/fetch/__init__.py @@ -10,7 +10,7 @@ data = HuggingFaceMetadataFetch().from_id("") assert isinstance(data, HuggingFaceMetadata) """ -from .fetch_base import ModelMetadataFetchBase -from .huggingface import HuggingFaceMetadataFetch +from invokeai.backend.model_manager.metadata.fetch.fetch_base import ModelMetadataFetchBase +from invokeai.backend.model_manager.metadata.fetch.huggingface import HuggingFaceMetadataFetch __all__ = ["ModelMetadataFetchBase", "HuggingFaceMetadataFetch"] diff --git a/invokeai/backend/model_manager/metadata/fetch/huggingface.py b/invokeai/backend/model_manager/metadata/fetch/huggingface.py index 49b942c7d7..8787ceeb36 100644 --- a/invokeai/backend/model_manager/metadata/fetch/huggingface.py +++ b/invokeai/backend/model_manager/metadata/fetch/huggingface.py @@ -25,6 +25,7 @@ from pydantic.networks import AnyHttpUrl from requests.sessions import Session from invokeai.backend.model_manager.config import ModelRepoVariant +from invokeai.backend.model_manager.metadata.fetch.fetch_base import ModelMetadataFetchBase from invokeai.backend.model_manager.metadata.metadata_base import ( AnyModelRepoMetadata, HuggingFaceMetadata, @@ -32,8 +33,6 @@ from invokeai.backend.model_manager.metadata.metadata_base import ( UnknownMetadataException, ) -from .fetch_base import ModelMetadataFetchBase - HF_MODEL_RE = r"https?://huggingface.co/([\w\-.]+/[\w\-.]+)" diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index 2f18f1a8a6..f6fb2d24bc 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -10,9 +10,7 @@ from picklescan.scanner import scan_file_path import invokeai.backend.util.logging as logger from invokeai.app.util.misc import uuid_string from invokeai.backend.model_hash.model_hash import HASHING_ALGORITHMS, ModelHash -from invokeai.backend.util.silence_warnings import SilenceWarnings - -from .config import ( +from invokeai.backend.model_manager.config import ( AnyModelConfig, BaseModelType, ControlAdapterDefaultSettings, @@ -26,7 +24,8 @@ from .config import ( ModelVariantType, SchedulerPredictionType, ) -from .util.model_util import lora_token_vector_length, read_checkpoint_meta +from invokeai.backend.model_manager.util.model_util import lora_token_vector_length, read_checkpoint_meta +from invokeai.backend.util.silence_warnings import SilenceWarnings CkptType = Dict[str | int, Any] diff --git a/invokeai/backend/model_patcher.py b/invokeai/backend/model_patcher.py index 051d114276..8c7a62c371 100644 --- a/invokeai/backend/model_patcher.py +++ b/invokeai/backend/model_patcher.py @@ -13,14 +13,13 @@ from diffusers import OnnxRuntimeModel, UNet2DConditionModel from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer from invokeai.app.shared.models import FreeUConfig +from invokeai.backend.lora import LoRAModelRaw from invokeai.backend.model_manager import AnyModel from invokeai.backend.model_manager.load.optimizations import skip_torch_weight_init from invokeai.backend.onnx.onnx_runtime import IAIOnnxRuntimeModel +from invokeai.backend.textual_inversion import TextualInversionManager, TextualInversionModelRaw from invokeai.backend.util.devices import TorchDevice -from .lora import LoRAModelRaw -from .textual_inversion import TextualInversionManager, TextualInversionModelRaw - """ loras = [ (lora_model1, 0.7), @@ -338,7 +337,7 @@ class ONNXModelPatcher: loras: List[Tuple[LoRAModelRaw, float]], prefix: str, ) -> None: - from .models.base import IAIOnnxRuntimeModel + from invokeai.backend.models.base import IAIOnnxRuntimeModel if not isinstance(model, IAIOnnxRuntimeModel): raise Exception("Only IAIOnnxRuntimeModel models supported") @@ -425,7 +424,7 @@ class ONNXModelPatcher: text_encoder: IAIOnnxRuntimeModel, ti_list: List[Tuple[str, Any]], ) -> Iterator[Tuple[CLIPTokenizer, TextualInversionManager]]: - from .models.base import IAIOnnxRuntimeModel + from invokeai.backend.models.base import IAIOnnxRuntimeModel if not isinstance(text_encoder, IAIOnnxRuntimeModel): raise Exception("Only IAIOnnxRuntimeModel models supported") diff --git a/invokeai/backend/stable_diffusion/__init__.py b/invokeai/backend/stable_diffusion/__init__.py index ed6782eefa..440cb4410b 100644 --- a/invokeai/backend/stable_diffusion/__init__.py +++ b/invokeai/backend/stable_diffusion/__init__.py @@ -2,9 +2,12 @@ Initialization file for the invokeai.backend.stable_diffusion package """ -from .diffusers_pipeline import PipelineIntermediateState, StableDiffusionGeneratorPipeline # noqa: F401 -from .diffusion import InvokeAIDiffuserComponent # noqa: F401 -from .seamless import set_seamless # noqa: F401 +from invokeai.backend.stable_diffusion.diffusers_pipeline import ( # noqa: F401 + PipelineIntermediateState, + StableDiffusionGeneratorPipeline, +) +from invokeai.backend.stable_diffusion.diffusion import InvokeAIDiffuserComponent # noqa: F401 +from invokeai.backend.stable_diffusion.seamless import set_seamless # noqa: F401 __all__ = [ "PipelineIntermediateState", diff --git a/invokeai/backend/stable_diffusion/diffusion/__init__.py b/invokeai/backend/stable_diffusion/diffusion/__init__.py index 854d127a36..712542f79c 100644 --- a/invokeai/backend/stable_diffusion/diffusion/__init__.py +++ b/invokeai/backend/stable_diffusion/diffusion/__init__.py @@ -2,4 +2,6 @@ Initialization file for invokeai.models.diffusion """ -from .shared_invokeai_diffusion import InvokeAIDiffuserComponent # noqa: F401 +from invokeai.backend.stable_diffusion.diffusion.shared_invokeai_diffusion import ( + InvokeAIDiffuserComponent, # noqa: F401 +) diff --git a/invokeai/backend/stable_diffusion/schedulers/__init__.py b/invokeai/backend/stable_diffusion/schedulers/__init__.py index 0b780d3ee2..6c02acda51 100644 --- a/invokeai/backend/stable_diffusion/schedulers/__init__.py +++ b/invokeai/backend/stable_diffusion/schedulers/__init__.py @@ -1,3 +1,3 @@ -from .schedulers import SCHEDULER_MAP # noqa: F401 +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_MAP # noqa: F401 __all__ = ["SCHEDULER_MAP"] diff --git a/invokeai/backend/textual_inversion.py b/invokeai/backend/textual_inversion.py index 4c7625ea37..483f2da88c 100644 --- a/invokeai/backend/textual_inversion.py +++ b/invokeai/backend/textual_inversion.py @@ -9,7 +9,7 @@ from safetensors.torch import load_file from transformers import CLIPTokenizer from typing_extensions import Self -from .raw_model import RawModel +from invokeai.backend.raw_model import RawModel class TextualInversionModelRaw(RawModel): diff --git a/invokeai/backend/util/__init__.py b/invokeai/backend/util/__init__.py index 1e4d467cd0..101215640a 100644 --- a/invokeai/backend/util/__init__.py +++ b/invokeai/backend/util/__init__.py @@ -2,8 +2,8 @@ Initialization file for invokeai.backend.util """ -from .logging import InvokeAILogger -from .util import GIG, Chdir, directory_size +from invokeai.backend.util.logging import InvokeAILogger +from invokeai.backend.util.util import GIG, Chdir, directory_size __all__ = [ "GIG", diff --git a/invokeai/version/__init__.py b/invokeai/version/__init__.py index dbc30bc636..57efb1af95 100644 --- a/invokeai/version/__init__.py +++ b/invokeai/version/__init__.py @@ -2,7 +2,7 @@ initialization file for invokeai """ -from .invokeai_version import __version__ # noqa: F401 +from invokeai.version.invokeai_version import __version__ # noqa: F401 __app_id__ = "invoke-ai/InvokeAI" __app_name__ = "InvokeAI" diff --git a/pyproject.toml b/pyproject.toml index ec215230f3..a11a19071c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -210,7 +210,7 @@ select = ["B", "C", "E", "F", "W", "I", "TID"] [tool.ruff.lint.flake8-tidy-imports] # Disallow all relative imports. -ban-relative-imports = "parents" +ban-relative-imports = "all" #=== End: Ruff diff --git a/tests/test_graph_execution_state.py b/tests/test_graph_execution_state.py index d8cbc38860..9aee5febc9 100644 --- a/tests/test_graph_execution_state.py +++ b/tests/test_graph_execution_state.py @@ -3,13 +3,6 @@ from unittest.mock import Mock import pytest -# This import must happen before other invoke imports or test in other files(!!) break -from .test_nodes import ( # isort: split - PromptCollectionTestInvocation, - PromptTestInvocation, - TextToImageTestInvocation, -) - from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, InvocationContext from invokeai.app.invocations.collections import RangeInvocation from invokeai.app.invocations.math import AddInvocation, MultiplyInvocation @@ -20,7 +13,13 @@ from invokeai.app.services.shared.graph import ( IterateInvocation, ) -from .test_nodes import create_edge +# This import must happen before other invoke imports or test in other files(!!) break +from tests.test_nodes import ( + PromptCollectionTestInvocation, + PromptTestInvocation, + TextToImageTestInvocation, + create_edge, +) @pytest.fixture diff --git a/tests/test_node_graph.py b/tests/test_node_graph.py index 861f1bd07b..ebaab9ff54 100644 --- a/tests/test_node_graph.py +++ b/tests/test_node_graph.py @@ -27,8 +27,7 @@ from invokeai.app.services.shared.graph import ( NodeNotFoundError, are_connections_compatible, ) - -from .test_nodes import ( +from tests.test_nodes import ( AnyTypeTestInvocation, ImageToImageTestInvocation, ListPassThroughInvocation, diff --git a/tests/test_session_queue.py b/tests/test_session_queue.py index bf26b9b002..f0a9dd4808 100644 --- a/tests/test_session_queue.py +++ b/tests/test_session_queue.py @@ -11,8 +11,7 @@ from invokeai.app.services.session_queue.session_queue_common import ( prepare_values_to_insert, ) from invokeai.app.services.shared.graph import Graph, GraphExecutionState - -from .test_nodes import PromptTestInvocation +from tests.test_nodes import PromptTestInvocation @pytest.fixture From 36202d6d25762a913d2e22df82de57a5e8fabbe3 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Thu, 4 Jul 2024 10:07:56 -0400 Subject: [PATCH 11/37] Delete unused duplicate libc_util.py file. The active version is at invokeai/backend/model_manager/libc_util.py. --- invokeai/backend/model_manager/libc_util.py | 75 --------------------- 1 file changed, 75 deletions(-) delete mode 100644 invokeai/backend/model_manager/libc_util.py diff --git a/invokeai/backend/model_manager/libc_util.py b/invokeai/backend/model_manager/libc_util.py deleted file mode 100644 index 1fbcae0a93..0000000000 --- a/invokeai/backend/model_manager/libc_util.py +++ /dev/null @@ -1,75 +0,0 @@ -import ctypes - - -class Struct_mallinfo2(ctypes.Structure): - """A ctypes Structure that matches the libc mallinfo2 struct. - - Docs: - - https://man7.org/linux/man-pages/man3/mallinfo.3.html - - https://www.gnu.org/software/libc/manual/html_node/Statistics-of-Malloc.html - - struct mallinfo2 { - size_t arena; /* Non-mmapped space allocated (bytes) */ - size_t ordblks; /* Number of free chunks */ - size_t smblks; /* Number of free fastbin blocks */ - size_t hblks; /* Number of mmapped regions */ - size_t hblkhd; /* Space allocated in mmapped regions (bytes) */ - size_t usmblks; /* See below */ - size_t fsmblks; /* Space in freed fastbin blocks (bytes) */ - size_t uordblks; /* Total allocated space (bytes) */ - size_t fordblks; /* Total free space (bytes) */ - size_t keepcost; /* Top-most, releasable space (bytes) */ - }; - """ - - _fields_ = [ - ("arena", ctypes.c_size_t), - ("ordblks", ctypes.c_size_t), - ("smblks", ctypes.c_size_t), - ("hblks", ctypes.c_size_t), - ("hblkhd", ctypes.c_size_t), - ("usmblks", ctypes.c_size_t), - ("fsmblks", ctypes.c_size_t), - ("uordblks", ctypes.c_size_t), - ("fordblks", ctypes.c_size_t), - ("keepcost", ctypes.c_size_t), - ] - - def __str__(self): - s = "" - s += f"{'arena': <10}= {(self.arena/2**30):15.5f} # Non-mmapped space allocated (GB) (uordblks + fordblks)\n" - s += f"{'ordblks': <10}= {(self.ordblks): >15} # Number of free chunks\n" - s += f"{'smblks': <10}= {(self.smblks): >15} # Number of free fastbin blocks \n" - s += f"{'hblks': <10}= {(self.hblks): >15} # Number of mmapped regions \n" - s += f"{'hblkhd': <10}= {(self.hblkhd/2**30):15.5f} # Space allocated in mmapped regions (GB)\n" - s += f"{'usmblks': <10}= {(self.usmblks): >15} # Unused\n" - s += f"{'fsmblks': <10}= {(self.fsmblks/2**30):15.5f} # Space in freed fastbin blocks (GB)\n" - s += ( - f"{'uordblks': <10}= {(self.uordblks/2**30):15.5f} # Space used by in-use allocations (non-mmapped)" - " (GB)\n" - ) - s += f"{'fordblks': <10}= {(self.fordblks/2**30):15.5f} # Space in free blocks (non-mmapped) (GB)\n" - s += f"{'keepcost': <10}= {(self.keepcost/2**30):15.5f} # Top-most, releasable space (GB)\n" - return s - - -class LibcUtil: - """A utility class for interacting with the C Standard Library (`libc`) via ctypes. - - Note that this class will raise on __init__() if 'libc.so.6' can't be found. Take care to handle environments where - this shared library is not available. - - TODO: Improve cross-OS compatibility of this class. - """ - - def __init__(self): - self._libc = ctypes.cdll.LoadLibrary("libc.so.6") - - def mallinfo2(self) -> Struct_mallinfo2: - """Calls `libc` `mallinfo2`. - - Docs: https://man7.org/linux/man-pages/man3/mallinfo.3.html - """ - mallinfo2 = self._libc.mallinfo2 - mallinfo2.restype = Struct_mallinfo2 - return mallinfo2() From 7c8846e309b4be1b36bfb44689939b9fc2bdf034 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Thu, 4 Jul 2024 11:30:38 -0400 Subject: [PATCH 12/37] Update the PR template QA instructions to 1) make it clear that authors are responsible for testing their PRs, and 2) encourage sufficient detail in the QA section. --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6c8fee470e..e29d481b41 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,7 +8,7 @@ ## QA Instructions - + ## Merge Plan From 35f8781ea22cccbbbb6fab92180ee2b7bca35a70 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Wed, 3 Jul 2024 11:13:16 -0400 Subject: [PATCH 13/37] Fix static type errors with SCHEDULER_NAME_VALUES. And, avoid bi-directional cross-directory imports, which contribute to circular import issues. --- invokeai/app/invocations/constants.py | 4 --- invokeai/app/invocations/denoise_latents.py | 3 +- invokeai/app/invocations/scheduler.py | 2 +- .../tiled_multi_diffusion_denoise_latents.py | 3 +- invokeai/backend/model_manager/config.py | 2 +- .../stable_diffusion/schedulers/schedulers.py | 32 ++++++++++++++++++- invokeai/invocation_api/__init__.py | 4 +-- .../schedulers/test_schedulers.py | 10 ++++++ 8 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 tests/backend/stable_diffusion/schedulers/test_schedulers.py diff --git a/invokeai/app/invocations/constants.py b/invokeai/app/invocations/constants.py index e01589be81..e97275e4fd 100644 --- a/invokeai/app/invocations/constants.py +++ b/invokeai/app/invocations/constants.py @@ -1,6 +1,5 @@ from typing import Literal -from invokeai.backend.stable_diffusion.schedulers import SCHEDULER_MAP from invokeai.backend.util.devices import TorchDevice LATENT_SCALE_FACTOR = 8 @@ -11,9 +10,6 @@ factor is hard-coded to a literal '8' rather than using this constant. The ratio of image:latent dimensions is LATENT_SCALE_FACTOR:1, or 8:1. """ -SCHEDULER_NAME_VALUES = Literal[tuple(SCHEDULER_MAP.keys())] -"""A literal type representing the valid scheduler names.""" - IMAGE_MODES = Literal["L", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"] """A literal type for PIL image modes supported by Invoke""" diff --git a/invokeai/app/invocations/denoise_latents.py b/invokeai/app/invocations/denoise_latents.py index fd901298f7..7ccf906893 100644 --- a/invokeai/app/invocations/denoise_latents.py +++ b/invokeai/app/invocations/denoise_latents.py @@ -17,7 +17,7 @@ from torchvision.transforms.functional import resize as tv_resize from transformers import CLIPVisionModelWithProjection from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation -from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR from invokeai.app.invocations.controlnet_image_processors import ControlField from invokeai.app.invocations.fields import ( ConditioningField, @@ -54,6 +54,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( TextConditioningRegions, ) from invokeai.backend.stable_diffusion.schedulers import SCHEDULER_MAP +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.hotfixes import ControlNetModel from invokeai.backend.util.mask import to_standard_float_mask diff --git a/invokeai/app/invocations/scheduler.py b/invokeai/app/invocations/scheduler.py index 52af20378e..a870a442ef 100644 --- a/invokeai/app/invocations/scheduler.py +++ b/invokeai/app/invocations/scheduler.py @@ -1,5 +1,4 @@ from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output -from invokeai.app.invocations.constants import SCHEDULER_NAME_VALUES from invokeai.app.invocations.fields import ( FieldDescriptions, InputField, @@ -7,6 +6,7 @@ from invokeai.app.invocations.fields import ( UIType, ) from invokeai.app.services.shared.invocation_context import InvocationContext +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES @invocation_output("scheduler_output") diff --git a/invokeai/app/invocations/tiled_multi_diffusion_denoise_latents.py b/invokeai/app/invocations/tiled_multi_diffusion_denoise_latents.py index 2566fd2551..5d408a4df7 100644 --- a/invokeai/app/invocations/tiled_multi_diffusion_denoise_latents.py +++ b/invokeai/app/invocations/tiled_multi_diffusion_denoise_latents.py @@ -8,7 +8,7 @@ from diffusers.schedulers.scheduling_utils import SchedulerMixin from pydantic import field_validator from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, invocation -from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES +from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR from invokeai.app.invocations.controlnet_image_processors import ControlField from invokeai.app.invocations.denoise_latents import DenoiseLatentsInvocation, get_scheduler from invokeai.app.invocations.fields import ( @@ -29,6 +29,7 @@ from invokeai.backend.stable_diffusion.multi_diffusion_pipeline import ( MultiDiffusionPipeline, MultiDiffusionRegionConditioning, ) +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES from invokeai.backend.tiles.tiles import ( calc_tiles_min_overlap, ) diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index a8eb13d339..dbcd259368 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -30,10 +30,10 @@ from diffusers.models.modeling_utils import ModelMixin from pydantic import BaseModel, ConfigDict, Discriminator, Field, Tag, TypeAdapter from typing_extensions import Annotated, Any, Dict -from invokeai.app.invocations.constants import SCHEDULER_NAME_VALUES from invokeai.app.util.misc import uuid_string from invokeai.backend.model_hash.hash_validator import validate_hash from invokeai.backend.raw_model import RawModel +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES # ModelMixin is the base class for all diffusers and transformers models # RawModel is the InvokeAI wrapper class for ip_adapters, loras, textual_inversion and onnx runtime diff --git a/invokeai/backend/stable_diffusion/schedulers/schedulers.py b/invokeai/backend/stable_diffusion/schedulers/schedulers.py index 3a55d52d4a..7d6851e278 100644 --- a/invokeai/backend/stable_diffusion/schedulers/schedulers.py +++ b/invokeai/backend/stable_diffusion/schedulers/schedulers.py @@ -1,3 +1,5 @@ +from typing import Any, Literal, Type + from diffusers import ( DDIMScheduler, DDPMScheduler, @@ -16,8 +18,36 @@ from diffusers import ( TCDScheduler, UniPCMultistepScheduler, ) +from diffusers.schedulers.scheduling_utils import SchedulerMixin -SCHEDULER_MAP = { +SCHEDULER_NAME_VALUES = Literal[ + "ddim", + "ddpm", + "deis", + "lms", + "lms_k", + "pndm", + "heun", + "heun_k", + "euler", + "euler_k", + "euler_a", + "kdpm_2", + "kdpm_2_a", + "dpmpp_2s", + "dpmpp_2s_k", + "dpmpp_2m", + "dpmpp_2m_k", + "dpmpp_2m_sde", + "dpmpp_2m_sde_k", + "dpmpp_sde", + "dpmpp_sde_k", + "unipc", + "lcm", + "tcd", +] + +SCHEDULER_MAP: dict[SCHEDULER_NAME_VALUES, tuple[Type[SchedulerMixin], dict[str, Any]]] = { "ddim": (DDIMScheduler, {}), "ddpm": (DDPMScheduler, {}), "deis": (DEISMultistepScheduler, {}), diff --git a/invokeai/invocation_api/__init__.py b/invokeai/invocation_api/__init__.py index 97260c4dfe..586f85b9c2 100644 --- a/invokeai/invocation_api/__init__.py +++ b/invokeai/invocation_api/__init__.py @@ -11,7 +11,6 @@ from invokeai.app.invocations.baseinvocation import ( invocation, invocation_output, ) -from invokeai.app.invocations.constants import SCHEDULER_NAME_VALUES from invokeai.app.invocations.fields import ( BoardField, ColorField, @@ -78,6 +77,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( ConditioningFieldData, SDXLConditioningInfo, ) +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES from invokeai.backend.util.devices import CPU_DEVICE, CUDA_DEVICE, MPS_DEVICE, choose_precision, choose_torch_device from invokeai.version import __version__ @@ -163,7 +163,7 @@ __all__ = [ "BaseModelType", "ModelType", "SubModelType", - # invokeai.app.invocations.constants + # invokeai.backend.stable_diffusion.schedulers.schedulers "SCHEDULER_NAME_VALUES", # invokeai.version "__version__", diff --git a/tests/backend/stable_diffusion/schedulers/test_schedulers.py b/tests/backend/stable_diffusion/schedulers/test_schedulers.py new file mode 100644 index 0000000000..bb49fc4f3b --- /dev/null +++ b/tests/backend/stable_diffusion/schedulers/test_schedulers.py @@ -0,0 +1,10 @@ +from typing import get_args + +from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_MAP, SCHEDULER_NAME_VALUES + + +def test_scheduler_map_has_all_keys(): + # Assert that SCHEDULER_MAP has all keys from SCHEDULER_NAME_VALUES. + # TODO(ryand): This feels like it should be a type check, but I couldn't find a clean way to do this and didn't want + # to spend more time on it. + assert set(SCHEDULER_MAP.keys()) == set(get_args(SCHEDULER_NAME_VALUES)) From bdf4fcda234591ae31031adbd40209a54b9adb01 Mon Sep 17 00:00:00 2001 From: ddm21 <98445934+ddm21@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:21:01 +0530 Subject: [PATCH 14/37] Fixed 404 error on latest release link (line 16): This commit corrects a broken link on line 16 that was pointing to the latest release but causing a 404 error (page not found) when clicked. The issue was identified as a trailing dot at the end of the URL, which has now been removed. This ensures users can access the intended latest release page. --- installer/templates/invoke.bat.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/templates/invoke.bat.in b/installer/templates/invoke.bat.in index c8ef19710b..774b667c08 100644 --- a/installer/templates/invoke.bat.in +++ b/installer/templates/invoke.bat.in @@ -13,7 +13,7 @@ echo 2. Open the developer console echo 3. Command-line help echo Q - Quit echo. -echo To update, download and run the installer from https://github.com/invoke-ai/InvokeAI/releases/latest. +echo To update, download and run the installer from https://github.com/invoke-ai/InvokeAI/releases/latest echo. set /P choice="Please enter 1-4, Q: [1] " if not defined choice set choice=1 From 9ca6980c7a027296f47f9ab3802718eea3747c19 Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 13:29:53 -0400 Subject: [PATCH 15/37] cleanup and bug fixes --- invokeai/app/services/boards/boards_common.py | 2 + invokeai/frontend/web/public/locales/en.json | 3 + .../Boards/BoardsList/AddBoardButton.tsx | 13 +- .../Boards/BoardsList/BoardsList.tsx | 124 +++++--- .../Boards/BoardsList/GalleryBoard.tsx | 129 ++++----- .../Boards/BoardsList/NoBoardBoard.tsx | 28 +- .../BoardsListWithPrivate/AddBoardButton.tsx | 32 --- .../BoardTotalsTooltip.tsx | 22 -- .../BoardsListWithPrivate.tsx | 120 -------- .../BoardsListWithPrivate/BoardsSearch.tsx | 66 ----- .../BoardsListWithPrivate/GalleryBoard.tsx | 194 ------------- .../BoardsListWithPrivate/NoBoardBoard.tsx | 96 ------- .../components/ImageGalleryContent.tsx | 22 +- .../frontend/web/src/services/api/schema.ts | 268 +++++++++--------- .../frontend/web/src/services/api/types.ts | 4 +- 15 files changed, 313 insertions(+), 810 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx diff --git a/invokeai/app/services/boards/boards_common.py b/invokeai/app/services/boards/boards_common.py index 0cb54102bb..3f3190a06a 100644 --- a/invokeai/app/services/boards/boards_common.py +++ b/invokeai/app/services/boards/boards_common.py @@ -12,6 +12,8 @@ class BoardDTO(BoardRecord): """The URL of the thumbnail of the most recent image in the board.""" image_count: int = Field(description="The number of images in the board.") """The number of images in the board.""" + is_private: Optional[bool] = Field(description="Whether the board is private.") + """Whether the board is private.""" def board_record_to_dto(board_record: BoardRecord, cover_image_name: Optional[str], image_count: int) -> BoardDTO: diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 289159eaf0..32c527d883 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -20,6 +20,7 @@ "archiveBoard": "Archive Board", "archived": "Archived", "autoAddBoard": "Auto-Add Board", + "boards": "Boards", "bottomMessage": "Deleting this board and its images will reset any features currently using them.", "cancel": "Cancel", "changeBoard": "Change Board", @@ -35,8 +36,10 @@ "movingImagesToBoard_other": "Moving {{count}} images to board:", "myBoard": "My Board", "noMatching": "No matching Boards", + "private": "Private", "searchBoard": "Search Boards...", "selectBoard": "Select a Board", + "shared": "Shared", "topMessage": "This board contains images used in the following features:", "unarchiveBoard": "Unarchive Board", "uncategorized": "Uncategorized", diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index 5cd4d001f4..abf690f693 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -4,13 +4,17 @@ import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; -const AddBoardButton = () => { +type Props = { + privateBoard: boolean; +}; + +const AddBoardButton = ({ privateBoard }: Props) => { const { t } = useTranslation(); const [createBoard, { isLoading }] = useCreateBoardMutation(); const DEFAULT_BOARD_NAME = t('boards.myBoard'); const handleCreateBoard = useCallback(() => { - createBoard(DEFAULT_BOARD_NAME); - }, [createBoard, DEFAULT_BOARD_NAME]); + createBoard({ board_name: DEFAULT_BOARD_NAME, private_board: privateBoard }); + }, [createBoard, DEFAULT_BOARD_NAME, privateBoard]); return ( { tooltip={t('boards.addBoard')} aria-label={t('boards.addBoard')} onClick={handleCreateBoard} - size="sm" + size="md" data-testid="add-board-button" + variant="ghost" /> ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index e47edd21fc..99b2becd61 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -1,11 +1,14 @@ -import { Collapse, Flex, Grid, GridItem } from '@invoke-ai/ui-library'; +import { Box, Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; +import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import type { CSSProperties } from 'react'; -import { memo, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiCaretUpBold } from 'react-icons/pi'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import type { BoardDTO } from 'services/api/types'; @@ -19,56 +22,109 @@ const overlayScrollbarsStyles: CSSProperties = { width: '100%', }; -type Props = { - isOpen: boolean; -}; - -const BoardsList = (props: Props) => { - const { isOpen } = props; +const BoardsListWithPrivate = () => { const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const queryArgs = useAppSelector(selectListBoardsQueryArgs); const { data: boards } = useListAllBoardsQuery(queryArgs); const filteredBoards = boardSearchText ? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) : boards; + const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private); + const filteredSharedBoards = boards?.filter((board) => !board.is_private); const [boardToDelete, setBoardToDelete] = useState(); + const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true); + const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true); + const { t } = useTranslation(); + + const handlePrivateBoardsToggle = useCallback( + () => setIsPrivateBoardsOpen(!isPrivateBoardsOpen), + [isPrivateBoardsOpen, setIsPrivateBoardsOpen] + ); + const handleSharedBoardsToggle = useCallback( + () => setIsSharedBoardsOpen(!isSharedBoardsOpen), + [isSharedBoardsOpen, setIsSharedBoardsOpen] + ); return ( <> - - - - - - - - - - - - {filteredBoards && - filteredBoards.map((board, index) => ( - + + + + + + + + {allowPrivateBoards && ( + <> + + + + + {t('boards.private')} + + + + + + + + {filteredPrivateBoards && + filteredPrivateBoards.map((board) => ( + + ))} + + + + )} + + + + + {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} + + + + + + + {filteredSharedBoards && + filteredSharedBoards.map((board) => ( - - ))} - - - - + ))} + + + + + ); }; -export default memo(BoardsList); +export default memo(BoardsListWithPrivate); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index fa5ed15930..7957633aa1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -1,15 +1,13 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library'; -import { createSelector } from '@reduxjs/toolkit'; import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import SelectionOverlay from 'common/components/SelectionOverlay'; import type { AddToBoardDropData } from 'features/dnd/types'; -import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon'; import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; -import { autoAddBoardIdChanged, boardIdSelected, selectGallerySlice } from 'features/gallery/store/gallerySlice'; +import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi'; @@ -19,10 +17,8 @@ import type { BoardDTO } from 'services/api/types'; const editableInputStyles: SystemStyleObject = { p: 0, - _focusVisible: { - p: 0, - textAlign: 'center', - }, + fontSize: 'md', + w: '100%', }; const ArchivedIcon = () => { @@ -43,12 +39,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps const dispatch = useAppDispatch(); const { t } = useTranslation(); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - const selectIsSelectedForAutoAdd = useMemo( - () => createSelector(selectGallerySlice, (gallery) => board.board_id === gallery.autoAddBoardId), - [board.board_id] - ); - const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd); const [isHovered, setIsHovered] = useState(false); const handleMouseOver = useCallback(() => { setIsHovered(true); @@ -114,16 +105,16 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps }, []); return ( - + {(ref) => ( @@ -135,68 +126,62 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps ref={ref} onClick={handleSelectBoard} w="full" - h="full" - position="relative" - justifyContent="center" alignItems="center" + justifyContent="space-between" borderRadius="base" cursor="pointer" - bg="base.800" + gap="6" + p="1" > - {board.archived && } - {coverImage?.thumbnail_url ? ( - - ) : ( - - - - )} - {isSelectedForAutoAdd && } - - - - + {board.archived && } + {coverImage?.thumbnail_url ? ( + - - + ) : ( + + + + )} + + + + + + + + + + {`${t('boards.imagesWithCount', { count: board.image_count })}`} + {t('unifiedCanvas.move')}} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 3f60dabf70..77563859a7 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -3,7 +3,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import SelectionOverlay from 'common/components/SelectionOverlay'; import type { RemoveFromBoardDropData } from 'features/dnd/types'; -import AutoAddIcon from 'features/gallery/components/Boards/AutoAddIcon'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; @@ -18,7 +17,6 @@ interface Props { const NoBoardBoard = memo(({ isSelected }: Props) => { const dispatch = useAppDispatch(); - const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); const boardName = useBoardName('none'); const handleSelectBoard = useCallback(() => { @@ -46,16 +44,16 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { ); const { t } = useTranslation(); return ( - + {(ref) => ( @@ -64,30 +62,22 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { ref={ref} onClick={handleSelectBoard} w="full" - position="relative" alignItems="center" borderRadius="base" cursor="pointer" - bg="base.800" + gap="6" + p="1" > invoke-ai-logo - {autoAddBoardId === 'none' && } - + {boardName} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx deleted file mode 100644 index 67e133ba22..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/AddBoardButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Icon } from '@invoke-ai/ui-library'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiPlusBold } from 'react-icons/pi'; -import { useCreateBoardMutation } from 'services/api/endpoints/boards'; - -type Props = { - privateBoard: boolean; -}; - -const AddBoardButton = ({ privateBoard }: Props) => { - const { t } = useTranslation(); - const [createBoard] = useCreateBoardMutation(); - const DEFAULT_BOARD_NAME = t('boards.myBoard'); - const handleCreateBoard = useCallback(() => { - createBoard({ DEFAULT_BOARD_NAME, privateBoard }); - }, [createBoard, DEFAULT_BOARD_NAME, privateBoard]); - - return ( - - ); -}; - -export default memo(AddBoardButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx deleted file mode 100644 index b4c89a002d..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardTotalsTooltip.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; - -type Props = { - board_id: string; - isArchived: boolean; -}; - -export const BoardTotalsTooltip = ({ board_id, isArchived }: Props) => { - const { t } = useTranslation(); - const { imagesTotal } = useGetBoardImagesTotalQuery(board_id, { - selectFromResult: ({ data }) => { - return { imagesTotal: data?.total ?? 0 }; - }, - }); - const { assetsTotal } = useGetBoardAssetsTotalQuery(board_id, { - selectFromResult: ({ data }) => { - return { assetsTotal: data?.total ?? 0 }; - }, - }); - return `${t('boards.imagesWithCount', { count: imagesTotal })}, ${t('boards.assetsWithCount', { count: assetsTotal })}${isArchived ? ` (${t('boards.archived')})` : ''}`; -}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx deleted file mode 100644 index b87a96f272..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsListWithPrivate.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; -import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; -import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; -import GallerySettingsPopover from 'features/gallery/components/GallerySettingsPopover/GallerySettingsPopover'; -import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import type { CSSProperties } from 'react'; -import { memo, useState } from 'react'; -import { PiCaretUpBold, PiPlusBold } from 'react-icons/pi'; -import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; -import type { BoardDTO } from 'services/api/types'; - -import AddBoardButton from './AddBoardButton'; -import BoardsSearch from './BoardsSearch'; -import GalleryBoard from './GalleryBoard'; -import NoBoardBoard from './NoBoardBoard'; - -const overlayScrollbarsStyles: CSSProperties = { - height: '100%', - width: '100%', -}; - -const BoardsListWithPrivate = () => { - const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); - const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); - const queryArgs = useAppSelector(selectListBoardsQueryArgs); - const { data: boards } = useListAllBoardsQuery(queryArgs); - const filteredPrivateBoards = boardSearchText - ? boards?.filter((board) => board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) - : boards?.filter((board) => board.is_private); - const filteredSharedBoards = boardSearchText - ? boards?.filter((board) => !board.is_private && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) - : boards?.filter((board) => !board.is_private); - const [boardToDelete, setBoardToDelete] = useState(); - const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true); - const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true); - - return ( - <> - - - - - - - - setIsPrivateBoardsOpen(!isPrivateBoardsOpen)} - gap={2} - alignItems="center" - cursor="pointer" - > - - Private - - - - - - - {filteredPrivateBoards && - filteredPrivateBoards.map((board) => ( - - ))} - - - - setIsSharedBoardsOpen(!isSharedBoardsOpen)} - gap={2} - alignItems="center" - cursor="pointer" - > - - - Shared - - - - - - {filteredSharedBoards && - filteredSharedBoards.map((board) => ( - - ))} - - - - - - - ); -}; - -export default memo(BoardsListWithPrivate); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx deleted file mode 100644 index 931c1e6cbb..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/BoardsSearch.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice'; -import type { ChangeEvent, KeyboardEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiXBold } from 'react-icons/pi'; - -const BoardsSearch = () => { - const dispatch = useAppDispatch(); - const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); - const { t } = useTranslation(); - - const handleBoardSearch = useCallback( - (searchTerm: string) => { - dispatch(boardSearchTextChanged(searchTerm)); - }, - [dispatch] - ); - - const clearBoardSearch = useCallback(() => { - dispatch(boardSearchTextChanged('')); - }, [dispatch]); - - const handleKeydown = useCallback( - (e: KeyboardEvent) => { - // exit search mode on escape - if (e.key === 'Escape') { - clearBoardSearch(); - } - }, - [clearBoardSearch] - ); - - const handleChange = useCallback( - (e: ChangeEvent) => { - handleBoardSearch(e.target.value); - }, - [handleBoardSearch] - ); - - return ( - - - {boardSearchText && boardSearchText.length && ( - - } - /> - - )} - - ); -}; - -export default memo(BoardsSearch); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx deleted file mode 100644 index 7eaae8ee62..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/GalleryBoard.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import type { SystemStyleObject } from '@invoke-ai/ui-library'; -import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library'; -import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIDroppable from 'common/components/IAIDroppable'; -import SelectionOverlay from 'common/components/SelectionOverlay'; -import type { AddToBoardDropData } from 'features/dnd/types'; -import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; -import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; -import { memo, useCallback, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi'; -import { useUpdateBoardMutation } from 'services/api/endpoints/boards'; -import { useGetImageDTOQuery } from 'services/api/endpoints/images'; -import type { BoardDTO } from 'services/api/types'; - -const editableInputStyles: SystemStyleObject = { - p: 0, - fontSize: 'md', - w: '100%', -}; - -const ArchivedIcon = () => { - return ( - - - - ); -}; - -interface GalleryBoardProps { - board: BoardDTO; - isSelected: boolean; - setBoardToDelete: (board?: BoardDTO) => void; -} - -const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - - const [isHovered, setIsHovered] = useState(false); - const handleMouseOver = useCallback(() => { - setIsHovered(true); - }, []); - const handleMouseOut = useCallback(() => { - setIsHovered(false); - }, []); - - const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken); - - const { board_name, board_id } = board; - const [localBoardName, setLocalBoardName] = useState(board_name); - - const handleSelectBoard = useCallback(() => { - dispatch(boardIdSelected({ boardId: board_id })); - if (autoAssignBoardOnClick && !board.archived) { - dispatch(autoAddBoardIdChanged(board_id)); - } - }, [board_id, autoAssignBoardOnClick, dispatch, board.archived]); - - const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation(); - - const droppableData: AddToBoardDropData = useMemo( - () => ({ - id: board_id, - actionType: 'ADD_TO_BOARD', - context: { boardId: board_id }, - }), - [board_id] - ); - - const handleSubmit = useCallback( - async (newBoardName: string) => { - // empty strings are not allowed - if (!newBoardName.trim()) { - setLocalBoardName(board_name); - return; - } - - // don't updated the board name if it hasn't changed - if (newBoardName === board_name) { - return; - } - - try { - const { board_name } = await updateBoard({ - board_id, - changes: { board_name: newBoardName }, - }).unwrap(); - - // update local state - setLocalBoardName(board_name); - } catch { - // revert on error - setLocalBoardName(board_name); - } - }, - [board_id, board_name, updateBoard] - ); - - const handleChange = useCallback((newBoardName: string) => { - setLocalBoardName(newBoardName); - }, []); - - return ( - - - - {(ref) => ( - } - openDelay={1000} - > - - - {board.archived && } - {coverImage?.thumbnail_url ? ( - - ) : ( - - - - )} - - - - - - - - - - {board.image_count} images - - {t('unifiedCanvas.move')}} /> - - - )} - - - - ); -}; - -export default memo(GalleryBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx deleted file mode 100644 index 77563859a7..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsListWithPrivate/NoBoardBoard.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { Box, Flex, Image, Text, Tooltip } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIDroppable from 'common/components/IAIDroppable'; -import SelectionOverlay from 'common/components/SelectionOverlay'; -import type { RemoveFromBoardDropData } from 'features/dnd/types'; -import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; -import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; -import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; -import InvokeLogoSVG from 'public/assets/images/invoke-symbol-wht-lrg.svg'; -import { memo, useCallback, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useBoardName } from 'services/api/hooks/useBoardName'; - -interface Props { - isSelected: boolean; -} - -const NoBoardBoard = memo(({ isSelected }: Props) => { - const dispatch = useAppDispatch(); - const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - const boardName = useBoardName('none'); - const handleSelectBoard = useCallback(() => { - dispatch(boardIdSelected({ boardId: 'none' })); - if (autoAssignBoardOnClick) { - dispatch(autoAddBoardIdChanged('none')); - } - }, [dispatch, autoAssignBoardOnClick]); - const [isHovered, setIsHovered] = useState(false); - - const handleMouseOver = useCallback(() => { - setIsHovered(true); - }, []); - - const handleMouseOut = useCallback(() => { - setIsHovered(false); - }, []); - - const droppableData: RemoveFromBoardDropData = useMemo( - () => ({ - id: 'no_board', - actionType: 'REMOVE_FROM_BOARD', - }), - [] - ); - const { t } = useTranslation(); - return ( - - - - {(ref) => ( - } openDelay={1000}> - - invoke-ai-logo - - {boardName} - - - {t('unifiedCanvas.move')}} /> - - - )} - - - - ); -}); - -NoBoardBoard.displayName = 'HoverableBoard'; - -export default memo(NoBoardBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index e21e7508fd..b0b147b510 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -9,7 +9,6 @@ import { PiImagesBold } from 'react-icons/pi'; import { RiServerLine } from 'react-icons/ri'; import BoardsList from './Boards/BoardsList/BoardsList'; -import BoardsListWithPrivate from './Boards/BoardsListWithPrivate/BoardsListWithPrivate'; import GalleryBoardName from './GalleryBoardName'; import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; @@ -19,7 +18,6 @@ import { GallerySearch } from './ImageGrid/GallerySearch'; const ImageGalleryContent = () => { const { t } = useTranslation(); const galleryView = useAppSelector((s) => s.gallery.galleryView); - const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const dispatch = useAppDispatch(); const galleryHeader = useStore($galleryHeader); const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = useDisclosure({ defaultIsOpen: true }); @@ -44,21 +42,15 @@ const ImageGalleryContent = () => { gap={2} > {galleryHeader} - {true ? ( + + + + + - - - ) : ( - - - - - - - - + - )} + diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index f64fba6e77..4318db2df4 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: { - 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_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"]; - 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"]; - sub: components["schemas"]["IntegerOutput"]; - normalbae_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"]; + img_blur: components["schemas"]["ImageOutput"]; + cv_inpaint: components["schemas"]["ImageOutput"]; + integer_math: components["schemas"]["IntegerOutput"]; 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"]; + mask_combine: components["schemas"]["ImageOutput"]; + sub: components["schemas"]["IntegerOutput"]; + crop_latents: components["schemas"]["LatentsOutput"]; + ideal_size: components["schemas"]["IdealSizeOutput"]; leres_image_processor: components["schemas"]["ImageOutput"]; + mask_from_id: components["schemas"]["ImageOutput"]; + infill_cv2: components["schemas"]["ImageOutput"]; + boolean: components["schemas"]["BooleanOutput"]; + conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; + infill_patchmatch: components["schemas"]["ImageOutput"]; + float_math: components["schemas"]["FloatOutput"]; + image_collection: components["schemas"]["ImageCollectionOutput"]; + img_conv: components["schemas"]["ImageOutput"]; + sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; + metadata: components["schemas"]["MetadataOutput"]; + save_image: components["schemas"]["ImageOutput"]; + boolean_collection: components["schemas"]["BooleanCollectionOutput"]; + img_paste: components["schemas"]["ImageOutput"]; + conditioning: components["schemas"]["ConditioningOutput"]; + color_map_image_processor: components["schemas"]["ImageOutput"]; + img_scale: components["schemas"]["ImageOutput"]; + string_join: components["schemas"]["StringOutput"]; + noise: components["schemas"]["NoiseOutput"]; + core_metadata: components["schemas"]["MetadataOutput"]; + lresize: components["schemas"]["LatentsOutput"]; + face_mask_detection: components["schemas"]["FaceMaskOutput"]; + calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; + mlsd_image_processor: components["schemas"]["ImageOutput"]; + sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + img_nsfw: components["schemas"]["ImageOutput"]; + main_model_loader: components["schemas"]["ModelLoaderOutput"]; + rand_int: components["schemas"]["IntegerOutput"]; + mul: components["schemas"]["IntegerOutput"]; + integer: components["schemas"]["IntegerOutput"]; + img_pad_crop: components["schemas"]["ImageOutput"]; + collect: components["schemas"]["CollectInvocationOutput"]; + pidi_image_processor: components["schemas"]["ImageOutput"]; + lora_loader: components["schemas"]["LoRALoaderOutput"]; + infill_tile: components["schemas"]["ImageOutput"]; + calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; + scheduler: components["schemas"]["SchedulerOutput"]; + midas_depth_image_processor: components["schemas"]["ImageOutput"]; + latents: components["schemas"]["LatentsOutput"]; + tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; + round_float: components["schemas"]["FloatOutput"]; + add: components["schemas"]["IntegerOutput"]; + dynamic_prompt: components["schemas"]["StringCollectionOutput"]; + l2i: components["schemas"]["ImageOutput"]; + integer_collection: components["schemas"]["IntegerCollectionOutput"]; + model_identifier: components["schemas"]["ModelIdentifierOutput"]; + normalbae_image_processor: components["schemas"]["ImageOutput"]; + img_chan: components["schemas"]["ImageOutput"]; + image: components["schemas"]["ImageOutput"]; + face_off: components["schemas"]["FaceOffOutput"]; + img_hue_adjust: components["schemas"]["ImageOutput"]; + color: components["schemas"]["ColorOutput"]; + color_correct: components["schemas"]["ImageOutput"]; + string_split_neg: components["schemas"]["StringPosNegOutput"]; + clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; + face_identifier: components["schemas"]["ImageOutput"]; + string_join_three: components["schemas"]["StringOutput"]; + img_channel_offset: components["schemas"]["ImageOutput"]; + infill_rgba: components["schemas"]["ImageOutput"]; + show_image: components["schemas"]["ImageOutput"]; + infill_lama: components["schemas"]["ImageOutput"]; + img_mul: components["schemas"]["ImageOutput"]; + segment_anything_processor: components["schemas"]["ImageOutput"]; + sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; mediapipe_face_processor: components["schemas"]["ImageOutput"]; + image_mask_to_tensor: components["schemas"]["MaskOutput"]; + string_split: components["schemas"]["String2Output"]; + i2l: components["schemas"]["LatentsOutput"]; + zoe_depth_image_processor: components["schemas"]["ImageOutput"]; + float_to_int: components["schemas"]["IntegerOutput"]; + ip_adapter: components["schemas"]["IPAdapterOutput"]; + string: components["schemas"]["StringOutput"]; + rectangle_mask: components["schemas"]["MaskOutput"]; + lineart_anime_image_processor: components["schemas"]["ImageOutput"]; + alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; + merge_tiles_to_image: components["schemas"]["ImageOutput"]; + div: components["schemas"]["IntegerOutput"]; + float_collection: components["schemas"]["FloatCollectionOutput"]; + tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; + seamless: components["schemas"]["SeamlessModeOutput"]; + float: components["schemas"]["FloatOutput"]; + lineart_image_processor: components["schemas"]["ImageOutput"]; + controlnet: components["schemas"]["ControlOutput"]; + step_param_easing: components["schemas"]["FloatCollectionOutput"]; + prompt_from_file: components["schemas"]["StringCollectionOutput"]; + rand_float: components["schemas"]["FloatOutput"]; + float_range: components["schemas"]["FloatCollectionOutput"]; + sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + latents_collection: components["schemas"]["LatentsCollectionOutput"]; + sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; + create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; + create_gradient_mask: components["schemas"]["GradientMaskOutput"]; + metadata_item: components["schemas"]["MetadataItemOutput"]; + img_ilerp: components["schemas"]["ImageOutput"]; content_shuffle_image_processor: components["schemas"]["ImageOutput"]; + img_watermark: components["schemas"]["ImageOutput"]; + invert_tensor_mask: components["schemas"]["MaskOutput"]; + img_crop: components["schemas"]["ImageOutput"]; + esrgan: components["schemas"]["ImageOutput"]; + iterate: components["schemas"]["IterateInvocationOutput"]; + unsharp_mask: components["schemas"]["ImageOutput"]; + mask_edge: components["schemas"]["ImageOutput"]; + canvas_paste_back: components["schemas"]["ImageOutput"]; + dw_openpose_image_processor: components["schemas"]["ImageOutput"]; + tomask: components["schemas"]["ImageOutput"]; + random_range: components["schemas"]["IntegerCollectionOutput"]; + lora_selector: components["schemas"]["LoRASelectorOutput"]; + compel: components["schemas"]["ConditioningOutput"]; + img_resize: components["schemas"]["ImageOutput"]; + sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; + lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; + string_replace: components["schemas"]["StringOutput"]; + string_collection: components["schemas"]["StringCollectionOutput"]; + blank_image: components["schemas"]["ImageOutput"]; + freeu: components["schemas"]["UNetOutput"]; + img_lerp: components["schemas"]["ImageOutput"]; + range: components["schemas"]["IntegerCollectionOutput"]; + depth_anything_image_processor: components["schemas"]["ImageOutput"]; + t2i_adapter: components["schemas"]["T2IAdapterOutput"]; + pair_tile_image: components["schemas"]["PairTileImageOutput"]; + lscale: components["schemas"]["LatentsOutput"]; + tile_image_processor: components["schemas"]["ImageOutput"]; + heuristic_resize: components["schemas"]["ImageOutput"]; + calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; + denoise_latents: components["schemas"]["LatentsOutput"]; + vae_loader: components["schemas"]["VAEOutput"]; + lblend: components["schemas"]["LatentsOutput"]; + hed_image_processor: components["schemas"]["ImageOutput"]; + canny_image_processor: components["schemas"]["ImageOutput"]; }; /** * InvocationStartedEvent @@ -15014,6 +15014,8 @@ export type operations = { query: { /** @description The name of the board to create */ board_name: string; + /** @description Whether the board is private */ + private_board?: boolean; }; }; responses: { diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index d5c857cefd..5beb5cbbf5 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -11,9 +11,7 @@ export type ListBoardsArgs = NonNullable Date: Mon, 8 Jul 2024 14:55:04 -0400 Subject: [PATCH 16/37] update BoardRecord --- .../app/services/board_records/board_records_common.py | 4 ++++ invokeai/app/services/boards/boards_common.py | 2 -- .../gallery/components/Boards/BoardsList/BoardsList.tsx | 4 ++-- .../features/gallery/components/ImageGalleryContent.tsx | 8 +------- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/invokeai/app/services/board_records/board_records_common.py b/invokeai/app/services/board_records/board_records_common.py index d763480a9f..0dda8a8b6b 100644 --- a/invokeai/app/services/board_records/board_records_common.py +++ b/invokeai/app/services/board_records/board_records_common.py @@ -24,6 +24,8 @@ class BoardRecord(BaseModelExcludeNull): """The name of the cover image of the board.""" archived: bool = Field(description="Whether or not the board is archived.") """Whether or not the board is archived.""" + is_private: Optional[bool] = Field(default=None, description="Whether the board is private.") + """Whether the board is private.""" def deserialize_board_record(board_dict: dict) -> BoardRecord: @@ -38,6 +40,7 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: updated_at = board_dict.get("updated_at", get_iso_timestamp()) deleted_at = board_dict.get("deleted_at", get_iso_timestamp()) archived = board_dict.get("archived", False) + is_private = board_dict.get("is_private", False) return BoardRecord( board_id=board_id, @@ -47,6 +50,7 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord: updated_at=updated_at, deleted_at=deleted_at, archived=archived, + is_private=is_private, ) diff --git a/invokeai/app/services/boards/boards_common.py b/invokeai/app/services/boards/boards_common.py index 3f3190a06a..0cb54102bb 100644 --- a/invokeai/app/services/boards/boards_common.py +++ b/invokeai/app/services/boards/boards_common.py @@ -12,8 +12,6 @@ class BoardDTO(BoardRecord): """The URL of the thumbnail of the most recent image in the board.""" image_count: int = Field(description="The number of images in the board.") """The number of images in the board.""" - is_private: Optional[bool] = Field(description="Whether the board is private.") - """Whether the board is private.""" def board_record_to_dto(board_record: BoardRecord, cover_image_name: Optional[str], image_count: int) -> BoardDTO: diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index 99b2becd61..5e7fc5878a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -22,7 +22,7 @@ const overlayScrollbarsStyles: CSSProperties = { width: '100%', }; -const BoardsListWithPrivate = () => { +const BoardsList = () => { const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText); const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); @@ -127,4 +127,4 @@ const BoardsListWithPrivate = () => { ); }; -export default memo(BoardsListWithPrivate); +export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index b0b147b510..3ad9db6f79 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -43,13 +43,7 @@ const ImageGalleryContent = () => { > {galleryHeader} - - - - - - - + From 79a7b112145db793b141b688adda12591d787cd9 Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 15:02:22 -0400 Subject: [PATCH 17/37] remove old boards list --- .../src/features/gallery/components/ImageGalleryContent.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 3ad9db6f79..2207622130 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -9,8 +9,6 @@ import { PiImagesBold } from 'react-icons/pi'; import { RiServerLine } from 'react-icons/ri'; import BoardsList from './Boards/BoardsList/BoardsList'; -import GalleryBoardName from './GalleryBoardName'; -import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; import { GalleryPagination } from './ImageGrid/GalleryPagination'; import { GallerySearch } from './ImageGrid/GallerySearch'; @@ -20,7 +18,7 @@ const ImageGalleryContent = () => { const galleryView = useAppSelector((s) => s.gallery.galleryView); const dispatch = useAppDispatch(); const galleryHeader = useStore($galleryHeader); - const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = useDisclosure({ defaultIsOpen: true }); + const { isOpen: isBoardListOpen } = useDisclosure({ defaultIsOpen: true }); const handleClickImages = useCallback(() => { dispatch(galleryViewChanged('images')); From 0e092c0fb5f90751af0622e4b76c618a5321ff09 Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 22:03:12 -0400 Subject: [PATCH 18/37] update is_private name --- invokeai/app/api/routers/boards.py | 2 +- invokeai/frontend/web/src/services/api/endpoints/boards.ts | 4 ++-- invokeai/frontend/web/src/services/api/schema.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py index fb09c0b463..fb828f4dc5 100644 --- a/invokeai/app/api/routers/boards.py +++ b/invokeai/app/api/routers/boards.py @@ -32,7 +32,7 @@ class DeleteBoardResult(BaseModel): ) async def create_board( board_name: str = Query(description="The name of the board to create"), - private_board: bool = Query(default=False, description="Whether the board is private"), + is_private: bool = Query(default=False, description="Whether the board is private"), ) -> BoardDTO: """Creates a board""" try: diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts index 026ffdfa56..6cfed5609a 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts @@ -88,10 +88,10 @@ export const boardsApi = api.injectEndpoints({ */ createBoard: build.mutation({ - query: ({ board_name, private_board }) => ({ + query: ({ board_name, is_private }) => ({ url: buildBoardsUrl(), method: 'POST', - params: { board_name, private_board }, + params: { board_name, is_private }, }), invalidatesTags: [{ type: 'Board', id: LIST_TAG }], }), diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 4318db2df4..fc41a1dc8e 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -15015,7 +15015,7 @@ export type operations = { /** @description The name of the board to create */ board_name: string; /** @description Whether the board is private */ - private_board?: boolean; + is_private?: boolean; }; }; responses: { From 178582569090e8840401a37b298d40ad55ff5d2a Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 22:03:42 -0400 Subject: [PATCH 19/37] add current gallery board name --- .../Boards/BoardsList/AddBoardButton.tsx | 8 ++--- .../Boards/BoardsList/BoardsList.tsx | 4 +-- .../gallery/components/GalleryBoardName.tsx | 34 +++++-------------- .../components/ImageGalleryContent.tsx | 2 ++ 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index abf690f693..6b0ebea257 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -5,16 +5,16 @@ import { PiPlusBold } from 'react-icons/pi'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; type Props = { - privateBoard: boolean; + isPrivateBoard: boolean; }; -const AddBoardButton = ({ privateBoard }: Props) => { +const AddBoardButton = ({ isPrivateBoard }: Props) => { const { t } = useTranslation(); const [createBoard, { isLoading }] = useCreateBoardMutation(); const DEFAULT_BOARD_NAME = t('boards.myBoard'); const handleCreateBoard = useCallback(() => { - createBoard({ board_name: DEFAULT_BOARD_NAME, private_board: privateBoard }); - }, [createBoard, DEFAULT_BOARD_NAME, privateBoard]); + createBoard({ board_name: DEFAULT_BOARD_NAME, is_private: isPrivateBoard }); + }, [createBoard, DEFAULT_BOARD_NAME, isPrivateBoard]); return ( { {t('boards.private')} - + @@ -104,7 +104,7 @@ const BoardsList = () => { {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} - + diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx index 55aec17ab2..bc851893c4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx @@ -1,16 +1,9 @@ -import { Button, Flex, Icon, Spacer } from '@invoke-ai/ui-library'; +import { Flex } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { memo, useMemo } from 'react'; -import { PiCaretUpBold } from 'react-icons/pi'; import { useBoardName } from 'services/api/hooks/useBoardName'; -type Props = { - isOpen: boolean; - onToggle: () => void; -}; - -const GalleryBoardName = (props: Props) => { - const { isOpen, onToggle } = props; +const GalleryBoardName = () => { const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardName = useBoardName(selectedBoardId); @@ -23,26 +16,15 @@ const GalleryBoardName = (props: Props) => { return ( - {formattedBoardName} - - ); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 2207622130..1394de8716 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -9,6 +9,7 @@ import { PiImagesBold } from 'react-icons/pi'; import { RiServerLine } from 'react-icons/ri'; import BoardsList from './Boards/BoardsList/BoardsList'; +import GalleryBoardName from './GalleryBoardName'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; import { GalleryPagination } from './ImageGrid/GalleryPagination'; import { GallerySearch } from './ImageGrid/GallerySearch'; @@ -42,6 +43,7 @@ const ImageGalleryContent = () => { {galleryHeader} + From 38c58044570a22ee0cbf40207e362df17d15f43a Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 22:09:23 -0400 Subject: [PATCH 20/37] remove unused disclosure --- .../src/features/gallery/components/ImageGalleryContent.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index 1394de8716..7e1c35312e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -1,4 +1,4 @@ -import { Box, Button, ButtonGroup, Flex, Tab, TabList, Tabs, useDisclosure } from '@invoke-ai/ui-library'; +import { Box, Button, ButtonGroup, Flex, Tab, TabList, Tabs } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { $galleryHeader } from 'app/store/nanostores/galleryHeader'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; @@ -19,7 +19,6 @@ const ImageGalleryContent = () => { const galleryView = useAppSelector((s) => s.gallery.galleryView); const dispatch = useAppDispatch(); const galleryHeader = useStore($galleryHeader); - const { isOpen: isBoardListOpen } = useDisclosure({ defaultIsOpen: true }); const handleClickImages = useCallback(() => { dispatch(galleryViewChanged('images')); @@ -42,7 +41,7 @@ const ImageGalleryContent = () => { > {galleryHeader} - + From 40c3b5e727b75351240da414f7d982336872786c Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 22:13:12 -0400 Subject: [PATCH 21/37] generate types again --- .../frontend/web/src/services/api/schema.ts | 283 ++++++++++-------- 1 file changed, 150 insertions(+), 133 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index fc41a1dc8e..7780ccbdc8 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -1058,6 +1058,11 @@ export type components = { * @description Whether or not the board is archived. */ archived: boolean; + /** + * Is Private + * @description Whether the board is private. + */ + is_private?: boolean | null; /** * Image Count * @description The number of images in the board. @@ -6561,6 +6566,12 @@ export type components = { * @default false */ tiled?: boolean; + /** + * Tile Size + * @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the + * @default 0 + */ + tile_size?: number; /** * Fp32 * @description Whether or not to use full float32 precision @@ -7293,145 +7304,145 @@ export type components = { project_id: string | null; }; InvocationOutputMap: { - range_of_size: components["schemas"]["IntegerCollectionOutput"]; - img_channel_multiply: components["schemas"]["ImageOutput"]; - img_blur: components["schemas"]["ImageOutput"]; - cv_inpaint: components["schemas"]["ImageOutput"]; - integer_math: components["schemas"]["IntegerOutput"]; - merge_metadata: components["schemas"]["MetadataOutput"]; - mask_combine: components["schemas"]["ImageOutput"]; - sub: components["schemas"]["IntegerOutput"]; - crop_latents: components["schemas"]["LatentsOutput"]; - ideal_size: components["schemas"]["IdealSizeOutput"]; - leres_image_processor: components["schemas"]["ImageOutput"]; - mask_from_id: components["schemas"]["ImageOutput"]; - infill_cv2: components["schemas"]["ImageOutput"]; - boolean: components["schemas"]["BooleanOutput"]; - conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; - infill_patchmatch: components["schemas"]["ImageOutput"]; - float_math: components["schemas"]["FloatOutput"]; - image_collection: components["schemas"]["ImageCollectionOutput"]; - img_conv: components["schemas"]["ImageOutput"]; - sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; - metadata: components["schemas"]["MetadataOutput"]; - save_image: components["schemas"]["ImageOutput"]; - boolean_collection: components["schemas"]["BooleanCollectionOutput"]; - img_paste: components["schemas"]["ImageOutput"]; - conditioning: components["schemas"]["ConditioningOutput"]; - color_map_image_processor: components["schemas"]["ImageOutput"]; - img_scale: components["schemas"]["ImageOutput"]; - string_join: components["schemas"]["StringOutput"]; - noise: components["schemas"]["NoiseOutput"]; - core_metadata: components["schemas"]["MetadataOutput"]; - lresize: components["schemas"]["LatentsOutput"]; - face_mask_detection: components["schemas"]["FaceMaskOutput"]; - calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; - mlsd_image_processor: components["schemas"]["ImageOutput"]; - sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; - img_nsfw: components["schemas"]["ImageOutput"]; - main_model_loader: components["schemas"]["ModelLoaderOutput"]; - rand_int: components["schemas"]["IntegerOutput"]; - mul: components["schemas"]["IntegerOutput"]; - integer: components["schemas"]["IntegerOutput"]; - img_pad_crop: components["schemas"]["ImageOutput"]; - collect: components["schemas"]["CollectInvocationOutput"]; - pidi_image_processor: components["schemas"]["ImageOutput"]; - lora_loader: components["schemas"]["LoRALoaderOutput"]; - infill_tile: components["schemas"]["ImageOutput"]; - calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; - scheduler: components["schemas"]["SchedulerOutput"]; - midas_depth_image_processor: components["schemas"]["ImageOutput"]; - latents: components["schemas"]["LatentsOutput"]; - tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; - round_float: components["schemas"]["FloatOutput"]; - add: components["schemas"]["IntegerOutput"]; - dynamic_prompt: components["schemas"]["StringCollectionOutput"]; - l2i: components["schemas"]["ImageOutput"]; - integer_collection: components["schemas"]["IntegerCollectionOutput"]; - model_identifier: components["schemas"]["ModelIdentifierOutput"]; - normalbae_image_processor: components["schemas"]["ImageOutput"]; - img_chan: components["schemas"]["ImageOutput"]; - image: components["schemas"]["ImageOutput"]; - face_off: components["schemas"]["FaceOffOutput"]; - img_hue_adjust: components["schemas"]["ImageOutput"]; - color: components["schemas"]["ColorOutput"]; - color_correct: components["schemas"]["ImageOutput"]; - string_split_neg: components["schemas"]["StringPosNegOutput"]; - clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; - face_identifier: components["schemas"]["ImageOutput"]; - string_join_three: components["schemas"]["StringOutput"]; - img_channel_offset: components["schemas"]["ImageOutput"]; - infill_rgba: components["schemas"]["ImageOutput"]; - show_image: components["schemas"]["ImageOutput"]; - infill_lama: components["schemas"]["ImageOutput"]; - img_mul: components["schemas"]["ImageOutput"]; - segment_anything_processor: components["schemas"]["ImageOutput"]; - sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; - mediapipe_face_processor: components["schemas"]["ImageOutput"]; - image_mask_to_tensor: components["schemas"]["MaskOutput"]; - string_split: components["schemas"]["String2Output"]; - i2l: components["schemas"]["LatentsOutput"]; - zoe_depth_image_processor: components["schemas"]["ImageOutput"]; - float_to_int: components["schemas"]["IntegerOutput"]; - ip_adapter: components["schemas"]["IPAdapterOutput"]; - string: components["schemas"]["StringOutput"]; rectangle_mask: components["schemas"]["MaskOutput"]; - lineart_anime_image_processor: components["schemas"]["ImageOutput"]; - alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; - merge_tiles_to_image: components["schemas"]["ImageOutput"]; - div: components["schemas"]["IntegerOutput"]; - float_collection: components["schemas"]["FloatCollectionOutput"]; - tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; - seamless: components["schemas"]["SeamlessModeOutput"]; - float: components["schemas"]["FloatOutput"]; - lineart_image_processor: components["schemas"]["ImageOutput"]; - controlnet: components["schemas"]["ControlOutput"]; - step_param_easing: components["schemas"]["FloatCollectionOutput"]; - prompt_from_file: components["schemas"]["StringCollectionOutput"]; - rand_float: components["schemas"]["FloatOutput"]; - float_range: components["schemas"]["FloatCollectionOutput"]; - sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"]; - latents_collection: components["schemas"]["LatentsCollectionOutput"]; - sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; - create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; - create_gradient_mask: components["schemas"]["GradientMaskOutput"]; - metadata_item: components["schemas"]["MetadataItemOutput"]; - img_ilerp: components["schemas"]["ImageOutput"]; - content_shuffle_image_processor: components["schemas"]["ImageOutput"]; - img_watermark: components["schemas"]["ImageOutput"]; - invert_tensor_mask: components["schemas"]["MaskOutput"]; - img_crop: components["schemas"]["ImageOutput"]; - esrgan: components["schemas"]["ImageOutput"]; - iterate: components["schemas"]["IterateInvocationOutput"]; - unsharp_mask: components["schemas"]["ImageOutput"]; - mask_edge: components["schemas"]["ImageOutput"]; - canvas_paste_back: components["schemas"]["ImageOutput"]; - dw_openpose_image_processor: components["schemas"]["ImageOutput"]; - tomask: components["schemas"]["ImageOutput"]; - random_range: components["schemas"]["IntegerCollectionOutput"]; - lora_selector: components["schemas"]["LoRASelectorOutput"]; + hed_image_processor: components["schemas"]["ImageOutput"]; compel: components["schemas"]["ConditioningOutput"]; img_resize: components["schemas"]["ImageOutput"]; - sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; - lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; - string_replace: components["schemas"]["StringOutput"]; + ideal_size: components["schemas"]["IdealSizeOutput"]; + rand_int: components["schemas"]["IntegerOutput"]; + clip_skip: components["schemas"]["CLIPSkipInvocationOutput"]; string_collection: components["schemas"]["StringCollectionOutput"]; - blank_image: components["schemas"]["ImageOutput"]; - freeu: components["schemas"]["UNetOutput"]; - img_lerp: components["schemas"]["ImageOutput"]; - range: components["schemas"]["IntegerCollectionOutput"]; - depth_anything_image_processor: components["schemas"]["ImageOutput"]; - t2i_adapter: components["schemas"]["T2IAdapterOutput"]; - pair_tile_image: components["schemas"]["PairTileImageOutput"]; - lscale: components["schemas"]["LatentsOutput"]; - tile_image_processor: components["schemas"]["ImageOutput"]; + create_gradient_mask: components["schemas"]["GradientMaskOutput"]; + round_float: components["schemas"]["FloatOutput"]; + scheduler: components["schemas"]["SchedulerOutput"]; + main_model_loader: components["schemas"]["ModelLoaderOutput"]; + string_split: components["schemas"]["String2Output"]; + mask_from_id: components["schemas"]["ImageOutput"]; + collect: components["schemas"]["CollectInvocationOutput"]; heuristic_resize: components["schemas"]["ImageOutput"]; - calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; - denoise_latents: components["schemas"]["LatentsOutput"]; - vae_loader: components["schemas"]["VAEOutput"]; - lblend: components["schemas"]["LatentsOutput"]; - hed_image_processor: components["schemas"]["ImageOutput"]; + tomask: components["schemas"]["ImageOutput"]; + boolean_collection: components["schemas"]["BooleanCollectionOutput"]; + core_metadata: components["schemas"]["MetadataOutput"]; canny_image_processor: components["schemas"]["ImageOutput"]; + string_replace: components["schemas"]["StringOutput"]; + face_mask_detection: components["schemas"]["FaceMaskOutput"]; + integer: components["schemas"]["IntegerOutput"]; + img_watermark: components["schemas"]["ImageOutput"]; + img_crop: components["schemas"]["ImageOutput"]; + t2i_adapter: components["schemas"]["T2IAdapterOutput"]; + create_denoise_mask: components["schemas"]["DenoiseMaskOutput"]; + rand_float: components["schemas"]["FloatOutput"]; + zoe_depth_image_processor: components["schemas"]["ImageOutput"]; + face_off: components["schemas"]["FaceOffOutput"]; + tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; + color_map_image_processor: components["schemas"]["ImageOutput"]; + lineart_anime_image_processor: components["schemas"]["ImageOutput"]; + face_identifier: components["schemas"]["ImageOutput"]; + float_math: components["schemas"]["FloatOutput"]; + mediapipe_face_processor: components["schemas"]["ImageOutput"]; + img_channel_multiply: components["schemas"]["ImageOutput"]; + metadata_item: components["schemas"]["MetadataItemOutput"]; + img_ilerp: components["schemas"]["ImageOutput"]; + conditioning: components["schemas"]["ConditioningOutput"]; + pidi_image_processor: components["schemas"]["ImageOutput"]; + seamless: components["schemas"]["SeamlessModeOutput"]; + latents: components["schemas"]["LatentsOutput"]; + img_chan: components["schemas"]["ImageOutput"]; + model_identifier: components["schemas"]["ModelIdentifierOutput"]; + noise: components["schemas"]["NoiseOutput"]; + string_join: components["schemas"]["StringOutput"]; + blank_image: components["schemas"]["ImageOutput"]; + calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"]; + invert_tensor_mask: components["schemas"]["MaskOutput"]; + save_image: components["schemas"]["ImageOutput"]; + unsharp_mask: components["schemas"]["ImageOutput"]; + image_mask_to_tensor: components["schemas"]["MaskOutput"]; + step_param_easing: components["schemas"]["FloatCollectionOutput"]; + merge_tiles_to_image: components["schemas"]["ImageOutput"]; + integer_collection: components["schemas"]["IntegerCollectionOutput"]; + calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; + integer_math: components["schemas"]["IntegerOutput"]; + range: components["schemas"]["IntegerCollectionOutput"]; + prompt_from_file: components["schemas"]["StringCollectionOutput"]; + segment_anything_processor: components["schemas"]["ImageOutput"]; + freeu: components["schemas"]["UNetOutput"]; + sub: components["schemas"]["IntegerOutput"]; + lresize: components["schemas"]["LatentsOutput"]; + float: components["schemas"]["FloatOutput"]; + float_collection: components["schemas"]["FloatCollectionOutput"]; + dynamic_prompt: components["schemas"]["StringCollectionOutput"]; + infill_lama: components["schemas"]["ImageOutput"]; + l2i: components["schemas"]["ImageOutput"]; + img_lerp: components["schemas"]["ImageOutput"]; + ip_adapter: components["schemas"]["IPAdapterOutput"]; + lora_collection_loader: components["schemas"]["LoRALoaderOutput"]; + color: components["schemas"]["ColorOutput"]; + tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; + cv_inpaint: components["schemas"]["ImageOutput"]; + lscale: components["schemas"]["LatentsOutput"]; + string: components["schemas"]["StringOutput"]; + sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"]; + string_join_three: components["schemas"]["StringOutput"]; + midas_depth_image_processor: components["schemas"]["ImageOutput"]; + esrgan: components["schemas"]["ImageOutput"]; + sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"]; + mul: components["schemas"]["IntegerOutput"]; + normalbae_image_processor: components["schemas"]["ImageOutput"]; + infill_rgba: components["schemas"]["ImageOutput"]; + sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"]; + vae_loader: components["schemas"]["VAEOutput"]; + float_to_int: components["schemas"]["IntegerOutput"]; + lora_selector: components["schemas"]["LoRASelectorOutput"]; + crop_latents: components["schemas"]["LatentsOutput"]; + img_mul: components["schemas"]["ImageOutput"]; + float_range: components["schemas"]["FloatCollectionOutput"]; + merge_metadata: components["schemas"]["MetadataOutput"]; + img_blur: components["schemas"]["ImageOutput"]; + boolean: components["schemas"]["BooleanOutput"]; + tile_image_processor: components["schemas"]["ImageOutput"]; + mlsd_image_processor: components["schemas"]["ImageOutput"]; + infill_patchmatch: components["schemas"]["ImageOutput"]; + img_pad_crop: components["schemas"]["ImageOutput"]; + leres_image_processor: components["schemas"]["ImageOutput"]; + sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + dw_openpose_image_processor: components["schemas"]["ImageOutput"]; + img_scale: components["schemas"]["ImageOutput"]; + pair_tile_image: components["schemas"]["PairTileImageOutput"]; + lblend: components["schemas"]["LatentsOutput"]; + range_of_size: components["schemas"]["IntegerCollectionOutput"]; + image_collection: components["schemas"]["ImageCollectionOutput"]; + calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"]; + img_channel_offset: components["schemas"]["ImageOutput"]; + alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; + infill_cv2: components["schemas"]["ImageOutput"]; + mask_combine: components["schemas"]["ImageOutput"]; + string_split_neg: components["schemas"]["StringPosNegOutput"]; + sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"]; + lineart_image_processor: components["schemas"]["ImageOutput"]; + img_nsfw: components["schemas"]["ImageOutput"]; + image: components["schemas"]["ImageOutput"]; + content_shuffle_image_processor: components["schemas"]["ImageOutput"]; + canvas_paste_back: components["schemas"]["ImageOutput"]; + iterate: components["schemas"]["IterateInvocationOutput"]; + div: components["schemas"]["IntegerOutput"]; + latents_collection: components["schemas"]["LatentsCollectionOutput"]; + img_conv: components["schemas"]["ImageOutput"]; + mask_edge: components["schemas"]["ImageOutput"]; + conditioning_collection: components["schemas"]["ConditioningCollectionOutput"]; + img_hue_adjust: components["schemas"]["ImageOutput"]; + depth_anything_image_processor: components["schemas"]["ImageOutput"]; + lora_loader: components["schemas"]["LoRALoaderOutput"]; + sdxl_compel_prompt: components["schemas"]["ConditioningOutput"]; + add: components["schemas"]["IntegerOutput"]; + controlnet: components["schemas"]["ControlOutput"]; + color_correct: components["schemas"]["ImageOutput"]; + random_range: components["schemas"]["IntegerCollectionOutput"]; + denoise_latents: components["schemas"]["LatentsOutput"]; + metadata: components["schemas"]["MetadataOutput"]; + i2l: components["schemas"]["LatentsOutput"]; + show_image: components["schemas"]["ImageOutput"]; + img_paste: components["schemas"]["ImageOutput"]; + infill_tile: components["schemas"]["ImageOutput"]; }; /** * InvocationStartedEvent @@ -7769,6 +7780,12 @@ export type components = { * @default false */ tiled?: boolean; + /** + * Tile Size + * @description The tile size for VAE tiling in pixels (image space). If set to 0, the default tile size for the + * @default 0 + */ + tile_size?: number; /** * Fp32 * @description Whether or not to use full float32 precision From e2667f957c21d9657cbddb60f87dd7e2b4be6d1d Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 22:16:31 -0400 Subject: [PATCH 22/37] prettier --- .../frontend/web/src/services/api/endpoints/boards.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts index 6cfed5609a..55ebeab318 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts @@ -1,5 +1,11 @@ import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types'; -import type { BoardDTO, CreateBoardArg, ListBoardsArgs, OffsetPaginatedResults_ImageDTO_, UpdateBoardArg } from 'services/api/types'; +import type { + BoardDTO, + CreateBoardArg, + ListBoardsArgs, + OffsetPaginatedResults_ImageDTO_, + UpdateBoardArg, +} from 'services/api/types'; import { getListImagesUrl } from 'services/api/util'; import type { ApiTagDescription } from '..'; From 907b2579845376b5c0cc1969f58e3c154244dadf Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 8 Jul 2024 23:20:50 -0400 Subject: [PATCH 23/37] remove unused file and addressed pr feedback --- .../gallery/components/Boards/AutoAddIcon.tsx | 16 ---- .../Boards/BoardsList/BoardsList.tsx | 76 +++++++++---------- 2 files changed, 35 insertions(+), 57 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx deleted file mode 100644 index 9dd6a59c49..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Badge, Flex } from '@invoke-ai/ui-library'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -const AutoAddIcon = () => { - const { t } = useTranslation(); - return ( - - - {t('common.auto')} - - - ); -}; - -export default memo(AutoAddIcon); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index cedce850d1..75f73adb6a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -1,4 +1,5 @@ -import { Box, Collapse, Flex, Icon, Text } from '@invoke-ai/ui-library'; +import { Box, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library'; +import { EMPTY_ARRAY } from 'app/store/constants'; import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal'; @@ -6,7 +7,7 @@ import GallerySettingsPopover from 'features/gallery/components/GallerySettingsP import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import type { CSSProperties } from 'react'; -import { memo, useCallback, useState } from 'react'; +import { memo, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiCaretUpBold } from 'react-icons/pi'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; @@ -28,24 +29,19 @@ const BoardsList = () => { const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const queryArgs = useAppSelector(selectListBoardsQueryArgs); const { data: boards } = useListAllBoardsQuery(queryArgs); - const filteredBoards = boardSearchText - ? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) - : boards; - const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private); - const filteredSharedBoards = boards?.filter((board) => !board.is_private); const [boardToDelete, setBoardToDelete] = useState(); - const [isPrivateBoardsOpen, setIsPrivateBoardsOpen] = useState(true); - const [isSharedBoardsOpen, setIsSharedBoardsOpen] = useState(true); + const privateBoardsDisclosure = useDisclosure({ defaultIsOpen: true }); + const sharedBoardsDisclosure = useDisclosure({ defaultIsOpen: true }); const { t } = useTranslation(); - const handlePrivateBoardsToggle = useCallback( - () => setIsPrivateBoardsOpen(!isPrivateBoardsOpen), - [isPrivateBoardsOpen, setIsPrivateBoardsOpen] - ); - const handleSharedBoardsToggle = useCallback( - () => setIsSharedBoardsOpen(!isSharedBoardsOpen), - [isSharedBoardsOpen, setIsSharedBoardsOpen] - ); + const { filteredPrivateBoards, filteredSharedBoards } = useMemo(() => { + const filteredBoards = boardSearchText + ? boards?.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase())) + : boards; + const filteredPrivateBoards = filteredBoards?.filter((board) => board.is_private) ?? EMPTY_ARRAY; + const filteredSharedBoards = filteredBoards?.filter((board) => !board.is_private) ?? EMPTY_ARRAY; + return { filteredPrivateBoards, filteredSharedBoards }; + }, [boardSearchText, boards]); return ( <> @@ -59,11 +55,11 @@ const BoardsList = () => { {allowPrivateBoards && ( <> - + { - + - {filteredPrivateBoards && - filteredPrivateBoards.map((board) => ( - - ))} + {filteredPrivateBoards.map((board) => ( + + ))} )} - + { - + - {filteredSharedBoards && - filteredSharedBoards.map((board) => ( - - ))} + {filteredSharedBoards.map((board) => ( + + ))} From 81cf47dd99ac190cbdb1c16cb44db9608fea8169 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 21:58:48 +1000 Subject: [PATCH 24/37] feat(ui): boards list layout & style tweaking --- .../src/common/components/IAIDropOverlay.tsx | 4 +- .../Boards/BoardsList/BoardsList.tsx | 127 +++++---- .../Boards/BoardsList/GalleryBoard.tsx | 256 +++++++++--------- .../Boards/BoardsList/NoBoardBoard.tsx | 103 ++++--- .../gallery/components/GalleryBoardName.tsx | 1 - .../components/ImageGalleryContent.tsx | 8 +- 6 files changed, 243 insertions(+), 256 deletions(-) diff --git a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx index cd3e0cbee1..51e5583bc6 100644 --- a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx @@ -52,8 +52,8 @@ const IAIDropOverlay = (props: Props) => { bottom={0.5} opacity={1} borderWidth={2} - borderColor={isOver ? 'base.50' : 'base.300'} - borderRadius="lg" + borderColor={isOver ? 'base.300' : 'base.500'} + borderRadius="base" borderStyle="dashed" transitionProperty="common" transitionDuration="0.1s" diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index 75f73adb6a..f7fd6150dc 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -1,4 +1,4 @@ -import { Box, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library'; +import { Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library'; import { EMPTY_ARRAY } from 'app/store/constants'; import { useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; @@ -45,80 +45,89 @@ const BoardsList = () => { return ( <> - - + + - - - {allowPrivateBoards && ( - <> - - - - - {t('boards.private')} - - - - - - - - {filteredPrivateBoards.map((board) => ( - - ))} - - - - )} - - + {allowPrivateBoards && ( + <> + + - - {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} + + {t('boards.private')} - + - - - {filteredSharedBoards.map((board) => ( - - ))} - + + + + + {filteredPrivateBoards.map((board) => ( + + ))} + + - - + + )} + + + + + {allowPrivateBoards ? t('boards.shared') : t('boards.boards')} + + + + + + + + {filteredSharedBoards.map((board) => ( + + ))} + + + ); }; - export default memo(BoardsList); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index 246454fa57..fd951a1e89 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -1,16 +1,25 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; -import { Box, Editable, EditableInput, EditablePreview, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library'; +import { + Editable, + EditableInput, + EditablePreview, + Flex, + Icon, + Image, + Text, + Tooltip, + useDisclosure, +} from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; -import SelectionOverlay from 'common/components/SelectionOverlay'; import type { AddToBoardDropData } from 'features/dnd/types'; import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiArchiveBold, PiImagesSquare } from 'react-icons/pi'; +import { PiArchiveBold, PiImageSquare } from 'react-icons/pi'; import { useUpdateBoardMutation } from 'services/api/endpoints/boards'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import type { BoardDTO } from 'services/api/types'; @@ -19,14 +28,13 @@ const editableInputStyles: SystemStyleObject = { p: 0, fontSize: 'md', w: '100%', + _focusVisible: { + p: 0, + }, }; -const ArchivedIcon = () => { - return ( - - - - ); +const _hover: SystemStyleObject = { + bg: 'base.800', }; interface GalleryBoardProps { @@ -39,65 +47,51 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps const dispatch = useAppDispatch(); const { t } = useTranslation(); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); - - const [isHovered, setIsHovered] = useState(false); - const handleMouseOver = useCallback(() => { - setIsHovered(true); - }, []); - const handleMouseOut = useCallback(() => { - setIsHovered(false); - }, []); - - const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken); - - const { board_name, board_id } = board; - const [localBoardName, setLocalBoardName] = useState(board_name); + const editingDisclosure = useDisclosure(); + const [localBoardName, setLocalBoardName] = useState(board.board_name); const handleSelectBoard = useCallback(() => { - dispatch(boardIdSelected({ boardId: board_id })); + dispatch(boardIdSelected({ boardId: board.board_id })); if (autoAssignBoardOnClick) { - dispatch(autoAddBoardIdChanged(board_id)); + dispatch(autoAddBoardIdChanged(board.board_id)); } - }, [board_id, autoAssignBoardOnClick, dispatch]); + }, [dispatch, board.board_id, autoAssignBoardOnClick]); const [updateBoard, { isLoading: isUpdateBoardLoading }] = useUpdateBoardMutation(); const droppableData: AddToBoardDropData = useMemo( () => ({ - id: board_id, + id: board.board_id, actionType: 'ADD_TO_BOARD', - context: { boardId: board_id }, + context: { boardId: board.board_id }, }), - [board_id] + [board.board_id] ); const handleSubmit = useCallback( async (newBoardName: string) => { - // empty strings are not allowed if (!newBoardName.trim()) { - setLocalBoardName(board_name); - return; - } + // empty strings are not allowed + setLocalBoardName(board.board_name); + } else if (newBoardName === board.board_name) { + // don't updated the board name if it hasn't changed + } else { + try { + const { board_name } = await updateBoard({ + board_id: board.board_id, + changes: { board_name: newBoardName }, + }).unwrap(); - // don't updated the board name if it hasn't changed - if (newBoardName === board_name) { - return; - } - - try { - const { board_name } = await updateBoard({ - board_id, - changes: { board_name: newBoardName }, - }).unwrap(); - - // update local state - setLocalBoardName(board_name); - } catch { - // revert on error - setLocalBoardName(board_name); + // update local state + setLocalBoardName(board_name); + } catch { + // revert on error + setLocalBoardName(board.board_name); + } } + editingDisclosure.onClose(); }, - [board_id, board_name, updateBoard] + [board.board_id, board.board_name, editingDisclosure, updateBoard] ); const handleChange = useCallback((newBoardName: string) => { @@ -105,92 +99,88 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps }, []); return ( - - - - {(ref) => ( - } - openDelay={1000} + + {(ref) => ( + } + openDelay={1000} + > + + + - - - {board.archived && } - {coverImage?.thumbnail_url ? ( - - ) : ( - - - - )} - - - - - - - - - - - {`${t('boards.imagesWithCount', { count: board.image_count })}`} - - - {t('unifiedCanvas.move')}} /> - - - )} - - - + + + + {board.archived && !editingDisclosure.isOpen && ( + + )} + {t('unifiedCanvas.move')}} /> + + + )} + ); }; export default memo(GalleryBoard); + +const CoverImage = ({ board }: { board: BoardDTO }) => { + const { currentData: coverImage } = useGetImageDTOQuery(board.cover_image_name ?? skipToken); + + if (coverImage) { + return ( + + ); + } + + return ( + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 77563859a7..c2ba470067 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -1,13 +1,12 @@ -import { Box, Flex, Image, Text, Tooltip } from '@invoke-ai/ui-library'; +import type { SystemStyleObject } from '@invoke-ai/ui-library'; +import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; -import SelectionOverlay from 'common/components/SelectionOverlay'; import type { RemoveFromBoardDropData } from 'features/dnd/types'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; -import InvokeLogoSVG from 'public/assets/images/invoke-symbol-wht-lrg.svg'; -import { memo, useCallback, useMemo, useState } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useBoardName } from 'services/api/hooks/useBoardName'; @@ -15,6 +14,10 @@ interface Props { isSelected: boolean; } +const _hover: SystemStyleObject = { + bg: 'base.800', +}; + const NoBoardBoard = memo(({ isSelected }: Props) => { const dispatch = useAppDispatch(); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); @@ -25,15 +28,6 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { dispatch(autoAddBoardIdChanged('none')); } }, [dispatch, autoAssignBoardOnClick]); - const [isHovered, setIsHovered] = useState(false); - - const handleMouseOver = useCallback(() => { - setIsHovered(true); - }, []); - - const handleMouseOut = useCallback(() => { - setIsHovered(false); - }, []); const droppableData: RemoveFromBoardDropData = useMemo( () => ({ @@ -44,50 +38,47 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { ); const { t } = useTranslation(); return ( - - - - {(ref) => ( - } openDelay={1000}> - - invoke-ai-logo + {(ref) => ( + } openDelay={1000}> + + + {/* iconified from public/assets/images/invoke-symbol-wht-lrg.svg */} + + - - {boardName} - - - {t('unifiedCanvas.move')}} /> - - - )} - - - + + + + + {boardName} + + {t('unifiedCanvas.move')}} /> + + + )} + ); }); diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx index bc851893c4..d4c22b4fe2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx @@ -16,7 +16,6 @@ const GalleryBoardName = () => { return ( { gap={2} > {galleryHeader} - - - - + + From 2faf1e2ed3f0f391beb7cdb336a6a4a1be8ad51e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:02:54 +1000 Subject: [PATCH 25/37] fix(ui): show uncategorized board when private boards disabled --- .../gallery/components/Boards/BoardsList/BoardsList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index f7fd6150dc..16bddeb705 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -81,7 +81,7 @@ const BoardsList = () => { options={overlayScrollbarsParams.options} > - + {allowPrivateBoards && } {filteredPrivateBoards.map((board) => ( { + {!allowPrivateBoards && } {filteredSharedBoards.map((board) => ( Date: Tue, 9 Jul 2024 22:14:21 +1000 Subject: [PATCH 26/37] fix(ui): restore auto-add indicator --- .../gallery/components/Boards/AutoAddBadge.tsx | 14 ++++++++++++++ .../components/Boards/BoardsList/GalleryBoard.tsx | 4 ++++ .../components/Boards/BoardsList/NoBoardBoard.tsx | 5 ++++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx new file mode 100644 index 0000000000..8ecd6a16bc --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx @@ -0,0 +1,14 @@ +import { Badge } from '@invoke-ai/ui-library'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const AutoAddBadge = memo(() => { + const { t } = useTranslation(); + return ( + + {t('common.auto')} + + ); +}); + +AutoAddBadge.displayName = 'AutoAddBadge'; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index fd951a1e89..a2830026fd 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -14,6 +14,7 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import type { AddToBoardDropData } from 'features/dnd/types'; +import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge'; import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; @@ -46,6 +47,7 @@ interface GalleryBoardProps { const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); + const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); const editingDisclosure = useDisclosure(); const [localBoardName, setLocalBoardName] = useState(board.board_name); @@ -144,6 +146,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps /> + {autoAddBoardId === board.board_id && } {board.archived && !editingDisclosure.isOpen && ( )} + {t('unifiedCanvas.move')}} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index c2ba470067..895511a951 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -3,6 +3,7 @@ import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import type { RemoveFromBoardDropData } from 'features/dnd/types'; +import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge'; import { BoardTotalsTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTotalsTooltip'; import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu'; import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; @@ -20,6 +21,7 @@ const _hover: SystemStyleObject = { const NoBoardBoard = memo(({ isSelected }: Props) => { const dispatch = useAppDispatch(); + const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); const boardName = useBoardName('none'); const handleSelectBoard = useCallback(() => { @@ -70,10 +72,11 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { color={isSelected ? 'base.100' : 'base.400'} fontWeight={isSelected ? 'semibold' : 'normal'} noOfLines={1} - flexShrink={0} + flexGrow={1} > {boardName} + {autoAddBoardId === 'none' && } {t('unifiedCanvas.move')}} /> From 060d698a1200012b09f2e8752291dcffb2b0f95b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:19:20 +1000 Subject: [PATCH 27/37] feat(ui): restore image count for boards --- .../gallery/components/Boards/BoardsList/GalleryBoard.tsx | 1 + .../gallery/components/Boards/BoardsList/NoBoardBoard.tsx | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index a2830026fd..ed965254ef 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -154,6 +154,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps filter="drop-shadow(0px 0px 0.1rem var(--invoke-colors-base-800))" /> )} + {board.image_count} {t('unifiedCanvas.move')}} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 895511a951..fb47bf5810 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -9,6 +9,7 @@ import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardB import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards'; import { useBoardName } from 'services/api/hooks/useBoardName'; interface Props { @@ -21,6 +22,11 @@ const _hover: SystemStyleObject = { const NoBoardBoard = memo(({ isSelected }: Props) => { const dispatch = useAppDispatch(); + const { imagesTotal } = useGetBoardImagesTotalQuery('none', { + selectFromResult: ({ data }) => { + return { imagesTotal: data?.total ?? 0 }; + }, + }); const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick); const boardName = useBoardName('none'); @@ -77,6 +83,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { {boardName} {autoAddBoardId === 'none' && } + {imagesTotal} {t('unifiedCanvas.move')}} /> From af63c538ed4a7b25e135a2f302ad0246b34c20d1 Mon Sep 17 00:00:00 2001 From: Ryan Dick Date: Tue, 9 Jul 2024 08:35:43 -0400 Subject: [PATCH 28/37] Demote error log to warning to models treated as having size 0. --- invokeai/backend/model_manager/load/model_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/backend/model_manager/load/model_util.py b/invokeai/backend/model_manager/load/model_util.py index c798b92d8c..64fbd29a1f 100644 --- a/invokeai/backend/model_manager/load/model_util.py +++ b/invokeai/backend/model_manager/load/model_util.py @@ -38,7 +38,7 @@ def calc_model_size_by_data(logger: logging.Logger, model: AnyModel) -> int: else: # TODO(ryand): Promote this from a log to an exception once we are confident that we are handling all of the # supported model types. - logger.error( + logger.warning( f"Failed to calculate model size for unexpected model type: {type(model)}. The model will be treated as " "having size 0." ) From 6014382c7b80170e4eb1258671dec051e484b542 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:37:41 +1000 Subject: [PATCH 29/37] feat(ui): select a board when it is created --- .../listeners/addArchivedOrDeletedBoardListener.ts | 2 -- .../Boards/BoardsList/AddBoardButton.tsx | 14 +++++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts index 0915929245..c569a6e36d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addArchivedOrDeletedBoardListener.ts @@ -15,8 +15,6 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis matcher: isAnyOf( // Updating a board may change its archived status boardsApi.endpoints.updateBoard.matchFulfilled, - // If the selected/auto-add board was deleted from a different session, we'll only know during the list request, - boardsApi.endpoints.listAllBoards.matchFulfilled, // If a board is deleted, we'll need to reset the auto-add board imagesApi.endpoints.deleteBoard.matchFulfilled, imagesApi.endpoints.deleteBoardAndImages.matchFulfilled, diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index 6b0ebea257..112d0d61f8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -1,4 +1,6 @@ import { IconButton } from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { boardIdSelected } from 'features/gallery/store/gallerySlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; @@ -10,11 +12,17 @@ type Props = { const AddBoardButton = ({ isPrivateBoard }: Props) => { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const [createBoard, { isLoading }] = useCreateBoardMutation(); const DEFAULT_BOARD_NAME = t('boards.myBoard'); - const handleCreateBoard = useCallback(() => { - createBoard({ board_name: DEFAULT_BOARD_NAME, is_private: isPrivateBoard }); - }, [createBoard, DEFAULT_BOARD_NAME, isPrivateBoard]); + const handleCreateBoard = useCallback(async () => { + try { + const board = await createBoard({ board_name: DEFAULT_BOARD_NAME, is_private: isPrivateBoard }).unwrap(); + dispatch(boardIdSelected({ boardId: board.board_id })); + } catch { + //no-op + } + }, [createBoard, DEFAULT_BOARD_NAME, isPrivateBoard, dispatch]); return ( Date: Tue, 9 Jul 2024 22:39:17 +1000 Subject: [PATCH 30/37] fix(ui): autoadd badge hides when editing name --- .../gallery/components/Boards/BoardsList/GalleryBoard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx index ed965254ef..32ed84558c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx @@ -146,7 +146,7 @@ const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps /> - {autoAddBoardId === board.board_id && } + {autoAddBoardId === board.board_id && !editingDisclosure.isOpen && } {board.archived && !editingDisclosure.isOpen && ( )} - {board.image_count} + {!editingDisclosure.isOpen && {board.image_count}} {t('unifiedCanvas.move')}} /> From d38d513d23f9ef0f1756c1c907a665cb9fed2083 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:39:32 +1000 Subject: [PATCH 31/37] fix(ui): autoadd badge doesn't flex shrink --- .../web/src/features/gallery/components/Boards/AutoAddBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx index 8ecd6a16bc..a8b1f9f4fb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/AutoAddBadge.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; export const AutoAddBadge = memo(() => { const { t } = useTranslation(); return ( - + {t('common.auto')} ); From 781b800ef76171367f87fc2b446593fd1cea048f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:40:50 +1000 Subject: [PATCH 32/37] feat(ui): boards lists start collapsed --- .../gallery/components/Boards/BoardsList/BoardsList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index 16bddeb705..6f37dbcdb5 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -30,8 +30,8 @@ const BoardsList = () => { const queryArgs = useAppSelector(selectListBoardsQueryArgs); const { data: boards } = useListAllBoardsQuery(queryArgs); const [boardToDelete, setBoardToDelete] = useState(); - const privateBoardsDisclosure = useDisclosure({ defaultIsOpen: true }); - const sharedBoardsDisclosure = useDisclosure({ defaultIsOpen: true }); + const privateBoardsDisclosure = useDisclosure({ defaultIsOpen: false }); + const sharedBoardsDisclosure = useDisclosure({ defaultIsOpen: false }); const { t } = useTranslation(); const { filteredPrivateBoards, filteredSharedBoards } = useMemo(() => { From 2460689c002923c37592ce4e33f579b194c87582 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:47:03 +1000 Subject: [PATCH 33/37] feat(ui): style board name --- .../gallery/components/GalleryBoardName.tsx | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx index d4c22b4fe2..233aa8a8c1 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryBoardName.tsx @@ -1,29 +1,17 @@ -import { Flex } from '@invoke-ai/ui-library'; +import { Flex, Text } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; -import { memo, useMemo } from 'react'; +import { memo } from 'react'; import { useBoardName } from 'services/api/hooks/useBoardName'; const GalleryBoardName = () => { const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId); const boardName = useBoardName(selectedBoardId); - const formattedBoardName = useMemo(() => { - if (boardName.length > 20) { - return `${boardName.substring(0, 20)}...`; - } - return boardName; - }, [boardName]); - return ( - - {formattedBoardName} + + + {boardName} + ); }; From 476ebd13aeedfeab2af899843d5378a8b1a3b161 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:51:08 +1000 Subject: [PATCH 34/37] feat(ui): add board button tooltip when private boards enabled --- invokeai/frontend/web/public/locales/en.json | 2 ++ .../Boards/BoardsList/AddBoardButton.tsx | 23 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 3332e21416..4b102d6cf3 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -17,6 +17,8 @@ }, "boards": { "addBoard": "Add Board", + "addPrivateBoard": "Add Private Board", + "addSharedBoard": "Add Shared Board", "archiveBoard": "Archive Board", "archived": "Archived", "autoAddBoard": "Auto-Add Board", diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index 112d0d61f8..c6ddb85daa 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -1,7 +1,7 @@ import { IconButton } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { boardIdSelected } from 'features/gallery/store/gallerySlice'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiPlusBold } from 'react-icons/pi'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; @@ -13,23 +13,32 @@ type Props = { const AddBoardButton = ({ isPrivateBoard }: Props) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); + const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards); const [createBoard, { isLoading }] = useCreateBoardMutation(); - const DEFAULT_BOARD_NAME = t('boards.myBoard'); + const label = useMemo(() => { + if (!allowPrivateBoards) { + return t('boards.addBoard'); + } + if (isPrivateBoard) { + return t('boards.addPrivateBoard'); + } + return t('boards.addSharedBoard'); + }, [allowPrivateBoards, isPrivateBoard, t]); const handleCreateBoard = useCallback(async () => { try { - const board = await createBoard({ board_name: DEFAULT_BOARD_NAME, is_private: isPrivateBoard }).unwrap(); + const board = await createBoard({ board_name: t('boards.myBoard'), is_private: isPrivateBoard }).unwrap(); dispatch(boardIdSelected({ boardId: board.board_id })); } catch { //no-op } - }, [createBoard, DEFAULT_BOARD_NAME, isPrivateBoard, dispatch]); + }, [t, createBoard, isPrivateBoard, dispatch]); return ( } isLoading={isLoading} - tooltip={t('boards.addBoard')} - aria-label={t('boards.addBoard')} + tooltip={label} + aria-label={label} onClick={handleCreateBoard} size="md" data-testid="add-board-button" From b672cc37a7e6d9e1cdf4a9b0175bd8342fd1d34f Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Mon, 24 Jun 2024 16:51:06 -0400 Subject: [PATCH 35/37] docs: overhaul Docker documentation, add to main README --- README.md | 29 ++++++++++ docker/README.md | 70 ++++++++++++++++++------- docs/installation/040_INSTALL_DOCKER.md | 65 ++++++++--------------- 3 files changed, 102 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 4c24ac6206..96c3210c66 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,33 @@ Invoke is available in two editions: More detail, including hardware requirements and manual install instructions, are available in the [installation documentation][installation docs]. +## Docker Container + +We publish official container images in Github Container Registry: https://github.com/invoke-ai/InvokeAI/pkgs/container/invokeai. Both CUDA and ROCm images are available. Check the above link for relevant tags. + +> [!IMPORTANT] +> Ensure that Docker is set up to use the GPU. Refer to [NVIDIA][nvidia docker docs] or [AMD][amd docker docs] documentation. + +### Generate! + +Run the container, modifying the command as necessary: + +```bash +docker run --runtime=nvidia --gpus=all --publish 9090:9090 ghcr.io/invoke-ai/invokeai +``` + +Then open `http://localhost:9090` and install some models using the Model Manager tab to begin generating. + +For ROCm, add `--device /dev/kfd --device /dev/dri` to the `docker run` command. + +### Persist your data + +You will likely want to persist your workspace outside of the container. Use the `--volume /home/myuser/invokeai:/invokeai` flag to mount some local directory (using its **absolute** path) to the `/invokeai` path inside the container. Your generated images and models will reside there. You can use this directory with other InvokeAI installations, or switch between runtime directories as needed. + +### DIY + +Build your own image and customize the environment to match your needs using our `docker-compose` stack. See [README.md](./docker/README.md) in the [docker](./docker) directory. + ## Troubleshooting, FAQ and Support Please review our [FAQ][faq] for solutions to common installation problems and other issues. @@ -126,3 +153,5 @@ Original portions of the software are Copyright © 2024 by respective contributo [latest release link]: https://github.com/invoke-ai/InvokeAI/releases/latest [translation status badge]: https://hosted.weblate.org/widgets/invokeai/-/svg-badge.svg [translation status link]: https://hosted.weblate.org/engage/invokeai/ +[nvidia docker docs]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html +[amd docker docs]: https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html diff --git a/docker/README.md b/docker/README.md index 9e7ac15145..fc6edeacd3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,41 +1,75 @@ -# InvokeAI Containerized +# Invoke in Docker -All commands should be run within the `docker` directory: `cd docker` +- Ensure that Docker can use the GPU on your system +- This documentation assumes Linux, but should work similarly under Windows with WSL2 +- We don't recommend running Invoke in Docker on macOS at this time. It works, but very slowly. -## Quickstart :rocket: +## Quickstart :lightning: -On a known working Linux+Docker+CUDA (Nvidia) system, execute `./run.sh` in this directory. It will take a few minutes - depending on your internet speed - to install the core models. Once the application starts up, open `http://localhost:9090` in your browser to Invoke! +No `docker compose`, no persistence, just a simple one-liner using the official images: -For more configuration options (using an AMD GPU, custom root directory location, etc): read on. +**CUDA:** -## Detailed setup +```bash +docker run --runtime=nvidia --gpus=all --publish 9090:9090 ghcr.io/invoke-ai/invokeai +``` + +**ROCm:** + +```bash +docker run --device /dev/kfd --device /dev/dri --publish 9090:9090 ghcr.io/invoke-ai/invokeai:main-rocm +``` + +Open `http://localhost:9090` in your browser once the container finishes booting, install some models, and generate away! + +> [!TIP] +> To persist your data (including downloaded models) outside of the container, add a `--volume/-v` flag to the above command, e.g.: `docker run --volume /some/local/path:/invokeai <...the rest of the command>` + +## Customize the container + +We ship the `run.sh` script, which is a convenient wrapper around `docker compose` for cases where custom image build args are needed. Alternatively, the familiar `docker compose` commands work just as well. + +```bash +cd docker +cp .env.sample .env +# edit .env to your liking if you need to; it is well commented. +./run.sh +``` + +It will take a few minutes to build the image the first time. Once the application starts up, open `http://localhost:9090` in your browser to invoke! + +## Docker setup in detail #### Linux 1. Ensure builkit is enabled in the Docker daemon settings (`/etc/docker/daemon.json`) 2. Install the `docker compose` plugin using your package manager, or follow a [tutorial](https://docs.docker.com/compose/install/linux/#install-using-the-repository). - - The deprecated `docker-compose` (hyphenated) CLI continues to work for now. + - The deprecated `docker-compose` (hyphenated) CLI probably won't work. Update to a recent version. 3. Ensure docker daemon is able to access the GPU. - - You may need to install [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) + - [NVIDIA docs](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) + - [AMD docs](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html) #### macOS +> [!TIP] +> You'll be better off installing Invoke directly on your system, because Docker can not use the GPU on macOS. + +If you are still reading: + 1. Ensure Docker has at least 16GB RAM 2. Enable VirtioFS for file sharing 3. Enable `docker compose` V2 support -This is done via Docker Desktop preferences +This is done via Docker Desktop preferences. -### Configure Invoke environment +### Configure the Invoke Environment -1. Make a copy of `.env.sample` and name it `.env` (`cp .env.sample .env` (Mac/Linux) or `copy example.env .env` (Windows)). Make changes as necessary. Set `INVOKEAI_ROOT` to an absolute path to: - a. the desired location of the InvokeAI runtime directory, or - b. an existing, v3.0.0 compatible runtime directory. +1. Make a copy of `.env.sample` and name it `.env` (`cp .env.sample .env` (Mac/Linux) or `copy example.env .env` (Windows)). Make changes as necessary. Set `INVOKEAI_ROOT` to an absolute path to the desired location of the InvokeAI runtime directory. It may be an existing directory from a previous installation (post 4.0.0). 1. Execute `run.sh` The image will be built automatically if needed. -The runtime directory (holding models and outputs) will be created in the location specified by `INVOKEAI_ROOT`. The default location is `~/invokeai`. The runtime directory will be populated with the base configs and models necessary to start generating. +The runtime directory (holding models and outputs) will be created in the location specified by `INVOKEAI_ROOT`. The default location is `~/invokeai`. Navigate to the Model Manager tab and install some models before generating. ### Use a GPU @@ -43,9 +77,9 @@ The runtime directory (holding models and outputs) will be created in the locati - WSL2 is *required* for Windows. - only `x86_64` architecture is supported. -The Docker daemon on the system must be already set up to use the GPU. In case of Linux, this involves installing `nvidia-docker-runtime` and configuring the `nvidia` runtime as default. Steps will be different for AMD. Please see Docker documentation for the most up-to-date instructions for using your GPU with Docker. +The Docker daemon on the system must be already set up to use the GPU. In case of Linux, this involves installing `nvidia-docker-runtime` and configuring the `nvidia` runtime as default. Steps will be different for AMD. Please see Docker/NVIDIA/AMD documentation for the most up-to-date instructions for using your GPU with Docker. -To use an AMD GPU, set `GPU_DRIVER=rocm` in your `.env` file. +To use an AMD GPU, set `GPU_DRIVER=rocm` in your `.env` file before running `./run.sh`. ## Customize @@ -59,10 +93,10 @@ Values are optional, but setting `INVOKEAI_ROOT` is highly recommended. The defa INVOKEAI_ROOT=/Volumes/WorkDrive/invokeai HUGGINGFACE_TOKEN=the_actual_token CONTAINER_UID=1000 -GPU_DRIVER=nvidia +GPU_DRIVER=cuda ``` -Any environment variables supported by InvokeAI can be set here - please see the [Configuration docs](https://invoke-ai.github.io/InvokeAI/features/CONFIGURATION/) for further detail. +Any environment variables supported by InvokeAI can be set here. See the [Configuration docs](https://invoke-ai.github.io/InvokeAI/features/CONFIGURATION/) for further detail. ## Even More Customizing! diff --git a/docs/installation/040_INSTALL_DOCKER.md b/docs/installation/040_INSTALL_DOCKER.md index 3814b72e80..119cff93d2 100644 --- a/docs/installation/040_INSTALL_DOCKER.md +++ b/docs/installation/040_INSTALL_DOCKER.md @@ -4,50 +4,37 @@ title: Installing with Docker # :fontawesome-brands-docker: Docker -!!! warning "macOS and AMD GPU Users" +!!! warning "macOS users" - We highly recommend to Install InvokeAI locally using [these instructions](INSTALLATION.md), - because Docker containers can not access the GPU on macOS. - -!!! warning "AMD GPU Users" - - Container support for AMD GPUs has been reported to work by the community, but has not received - extensive testing. Please make sure to set the `GPU_DRIVER=rocm` environment variable (see below), and - use the `build.sh` script to build the image for this to take effect at build time. + Docker can not access the GPU on macOS, so your generation speeds will be slow. [Install InvokeAI](INSTALLATION.md) instead. !!! tip "Linux and Windows Users" - For optimal performance, configure your Docker daemon to access your machine's GPU. + Configure Docker to access your machine's GPU. Docker Desktop on Windows [includes GPU support](https://www.docker.com/blog/wsl-2-gpu-support-for-docker-desktop-on-nvidia-gpus/). - Linux users should install and configure the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) - -## Why containers? - -They provide a flexible, reliable way to build and deploy InvokeAI. -See [Processes](https://12factor.net/processes) under the Twelve-Factor App -methodology for details on why running applications in such a stateless fashion is important. - -The container is configured for CUDA by default, but can be built to support AMD GPUs -by setting the `GPU_DRIVER=rocm` environment variable at Docker image build time. - -Developers on Apple silicon (M1/M2/M3): You -[can't access your GPU cores from Docker containers](https://github.com/pytorch/pytorch/issues/81224) -and performance is reduced compared with running it directly on macOS but for -development purposes it's fine. Once you're done with development tasks on your -laptop you can build for the target platform and architecture and deploy to -another environment with NVIDIA GPUs on-premises or in the cloud. + Linux users should follow the [NVIDIA](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) or [AMD](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html) documentation. ## TL;DR -This assumes properly configured Docker on Linux or Windows/WSL2. Read on for detailed customization options. +Ensure your Docker setup is able to use your GPU. Then: + + ```bash + docker run --runtime=nvidia --gpus=all --publish 9090:9090 ghcr.io/invoke-ai/invokeai + ``` + +Once the container starts up, open http://localhost:9090 in your browser, install some models, and start generating. + +## Build-It-Yourself + +All the docker materials are located inside the [docker](https://github.com/invoke-ai/InvokeAI/tree/main/docker) directory in the Git repo. ```bash - # docker compose commands should be run from the `docker` directory cd docker + cp .env.sample .env docker compose up ``` -## Installation in a Linux container (desktop) +We also ship the `run.sh` convenience script. See the `docker/README.md` file for detailed instructions on how to customize the docker setup to your needs. ### Prerequisites @@ -58,18 +45,9 @@ Preferences, Resources, Advanced. Increase the CPUs and Memory to avoid this [Issue](https://github.com/invoke-ai/InvokeAI/issues/342). You may need to increase Swap and Disk image size too. -#### Get a Huggingface-Token - -Besides the Docker Agent you will need an Account on -[huggingface.co](https://huggingface.co/join). - -After you succesfully registered your account, go to -[huggingface.co/settings/tokens](https://huggingface.co/settings/tokens), create -a token and copy it, since you will need in for the next step. - ### Setup -Set up your environmnent variables. In the `docker` directory, make a copy of `.env.sample` and name it `.env`. Make changes as necessary. +Set up your environment variables. In the `docker` directory, make a copy of `.env.sample` and name it `.env`. Make changes as necessary. Any environment variables supported by InvokeAI can be set here - please see the [CONFIGURATION](../features/CONFIGURATION.md) for further detail. @@ -103,10 +81,9 @@ Once the container starts up (and configures the InvokeAI root directory if this ## Troubleshooting / FAQ - Q: I am running on Windows under WSL2, and am seeing a "no such file or directory" error. -- A: Your `docker-entrypoint.sh` file likely has Windows (CRLF) as opposed to Unix (LF) line endings, - and you may have cloned this repository before the issue was fixed. To solve this, please change - the line endings in the `docker-entrypoint.sh` file to `LF`. You can do this in VSCode +- A: Your `docker-entrypoint.sh` might have has Windows (CRLF) line endings, depending how you cloned the repository. + To solve this, change the line endings in the `docker-entrypoint.sh` file to `LF`. You can do this in VSCode (`Ctrl+P` and search for "line endings"), or by using the `dos2unix` utility in WSL. Finally, you may delete `docker-entrypoint.sh` followed by `git pull; git checkout docker/docker-entrypoint.sh` to reset the file to its most recent version. - For more information on this issue, please see the [Docker Desktop documentation](https://docs.docker.com/desktop/troubleshoot/topics/#avoid-unexpected-syntax-errors-use-unix-style-line-endings-for-files-in-containers) + For more information on this issue, see [Docker Desktop documentation](https://docs.docker.com/desktop/troubleshoot/topics/#avoid-unexpected-syntax-errors-use-unix-style-line-endings-for-files-in-containers) From 42c2dea202c729c0e877cb954c3b585a3b328e62 Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Mon, 24 Jun 2024 16:56:07 -0400 Subject: [PATCH 36/37] fix(docker): change 'nvidia' profile name to 'cuda' --- docker/.env.sample | 5 +++-- docker/docker-compose.yml | 4 +--- docker/run.sh | 6 +++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docker/.env.sample b/docker/.env.sample index aeb69bfd27..eef690a808 100644 --- a/docker/.env.sample +++ b/docker/.env.sample @@ -19,8 +19,9 @@ ## INVOKEAI_PORT is the port on which the InvokeAI web interface will be available # INVOKEAI_PORT=9090 -## GPU_DRIVER can be set to either `nvidia` or `rocm` to enable GPU support in the container accordingly. -# GPU_DRIVER=nvidia #| rocm +## GPU_DRIVER can be set to either `cuda` or `rocm` to enable GPU support in the container accordingly. +# GPU_DRIVER=cuda #| rocm ## CONTAINER_UID can be set to the UID of the user on the host system that should own the files in the container. +## It is usually not necessary to change this. Use `id -u` on the host system to find the UID. # CONTAINER_UID=1000 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2ad50e74a1..af96cc1c8f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,7 +1,5 @@ # Copyright (c) 2023 Eugene Brodsky https://github.com/ebr -version: '3.8' - x-invokeai: &invokeai image: "local/invokeai:latest" build: @@ -32,7 +30,7 @@ x-invokeai: &invokeai services: - invokeai-nvidia: + invokeai-cuda: <<: *invokeai deploy: resources: diff --git a/docker/run.sh b/docker/run.sh index d413e53453..1040e865bf 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -8,11 +8,15 @@ run() { local build_args="" local profile="" + # create .env file if it doesn't exist, otherwise docker compose will fail touch .env + + # parse .env file for build args build_args=$(awk '$1 ~ /=[^$]/ && $0 !~ /^#/ {print "--build-arg " $0 " "}' .env) && profile="$(awk -F '=' '/GPU_DRIVER/ {print $2}' .env)" - [[ -z "$profile" ]] && profile="nvidia" + # default to 'cuda' profile + [[ -z "$profile" ]] && profile="cuda" local service_name="invokeai-$profile" From 4313578d8ea2da7d7812997653a070b1baff257d Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Mon, 24 Jun 2024 16:57:43 -0400 Subject: [PATCH 37/37] fix(docker): ensure 'chown' does not break on read-only fs; fixes #6264 --- docker/docker-entrypoint.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 7fb52f3af9..686bd9630f 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -23,18 +23,18 @@ usermod -u ${USER_ID} ${USER} 1>/dev/null # but it is useful to have the full SSH server e.g. on Runpod. # (use SCP to copy files to/from the image, etc) if [[ -v "PUBLIC_KEY" ]] && [[ ! -d "${HOME}/.ssh" ]]; then - apt-get update - apt-get install -y openssh-server - pushd "$HOME" - mkdir -p .ssh - echo "${PUBLIC_KEY}" > .ssh/authorized_keys - chmod -R 700 .ssh - popd - service ssh start + apt-get update + apt-get install -y openssh-server + pushd "$HOME" + mkdir -p .ssh + echo "${PUBLIC_KEY}" >.ssh/authorized_keys + chmod -R 700 .ssh + popd + service ssh start fi mkdir -p "${INVOKEAI_ROOT}" -chown --recursive ${USER} "${INVOKEAI_ROOT}" +chown --recursive ${USER} "${INVOKEAI_ROOT}" || true cd "${INVOKEAI_ROOT}" # Run the CMD as the Container User (not root).