mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
style preset images
This commit is contained in:
parent
2604fd9fde
commit
cc96dcf0ed
@ -31,6 +31,8 @@ from invokeai.app.services.session_processor.session_processor_default import (
|
|||||||
)
|
)
|
||||||
from invokeai.app.services.session_queue.session_queue_sqlite import SqliteSessionQueue
|
from invokeai.app.services.session_queue.session_queue_sqlite import SqliteSessionQueue
|
||||||
from invokeai.app.services.shared.sqlite.sqlite_util import init_db
|
from invokeai.app.services.shared.sqlite.sqlite_util import init_db
|
||||||
|
from invokeai.app.services.style_preset_images.style_preset_images_base import StylePresetImageFileStorageBase
|
||||||
|
from invokeai.app.services.style_preset_images.style_preset_images_default import StylePresetImageFileStorageDisk
|
||||||
from invokeai.app.services.style_preset_records.style_preset_records_sqlite import SqliteStylePresetRecordsStorage
|
from invokeai.app.services.style_preset_records.style_preset_records_sqlite import SqliteStylePresetRecordsStorage
|
||||||
from invokeai.app.services.urls.urls_default import LocalUrlService
|
from invokeai.app.services.urls.urls_default import LocalUrlService
|
||||||
from invokeai.app.services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
|
from invokeai.app.services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
|
||||||
@ -75,6 +77,7 @@ class ApiDependencies:
|
|||||||
image_files = DiskImageFileStorage(f"{output_folder}/images")
|
image_files = DiskImageFileStorage(f"{output_folder}/images")
|
||||||
|
|
||||||
model_images_folder = config.models_path
|
model_images_folder = config.models_path
|
||||||
|
style_preset_images_folder = config.style_preset_images_path
|
||||||
|
|
||||||
db = init_db(config=config, logger=logger, image_files=image_files)
|
db = init_db(config=config, logger=logger, image_files=image_files)
|
||||||
|
|
||||||
@ -111,6 +114,9 @@ class ApiDependencies:
|
|||||||
urls = LocalUrlService()
|
urls = LocalUrlService()
|
||||||
workflow_records = SqliteWorkflowRecordsStorage(db=db)
|
workflow_records = SqliteWorkflowRecordsStorage(db=db)
|
||||||
style_preset_records = SqliteStylePresetRecordsStorage(db=db)
|
style_preset_records = SqliteStylePresetRecordsStorage(db=db)
|
||||||
|
style_preset_images_service = StylePresetImageFileStorageDisk(
|
||||||
|
style_preset_images_folder / "style_preset_images"
|
||||||
|
)
|
||||||
|
|
||||||
services = InvocationServices(
|
services = InvocationServices(
|
||||||
board_image_records=board_image_records,
|
board_image_records=board_image_records,
|
||||||
@ -137,6 +143,7 @@ class ApiDependencies:
|
|||||||
tensors=tensors,
|
tensors=tensors,
|
||||||
conditioning=conditioning,
|
conditioning=conditioning,
|
||||||
style_preset_records=style_preset_records,
|
style_preset_records=style_preset_records,
|
||||||
|
style_preset_images_service=style_preset_images_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
ApiDependencies.invoker = Invoker(services)
|
ApiDependencies.invoker = Invoker(services)
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
from fastapi import APIRouter, Body, HTTPException, Path
|
import io
|
||||||
|
import traceback
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Body, File, Form, HTTPException, Path, UploadFile
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from invokeai.app.api.dependencies import ApiDependencies
|
from invokeai.app.api.dependencies import ApiDependencies
|
||||||
|
from invokeai.app.api.routers.model_manager import IMAGE_MAX_AGE
|
||||||
from invokeai.app.services.style_preset_records.style_preset_records_common import (
|
from invokeai.app.services.style_preset_records.style_preset_records_common import (
|
||||||
|
PresetData,
|
||||||
StylePresetChanges,
|
StylePresetChanges,
|
||||||
StylePresetNotFoundError,
|
StylePresetNotFoundError,
|
||||||
StylePresetRecordDTO,
|
StylePresetRecordWithImage,
|
||||||
StylePresetWithoutId,
|
StylePresetWithoutId,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,15 +23,17 @@ style_presets_router = APIRouter(prefix="/v1/style_presets", tags=["style_preset
|
|||||||
"/i/{style_preset_id}",
|
"/i/{style_preset_id}",
|
||||||
operation_id="get_style_preset",
|
operation_id="get_style_preset",
|
||||||
responses={
|
responses={
|
||||||
200: {"model": StylePresetRecordDTO},
|
200: {"model": StylePresetRecordWithImage},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def get_style_preset(
|
async def get_style_preset(
|
||||||
style_preset_id: str = Path(description="The style preset to get"),
|
style_preset_id: str = Path(description="The style preset to get"),
|
||||||
) -> StylePresetRecordDTO:
|
) -> StylePresetRecordWithImage:
|
||||||
"""Gets a style preset"""
|
"""Gets a style preset"""
|
||||||
try:
|
try:
|
||||||
return ApiDependencies.invoker.services.style_preset_records.get(style_preset_id)
|
image = ApiDependencies.invoker.services.style_preset_images_service.get_url(style_preset_id)
|
||||||
|
style_preset = ApiDependencies.invoker.services.style_preset_records.get(style_preset_id)
|
||||||
|
return StylePresetRecordWithImage(image=image, **style_preset.model_dump())
|
||||||
except StylePresetNotFoundError:
|
except StylePresetNotFoundError:
|
||||||
raise HTTPException(status_code=404, detail="Style preset not found")
|
raise HTTPException(status_code=404, detail="Style preset not found")
|
||||||
|
|
||||||
@ -32,15 +42,45 @@ async def get_style_preset(
|
|||||||
"/i/{style_preset_id}",
|
"/i/{style_preset_id}",
|
||||||
operation_id="update_style_preset",
|
operation_id="update_style_preset",
|
||||||
responses={
|
responses={
|
||||||
200: {"model": StylePresetRecordDTO},
|
200: {"model": StylePresetRecordWithImage},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def update_style_preset(
|
async def update_style_preset(
|
||||||
style_preset_id: str = Path(description="The id of the style preset to update"),
|
style_preset_id: str = Path(description="The id of the style preset to update"),
|
||||||
changes: StylePresetChanges = Body(description="The updated style preset", embed=True),
|
name: str = Form(description="The name of the style preset to create"),
|
||||||
) -> StylePresetRecordDTO:
|
positive_prompt: str = Form(description="The positive prompt of the style preset"),
|
||||||
|
negative_prompt: str = Form(description="The negative prompt of the style preset"),
|
||||||
|
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||||
|
) -> StylePresetRecordWithImage:
|
||||||
"""Updates a style preset"""
|
"""Updates a style preset"""
|
||||||
return ApiDependencies.invoker.services.style_preset_records.update(id=style_preset_id, changes=changes)
|
if image is not None:
|
||||||
|
if not image.content_type or not image.content_type.startswith("image"):
|
||||||
|
raise HTTPException(status_code=415, detail="Not an image")
|
||||||
|
|
||||||
|
contents = await image.read()
|
||||||
|
try:
|
||||||
|
pil_image = Image.open(io.BytesIO(contents))
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
|
||||||
|
raise HTTPException(status_code=415, detail="Failed to read image")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ApiDependencies.invoker.services.style_preset_images_service.save(pil_image, style_preset_id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=409, detail=str(e))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=409, detail=str(e))
|
||||||
|
|
||||||
|
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
|
||||||
|
changes = StylePresetChanges(name=name, preset_data=preset_data)
|
||||||
|
|
||||||
|
style_preset_image = ApiDependencies.invoker.services.style_preset_images_service.get_url(style_preset_id)
|
||||||
|
style_preset = ApiDependencies.invoker.services.style_preset_records.update(id=style_preset_id, changes=changes)
|
||||||
|
return StylePresetRecordWithImage(image=style_preset_image, **style_preset.model_dump())
|
||||||
|
|
||||||
|
|
||||||
@style_presets_router.delete(
|
@style_presets_router.delete(
|
||||||
@ -51,6 +91,7 @@ async def delete_style_preset(
|
|||||||
style_preset_id: str = Path(description="The style preset to delete"),
|
style_preset_id: str = Path(description="The style preset to delete"),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Deletes a style preset"""
|
"""Deletes a style preset"""
|
||||||
|
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id)
|
||||||
ApiDependencies.invoker.services.style_preset_records.delete(style_preset_id)
|
ApiDependencies.invoker.services.style_preset_records.delete(style_preset_id)
|
||||||
|
|
||||||
|
|
||||||
@ -58,23 +99,87 @@ async def delete_style_preset(
|
|||||||
"/",
|
"/",
|
||||||
operation_id="create_style_preset",
|
operation_id="create_style_preset",
|
||||||
responses={
|
responses={
|
||||||
200: {"model": StylePresetRecordDTO},
|
200: {"model": StylePresetRecordWithImage},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def create_style_preset(
|
async def create_style_preset(
|
||||||
style_preset: StylePresetWithoutId = Body(description="The style preset to create", embed=True),
|
name: str = Form(description="The name of the style preset to create"),
|
||||||
) -> StylePresetRecordDTO:
|
positive_prompt: str = Form(description="The positive prompt of the style preset"),
|
||||||
|
negative_prompt: str = Form(description="The negative prompt of the style preset"),
|
||||||
|
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||||
|
) -> StylePresetRecordWithImage:
|
||||||
"""Creates a style preset"""
|
"""Creates a style preset"""
|
||||||
return ApiDependencies.invoker.services.style_preset_records.create(style_preset=style_preset)
|
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
|
||||||
|
style_preset = StylePresetWithoutId(name=name, preset_data=preset_data)
|
||||||
|
new_style_preset = ApiDependencies.invoker.services.style_preset_records.create(style_preset=style_preset)
|
||||||
|
|
||||||
|
if image is not None:
|
||||||
|
if not image.content_type or not image.content_type.startswith("image"):
|
||||||
|
raise HTTPException(status_code=415, detail="Not an image")
|
||||||
|
|
||||||
|
contents = await image.read()
|
||||||
|
try:
|
||||||
|
pil_image = Image.open(io.BytesIO(contents))
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
|
||||||
|
raise HTTPException(status_code=415, detail="Failed to read image")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ApiDependencies.invoker.services.style_preset_images_service.save(pil_image, new_style_preset.id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=409, detail=str(e))
|
||||||
|
|
||||||
|
preset_image = ApiDependencies.invoker.services.style_preset_images_service.get_url(new_style_preset.id)
|
||||||
|
return StylePresetRecordWithImage(image=preset_image, **new_style_preset.model_dump())
|
||||||
|
|
||||||
|
|
||||||
@style_presets_router.get(
|
@style_presets_router.get(
|
||||||
"/",
|
"/",
|
||||||
operation_id="list_style_presets",
|
operation_id="list_style_presets",
|
||||||
responses={
|
responses={
|
||||||
200: {"model": list[StylePresetRecordDTO]},
|
200: {"model": list[StylePresetRecordWithImage]},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def list_style_presets() -> list[StylePresetRecordDTO]:
|
async def list_style_presets() -> list[StylePresetRecordWithImage]:
|
||||||
"""Gets a page of style presets"""
|
"""Gets a page of style presets"""
|
||||||
return ApiDependencies.invoker.services.style_preset_records.get_many()
|
style_presets_with_image: list[StylePresetRecordWithImage] = []
|
||||||
|
style_presets = ApiDependencies.invoker.services.style_preset_records.get_many()
|
||||||
|
for preset in style_presets:
|
||||||
|
image = ApiDependencies.invoker.services.style_preset_images_service.get_url(preset.id)
|
||||||
|
style_preset_with_image = StylePresetRecordWithImage(image=image, **preset.model_dump())
|
||||||
|
style_presets_with_image.append(style_preset_with_image)
|
||||||
|
|
||||||
|
return style_presets_with_image
|
||||||
|
|
||||||
|
|
||||||
|
@style_presets_router.get(
|
||||||
|
"/i/{style_preset_id}/image",
|
||||||
|
operation_id="get_style_preset_image",
|
||||||
|
responses={
|
||||||
|
200: {
|
||||||
|
"description": "The style preset image was fetched successfully",
|
||||||
|
},
|
||||||
|
400: {"description": "Bad request"},
|
||||||
|
404: {"description": "The style preset image could not be found"},
|
||||||
|
},
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
|
async def get_style_preset_image(
|
||||||
|
style_preset_id: str = Path(description="The id of the style preset image to get"),
|
||||||
|
) -> FileResponse:
|
||||||
|
"""Gets an image file that previews the model"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
path = ApiDependencies.invoker.services.style_preset_images_service.get_path(style_preset_id)
|
||||||
|
|
||||||
|
response = FileResponse(
|
||||||
|
path,
|
||||||
|
media_type="image/png",
|
||||||
|
filename=style_preset_id + ".png",
|
||||||
|
content_disposition_type="inline",
|
||||||
|
)
|
||||||
|
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
|
||||||
|
return response
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
@ -153,6 +153,7 @@ class InvokeAIAppConfig(BaseSettings):
|
|||||||
db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.")
|
db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.")
|
||||||
outputs_dir: Path = Field(default=Path("outputs"), description="Path to directory for outputs.")
|
outputs_dir: Path = Field(default=Path("outputs"), description="Path to directory for outputs.")
|
||||||
custom_nodes_dir: Path = Field(default=Path("nodes"), description="Path to directory for custom nodes.")
|
custom_nodes_dir: Path = Field(default=Path("nodes"), description="Path to directory for custom nodes.")
|
||||||
|
style_preset_images_path: Path = Field(default=Path("style_preset_images"), description="Path to directory for style preset images.")
|
||||||
|
|
||||||
# LOGGING
|
# LOGGING
|
||||||
log_handlers: list[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".')
|
log_handlers: list[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".')
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from invokeai.app.services.object_serializer.object_serializer_base import ObjectSerializerBase
|
from invokeai.app.services.object_serializer.object_serializer_base import ObjectSerializerBase
|
||||||
|
from invokeai.app.services.style_preset_images.style_preset_images_base import StylePresetImageFileStorageBase
|
||||||
from invokeai.app.services.style_preset_records.style_preset_records_base import StylePresetRecordsStorageBase
|
from invokeai.app.services.style_preset_records.style_preset_records_base import StylePresetRecordsStorageBase
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -63,6 +64,7 @@ class InvocationServices:
|
|||||||
tensors: "ObjectSerializerBase[torch.Tensor]",
|
tensors: "ObjectSerializerBase[torch.Tensor]",
|
||||||
conditioning: "ObjectSerializerBase[ConditioningFieldData]",
|
conditioning: "ObjectSerializerBase[ConditioningFieldData]",
|
||||||
style_preset_records: "StylePresetRecordsStorageBase",
|
style_preset_records: "StylePresetRecordsStorageBase",
|
||||||
|
style_preset_images_service: "StylePresetImageFileStorageBase",
|
||||||
):
|
):
|
||||||
self.board_images = board_images
|
self.board_images = board_images
|
||||||
self.board_image_records = board_image_records
|
self.board_image_records = board_image_records
|
||||||
@ -88,3 +90,4 @@ class InvocationServices:
|
|||||||
self.tensors = tensors
|
self.tensors = tensors
|
||||||
self.conditioning = conditioning
|
self.conditioning = conditioning
|
||||||
self.style_preset_records = style_preset_records
|
self.style_preset_records = style_preset_records
|
||||||
|
self.style_preset_images_service = style_preset_images_service
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL.Image import Image as PILImageType
|
||||||
|
|
||||||
|
|
||||||
|
class StylePresetImageFileStorageBase(ABC):
|
||||||
|
"""Low-level service responsible for storing and retrieving image files."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get(self, style_preset_id: str) -> PILImageType:
|
||||||
|
"""Retrieves a style preset image as PIL Image."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_path(self, style_preset_id: str) -> Path:
|
||||||
|
"""Gets the internal path to a style preset image."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_url(self, style_preset_id: str) -> str | None:
|
||||||
|
"""Gets the URL to fetch a style preset image."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def save(self, image: PILImageType, style_preset_id: str) -> None:
|
||||||
|
"""Saves a style preset image."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete(self, style_preset_id: str) -> None:
|
||||||
|
"""Deletes a style preset image."""
|
||||||
|
pass
|
@ -0,0 +1,19 @@
|
|||||||
|
class StylePresetImageFileNotFoundException(Exception):
|
||||||
|
"""Raised when an image file is not found in storage."""
|
||||||
|
|
||||||
|
def __init__(self, message="Style preset image file not found"):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class StylePresetImageFileSaveException(Exception):
|
||||||
|
"""Raised when an image cannot be saved."""
|
||||||
|
|
||||||
|
def __init__(self, message="Style preset image file not saved"):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class StylePresetImageFileDeleteException(Exception):
|
||||||
|
"""Raised when an image cannot be deleted."""
|
||||||
|
|
||||||
|
def __init__(self, message="Style preset image file not deleted"):
|
||||||
|
super().__init__(message)
|
@ -0,0 +1,84 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL.Image import Image as PILImageType
|
||||||
|
from send2trash import send2trash
|
||||||
|
|
||||||
|
from invokeai.app.services.invoker import Invoker
|
||||||
|
from invokeai.app.services.style_preset_images.style_preset_images_base import StylePresetImageFileStorageBase
|
||||||
|
from invokeai.app.services.style_preset_images.style_preset_images_common import (
|
||||||
|
StylePresetImageFileDeleteException,
|
||||||
|
StylePresetImageFileNotFoundException,
|
||||||
|
StylePresetImageFileSaveException,
|
||||||
|
)
|
||||||
|
from invokeai.app.util.misc import uuid_string
|
||||||
|
from invokeai.app.util.thumbnails import make_thumbnail
|
||||||
|
|
||||||
|
|
||||||
|
class StylePresetImageFileStorageDisk(StylePresetImageFileStorageBase):
|
||||||
|
"""Stores images on disk"""
|
||||||
|
|
||||||
|
def __init__(self, style_preset_images_folder: Path):
|
||||||
|
self._style_preset_images_folder = style_preset_images_folder
|
||||||
|
self._validate_storage_folders()
|
||||||
|
|
||||||
|
def start(self, invoker: Invoker) -> None:
|
||||||
|
self._invoker = invoker
|
||||||
|
|
||||||
|
def get(self, style_preset_id: str) -> PILImageType:
|
||||||
|
try:
|
||||||
|
path = self.get_path(style_preset_id)
|
||||||
|
|
||||||
|
if not self._validate_path(path):
|
||||||
|
raise StylePresetImageFileNotFoundException
|
||||||
|
|
||||||
|
return Image.open(path)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise StylePresetImageFileNotFoundException from e
|
||||||
|
|
||||||
|
def save(self, image: PILImageType, style_preset_id: str) -> None:
|
||||||
|
try:
|
||||||
|
self._validate_storage_folders()
|
||||||
|
image_path = self._style_preset_images_folder / (style_preset_id + ".webp")
|
||||||
|
thumbnail = make_thumbnail(image, 256)
|
||||||
|
thumbnail.save(image_path, format="webp")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise StylePresetImageFileSaveException from e
|
||||||
|
|
||||||
|
def get_path(self, style_preset_id: str) -> Path:
|
||||||
|
path = self._style_preset_images_folder / (style_preset_id + ".webp")
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_url(self, style_preset_id: str) -> str | None:
|
||||||
|
path = self.get_path(style_preset_id)
|
||||||
|
if not self._validate_path(path):
|
||||||
|
return
|
||||||
|
|
||||||
|
url = self._invoker.services.urls.get_style_preset_image_url(style_preset_id)
|
||||||
|
|
||||||
|
# The image URL never changes, so we must add random query string to it to prevent caching
|
||||||
|
url += f"?{uuid_string()}"
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
def delete(self, style_preset_id: str) -> None:
|
||||||
|
try:
|
||||||
|
path = self.get_path(style_preset_id)
|
||||||
|
|
||||||
|
if not self._validate_path(path):
|
||||||
|
raise StylePresetImageFileNotFoundException
|
||||||
|
|
||||||
|
send2trash(path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise StylePresetImageFileDeleteException from e
|
||||||
|
|
||||||
|
def _validate_path(self, path: Path) -> bool:
|
||||||
|
"""Validates the path given for an image."""
|
||||||
|
return path.exists()
|
||||||
|
|
||||||
|
def _validate_storage_folders(self) -> None:
|
||||||
|
"""Checks if the required folders exist and create them if they don't"""
|
||||||
|
self._style_preset_images_folder.mkdir(parents=True, exist_ok=True)
|
@ -39,3 +39,7 @@ class StylePresetRecordDTO(StylePresetWithoutId):
|
|||||||
|
|
||||||
|
|
||||||
StylePresetRecordDTOValidator = TypeAdapter(StylePresetRecordDTO)
|
StylePresetRecordDTOValidator = TypeAdapter(StylePresetRecordDTO)
|
||||||
|
|
||||||
|
|
||||||
|
class StylePresetRecordWithImage(StylePresetRecordDTO):
|
||||||
|
image: Optional[str] = Field(description="The path for image")
|
||||||
|
@ -13,3 +13,8 @@ class UrlServiceBase(ABC):
|
|||||||
def get_model_image_url(self, model_key: str) -> str:
|
def get_model_image_url(self, model_key: str) -> str:
|
||||||
"""Gets the URL for a model image"""
|
"""Gets the URL for a model image"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_style_preset_image_url(self, style_preset_id: str) -> str:
|
||||||
|
"""Gets the URL for a style preset image"""
|
||||||
|
pass
|
||||||
|
@ -19,3 +19,6 @@ class LocalUrlService(UrlServiceBase):
|
|||||||
|
|
||||||
def get_model_image_url(self, model_key: str) -> str:
|
def get_model_image_url(self, model_key: str) -> str:
|
||||||
return f"{self._base_url_v2}/models/i/{model_key}/image"
|
return f"{self._base_url_v2}/models/i/{model_key}/image"
|
||||||
|
|
||||||
|
def get_style_preset_image_url(self, style_preset_id: str) -> str:
|
||||||
|
return f"{self._base_url}/style_presets/i/{style_preset_id}/image"
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
PiFlowArrowBold,
|
PiFlowArrowBold,
|
||||||
PiFoldersBold,
|
PiFoldersBold,
|
||||||
PiImagesBold,
|
PiImagesBold,
|
||||||
|
PiPaintBrushBold,
|
||||||
PiPlantBold,
|
PiPlantBold,
|
||||||
PiQuotesBold,
|
PiQuotesBold,
|
||||||
PiShareFatBold,
|
PiShareFatBold,
|
||||||
@ -39,6 +40,8 @@ import {
|
|||||||
} from 'react-icons/pi';
|
} from 'react-icons/pi';
|
||||||
import { useStarImagesMutation, useUnstarImagesMutation } from 'services/api/endpoints/images';
|
import { useStarImagesMutation, useUnstarImagesMutation } from 'services/api/endpoints/images';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
|
import { isMenuOpenChanged } from '../../../stylePresets/store/stylePresetSlice';
|
||||||
|
import { createPresetFromImageChanged } from '../../../stylePresets/store/stylePresetModalSlice';
|
||||||
|
|
||||||
type SingleSelectionMenuItemsProps = {
|
type SingleSelectionMenuItemsProps = {
|
||||||
imageDTO: ImageDTO;
|
imageDTO: ImageDTO;
|
||||||
@ -130,6 +133,11 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
dispatch(setActiveTab('upscaling'));
|
dispatch(setActiveTab('upscaling'));
|
||||||
}, [dispatch, imageDTO]);
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
|
const handleCreatePreset = useCallback(() => {
|
||||||
|
dispatch(createPresetFromImageChanged(imageDTO));
|
||||||
|
dispatch(isMenuOpenChanged(true));
|
||||||
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem as="a" href={imageDTO.image_url} target="_blank" icon={<PiShareFatBold />}>
|
<MenuItem as="a" href={imageDTO.image_url} target="_blank" icon={<PiShareFatBold />}>
|
||||||
@ -182,6 +190,13 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
|||||||
>
|
>
|
||||||
{t('parameters.useAll')}
|
{t('parameters.useAll')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiPaintBrushBold />}
|
||||||
|
onClickCapture={handleCreatePreset}
|
||||||
|
isDisabled={isLoadingMetadata || !hasPrompts}
|
||||||
|
>
|
||||||
|
Create Preset
|
||||||
|
</MenuItem>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem icon={<PiShareFatBold />} onClickCapture={handleSendToImageToImage} id="send-to-img2img">
|
<MenuItem icon={<PiShareFatBold />} onClickCapture={handleSendToImageToImage} id="send-to-img2img">
|
||||||
{t('parameters.sendToImg2Img')}
|
{t('parameters.sendToImg2Img')}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
|
|
||||||
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
||||||
import { activeStylePresetChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import type { MouseEventHandler} from 'react';
|
import type { MouseEventHandler } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { CgPushDown } from 'react-icons/cg';
|
import { CgPushDown } from 'react-icons/cg';
|
||||||
import { PiXBold } from 'react-icons/pi';
|
import { PiXBold } from 'react-icons/pi';
|
||||||
|
import StylePresetImage from './StylePresetImage';
|
||||||
|
|
||||||
export const ActiveStylePreset = () => {
|
export const ActiveStylePreset = () => {
|
||||||
const { activeStylePreset } = useAppSelector((s) => s.stylePreset);
|
const { activeStylePreset } = useAppSelector((s) => s.stylePreset);
|
||||||
@ -40,7 +40,7 @@ export const ActiveStylePreset = () => {
|
|||||||
<>
|
<>
|
||||||
<Flex justifyContent="space-between" w="full" alignItems="center">
|
<Flex justifyContent="space-between" w="full" alignItems="center">
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<ModelImage image_url={null} />
|
<StylePresetImage presetImageUrl={activeStylePreset.image} />
|
||||||
<Flex flexDir="column">
|
<Flex flexDir="column">
|
||||||
<Text variant="subtext" fontSize="xs">
|
<Text variant="subtext" fontSize="xs">
|
||||||
Prompt Style
|
Prompt Style
|
||||||
|
@ -4,21 +4,23 @@ import { useStylePresetFields } from 'features/stylePresets/hooks/useStylePreset
|
|||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import type { SubmitHandler} from 'react-hook-form';
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { PiBracketsCurlyBold } from 'react-icons/pi';
|
import { PiBracketsCurlyBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import { StylePresetPromptField } from './StylePresetPromptField';
|
import { StylePresetPromptField } from './StylePresetPromptField';
|
||||||
|
import { StylePresetImageField } from './StylePresetImageField';
|
||||||
|
|
||||||
export type StylePresetFormData = {
|
export type StylePresetFormData = {
|
||||||
name: string;
|
name: string;
|
||||||
positivePrompt: string;
|
positivePrompt: string;
|
||||||
negativePrompt: string;
|
negativePrompt: string;
|
||||||
|
image: File | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordDTO | null }) => {
|
export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordWithImage | null }) => {
|
||||||
const [createStylePreset] = useCreateStylePresetMutation();
|
const [createStylePreset] = useCreateStylePresetMutation();
|
||||||
const [updateStylePreset] = useUpdateStylePresetMutation();
|
const [updateStylePreset] = useUpdateStylePresetMutation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -31,20 +33,21 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
|
|||||||
|
|
||||||
const handleClickSave = useCallback<SubmitHandler<StylePresetFormData>>(
|
const handleClickSave = useCallback<SubmitHandler<StylePresetFormData>>(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
|
const payload = {
|
||||||
|
name: data.name,
|
||||||
|
positive_prompt: data.positivePrompt,
|
||||||
|
negative_prompt: data.negativePrompt,
|
||||||
|
image: data.image,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (updatingPreset) {
|
if (updatingPreset) {
|
||||||
await updateStylePreset({
|
await updateStylePreset({
|
||||||
id: updatingPreset.id,
|
id: updatingPreset.id,
|
||||||
changes: {
|
...payload,
|
||||||
name: data.name,
|
|
||||||
preset_data: { positive_prompt: data.positivePrompt, negative_prompt: data.negativePrompt },
|
|
||||||
},
|
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
} else {
|
} else {
|
||||||
await createStylePreset({
|
await createStylePreset(payload).unwrap();
|
||||||
name: data.name,
|
|
||||||
preset_data: { positive_prompt: data.positivePrompt, negative_prompt: data.negativePrompt },
|
|
||||||
}).unwrap();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
@ -61,18 +64,20 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap="4">
|
<Flex flexDir="column" gap="4">
|
||||||
<FormControl>
|
<Flex alignItems="center" gap="4">
|
||||||
|
<StylePresetImageField control={control} name="image" />
|
||||||
|
<FormControl orientation="vertical">
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<Input size="md" {...register('name')} />
|
<Input size="md" {...register('name')} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Flex flexDir="column" bgColor="base.750" borderRadius="base" padding="10px" gap="10px">
|
</Flex>
|
||||||
|
|
||||||
|
<StylePresetPromptField label="Positive Prompt" control={control} name="positivePrompt" />
|
||||||
|
<StylePresetPromptField label="Negative Prompt" control={control} name="negativePrompt" />
|
||||||
<Text variant="subtext">
|
<Text variant="subtext">
|
||||||
Use the <Icon as={PiBracketsCurlyBold} /> button to specify where your manual prompt should be included in the
|
Use the <Icon as={PiBracketsCurlyBold} /> button to specify where your manual prompt should be included in the
|
||||||
template. If you do not provide one, the template will be appended to your prompt.
|
template. If you do not provide one, the template will be appended to your prompt.
|
||||||
</Text>
|
</Text>
|
||||||
<StylePresetPromptField label="Positive Prompt" control={control} name="positivePrompt" />
|
|
||||||
<StylePresetPromptField label="Negative Prompt" control={control} name="negativePrompt" />
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Button onClick={handleSubmit(handleClickSave)}>Save</Button>
|
<Button onClick={handleSubmit(handleClickSave)}>Save</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { Flex, Icon, Image } from '@invoke-ai/ui-library';
|
||||||
|
import { typedMemo } from 'common/util/typedMemo';
|
||||||
|
import { PiImage } from 'react-icons/pi';
|
||||||
|
|
||||||
|
const IMAGE_THUMBNAIL_SIZE = '40px';
|
||||||
|
const FALLBACK_ICON_SIZE = '24px';
|
||||||
|
|
||||||
|
const StylePresetImage = ({ presetImageUrl }: { presetImageUrl: string | null }) => {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src={presetImageUrl || ''}
|
||||||
|
fallbackStrategy="beforeLoadOrError"
|
||||||
|
fallback={
|
||||||
|
<Flex
|
||||||
|
height={IMAGE_THUMBNAIL_SIZE}
|
||||||
|
minWidth={IMAGE_THUMBNAIL_SIZE}
|
||||||
|
bg="base.650"
|
||||||
|
borderRadius="base"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Icon color="base.500" as={PiImage} boxSize={FALLBACK_ICON_SIZE} />
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
objectFit="cover"
|
||||||
|
objectPosition="50% 50%"
|
||||||
|
height={IMAGE_THUMBNAIL_SIZE}
|
||||||
|
width={IMAGE_THUMBNAIL_SIZE}
|
||||||
|
minHeight={IMAGE_THUMBNAIL_SIZE}
|
||||||
|
minWidth={IMAGE_THUMBNAIL_SIZE}
|
||||||
|
borderRadius="base"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default typedMemo(StylePresetImage);
|
@ -0,0 +1,79 @@
|
|||||||
|
import { Tooltip, Flex, Button, Icon, Box, Image, IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import { PiArrowCounterClockwiseBold, PiUploadSimpleBold } from 'react-icons/pi';
|
||||||
|
import { useController, UseControllerProps } from 'react-hook-form';
|
||||||
|
import { StylePresetFormData } from './StylePresetForm';
|
||||||
|
|
||||||
|
export const StylePresetImageField = (props: UseControllerProps<StylePresetFormData>) => {
|
||||||
|
const { field } = useController(props);
|
||||||
|
const onDropAccepted = useCallback(
|
||||||
|
(files: File[]) => {
|
||||||
|
const file = files[0];
|
||||||
|
if (file) {
|
||||||
|
field.onChange(file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[field, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleResetImage = useCallback(() => {
|
||||||
|
field.onChange(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { getInputProps, getRootProps } = useDropzone({
|
||||||
|
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||||
|
onDropAccepted,
|
||||||
|
noDrag: true,
|
||||||
|
multiple: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (field.value) {
|
||||||
|
return (
|
||||||
|
<Box position="relative" flexShrink={0}>
|
||||||
|
<Image
|
||||||
|
src={URL.createObjectURL(field.value as File)}
|
||||||
|
objectFit="cover"
|
||||||
|
objectPosition="50% 50%"
|
||||||
|
w={65}
|
||||||
|
h={65}
|
||||||
|
minWidth={65}
|
||||||
|
borderRadius="base"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
position="absolute"
|
||||||
|
insetInlineEnd={0}
|
||||||
|
insetBlockStart={0}
|
||||||
|
onClick={handleResetImage}
|
||||||
|
aria-label={t('modelManager.deleteModelImage')}
|
||||||
|
tooltip={t('modelManager.deleteModelImage')}
|
||||||
|
icon={<PiArrowCounterClockwiseBold />}
|
||||||
|
size="md"
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip label={t('modelManager.uploadImage')}>
|
||||||
|
<Flex
|
||||||
|
as={Button}
|
||||||
|
w={65}
|
||||||
|
h={65}
|
||||||
|
opacity={0.3}
|
||||||
|
borderRadius="base"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
flexShrink={0}
|
||||||
|
{...getRootProps()}
|
||||||
|
>
|
||||||
|
<Icon as={PiUploadSimpleBold} w={8} h={8} />
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { PiCaretDownBold } from 'react-icons/pi';
|
import { PiCaretDownBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import { StylePresetListItem } from './StylePresetListItem';
|
import { StylePresetListItem } from './StylePresetListItem';
|
||||||
|
|
||||||
export const StylePresetList = ({ title, data }: { title: string; data: StylePresetRecordDTO[] }) => {
|
export const StylePresetList = ({ title, data }: { title: string; data: StylePresetRecordWithImage[] }) => {
|
||||||
const { onToggle, isOpen } = useDisclosure({ defaultIsOpen: true });
|
const { onToggle, isOpen } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
|
@ -1,33 +1,43 @@
|
|||||||
import { Badge, Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
import { Badge, Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||||
|
import type { MouseEvent } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
|
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
|
||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
|
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||||
|
import StylePresetImage from './StylePresetImage';
|
||||||
|
|
||||||
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }) => {
|
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithImage }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [deleteStylePreset] = useDeleteStylePresetMutation();
|
const [deleteStylePreset] = useDeleteStylePresetMutation();
|
||||||
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||||
|
|
||||||
const handleClickEdit = useCallback(() => {
|
const handleClickEdit = useCallback(
|
||||||
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
dispatch(updatingStylePresetChanged(preset));
|
dispatch(updatingStylePresetChanged(preset));
|
||||||
dispatch(isModalOpenChanged(true));
|
dispatch(isModalOpenChanged(true));
|
||||||
}, [dispatch, preset]);
|
},
|
||||||
|
[dispatch, preset]
|
||||||
|
);
|
||||||
|
|
||||||
const handleClickApply = useCallback(() => {
|
const handleClickApply = useCallback(() => {
|
||||||
dispatch(activeStylePresetChanged(preset));
|
dispatch(activeStylePresetChanged(preset));
|
||||||
dispatch(isMenuOpenChanged(false));
|
dispatch(isMenuOpenChanged(false));
|
||||||
}, [dispatch, preset]);
|
}, [dispatch, preset]);
|
||||||
|
|
||||||
const handleDeletePreset = useCallback(async () => {
|
const handleDeletePreset = useCallback(
|
||||||
|
async (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
try {
|
try {
|
||||||
await deleteStylePreset(preset.id);
|
await deleteStylePreset(preset.id);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [preset]);
|
},
|
||||||
|
[preset]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -40,7 +50,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
w="full"
|
w="full"
|
||||||
>
|
>
|
||||||
<ModelImage image_url={null} />
|
<StylePresetImage presetImageUrl={preset.image} />
|
||||||
<Flex flexDir="column" w="full">
|
<Flex flexDir="column" w="full">
|
||||||
<Flex w="full" justifyContent="space-between">
|
<Flex w="full" justifyContent="space-between">
|
||||||
<Flex alignItems="center" gap="2">
|
<Flex alignItems="center" gap="2">
|
||||||
|
@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
import type { StylePresetRecordDTO} from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import { StylePresetList } from './StylePresetList';
|
import { StylePresetList } from './StylePresetList';
|
||||||
@ -18,7 +18,7 @@ export const StylePresetMenu = () => {
|
|||||||
data?.filter((preset) => preset.name.toLowerCase().includes(searchTerm.toLowerCase())) || EMPTY_ARRAY;
|
data?.filter((preset) => preset.name.toLowerCase().includes(searchTerm.toLowerCase())) || EMPTY_ARRAY;
|
||||||
|
|
||||||
const groupedData = filteredData.reduce(
|
const groupedData = filteredData.reduce(
|
||||||
(acc: { defaultPresets: StylePresetRecordDTO[]; presets: StylePresetRecordDTO[] }, preset) => {
|
(acc: { defaultPresets: StylePresetRecordWithImage[]; presets: StylePresetRecordWithImage[] }, preset) => {
|
||||||
if (preset.is_default) {
|
if (preset.is_default) {
|
||||||
acc.defaultPresets.push(preset);
|
acc.defaultPresets.push(preset);
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,7 +24,7 @@ export const StylePresetPromptField = (props: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const value = useMemo(() => {
|
const value = useMemo(() => {
|
||||||
return field.value;
|
return field.value as string;
|
||||||
}, [field.value]);
|
}, [field.value]);
|
||||||
|
|
||||||
const insertPromptPlaceholder = useCallback(() => {
|
const insertPromptPlaceholder = useCallback(() => {
|
||||||
@ -40,7 +40,7 @@ export const StylePresetPromptField = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, [value, field, textareaRef]);
|
}, [value, field, textareaRef]);
|
||||||
|
|
||||||
const isPromptPresent = useMemo(() => value.includes(PRESET_PLACEHOLDER), [value]);
|
const isPromptPresent = useMemo(() => value?.includes(PRESET_PLACEHOLDER), [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl orientation="vertical">
|
<FormControl orientation="vertical">
|
||||||
|
@ -1,17 +1,45 @@
|
|||||||
import { useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
|
import { useAppSelector } from '../../../app/store/storeHooks';
|
||||||
|
import { useDebouncedMetadata } from '../../../services/api/hooks/useDebouncedMetadata';
|
||||||
|
import { handlers } from '../../metadata/util/handlers';
|
||||||
|
import { useImageUrlToBlob } from '../../../common/hooks/useImageUrlToBlob';
|
||||||
|
|
||||||
|
|
||||||
export const useStylePresetFields = (preset: StylePresetRecordDTO | null) => {
|
export const useStylePresetFields = (preset: StylePresetRecordWithImage | null) => {
|
||||||
const stylePresetFieldDefaults = useMemo(() => {
|
const createPresetFromImage = useAppSelector(s => s.stylePresetModal.createPresetFromImage)
|
||||||
|
|
||||||
|
const imageUrlToBlob = useImageUrlToBlob();
|
||||||
|
|
||||||
|
const getStylePresetFieldDefaults = useCallback(async () => {
|
||||||
|
if (preset) {
|
||||||
|
let file: File | null = null;
|
||||||
|
if (preset.image) {
|
||||||
|
const blob = await imageUrlToBlob(preset.image);
|
||||||
|
if (blob) {
|
||||||
|
file = new File([blob], "name");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: preset ? preset.name : '',
|
name: preset.name,
|
||||||
positivePrompt: preset ? preset.preset_data.positive_prompt : '',
|
positivePrompt: preset.preset_data.positive_prompt,
|
||||||
negativePrompt: preset ? preset.preset_data.negative_prompt : ''
|
negativePrompt: preset.preset_data.negative_prompt,
|
||||||
|
image: file
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "",
|
||||||
|
positivePrompt: "",
|
||||||
|
negativePrompt: "",
|
||||||
|
image: null
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
preset
|
preset
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return stylePresetFieldDefaults;
|
return getStylePresetFieldDefaults;
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import type { StylePresetModalState } from './types';
|
import type { StylePresetModalState, StylePresetPrefillOptions } from './types';
|
||||||
|
import { ImageDTO } from '../../../services/api/types';
|
||||||
|
|
||||||
|
|
||||||
export const initialState: StylePresetModalState = {
|
export const initialState: StylePresetModalState = {
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
updatingStylePreset: null,
|
updatingStylePreset: null,
|
||||||
|
createPresetFromImage: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -19,12 +21,15 @@ export const stylePresetModalSlice = createSlice({
|
|||||||
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isModalOpen = action.payload;
|
state.isModalOpen = action.payload;
|
||||||
},
|
},
|
||||||
updatingStylePresetChanged: (state, action: PayloadAction<StylePresetRecordDTO | null>) => {
|
updatingStylePresetChanged: (state, action: PayloadAction<StylePresetRecordWithImage | null>) => {
|
||||||
state.updatingStylePreset = action.payload;
|
state.updatingStylePreset = action.payload;
|
||||||
},
|
},
|
||||||
|
createPresetFromImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
|
||||||
|
state.createPresetFromImage = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { isModalOpenChanged, updatingStylePresetChanged } = stylePresetModalSlice.actions;
|
export const { isModalOpenChanged, updatingStylePresetChanged, createPresetFromImageChanged } = stylePresetModalSlice.actions;
|
||||||
|
|
||||||
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;
|
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
|
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import type { StylePresetState } from './types';
|
import type { StylePresetState } from './types';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export const stylePresetSlice = createSlice({
|
|||||||
isMenuOpenChanged: (state, action: PayloadAction<boolean>) => {
|
isMenuOpenChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isMenuOpen = action.payload;
|
state.isMenuOpen = action.payload;
|
||||||
},
|
},
|
||||||
activeStylePresetChanged: (state, action: PayloadAction<StylePresetRecordDTO | null>) => {
|
activeStylePresetChanged: (state, action: PayloadAction<StylePresetRecordWithImage | null>) => {
|
||||||
state.activeStylePreset = action.payload;
|
state.activeStylePreset = action.payload;
|
||||||
},
|
},
|
||||||
searchTermChanged: (state, action: PayloadAction<string>) => {
|
searchTermChanged: (state, action: PayloadAction<string>) => {
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import type { StylePresetRecordDTO } from "services/api/endpoints/stylePresets";
|
import type { StylePresetRecordWithImage } from "services/api/endpoints/stylePresets";
|
||||||
|
import { ImageDTO } from "../../../services/api/types";
|
||||||
|
|
||||||
export type StylePresetModalState = {
|
export type StylePresetModalState = {
|
||||||
isModalOpen: boolean;
|
isModalOpen: boolean;
|
||||||
updatingStylePreset: StylePresetRecordDTO | null;
|
updatingStylePreset: StylePresetRecordWithImage | null;
|
||||||
|
createPresetFromImage: ImageDTO | null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StylePresetPrefillOptions = {
|
||||||
|
positivePrompt: string;
|
||||||
|
negativePrompt: string;
|
||||||
|
image: File;
|
||||||
|
}
|
||||||
|
|
||||||
export type StylePresetState = {
|
export type StylePresetState = {
|
||||||
isMenuOpen: boolean;
|
isMenuOpen: boolean;
|
||||||
activeStylePreset: StylePresetRecordDTO | null;
|
activeStylePreset: StylePresetRecordWithImage | null;
|
||||||
searchTerm: string
|
searchTerm: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import type { paths } from 'services/api/schema';
|
|||||||
|
|
||||||
import { api, buildV1Url, LIST_TAG } from '..';
|
import { api, buildV1Url, LIST_TAG } from '..';
|
||||||
|
|
||||||
export type StylePresetRecordDTO = paths['/api/v1/style_presets/i/{style_preset_id}']['get']['responses']['200']['content']['application/json']
|
export type StylePresetRecordWithImage = paths['/api/v1/style_presets/i/{style_preset_id}']['get']['responses']['200']['content']['application/json']
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an endpoint URL for the style_presets router
|
* Builds an endpoint URL for the style_presets router
|
||||||
@ -36,13 +36,23 @@ export const stylePresetsApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
createStylePreset: build.mutation<
|
createStylePreset: build.mutation<
|
||||||
paths['/api/v1/style_presets/']['post']['responses']['200']['content']['application/json'],
|
paths['/api/v1/style_presets/']['post']['responses']['200']['content']['application/json'],
|
||||||
paths['/api/v1/style_presets/']['post']['requestBody']['content']['application/json']['style_preset']
|
paths['/api/v1/style_presets/']['post']['requestBody']['content']['multipart/form-data']
|
||||||
>({
|
>({
|
||||||
query: (style_preset) => ({
|
query: ({ name, positive_prompt, negative_prompt, image }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (image) {
|
||||||
|
formData.append('image', image);
|
||||||
|
}
|
||||||
|
formData.append('name', name);
|
||||||
|
formData.append('positive_prompt', positive_prompt);
|
||||||
|
formData.append('negative_prompt', negative_prompt);
|
||||||
|
|
||||||
|
return {
|
||||||
url: buildStylePresetsUrl(),
|
url: buildStylePresetsUrl(),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { style_preset },
|
body: formData,
|
||||||
}),
|
};
|
||||||
|
},
|
||||||
invalidatesTags: [
|
invalidatesTags: [
|
||||||
{ type: 'StylePreset', id: LIST_TAG },
|
{ type: 'StylePreset', id: LIST_TAG },
|
||||||
{ type: 'StylePreset', id: LIST_TAG },
|
{ type: 'StylePreset', id: LIST_TAG },
|
||||||
@ -50,16 +60,25 @@ export const stylePresetsApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
updateStylePreset: build.mutation<
|
updateStylePreset: build.mutation<
|
||||||
paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['responses']['200']['content']['application/json'],
|
paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['responses']['200']['content']['application/json'],
|
||||||
{
|
paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['requestBody']['content']['multipart/form-data'] & { id: string }
|
||||||
id: string;
|
|
||||||
changes: paths['/api/v1/style_presets/i/{style_preset_id}']['patch']['requestBody']['content']['application/json']['changes'];
|
|
||||||
}
|
|
||||||
>({
|
>({
|
||||||
query: ({ id, changes }) => ({
|
query: ({ id, name, positive_prompt, negative_prompt, image }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (image) {
|
||||||
|
formData.append('image', image);
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append('name', name);
|
||||||
|
formData.append('positive_prompt', positive_prompt);
|
||||||
|
formData.append('negative_prompt', negative_prompt);
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
url: buildStylePresetsUrl(`i/${id}`),
|
url: buildStylePresetsUrl(`i/${id}`),
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: { changes },
|
body: formData
|
||||||
}),
|
}
|
||||||
|
},
|
||||||
invalidatesTags: (response, error, { id }) => [
|
invalidatesTags: (response, error, { id }) => [
|
||||||
{ type: 'StylePreset', id: LIST_TAG },
|
{ type: 'StylePreset', id: LIST_TAG },
|
||||||
{ type: 'StylePreset', id: id },
|
{ type: 'StylePreset', id: id },
|
||||||
|
@ -561,6 +561,13 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations["create_style_preset"];
|
post: operations["create_style_preset"];
|
||||||
};
|
};
|
||||||
|
"/api/v1/style_presets/i/{style_preset_id}/image": {
|
||||||
|
/**
|
||||||
|
* Get Style Preset Image
|
||||||
|
* @description Gets an image file that previews the model
|
||||||
|
*/
|
||||||
|
get: operations["get_style_preset_image"];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
@ -1146,8 +1153,26 @@ export type components = {
|
|||||||
};
|
};
|
||||||
/** Body_create_style_preset */
|
/** Body_create_style_preset */
|
||||||
Body_create_style_preset: {
|
Body_create_style_preset: {
|
||||||
/** @description The style preset to create */
|
/**
|
||||||
style_preset: components["schemas"]["StylePresetWithoutId"];
|
* Name
|
||||||
|
* @description The name of the style preset to create
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Positive Prompt
|
||||||
|
* @description The positive prompt of the style preset
|
||||||
|
*/
|
||||||
|
positive_prompt: string;
|
||||||
|
/**
|
||||||
|
* Negative Prompt
|
||||||
|
* @description The negative prompt of the style preset
|
||||||
|
*/
|
||||||
|
negative_prompt: string;
|
||||||
|
/**
|
||||||
|
* Image
|
||||||
|
* @description The image file to upload
|
||||||
|
*/
|
||||||
|
image?: Blob | null;
|
||||||
};
|
};
|
||||||
/** Body_create_workflow */
|
/** Body_create_workflow */
|
||||||
Body_create_workflow: {
|
Body_create_workflow: {
|
||||||
@ -1273,8 +1298,26 @@ export type components = {
|
|||||||
};
|
};
|
||||||
/** Body_update_style_preset */
|
/** Body_update_style_preset */
|
||||||
Body_update_style_preset: {
|
Body_update_style_preset: {
|
||||||
/** @description The updated style preset */
|
/**
|
||||||
changes: components["schemas"]["StylePresetChanges"];
|
* Name
|
||||||
|
* @description The name of the style preset to create
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Positive Prompt
|
||||||
|
* @description The positive prompt of the style preset
|
||||||
|
*/
|
||||||
|
positive_prompt: string;
|
||||||
|
/**
|
||||||
|
* Negative Prompt
|
||||||
|
* @description The negative prompt of the style preset
|
||||||
|
*/
|
||||||
|
negative_prompt: string;
|
||||||
|
/**
|
||||||
|
* Image
|
||||||
|
* @description The image file to upload
|
||||||
|
*/
|
||||||
|
image?: Blob | null;
|
||||||
};
|
};
|
||||||
/** Body_update_workflow */
|
/** Body_update_workflow */
|
||||||
Body_update_workflow: {
|
Body_update_workflow: {
|
||||||
@ -7345,147 +7388,147 @@ export type components = {
|
|||||||
project_id: string | null;
|
project_id: string | null;
|
||||||
};
|
};
|
||||||
InvocationOutputMap: {
|
InvocationOutputMap: {
|
||||||
esrgan: components["schemas"]["ImageOutput"];
|
|
||||||
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
|
||||||
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
|
||||||
merge_metadata: components["schemas"]["MetadataOutput"];
|
|
||||||
lresize: components["schemas"]["LatentsOutput"];
|
|
||||||
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
|
||||||
img_channel_multiply: components["schemas"]["ImageOutput"];
|
|
||||||
add: components["schemas"]["IntegerOutput"];
|
|
||||||
lscale: components["schemas"]["LatentsOutput"];
|
|
||||||
string_replace: components["schemas"]["StringOutput"];
|
|
||||||
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
|
||||||
collect: components["schemas"]["CollectInvocationOutput"];
|
|
||||||
face_off: components["schemas"]["FaceOffOutput"];
|
|
||||||
ideal_size: components["schemas"]["IdealSizeOutput"];
|
|
||||||
float_to_int: components["schemas"]["IntegerOutput"];
|
|
||||||
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
sub: components["schemas"]["IntegerOutput"];
|
|
||||||
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
rectangle_mask: components["schemas"]["MaskOutput"];
|
|
||||||
img_watermark: components["schemas"]["ImageOutput"];
|
|
||||||
img_ilerp: components["schemas"]["ImageOutput"];
|
|
||||||
pidi_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
vae_loader: components["schemas"]["VAEOutput"];
|
|
||||||
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
|
||||||
float_range: components["schemas"]["FloatCollectionOutput"];
|
|
||||||
spandrel_image_to_image_autoscale: components["schemas"]["ImageOutput"];
|
|
||||||
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
|
||||||
denoise_latents: components["schemas"]["LatentsOutput"];
|
|
||||||
image_collection: components["schemas"]["ImageCollectionOutput"];
|
|
||||||
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
|
||||||
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
|
||||||
mul: components["schemas"]["IntegerOutput"];
|
|
||||||
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
|
||||||
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
|
||||||
save_image: components["schemas"]["ImageOutput"];
|
|
||||||
blank_image: components["schemas"]["ImageOutput"];
|
|
||||||
conditioning: components["schemas"]["ConditioningOutput"];
|
|
||||||
img_chan: components["schemas"]["ImageOutput"];
|
|
||||||
string_split: components["schemas"]["String2Output"];
|
|
||||||
segment_anything_processor: components["schemas"]["ImageOutput"];
|
|
||||||
unsharp_mask: components["schemas"]["ImageOutput"];
|
|
||||||
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
|
||||||
color: components["schemas"]["ColorOutput"];
|
|
||||||
range_of_size: components["schemas"]["IntegerCollectionOutput"];
|
|
||||||
face_identifier: components["schemas"]["ImageOutput"];
|
|
||||||
div: components["schemas"]["IntegerOutput"];
|
|
||||||
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
|
||||||
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
|
||||||
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
|
||||||
latents: components["schemas"]["LatentsOutput"];
|
|
||||||
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
|
||||||
infill_lama: components["schemas"]["ImageOutput"];
|
|
||||||
compel: components["schemas"]["ConditioningOutput"];
|
|
||||||
round_float: components["schemas"]["FloatOutput"];
|
|
||||||
string_join: components["schemas"]["StringOutput"];
|
|
||||||
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
metadata: components["schemas"]["MetadataOutput"];
|
|
||||||
lineart_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
controlnet: components["schemas"]["ControlOutput"];
|
|
||||||
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
|
||||||
img_lerp: components["schemas"]["ImageOutput"];
|
|
||||||
scheduler: components["schemas"]["SchedulerOutput"];
|
|
||||||
l2i: components["schemas"]["ImageOutput"];
|
|
||||||
img_paste: components["schemas"]["ImageOutput"];
|
|
||||||
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
|
||||||
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
|
||||||
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
|
||||||
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
|
||||||
integer: components["schemas"]["IntegerOutput"];
|
integer: components["schemas"]["IntegerOutput"];
|
||||||
img_mul: components["schemas"]["ImageOutput"];
|
color_map_image_processor: components["schemas"]["ImageOutput"];
|
||||||
rand_int: components["schemas"]["IntegerOutput"];
|
rand_int: components["schemas"]["IntegerOutput"];
|
||||||
string_join_three: components["schemas"]["StringOutput"];
|
|
||||||
img_blur: components["schemas"]["ImageOutput"];
|
|
||||||
float: components["schemas"]["FloatOutput"];
|
|
||||||
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
|
||||||
crop_latents: components["schemas"]["LatentsOutput"];
|
|
||||||
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
|
||||||
show_image: components["schemas"]["ImageOutput"];
|
|
||||||
img_channel_offset: components["schemas"]["ImageOutput"];
|
|
||||||
i2l: components["schemas"]["LatentsOutput"];
|
|
||||||
core_metadata: components["schemas"]["MetadataOutput"];
|
|
||||||
infill_cv2: components["schemas"]["ImageOutput"];
|
infill_cv2: components["schemas"]["ImageOutput"];
|
||||||
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
img_scale: components["schemas"]["ImageOutput"];
|
||||||
rand_float: components["schemas"]["FloatOutput"];
|
random_range: components["schemas"]["IntegerCollectionOutput"];
|
||||||
float_math: components["schemas"]["FloatOutput"];
|
latents: components["schemas"]["LatentsOutput"];
|
||||||
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
cv_inpaint: components["schemas"]["ImageOutput"];
|
||||||
image: components["schemas"]["ImageOutput"];
|
float_range: components["schemas"]["FloatCollectionOutput"];
|
||||||
infill_patchmatch: components["schemas"]["ImageOutput"];
|
img_pad_crop: components["schemas"]["ImageOutput"];
|
||||||
noise: components["schemas"]["NoiseOutput"];
|
range_of_size: components["schemas"]["IntegerCollectionOutput"];
|
||||||
tile_image_processor: components["schemas"]["ImageOutput"];
|
vae_loader: components["schemas"]["VAEOutput"];
|
||||||
spandrel_image_to_image: components["schemas"]["ImageOutput"];
|
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
||||||
hed_image_processor: components["schemas"]["ImageOutput"];
|
freeu: components["schemas"]["UNetOutput"];
|
||||||
heuristic_resize: components["schemas"]["ImageOutput"];
|
float_collection: components["schemas"]["FloatCollectionOutput"];
|
||||||
img_hue_adjust: components["schemas"]["ImageOutput"];
|
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||||
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
|
||||||
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
|
||||||
canvas_paste_back: components["schemas"]["ImageOutput"];
|
|
||||||
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
|
||||||
img_nsfw: components["schemas"]["ImageOutput"];
|
|
||||||
range: components["schemas"]["IntegerCollectionOutput"];
|
range: components["schemas"]["IntegerCollectionOutput"];
|
||||||
|
string_replace: components["schemas"]["StringOutput"];
|
||||||
|
boolean: components["schemas"]["BooleanOutput"];
|
||||||
|
show_image: components["schemas"]["ImageOutput"];
|
||||||
|
img_hue_adjust: components["schemas"]["ImageOutput"];
|
||||||
|
metadata: components["schemas"]["MetadataOutput"];
|
||||||
|
img_conv: components["schemas"]["ImageOutput"];
|
||||||
|
sub: components["schemas"]["IntegerOutput"];
|
||||||
|
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
||||||
|
save_image: components["schemas"]["ImageOutput"];
|
||||||
|
rectangle_mask: components["schemas"]["MaskOutput"];
|
||||||
|
ideal_size: components["schemas"]["IdealSizeOutput"];
|
||||||
|
lresize: components["schemas"]["LatentsOutput"];
|
||||||
|
lblend: components["schemas"]["LatentsOutput"];
|
||||||
|
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
|
||||||
|
rand_float: components["schemas"]["FloatOutput"];
|
||||||
|
prompt_from_file: components["schemas"]["StringCollectionOutput"];
|
||||||
|
tomask: components["schemas"]["ImageOutput"];
|
||||||
|
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
|
||||||
|
color_correct: components["schemas"]["ImageOutput"];
|
||||||
|
img_channel_offset: components["schemas"]["ImageOutput"];
|
||||||
|
compel: components["schemas"]["ConditioningOutput"];
|
||||||
|
infill_tile: components["schemas"]["ImageOutput"];
|
||||||
|
img_resize: components["schemas"]["ImageOutput"];
|
||||||
|
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
|
||||||
|
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
||||||
|
mlsd_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_channel_multiply: components["schemas"]["ImageOutput"];
|
||||||
|
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
||||||
|
midas_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
string_collection: components["schemas"]["StringCollectionOutput"];
|
||||||
|
mask_from_id: components["schemas"]["ImageOutput"];
|
||||||
string: components["schemas"]["StringOutput"];
|
string: components["schemas"]["StringOutput"];
|
||||||
|
float: components["schemas"]["FloatOutput"];
|
||||||
|
model_identifier: components["schemas"]["ModelIdentifierOutput"];
|
||||||
|
pidi_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
string_join: components["schemas"]["StringOutput"];
|
||||||
|
spandrel_image_to_image_autoscale: components["schemas"]["ImageOutput"];
|
||||||
|
lora_selector: components["schemas"]["LoRASelectorOutput"];
|
||||||
|
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
||||||
|
image_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||||
|
normalbae_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
face_off: components["schemas"]["FaceOffOutput"];
|
||||||
|
mul: components["schemas"]["IntegerOutput"];
|
||||||
|
segment_anything_processor: components["schemas"]["ImageOutput"];
|
||||||
|
round_float: components["schemas"]["FloatOutput"];
|
||||||
|
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
|
||||||
|
denoise_latents: components["schemas"]["LatentsOutput"];
|
||||||
|
string_split_neg: components["schemas"]["StringPosNegOutput"];
|
||||||
|
string_split: components["schemas"]["String2Output"];
|
||||||
|
invert_tensor_mask: components["schemas"]["MaskOutput"];
|
||||||
|
main_model_loader: components["schemas"]["ModelLoaderOutput"];
|
||||||
|
img_crop: components["schemas"]["ImageOutput"];
|
||||||
|
img_watermark: components["schemas"]["ImageOutput"];
|
||||||
|
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
add: components["schemas"]["IntegerOutput"];
|
||||||
|
conditioning: components["schemas"]["ConditioningOutput"];
|
||||||
|
esrgan: components["schemas"]["ImageOutput"];
|
||||||
|
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
|
||||||
|
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
|
||||||
|
mediapipe_face_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_chan: components["schemas"]["ImageOutput"];
|
||||||
|
face_mask_detection: components["schemas"]["FaceMaskOutput"];
|
||||||
|
lineart_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
blank_image: components["schemas"]["ImageOutput"];
|
||||||
|
image_collection: components["schemas"]["ImageCollectionOutput"];
|
||||||
|
img_nsfw: components["schemas"]["ImageOutput"];
|
||||||
|
unsharp_mask: components["schemas"]["ImageOutput"];
|
||||||
|
scheduler: components["schemas"]["SchedulerOutput"];
|
||||||
|
metadata_item: components["schemas"]["MetadataItemOutput"];
|
||||||
|
crop_latents: components["schemas"]["LatentsOutput"];
|
||||||
|
string_join_three: components["schemas"]["StringOutput"];
|
||||||
|
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
depth_anything_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
controlnet: components["schemas"]["ControlOutput"];
|
||||||
|
mask_edge: components["schemas"]["ImageOutput"];
|
||||||
|
img_ilerp: components["schemas"]["ImageOutput"];
|
||||||
|
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
||||||
|
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
|
||||||
|
face_identifier: components["schemas"]["ImageOutput"];
|
||||||
|
i2l: components["schemas"]["LatentsOutput"];
|
||||||
|
infill_lama: components["schemas"]["ImageOutput"];
|
||||||
|
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||||
|
mask_combine: components["schemas"]["ImageOutput"];
|
||||||
|
noise: components["schemas"]["NoiseOutput"];
|
||||||
|
div: components["schemas"]["IntegerOutput"];
|
||||||
|
img_paste: components["schemas"]["ImageOutput"];
|
||||||
|
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
|
||||||
|
iterate: components["schemas"]["IterateInvocationOutput"];
|
||||||
|
merge_tiles_to_image: components["schemas"]["ImageOutput"];
|
||||||
|
l2i: components["schemas"]["ImageOutput"];
|
||||||
|
float_math: components["schemas"]["FloatOutput"];
|
||||||
|
img_lerp: components["schemas"]["ImageOutput"];
|
||||||
|
spandrel_image_to_image: components["schemas"]["ImageOutput"];
|
||||||
|
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
|
||||||
|
ip_adapter: components["schemas"]["IPAdapterOutput"];
|
||||||
|
step_param_easing: components["schemas"]["FloatCollectionOutput"];
|
||||||
|
heuristic_resize: components["schemas"]["ImageOutput"];
|
||||||
|
canny_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
hed_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_mul: components["schemas"]["ImageOutput"];
|
||||||
|
merge_metadata: components["schemas"]["MetadataOutput"];
|
||||||
|
color: components["schemas"]["ColorOutput"];
|
||||||
|
lscale: components["schemas"]["LatentsOutput"];
|
||||||
|
integer_math: components["schemas"]["IntegerOutput"];
|
||||||
|
infill_rgba: components["schemas"]["ImageOutput"];
|
||||||
|
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
tile_image_processor: components["schemas"]["ImageOutput"];
|
||||||
|
img_blur: components["schemas"]["ImageOutput"];
|
||||||
|
float_to_int: components["schemas"]["IntegerOutput"];
|
||||||
|
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
||||||
|
collect: components["schemas"]["CollectInvocationOutput"];
|
||||||
|
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
|
||||||
|
infill_patchmatch: components["schemas"]["ImageOutput"];
|
||||||
|
image: components["schemas"]["ImageOutput"];
|
||||||
|
leres_image_processor: components["schemas"]["ImageOutput"];
|
||||||
seamless: components["schemas"]["SeamlessModeOutput"];
|
seamless: components["schemas"]["SeamlessModeOutput"];
|
||||||
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
integer_collection: components["schemas"]["IntegerCollectionOutput"];
|
||||||
iterate: components["schemas"]["IterateInvocationOutput"];
|
core_metadata: components["schemas"]["MetadataOutput"];
|
||||||
img_scale: components["schemas"]["ImageOutput"];
|
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
|
||||||
pair_tile_image: components["schemas"]["PairTileImageOutput"];
|
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
|
||||||
canny_image_processor: components["schemas"]["ImageOutput"];
|
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
|
||||||
integer_math: components["schemas"]["IntegerOutput"];
|
canvas_paste_back: components["schemas"]["ImageOutput"];
|
||||||
leres_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
|
|
||||||
float_collection: components["schemas"]["FloatCollectionOutput"];
|
|
||||||
string_collection: components["schemas"]["StringCollectionOutput"];
|
|
||||||
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
|
|
||||||
mask_combine: components["schemas"]["ImageOutput"];
|
|
||||||
latents_collection: components["schemas"]["LatentsCollectionOutput"];
|
|
||||||
mask_edge: components["schemas"]["ImageOutput"];
|
|
||||||
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
|
|
||||||
img_conv: components["schemas"]["ImageOutput"];
|
|
||||||
color_correct: components["schemas"]["ImageOutput"];
|
|
||||||
lblend: components["schemas"]["LatentsOutput"];
|
|
||||||
mask_from_id: components["schemas"]["ImageOutput"];
|
|
||||||
img_pad_crop: components["schemas"]["ImageOutput"];
|
|
||||||
metadata_item: components["schemas"]["MetadataItemOutput"];
|
|
||||||
lora_loader: components["schemas"]["LoRALoaderOutput"];
|
|
||||||
cv_inpaint: components["schemas"]["ImageOutput"];
|
|
||||||
color_map_image_processor: components["schemas"]["ImageOutput"];
|
|
||||||
img_resize: components["schemas"]["ImageOutput"];
|
|
||||||
random_range: components["schemas"]["IntegerCollectionOutput"];
|
|
||||||
img_crop: components["schemas"]["ImageOutput"];
|
|
||||||
freeu: components["schemas"]["UNetOutput"];
|
|
||||||
infill_tile: components["schemas"]["ImageOutput"];
|
|
||||||
infill_rgba: components["schemas"]["ImageOutput"];
|
|
||||||
tomask: components["schemas"]["ImageOutput"];
|
|
||||||
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
|
|
||||||
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
|
|
||||||
boolean: components["schemas"]["BooleanOutput"];
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* InvocationStartedEvent
|
* InvocationStartedEvent
|
||||||
@ -12642,18 +12685,8 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
type: "string_split_neg";
|
type: "string_split_neg";
|
||||||
};
|
};
|
||||||
/** StylePresetChanges */
|
/** StylePresetRecordWithImage */
|
||||||
StylePresetChanges: {
|
StylePresetRecordWithImage: {
|
||||||
/**
|
|
||||||
* Name
|
|
||||||
* @description The style preset's new name.
|
|
||||||
*/
|
|
||||||
name?: string | null;
|
|
||||||
/** @description The updated data for style preset. */
|
|
||||||
preset_data?: components["schemas"]["PresetData"] | null;
|
|
||||||
};
|
|
||||||
/** StylePresetRecordDTO */
|
|
||||||
StylePresetRecordDTO: {
|
|
||||||
/**
|
/**
|
||||||
* Name
|
* Name
|
||||||
* @description The name of the style preset.
|
* @description The name of the style preset.
|
||||||
@ -12671,16 +12704,11 @@ export type components = {
|
|||||||
* @description Whether or not the style preset is default
|
* @description Whether or not the style preset is default
|
||||||
*/
|
*/
|
||||||
is_default: boolean;
|
is_default: boolean;
|
||||||
};
|
|
||||||
/** StylePresetWithoutId */
|
|
||||||
StylePresetWithoutId: {
|
|
||||||
/**
|
/**
|
||||||
* Name
|
* Image
|
||||||
* @description The name of the style preset.
|
* @description The path for image
|
||||||
*/
|
*/
|
||||||
name: string;
|
image: string | null;
|
||||||
/** @description The preset data */
|
|
||||||
preset_data: components["schemas"]["PresetData"];
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* SubModelType
|
* SubModelType
|
||||||
@ -16224,7 +16252,7 @@ export type operations = {
|
|||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["StylePresetRecordDTO"];
|
"application/json": components["schemas"]["StylePresetRecordWithImage"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
@ -16274,14 +16302,14 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["Body_update_style_preset"];
|
"multipart/form-data": components["schemas"]["Body_update_style_preset"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["StylePresetRecordDTO"];
|
"application/json": components["schemas"]["StylePresetRecordWithImage"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
@ -16301,7 +16329,7 @@ export type operations = {
|
|||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["StylePresetRecordDTO"][];
|
"application/json": components["schemas"]["StylePresetRecordWithImage"][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -16313,14 +16341,14 @@ export type operations = {
|
|||||||
create_style_preset: {
|
create_style_preset: {
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["Body_create_style_preset"];
|
"multipart/form-data": components["schemas"]["Body_create_style_preset"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["StylePresetRecordDTO"];
|
"application/json": components["schemas"]["StylePresetRecordWithImage"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
@ -16331,4 +16359,38 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Get Style Preset Image
|
||||||
|
* @description Gets an image file that previews the model
|
||||||
|
*/
|
||||||
|
get_style_preset_image: {
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
/** @description The id of the style preset image to get */
|
||||||
|
style_preset_id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description The style preset image was fetched successfully */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Bad request */
|
||||||
|
400: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description The style preset image could not be found */
|
||||||
|
404: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
Loading…
Reference in New Issue
Block a user