update
This commit is contained in:
parent
371db2d771
commit
c55a7b566f
@ -6,8 +6,6 @@ import warnings
|
|||||||
|
|
||||||
warnings.simplefilter("ignore", UserWarning)
|
warnings.simplefilter("ignore", UserWarning)
|
||||||
|
|
||||||
from lama_cleaner.parse_args import parse_args
|
|
||||||
|
|
||||||
|
|
||||||
def entry_point():
|
def entry_point():
|
||||||
# To make os.environ["XDG_CACHE_HOME"] = args.model_cache_dir works for diffusers
|
# To make os.environ["XDG_CACHE_HOME"] = args.model_cache_dir works for diffusers
|
||||||
|
@ -23,14 +23,6 @@ AVAILABLE_MODELS = [
|
|||||||
"fcf",
|
"fcf",
|
||||||
"manga",
|
"manga",
|
||||||
"cv2",
|
"cv2",
|
||||||
"sd1.5",
|
|
||||||
"anything4",
|
|
||||||
"realisticVision1.4",
|
|
||||||
"sd2",
|
|
||||||
"sdxl",
|
|
||||||
"paint_by_example",
|
|
||||||
"instruct_pix2pix",
|
|
||||||
"kandinsky2.2",
|
|
||||||
]
|
]
|
||||||
DIFFUSERS_MODEL_FP16_REVERSION = [
|
DIFFUSERS_MODEL_FP16_REVERSION = [
|
||||||
"runwayml/stable-diffusion-inpainting",
|
"runwayml/stable-diffusion-inpainting",
|
||||||
|
@ -41,27 +41,6 @@ def folder_name_to_show_name(name: str) -> str:
|
|||||||
return name.replace("models--", "").replace("--", "/")
|
return name.replace("models--", "").replace("--", "/")
|
||||||
|
|
||||||
|
|
||||||
def scan_diffusers_models(
|
|
||||||
cache_dir, class_name: List[str], model_type: ModelType
|
|
||||||
) -> List[ModelInfo]:
|
|
||||||
cache_dir = Path(cache_dir)
|
|
||||||
res = []
|
|
||||||
for it in cache_dir.glob("**/*/model_index.json"):
|
|
||||||
with open(it, "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
if data["_class_name"] in class_name:
|
|
||||||
name = folder_name_to_show_name(it.parent.parent.parent.name)
|
|
||||||
if name not in res:
|
|
||||||
res.append(
|
|
||||||
ModelInfo(
|
|
||||||
name=name,
|
|
||||||
path=name,
|
|
||||||
model_type=model_type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def scan_single_file_diffusion_models(cache_dir) -> List[ModelInfo]:
|
def scan_single_file_diffusion_models(cache_dir) -> List[ModelInfo]:
|
||||||
cache_dir = Path(cache_dir)
|
cache_dir = Path(cache_dir)
|
||||||
res = []
|
res = []
|
||||||
@ -111,7 +90,6 @@ def scan_models() -> List[ModelInfo]:
|
|||||||
available_models = []
|
available_models = []
|
||||||
available_models.extend(scan_inpaint_models())
|
available_models.extend(scan_inpaint_models())
|
||||||
available_models.extend(scan_single_file_diffusion_models(DEFAULT_MODEL_DIR))
|
available_models.extend(scan_single_file_diffusion_models(DEFAULT_MODEL_DIR))
|
||||||
|
|
||||||
cache_dir = Path(DIFFUSERS_CACHE)
|
cache_dir = Path(DIFFUSERS_CACHE)
|
||||||
diffusers_model_names = []
|
diffusers_model_names = []
|
||||||
for it in cache_dir.glob("**/*/model_index.json"):
|
for it in cache_dir.glob("**/*/model_index.json"):
|
||||||
|
@ -65,5 +65,5 @@ class Kandinsky(DiffusionInpaintModel):
|
|||||||
|
|
||||||
|
|
||||||
class Kandinsky22(Kandinsky):
|
class Kandinsky22(Kandinsky):
|
||||||
name = "kandinsky2.2"
|
name = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
|
||||||
model_id_or_path = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
|
model_id_or_path = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
|
||||||
|
@ -38,12 +38,6 @@ class PaintByExample(DiffusionInpaintModel):
|
|||||||
else:
|
else:
|
||||||
self.model = self.model.to(device)
|
self.model = self.model.to(device)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def download():
|
|
||||||
from diffusers import DiffusionPipeline
|
|
||||||
|
|
||||||
DiffusionPipeline.from_pretrained("Fantasy-Studio/Paint-by-Example")
|
|
||||||
|
|
||||||
def forward(self, image, mask, config: Config):
|
def forward(self, image, mask, config: Config):
|
||||||
"""Input image and output image have same size
|
"""Input image and output image have same size
|
||||||
image: [H, W, C] RGB
|
image: [H, W, C] RGB
|
||||||
|
@ -22,20 +22,11 @@ class ModelManager:
|
|||||||
self.sd_controlnet_method = ""
|
self.sd_controlnet_method = ""
|
||||||
self.model = self.init_model(name, device, **kwargs)
|
self.model = self.init_model(name, device, **kwargs)
|
||||||
|
|
||||||
def _map_old_name(self, name: str) -> str:
|
|
||||||
for old_name, model_cls in models.items():
|
|
||||||
if name == old_name and hasattr(model_cls, "model_id_or_path"):
|
|
||||||
name = model_cls.model_id_or_path
|
|
||||||
break
|
|
||||||
return name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_model(self) -> Dict:
|
def current_model(self) -> Dict:
|
||||||
name = self._map_old_name(self.name)
|
|
||||||
return self.available_models[name].model_dump()
|
return self.available_models[name].model_dump()
|
||||||
|
|
||||||
def init_model(self, name: str, device, **kwargs):
|
def init_model(self, name: str, device, **kwargs):
|
||||||
name = self._map_old_name(name)
|
|
||||||
logger.info(f"Loading model: {name}")
|
logger.info(f"Loading model: {name}")
|
||||||
if name not in self.available_models:
|
if name not in self.available_models:
|
||||||
raise NotImplementedError(f"Unsupported model: {name}")
|
raise NotImplementedError(f"Unsupported model: {name}")
|
||||||
|
@ -1,257 +0,0 @@
|
|||||||
import os
|
|
||||||
import imghdr
|
|
||||||
import argparse
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lama_cleaner.const import *
|
|
||||||
from lama_cleaner.download import cli_download_model, scan_models
|
|
||||||
from lama_cleaner.runtime import dump_environment_info
|
|
||||||
|
|
||||||
DOWNLOAD_SUBCOMMAND = "download"
|
|
||||||
|
|
||||||
|
|
||||||
def download_parse_args(parser):
|
|
||||||
subparsers = parser.add_subparsers(dest="subcommand")
|
|
||||||
subparser = subparsers.add_parser(DOWNLOAD_SUBCOMMAND, help="Download models")
|
|
||||||
subparser.add_argument(
|
|
||||||
"--model", help="Erase model name(lama/mat...) or model id on huggingface"
|
|
||||||
)
|
|
||||||
subparser.add_argument(
|
|
||||||
"--model-dir", type=str, default=DEFAULT_MODEL_DIR, help=MODEL_DIR_HELP
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
|
||||||
)
|
|
||||||
download_parse_args(parser)
|
|
||||||
|
|
||||||
parser.add_argument("--host", default="127.0.0.1")
|
|
||||||
parser.add_argument("--port", default=8080, type=int)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--config-installer",
|
|
||||||
action="store_true",
|
|
||||||
help="Open config web page, mainly for windows installer",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--load-installer-config",
|
|
||||||
action="store_true",
|
|
||||||
help="Load all cmd args from installer config file",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--installer-config", default=None, help="Config file for windows installer"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--model",
|
|
||||||
default=DEFAULT_MODEL,
|
|
||||||
help=f"Available models: [{', '.join(AVAILABLE_MODELS)}], or model id on huggingface",
|
|
||||||
)
|
|
||||||
parser.add_argument("--no-half", action="store_true", help=NO_HALF_HELP)
|
|
||||||
parser.add_argument("--cpu-offload", action="store_true", help=CPU_OFFLOAD_HELP)
|
|
||||||
parser.add_argument("--disable-nsfw", action="store_true", help=DISABLE_NSFW_HELP)
|
|
||||||
parser.add_argument(
|
|
||||||
"--sd-cpu-textencoder", action="store_true", help=CPU_TEXTENCODER_HELP
|
|
||||||
)
|
|
||||||
parser.add_argument("--sd-controlnet", action="store_true", help=SD_CONTROLNET_HELP)
|
|
||||||
parser.add_argument(
|
|
||||||
"--sd-controlnet-method",
|
|
||||||
default=DEFAULT_SD_CONTROLNET_METHOD,
|
|
||||||
choices=SD_CONTROLNET_CHOICES,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--local-files-only", action="store_true", help=LOCAL_FILES_ONLY_HELP
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--device", default=DEFAULT_DEVICE, type=str, choices=AVAILABLE_DEVICES
|
|
||||||
)
|
|
||||||
parser.add_argument("--gui", action="store_true", help=GUI_HELP)
|
|
||||||
parser.add_argument(
|
|
||||||
"--gui-size",
|
|
||||||
default=[1600, 1000],
|
|
||||||
nargs=2,
|
|
||||||
type=int,
|
|
||||||
help="Set window size for GUI",
|
|
||||||
)
|
|
||||||
parser.add_argument("--input", type=str, default=None, help=INPUT_HELP)
|
|
||||||
parser.add_argument("--output-dir", type=str, default=None, help=OUTPUT_DIR_HELP)
|
|
||||||
parser.add_argument(
|
|
||||||
"--model-dir", type=str, default=DEFAULT_MODEL_DIR, help=MODEL_DIR_HELP
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--disable-model-switch",
|
|
||||||
action="store_true",
|
|
||||||
help="Disable model switch in frontend",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--quality",
|
|
||||||
default=95,
|
|
||||||
type=int,
|
|
||||||
help=QUALITY_HELP,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Plugins
|
|
||||||
parser.add_argument(
|
|
||||||
"--enable-interactive-seg",
|
|
||||||
action="store_true",
|
|
||||||
help=INTERACTIVE_SEG_HELP,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--interactive-seg-model",
|
|
||||||
default="vit_l",
|
|
||||||
choices=AVAILABLE_INTERACTIVE_SEG_MODELS,
|
|
||||||
help=INTERACTIVE_SEG_MODEL_HELP,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--interactive-seg-device",
|
|
||||||
default="cpu",
|
|
||||||
choices=AVAILABLE_INTERACTIVE_SEG_DEVICES,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--enable-remove-bg",
|
|
||||||
action="store_true",
|
|
||||||
help=REMOVE_BG_HELP,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--enable-anime-seg",
|
|
||||||
action="store_true",
|
|
||||||
help=ANIMESEG_HELP,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--enable-realesrgan",
|
|
||||||
action="store_true",
|
|
||||||
help=REALESRGAN_HELP,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--realesrgan-device",
|
|
||||||
default="cpu",
|
|
||||||
type=str,
|
|
||||||
choices=REALESRGAN_AVAILABLE_DEVICES,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--realesrgan-model",
|
|
||||||
default=RealESRGANModelName.realesr_general_x4v3.value,
|
|
||||||
type=str,
|
|
||||||
choices=RealESRGANModelNameList,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--realesrgan-no-half",
|
|
||||||
action="store_true",
|
|
||||||
help="Disable half precision for RealESRGAN",
|
|
||||||
)
|
|
||||||
parser.add_argument("--enable-gfpgan", action="store_true", help=GFPGAN_HELP)
|
|
||||||
parser.add_argument(
|
|
||||||
"--gfpgan-device", default="cpu", type=str, choices=GFPGAN_AVAILABLE_DEVICES
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--enable-restoreformer", action="store_true", help=RESTOREFORMER_HELP
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--restoreformer-device",
|
|
||||||
default="cpu",
|
|
||||||
type=str,
|
|
||||||
choices=RESTOREFORMER_AVAILABLE_DEVICES,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--install-plugins-package",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
#########
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
# collect system info to help debug
|
|
||||||
dump_environment_info()
|
|
||||||
if args.subcommand == DOWNLOAD_SUBCOMMAND:
|
|
||||||
cli_download_model(args.model, args.model_dir)
|
|
||||||
return
|
|
||||||
|
|
||||||
if args.install_plugins_package:
|
|
||||||
from lama_cleaner.installer import install_plugins_package
|
|
||||||
|
|
||||||
install_plugins_package()
|
|
||||||
exit()
|
|
||||||
|
|
||||||
if args.config_installer:
|
|
||||||
if args.installer_config is None:
|
|
||||||
parser.error(
|
|
||||||
"args.config_installer==True, must set args.installer_config to store config file"
|
|
||||||
)
|
|
||||||
from lama_cleaner.web_config import main
|
|
||||||
|
|
||||||
logger.info("Launching installer web config page")
|
|
||||||
main(args.installer_config)
|
|
||||||
exit()
|
|
||||||
|
|
||||||
if args.load_installer_config:
|
|
||||||
if args.installer_config and not os.path.exists(args.installer_config):
|
|
||||||
parser.error(f"args.installer_config={args.installer_config} not exists")
|
|
||||||
|
|
||||||
logger.info(f"Loading installer config from {args.installer_config}")
|
|
||||||
_args = load_config(args.installer_config)
|
|
||||||
for k, v in vars(_args).items():
|
|
||||||
if k in vars(args):
|
|
||||||
setattr(args, k, v)
|
|
||||||
|
|
||||||
if args.device == "cuda":
|
|
||||||
import platform
|
|
||||||
|
|
||||||
if platform.system() == "Darwin":
|
|
||||||
logger.info("MacOS does not support cuda, use cpu instead")
|
|
||||||
setattr(args, "device", "cpu")
|
|
||||||
else:
|
|
||||||
import torch
|
|
||||||
|
|
||||||
if torch.cuda.is_available() is False:
|
|
||||||
parser.error(
|
|
||||||
"torch.cuda.is_available() is False, please use --device cpu or check your pytorch installation"
|
|
||||||
)
|
|
||||||
|
|
||||||
os.environ["U2NET_HOME"] = DEFAULT_MODEL_DIR
|
|
||||||
if args.model_dir and args.model_dir is not None:
|
|
||||||
if os.path.isfile(args.model_dir):
|
|
||||||
parser.error(f"invalid --model-dir: {args.model_dir} is a file")
|
|
||||||
|
|
||||||
if not os.path.exists(args.model_dir):
|
|
||||||
logger.info(f"Create model cache directory: {args.model_dir}")
|
|
||||||
Path(args.model_dir).mkdir(exist_ok=True, parents=True)
|
|
||||||
|
|
||||||
os.environ["XDG_CACHE_HOME"] = args.model_dir
|
|
||||||
os.environ["U2NET_HOME"] = args.model_dir
|
|
||||||
|
|
||||||
if args.sd_run_local or args.local_files_only:
|
|
||||||
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
|
||||||
os.environ["HF_HUB_OFFLINE"] = "1"
|
|
||||||
|
|
||||||
if args.model not in AVAILABLE_MODELS:
|
|
||||||
scanned_models = scan_models()
|
|
||||||
if args.model not in [it.name for it in scanned_models]:
|
|
||||||
parser.error(
|
|
||||||
f"invalid --model: {args.model} not exists. Available models: {AVAILABLE_MODELS} or {[it.name for it in scanned_models]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.input and args.input is not None:
|
|
||||||
if not os.path.exists(args.input):
|
|
||||||
parser.error(f"invalid --input: {args.input} not exists")
|
|
||||||
if os.path.isfile(args.input):
|
|
||||||
if imghdr.what(args.input) is None:
|
|
||||||
parser.error(f"invalid --input: {args.input} is not a valid image file")
|
|
||||||
else:
|
|
||||||
if args.output_dir is None:
|
|
||||||
parser.error(
|
|
||||||
f"invalid --input: {args.input} is a directory, --output-dir is required"
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.output_dir is not None:
|
|
||||||
output_dir = Path(args.output_dir)
|
|
||||||
if not output_dir.exists():
|
|
||||||
logger.info(f"Creating output directory: {output_dir}")
|
|
||||||
output_dir.mkdir(parents=True)
|
|
||||||
else:
|
|
||||||
if not output_dir.is_dir():
|
|
||||||
parser.error(f"invalid --output-dir: {output_dir} is not a directory")
|
|
||||||
|
|
||||||
return args
|
|
@ -585,7 +585,7 @@ def start(
|
|||||||
port: int = Option(8080),
|
port: int = Option(8080),
|
||||||
model: str = Option(
|
model: str = Option(
|
||||||
DEFAULT_MODEL,
|
DEFAULT_MODEL,
|
||||||
help=f"Available models: [{', '.join(AVAILABLE_MODELS)}]. "
|
help=f"Available erase models: [{', '.join(AVAILABLE_MODELS)}]. "
|
||||||
f"You can use download command to download other SD/SDXL normal/inpainting models on huggingface",
|
f"You can use download command to download other SD/SDXL normal/inpainting models on huggingface",
|
||||||
),
|
),
|
||||||
model_dir: Path = Option(
|
model_dir: Path = Option(
|
||||||
@ -644,13 +644,12 @@ def start(
|
|||||||
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
||||||
os.environ["HF_HUB_OFFLINE"] = "1"
|
os.environ["HF_HUB_OFFLINE"] = "1"
|
||||||
|
|
||||||
if model not in AVAILABLE_MODELS:
|
scanned_models = scan_models()
|
||||||
scanned_models = scan_models()
|
if model not in [it.name for it in scanned_models]:
|
||||||
if model not in [it.name for it in scanned_models]:
|
logger.error(
|
||||||
logger.error(
|
f"invalid model: {model} not exists. Available models: {[it.name for it in scanned_models]}"
|
||||||
f"invalid --model: {model} not exists. Available models: {AVAILABLE_MODELS} or {[it.name for it in scanned_models]}"
|
)
|
||||||
)
|
exit()
|
||||||
exit()
|
|
||||||
|
|
||||||
global_config.image_quality = quality
|
global_config.image_quality = quality
|
||||||
global_config.disable_model_switch = disable_model_switch
|
global_config.disable_model_switch = disable_model_switch
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
torch>=1.9.0
|
torch>=2.0.0
|
||||||
|
typer
|
||||||
opencv-python
|
opencv-python
|
||||||
flask==2.2.3
|
flask==2.2.3
|
||||||
flask-socketio
|
flask-socketio
|
||||||
|
@ -3,7 +3,7 @@ import { nanoid } from "nanoid"
|
|||||||
|
|
||||||
import useInputImage from "@/hooks/useInputImage"
|
import useInputImage from "@/hooks/useInputImage"
|
||||||
import { keepGUIAlive } from "@/lib/utils"
|
import { keepGUIAlive } from "@/lib/utils"
|
||||||
import { getServerConfig, isDesktop } from "@/lib/api"
|
import { getServerConfig } from "@/lib/api"
|
||||||
import Header from "@/components/Header"
|
import Header from "@/components/Header"
|
||||||
import Workspace from "@/components/Workspace"
|
import Workspace from "@/components/Workspace"
|
||||||
import FileSelect from "@/components/FileSelect"
|
import FileSelect from "@/components/FileSelect"
|
||||||
@ -40,21 +40,14 @@ function Home() {
|
|||||||
updateAppState({ windowSize })
|
updateAppState({ windowSize })
|
||||||
}, [windowSize])
|
}, [windowSize])
|
||||||
|
|
||||||
// Keeping GUI Window Open
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
const isRunDesktop = await isDesktop().then((res) => res.text())
|
|
||||||
if (isRunDesktop === "True") {
|
|
||||||
keepGUIAlive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchData()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchServerConfig = async () => {
|
const fetchServerConfig = async () => {
|
||||||
const serverConfig = await getServerConfig().then((res) => res.json())
|
const serverConfig = await getServerConfig().then((res) => res.json())
|
||||||
setServerConfig(serverConfig)
|
setServerConfig(serverConfig)
|
||||||
|
if (serverConfig.isDesktop) {
|
||||||
|
// Keeping GUI Window Open
|
||||||
|
keepGUIAlive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fetchServerConfig()
|
fetchServerConfig()
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -382,6 +382,9 @@ export default function Editor(props: EditorProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onPointerUp = (ev: SyntheticEvent) => {
|
const onPointerUp = (ev: SyntheticEvent) => {
|
||||||
|
if (!hadDrawSomething()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (isMidClick(ev)) {
|
if (isMidClick(ev)) {
|
||||||
setIsPanning(false)
|
setIsPanning(false)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IconButton } from "@/components/ui/button"
|
import { IconButton } from "@/components/ui/button"
|
||||||
import { useToggle } from "@uidotdev/usehooks"
|
import { useToggle } from "@uidotdev/usehooks"
|
||||||
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog"
|
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog"
|
||||||
import { Info, Settings } from "lucide-react"
|
import { HelpCircle, Settings } from "lucide-react"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
@ -19,7 +19,7 @@ import {
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Switch } from "./ui/switch"
|
import { Switch } from "./ui/switch"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
||||||
import { useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { fetchModelInfos, switchModel } from "@/lib/api"
|
import { fetchModelInfos, switchModel } from "@/lib/api"
|
||||||
@ -30,7 +30,6 @@ import { useToast } from "./ui/use-toast"
|
|||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogContent,
|
AlertDialogContent,
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
} from "./ui/alert-dialog"
|
} from "./ui/alert-dialog"
|
||||||
import {
|
import {
|
||||||
@ -85,6 +84,9 @@ export function SettingsDialog() {
|
|||||||
])
|
])
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const [model, setModel] = useState<ModelInfo>(settings.model)
|
const [model, setModel] = useState<ModelInfo>(settings.model)
|
||||||
|
useEffect(() => {
|
||||||
|
setModel(settings.model)
|
||||||
|
}, [settings.model])
|
||||||
|
|
||||||
const { data: modelInfos, status } = useQuery({
|
const { data: modelInfos, status } = useQuery({
|
||||||
queryKey: ["modelInfos"],
|
queryKey: ["modelInfos"],
|
||||||
@ -163,7 +165,6 @@ export function SettingsDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onModelSelect(info: ModelInfo) {
|
function onModelSelect(info: ModelInfo) {
|
||||||
console.log(info)
|
|
||||||
setModel(info)
|
setModel(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,35 +212,35 @@ export function SettingsDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4 w-[510px]">
|
||||||
<div className="flex flex-col gap-4 rounded-md">
|
<div className="flex flex-col gap-4 rounded-md">
|
||||||
<div>Current Model</div>
|
<div className="font-medium">Current Model</div>
|
||||||
<div>{model.name}</div>
|
<div>{model.name}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="space-y-4 rounded-md">
|
<div className="space-y-4 rounded-md">
|
||||||
<div className="flex gap-4 items-center justify-start">
|
<div className="flex gap-1 items-center justify-start">
|
||||||
<div>Available models</div>
|
<div className="font-medium">Available models</div>
|
||||||
<IconButton tooltip="How to download new model" asChild>
|
{/* <IconButton tooltip="How to download new model" asChild>
|
||||||
<Info />
|
<HelpCircle size={16} strokeWidth={1.5} className="opacity-50" />
|
||||||
</IconButton>
|
</IconButton> */}
|
||||||
</div>
|
</div>
|
||||||
<Tabs defaultValue={defaultTab}>
|
<Tabs defaultValue={defaultTab}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value={MODEL_TYPE_INPAINT}>Inpaint</TabsTrigger>
|
<TabsTrigger value={MODEL_TYPE_INPAINT}>Erase</TabsTrigger>
|
||||||
<TabsTrigger value={MODEL_TYPE_DIFFUSERS_SD}>
|
<TabsTrigger value={MODEL_TYPE_DIFFUSERS_SD}>
|
||||||
Diffusion
|
Stable Diffusion
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value={MODEL_TYPE_DIFFUSERS_SD_INPAINT}>
|
<TabsTrigger value={MODEL_TYPE_DIFFUSERS_SD_INPAINT}>
|
||||||
Diffusion inpaint
|
Stable Diffusion Inpaint
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value={MODEL_TYPE_OTHER}>
|
<TabsTrigger value={MODEL_TYPE_OTHER}>
|
||||||
Diffusion other
|
Other Diffusion
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<ScrollArea className="h-[240px] w-full mt-2 outline-none">
|
<ScrollArea className="h-[240px] w-full mt-2 outline-none border rounded-lg">
|
||||||
<TabsContent value={MODEL_TYPE_INPAINT}>
|
<TabsContent value={MODEL_TYPE_INPAINT}>
|
||||||
{renderModelList([MODEL_TYPE_INPAINT])}
|
{renderModelList([MODEL_TYPE_INPAINT])}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
@ -267,7 +268,7 @@ export function SettingsDialog() {
|
|||||||
|
|
||||||
function renderGeneralSettings() {
|
function renderGeneralSettings() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 w-[400px]">
|
<div className="space-y-4 w-[510px]">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="enableManualInpainting"
|
name="enableManualInpainting"
|
||||||
@ -276,7 +277,8 @@ export function SettingsDialog() {
|
|||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Enable manual inpainting</FormLabel>
|
<FormLabel>Enable manual inpainting</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Click a button to trigger inpainting after draw mask.
|
For erase model, click a button to trigger inpainting after
|
||||||
|
draw mask.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -468,7 +470,7 @@ export function SettingsDialog() {
|
|||||||
</div>
|
</div>
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<div className="flex w-full justify-center">
|
<div className="flex w-full justify-center">
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
{tab === TAB_MODEL ? renderModelSettings() : <></>}
|
{tab === TAB_MODEL ? renderModelSettings() : <></>}
|
||||||
{tab === TAB_GENERAL ? renderGeneralSettings() : <></>}
|
{tab === TAB_GENERAL ? renderGeneralSettings() : <></>}
|
||||||
|
77
web_app/src/components/SidePanel/CV2Options.tsx
Normal file
77
web_app/src/components/SidePanel/CV2Options.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
import { LabelTitle, RowContainer } from "./LabelTitle"
|
||||||
|
import { NumberInput } from "../ui/input"
|
||||||
|
import { Slider } from "../ui/slider"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select"
|
||||||
|
import { CV2Flag } from "@/lib/types"
|
||||||
|
|
||||||
|
const CV2Options = () => {
|
||||||
|
const [settings, updateSettings] = useStore((state) => [
|
||||||
|
state.settings,
|
||||||
|
state.updateSettings,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 mt-4">
|
||||||
|
<RowContainer>
|
||||||
|
<LabelTitle
|
||||||
|
text="CV2 Flag"
|
||||||
|
url="https://docs.opencv.org/4.8.0/d7/d8b/group__photo__inpaint.html#gga8002a65f5a3328fbf15df81b842d3c3ca892824c38e258feb5e72f308a358d52e"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
value={settings.cv2Flag as string}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const flag = value as CV2Flag
|
||||||
|
updateSettings({ cv2Flag: flag })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[160px]">
|
||||||
|
<SelectValue placeholder="Select flag" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectGroup>
|
||||||
|
{Object.values(CV2Flag).map((flag) => (
|
||||||
|
<SelectItem key={flag as string} value={flag as string}>
|
||||||
|
{flag as string}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</RowContainer>
|
||||||
|
<LabelTitle
|
||||||
|
text="CV2 Radius"
|
||||||
|
url="https://docs.opencv.org/4.8.0/d7/d8b/group__photo__inpaint.html#gga8002a65f5a3328fbf15df81b842d3c3ca892824c38e258feb5e72f308a358d52e"
|
||||||
|
/>
|
||||||
|
<RowContainer>
|
||||||
|
<Slider
|
||||||
|
className="w-[180px]"
|
||||||
|
defaultValue={[5]}
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={[Math.floor(settings.cv2Radius)]}
|
||||||
|
onValueChange={(vals) => updateSettings({ cv2Radius: vals[0] })}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
id="cv2-radius"
|
||||||
|
className="w-[60px] rounded-full"
|
||||||
|
numberValue={settings.cv2Radius}
|
||||||
|
allowFloat={false}
|
||||||
|
onNumberValueChange={(val) => {
|
||||||
|
updateSettings({ cv2Radius: val })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RowContainer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CV2Options
|
@ -1,9 +1,7 @@
|
|||||||
import { FormEvent, useState } from "react"
|
import { FormEvent } from "react"
|
||||||
import { useToggle } from "react-use"
|
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
import { Switch } from "./ui/switch"
|
import { Switch } from "../ui/switch"
|
||||||
import { Label } from "./ui/label"
|
import { NumberInput } from "../ui/input"
|
||||||
import { NumberInput } from "./ui/input"
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -11,56 +9,28 @@ import {
|
|||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "./ui/select"
|
} from "../ui/select"
|
||||||
import { Textarea } from "./ui/textarea"
|
import { Textarea } from "../ui/textarea"
|
||||||
import { SDSampler } from "@/lib/types"
|
import { SDSampler } from "@/lib/types"
|
||||||
import { Separator } from "./ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { ScrollArea } from "./ui/scroll-area"
|
import { Move, MoveHorizontal, MoveVertical, Upload } from "lucide-react"
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTrigger } from "./ui/sheet"
|
import { Button, ImageUploadButton } from "../ui/button"
|
||||||
import {
|
import { Slider } from "../ui/slider"
|
||||||
ArrowDownFromLine,
|
|
||||||
ArrowLeftFromLine,
|
|
||||||
ArrowRightFromLine,
|
|
||||||
ArrowUpFromLine,
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
HelpCircle,
|
|
||||||
LucideIcon,
|
|
||||||
Maximize,
|
|
||||||
Move,
|
|
||||||
MoveHorizontal,
|
|
||||||
MoveVertical,
|
|
||||||
Upload,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { Button, ImageUploadButton } from "./ui/button"
|
|
||||||
import useHotKey from "@/hooks/useHotkey"
|
|
||||||
import { Slider } from "./ui/slider"
|
|
||||||
import { useImage } from "@/hooks/useImage"
|
import { useImage } from "@/hooks/useImage"
|
||||||
import {
|
import {
|
||||||
EXTENDER_ALL,
|
EXTENDER_ALL,
|
||||||
EXTENDER_BUILTIN_ALL,
|
|
||||||
EXTENDER_BUILTIN_X_LEFT,
|
|
||||||
EXTENDER_BUILTIN_X_RIGHT,
|
|
||||||
EXTENDER_BUILTIN_Y_BOTTOM,
|
|
||||||
EXTENDER_BUILTIN_Y_TOP,
|
|
||||||
EXTENDER_X,
|
EXTENDER_X,
|
||||||
EXTENDER_Y,
|
EXTENDER_Y,
|
||||||
INSTRUCT_PIX2PIX,
|
INSTRUCT_PIX2PIX,
|
||||||
PAINT_BY_EXAMPLE,
|
PAINT_BY_EXAMPLE,
|
||||||
} from "@/lib/const"
|
} from "@/lib/const"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"
|
import { RowContainer, LabelTitle } from "./LabelTitle"
|
||||||
|
|
||||||
const RowContainer = ({ children }: { children: React.ReactNode }) => (
|
|
||||||
<div className="flex justify-between items-center pr-2">{children}</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const ExtenderButton = ({
|
const ExtenderButton = ({
|
||||||
IconCls,
|
|
||||||
text,
|
text,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
IconCls: LucideIcon
|
|
||||||
text: string
|
text: string
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}) => {
|
}) => {
|
||||||
@ -73,92 +43,32 @@ const ExtenderButton = ({
|
|||||||
disabled={!showExtender}
|
disabled={!showExtender}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">{text}</div>
|
||||||
<IconCls size={15} strokeWidth={1} />
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const LabelTitle = ({
|
const DiffusionOptions = () => {
|
||||||
text,
|
|
||||||
toolTip,
|
|
||||||
url,
|
|
||||||
htmlFor,
|
|
||||||
disabled = false,
|
|
||||||
}: {
|
|
||||||
text: string
|
|
||||||
toolTip?: string
|
|
||||||
url?: string
|
|
||||||
htmlFor?: string
|
|
||||||
disabled?: boolean
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Label
|
|
||||||
htmlFor={htmlFor ? htmlFor : text.toLowerCase().replace(" ", "-")}
|
|
||||||
className="font-medium"
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Label>
|
|
||||||
</TooltipTrigger>
|
|
||||||
{toolTip ? (
|
|
||||||
<TooltipContent className="flex flex-col max-w-xs text-sm" side="left">
|
|
||||||
<p>{toolTip}</p>
|
|
||||||
{url ? (
|
|
||||||
<Button variant="link" className="justify-end">
|
|
||||||
<a href={url} target="_blank">
|
|
||||||
More info
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</TooltipContent>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const SidePanel = () => {
|
|
||||||
const [
|
const [
|
||||||
settings,
|
settings,
|
||||||
windowSize,
|
|
||||||
paintByExampleFile,
|
paintByExampleFile,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
showSidePanel,
|
|
||||||
runInpainting,
|
runInpainting,
|
||||||
updateAppState,
|
updateAppState,
|
||||||
updateExtenderByBuiltIn,
|
updateExtenderByBuiltIn,
|
||||||
updateExtenderDirection,
|
updateExtenderDirection,
|
||||||
] = useStore((state) => [
|
] = useStore((state) => [
|
||||||
state.settings,
|
state.settings,
|
||||||
state.windowSize,
|
|
||||||
state.paintByExampleFile,
|
state.paintByExampleFile,
|
||||||
state.getIsProcessing(),
|
state.getIsProcessing(),
|
||||||
state.updateSettings,
|
state.updateSettings,
|
||||||
state.showSidePanel(),
|
|
||||||
state.runInpainting,
|
state.runInpainting,
|
||||||
state.updateAppState,
|
state.updateAppState,
|
||||||
state.updateExtenderByBuiltIn,
|
state.updateExtenderByBuiltIn,
|
||||||
state.updateExtenderDirection,
|
state.updateExtenderDirection,
|
||||||
])
|
])
|
||||||
const [exampleImage, isExampleImageLoaded] = useImage(paintByExampleFile)
|
const [exampleImage, isExampleImageLoaded] = useImage(paintByExampleFile)
|
||||||
const [open, toggleOpen] = useToggle(true)
|
|
||||||
|
|
||||||
useHotKey("c", () => {
|
|
||||||
toggleOpen()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!showSidePanel) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const onKeyUp = (e: React.KeyboardEvent) => {
|
const onKeyUp = (e: React.KeyboardEvent) => {
|
||||||
// negativePrompt 回车触发 inpainting
|
// negativePrompt 回车触发 inpainting
|
||||||
@ -582,32 +492,20 @@ const SidePanel = () => {
|
|||||||
className="flex gap-2 justify-center mt-0"
|
className="flex gap-2 justify-center mt-0"
|
||||||
>
|
>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={ArrowLeftFromLine}
|
text="1.25x"
|
||||||
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 1.25)}
|
||||||
|
/>
|
||||||
|
<ExtenderButton
|
||||||
text="1.5x"
|
text="1.5x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 1.5)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_LEFT, 1.5)
|
/>
|
||||||
}
|
<ExtenderButton
|
||||||
|
text="1.75x"
|
||||||
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 1.75)}
|
||||||
/>
|
/>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={ArrowLeftFromLine}
|
|
||||||
text="2.0x"
|
text="2.0x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 2.0)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_LEFT, 2.0)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ExtenderButton
|
|
||||||
IconCls={ArrowRightFromLine}
|
|
||||||
text="1.5x"
|
|
||||||
onClick={() =>
|
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_RIGHT, 1.5)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ExtenderButton
|
|
||||||
IconCls={ArrowRightFromLine}
|
|
||||||
text="2.0x"
|
|
||||||
onClick={() =>
|
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_RIGHT, 2.0)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent
|
<TabsContent
|
||||||
@ -615,32 +513,20 @@ const SidePanel = () => {
|
|||||||
className="flex gap-2 justify-center mt-0"
|
className="flex gap-2 justify-center mt-0"
|
||||||
>
|
>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={ArrowUpFromLine}
|
text="1.25x"
|
||||||
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 1.25)}
|
||||||
|
/>
|
||||||
|
<ExtenderButton
|
||||||
text="1.5x"
|
text="1.5x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 1.5)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_TOP, 1.5)
|
/>
|
||||||
}
|
<ExtenderButton
|
||||||
|
text="1.75x"
|
||||||
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 1.75)}
|
||||||
/>
|
/>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={ArrowUpFromLine}
|
|
||||||
text="2.0x"
|
text="2.0x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 2.0)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_TOP, 2.0)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ExtenderButton
|
|
||||||
IconCls={ArrowDownFromLine}
|
|
||||||
text="1.5x"
|
|
||||||
onClick={() =>
|
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_BOTTOM, 1.5)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ExtenderButton
|
|
||||||
IconCls={ArrowDownFromLine}
|
|
||||||
text="2.0x"
|
|
||||||
onClick={() =>
|
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_BOTTOM, 2.0)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent
|
<TabsContent
|
||||||
@ -648,32 +534,20 @@ const SidePanel = () => {
|
|||||||
className="flex gap-2 justify-center mt-0"
|
className="flex gap-2 justify-center mt-0"
|
||||||
>
|
>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={Maximize}
|
|
||||||
text="1.25x"
|
text="1.25x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 1.25)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 1.25)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={Maximize}
|
|
||||||
text="1.5x"
|
text="1.5x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 1.5)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 1.5)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={Maximize}
|
|
||||||
text="1.75x"
|
text="1.75x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 1.75)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 1.75)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<ExtenderButton
|
<ExtenderButton
|
||||||
IconCls={Maximize}
|
|
||||||
text="2.0x"
|
text="2.0x"
|
||||||
onClick={() =>
|
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 2.0)}
|
||||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 2.0)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@ -684,247 +558,194 @@ const SidePanel = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={open} modal={false}>
|
<div className="flex flex-col gap-4 mt-4">
|
||||||
<SheetTrigger
|
<RowContainer>
|
||||||
tabIndex={-1}
|
<LabelTitle
|
||||||
className="z-10 outline-none absolute top-[68px] right-6 rounded-lg border bg-background"
|
text="Cropper"
|
||||||
>
|
toolTip="Inpainting on part of image, improve inference speed and reduce memory usage."
|
||||||
<Button
|
/>
|
||||||
variant="ghost"
|
<Switch
|
||||||
size="icon"
|
id="cropper"
|
||||||
asChild
|
checked={settings.showCropper}
|
||||||
className="p-1.5"
|
onCheckedChange={(value) => {
|
||||||
onClick={toggleOpen}
|
updateSettings({ showCropper: value })
|
||||||
|
if (value) {
|
||||||
|
updateSettings({ showExtender: false })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RowContainer>
|
||||||
|
|
||||||
|
{renderExtender()}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<LabelTitle
|
||||||
|
htmlFor="steps"
|
||||||
|
text="Steps"
|
||||||
|
toolTip="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
||||||
|
/>
|
||||||
|
<RowContainer>
|
||||||
|
<Slider
|
||||||
|
className="w-[180px]"
|
||||||
|
defaultValue={[30]}
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={[Math.floor(settings.sdSteps)]}
|
||||||
|
onValueChange={(vals) => updateSettings({ sdSteps: vals[0] })}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
id="steps"
|
||||||
|
className="w-[60px] rounded-full"
|
||||||
|
numberValue={settings.sdSteps}
|
||||||
|
allowFloat={false}
|
||||||
|
onNumberValueChange={(val) => {
|
||||||
|
updateSettings({ sdSteps: val })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RowContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<LabelTitle
|
||||||
|
text="Guidance scale"
|
||||||
|
url="https://huggingface.co/docs/diffusers/main/en/using-diffusers/inpaint#guidance-scale"
|
||||||
|
toolTip="Guidance scale affects how aligned the text prompt and generated image are. Higher value means the prompt and generated image are closely aligned, so the output is a stricter interpretation of the prompt"
|
||||||
|
/>
|
||||||
|
<RowContainer>
|
||||||
|
<Slider
|
||||||
|
className="w-[180px]"
|
||||||
|
defaultValue={[750]}
|
||||||
|
min={0}
|
||||||
|
max={1500}
|
||||||
|
step={1}
|
||||||
|
value={[Math.floor(settings.sdGuidanceScale * 100)]}
|
||||||
|
onValueChange={(vals) =>
|
||||||
|
updateSettings({ sdGuidanceScale: vals[0] / 100 })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
id="guidance-scale"
|
||||||
|
className="w-[60px] rounded-full"
|
||||||
|
numberValue={settings.sdGuidanceScale}
|
||||||
|
allowFloat
|
||||||
|
onNumberValueChange={(val) => {
|
||||||
|
updateSettings({ sdGuidanceScale: val })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RowContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{renderP2PImageGuidanceScale()}
|
||||||
|
{renderStrength()}
|
||||||
|
|
||||||
|
<RowContainer>
|
||||||
|
<LabelTitle text="Sampler" />
|
||||||
|
<Select
|
||||||
|
value={settings.sdSampler as string}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const sampler = value as SDSampler
|
||||||
|
updateSettings({ sdSampler: sampler })
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ChevronLeft strokeWidth={1} />
|
<SelectTrigger className="w-[100px]">
|
||||||
</Button>
|
<SelectValue placeholder="Select sampler" />
|
||||||
</SheetTrigger>
|
</SelectTrigger>
|
||||||
<SheetContent
|
<SelectContent align="end">
|
||||||
side="right"
|
<SelectGroup>
|
||||||
className="w-[300px] mt-[60px] outline-none pl-4 pr-1"
|
{Object.values(SDSampler).map((sampler) => (
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
<SelectItem key={sampler as string} value={sampler as string}>
|
||||||
onPointerDownOutside={(event) => event.preventDefault()}
|
{sampler as string}
|
||||||
>
|
</SelectItem>
|
||||||
<SheetHeader>
|
))}
|
||||||
<RowContainer>
|
</SelectGroup>
|
||||||
<div className="overflow-hidden mr-8">
|
</SelectContent>
|
||||||
{
|
</Select>
|
||||||
settings.model.name.split("/")[
|
</RowContainer>
|
||||||
settings.model.name.split("/").length - 1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="border h-6 w-6"
|
|
||||||
onClick={toggleOpen}
|
|
||||||
>
|
|
||||||
<ChevronRight strokeWidth={1} />
|
|
||||||
</Button>
|
|
||||||
</RowContainer>
|
|
||||||
<Separator />
|
|
||||||
</SheetHeader>
|
|
||||||
<ScrollArea
|
|
||||||
style={{ height: windowSize.height - 160 }}
|
|
||||||
className="pr-3"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-4 mt-4">
|
|
||||||
<RowContainer>
|
|
||||||
<LabelTitle
|
|
||||||
text="Cropper"
|
|
||||||
toolTip="Inpainting on part of image, improve inference speed and reduce memory usage."
|
|
||||||
/>
|
|
||||||
<Switch
|
|
||||||
id="cropper"
|
|
||||||
checked={settings.showCropper}
|
|
||||||
onCheckedChange={(value) => {
|
|
||||||
updateSettings({ showCropper: value })
|
|
||||||
if (value) {
|
|
||||||
updateSettings({ showExtender: false })
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</RowContainer>
|
|
||||||
|
|
||||||
{renderExtender()}
|
<RowContainer>
|
||||||
|
{/* 每次会从服务器返回更新该值 */}
|
||||||
|
<LabelTitle
|
||||||
|
text="Seed"
|
||||||
|
toolTip="Using same parameters and a fixed seed can generate same result image."
|
||||||
|
/>
|
||||||
|
{/* <Pin /> */}
|
||||||
|
<div className="flex gap-2 justify-center items-center">
|
||||||
|
<Switch
|
||||||
|
id="seed"
|
||||||
|
checked={settings.seedFixed}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
updateSettings({ seedFixed: value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
id="seed"
|
||||||
|
className="w-[100px]"
|
||||||
|
disabled={!settings.seedFixed}
|
||||||
|
numberValue={settings.seed}
|
||||||
|
allowFloat={false}
|
||||||
|
onNumberValueChange={(val) => {
|
||||||
|
updateSettings({ seed: val })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
{renderNegativePrompt()}
|
||||||
<LabelTitle
|
|
||||||
htmlFor="steps"
|
|
||||||
text="Steps"
|
|
||||||
toolTip="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
|
||||||
/>
|
|
||||||
<RowContainer>
|
|
||||||
<Slider
|
|
||||||
className="w-[180px]"
|
|
||||||
defaultValue={[30]}
|
|
||||||
min={1}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
value={[Math.floor(settings.sdSteps)]}
|
|
||||||
onValueChange={(vals) => updateSettings({ sdSteps: vals[0] })}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
id="steps"
|
|
||||||
className="w-[60px] rounded-full"
|
|
||||||
numberValue={settings.sdSteps}
|
|
||||||
allowFloat={false}
|
|
||||||
onNumberValueChange={(val) => {
|
|
||||||
updateSettings({ sdSteps: val })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</RowContainer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
<Separator />
|
||||||
<LabelTitle
|
|
||||||
text="Guidance scale"
|
|
||||||
url="https://huggingface.co/docs/diffusers/main/en/using-diffusers/inpaint#guidance-scale"
|
|
||||||
toolTip="Guidance scale affects how aligned the text prompt and generated image are. Higher value means the prompt and generated image are closely aligned, so the output is a stricter interpretation of the prompt"
|
|
||||||
/>
|
|
||||||
<RowContainer>
|
|
||||||
<Slider
|
|
||||||
className="w-[180px]"
|
|
||||||
defaultValue={[750]}
|
|
||||||
min={0}
|
|
||||||
max={1500}
|
|
||||||
step={1}
|
|
||||||
value={[Math.floor(settings.sdGuidanceScale * 100)]}
|
|
||||||
onValueChange={(vals) =>
|
|
||||||
updateSettings({ sdGuidanceScale: vals[0] / 100 })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
id="guidance-scale"
|
|
||||||
className="w-[60px] rounded-full"
|
|
||||||
numberValue={settings.sdGuidanceScale}
|
|
||||||
allowFloat
|
|
||||||
onNumberValueChange={(val) => {
|
|
||||||
updateSettings({ sdGuidanceScale: val })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</RowContainer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{renderP2PImageGuidanceScale()}
|
{renderConterNetSetting()}
|
||||||
{renderStrength()}
|
{renderFreeu()}
|
||||||
|
{renderLCMLora()}
|
||||||
|
|
||||||
<RowContainer>
|
<div className="flex flex-col gap-1">
|
||||||
<LabelTitle text="Sampler" />
|
<LabelTitle
|
||||||
<Select
|
text="Mask blur"
|
||||||
value={settings.sdSampler as string}
|
toolTip="How much to blur the mask before processing, in pixels."
|
||||||
onValueChange={(value) => {
|
/>
|
||||||
const sampler = value as SDSampler
|
<RowContainer>
|
||||||
updateSettings({ sdSampler: sampler })
|
<Slider
|
||||||
}}
|
className="w-[180px]"
|
||||||
>
|
defaultValue={[5]}
|
||||||
<SelectTrigger className="w-[100px]">
|
min={0}
|
||||||
<SelectValue placeholder="Select sampler" />
|
max={35}
|
||||||
</SelectTrigger>
|
step={1}
|
||||||
<SelectContent align="end">
|
value={[Math.floor(settings.sdMaskBlur)]}
|
||||||
<SelectGroup>
|
onValueChange={(vals) => updateSettings({ sdMaskBlur: vals[0] })}
|
||||||
{Object.values(SDSampler).map((sampler) => (
|
/>
|
||||||
<SelectItem
|
<NumberInput
|
||||||
key={sampler as string}
|
id="mask-blur"
|
||||||
value={sampler as string}
|
className="w-[60px] rounded-full"
|
||||||
>
|
numberValue={settings.sdMaskBlur}
|
||||||
{sampler as string}
|
allowFloat={false}
|
||||||
</SelectItem>
|
onNumberValueChange={(value) => {
|
||||||
))}
|
updateSettings({ sdMaskBlur: value })
|
||||||
</SelectGroup>
|
}}
|
||||||
</SelectContent>
|
/>
|
||||||
</Select>
|
</RowContainer>
|
||||||
</RowContainer>
|
</div>
|
||||||
|
|
||||||
<RowContainer>
|
<RowContainer>
|
||||||
{/* 每次会从服务器返回更新该值 */}
|
<LabelTitle
|
||||||
<LabelTitle
|
text="Match histograms"
|
||||||
text="Seed"
|
toolTip="Match the inpainting result histogram to the source image histogram"
|
||||||
toolTip="Using same parameters and a fixed seed can generate same result image."
|
url="https://github.com/Sanster/lama-cleaner/pull/143#issuecomment-1325859307"
|
||||||
/>
|
/>
|
||||||
{/* <Pin /> */}
|
<Switch
|
||||||
<div className="flex gap-2 justify-center items-center">
|
id="match-histograms"
|
||||||
<Switch
|
checked={settings.sdMatchHistograms}
|
||||||
id="seed"
|
onCheckedChange={(value) => {
|
||||||
checked={settings.seedFixed}
|
updateSettings({ sdMatchHistograms: value })
|
||||||
onCheckedChange={(value) => {
|
}}
|
||||||
updateSettings({ seedFixed: value })
|
/>
|
||||||
}}
|
</RowContainer>
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
id="seed"
|
|
||||||
className="w-[100px]"
|
|
||||||
disabled={!settings.seedFixed}
|
|
||||||
numberValue={settings.seed}
|
|
||||||
allowFloat={false}
|
|
||||||
onNumberValueChange={(val) => {
|
|
||||||
updateSettings({ seed: val })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</RowContainer>
|
|
||||||
|
|
||||||
{renderNegativePrompt()}
|
<Separator />
|
||||||
|
|
||||||
<Separator />
|
{renderPaintByExample()}
|
||||||
|
</div>
|
||||||
{renderConterNetSetting()}
|
|
||||||
{renderFreeu()}
|
|
||||||
{renderLCMLora()}
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<LabelTitle
|
|
||||||
text="Mask blur"
|
|
||||||
toolTip="How much to blur the mask before processing, in pixels."
|
|
||||||
/>
|
|
||||||
<RowContainer>
|
|
||||||
<Slider
|
|
||||||
className="w-[180px]"
|
|
||||||
defaultValue={[5]}
|
|
||||||
min={0}
|
|
||||||
max={35}
|
|
||||||
step={1}
|
|
||||||
value={[Math.floor(settings.sdMaskBlur)]}
|
|
||||||
onValueChange={(vals) =>
|
|
||||||
updateSettings({ sdMaskBlur: vals[0] })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
id="mask-blur"
|
|
||||||
className="w-[60px] rounded-full"
|
|
||||||
numberValue={settings.sdMaskBlur}
|
|
||||||
allowFloat={false}
|
|
||||||
onNumberValueChange={(value) => {
|
|
||||||
updateSettings({ sdMaskBlur: value })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</RowContainer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<RowContainer>
|
|
||||||
<LabelTitle
|
|
||||||
text="Match histograms"
|
|
||||||
toolTip="Match the inpainting result histogram to the source image histogram"
|
|
||||||
url="https://github.com/Sanster/lama-cleaner/pull/143#issuecomment-1325859307"
|
|
||||||
/>
|
|
||||||
<Switch
|
|
||||||
id="match-histograms"
|
|
||||||
checked={settings.sdMatchHistograms}
|
|
||||||
onCheckedChange={(value) => {
|
|
||||||
updateSettings({ sdMatchHistograms: value })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</RowContainer>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{renderPaintByExample()}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SidePanel
|
export default DiffusionOptions
|
77
web_app/src/components/SidePanel/LDMOptions.tsx
Normal file
77
web_app/src/components/SidePanel/LDMOptions.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
import { LabelTitle, RowContainer } from "./LabelTitle"
|
||||||
|
import { NumberInput } from "../ui/input"
|
||||||
|
import { Slider } from "../ui/slider"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select"
|
||||||
|
import { LDMSampler } from "@/lib/types"
|
||||||
|
|
||||||
|
const LDMOptions = () => {
|
||||||
|
const [settings, updateSettings] = useStore((state) => [
|
||||||
|
state.settings,
|
||||||
|
state.updateSettings,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 mt-4">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<LabelTitle
|
||||||
|
htmlFor="steps"
|
||||||
|
text="Steps"
|
||||||
|
toolTip="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
||||||
|
/>
|
||||||
|
<RowContainer>
|
||||||
|
<Slider
|
||||||
|
className="w-[180px]"
|
||||||
|
defaultValue={[30]}
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={[Math.floor(settings.ldmSteps)]}
|
||||||
|
onValueChange={(vals) => updateSettings({ ldmSteps: vals[0] })}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
id="steps"
|
||||||
|
className="w-[60px] rounded-full"
|
||||||
|
numberValue={settings.ldmSteps}
|
||||||
|
allowFloat={false}
|
||||||
|
onNumberValueChange={(val) => {
|
||||||
|
updateSettings({ ldmSteps: val })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RowContainer>
|
||||||
|
</div>
|
||||||
|
<RowContainer>
|
||||||
|
<LabelTitle text="Sampler" />
|
||||||
|
<Select
|
||||||
|
value={settings.ldmSampler as string}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const sampler = value as LDMSampler
|
||||||
|
updateSettings({ ldmSampler: sampler })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[100px]">
|
||||||
|
<SelectValue placeholder="Select sampler" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectGroup>
|
||||||
|
{Object.values(LDMSampler).map((sampler) => (
|
||||||
|
<SelectItem key={sampler as string} value={sampler as string}>
|
||||||
|
{sampler as string}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</RowContainer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LDMOptions
|
53
web_app/src/components/SidePanel/LabelTitle.tsx
Normal file
53
web_app/src/components/SidePanel/LabelTitle.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Button } from "../ui/button"
|
||||||
|
import { Label } from "../ui/label"
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"
|
||||||
|
|
||||||
|
const RowContainer = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<div className="flex justify-between items-center pr-2">{children}</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const LabelTitle = ({
|
||||||
|
text,
|
||||||
|
toolTip = "",
|
||||||
|
url,
|
||||||
|
htmlFor,
|
||||||
|
disabled = false,
|
||||||
|
}: {
|
||||||
|
text: string
|
||||||
|
toolTip?: string
|
||||||
|
url?: string
|
||||||
|
htmlFor?: string
|
||||||
|
disabled?: boolean
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Label
|
||||||
|
htmlFor={htmlFor ? htmlFor : text.toLowerCase().replace(" ", "-")}
|
||||||
|
className="font-medium"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Label>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{toolTip || url ? (
|
||||||
|
<TooltipContent className="flex flex-col max-w-xs text-sm" side="left">
|
||||||
|
<p>{toolTip}</p>
|
||||||
|
{url ? (
|
||||||
|
<Button variant="link" className="justify-end">
|
||||||
|
<a href={url} target="_blank">
|
||||||
|
More info
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</TooltipContent>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LabelTitle, RowContainer }
|
98
web_app/src/components/SidePanel/index.tsx
Normal file
98
web_app/src/components/SidePanel/index.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { useToggle } from "react-use"
|
||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
import { Separator } from "../ui/separator"
|
||||||
|
import { ScrollArea } from "../ui/scroll-area"
|
||||||
|
import { Sheet, SheetContent, SheetHeader, SheetTrigger } from "../ui/sheet"
|
||||||
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
import useHotKey from "@/hooks/useHotkey"
|
||||||
|
import { RowContainer } from "./LabelTitle"
|
||||||
|
import { CV2, LDM, MODEL_TYPE_INPAINT } from "@/lib/const"
|
||||||
|
import LDMOptions from "./LDMOptions"
|
||||||
|
import DiffusionOptions from "./DiffusionOptions"
|
||||||
|
import CV2Options from "./CV2Options"
|
||||||
|
|
||||||
|
const SidePanel = () => {
|
||||||
|
const [settings, windowSize] = useStore((state) => [
|
||||||
|
state.settings,
|
||||||
|
state.windowSize,
|
||||||
|
])
|
||||||
|
|
||||||
|
const [open, toggleOpen] = useToggle(true)
|
||||||
|
|
||||||
|
useHotKey("c", () => {
|
||||||
|
toggleOpen()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
settings.model.name !== LDM &&
|
||||||
|
settings.model.name !== CV2 &&
|
||||||
|
settings.model.model_type === MODEL_TYPE_INPAINT
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSidePanelOptions = () => {
|
||||||
|
if (settings.model.name === LDM) {
|
||||||
|
return <LDMOptions />
|
||||||
|
}
|
||||||
|
if (settings.model.name === CV2) {
|
||||||
|
return <CV2Options />
|
||||||
|
}
|
||||||
|
return <DiffusionOptions />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} modal={false}>
|
||||||
|
<SheetTrigger
|
||||||
|
tabIndex={-1}
|
||||||
|
className="z-10 outline-none absolute top-[68px] right-6 rounded-lg border bg-background"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
asChild
|
||||||
|
className="p-1.5"
|
||||||
|
onClick={toggleOpen}
|
||||||
|
>
|
||||||
|
<ChevronLeft strokeWidth={1} />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent
|
||||||
|
side="right"
|
||||||
|
className="w-[300px] mt-[60px] outline-none pl-4 pr-1"
|
||||||
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
|
onPointerDownOutside={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
|
<SheetHeader>
|
||||||
|
<RowContainer>
|
||||||
|
<div className="overflow-hidden mr-8">
|
||||||
|
{
|
||||||
|
settings.model.name.split("/")[
|
||||||
|
settings.model.name.split("/").length - 1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="border h-6 w-6"
|
||||||
|
onClick={toggleOpen}
|
||||||
|
>
|
||||||
|
<ChevronRight strokeWidth={1} />
|
||||||
|
</Button>
|
||||||
|
</RowContainer>
|
||||||
|
<Separator />
|
||||||
|
</SheetHeader>
|
||||||
|
<ScrollArea
|
||||||
|
style={{ height: windowSize.height - 160 }}
|
||||||
|
className="pr-3"
|
||||||
|
>
|
||||||
|
{renderSidePanelOptions()}
|
||||||
|
</ScrollArea>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SidePanel
|
@ -1,23 +1,11 @@
|
|||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import Editor from "./Editor"
|
import Editor from "./Editor"
|
||||||
import {
|
|
||||||
AIModel,
|
|
||||||
isPaintByExampleState,
|
|
||||||
isPix2PixState,
|
|
||||||
isSDState,
|
|
||||||
} from "@/lib/store"
|
|
||||||
import { currentModel } from "@/lib/api"
|
import { currentModel } from "@/lib/api"
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
import ImageSize from "./ImageSize"
|
import ImageSize from "./ImageSize"
|
||||||
import Plugins from "./Plugins"
|
import Plugins from "./Plugins"
|
||||||
import { InteractiveSeg } from "./InteractiveSeg"
|
import { InteractiveSeg } from "./InteractiveSeg"
|
||||||
import SidePanel from "./SidePanel"
|
import SidePanel from "./SidePanel"
|
||||||
// import SidePanel from "./SidePanel/SidePanel"
|
|
||||||
// import PESidePanel from "./SidePanel/PESidePanel"
|
|
||||||
// import P2PSidePanel from "./SidePanel/P2PSidePanel"
|
|
||||||
// import Plugins from "./Plugins/Plugins"
|
|
||||||
// import Flex from "./shared/Layout"
|
|
||||||
// import ImageSize from "./ImageSize/ImageSize"
|
|
||||||
|
|
||||||
const Workspace = () => {
|
const Workspace = () => {
|
||||||
const [file, updateSettings] = useStore((state) => [
|
const [file, updateSettings] = useStore((state) => [
|
||||||
@ -35,10 +23,6 @@ const Workspace = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* {isSD ? <SidePanel /> : <></>}
|
|
||||||
{isPaintByExample ? <PESidePanel /> : <></>}
|
|
||||||
{isPix2Pix ? <P2PSidePanel /> : <></>}
|
|
||||||
{/* <SettingModal onClose={onSettingClose} /> */}
|
|
||||||
<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
|
<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
|
||||||
<Plugins />
|
<Plugins />
|
||||||
<ImageSize />
|
<ImageSize />
|
||||||
|
@ -125,12 +125,6 @@ export function fetchModelInfos(): Promise<ModelInfo[]> {
|
|||||||
return api.get("/models").then((response) => response.data)
|
return api.get("/models").then((response) => response.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDesktop() {
|
|
||||||
return fetch(`${API_ENDPOINT}/is_desktop`, {
|
|
||||||
method: "GET",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function modelDownloaded(name: string) {
|
export function modelDownloaded(name: string) {
|
||||||
return fetch(`${API_ENDPOINT}/model_downloaded/${name}`, {
|
return fetch(`${API_ENDPOINT}/model_downloaded/${name}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -11,12 +11,9 @@ export const BRUSH_COLOR = "#ffcc00bb"
|
|||||||
export const EXTENDER_X = "extender_x"
|
export const EXTENDER_X = "extender_x"
|
||||||
export const EXTENDER_Y = "extender_y"
|
export const EXTENDER_Y = "extender_y"
|
||||||
export const EXTENDER_ALL = "extender_all"
|
export const EXTENDER_ALL = "extender_all"
|
||||||
export const EXTENDER_BUILTIN_X_LEFT = "extender_builtin_x_left"
|
|
||||||
export const EXTENDER_BUILTIN_X_RIGHT = "extender_builtin_x_right"
|
|
||||||
export const EXTENDER_BUILTIN_Y_TOP = "extender_builtin_y_top"
|
|
||||||
export const EXTENDER_BUILTIN_Y_BOTTOM = "extender_builtin_y_bottom"
|
|
||||||
export const EXTENDER_BUILTIN_ALL = "extender_builtin_all"
|
|
||||||
|
|
||||||
|
export const LDM = "ldm"
|
||||||
|
export const CV2 = "cv2"
|
||||||
export const PAINT_BY_EXAMPLE = "Fantasy-Studio/Paint-by-Example"
|
export const PAINT_BY_EXAMPLE = "Fantasy-Studio/Paint-by-Example"
|
||||||
export const INSTRUCT_PIX2PIX = "timbrooks/instruct-pix2pix"
|
export const INSTRUCT_PIX2PIX = "timbrooks/instruct-pix2pix"
|
||||||
export const KANDINSKY_2_2 = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
|
export const KANDINSKY_2_2 = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
|
||||||
|
@ -22,11 +22,6 @@ import {
|
|||||||
DEFAULT_BRUSH_SIZE,
|
DEFAULT_BRUSH_SIZE,
|
||||||
DEFAULT_NEGATIVE_PROMPT,
|
DEFAULT_NEGATIVE_PROMPT,
|
||||||
EXTENDER_ALL,
|
EXTENDER_ALL,
|
||||||
EXTENDER_BUILTIN_ALL,
|
|
||||||
EXTENDER_BUILTIN_X_LEFT,
|
|
||||||
EXTENDER_BUILTIN_X_RIGHT,
|
|
||||||
EXTENDER_BUILTIN_Y_BOTTOM,
|
|
||||||
EXTENDER_BUILTIN_Y_TOP,
|
|
||||||
EXTENDER_X,
|
EXTENDER_X,
|
||||||
EXTENDER_Y,
|
EXTENDER_Y,
|
||||||
MODEL_TYPE_INPAINT,
|
MODEL_TYPE_INPAINT,
|
||||||
@ -112,6 +107,7 @@ type ServerConfig = {
|
|||||||
enableAutoSaving: boolean
|
enableAutoSaving: boolean
|
||||||
enableControlnet: boolean
|
enableControlnet: boolean
|
||||||
controlnetMethod: string
|
controlnetMethod: string
|
||||||
|
isDesktop: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type InteractiveSegState = {
|
type InteractiveSegState = {
|
||||||
@ -129,6 +125,7 @@ type EditorState = {
|
|||||||
lineGroups: LineGroup[]
|
lineGroups: LineGroup[]
|
||||||
lastLineGroup: LineGroup
|
lastLineGroup: LineGroup
|
||||||
curLineGroup: LineGroup
|
curLineGroup: LineGroup
|
||||||
|
// 只用来显示
|
||||||
extraMasks: HTMLImageElement[]
|
extraMasks: HTMLImageElement[]
|
||||||
// redo 相关
|
// redo 相关
|
||||||
redoRenders: HTMLImageElement[]
|
redoRenders: HTMLImageElement[]
|
||||||
@ -153,7 +150,7 @@ type AppState = {
|
|||||||
|
|
||||||
cropperState: CropperState
|
cropperState: CropperState
|
||||||
extenderState: CropperState
|
extenderState: CropperState
|
||||||
isCropperExtenderResizing: bool
|
isCropperExtenderResizing: boolean
|
||||||
|
|
||||||
serverConfig: ServerConfig
|
serverConfig: ServerConfig
|
||||||
|
|
||||||
@ -194,7 +191,6 @@ type AppAction = {
|
|||||||
resetInteractiveSegState: () => void
|
resetInteractiveSegState: () => void
|
||||||
handleInteractiveSegAccept: () => void
|
handleInteractiveSegAccept: () => void
|
||||||
showPromptInput: () => boolean
|
showPromptInput: () => boolean
|
||||||
showSidePanel: () => boolean
|
|
||||||
|
|
||||||
runInpainting: () => Promise<void>
|
runInpainting: () => Promise<void>
|
||||||
showPrevMask: () => Promise<void>
|
showPrevMask: () => Promise<void>
|
||||||
@ -281,6 +277,7 @@ const defaultValues: AppState = {
|
|||||||
enableAutoSaving: false,
|
enableAutoSaving: false,
|
||||||
enableControlnet: false,
|
enableControlnet: false,
|
||||||
controlnetMethod: "lllyasviel/control_v11p_sd15_canny",
|
controlnetMethod: "lllyasviel/control_v11p_sd15_canny",
|
||||||
|
isDesktop: false,
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
model: {
|
model: {
|
||||||
@ -334,6 +331,9 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
...defaultValues,
|
...defaultValues,
|
||||||
|
|
||||||
showPrevMask: async () => {
|
showPrevMask: async () => {
|
||||||
|
if (get().settings.showExtender) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const { lastLineGroup, curLineGroup } = get().editorState
|
const { lastLineGroup, curLineGroup } = get().editorState
|
||||||
const { prevInteractiveSegMask, interactiveSegMask } =
|
const { prevInteractiveSegMask, interactiveSegMask } =
|
||||||
get().interactiveSegState
|
get().interactiveSegState
|
||||||
@ -380,7 +380,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
}
|
}
|
||||||
return targetFile
|
return targetFile
|
||||||
},
|
},
|
||||||
// todo: 传入 custom mask,单独逻辑
|
|
||||||
runInpainting: async () => {
|
runInpainting: async () => {
|
||||||
const {
|
const {
|
||||||
isInpainting,
|
isInpainting,
|
||||||
@ -399,6 +399,14 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
if (file === null) {
|
if (file === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
settings.showExtender &&
|
||||||
|
extenderState.height === imageHeight &&
|
||||||
|
extenderState.width === imageWidth
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { lastLineGroup, curLineGroup, lineGroups, renders } =
|
const { lastLineGroup, curLineGroup, lineGroups, renders } =
|
||||||
get().editorState
|
get().editorState
|
||||||
|
|
||||||
@ -406,42 +414,33 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
get().interactiveSegState
|
get().interactiveSegState
|
||||||
|
|
||||||
const useLastLineGroup =
|
const useLastLineGroup =
|
||||||
curLineGroup.length === 0 && interactiveSegMask === null
|
curLineGroup.length === 0 &&
|
||||||
|
interactiveSegMask === null &&
|
||||||
const maskImage = useLastLineGroup
|
!settings.showExtender
|
||||||
? prevInteractiveSegMask
|
|
||||||
: interactiveSegMask
|
|
||||||
|
|
||||||
// useLastLineGroup 的影响
|
// useLastLineGroup 的影响
|
||||||
// 1. 使用上一次的 mask
|
// 1. 使用上一次的 mask
|
||||||
// 2. 结果替换当前 render
|
// 2. 结果替换当前 render
|
||||||
|
let maskImage = null
|
||||||
let maskLineGroup: LineGroup = []
|
let maskLineGroup: LineGroup = []
|
||||||
if (useLastLineGroup === true) {
|
if (useLastLineGroup === true) {
|
||||||
if (
|
|
||||||
lastLineGroup.length === 0 &&
|
|
||||||
maskImage === null &&
|
|
||||||
!settings.showExtender
|
|
||||||
) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
description: "Please draw mask on picture",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
maskLineGroup = lastLineGroup
|
maskLineGroup = lastLineGroup
|
||||||
|
maskImage = prevInteractiveSegMask
|
||||||
} else {
|
} else {
|
||||||
if (
|
|
||||||
curLineGroup.length === 0 &&
|
|
||||||
maskImage === null &&
|
|
||||||
!settings.showExtender
|
|
||||||
) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
description: "Please draw mask on picture",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
maskLineGroup = curLineGroup
|
maskLineGroup = curLineGroup
|
||||||
|
maskImage = interactiveSegMask
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
maskLineGroup.length === 0 &&
|
||||||
|
maskImage === null &&
|
||||||
|
!settings.showExtender
|
||||||
|
) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: "Please draw mask on picture",
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const newLineGroups = [...lineGroups, maskLineGroup]
|
const newLineGroups = [...lineGroups, maskLineGroup]
|
||||||
@ -498,6 +497,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
const newRender = new Image()
|
const newRender = new Image()
|
||||||
await loadImage(newRender, blob)
|
await loadImage(newRender, blob)
|
||||||
const newRenders = [...renders, newRender]
|
const newRenders = [...renders, newRender]
|
||||||
|
get().setImageSize(newRender.width, newRender.height)
|
||||||
get().updateEditorState({
|
get().updateEditorState({
|
||||||
renders: newRenders,
|
renders: newRenders,
|
||||||
lineGroups: newLineGroups,
|
lineGroups: newLineGroups,
|
||||||
@ -545,7 +545,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
const { blob } = res
|
const { blob } = res
|
||||||
const newRender = new Image()
|
const newRender = new Image()
|
||||||
await loadImage(newRender, blob)
|
await loadImage(newRender, blob)
|
||||||
get().setImageSize(newRender.height, newRender.width)
|
get().setImageSize(newRender.width, newRender.height)
|
||||||
const newRenders = [...renders, newRender]
|
const newRenders = [...renders, newRender]
|
||||||
const newLineGroups = [...lineGroups, []]
|
const newLineGroups = [...lineGroups, []]
|
||||||
get().updateEditorState({
|
get().updateEditorState({
|
||||||
@ -739,11 +739,6 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
showSidePanel: (): boolean => {
|
|
||||||
const model = get().settings.model
|
|
||||||
return model.model_type !== MODEL_TYPE_INPAINT
|
|
||||||
},
|
|
||||||
|
|
||||||
setServerConfig: (newValue: ServerConfig) => {
|
setServerConfig: (newValue: ServerConfig) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.serverConfig = newValue
|
state.serverConfig = newValue
|
||||||
@ -910,6 +905,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
state.extenderState.width = state.imageWidth
|
state.extenderState.width = state.imageWidth
|
||||||
state.extenderState.height = state.imageHeight
|
state.extenderState.height = state.imageHeight
|
||||||
})
|
})
|
||||||
|
get().updateExtenderByBuiltIn(newValue, 1.5)
|
||||||
},
|
},
|
||||||
|
|
||||||
updateExtenderByBuiltIn: (direction: string, scale: number) => {
|
updateExtenderByBuiltIn: (direction: string, scale: number) => {
|
||||||
@ -920,21 +916,15 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
height = imageHeight
|
height = imageHeight
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case EXTENDER_BUILTIN_X_LEFT:
|
case EXTENDER_X:
|
||||||
x = -Math.ceil(imageWidth * (scale - 1))
|
x = -Math.ceil((imageWidth * (scale - 1)) / 2)
|
||||||
width = Math.ceil(imageWidth * scale)
|
width = Math.ceil(imageWidth * scale)
|
||||||
break
|
break
|
||||||
case EXTENDER_BUILTIN_X_RIGHT:
|
case EXTENDER_Y:
|
||||||
width = Math.ceil(imageWidth * scale)
|
y = -Math.ceil((imageHeight * (scale - 1)) / 2)
|
||||||
break
|
|
||||||
case EXTENDER_BUILTIN_Y_TOP:
|
|
||||||
y = -Math.ceil(imageHeight * (scale - 1))
|
|
||||||
height = Math.ceil(imageHeight * scale)
|
height = Math.ceil(imageHeight * scale)
|
||||||
break
|
break
|
||||||
case EXTENDER_BUILTIN_Y_BOTTOM:
|
case EXTENDER_ALL:
|
||||||
height = Math.ceil(imageHeight * scale)
|
|
||||||
break
|
|
||||||
case EXTENDER_BUILTIN_ALL:
|
|
||||||
x = -Math.ceil((imageWidth * (scale - 1)) / 2)
|
x = -Math.ceil((imageWidth * (scale - 1)) / 2)
|
||||||
y = -Math.ceil((imageHeight * (scale - 1)) / 2)
|
y = -Math.ceil((imageHeight * (scale - 1)) / 2)
|
||||||
width = Math.ceil(imageWidth * scale)
|
width = Math.ceil(imageWidth * scale)
|
||||||
|
Loading…
Reference in New Issue
Block a user