Merge remote-tracking branch 'origin/main' into maryhipp/style-presets

This commit is contained in:
Mary Hipp 2024-08-12 14:53:45 -04:00
commit 9c732ac3b1
10 changed files with 20634 additions and 19462 deletions

View File

@ -1,5 +1,6 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
import asyncio
from logging import Logger from logging import Logger
import torch import torch
@ -65,7 +66,12 @@ class ApiDependencies:
invoker: Invoker invoker: Invoker
@staticmethod @staticmethod
def initialize(config: InvokeAIAppConfig, event_handler_id: int, logger: Logger = logger) -> None: def initialize(
config: InvokeAIAppConfig,
event_handler_id: int,
loop: asyncio.AbstractEventLoop,
logger: Logger = logger,
) -> None:
logger.info(f"InvokeAI version {__version__}") logger.info(f"InvokeAI version {__version__}")
logger.info(f"Root directory = {str(config.root_path)}") logger.info(f"Root directory = {str(config.root_path)}")
@ -87,7 +93,7 @@ class ApiDependencies:
board_images = BoardImagesService() board_images = BoardImagesService()
board_records = SqliteBoardRecordStorage(db=db) board_records = SqliteBoardRecordStorage(db=db)
boards = BoardService() boards = BoardService()
events = FastAPIEventService(event_handler_id) events = FastAPIEventService(event_handler_id, loop=loop)
bulk_download = BulkDownloadService() bulk_download = BulkDownloadService()
image_records = SqliteImageRecordStorage(db=db) image_records = SqliteImageRecordStorage(db=db)
images = ImageService() images = ImageService()

View File

@ -218,9 +218,8 @@ async def get_image_workflow(
raise HTTPException(status_code=404) raise HTTPException(status_code=404)
@images_router.api_route( @images_router.get(
"/i/{image_name}/full", "/i/{image_name}/full",
methods=["GET", "HEAD"],
operation_id="get_image_full", operation_id="get_image_full",
response_class=Response, response_class=Response,
responses={ responses={
@ -231,6 +230,18 @@ async def get_image_workflow(
404: {"description": "Image not found"}, 404: {"description": "Image not found"},
}, },
) )
@images_router.head(
"/i/{image_name}/full",
operation_id="get_image_full_head",
response_class=Response,
responses={
200: {
"description": "Return the full-resolution image",
"content": {"image/png": {}},
},
404: {"description": "Image not found"},
},
)
async def get_image_full( async def get_image_full(
image_name: str = Path(description="The name of full-resolution image file to get"), image_name: str = Path(description="The name of full-resolution image file to get"),
) -> Response: ) -> Response:
@ -242,6 +253,7 @@ async def get_image_full(
content = f.read() content = f.read()
response = Response(content, media_type="image/png") response = Response(content, media_type="image/png")
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}" response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
response.headers["Content-Disposition"] = f'inline; filename="{image_name}"'
return response return response
except Exception: except Exception:
raise HTTPException(status_code=404) raise HTTPException(status_code=404)

View File

@ -56,11 +56,13 @@ mimetypes.add_type("text/css", ".css")
torch_device_name = TorchDevice.get_torch_device_name() torch_device_name = TorchDevice.get_torch_device_name()
logger.info(f"Using torch device: {torch_device_name}") logger.info(f"Using torch device: {torch_device_name}")
loop = asyncio.new_event_loop()
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
# Add startup event to load dependencies # Add startup event to load dependencies
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger) ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, loop=loop, logger=logger)
yield yield
# Shut down threads # Shut down threads
ApiDependencies.shutdown() ApiDependencies.shutdown()
@ -186,8 +188,6 @@ def invoke_api() -> None:
check_cudnn(logger) check_cudnn(logger)
# Start our own event loop for eventing usage
loop = asyncio.new_event_loop()
config = uvicorn.Config( config = uvicorn.Config(
app=app, app=app,
host=app_config.host, host=app_config.host,

View File

@ -1,46 +1,44 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
import asyncio import asyncio
import threading import threading
from queue import Empty, Queue
from fastapi_events.dispatcher import dispatch from fastapi_events.dispatcher import dispatch
from invokeai.app.services.events.events_base import EventServiceBase from invokeai.app.services.events.events_base import EventServiceBase
from invokeai.app.services.events.events_common import ( from invokeai.app.services.events.events_common import EventBase
EventBase,
)
class FastAPIEventService(EventServiceBase): class FastAPIEventService(EventServiceBase):
def __init__(self, event_handler_id: int) -> None: def __init__(self, event_handler_id: int, loop: asyncio.AbstractEventLoop) -> None:
self.event_handler_id = event_handler_id self.event_handler_id = event_handler_id
self._queue = Queue[EventBase | None]() self._queue = asyncio.Queue[EventBase | None]()
self._stop_event = threading.Event() self._stop_event = threading.Event()
asyncio.create_task(self._dispatch_from_queue(stop_event=self._stop_event)) self._loop = loop
# We need to store a reference to the task so it doesn't get GC'd
# See: https://docs.python.org/3/library/asyncio-task.html#creating-tasks
self._background_tasks: set[asyncio.Task[None]] = set()
task = self._loop.create_task(self._dispatch_from_queue(stop_event=self._stop_event))
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.remove)
super().__init__() super().__init__()
def stop(self, *args, **kwargs): def stop(self, *args, **kwargs):
self._stop_event.set() self._stop_event.set()
self._queue.put(None) self._loop.call_soon_threadsafe(self._queue.put_nowait, None)
def dispatch(self, event: EventBase) -> None: def dispatch(self, event: EventBase) -> None:
self._queue.put(event) self._loop.call_soon_threadsafe(self._queue.put_nowait, event)
async def _dispatch_from_queue(self, stop_event: threading.Event): async def _dispatch_from_queue(self, stop_event: threading.Event):
"""Get events on from the queue and dispatch them, from the correct thread""" """Get events on from the queue and dispatch them, from the correct thread"""
while not stop_event.is_set(): while not stop_event.is_set():
try: try:
event = self._queue.get(block=False) event = await self._queue.get()
if not event: # Probably stopping if not event: # Probably stopping
continue continue
# Leave the payloads as live pydantic models # Leave the payloads as live pydantic models
dispatch(event, middleware_id=self.event_handler_id, payload_schema_dump=False) dispatch(event, middleware_id=self.event_handler_id, payload_schema_dump=False)
except Empty:
await asyncio.sleep(0.1)
pass
except asyncio.CancelledError as e: except asyncio.CancelledError as e:
raise e # Raise a proper error raise e # Raise a proper error

View File

@ -81,7 +81,7 @@ def get_openapi_func(
# Add the output map to the schema # Add the output map to the schema
openapi_schema["components"]["schemas"]["InvocationOutputMap"] = { openapi_schema["components"]["schemas"]["InvocationOutputMap"] = {
"type": "object", "type": "object",
"properties": invocation_output_map_properties, "properties": dict(sorted(invocation_output_map_properties.items())),
"required": invocation_output_map_required, "required": invocation_output_map_required,
} }

View File

@ -53,61 +53,61 @@
}, },
"dependencies": { "dependencies": {
"@chakra-ui/react-use-size": "^2.1.0", "@chakra-ui/react-use-size": "^2.1.0",
"@dagrejs/dagre": "^1.1.2", "@dagrejs/dagre": "^1.1.3",
"@dagrejs/graphlib": "^2.2.2", "@dagrejs/graphlib": "^2.2.3",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@fontsource-variable/inter": "^5.0.18", "@fontsource-variable/inter": "^5.0.20",
"@invoke-ai/ui-library": "^0.0.25", "@invoke-ai/ui-library": "^0.0.25",
"@nanostores/react": "^0.7.2", "@nanostores/react": "^0.7.3",
"@reduxjs/toolkit": "2.2.3", "@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0", "@roarr/browser-log-writer": "^1.3.0",
"chakra-react-select": "^4.7.6", "chakra-react-select": "^4.9.1",
"compare-versions": "^6.1.0", "compare-versions": "^6.1.1",
"dateformat": "^5.0.3", "dateformat": "^5.0.3",
"fracturedjsonjs": "^4.0.1", "fracturedjsonjs": "^4.0.2",
"framer-motion": "^11.1.8", "framer-motion": "^11.3.24",
"i18next": "^23.11.3", "i18next": "^23.12.2",
"i18next-http-backend": "^2.5.1", "i18next-http-backend": "^2.5.2",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"jsondiffpatch": "^0.6.0", "jsondiffpatch": "^0.6.0",
"konva": "^9.3.6", "konva": "^9.3.14",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nanostores": "^0.10.3", "nanostores": "^0.11.2",
"new-github-issue-url": "^1.0.0", "new-github-issue-url": "^1.0.0",
"overlayscrollbars": "^2.7.3", "overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6", "overlayscrollbars-react": "^0.5.6",
"query-string": "^9.0.0", "query-string": "^9.1.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.13", "react-error-boundary": "^4.0.13",
"react-hook-form": "^7.51.4", "react-hook-form": "^7.52.2",
"react-hotkeys-hook": "4.5.0", "react-hotkeys-hook": "4.5.0",
"react-i18next": "^14.1.1", "react-i18next": "^14.1.3",
"react-icons": "^5.2.0", "react-icons": "^5.2.1",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-redux": "9.1.2", "react-redux": "9.1.2",
"react-resizable-panels": "^2.0.19", "react-resizable-panels": "^2.0.23",
"react-select": "5.8.0", "react-select": "5.8.0",
"react-use": "^17.5.0", "react-use": "^17.5.1",
"react-virtuoso": "^4.7.10", "react-virtuoso": "^4.9.0",
"reactflow": "^11.11.3", "reactflow": "^11.11.4",
"redux-dynamic-middlewares": "^2.2.0", "redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.1.0", "redux-remember": "^5.1.0",
"redux-undo": "^1.1.0", "redux-undo": "^1.1.0",
"rfdc": "^1.3.1", "rfdc": "^1.4.1",
"roarr": "^7.21.1", "roarr": "^7.21.1",
"serialize-error": "^11.0.3", "serialize-error": "^11.0.3",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.7.5",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.2",
"use-device-pixel-ratio": "^1.1.2", "use-device-pixel-ratio": "^1.1.2",
"use-image": "^1.1.1", "use-image": "^1.1.1",
"uuid": "^9.0.1", "uuid": "^10.0.0",
"zod": "^3.23.6", "zod": "^3.23.8",
"zod-validation-error": "^3.2.0" "zod-validation-error": "^3.3.1"
}, },
"peerDependencies": { "peerDependencies": {
"@chakra-ui/react": "^2.8.2", "@chakra-ui/react": "^2.8.2",
@ -118,38 +118,38 @@
"devDependencies": { "devDependencies": {
"@invoke-ai/eslint-config-react": "^0.0.14", "@invoke-ai/eslint-config-react": "^0.0.14",
"@invoke-ai/prettier-config-react": "^0.0.7", "@invoke-ai/prettier-config-react": "^0.0.7",
"@storybook/addon-essentials": "^8.0.10", "@storybook/addon-essentials": "^8.2.8",
"@storybook/addon-interactions": "^8.0.10", "@storybook/addon-interactions": "^8.2.8",
"@storybook/addon-links": "^8.0.10", "@storybook/addon-links": "^8.2.8",
"@storybook/addon-storysource": "^8.0.10", "@storybook/addon-storysource": "^8.2.8",
"@storybook/manager-api": "^8.0.10", "@storybook/manager-api": "^8.2.8",
"@storybook/react": "^8.0.10", "@storybook/react": "^8.2.8",
"@storybook/react-vite": "^8.0.10", "@storybook/react-vite": "^8.2.8",
"@storybook/theming": "^8.0.10", "@storybook/theming": "^8.2.8",
"@types/dateformat": "^5.0.2", "@types/dateformat": "^5.0.2",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.10", "@types/node": "^20.14.15",
"@types/react": "^18.3.1", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/uuid": "^9.0.8", "@types/uuid": "^10.0.0",
"@vitejs/plugin-react-swc": "^3.6.0", "@vitejs/plugin-react-swc": "^3.7.0",
"@vitest/coverage-v8": "^1.5.0", "@vitest/coverage-v8": "^1.5.0",
"@vitest/ui": "^1.5.0", "@vitest/ui": "^1.5.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dpdm": "^3.14.0", "dpdm": "^3.14.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-i18next": "^6.0.3", "eslint-plugin-i18next": "^6.0.9",
"eslint-plugin-path": "^1.3.0", "eslint-plugin-path": "^1.3.0",
"knip": "^5.12.3", "knip": "^5.27.2",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"openapi-typescript": "^6.7.5", "openapi-typescript": "^7.3.0",
"prettier": "^3.2.5", "prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"storybook": "^8.0.10", "storybook": "^8.2.8",
"ts-toolbelt": "^9.6.0", "ts-toolbelt": "^9.6.0",
"tsafe": "^1.6.6", "tsafe": "^1.7.2",
"typescript": "^5.4.5", "typescript": "^5.5.4",
"vite": "^5.2.11", "vite": "^5.4.0",
"vite-plugin-css-injected-by-js": "^3.5.1", "vite-plugin-css-injected-by-js": "^3.5.1",
"vite-plugin-dts": "^3.9.1", "vite-plugin-dts": "^3.9.1",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,40 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import fs from 'node:fs'; import fs from 'node:fs';
import openapiTS from 'openapi-typescript'; import openapiTS, { astToString } from 'openapi-typescript';
import ts from 'typescript';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json'; const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.ts'; const OUTPUT_FILE = 'src/services/api/schema.ts';
async function generateTypes(schema) { async function generateTypes(schema) {
process.stdout.write(`Generating types ${OUTPUT_FILE}...`); process.stdout.write(`Generating types ${OUTPUT_FILE}...`);
// Use https://ts-ast-viewer.com to figure out how to create these AST nodes - define a type and use the bottom-left pane's output
// `Blob` type
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Blob'));
// `null` type
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
// `Record<string, unknown>` type
const RECORD_STRING_UNKNOWN = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
]);
const types = await openapiTS(schema, { const types = await openapiTS(schema, {
exportType: true, exportType: true,
transform: (schemaObject) => { transform: (schemaObject) => {
if ('format' in schemaObject && schemaObject.format === 'binary') { if ('format' in schemaObject && schemaObject.format === 'binary') {
return schemaObject.nullable ? 'Blob | null' : 'Blob'; return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
} }
if (schemaObject.title === 'MetadataField') { if (schemaObject.title === 'MetadataField') {
// This is `Record<string, never>` by default, but it actually accepts any a dict of any valid JSON value. // This is `Record<string, never>` by default, but it actually accepts any a dict of any valid JSON value.
return 'Record<string, unknown>'; return RECORD_STRING_UNKNOWN;
} }
}, },
defaultNonNullable: false,
}); });
fs.writeFileSync(OUTPUT_FILE, types); fs.writeFileSync(OUTPUT_FILE, astToString(types));
process.stdout.write(`\nOK!\r\n`); process.stdout.write(`\nOK!\r\n`);
} }

View File

@ -57,7 +57,11 @@ export const UpscaleWarning = () => {
$installModelsTab.set(3); $installModelsTab.set(3);
}, [dispatch]); }, [dispatch]);
if ((!modelWarnings.length && !otherWarnings.length) || isLoading || !shouldShowButton) { if (modelWarnings.length && !shouldShowButton) {
return null;
}
if ((!modelWarnings.length && !otherWarnings.length) || isLoading) {
return null; return null;
} }

File diff suppressed because one or more lines are too long