diff --git a/docs/features/PROMPTS.md b/docs/features/PROMPTS.md index be11e4cce6..07b942177a 100644 --- a/docs/features/PROMPTS.md +++ b/docs/features/PROMPTS.md @@ -120,7 +120,7 @@ Generate an image with a given prompt, record the seed of the image, and then use the `prompt2prompt` syntax to substitute words in the original prompt for words in a new prompt. This works for `img2img` as well. -For example, consider the prompt `a cat.swap(dog) playing with a ball in the forest`. Normally, because of the word words interact with each other when doing a stable diffusion image generation, these two prompts would generate different compositions: +For example, consider the prompt `a cat.swap(dog) playing with a ball in the forest`. Normally, because the words interact with each other when doing a stable diffusion image generation, these two prompts would generate different compositions: - `a cat playing with a ball in the forest` - `a dog playing with a ball in the forest` diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py index 39d570ec99..2137aa9be7 100644 --- a/invokeai/app/api/routers/app_info.py +++ b/invokeai/app/api/routers/app_info.py @@ -1,7 +1,11 @@ import typing from enum import Enum +from importlib.metadata import PackageNotFoundError, version from pathlib import Path +from platform import python_version +from typing import Optional +import torch from fastapi import Body from fastapi.routing import APIRouter from pydantic import BaseModel, Field @@ -40,6 +44,24 @@ class AppVersion(BaseModel): version: str = Field(description="App version") +class AppDependencyVersions(BaseModel): + """App depencency Versions Response""" + + accelerate: str = Field(description="accelerate version") + compel: str = Field(description="compel version") + cuda: Optional[str] = Field(description="CUDA version") + diffusers: str = Field(description="diffusers version") + numpy: str = Field(description="Numpy version") + opencv: str = Field(description="OpenCV version") + onnx: str = Field(description="ONNX version") + pillow: str = Field(description="Pillow (PIL) version") + python: str = Field(description="Python version") + torch: str = Field(description="PyTorch version") + torchvision: str = Field(description="PyTorch Vision version") + transformers: str = Field(description="transformers version") + xformers: Optional[str] = Field(description="xformers version") + + class AppConfig(BaseModel): """App Config Response""" @@ -54,6 +76,29 @@ async def get_version() -> AppVersion: return AppVersion(version=__version__) +@app_router.get("/app_deps", operation_id="get_app_deps", status_code=200, response_model=AppDependencyVersions) +async def get_app_deps() -> AppDependencyVersions: + try: + xformers = version("xformers") + except PackageNotFoundError: + xformers = None + return AppDependencyVersions( + accelerate=version("accelerate"), + compel=version("compel"), + cuda=torch.version.cuda, + diffusers=version("diffusers"), + numpy=version("numpy"), + opencv=version("opencv-python"), + onnx=version("onnx"), + pillow=version("pillow"), + python=python_version(), + torch=torch.version.__version__, + torchvision=version("torchvision"), + transformers=version("transformers"), + xformers=xformers, + ) + + @app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig) async def get_config() -> AppConfig: infill_methods = ["tile", "lama", "cv2"] diff --git a/invokeai/app/services/shared/graph.py b/invokeai/app/services/shared/graph.py index c825a84011..854defc945 100644 --- a/invokeai/app/services/shared/graph.py +++ b/invokeai/app/services/shared/graph.py @@ -207,10 +207,12 @@ class IterateInvocationOutput(BaseInvocationOutput): item: Any = OutputField( description="The item being iterated over", title="Collection Item", ui_type=UIType._CollectionItem ) + index: int = OutputField(description="The index of the item", title="Index") + total: int = OutputField(description="The total number of items", title="Total") # TODO: Fill this out and move to invocations -@invocation("iterate", version="1.0.0") +@invocation("iterate", version="1.1.0") class IterateInvocation(BaseInvocation): """Iterates over a list of items""" @@ -221,7 +223,7 @@ class IterateInvocation(BaseInvocation): def invoke(self, context: InvocationContext) -> IterateInvocationOutput: """Produces the outputs as values""" - return IterateInvocationOutput(item=self.collection[self.index]) + return IterateInvocationOutput(item=self.collection[self.index], index=self.index, total=len(self.collection)) @invocation_output("collect_output") diff --git a/invokeai/frontend/web/src/app/hooks/useSocketIO.ts b/invokeai/frontend/web/src/app/hooks/useSocketIO.ts index 91048fa63c..b2f08b2815 100644 --- a/invokeai/frontend/web/src/app/hooks/useSocketIO.ts +++ b/invokeai/frontend/web/src/app/hooks/useSocketIO.ts @@ -3,8 +3,8 @@ import { $authToken } from 'app/store/nanostores/authToken'; import { $baseUrl } from 'app/store/nanostores/baseUrl'; import { $isDebugging } from 'app/store/nanostores/isDebugging'; import { useAppDispatch } from 'app/store/storeHooks'; -import { MapStore, WritableAtom, atom, map } from 'nanostores'; -import { useEffect } from 'react'; +import { MapStore, atom, map } from 'nanostores'; +import { useEffect, useMemo } from 'react'; import { ClientToServerEvents, ServerToClientEvents, @@ -16,57 +16,10 @@ import { ManagerOptions, Socket, SocketOptions, io } from 'socket.io-client'; declare global { interface Window { $socketOptions?: MapStore>; - $socketUrl?: WritableAtom; } } -const makeSocketOptions = (): Partial => { - const socketOptions: Parameters[0] = { - timeout: 60000, - path: '/ws/socket.io', - autoConnect: false, // achtung! removing this breaks the dynamic middleware - forceNew: true, - }; - - // if building in package mode, replace socket url with open api base url minus the http protocol - if (['nodes', 'package'].includes(import.meta.env.MODE)) { - const authToken = $authToken.get(); - if (authToken) { - // TODO: handle providing jwt to socket.io - socketOptions.auth = { token: authToken }; - } - - socketOptions.transports = ['websocket', 'polling']; - } - - return socketOptions; -}; - -const makeSocketUrl = (): string => { - const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; - let socketUrl = `${wsProtocol}://${window.location.host}`; - if (['nodes', 'package'].includes(import.meta.env.MODE)) { - const baseUrl = $baseUrl.get(); - if (baseUrl) { - //eslint-disable-next-line - socketUrl = baseUrl.replace(/^https?\:\/\//i, ''); - } - } - return socketUrl; -}; - -const makeSocket = (): Socket => { - const socketOptions = makeSocketOptions(); - const socketUrl = $socketUrl.get(); - const socket: Socket = io( - socketUrl, - { ...socketOptions, ...$socketOptions.get() } - ); - return socket; -}; - export const $socketOptions = map>({}); -export const $socketUrl = atom(makeSocketUrl()); export const $isSocketInitialized = atom(false); /** @@ -74,23 +27,50 @@ export const $isSocketInitialized = atom(false); */ export const useSocketIO = () => { const dispatch = useAppDispatch(); - const socketOptions = useStore($socketOptions); - const socketUrl = useStore($socketUrl); const baseUrl = useStore($baseUrl); const authToken = useStore($authToken); + const addlSocketOptions = useStore($socketOptions); + + const socketUrl = useMemo(() => { + const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; + if (baseUrl) { + return baseUrl.replace(/^https?:\/\//i, ''); + } + + return `${wsProtocol}://${window.location.host}`; + }, [baseUrl]); + + const socketOptions = useMemo(() => { + const options: Parameters[0] = { + timeout: 60000, + path: '/ws/socket.io', + autoConnect: false, // achtung! removing this breaks the dynamic middleware + forceNew: true, + }; + + if (authToken) { + options.auth = { token: authToken }; + options.transports = ['websocket', 'polling']; + } + + return { ...options, ...addlSocketOptions }; + }, [authToken, addlSocketOptions]); useEffect(() => { if ($isSocketInitialized.get()) { // Singleton! return; } - const socket = makeSocket(); + + const socket: Socket = io( + socketUrl, + socketOptions + ); setEventListeners({ dispatch, socket }); socket.connect(); if ($isDebugging.get()) { window.$socketOptions = $socketOptions; - window.$socketUrl = $socketUrl; console.log('Socket initialized', socket); } @@ -99,11 +79,10 @@ export const useSocketIO = () => { return () => { if ($isDebugging.get()) { window.$socketOptions = undefined; - window.$socketUrl = undefined; console.log('Socket teardown', socket); } socket.disconnect(); $isSocketInitialized.set(false); }; - }, [dispatch, socketOptions, socketUrl, baseUrl, authToken]); + }, [dispatch, socketOptions, socketUrl]); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index 092d4682f7..19c5f1a4e3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -73,7 +73,13 @@ const BoardContextMenu = ({ addToast({ title: t('gallery.preparingDownload'), status: 'success', - ...(response.response ? { description: response.response } : {}), + ...(response.response + ? { + description: response.response, + duration: null, + isClosable: true, + } + : {}), }) ); } catch { diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx index bb6751dcc3..273fa1ea54 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx @@ -59,7 +59,13 @@ const MultipleSelectionMenuItems = () => { addToast({ title: t('gallery.preparingDownload'), status: 'success', - ...(response.response ? { description: response.response } : {}), + ...(response.response + ? { + description: response.response, + duration: null, + isClosable: true, + } + : {}), }) ); } catch {