refac: images

This commit is contained in:
Timothy Jaeryang Baek 2025-11-04 13:30:59 -05:00
parent 939521b75d
commit 8e5690aab4
8 changed files with 279 additions and 476 deletions

View file

@ -3103,35 +3103,17 @@ AUTOMATIC1111_API_AUTH = PersistentConfig(
os.getenv("AUTOMATIC1111_API_AUTH", ""), os.getenv("AUTOMATIC1111_API_AUTH", ""),
) )
AUTOMATIC1111_CFG_SCALE = PersistentConfig( automatic1111_params = os.getenv("AUTOMATIC1111_PARAMS", "")
"AUTOMATIC1111_CFG_SCALE", try:
"image_generation.automatic1111.cfg_scale", automatic1111_params = json.loads(automatic1111_params)
( except json.JSONDecodeError:
float(os.environ.get("AUTOMATIC1111_CFG_SCALE")) automatic1111_params = {}
if os.environ.get("AUTOMATIC1111_CFG_SCALE")
else None
),
)
AUTOMATIC1111_SAMPLER = PersistentConfig( AUTOMATIC1111_PARAMS = PersistentConfig(
"AUTOMATIC1111_SAMPLER", "AUTOMATIC1111_PARAMS",
"image_generation.automatic1111.sampler", "image_generation.automatic1111.api_auth",
( automatic1111_params,
os.environ.get("AUTOMATIC1111_SAMPLER")
if os.environ.get("AUTOMATIC1111_SAMPLER")
else None
),
)
AUTOMATIC1111_SCHEDULER = PersistentConfig(
"AUTOMATIC1111_SCHEDULER",
"image_generation.automatic1111.scheduler",
(
os.environ.get("AUTOMATIC1111_SCHEDULER")
if os.environ.get("AUTOMATIC1111_SCHEDULER")
else None
),
) )
COMFYUI_BASE_URL = PersistentConfig( COMFYUI_BASE_URL = PersistentConfig(

View file

@ -146,9 +146,7 @@ from open_webui.config import (
# Image # Image
AUTOMATIC1111_API_AUTH, AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL, AUTOMATIC1111_BASE_URL,
AUTOMATIC1111_CFG_SCALE, AUTOMATIC1111_PARAMS,
AUTOMATIC1111_SAMPLER,
AUTOMATIC1111_SCHEDULER,
COMFYUI_BASE_URL, COMFYUI_BASE_URL,
COMFYUI_API_KEY, COMFYUI_API_KEY,
COMFYUI_WORKFLOW, COMFYUI_WORKFLOW,
@ -1064,6 +1062,10 @@ app.state.config.IMAGE_GENERATION_ENGINE = IMAGE_GENERATION_ENGINE
app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION
app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION
app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
app.state.config.IMAGE_SIZE = IMAGE_SIZE
app.state.config.IMAGE_STEPS = IMAGE_STEPS
app.state.config.IMAGES_OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL app.state.config.IMAGES_OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL
app.state.config.IMAGES_OPENAI_API_VERSION = IMAGES_OPENAI_API_VERSION app.state.config.IMAGES_OPENAI_API_VERSION = IMAGES_OPENAI_API_VERSION
app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
@ -1071,21 +1073,15 @@ app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
app.state.config.IMAGES_GEMINI_API_BASE_URL = IMAGES_GEMINI_API_BASE_URL app.state.config.IMAGES_GEMINI_API_BASE_URL = IMAGES_GEMINI_API_BASE_URL
app.state.config.IMAGES_GEMINI_API_KEY = IMAGES_GEMINI_API_KEY app.state.config.IMAGES_GEMINI_API_KEY = IMAGES_GEMINI_API_KEY
app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH
app.state.config.AUTOMATIC1111_CFG_SCALE = AUTOMATIC1111_CFG_SCALE app.state.config.AUTOMATIC1111_PARAMS = AUTOMATIC1111_PARAMS
app.state.config.AUTOMATIC1111_SAMPLER = AUTOMATIC1111_SAMPLER
app.state.config.AUTOMATIC1111_SCHEDULER = AUTOMATIC1111_SCHEDULER
app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
app.state.config.COMFYUI_API_KEY = COMFYUI_API_KEY app.state.config.COMFYUI_API_KEY = COMFYUI_API_KEY
app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW
app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES
app.state.config.IMAGE_SIZE = IMAGE_SIZE
app.state.config.IMAGE_STEPS = IMAGE_STEPS
######################################## ########################################
# #

View file

@ -23,10 +23,11 @@ from open_webui.constants import ERROR_MESSAGES
from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS, SRC_LOG_LEVELS from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS, SRC_LOG_LEVELS
from open_webui.routers.files import upload_file_handler from open_webui.routers.files import upload_file_handler
from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import include_user_info_headers
from open_webui.utils.images.comfyui import ( from open_webui.utils.images.comfyui import (
ComfyUIGenerateImageForm, ComfyUICreateImageForm,
ComfyUIWorkflow, ComfyUIWorkflow,
comfyui_generate_image, comfyui_create_image,
) )
from pydantic import BaseModel from pydantic import BaseModel
@ -36,210 +37,9 @@ log.setLevel(SRC_LOG_LEVELS["IMAGES"])
IMAGE_CACHE_DIR = CACHE_DIR / "image" / "generations" IMAGE_CACHE_DIR = CACHE_DIR / "image" / "generations"
IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True) IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
router = APIRouter() router = APIRouter()
@router.get("/config")
async def get_config(request: Request, user=Depends(get_admin_user)):
return {
"enabled": request.app.state.config.ENABLE_IMAGE_GENERATION,
"engine": request.app.state.config.IMAGE_GENERATION_ENGINE,
"prompt_generation": request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION,
"openai": {
"OPENAI_API_BASE_URL": request.app.state.config.IMAGES_OPENAI_API_BASE_URL,
"OPENAI_API_VERSION": request.app.state.config.IMAGES_OPENAI_API_VERSION,
"OPENAI_API_KEY": request.app.state.config.IMAGES_OPENAI_API_KEY,
},
"automatic1111": {
"AUTOMATIC1111_BASE_URL": request.app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": request.app.state.config.AUTOMATIC1111_API_AUTH,
"AUTOMATIC1111_CFG_SCALE": request.app.state.config.AUTOMATIC1111_CFG_SCALE,
"AUTOMATIC1111_SAMPLER": request.app.state.config.AUTOMATIC1111_SAMPLER,
"AUTOMATIC1111_SCHEDULER": request.app.state.config.AUTOMATIC1111_SCHEDULER,
},
"comfyui": {
"COMFYUI_BASE_URL": request.app.state.config.COMFYUI_BASE_URL,
"COMFYUI_API_KEY": request.app.state.config.COMFYUI_API_KEY,
"COMFYUI_WORKFLOW": request.app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": request.app.state.config.COMFYUI_WORKFLOW_NODES,
},
"gemini": {
"GEMINI_API_BASE_URL": request.app.state.config.IMAGES_GEMINI_API_BASE_URL,
"GEMINI_API_KEY": request.app.state.config.IMAGES_GEMINI_API_KEY,
},
}
class OpenAIConfigForm(BaseModel):
OPENAI_API_BASE_URL: str
OPENAI_API_VERSION: str
OPENAI_API_KEY: str
class Automatic1111ConfigForm(BaseModel):
AUTOMATIC1111_BASE_URL: str
AUTOMATIC1111_API_AUTH: str
AUTOMATIC1111_CFG_SCALE: Optional[str | float | int]
AUTOMATIC1111_SAMPLER: Optional[str]
AUTOMATIC1111_SCHEDULER: Optional[str]
class ComfyUIConfigForm(BaseModel):
COMFYUI_BASE_URL: str
COMFYUI_API_KEY: str
COMFYUI_WORKFLOW: str
COMFYUI_WORKFLOW_NODES: list[dict]
class GeminiConfigForm(BaseModel):
GEMINI_API_BASE_URL: str
GEMINI_API_KEY: str
class ConfigForm(BaseModel):
enabled: bool
engine: str
prompt_generation: bool
openai: OpenAIConfigForm
automatic1111: Automatic1111ConfigForm
comfyui: ComfyUIConfigForm
gemini: GeminiConfigForm
@router.post("/config/update")
async def update_config(
request: Request, form_data: ConfigForm, user=Depends(get_admin_user)
):
request.app.state.config.IMAGE_GENERATION_ENGINE = form_data.engine
request.app.state.config.ENABLE_IMAGE_GENERATION = form_data.enabled
request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = (
form_data.prompt_generation
)
request.app.state.config.IMAGES_OPENAI_API_BASE_URL = (
form_data.openai.OPENAI_API_BASE_URL
)
request.app.state.config.IMAGES_OPENAI_API_VERSION = (
form_data.openai.OPENAI_API_VERSION
)
request.app.state.config.IMAGES_OPENAI_API_KEY = form_data.openai.OPENAI_API_KEY
request.app.state.config.IMAGES_GEMINI_API_BASE_URL = (
form_data.gemini.GEMINI_API_BASE_URL
)
request.app.state.config.IMAGES_GEMINI_API_KEY = form_data.gemini.GEMINI_API_KEY
request.app.state.config.AUTOMATIC1111_BASE_URL = (
form_data.automatic1111.AUTOMATIC1111_BASE_URL
)
request.app.state.config.AUTOMATIC1111_API_AUTH = (
form_data.automatic1111.AUTOMATIC1111_API_AUTH
)
request.app.state.config.AUTOMATIC1111_CFG_SCALE = (
float(form_data.automatic1111.AUTOMATIC1111_CFG_SCALE)
if form_data.automatic1111.AUTOMATIC1111_CFG_SCALE
else None
)
request.app.state.config.AUTOMATIC1111_SAMPLER = (
form_data.automatic1111.AUTOMATIC1111_SAMPLER
if form_data.automatic1111.AUTOMATIC1111_SAMPLER
else None
)
request.app.state.config.AUTOMATIC1111_SCHEDULER = (
form_data.automatic1111.AUTOMATIC1111_SCHEDULER
if form_data.automatic1111.AUTOMATIC1111_SCHEDULER
else None
)
request.app.state.config.COMFYUI_BASE_URL = (
form_data.comfyui.COMFYUI_BASE_URL.strip("/")
)
request.app.state.config.COMFYUI_API_KEY = form_data.comfyui.COMFYUI_API_KEY
request.app.state.config.COMFYUI_WORKFLOW = form_data.comfyui.COMFYUI_WORKFLOW
request.app.state.config.COMFYUI_WORKFLOW_NODES = (
form_data.comfyui.COMFYUI_WORKFLOW_NODES
)
return {
"enabled": request.app.state.config.ENABLE_IMAGE_GENERATION,
"engine": request.app.state.config.IMAGE_GENERATION_ENGINE,
"prompt_generation": request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION,
"openai": {
"OPENAI_API_BASE_URL": request.app.state.config.IMAGES_OPENAI_API_BASE_URL,
"OPENAI_API_VERSION": request.app.state.config.IMAGES_OPENAI_API_VERSION,
"OPENAI_API_KEY": request.app.state.config.IMAGES_OPENAI_API_KEY,
},
"automatic1111": {
"AUTOMATIC1111_BASE_URL": request.app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": request.app.state.config.AUTOMATIC1111_API_AUTH,
"AUTOMATIC1111_CFG_SCALE": request.app.state.config.AUTOMATIC1111_CFG_SCALE,
"AUTOMATIC1111_SAMPLER": request.app.state.config.AUTOMATIC1111_SAMPLER,
"AUTOMATIC1111_SCHEDULER": request.app.state.config.AUTOMATIC1111_SCHEDULER,
},
"comfyui": {
"COMFYUI_BASE_URL": request.app.state.config.COMFYUI_BASE_URL,
"COMFYUI_API_KEY": request.app.state.config.COMFYUI_API_KEY,
"COMFYUI_WORKFLOW": request.app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": request.app.state.config.COMFYUI_WORKFLOW_NODES,
},
"gemini": {
"GEMINI_API_BASE_URL": request.app.state.config.IMAGES_GEMINI_API_BASE_URL,
"GEMINI_API_KEY": request.app.state.config.IMAGES_GEMINI_API_KEY,
},
}
def get_automatic1111_api_auth(request: Request):
if request.app.state.config.AUTOMATIC1111_API_AUTH is None:
return ""
else:
auth1111_byte_string = request.app.state.config.AUTOMATIC1111_API_AUTH.encode(
"utf-8"
)
auth1111_base64_encoded_bytes = base64.b64encode(auth1111_byte_string)
auth1111_base64_encoded_string = auth1111_base64_encoded_bytes.decode("utf-8")
return f"Basic {auth1111_base64_encoded_string}"
@router.get("/config/url/verify")
async def verify_url(request: Request, user=Depends(get_admin_user)):
if request.app.state.config.IMAGE_GENERATION_ENGINE == "automatic1111":
try:
r = requests.get(
url=f"{request.app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": get_automatic1111_api_auth(request)},
)
r.raise_for_status()
return True
except Exception:
request.app.state.config.ENABLE_IMAGE_GENERATION = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "comfyui":
headers = None
if request.app.state.config.COMFYUI_API_KEY:
headers = {
"Authorization": f"Bearer {request.app.state.config.COMFYUI_API_KEY}"
}
try:
r = requests.get(
url=f"{request.app.state.config.COMFYUI_BASE_URL}/object_info",
headers=headers,
)
r.raise_for_status()
return True
except Exception:
request.app.state.config.ENABLE_IMAGE_GENERATION = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
else:
return True
def set_image_model(request: Request, model: str): def set_image_model(request: Request, model: str):
log.info(f"Setting image model to {model}") log.info(f"Setting image model to {model}")
request.app.state.config.IMAGE_GENERATION_MODEL = model request.app.state.config.IMAGE_GENERATION_MODEL = model
@ -295,28 +95,68 @@ def get_image_model(request):
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
class ImageConfigForm(BaseModel): class ImagesConfig(BaseModel):
MODEL: str ENABLE_IMAGE_GENERATION: bool
IMAGE_GENERATION_ENGINE: str
ENABLE_IMAGE_PROMPT_GENERATION: bool
IMAGE_GENERATION_MODEL: str
IMAGE_SIZE: str IMAGE_SIZE: str
IMAGE_STEPS: int IMAGE_STEPS: int
IMAGES_OPENAI_API_BASE_URL: str
IMAGES_OPENAI_API_KEY: str
IMAGES_OPENAI_API_VERSION: str
AUTOMATIC1111_BASE_URL: str
AUTOMATIC1111_API_AUTH: str
AUTOMATIC1111_PARAMS: Optional[dict | str]
COMFYUI_BASE_URL: str
COMFYUI_API_KEY: str
COMFYUI_WORKFLOW: str
COMFYUI_WORKFLOW_NODES: list[dict]
IMAGES_GEMINI_API_BASE_URL: str
IMAGES_GEMINI_API_KEY: str
@router.get("/image/config")
async def get_image_config(request: Request, user=Depends(get_admin_user)): @router.get("/config", response_model=ImagesConfig)
async def get_config(request: Request, user=Depends(get_admin_user)):
return { return {
"MODEL": request.app.state.config.IMAGE_GENERATION_MODEL, "ENABLE_IMAGE_GENERATION": request.app.state.config.ENABLE_IMAGE_GENERATION,
"IMAGE_GENERATION_ENGINE": request.app.state.config.IMAGE_GENERATION_ENGINE,
"ENABLE_IMAGE_PROMPT_GENERATION": request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION,
"IMAGE_GENERATION_MODEL": request.app.state.config.IMAGE_GENERATION_MODEL,
"IMAGE_SIZE": request.app.state.config.IMAGE_SIZE, "IMAGE_SIZE": request.app.state.config.IMAGE_SIZE,
"IMAGE_STEPS": request.app.state.config.IMAGE_STEPS, "IMAGE_STEPS": request.app.state.config.IMAGE_STEPS,
"IMAGES_OPENAI_API_BASE_URL": request.app.state.config.IMAGES_OPENAI_API_BASE_URL,
"IMAGES_OPENAI_API_KEY": request.app.state.config.IMAGES_OPENAI_API_KEY,
"IMAGES_OPENAI_API_VERSION": request.app.state.config.IMAGES_OPENAI_API_VERSION,
"AUTOMATIC1111_BASE_URL": request.app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": request.app.state.config.AUTOMATIC1111_API_AUTH,
"AUTOMATIC1111_PARAMS": request.app.state.config.AUTOMATIC1111_PARAMS,
"COMFYUI_BASE_URL": request.app.state.config.COMFYUI_BASE_URL,
"COMFYUI_API_KEY": request.app.state.config.COMFYUI_API_KEY,
"COMFYUI_WORKFLOW": request.app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": request.app.state.config.COMFYUI_WORKFLOW_NODES,
"IMAGES_GEMINI_API_BASE_URL": request.app.state.config.IMAGES_GEMINI_API_BASE_URL,
"IMAGES_GEMINI_API_KEY": request.app.state.config.IMAGES_GEMINI_API_KEY,
} }
@router.post("/image/config/update") @router.post("/config/update")
async def update_image_config( async def update_config(
request: Request, form_data: ImageConfigForm, user=Depends(get_admin_user) request: Request, form_data: ImagesConfig, user=Depends(get_admin_user)
): ):
set_image_model(request, form_data.MODEL) request.app.state.config.IMAGE_GENERATION_ENGINE = form_data.IMAGE_GENERATION_ENGINE
request.app.state.config.ENABLE_IMAGE_GENERATION = form_data.ENABLE_IMAGE_GENERATION
request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = (
form_data.ENABLE_IMAGE_PROMPT_GENERATION
)
if form_data.IMAGE_SIZE == "auto" and form_data.MODEL != "gpt-image-1": set_image_model(request, form_data.IMAGE_GENERATION_MODEL)
if (
form_data.IMAGE_SIZE == "auto"
and form_data.IMAGE_GENERATION_MODEL != "gpt-image-1"
):
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail=ERROR_MESSAGES.INCORRECT_FORMAT( detail=ERROR_MESSAGES.INCORRECT_FORMAT(
@ -341,13 +181,95 @@ async def update_image_config(
detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 50)."), detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 50)."),
) )
request.app.state.config.IMAGES_OPENAI_API_BASE_URL = (
form_data.IMAGES_OPENAI_API_BASE_URL
)
request.app.state.config.IMAGES_OPENAI_API_KEY = form_data.IMAGES_OPENAI_API_KEY
request.app.state.config.IMAGES_OPENAI_API_VERSION = (
form_data.IMAGES_OPENAI_API_VERSION
)
request.app.state.config.AUTOMATIC1111_BASE_URL = form_data.AUTOMATIC1111_BASE_URL
request.app.state.config.AUTOMATIC1111_API_AUTH = form_data.AUTOMATIC1111_API_AUTH
request.app.state.config.AUTOMATIC1111_PARAMS = form_data.AUTOMATIC1111_PARAMS
request.app.state.config.COMFYUI_BASE_URL = form_data.COMFYUI_BASE_URL.strip("/")
request.app.state.config.COMFYUI_API_KEY = form_data.COMFYUI_API_KEY
request.app.state.config.COMFYUI_WORKFLOW = form_data.COMFYUI_WORKFLOW
request.app.state.config.COMFYUI_WORKFLOW_NODES = form_data.COMFYUI_WORKFLOW_NODES
request.app.state.config.IMAGES_GEMINI_API_BASE_URL = (
form_data.IMAGES_GEMINI_API_BASE_URL
)
request.app.state.config.IMAGES_GEMINI_API_KEY = form_data.IMAGES_GEMINI_API_KEY
return { return {
"MODEL": request.app.state.config.IMAGE_GENERATION_MODEL, "ENABLE_IMAGE_GENERATION": request.app.state.config.ENABLE_IMAGE_GENERATION,
"IMAGE_GENERATION_ENGINE": request.app.state.config.IMAGE_GENERATION_ENGINE,
"ENABLE_IMAGE_PROMPT_GENERATION": request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION,
"IMAGE_GENERATION_MODEL": request.app.state.config.IMAGE_GENERATION_MODEL,
"IMAGE_SIZE": request.app.state.config.IMAGE_SIZE, "IMAGE_SIZE": request.app.state.config.IMAGE_SIZE,
"IMAGE_STEPS": request.app.state.config.IMAGE_STEPS, "IMAGE_STEPS": request.app.state.config.IMAGE_STEPS,
"IMAGES_OPENAI_API_BASE_URL": request.app.state.config.IMAGES_OPENAI_API_BASE_URL,
"IMAGES_OPENAI_API_KEY": request.app.state.config.IMAGES_OPENAI_API_KEY,
"IMAGES_OPENAI_API_VERSION": request.app.state.config.IMAGES_OPENAI_API_VERSION,
"AUTOMATIC1111_BASE_URL": request.app.state.config.AUTOMATIC1111_BASE_URL,
"AUTOMATIC1111_API_AUTH": request.app.state.config.AUTOMATIC1111_API_AUTH,
"AUTOMATIC1111_PARAMS": request.app.state.config.AUTOMATIC1111_PARAMS,
"COMFYUI_BASE_URL": request.app.state.config.COMFYUI_BASE_URL,
"COMFYUI_API_KEY": request.app.state.config.COMFYUI_API_KEY,
"COMFYUI_WORKFLOW": request.app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": request.app.state.config.COMFYUI_WORKFLOW_NODES,
"IMAGES_GEMINI_API_BASE_URL": request.app.state.config.IMAGES_GEMINI_API_BASE_URL,
"IMAGES_GEMINI_API_KEY": request.app.state.config.IMAGES_GEMINI_API_KEY,
} }
def get_automatic1111_api_auth(request: Request):
if request.app.state.config.AUTOMATIC1111_API_AUTH is None:
return ""
else:
auth1111_byte_string = request.app.state.config.AUTOMATIC1111_API_AUTH.encode(
"utf-8"
)
auth1111_base64_encoded_bytes = base64.b64encode(auth1111_byte_string)
auth1111_base64_encoded_string = auth1111_base64_encoded_bytes.decode("utf-8")
return f"Basic {auth1111_base64_encoded_string}"
@router.get("/config/url/verify")
async def verify_url(request: Request, user=Depends(get_admin_user)):
if request.app.state.config.IMAGE_GENERATION_ENGINE == "automatic1111":
try:
r = requests.get(
url=f"{request.app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
headers={"authorization": get_automatic1111_api_auth(request)},
)
r.raise_for_status()
return True
except Exception:
request.app.state.config.ENABLE_IMAGE_GENERATION = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "comfyui":
headers = None
if request.app.state.config.COMFYUI_API_KEY:
headers = {
"Authorization": f"Bearer {request.app.state.config.COMFYUI_API_KEY}"
}
try:
r = requests.get(
url=f"{request.app.state.config.COMFYUI_BASE_URL}/object_info",
headers=headers,
)
r.raise_for_status()
return True
except Exception:
request.app.state.config.ENABLE_IMAGE_GENERATION = False
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
else:
return True
@router.get("/models") @router.get("/models")
def get_models(request: Request, user=Depends(get_verified_user)): def get_models(request: Request, user=Depends(get_verified_user)):
try: try:
@ -430,7 +352,7 @@ def get_models(request: Request, user=Depends(get_verified_user)):
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
class GenerateImageForm(BaseModel): class CreateImageForm(BaseModel):
model: Optional[str] = None model: Optional[str] = None
prompt: str prompt: str
size: Optional[str] = None size: Optional[str] = None
@ -438,27 +360,15 @@ class GenerateImageForm(BaseModel):
negative_prompt: Optional[str] = None negative_prompt: Optional[str] = None
def load_b64_image_data(b64_str): def get_image_data(data: str, headers=None):
try: try:
if "," in b64_str: # if data url
header, encoded = b64_str.split(",", 1)
mime_type = header.split(";")[0].lstrip("data:")
img_data = base64.b64decode(encoded)
else:
mime_type = "image/png"
img_data = base64.b64decode(b64_str)
return img_data, mime_type
except Exception as e:
log.exception(f"Error loading image data: {e}")
return None, None
if data.startswith("http"):
def load_url_image_data(url, headers=None):
try:
if headers: if headers:
r = requests.get(url, headers=headers) r = requests.get(data, headers=headers)
else: else:
r = requests.get(url) r = requests.get(data)
r.raise_for_status() r.raise_for_status()
if r.headers["content-type"].split("/")[0] == "image": if r.headers["content-type"].split("/")[0] == "image":
@ -467,10 +377,18 @@ def load_url_image_data(url, headers=None):
else: else:
log.error("Url does not point to an image.") log.error("Url does not point to an image.")
return None return None
else:
if "," in data:
header, encoded = data.split(",", 1)
mime_type = header.split(";")[0].lstrip("data:")
img_data = base64.b64decode(encoded)
else:
mime_type = "image/png"
img_data = base64.b64decode(data)
return img_data, mime_type
except Exception as e: except Exception as e:
log.exception(f"Error saving image: {e}") log.exception(f"Error loading image data: {e}")
return None return None, None
def upload_image(request, image_data, content_type, metadata, user): def upload_image(request, image_data, content_type, metadata, user):
@ -496,7 +414,7 @@ def upload_image(request, image_data, content_type, metadata, user):
@router.post("/generations") @router.post("/generations")
async def image_generations( async def image_generations(
request: Request, request: Request,
form_data: GenerateImageForm, form_data: CreateImageForm,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
# if IMAGE_SIZE = 'auto', default WidthxHeight to the 512x512 default # if IMAGE_SIZE = 'auto', default WidthxHeight to the 512x512 default
@ -519,17 +437,14 @@ async def image_generations(
r = None r = None
try: try:
if request.app.state.config.IMAGE_GENERATION_ENGINE == "openai": if request.app.state.config.IMAGE_GENERATION_ENGINE == "openai":
headers = {}
headers["Authorization"] = ( headers = {
f"Bearer {request.app.state.config.IMAGES_OPENAI_API_KEY}" "Authorization": f"Bearer {request.app.state.config.IMAGES_OPENAI_API_KEY}",
) "Content-Type": "application/json",
headers["Content-Type"] = "application/json" }
if ENABLE_FORWARD_USER_INFO_HEADERS: if ENABLE_FORWARD_USER_INFO_HEADERS:
headers["X-OpenWebUI-User-Name"] = quote(user.name, safe=" ") headers = include_user_info_headers(headers, user)
headers["X-OpenWebUI-User-Id"] = user.id
headers["X-OpenWebUI-User-Email"] = user.email
headers["X-OpenWebUI-User-Role"] = user.role
data = { data = {
"model": model, "model": model,
@ -568,9 +483,9 @@ async def image_generations(
for image in res["data"]: for image in res["data"]:
if image_url := image.get("url", None): if image_url := image.get("url", None):
image_data, content_type = load_url_image_data(image_url, headers) image_data, content_type = get_image_data(image_url, headers)
else: else:
image_data, content_type = load_b64_image_data(image["b64_json"]) image_data, content_type = get_image_data(image["b64_json"])
url = upload_image(request, image_data, content_type, data, user) url = upload_image(request, image_data, content_type, data, user)
images.append({"url": url}) images.append({"url": url})
@ -602,9 +517,7 @@ async def image_generations(
images = [] images = []
for image in res["predictions"]: for image in res["predictions"]:
image_data, content_type = load_b64_image_data( image_data, content_type = get_image_data(image["bytesBase64Encoded"])
image["bytesBase64Encoded"]
)
url = upload_image(request, image_data, content_type, data, user) url = upload_image(request, image_data, content_type, data, user)
images.append({"url": url}) images.append({"url": url})
@ -624,7 +537,7 @@ async def image_generations(
if form_data.negative_prompt is not None: if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt data["negative_prompt"] = form_data.negative_prompt
form_data = ComfyUIGenerateImageForm( form_data = ComfyUICreateImageForm(
**{ **{
"workflow": ComfyUIWorkflow( "workflow": ComfyUIWorkflow(
**{ **{
@ -635,7 +548,7 @@ async def image_generations(
**data, **data,
} }
) )
res = await comfyui_generate_image( res = await comfyui_create_image(
model, model,
form_data, form_data,
user.id, user.id,
@ -653,7 +566,7 @@ async def image_generations(
"Authorization": f"Bearer {request.app.state.config.COMFYUI_API_KEY}" "Authorization": f"Bearer {request.app.state.config.COMFYUI_API_KEY}"
} }
image_data, content_type = load_url_image_data(image["url"], headers) image_data, content_type = get_image_data(image["url"], headers)
url = upload_image( url = upload_image(
request, request,
image_data, image_data,
@ -683,14 +596,8 @@ async def image_generations(
if form_data.negative_prompt is not None: if form_data.negative_prompt is not None:
data["negative_prompt"] = form_data.negative_prompt data["negative_prompt"] = form_data.negative_prompt
if request.app.state.config.AUTOMATIC1111_CFG_SCALE: if request.app.state.config.AUTOMATIC1111_PARAMS:
data["cfg_scale"] = request.app.state.config.AUTOMATIC1111_CFG_SCALE data = {**data, **request.app.state.config.AUTOMATIC1111_PARAMS}
if request.app.state.config.AUTOMATIC1111_SAMPLER:
data["sampler_name"] = request.app.state.config.AUTOMATIC1111_SAMPLER
if request.app.state.config.AUTOMATIC1111_SCHEDULER:
data["scheduler"] = request.app.state.config.AUTOMATIC1111_SCHEDULER
# Use asyncio.to_thread for the requests.post call # Use asyncio.to_thread for the requests.post call
r = await asyncio.to_thread( r = await asyncio.to_thread(
@ -706,7 +613,7 @@ async def image_generations(
images = [] images = []
for image in res["images"]: for image in res["images"]:
image_data, content_type = load_b64_image_data(image) image_data, content_type = get_image_data(image)
url = upload_image( url = upload_image(
request, request,
image_data, image_data,

View file

@ -1,5 +1,5 @@
from open_webui.routers.images import ( from open_webui.routers.images import (
load_b64_image_data, get_image_data,
upload_image, upload_image,
) )
@ -22,7 +22,7 @@ def get_image_url_from_base64(request, base64_image_string, metadata, user):
if "data:image/png;base64" in base64_image_string: if "data:image/png;base64" in base64_image_string:
image_url = "" image_url = ""
# Extract base64 image data from the line # Extract base64 image data from the line
image_data, content_type = load_b64_image_data(base64_image_string) image_data, content_type = get_image_data(base64_image_string)
if image_data is not None: if image_data is not None:
image_url = upload_image( image_url = upload_image(
request, request,

View file

@ -0,0 +1,11 @@
from urllib.parse import quote
def include_user_info_headers(headers, user):
return {
**headers,
"X-OpenWebUI-User-Name": quote(user.name, safe=" "),
"X-OpenWebUI-User-Id": user.id,
"X-OpenWebUI-User-Email": user.email,
"X-OpenWebUI-User-Role": user.role,
}

View file

@ -103,7 +103,7 @@ class ComfyUIWorkflow(BaseModel):
nodes: list[ComfyUINodeInput] nodes: list[ComfyUINodeInput]
class ComfyUIGenerateImageForm(BaseModel): class ComfyUICreateImageForm(BaseModel):
workflow: ComfyUIWorkflow workflow: ComfyUIWorkflow
prompt: str prompt: str
@ -116,8 +116,8 @@ class ComfyUIGenerateImageForm(BaseModel):
seed: Optional[int] = None seed: Optional[int] = None
async def comfyui_generate_image( async def comfyui_create_image(
model: str, payload: ComfyUIGenerateImageForm, client_id, base_url, api_key model: str, payload: ComfyUICreateImageForm, client_id, base_url, api_key
): ):
ws_url = base_url.replace("http://", "ws://").replace("https://", "wss://") ws_url = base_url.replace("http://", "ws://").replace("https://", "wss://")
workflow = json.loads(payload.workflow.workflow) workflow = json.loads(payload.workflow.workflow)

View file

@ -45,9 +45,8 @@ from open_webui.routers.retrieval import (
SearchForm, SearchForm,
) )
from open_webui.routers.images import ( from open_webui.routers.images import (
load_b64_image_data,
image_generations, image_generations,
GenerateImageForm, CreateImageForm,
upload_image, upload_image,
) )
from open_webui.routers.pipelines import ( from open_webui.routers.pipelines import (
@ -770,7 +769,7 @@ async def chat_image_generation_handler(
try: try:
images = await image_generations( images = await image_generations(
request=request, request=request,
form_data=GenerateImageForm(**{"prompt": prompt}), form_data=CreateImageForm(**{"prompt": prompt}),
user=user, user=user,
) )

View file

@ -24,47 +24,8 @@
let loading = false; let loading = false;
let config = null;
let imageGenerationConfig = null;
let models = null; let models = null;
let config = null;
let samplers = [
'DPM++ 2M',
'DPM++ SDE',
'DPM++ 2M SDE',
'DPM++ 2M SDE Heun',
'DPM++ 2S a',
'DPM++ 3M SDE',
'Euler a',
'Euler',
'LMS',
'Heun',
'DPM2',
'DPM2 a',
'DPM fast',
'DPM adaptive',
'Restart',
'DDIM',
'DDIM CFG++',
'PLMS',
'UniPC'
];
let schedulers = [
'Automatic',
'Uniform',
'Karras',
'Exponential',
'Polyexponential',
'SGM Uniform',
'KL Optimal',
'Align Your Steps',
'Simple',
'Normal',
'DDIM',
'Beta'
];
let requiredWorkflowNodes = [ let requiredWorkflowNodes = [
{ {
@ -141,16 +102,16 @@
const saveHandler = async () => { const saveHandler = async () => {
loading = true; loading = true;
if (config?.comfyui?.COMFYUI_WORKFLOW) { if (config?.COMFYUI_WORKFLOW) {
if (!validateJSON(config.comfyui.COMFYUI_WORKFLOW)) { if (!validateJSON(config?.COMFYUI_WORKFLOW)) {
toast.error($i18n.t('Invalid JSON format for ComfyUI Workflow.')); toast.error($i18n.t('Invalid JSON format for ComfyUI Workflow.'));
loading = false; loading = false;
return; return;
} }
} }
if (config?.comfyui?.COMFYUI_WORKFLOW) { if (config?.COMFYUI_WORKFLOW) {
config.comfyui.COMFYUI_WORKFLOW_NODES = requiredWorkflowNodes.map((node) => { config.COMFYUI_WORKFLOW_NODES = requiredWorkflowNodes.map((node) => {
return { return {
type: node.type, type: node.type,
key: node.key, key: node.key,
@ -166,13 +127,8 @@
return null; return null;
}); });
await updateImageGenerationConfig(localStorage.token, imageGenerationConfig).catch((error) => {
toast.error(`${error}`);
loading = false;
return null;
});
getModels(); getModels();
dispatch('save'); dispatch('save');
loading = false; loading = false;
}; };
@ -188,25 +144,20 @@
config = res; config = res;
} }
if (config.enabled) { if (config.ENABLE_IMAGE_GENERATION) {
getModels(); getModels();
} }
if (config.comfyui.COMFYUI_WORKFLOW) { if (config.COMFYUI_WORKFLOW) {
try { try {
config.comfyui.COMFYUI_WORKFLOW = JSON.stringify( config.COMFYUI_WORKFLOW = JSON.stringify(JSON.parse(config.COMFYUI_WORKFLOW), null, 2);
JSON.parse(config.comfyui.COMFYUI_WORKFLOW),
null,
2
);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
requiredWorkflowNodes = requiredWorkflowNodes.map((node) => { requiredWorkflowNodes = requiredWorkflowNodes.map((node) => {
const n = config.comfyui.COMFYUI_WORKFLOW_NODES.find((n) => n.type === node.type) ?? node; const n = config.COMFYUI_WORKFLOW_NODES.find((n) => n.type === node.type) ?? node;
console.debug(n); console.debug(n);
return { return {
@ -215,15 +166,6 @@
node_ids: typeof n.node_ids === 'string' ? n.node_ids : n.node_ids.join(',') node_ids: typeof n.node_ids === 'string' ? n.node_ids : n.node_ids.join(',')
}; };
}); });
const imageConfigRes = await getImageGenerationConfig(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (imageConfigRes) {
imageGenerationConfig = imageConfigRes;
}
} }
}); });
</script> </script>
@ -235,9 +177,11 @@
}} }}
> >
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden pr-2"> <div class=" space-y-3 overflow-y-scroll scrollbar-hidden pr-2">
{#if config && imageGenerationConfig} {#if config}
<div> <div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div> <div class=" mb-2.5 text-base font-medium">{$i18n.t('Create Image')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div> <div>
<div class=" py-1 flex w-full justify-between"> <div class=" py-1 flex w-full justify-between">
@ -247,29 +191,35 @@
<div class="px-1"> <div class="px-1">
<Switch <Switch
bind:state={config.enabled} bind:state={config.ENABLE_IMAGE_GENERATION}
on:change={(e) => { on:change={(e) => {
const enabled = e.detail; const enabled = e.detail;
if (enabled) { if (enabled) {
if ( if (
config.engine === 'automatic1111' && config.IMAGE_GENERATION_ENGINE === 'automatic1111' &&
config.automatic1111.AUTOMATIC1111_BASE_URL === '' config.AUTOMATIC1111_BASE_URL === ''
) { ) {
toast.error($i18n.t('AUTOMATIC1111 Base URL is required.')); toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
config.enabled = false; config.ENABLE_IMAGE_GENERATION = false;
} else if ( } else if (
config.engine === 'comfyui' && config.IMAGE_GENERATION_ENGINE === 'comfyui' &&
config.comfyui.COMFYUI_BASE_URL === '' config.COMFYUI_BASE_URL === ''
) { ) {
toast.error($i18n.t('ComfyUI Base URL is required.')); toast.error($i18n.t('ComfyUI Base URL is required.'));
config.enabled = false; config.ENABLE_IMAGE_GENERATION = false;
} else if (config.engine === 'openai' && config.openai.OPENAI_API_KEY === '') { } else if (
config.IMAGE_GENERATION_ENGINE === 'openai' &&
config.OPENAI_API_KEY === ''
) {
toast.error($i18n.t('OpenAI API Key is required.')); toast.error($i18n.t('OpenAI API Key is required.'));
config.enabled = false; config.ENABLE_IMAGE_GENERATION = false;
} else if (config.engine === 'gemini' && config.gemini.GEMINI_API_KEY === '') { } else if (
config.IMAGE_GENERATION_ENGINE === 'gemini' &&
config.GEMINI_API_KEY === ''
) {
toast.error($i18n.t('Gemini API Key is required.')); toast.error($i18n.t('Gemini API Key is required.'));
config.enabled = false; config.ENABLE_IMAGE_GENERATION = false;
} }
} }
@ -284,7 +234,7 @@
<div class=" py-1 flex w-full justify-between"> <div class=" py-1 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Image Prompt Generation')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Image Prompt Generation')}</div>
<div class="px-1"> <div class="px-1">
<Switch bind:state={config.prompt_generation} /> <Switch bind:state={config.ENABLE_IMAGE_PROMPT_GENERATION} />
</div> </div>
</div> </div>
{/if} {/if}
@ -294,7 +244,7 @@
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right" class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={config.engine} bind:value={config.IMAGE_GENERATION_ENGINE}
placeholder={$i18n.t('Select Engine')} placeholder={$i18n.t('Select Engine')}
on:change={async () => { on:change={async () => {
updateConfigHandler(); updateConfigHandler();
@ -311,7 +261,7 @@
<hr class=" border-gray-100 dark:border-gray-850" /> <hr class=" border-gray-100 dark:border-gray-850" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#if (config?.engine ?? 'automatic1111') === 'automatic1111'} {#if (config?.IMAGE_GENERATION_ENGINE ?? 'automatic1111') === 'automatic1111'}
<div> <div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
<div class="flex w-full"> <div class="flex w-full">
@ -319,12 +269,13 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')} placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={config.automatic1111.AUTOMATIC1111_BASE_URL} bind:value={config.AUTOMATIC1111_BASE_URL}
/> />
</div> </div>
<button <button
class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition" class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
type="button" type="button"
aria-label="verify connection"
on:click={async () => { on:click={async () => {
await updateConfigHandler(); await updateConfigHandler();
const res = await verifyConfigUrl(localStorage.token).catch((error) => { const res = await verifyConfigUrl(localStorage.token).catch((error) => {
@ -370,7 +321,7 @@
</div> </div>
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('Enter api auth string (e.g. username:password)')} placeholder={$i18n.t('Enter api auth string (e.g. username:password)')}
bind:value={config.automatic1111.AUTOMATIC1111_API_AUTH} bind:value={config.AUTOMATIC1111_API_AUTH}
required={false} required={false}
/> />
@ -388,66 +339,22 @@
</div> </div>
</div> </div>
<!---Sampler--> <div class="mb-1 text-xs text-gray-400 dark:text-gray-500">
<div> <div class="w-full">
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Sampler')}</div> <div class=" mb-1.5 text-xs font-medium">{$i18n.t('Additional Parameters')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1">
<Tooltip content={$i18n.t('Enter Sampler (e.g. Euler a)')} placement="top-start"> <Textarea
<input className="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
list="sampler-list" bind:value={config.AUTOMATIC1111_PARAMS}
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" placeholder={$i18n.t('Enter additional parameters in JSON format')}
placeholder={$i18n.t('Enter Sampler (e.g. Euler a)')} minSize={100}
bind:value={config.automatic1111.AUTOMATIC1111_SAMPLER}
/> />
<datalist id="sampler-list">
{#each samplers ?? [] as sampler}
<option value={sampler}>{sampler}</option>
{/each}
</datalist>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>
<!---Scheduler-->
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Scheduler')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<Tooltip content={$i18n.t('Enter Scheduler (e.g. Karras)')} placement="top-start">
<input
list="scheduler-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter Scheduler (e.g. Karras)')}
bind:value={config.automatic1111.AUTOMATIC1111_SCHEDULER}
/>
<datalist id="scheduler-list">
{#each schedulers ?? [] as scheduler}
<option value={scheduler}>{scheduler}</option>
{/each}
</datalist>
</Tooltip>
</div> </div>
</div> {:else if config?.IMAGE_GENERATION_ENGINE === 'comfyui'}
</div>
<!---CFG scale-->
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set CFG Scale')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<Tooltip content={$i18n.t('Enter CFG Scale (e.g. 7.0)')} placement="top-start">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter CFG Scale (e.g. 7.0)')}
bind:value={config.automatic1111.AUTOMATIC1111_CFG_SCALE}
/>
</Tooltip>
</div>
</div>
</div>
{:else if config?.engine === 'comfyui'}
<div class=""> <div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Base URL')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Base URL')}</div>
<div class="flex w-full"> <div class="flex w-full">
@ -455,12 +362,13 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')} placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={config.comfyui.COMFYUI_BASE_URL} bind:value={config.COMFYUI_BASE_URL}
/> />
</div> </div>
<button <button
class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition" class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
type="button" type="button"
aria-label="verify connection"
on:click={async () => { on:click={async () => {
await updateConfigHandler(); await updateConfigHandler();
const res = await verifyConfigUrl(localStorage.token).catch((error) => { const res = await verifyConfigUrl(localStorage.token).catch((error) => {
@ -495,7 +403,7 @@
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('sk-1234')} placeholder={$i18n.t('sk-1234')}
bind:value={config.comfyui.COMFYUI_API_KEY} bind:value={config.COMFYUI_API_KEY}
required={false} required={false}
/> />
</div> </div>
@ -505,11 +413,11 @@
<div class=""> <div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow')}</div>
{#if config.comfyui.COMFYUI_WORKFLOW} {#if config.COMFYUI_WORKFLOW}
<Textarea <Textarea
class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none" class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none"
rows="10" rows="10"
bind:value={config.comfyui.COMFYUI_WORKFLOW} bind:value={config.COMFYUI_WORKFLOW}
required required
/> />
{/if} {/if}
@ -526,7 +434,7 @@
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
config.comfyui.COMFYUI_WORKFLOW = e.target.result; config.COMFYUI_WORKFLOW = e.target.result;
e.target.value = null; e.target.value = null;
}; };
@ -551,7 +459,7 @@
</div> </div>
</div> </div>
{#if config.comfyui.COMFYUI_WORKFLOW} {#if config.COMFYUI_WORKFLOW}
<div class=""> <div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow Nodes')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow Nodes')}</div>
@ -597,7 +505,7 @@
</div> </div>
</div> </div>
{/if} {/if}
{:else if config?.engine === 'openai'} {:else if config?.IMAGE_GENERATION_ENGINE === 'openai'}
<div> <div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('OpenAI API Config')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('OpenAI API Config')}</div>
<div class="flex w-full"> <div class="flex w-full">
@ -605,7 +513,7 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('API Base URL')} placeholder={$i18n.t('API Base URL')}
bind:value={config.openai.OPENAI_API_BASE_URL} bind:value={config.IMAGES_OPENAI_API_BASE_URL}
required required
/> />
</div> </div>
@ -618,7 +526,7 @@
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('API Key')} placeholder={$i18n.t('API Key')}
bind:value={config.openai.OPENAI_API_KEY} bind:value={config.IMAGES_OPENAI_API_KEY}
required required
/> />
</div> </div>
@ -632,12 +540,12 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('API Version')} placeholder={$i18n.t('API Version')}
bind:value={config.openai.OPENAI_API_VERSION} bind:value={config.IMAGES_OPENAI_API_VERSION}
/> />
</div> </div>
</div> </div>
</div> </div>
{:else if config?.engine === 'gemini'} {:else if config?.IMAGE_GENERATION_ENGINE === 'gemini'}
<div> <div>
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Gemini API Config')}</div> <div class=" mb-1.5 text-sm font-medium">{$i18n.t('Gemini API Config')}</div>
@ -645,20 +553,20 @@
<input <input
class="flex-1 w-full text-sm bg-transparent outline-none" class="flex-1 w-full text-sm bg-transparent outline-none"
placeholder={$i18n.t('API Base URL')} placeholder={$i18n.t('API Base URL')}
bind:value={config.gemini.GEMINI_API_BASE_URL} bind:value={config.IMAGES_GEMINI_API_BASE_URL}
required required
/> />
<SensitiveInput <SensitiveInput
placeholder={$i18n.t('API Key')} placeholder={$i18n.t('API Key')}
bind:value={config.gemini.GEMINI_API_KEY} bind:value={config.IMAGES_GEMINI_API_KEY}
/> />
</div> </div>
</div> </div>
{/if} {/if}
</div> </div>
{#if config?.enabled} {#if config?.ENABLE_IMAGE_GENERATION}
<hr class=" border-gray-100 dark:border-gray-850" /> <hr class=" border-gray-100 dark:border-gray-850" />
<div> <div>
@ -671,7 +579,7 @@
<input <input
list="model-list" list="model-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={imageGenerationConfig.MODEL} bind:value={config.IMAGE_GENERATION_MODEL}
placeholder={$i18n.t('Select a model')} placeholder={$i18n.t('Select a model')}
required required
/> />
@ -696,7 +604,7 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')} placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
bind:value={imageGenerationConfig.IMAGE_SIZE} bind:value={config.IMAGE_SIZE}
required required
/> />
</Tooltip> </Tooltip>
@ -704,7 +612,7 @@
</div> </div>
</div> </div>
{#if ['comfyui', 'automatic1111', ''].includes(config?.engine)} {#if ['comfyui', 'automatic1111', ''].includes(config?.IMAGE_GENERATION_ENGINE)}
<div> <div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
<div class="flex w-full"> <div class="flex w-full">
@ -713,7 +621,7 @@
<input <input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')} placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
bind:value={imageGenerationConfig.IMAGE_STEPS} bind:value={config.IMAGE_STEPS}
required required
/> />
</Tooltip> </Tooltip>