mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into export-more-for-header
This commit is contained in:
commit
1b215059e7
@ -17,7 +17,7 @@ notebooks.
|
|||||||
|
|
||||||
You will need a GPU to perform training in a reasonable length of
|
You will need a GPU to perform training in a reasonable length of
|
||||||
time, and at least 12 GB of VRAM. We recommend using the [`xformers`
|
time, and at least 12 GB of VRAM. We recommend using the [`xformers`
|
||||||
library](../installation/070_INSTALL_XFORMERS) to accelerate the
|
library](../installation/070_INSTALL_XFORMERS.md) to accelerate the
|
||||||
training process further. During training, about ~8 GB is temporarily
|
training process further. During training, about ~8 GB is temporarily
|
||||||
needed in order to store intermediate models, checkpoints and logs.
|
needed in order to store intermediate models, checkpoints and logs.
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ You need to have opencv installed so that pypatchmatch can be built:
|
|||||||
brew install opencv
|
brew install opencv
|
||||||
```
|
```
|
||||||
|
|
||||||
The next time you start `invoke`, after sucesfully installing opencv, pypatchmatch will be built.
|
The next time you start `invoke`, after successfully installing opencv, pypatchmatch will be built.
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ Prior to installing PyPatchMatch, you need to take the following steps:
|
|||||||
|
|
||||||
5. Confirm that pypatchmatch is installed. At the command-line prompt enter
|
5. Confirm that pypatchmatch is installed. At the command-line prompt enter
|
||||||
`python`, and then at the `>>>` line type
|
`python`, and then at the `>>>` line type
|
||||||
`from patchmatch import patch_match`: It should look like the follwing:
|
`from patchmatch import patch_match`: It should look like the following:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
Python 3.9.5 (default, Nov 23 2021, 15:27:38)
|
Python 3.9.5 (default, Nov 23 2021, 15:27:38)
|
||||||
@ -108,4 +108,4 @@ Prior to installing PyPatchMatch, you need to take the following steps:
|
|||||||
|
|
||||||
[**Next, Follow Steps 4-6 from the Debian Section above**](#linux)
|
[**Next, Follow Steps 4-6 from the Debian Section above**](#linux)
|
||||||
|
|
||||||
If you see no errors, then you're ready to go!
|
If you see no errors you're ready to go!
|
||||||
|
@ -4,6 +4,8 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any, Literal, Optional, Union
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from torch import Tensor
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from skimage.exposure.histogram_matching import match_histograms
|
from skimage.exposure.histogram_matching import match_histograms
|
||||||
@ -12,7 +14,9 @@ from ..services.image_storage import ImageType
|
|||||||
from ..services.invocation_services import InvocationServices
|
from ..services.invocation_services import InvocationServices
|
||||||
from .baseinvocation import BaseInvocation, InvocationContext
|
from .baseinvocation import BaseInvocation, InvocationContext
|
||||||
from .image import ImageField, ImageOutput
|
from .image import ImageField, ImageOutput
|
||||||
from ...backend.generator import Txt2Img, Img2Img, Inpaint, InvokeAIGenerator
|
from ...backend.generator import Txt2Img, Img2Img, Inpaint, InvokeAIGenerator, Generator
|
||||||
|
from ...backend.stable_diffusion import PipelineIntermediateState
|
||||||
|
from ...backend.util.util import image_to_dataURL
|
||||||
|
|
||||||
SAMPLER_NAME_VALUES = Literal[
|
SAMPLER_NAME_VALUES = Literal[
|
||||||
tuple(InvokeAIGenerator.schedulers())
|
tuple(InvokeAIGenerator.schedulers())
|
||||||
@ -41,18 +45,32 @@ class TextToImageInvocation(BaseInvocation):
|
|||||||
|
|
||||||
# TODO: pass this an emitter method or something? or a session for dispatching?
|
# TODO: pass this an emitter method or something? or a session for dispatching?
|
||||||
def dispatch_progress(
|
def dispatch_progress(
|
||||||
self, context: InvocationContext, sample: Any = None, step: int = 0
|
self, context: InvocationContext, sample: Tensor, step: int
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# TODO: only output a preview image when requested
|
||||||
|
image = Generator.sample_to_lowres_estimated_image(sample)
|
||||||
|
|
||||||
|
(width, height) = image.size
|
||||||
|
width *= 8
|
||||||
|
height *= 8
|
||||||
|
|
||||||
|
dataURL = image_to_dataURL(image, image_format="JPEG")
|
||||||
|
|
||||||
context.services.events.emit_generator_progress(
|
context.services.events.emit_generator_progress(
|
||||||
context.graph_execution_state_id,
|
context.graph_execution_state_id,
|
||||||
self.id,
|
self.id,
|
||||||
|
{
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
"dataURL": dataURL
|
||||||
|
},
|
||||||
step,
|
step,
|
||||||
float(step) / float(self.steps),
|
self.steps,
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
def step_callback(sample, step=0):
|
def step_callback(state: PipelineIntermediateState):
|
||||||
self.dispatch_progress(context, sample, step)
|
self.dispatch_progress(context, state.latents, state.step)
|
||||||
|
|
||||||
# Handle invalid model parameter
|
# Handle invalid model parameter
|
||||||
# TODO: figure out if this can be done via a validator that uses the model_cache
|
# TODO: figure out if this can be done via a validator that uses the model_cache
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
|
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, TypedDict
|
||||||
|
|
||||||
|
ProgressImage = TypedDict(
|
||||||
|
"ProgressImage", {"dataURL": str, "width": int, "height": int}
|
||||||
|
)
|
||||||
|
|
||||||
class EventServiceBase:
|
class EventServiceBase:
|
||||||
session_event: str = "session_event"
|
session_event: str = "session_event"
|
||||||
@ -23,8 +26,9 @@ class EventServiceBase:
|
|||||||
self,
|
self,
|
||||||
graph_execution_state_id: str,
|
graph_execution_state_id: str,
|
||||||
invocation_id: str,
|
invocation_id: str,
|
||||||
|
progress_image: ProgressImage | None,
|
||||||
step: int,
|
step: int,
|
||||||
percent: float,
|
total_steps: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Emitted when there is generation progress"""
|
"""Emitted when there is generation progress"""
|
||||||
self.__emit_session_event(
|
self.__emit_session_event(
|
||||||
@ -32,8 +36,9 @@ class EventServiceBase:
|
|||||||
payload=dict(
|
payload=dict(
|
||||||
graph_execution_state_id=graph_execution_state_id,
|
graph_execution_state_id=graph_execution_state_id,
|
||||||
invocation_id=invocation_id,
|
invocation_id=invocation_id,
|
||||||
|
progress_image=progress_image,
|
||||||
step=step,
|
step=step,
|
||||||
percent=percent,
|
total_steps=total_steps,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -773,6 +773,24 @@ class GraphExecutionState(BaseModel):
|
|||||||
default_factory=dict,
|
default_factory=dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Declare all fields as required; necessary for OpenAPI schema generation build.
|
||||||
|
# Technically only fields without a `default_factory` need to be listed here.
|
||||||
|
# See: https://github.com/pydantic/pydantic/discussions/4577
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
'required': [
|
||||||
|
'id',
|
||||||
|
'graph',
|
||||||
|
'execution_graph',
|
||||||
|
'executed',
|
||||||
|
'executed_history',
|
||||||
|
'results',
|
||||||
|
'errors',
|
||||||
|
'prepared_source_mapping',
|
||||||
|
'source_prepared_mapping',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def next(self) -> BaseInvocation | None:
|
def next(self) -> BaseInvocation | None:
|
||||||
"""Gets the next node ready to execute."""
|
"""Gets the next node ready to execute."""
|
||||||
|
|
||||||
|
@ -497,7 +497,8 @@ class Generator:
|
|||||||
matched_result.paste(init_image, (0, 0), mask=multiplied_blurred_init_mask)
|
matched_result.paste(init_image, (0, 0), mask=multiplied_blurred_init_mask)
|
||||||
return matched_result
|
return matched_result
|
||||||
|
|
||||||
def sample_to_lowres_estimated_image(self, samples):
|
@staticmethod
|
||||||
|
def sample_to_lowres_estimated_image(samples):
|
||||||
# origingally adapted from code by @erucipe and @keturn here:
|
# origingally adapted from code by @erucipe and @keturn here:
|
||||||
# https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/7
|
# https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/7
|
||||||
|
|
||||||
|
@ -3,6 +3,9 @@ import math
|
|||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
from collections import abc
|
from collections import abc
|
||||||
from inspect import isfunction
|
from inspect import isfunction
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -364,3 +367,16 @@ def url_attachment_name(url: str) -> dict:
|
|||||||
def download_with_progress_bar(url: str, dest: Path) -> bool:
|
def download_with_progress_bar(url: str, dest: Path) -> bool:
|
||||||
result = download_with_resume(url, dest, access_token=None)
|
result = download_with_resume(url, dest, access_token=None)
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
|
|
||||||
|
def image_to_dataURL(image: Image.Image, image_format: str = "PNG") -> str:
|
||||||
|
"""
|
||||||
|
Converts an image into a base64 image dataURL.
|
||||||
|
"""
|
||||||
|
buffered = io.BytesIO()
|
||||||
|
image.save(buffered, format=image_format)
|
||||||
|
mime_type = Image.MIME.get(image_format.upper(), "image/" + image_format.lower())
|
||||||
|
image_base64 = f"data:{mime_type};base64," + base64.b64encode(
|
||||||
|
buffered.getvalue()
|
||||||
|
).decode("UTF-8")
|
||||||
|
return image_base64
|
||||||
|
188
invokeai/frontend/web/dist/assets/App-16da1b5e.js
vendored
188
invokeai/frontend/web/dist/assets/App-16da1b5e.js
vendored
File diff suppressed because one or more lines are too long
188
invokeai/frontend/web/dist/assets/App-843b023b.js
vendored
Normal file
188
invokeai/frontend/web/dist/assets/App-843b023b.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
|||||||
import{j as y,cN as Ie,r as _,cO as bt,q as Lr,cP as o,cQ as b,cR as v,cS as S,cT as Vr,cU as ut,cV as vt,cM as ft,cW as mt,n as gt,cX as ht,E as pt}from"./index-d64f4654.js";import{d as yt,i as St,T as xt,j as $t,h as kt}from"./storeHooks-0eed8e9f.js";var Or=`
|
import{j as y,cN as Ie,r as _,cO as bt,q as Lr,cP as o,cQ as b,cR as v,cS as S,cT as Vr,cU as ut,cV as vt,cM as ft,cW as mt,n as gt,cX as ht,E as pt}from"./index-f7f41e1f.js";import{d as yt,i as St,T as xt,j as $t,h as kt}from"./storeHooks-eaf47ae3.js";var Or=`
|
||||||
:root {
|
:root {
|
||||||
--chakra-vh: 100vh;
|
--chakra-vh: 100vh;
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
invokeai/frontend/web/dist/index.html
vendored
2
invokeai/frontend/web/dist/index.html
vendored
@ -12,7 +12,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="./assets/index-d64f4654.js"></script>
|
<script type="module" crossorigin src="./assets/index-f7f41e1f.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index-5483945c.css">
|
<link rel="stylesheet" href="./assets/index-5483945c.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
3
invokeai/frontend/web/dist/locales/en.json
vendored
3
invokeai/frontend/web/dist/locales/en.json
vendored
@ -64,6 +64,8 @@
|
|||||||
"trainingDesc2": "InvokeAI already supports training custom embeddings using Textual Inversion using the main script.",
|
"trainingDesc2": "InvokeAI already supports training custom embeddings using Textual Inversion using the main script.",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"accept": "Accept",
|
||||||
"load": "Load",
|
"load": "Load",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"statusConnected": "Connected",
|
"statusConnected": "Connected",
|
||||||
@ -333,6 +335,7 @@
|
|||||||
"addNewModel": "Add New Model",
|
"addNewModel": "Add New Model",
|
||||||
"addCheckpointModel": "Add Checkpoint / Safetensor Model",
|
"addCheckpointModel": "Add Checkpoint / Safetensor Model",
|
||||||
"addDiffuserModel": "Add Diffusers",
|
"addDiffuserModel": "Add Diffusers",
|
||||||
|
"scanForModels": "Scan For Models",
|
||||||
"addManually": "Add Manually",
|
"addManually": "Add Manually",
|
||||||
"manual": "Manual",
|
"manual": "Manual",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@ -64,6 +64,8 @@
|
|||||||
"trainingDesc2": "InvokeAI already supports training custom embeddings using Textual Inversion using the main script.",
|
"trainingDesc2": "InvokeAI already supports training custom embeddings using Textual Inversion using the main script.",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"accept": "Accept",
|
||||||
"load": "Load",
|
"load": "Load",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"statusConnected": "Connected",
|
"statusConnected": "Connected",
|
||||||
@ -333,6 +335,7 @@
|
|||||||
"addNewModel": "Add New Model",
|
"addNewModel": "Add New Model",
|
||||||
"addCheckpointModel": "Add Checkpoint / Safetensor Model",
|
"addCheckpointModel": "Add Checkpoint / Safetensor Model",
|
||||||
"addDiffuserModel": "Add Diffusers",
|
"addDiffuserModel": "Add Diffusers",
|
||||||
|
"scanForModels": "Scan For Models",
|
||||||
"addManually": "Add Manually",
|
"addManually": "Add Manually",
|
||||||
"manual": "Manual",
|
"manual": "Manual",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@ -31,18 +31,14 @@ export const DIFFUSERS_SAMPLERS: Array<string> = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Valid image widths
|
// Valid image widths
|
||||||
export const WIDTHS: Array<number> = [
|
export const WIDTHS: Array<number> = Array.from(Array(65)).map(
|
||||||
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,
|
(_x, i) => i * 64
|
||||||
1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792,
|
);
|
||||||
1856, 1920, 1984, 2048,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Valid image heights
|
// Valid image heights
|
||||||
export const HEIGHTS: Array<number> = [
|
export const HEIGHTS: Array<number> = Array.from(Array(65)).map(
|
||||||
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,
|
(_x, i) => i * 64
|
||||||
1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792,
|
);
|
||||||
1856, 1920, 1984, 2048,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Valid upscaling levels
|
// Valid upscaling levels
|
||||||
export const UPSCALING_LEVELS: Array<{ key: string; value: number }> = [
|
export const UPSCALING_LEVELS: Array<{ key: string; value: number }> = [
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { cloneElement, memo, ReactElement, ReactNode, useRef } from 'react';
|
import { cloneElement, memo, ReactElement, ReactNode, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import IAIButton from './IAIButton';
|
import IAIButton from './IAIButton';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -22,10 +23,12 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
acceptButtonText = 'Accept',
|
acceptButtonText = t('common.accept'),
|
||||||
acceptCallback,
|
acceptCallback,
|
||||||
cancelButtonText = 'Cancel',
|
cancelButtonText = t('common.cancel'),
|
||||||
cancelCallback,
|
cancelCallback,
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
@ -56,6 +59,7 @@ const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
leastDestructiveRef={cancelRef}
|
leastDestructiveRef={cancelRef}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
isCentered
|
||||||
>
|
>
|
||||||
<AlertDialogOverlay>
|
<AlertDialogOverlay>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
|
8
invokeai/frontend/web/src/common/components/IAIForm.tsx
Normal file
8
invokeai/frontend/web/src/common/components/IAIForm.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { chakra } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chakra-enabled <form />
|
||||||
|
*/
|
||||||
|
const IAIForm = chakra.form;
|
||||||
|
|
||||||
|
export default IAIForm;
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
export function IAIFormItemWrapper({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactElement | ReactElement[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: 4,
|
||||||
|
rowGap: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
|
width: 'full',
|
||||||
|
bg: 'base.900',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
@ -104,7 +104,6 @@ const IAICanvasMaskOptions = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
|
@ -88,7 +88,7 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
isLazy={false}
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
tooltip={t('unifiedCanvas.canvasSettings')}
|
tooltip={t('unifiedCanvas.canvasSettings')}
|
||||||
|
@ -219,7 +219,6 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
onClick={handleSelectColorPickerTool}
|
onClick={handleSelectColorPickerTool}
|
||||||
/>
|
/>
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('unifiedCanvas.brushOptions')}
|
aria-label={t('unifiedCanvas.brushOptions')}
|
||||||
|
@ -405,7 +405,6 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
>
|
>
|
||||||
<ButtonGroup isAttached={true}>
|
<ButtonGroup isAttached={true}>
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={`${t('parameters.sendTo')}...`}
|
aria-label={`${t('parameters.sendTo')}...`}
|
||||||
@ -505,7 +504,6 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
|
|
||||||
<ButtonGroup isAttached={true}>
|
<ButtonGroup isAttached={true}>
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaGrinStars />}
|
icon={<FaGrinStars />}
|
||||||
@ -535,7 +533,6 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
</IAIPopover>
|
</IAIPopover>
|
||||||
|
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaExpandArrowsAlt />}
|
icon={<FaExpandArrowsAlt />}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
type CurrentImageFallbackProps = SpinnerProps;
|
||||||
|
|
||||||
|
const CurrentImageFallback = (props: CurrentImageFallbackProps) => {
|
||||||
|
const { size = 'xl', ...rest } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
color: 'base.400',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spinner size={size} {...rest} />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrentImageFallback;
|
@ -7,6 +7,7 @@ import { isEqual } from 'lodash';
|
|||||||
import { APP_METADATA_HEIGHT } from 'theme/util/constants';
|
import { APP_METADATA_HEIGHT } from 'theme/util/constants';
|
||||||
|
|
||||||
import { gallerySelector } from '../store/gallerySelectors';
|
import { gallerySelector } from '../store/gallerySelectors';
|
||||||
|
import CurrentImageFallback from './CurrentImageFallback';
|
||||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||||
import NextPrevImageButtons from './NextPrevImageButtons';
|
import NextPrevImageButtons from './NextPrevImageButtons';
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ export default function CurrentImagePreview() {
|
|||||||
src={imageToDisplay.url}
|
src={imageToDisplay.url}
|
||||||
width={imageToDisplay.width}
|
width={imageToDisplay.width}
|
||||||
height={imageToDisplay.height}
|
height={imageToDisplay.height}
|
||||||
|
fallback={!isIntermediate ? <CurrentImageFallback /> : undefined}
|
||||||
sx={{
|
sx={{
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
|
@ -55,7 +55,6 @@ export default function LanguagePicker() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('common.languagePickerLabel')}
|
aria-label={t('common.languagePickerLabel')}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Flex,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormErrorMessage,
|
FormErrorMessage,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
@ -25,10 +26,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import type { InvokeModelConfigProps } from 'app/invokeai';
|
import type { InvokeModelConfigProps } from 'app/invokeai';
|
||||||
import type { RootState } from 'app/store';
|
import type { RootState } from 'app/store';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
|
import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
|
||||||
import type { FieldInputProps, FormikProps } from 'formik';
|
import type { FieldInputProps, FormikProps } from 'formik';
|
||||||
import { BiArrowBack } from 'react-icons/bi';
|
import IAIForm from 'common/components/IAIForm';
|
||||||
|
import { IAIFormItemWrapper } from 'common/components/IAIForms/IAIFormItemWrapper';
|
||||||
|
|
||||||
const MIN_MODEL_SIZE = 64;
|
const MIN_MODEL_SIZE = 64;
|
||||||
const MAX_MODEL_SIZE = 2048;
|
const MAX_MODEL_SIZE = 2048;
|
||||||
@ -72,243 +73,250 @@ export default function AddCheckpointModel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack gap={2} alignItems="flex-start">
|
<VStack gap={2} alignItems="flex-start">
|
||||||
<IAIIconButton
|
<Flex columnGap={4}>
|
||||||
aria-label={t('common.back')}
|
<IAICheckbox
|
||||||
tooltip={t('common.back')}
|
isChecked={!addManually}
|
||||||
onClick={() => dispatch(setAddNewModelUIOption(null))}
|
label={t('modelManager.scanForModels')}
|
||||||
width="max-content"
|
onChange={() => setAddmanually(!addManually)}
|
||||||
position="absolute"
|
/>
|
||||||
zIndex={1}
|
<IAICheckbox
|
||||||
size="sm"
|
label={t('modelManager.addManually')}
|
||||||
insetInlineEnd={12}
|
isChecked={addManually}
|
||||||
top={3}
|
onChange={() => setAddmanually(!addManually)}
|
||||||
icon={<BiArrowBack />}
|
/>
|
||||||
/>
|
</Flex>
|
||||||
|
|
||||||
<SearchModels />
|
{addManually ? (
|
||||||
<IAICheckbox
|
|
||||||
label={t('modelManager.addManually')}
|
|
||||||
isChecked={addManually}
|
|
||||||
onChange={() => setAddmanually(!addManually)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{addManually && (
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={addModelFormValues}
|
initialValues={addModelFormValues}
|
||||||
onSubmit={addModelFormSubmitHandler}
|
onSubmit={addModelFormSubmitHandler}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, errors, touched }) => (
|
{({ handleSubmit, errors, touched }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<IAIForm onSubmit={handleSubmit} sx={{ w: 'full' }}>
|
||||||
<VStack rowGap={2}>
|
<VStack rowGap={2}>
|
||||||
<Text fontSize={20} fontWeight="bold" alignSelf="start">
|
<Text fontSize={20} fontWeight="bold" alignSelf="start">
|
||||||
{t('modelManager.manual')}
|
{t('modelManager.manual')}
|
||||||
</Text>
|
</Text>
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<FormControl
|
<IAIFormItemWrapper>
|
||||||
isInvalid={!!errors.name && touched.name}
|
<FormControl
|
||||||
isRequired
|
isInvalid={!!errors.name && touched.name}
|
||||||
>
|
isRequired
|
||||||
<FormLabel htmlFor="name" fontSize="sm">
|
>
|
||||||
{t('modelManager.name')}
|
<FormLabel htmlFor="name" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.name')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field
|
<VStack alignItems="start">
|
||||||
as={IAIInput}
|
<Field
|
||||||
id="name"
|
as={IAIInput}
|
||||||
name="name"
|
id="name"
|
||||||
type="text"
|
name="name"
|
||||||
validate={baseValidation}
|
type="text"
|
||||||
width="2xl"
|
validate={baseValidation}
|
||||||
/>
|
width="full"
|
||||||
{!!errors.name && touched.name ? (
|
/>
|
||||||
<FormErrorMessage>{errors.name}</FormErrorMessage>
|
{!!errors.name && touched.name ? (
|
||||||
) : (
|
<FormErrorMessage>{errors.name}</FormErrorMessage>
|
||||||
<FormHelperText margin={0}>
|
) : (
|
||||||
{t('modelManager.nameValidationMsg')}
|
<FormHelperText margin={0}>
|
||||||
</FormHelperText>
|
{t('modelManager.nameValidationMsg')}
|
||||||
)}
|
</FormHelperText>
|
||||||
</VStack>
|
)}
|
||||||
</FormControl>
|
</VStack>
|
||||||
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<FormControl
|
<IAIFormItemWrapper>
|
||||||
isInvalid={!!errors.description && touched.description}
|
<FormControl
|
||||||
isRequired
|
isInvalid={!!errors.description && touched.description}
|
||||||
>
|
isRequired
|
||||||
<FormLabel htmlFor="description" fontSize="sm">
|
>
|
||||||
{t('modelManager.description')}
|
<FormLabel htmlFor="description" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.description')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field
|
<VStack alignItems="start">
|
||||||
as={IAIInput}
|
<Field
|
||||||
id="description"
|
as={IAIInput}
|
||||||
name="description"
|
id="description"
|
||||||
type="text"
|
name="description"
|
||||||
width="2xl"
|
type="text"
|
||||||
/>
|
width="full"
|
||||||
{!!errors.description && touched.description ? (
|
/>
|
||||||
<FormErrorMessage>{errors.description}</FormErrorMessage>
|
{!!errors.description && touched.description ? (
|
||||||
) : (
|
<FormErrorMessage>
|
||||||
<FormHelperText margin={0}>
|
{errors.description}
|
||||||
{t('modelManager.descriptionValidationMsg')}
|
</FormErrorMessage>
|
||||||
</FormHelperText>
|
) : (
|
||||||
)}
|
<FormHelperText margin={0}>
|
||||||
</VStack>
|
{t('modelManager.descriptionValidationMsg')}
|
||||||
</FormControl>
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
{/* Config */}
|
{/* Config */}
|
||||||
<FormControl
|
<IAIFormItemWrapper>
|
||||||
isInvalid={!!errors.config && touched.config}
|
<FormControl
|
||||||
isRequired
|
isInvalid={!!errors.config && touched.config}
|
||||||
>
|
isRequired
|
||||||
<FormLabel htmlFor="config" fontSize="sm">
|
>
|
||||||
{t('modelManager.config')}
|
<FormLabel htmlFor="config" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.config')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field
|
<VStack alignItems="start">
|
||||||
as={IAIInput}
|
<Field
|
||||||
id="config"
|
as={IAIInput}
|
||||||
name="config"
|
id="config"
|
||||||
type="text"
|
name="config"
|
||||||
width="2xl"
|
type="text"
|
||||||
/>
|
width="full"
|
||||||
{!!errors.config && touched.config ? (
|
/>
|
||||||
<FormErrorMessage>{errors.config}</FormErrorMessage>
|
{!!errors.config && touched.config ? (
|
||||||
) : (
|
<FormErrorMessage>{errors.config}</FormErrorMessage>
|
||||||
<FormHelperText margin={0}>
|
) : (
|
||||||
{t('modelManager.configValidationMsg')}
|
<FormHelperText margin={0}>
|
||||||
</FormHelperText>
|
{t('modelManager.configValidationMsg')}
|
||||||
)}
|
</FormHelperText>
|
||||||
</VStack>
|
)}
|
||||||
</FormControl>
|
</VStack>
|
||||||
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
{/* Weights */}
|
{/* Weights */}
|
||||||
<FormControl
|
<IAIFormItemWrapper>
|
||||||
isInvalid={!!errors.weights && touched.weights}
|
<FormControl
|
||||||
isRequired
|
isInvalid={!!errors.weights && touched.weights}
|
||||||
>
|
isRequired
|
||||||
<FormLabel htmlFor="config" fontSize="sm">
|
>
|
||||||
{t('modelManager.modelLocation')}
|
<FormLabel htmlFor="config" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.modelLocation')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field
|
<VStack alignItems="start">
|
||||||
as={IAIInput}
|
<Field
|
||||||
id="weights"
|
as={IAIInput}
|
||||||
name="weights"
|
id="weights"
|
||||||
type="text"
|
name="weights"
|
||||||
width="2xl"
|
type="text"
|
||||||
/>
|
width="full"
|
||||||
{!!errors.weights && touched.weights ? (
|
/>
|
||||||
<FormErrorMessage>{errors.weights}</FormErrorMessage>
|
{!!errors.weights && touched.weights ? (
|
||||||
) : (
|
<FormErrorMessage>{errors.weights}</FormErrorMessage>
|
||||||
<FormHelperText margin={0}>
|
) : (
|
||||||
{t('modelManager.modelLocationValidationMsg')}
|
<FormHelperText margin={0}>
|
||||||
</FormHelperText>
|
{t('modelManager.modelLocationValidationMsg')}
|
||||||
)}
|
</FormHelperText>
|
||||||
</VStack>
|
)}
|
||||||
</FormControl>
|
</VStack>
|
||||||
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
{/* VAE */}
|
{/* VAE */}
|
||||||
<FormControl isInvalid={!!errors.vae && touched.vae}>
|
<IAIFormItemWrapper>
|
||||||
<FormLabel htmlFor="vae" fontSize="sm">
|
<FormControl isInvalid={!!errors.vae && touched.vae}>
|
||||||
{t('modelManager.vaeLocation')}
|
<FormLabel htmlFor="vae" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.vaeLocation')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field
|
<VStack alignItems="start">
|
||||||
as={IAIInput}
|
<Field
|
||||||
id="vae"
|
as={IAIInput}
|
||||||
name="vae"
|
id="vae"
|
||||||
type="text"
|
name="vae"
|
||||||
width="2xl"
|
type="text"
|
||||||
/>
|
width="full"
|
||||||
{!!errors.vae && touched.vae ? (
|
/>
|
||||||
<FormErrorMessage>{errors.vae}</FormErrorMessage>
|
{!!errors.vae && touched.vae ? (
|
||||||
) : (
|
<FormErrorMessage>{errors.vae}</FormErrorMessage>
|
||||||
<FormHelperText margin={0}>
|
) : (
|
||||||
{t('modelManager.vaeLocationValidationMsg')}
|
<FormHelperText margin={0}>
|
||||||
</FormHelperText>
|
{t('modelManager.vaeLocationValidationMsg')}
|
||||||
)}
|
</FormHelperText>
|
||||||
</VStack>
|
)}
|
||||||
</FormControl>
|
</VStack>
|
||||||
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
<HStack width="100%">
|
<HStack width="100%">
|
||||||
{/* Width */}
|
{/* Width */}
|
||||||
<FormControl isInvalid={!!errors.width && touched.width}>
|
<IAIFormItemWrapper>
|
||||||
<FormLabel htmlFor="width" fontSize="sm">
|
<FormControl isInvalid={!!errors.width && touched.width}>
|
||||||
{t('modelManager.width')}
|
<FormLabel htmlFor="width" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.width')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field id="width" name="width">
|
<VStack alignItems="start">
|
||||||
{({
|
<Field id="width" name="width">
|
||||||
field,
|
{({
|
||||||
form,
|
field,
|
||||||
}: {
|
form,
|
||||||
field: FieldInputProps<number>;
|
}: {
|
||||||
form: FormikProps<InvokeModelConfigProps>;
|
field: FieldInputProps<number>;
|
||||||
}) => (
|
form: FormikProps<InvokeModelConfigProps>;
|
||||||
<IAINumberInput
|
}) => (
|
||||||
id="width"
|
<IAINumberInput
|
||||||
name="width"
|
id="width"
|
||||||
min={MIN_MODEL_SIZE}
|
name="width"
|
||||||
max={MAX_MODEL_SIZE}
|
min={MIN_MODEL_SIZE}
|
||||||
step={64}
|
max={MAX_MODEL_SIZE}
|
||||||
width="90%"
|
step={64}
|
||||||
value={form.values.width}
|
value={form.values.width}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
form.setFieldValue(field.name, Number(value))
|
form.setFieldValue(field.name, Number(value))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
{!!errors.width && touched.width ? (
|
{!!errors.width && touched.width ? (
|
||||||
<FormErrorMessage>{errors.width}</FormErrorMessage>
|
<FormErrorMessage>{errors.width}</FormErrorMessage>
|
||||||
) : (
|
) : (
|
||||||
<FormHelperText margin={0}>
|
<FormHelperText margin={0}>
|
||||||
{t('modelManager.widthValidationMsg')}
|
{t('modelManager.widthValidationMsg')}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
{/* Height */}
|
{/* Height */}
|
||||||
<FormControl isInvalid={!!errors.height && touched.height}>
|
<IAIFormItemWrapper>
|
||||||
<FormLabel htmlFor="height" fontSize="sm">
|
<FormControl isInvalid={!!errors.height && touched.height}>
|
||||||
{t('modelManager.height')}
|
<FormLabel htmlFor="height" fontSize="sm">
|
||||||
</FormLabel>
|
{t('modelManager.height')}
|
||||||
<VStack alignItems="start">
|
</FormLabel>
|
||||||
<Field id="height" name="height">
|
<VStack alignItems="start">
|
||||||
{({
|
<Field id="height" name="height">
|
||||||
field,
|
{({
|
||||||
form,
|
field,
|
||||||
}: {
|
form,
|
||||||
field: FieldInputProps<number>;
|
}: {
|
||||||
form: FormikProps<InvokeModelConfigProps>;
|
field: FieldInputProps<number>;
|
||||||
}) => (
|
form: FormikProps<InvokeModelConfigProps>;
|
||||||
<IAINumberInput
|
}) => (
|
||||||
id="height"
|
<IAINumberInput
|
||||||
name="height"
|
id="height"
|
||||||
min={MIN_MODEL_SIZE}
|
name="height"
|
||||||
max={MAX_MODEL_SIZE}
|
min={MIN_MODEL_SIZE}
|
||||||
width="90%"
|
max={MAX_MODEL_SIZE}
|
||||||
step={64}
|
step={64}
|
||||||
value={form.values.height}
|
value={form.values.height}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
form.setFieldValue(field.name, Number(value))
|
form.setFieldValue(field.name, Number(value))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
{!!errors.height && touched.height ? (
|
{!!errors.height && touched.height ? (
|
||||||
<FormErrorMessage>{errors.height}</FormErrorMessage>
|
<FormErrorMessage>{errors.height}</FormErrorMessage>
|
||||||
) : (
|
) : (
|
||||||
<FormHelperText margin={0}>
|
<FormHelperText margin={0}>
|
||||||
{t('modelManager.heightValidationMsg')}
|
{t('modelManager.heightValidationMsg')}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
</IAIFormItemWrapper>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
<IAIButton
|
<IAIButton
|
||||||
@ -319,9 +327,11 @@ export default function AddCheckpointModel() {
|
|||||||
{t('modelManager.addModel')}
|
{t('modelManager.addModel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</VStack>
|
</VStack>
|
||||||
</form>
|
</IAIForm>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
) : (
|
||||||
|
<SearchModels />
|
||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
|
@ -11,36 +11,14 @@ import { InvokeDiffusersModelConfigProps } from 'app/invokeai';
|
|||||||
import { addNewModel } from 'app/socketio/actions';
|
import { addNewModel } from 'app/socketio/actions';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import IAIInput from 'common/components/IAIInput';
|
import IAIInput from 'common/components/IAIInput';
|
||||||
import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
|
import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
|
||||||
import { Field, Formik } from 'formik';
|
import { Field, Formik } from 'formik';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BiArrowBack } from 'react-icons/bi';
|
|
||||||
|
|
||||||
import type { RootState } from 'app/store';
|
import type { RootState } from 'app/store';
|
||||||
import type { ReactElement } from 'react';
|
import IAIForm from 'common/components/IAIForm';
|
||||||
|
import { IAIFormItemWrapper } from 'common/components/IAIForms/IAIFormItemWrapper';
|
||||||
function FormItemWrapper({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: ReactElement | ReactElement[];
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
flexDirection: 'column',
|
|
||||||
padding: 4,
|
|
||||||
rowGap: 4,
|
|
||||||
borderRadius: 'base',
|
|
||||||
width: 'full',
|
|
||||||
bg: 'base.900',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AddDiffusersModel() {
|
export default function AddDiffusersModel() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -89,26 +67,14 @@ export default function AddDiffusersModel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex>
|
<Flex>
|
||||||
<IAIIconButton
|
|
||||||
aria-label={t('common.back')}
|
|
||||||
tooltip={t('common.back')}
|
|
||||||
onClick={() => dispatch(setAddNewModelUIOption(null))}
|
|
||||||
width="max-content"
|
|
||||||
position="absolute"
|
|
||||||
zIndex={1}
|
|
||||||
size="sm"
|
|
||||||
insetInlineEnd={12}
|
|
||||||
top={3}
|
|
||||||
icon={<BiArrowBack />}
|
|
||||||
/>
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={addModelFormValues}
|
initialValues={addModelFormValues}
|
||||||
onSubmit={addModelFormSubmitHandler}
|
onSubmit={addModelFormSubmitHandler}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, errors, touched }) => (
|
{({ handleSubmit, errors, touched }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<IAIForm onSubmit={handleSubmit}>
|
||||||
<VStack rowGap={2}>
|
<VStack rowGap={2}>
|
||||||
<FormItemWrapper>
|
<IAIFormItemWrapper>
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<FormControl
|
<FormControl
|
||||||
isInvalid={!!errors.name && touched.name}
|
isInvalid={!!errors.name && touched.name}
|
||||||
@ -136,9 +102,9 @@ export default function AddDiffusersModel() {
|
|||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItemWrapper>
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
<FormItemWrapper>
|
<IAIFormItemWrapper>
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<FormControl
|
<FormControl
|
||||||
isInvalid={!!errors.description && touched.description}
|
isInvalid={!!errors.description && touched.description}
|
||||||
@ -165,9 +131,9 @@ export default function AddDiffusersModel() {
|
|||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItemWrapper>
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
<FormItemWrapper>
|
<IAIFormItemWrapper>
|
||||||
<Text fontWeight="bold" fontSize="sm">
|
<Text fontWeight="bold" fontSize="sm">
|
||||||
{t('modelManager.formMessageDiffusersModelLocation')}
|
{t('modelManager.formMessageDiffusersModelLocation')}
|
||||||
</Text>
|
</Text>
|
||||||
@ -226,9 +192,9 @@ export default function AddDiffusersModel() {
|
|||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItemWrapper>
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
<FormItemWrapper>
|
<IAIFormItemWrapper>
|
||||||
{/* VAE Path */}
|
{/* VAE Path */}
|
||||||
<Text fontWeight="bold">
|
<Text fontWeight="bold">
|
||||||
{t('modelManager.formMessageDiffusersVAELocation')}
|
{t('modelManager.formMessageDiffusersVAELocation')}
|
||||||
@ -290,13 +256,13 @@ export default function AddDiffusersModel() {
|
|||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItemWrapper>
|
</IAIFormItemWrapper>
|
||||||
|
|
||||||
<IAIButton type="submit" isLoading={isProcessing}>
|
<IAIButton type="submit" isLoading={isProcessing}>
|
||||||
{t('modelManager.addModel')}
|
{t('modelManager.addModel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</VStack>
|
</VStack>
|
||||||
</form>
|
</IAIForm>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
|
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaArrowLeft, FaPlus } from 'react-icons/fa';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -23,6 +23,7 @@ import type { RootState } from 'app/store';
|
|||||||
import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
|
import { setAddNewModelUIOption } from 'features/ui/store/uiSlice';
|
||||||
import AddCheckpointModel from './AddCheckpointModel';
|
import AddCheckpointModel from './AddCheckpointModel';
|
||||||
import AddDiffusersModel from './AddDiffusersModel';
|
import AddDiffusersModel from './AddDiffusersModel';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
|
||||||
function AddModelBox({
|
function AddModelBox({
|
||||||
text,
|
text,
|
||||||
@ -83,8 +84,22 @@ export default function AddModel() {
|
|||||||
closeOnOverlayClick={false}
|
closeOnOverlayClick={false}
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent margin="auto" paddingInlineEnd={4}>
|
<ModalContent margin="auto">
|
||||||
<ModalHeader>{t('modelManager.addNewModel')}</ModalHeader>
|
<ModalHeader>{t('modelManager.addNewModel')} </ModalHeader>
|
||||||
|
{addNewModelUIOption !== null && (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label={t('common.back')}
|
||||||
|
tooltip={t('common.back')}
|
||||||
|
onClick={() => dispatch(setAddNewModelUIOption(null))}
|
||||||
|
position="absolute"
|
||||||
|
variant="ghost"
|
||||||
|
zIndex={1}
|
||||||
|
size="sm"
|
||||||
|
insetInlineEnd={12}
|
||||||
|
top={2}
|
||||||
|
icon={<FaArrowLeft />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{addNewModelUIOption == null && (
|
{addNewModelUIOption == null && (
|
||||||
|
@ -28,6 +28,7 @@ import { isEqual, pickBy } from 'lodash';
|
|||||||
import ModelConvert from './ModelConvert';
|
import ModelConvert from './ModelConvert';
|
||||||
import IAIFormHelperText from 'common/components/IAIForms/IAIFormHelperText';
|
import IAIFormHelperText from 'common/components/IAIForms/IAIFormHelperText';
|
||||||
import IAIFormErrorMessage from 'common/components/IAIForms/IAIFormErrorMessage';
|
import IAIFormErrorMessage from 'common/components/IAIForms/IAIFormErrorMessage';
|
||||||
|
import IAIForm from 'common/components/IAIForm';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[systemSelector],
|
[systemSelector],
|
||||||
@ -120,7 +121,7 @@ export default function CheckpointModelEdit() {
|
|||||||
onSubmit={editModelFormSubmitHandler}
|
onSubmit={editModelFormSubmitHandler}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, errors, touched }) => (
|
{({ handleSubmit, errors, touched }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<IAIForm onSubmit={handleSubmit}>
|
||||||
<VStack rowGap={2} alignItems="start">
|
<VStack rowGap={2} alignItems="start">
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<FormControl
|
<FormControl
|
||||||
@ -317,7 +318,7 @@ export default function CheckpointModelEdit() {
|
|||||||
{t('modelManager.updateModel')}
|
{t('modelManager.updateModel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</VStack>
|
</VStack>
|
||||||
</form>
|
</IAIForm>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -18,6 +18,7 @@ import type { RootState } from 'app/store';
|
|||||||
import { isEqual, pickBy } from 'lodash';
|
import { isEqual, pickBy } from 'lodash';
|
||||||
import IAIFormHelperText from 'common/components/IAIForms/IAIFormHelperText';
|
import IAIFormHelperText from 'common/components/IAIForms/IAIFormHelperText';
|
||||||
import IAIFormErrorMessage from 'common/components/IAIForms/IAIFormErrorMessage';
|
import IAIFormErrorMessage from 'common/components/IAIForms/IAIFormErrorMessage';
|
||||||
|
import IAIForm from 'common/components/IAIForm';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[systemSelector],
|
[systemSelector],
|
||||||
@ -116,7 +117,7 @@ export default function DiffusersModelEdit() {
|
|||||||
onSubmit={editModelFormSubmitHandler}
|
onSubmit={editModelFormSubmitHandler}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, errors, touched }) => (
|
{({ handleSubmit, errors, touched }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<IAIForm onSubmit={handleSubmit}>
|
||||||
<VStack rowGap={2} alignItems="start">
|
<VStack rowGap={2} alignItems="start">
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<FormControl
|
<FormControl
|
||||||
@ -259,7 +260,7 @@ export default function DiffusersModelEdit() {
|
|||||||
{t('modelManager.updateModel')}
|
{t('modelManager.updateModel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</VStack>
|
</VStack>
|
||||||
</form>
|
</IAIForm>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -12,14 +12,13 @@ import {
|
|||||||
RadioGroup,
|
RadioGroup,
|
||||||
Spacer,
|
Spacer,
|
||||||
Text,
|
Text,
|
||||||
VStack,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { FaPlus, FaSearch } from 'react-icons/fa';
|
import { FaSearch, FaTrash } from 'react-icons/fa';
|
||||||
|
|
||||||
import { addNewModel, searchForModels } from 'app/socketio/actions';
|
import { addNewModel, searchForModels } from 'app/socketio/actions';
|
||||||
import {
|
import {
|
||||||
@ -34,7 +33,7 @@ import IAIInput from 'common/components/IAIInput';
|
|||||||
import { Field, Formik } from 'formik';
|
import { Field, Formik } from 'formik';
|
||||||
import { forEach, remove } from 'lodash';
|
import { forEach, remove } from 'lodash';
|
||||||
import type { ChangeEvent, ReactNode } from 'react';
|
import type { ChangeEvent, ReactNode } from 'react';
|
||||||
import { BiReset } from 'react-icons/bi';
|
import IAIForm from 'common/components/IAIForm';
|
||||||
|
|
||||||
const existingModelsSelector = createSelector([systemSelector], (system) => {
|
const existingModelsSelector = createSelector([systemSelector], (system) => {
|
||||||
const { model_list } = system;
|
const { model_list } = system;
|
||||||
@ -71,34 +70,32 @@ function SearchModelEntry({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack>
|
<Flex
|
||||||
<Flex
|
flexDirection="column"
|
||||||
flexDirection="column"
|
gap={2}
|
||||||
gap={2}
|
backgroundColor={
|
||||||
backgroundColor={
|
modelsToAdd.includes(model.name) ? 'accent.650' : 'base.800'
|
||||||
modelsToAdd.includes(model.name) ? 'accent.650' : 'base.800'
|
}
|
||||||
}
|
paddingX={4}
|
||||||
paddingX={4}
|
paddingY={2}
|
||||||
paddingY={2}
|
borderRadius={4}
|
||||||
borderRadius={4}
|
>
|
||||||
>
|
<Flex gap={4} alignItems="center" justifyContent="space-between">
|
||||||
<Flex gap={4}>
|
<IAICheckbox
|
||||||
<IAICheckbox
|
value={model.name}
|
||||||
value={model.name}
|
label={<Text fontWeight={500}>{model.name}</Text>}
|
||||||
label={<Text fontWeight={500}>{model.name}</Text>}
|
isChecked={modelsToAdd.includes(model.name)}
|
||||||
isChecked={modelsToAdd.includes(model.name)}
|
isDisabled={existingModels.includes(model.location)}
|
||||||
isDisabled={existingModels.includes(model.location)}
|
onChange={foundModelsChangeHandler}
|
||||||
onChange={foundModelsChangeHandler}
|
></IAICheckbox>
|
||||||
></IAICheckbox>
|
{existingModels.includes(model.location) && (
|
||||||
{existingModels.includes(model.location) && (
|
<Badge colorScheme="accent">{t('modelManager.modelExists')}</Badge>
|
||||||
<Badge colorScheme="accent">{t('modelManager.modelExists')}</Badge>
|
)}
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Text fontStyle="italic" variant="subtext">
|
|
||||||
{model.location}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</VStack>
|
<Text fontStyle="italic" variant="subtext">
|
||||||
|
{model.location}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,10 +212,10 @@ export default function SearchModels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex flexDirection="column" rowGap={4}>
|
||||||
{newFoundModels}
|
{newFoundModels}
|
||||||
{shouldShowExistingModelsInSearch && existingFoundModels}
|
{shouldShowExistingModelsInSearch && existingFoundModels}
|
||||||
</>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -245,26 +242,26 @@ export default function SearchModels() {
|
|||||||
<Text
|
<Text
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
fontSize: 'sm',
|
|
||||||
}}
|
}}
|
||||||
variant="subtext"
|
variant="subtext"
|
||||||
>
|
>
|
||||||
{t('modelManager.checkpointFolder')}
|
{t('modelManager.checkpointFolder')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text sx={{ fontWeight: 500, fontSize: 'sm' }}>{searchFolder}</Text>
|
<Text sx={{ fontWeight: 500 }}>{searchFolder}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('modelManager.scanAgain')}
|
aria-label={t('modelManager.scanAgain')}
|
||||||
tooltip={t('modelManager.scanAgain')}
|
tooltip={t('modelManager.scanAgain')}
|
||||||
icon={<BiReset />}
|
icon={<FaSearch />}
|
||||||
fontSize={18}
|
fontSize={18}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
onClick={() => dispatch(searchForModels(searchFolder))}
|
onClick={() => dispatch(searchForModels(searchFolder))}
|
||||||
/>
|
/>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('modelManager.clearCheckpointFolder')}
|
aria-label={t('modelManager.clearCheckpointFolder')}
|
||||||
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
|
tooltip={t('modelManager.clearCheckpointFolder')}
|
||||||
|
icon={<FaTrash />}
|
||||||
onClick={resetSearchModelHandler}
|
onClick={resetSearchModelHandler}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -276,9 +273,9 @@ export default function SearchModels() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ handleSubmit }) => (
|
{({ handleSubmit }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<IAIForm onSubmit={handleSubmit} width="100%">
|
||||||
<HStack columnGap={2} alignItems="flex-end" width="100%">
|
<HStack columnGap={2} alignItems="flex-end">
|
||||||
<FormControl isRequired width="lg">
|
<FormControl flexGrow={1}>
|
||||||
<Field
|
<Field
|
||||||
as={IAIInput}
|
as={IAIInput}
|
||||||
id="checkpointFolder"
|
id="checkpointFolder"
|
||||||
@ -294,12 +291,12 @@ export default function SearchModels() {
|
|||||||
tooltip={t('modelManager.findModels')}
|
tooltip={t('modelManager.findModels')}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
paddingX={10}
|
px={8}
|
||||||
>
|
>
|
||||||
{t('modelManager.findModels')}
|
{t('modelManager.findModels')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</HStack>
|
</HStack>
|
||||||
</form>
|
</IAIForm>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
)}
|
)}
|
||||||
@ -410,7 +407,6 @@ export default function SearchModels() {
|
|||||||
maxHeight={72}
|
maxHeight={72}
|
||||||
overflowY="scroll"
|
overflowY="scroll"
|
||||||
borderRadius="sm"
|
borderRadius="sm"
|
||||||
paddingInlineEnd={4}
|
|
||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
{foundModels.length > 0 ? (
|
{foundModels.length > 0 ? (
|
||||||
|
@ -50,7 +50,6 @@ export default function ThemeChanger() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('common.themeLabel')}
|
aria-label={t('common.themeLabel')}
|
||||||
|
@ -166,20 +166,8 @@ export default function InvokeTabs() {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* isLazy means the tabs are mounted and unmounted when changing them. There is a tradeoff here,
|
|
||||||
* as mounting is expensive, but so is retaining all tabs in the DOM at all times.
|
|
||||||
*
|
|
||||||
* Removing isLazy messes with the outside click watcher, which is used by ResizableDrawer.
|
|
||||||
* Because you have multiple handlers listening for an outside click, any click anywhere triggers
|
|
||||||
* the watcher for the hidden drawers, closing the open drawer.
|
|
||||||
*
|
|
||||||
* TODO: Add logic to the `useOutsideClick` in ResizableDrawer to enable it only for the active
|
|
||||||
* tab's drawer.
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
isLazy
|
|
||||||
defaultIndex={activeTab}
|
defaultIndex={activeTab}
|
||||||
index={activeTab}
|
index={activeTab}
|
||||||
onChange={(index: number) => {
|
onChange={(index: number) => {
|
||||||
|
@ -93,12 +93,9 @@ const ResizableDrawer = ({
|
|||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
ref: outsideClickRef,
|
ref: outsideClickRef,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
if (isPinned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
|
enabled: isOpen && !isPinned,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleEnables = useMemo(
|
const handleEnables = useMemo(
|
||||||
|
@ -77,7 +77,6 @@ export default function UnifiedCanvasColorPicker() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -56,7 +56,7 @@ const UnifiedCanvasSettings = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
isLazy={false}
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
tooltip={t('unifiedCanvas.canvasSettings')}
|
tooltip={t('unifiedCanvas.canvasSettings')}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -38,7 +38,7 @@ dependencies = [
|
|||||||
"albumentations",
|
"albumentations",
|
||||||
"click",
|
"click",
|
||||||
"clip_anytorch", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip",
|
"clip_anytorch", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip",
|
||||||
"compel==0.1.10",
|
"compel==1.0.1",
|
||||||
"datasets",
|
"datasets",
|
||||||
"diffusers[torch]~=0.14",
|
"diffusers[torch]~=0.14",
|
||||||
"dnspython==2.2.1",
|
"dnspython==2.2.1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user