mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge remote-tracking branch 'origin/main' into maryhipp/style-presets
This commit is contained in:
commit
9c732ac3b1
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
@ -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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 it is too large
Load Diff
Loading…
Reference in New Issue
Block a user