main.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. import asyncio
  2. import base64
  3. import json
  4. import logging
  5. import mimetypes
  6. import re
  7. import uuid
  8. from pathlib import Path
  9. from typing import Optional
  10. import requests
  11. from open_webui.apps.images.utils.comfyui import (
  12. ComfyUIGenerateImageForm,
  13. ComfyUIWorkflow,
  14. comfyui_generate_image,
  15. )
  16. from open_webui.config import (
  17. AUTOMATIC1111_API_AUTH,
  18. AUTOMATIC1111_BASE_URL,
  19. AUTOMATIC1111_CFG_SCALE,
  20. AUTOMATIC1111_SAMPLER,
  21. AUTOMATIC1111_SCHEDULER,
  22. CACHE_DIR,
  23. COMFYUI_BASE_URL,
  24. COMFYUI_WORKFLOW,
  25. COMFYUI_WORKFLOW_NODES,
  26. CORS_ALLOW_ORIGIN,
  27. ENABLE_IMAGE_GENERATION,
  28. IMAGE_GENERATION_ENGINE,
  29. IMAGE_GENERATION_MODEL,
  30. IMAGE_SIZE,
  31. IMAGE_STEPS,
  32. IMAGES_OPENAI_API_BASE_URL,
  33. IMAGES_OPENAI_API_KEY,
  34. AppConfig,
  35. )
  36. from open_webui.constants import ERROR_MESSAGES
  37. from open_webui.env import ENV, SRC_LOG_LEVELS, ENABLE_FORWARD_USER_INFO_HEADERS
  38. from fastapi import Depends, FastAPI, HTTPException, Request
  39. from fastapi.middleware.cors import CORSMiddleware
  40. from pydantic import BaseModel
  41. from open_webui.utils.utils import get_admin_user, get_verified_user
  42. log = logging.getLogger(__name__)
  43. log.setLevel(SRC_LOG_LEVELS["IMAGES"])
  44. IMAGE_CACHE_DIR = Path(CACHE_DIR).joinpath("./image/generations/")
  45. IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
  46. app = FastAPI(
  47. docs_url="/docs" if ENV == "dev" else None,
  48. openapi_url="/openapi.json" if ENV == "dev" else None,
  49. redoc_url=None,
  50. )
  51. app.add_middleware(
  52. CORSMiddleware,
  53. allow_origins=CORS_ALLOW_ORIGIN,
  54. allow_credentials=True,
  55. allow_methods=["*"],
  56. allow_headers=["*"],
  57. )
  58. app.state.config = AppConfig()
  59. app.state.config.ENGINE = IMAGE_GENERATION_ENGINE
  60. app.state.config.ENABLED = ENABLE_IMAGE_GENERATION
  61. app.state.config.OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL
  62. app.state.config.OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
  63. app.state.config.MODEL = IMAGE_GENERATION_MODEL
  64. app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
  65. app.state.config.AUTOMATIC1111_API_AUTH = AUTOMATIC1111_API_AUTH
  66. app.state.config.AUTOMATIC1111_CFG_SCALE = AUTOMATIC1111_CFG_SCALE
  67. app.state.config.AUTOMATIC1111_SAMPLER = AUTOMATIC1111_SAMPLER
  68. app.state.config.AUTOMATIC1111_SCHEDULER = AUTOMATIC1111_SCHEDULER
  69. app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL
  70. app.state.config.COMFYUI_WORKFLOW = COMFYUI_WORKFLOW
  71. app.state.config.COMFYUI_WORKFLOW_NODES = COMFYUI_WORKFLOW_NODES
  72. app.state.config.IMAGE_SIZE = IMAGE_SIZE
  73. app.state.config.IMAGE_STEPS = IMAGE_STEPS
  74. @app.get("/config")
  75. async def get_config(request: Request, user=Depends(get_admin_user)):
  76. return {
  77. "enabled": app.state.config.ENABLED,
  78. "engine": app.state.config.ENGINE,
  79. "openai": {
  80. "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
  81. "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
  82. },
  83. "automatic1111": {
  84. "AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
  85. "AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
  86. "AUTOMATIC1111_CFG_SCALE": app.state.config.AUTOMATIC1111_CFG_SCALE,
  87. "AUTOMATIC1111_SAMPLER": app.state.config.AUTOMATIC1111_SAMPLER,
  88. "AUTOMATIC1111_SCHEDULER": app.state.config.AUTOMATIC1111_SCHEDULER,
  89. },
  90. "comfyui": {
  91. "COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
  92. "COMFYUI_WORKFLOW": app.state.config.COMFYUI_WORKFLOW,
  93. "COMFYUI_WORKFLOW_NODES": app.state.config.COMFYUI_WORKFLOW_NODES,
  94. },
  95. }
  96. class OpenAIConfigForm(BaseModel):
  97. OPENAI_API_BASE_URL: str
  98. OPENAI_API_KEY: str
  99. class Automatic1111ConfigForm(BaseModel):
  100. AUTOMATIC1111_BASE_URL: str
  101. AUTOMATIC1111_API_AUTH: str
  102. AUTOMATIC1111_CFG_SCALE: Optional[str]
  103. AUTOMATIC1111_SAMPLER: Optional[str]
  104. AUTOMATIC1111_SCHEDULER: Optional[str]
  105. class ComfyUIConfigForm(BaseModel):
  106. COMFYUI_BASE_URL: str
  107. COMFYUI_WORKFLOW: str
  108. COMFYUI_WORKFLOW_NODES: list[dict]
  109. class ConfigForm(BaseModel):
  110. enabled: bool
  111. engine: str
  112. openai: OpenAIConfigForm
  113. automatic1111: Automatic1111ConfigForm
  114. comfyui: ComfyUIConfigForm
  115. @app.post("/config/update")
  116. async def update_config(form_data: ConfigForm, user=Depends(get_admin_user)):
  117. app.state.config.ENGINE = form_data.engine
  118. app.state.config.ENABLED = form_data.enabled
  119. app.state.config.OPENAI_API_BASE_URL = form_data.openai.OPENAI_API_BASE_URL
  120. app.state.config.OPENAI_API_KEY = form_data.openai.OPENAI_API_KEY
  121. app.state.config.AUTOMATIC1111_BASE_URL = (
  122. form_data.automatic1111.AUTOMATIC1111_BASE_URL
  123. )
  124. app.state.config.AUTOMATIC1111_API_AUTH = (
  125. form_data.automatic1111.AUTOMATIC1111_API_AUTH
  126. )
  127. app.state.config.AUTOMATIC1111_CFG_SCALE = (
  128. float(form_data.automatic1111.AUTOMATIC1111_CFG_SCALE)
  129. if form_data.automatic1111.AUTOMATIC1111_CFG_SCALE
  130. else None
  131. )
  132. app.state.config.AUTOMATIC1111_SAMPLER = (
  133. form_data.automatic1111.AUTOMATIC1111_SAMPLER
  134. if form_data.automatic1111.AUTOMATIC1111_SAMPLER
  135. else None
  136. )
  137. app.state.config.AUTOMATIC1111_SCHEDULER = (
  138. form_data.automatic1111.AUTOMATIC1111_SCHEDULER
  139. if form_data.automatic1111.AUTOMATIC1111_SCHEDULER
  140. else None
  141. )
  142. app.state.config.COMFYUI_BASE_URL = form_data.comfyui.COMFYUI_BASE_URL.strip("/")
  143. app.state.config.COMFYUI_WORKFLOW = form_data.comfyui.COMFYUI_WORKFLOW
  144. app.state.config.COMFYUI_WORKFLOW_NODES = form_data.comfyui.COMFYUI_WORKFLOW_NODES
  145. return {
  146. "enabled": app.state.config.ENABLED,
  147. "engine": app.state.config.ENGINE,
  148. "openai": {
  149. "OPENAI_API_BASE_URL": app.state.config.OPENAI_API_BASE_URL,
  150. "OPENAI_API_KEY": app.state.config.OPENAI_API_KEY,
  151. },
  152. "automatic1111": {
  153. "AUTOMATIC1111_BASE_URL": app.state.config.AUTOMATIC1111_BASE_URL,
  154. "AUTOMATIC1111_API_AUTH": app.state.config.AUTOMATIC1111_API_AUTH,
  155. "AUTOMATIC1111_CFG_SCALE": app.state.config.AUTOMATIC1111_CFG_SCALE,
  156. "AUTOMATIC1111_SAMPLER": app.state.config.AUTOMATIC1111_SAMPLER,
  157. "AUTOMATIC1111_SCHEDULER": app.state.config.AUTOMATIC1111_SCHEDULER,
  158. },
  159. "comfyui": {
  160. "COMFYUI_BASE_URL": app.state.config.COMFYUI_BASE_URL,
  161. "COMFYUI_WORKFLOW": app.state.config.COMFYUI_WORKFLOW,
  162. "COMFYUI_WORKFLOW_NODES": app.state.config.COMFYUI_WORKFLOW_NODES,
  163. },
  164. }
  165. def get_automatic1111_api_auth():
  166. if app.state.config.AUTOMATIC1111_API_AUTH is None:
  167. return ""
  168. else:
  169. auth1111_byte_string = app.state.config.AUTOMATIC1111_API_AUTH.encode("utf-8")
  170. auth1111_base64_encoded_bytes = base64.b64encode(auth1111_byte_string)
  171. auth1111_base64_encoded_string = auth1111_base64_encoded_bytes.decode("utf-8")
  172. return f"Basic {auth1111_base64_encoded_string}"
  173. @app.get("/config/url/verify")
  174. async def verify_url(user=Depends(get_admin_user)):
  175. if app.state.config.ENGINE == "automatic1111":
  176. try:
  177. r = requests.get(
  178. url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
  179. headers={"authorization": get_automatic1111_api_auth()},
  180. )
  181. r.raise_for_status()
  182. return True
  183. except Exception:
  184. app.state.config.ENABLED = False
  185. raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
  186. elif app.state.config.ENGINE == "comfyui":
  187. try:
  188. r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
  189. r.raise_for_status()
  190. return True
  191. except Exception:
  192. app.state.config.ENABLED = False
  193. raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL)
  194. else:
  195. return True
  196. def set_image_model(model: str):
  197. log.info(f"Setting image model to {model}")
  198. app.state.config.MODEL = model
  199. if app.state.config.ENGINE in ["", "automatic1111"]:
  200. api_auth = get_automatic1111_api_auth()
  201. r = requests.get(
  202. url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
  203. headers={"authorization": api_auth},
  204. )
  205. options = r.json()
  206. if model != options["sd_model_checkpoint"]:
  207. options["sd_model_checkpoint"] = model
  208. r = requests.post(
  209. url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
  210. json=options,
  211. headers={"authorization": api_auth},
  212. )
  213. return app.state.config.MODEL
  214. def get_image_model():
  215. if app.state.config.ENGINE == "openai":
  216. return app.state.config.MODEL if app.state.config.MODEL else "dall-e-2"
  217. elif app.state.config.ENGINE == "comfyui":
  218. return app.state.config.MODEL if app.state.config.MODEL else ""
  219. elif app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == "":
  220. try:
  221. r = requests.get(
  222. url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options",
  223. headers={"authorization": get_automatic1111_api_auth()},
  224. )
  225. options = r.json()
  226. return options["sd_model_checkpoint"]
  227. except Exception as e:
  228. app.state.config.ENABLED = False
  229. raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
  230. class ImageConfigForm(BaseModel):
  231. MODEL: str
  232. IMAGE_SIZE: str
  233. IMAGE_STEPS: int
  234. @app.get("/image/config")
  235. async def get_image_config(user=Depends(get_admin_user)):
  236. return {
  237. "MODEL": app.state.config.MODEL,
  238. "IMAGE_SIZE": app.state.config.IMAGE_SIZE,
  239. "IMAGE_STEPS": app.state.config.IMAGE_STEPS,
  240. }
  241. @app.post("/image/config/update")
  242. async def update_image_config(form_data: ImageConfigForm, user=Depends(get_admin_user)):
  243. set_image_model(form_data.MODEL)
  244. pattern = r"^\d+x\d+$"
  245. if re.match(pattern, form_data.IMAGE_SIZE):
  246. app.state.config.IMAGE_SIZE = form_data.IMAGE_SIZE
  247. else:
  248. raise HTTPException(
  249. status_code=400,
  250. detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 512x512)."),
  251. )
  252. if form_data.IMAGE_STEPS >= 0:
  253. app.state.config.IMAGE_STEPS = form_data.IMAGE_STEPS
  254. else:
  255. raise HTTPException(
  256. status_code=400,
  257. detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 50)."),
  258. )
  259. return {
  260. "MODEL": app.state.config.MODEL,
  261. "IMAGE_SIZE": app.state.config.IMAGE_SIZE,
  262. "IMAGE_STEPS": app.state.config.IMAGE_STEPS,
  263. }
  264. @app.get("/models")
  265. def get_models(user=Depends(get_verified_user)):
  266. try:
  267. if app.state.config.ENGINE == "openai":
  268. return [
  269. {"id": "dall-e-2", "name": "DALL·E 2"},
  270. {"id": "dall-e-3", "name": "DALL·E 3"},
  271. ]
  272. elif app.state.config.ENGINE == "comfyui":
  273. # TODO - get models from comfyui
  274. r = requests.get(url=f"{app.state.config.COMFYUI_BASE_URL}/object_info")
  275. info = r.json()
  276. workflow = json.loads(app.state.config.COMFYUI_WORKFLOW)
  277. model_node_id = None
  278. for node in app.state.config.COMFYUI_WORKFLOW_NODES:
  279. if node["type"] == "model":
  280. if node["node_ids"]:
  281. model_node_id = node["node_ids"][0]
  282. break
  283. if model_node_id:
  284. model_list_key = None
  285. print(workflow[model_node_id]["class_type"])
  286. for key in info[workflow[model_node_id]["class_type"]]["input"][
  287. "required"
  288. ]:
  289. if "_name" in key:
  290. model_list_key = key
  291. break
  292. if model_list_key:
  293. return list(
  294. map(
  295. lambda model: {"id": model, "name": model},
  296. info[workflow[model_node_id]["class_type"]]["input"][
  297. "required"
  298. ][model_list_key][0],
  299. )
  300. )
  301. else:
  302. return list(
  303. map(
  304. lambda model: {"id": model, "name": model},
  305. info["CheckpointLoaderSimple"]["input"]["required"][
  306. "ckpt_name"
  307. ][0],
  308. )
  309. )
  310. elif (
  311. app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == ""
  312. ):
  313. r = requests.get(
  314. url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models",
  315. headers={"authorization": get_automatic1111_api_auth()},
  316. )
  317. models = r.json()
  318. return list(
  319. map(
  320. lambda model: {"id": model["title"], "name": model["model_name"]},
  321. models,
  322. )
  323. )
  324. except Exception as e:
  325. app.state.config.ENABLED = False
  326. raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
  327. class GenerateImageForm(BaseModel):
  328. model: Optional[str] = None
  329. prompt: str
  330. size: Optional[str] = None
  331. n: int = 1
  332. negative_prompt: Optional[str] = None
  333. def save_b64_image(b64_str):
  334. try:
  335. image_id = str(uuid.uuid4())
  336. if "," in b64_str:
  337. header, encoded = b64_str.split(",", 1)
  338. mime_type = header.split(";")[0]
  339. img_data = base64.b64decode(encoded)
  340. image_format = mimetypes.guess_extension(mime_type)
  341. image_filename = f"{image_id}{image_format}"
  342. file_path = IMAGE_CACHE_DIR / f"{image_filename}"
  343. with open(file_path, "wb") as f:
  344. f.write(img_data)
  345. return image_filename
  346. else:
  347. image_filename = f"{image_id}.png"
  348. file_path = IMAGE_CACHE_DIR.joinpath(image_filename)
  349. img_data = base64.b64decode(b64_str)
  350. # Write the image data to a file
  351. with open(file_path, "wb") as f:
  352. f.write(img_data)
  353. return image_filename
  354. except Exception as e:
  355. log.exception(f"Error saving image: {e}")
  356. return None
  357. def save_url_image(url):
  358. image_id = str(uuid.uuid4())
  359. try:
  360. r = requests.get(url)
  361. r.raise_for_status()
  362. if r.headers["content-type"].split("/")[0] == "image":
  363. mime_type = r.headers["content-type"]
  364. image_format = mimetypes.guess_extension(mime_type)
  365. if not image_format:
  366. raise ValueError("Could not determine image type from MIME type")
  367. image_filename = f"{image_id}{image_format}"
  368. file_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}")
  369. with open(file_path, "wb") as image_file:
  370. for chunk in r.iter_content(chunk_size=8192):
  371. image_file.write(chunk)
  372. return image_filename
  373. else:
  374. log.error("Url does not point to an image.")
  375. return None
  376. except Exception as e:
  377. log.exception(f"Error saving image: {e}")
  378. return None
  379. @app.post("/generations")
  380. async def image_generations(
  381. form_data: GenerateImageForm,
  382. user=Depends(get_verified_user),
  383. ):
  384. width, height = tuple(map(int, app.state.config.IMAGE_SIZE.split("x")))
  385. r = None
  386. try:
  387. if app.state.config.ENGINE == "openai":
  388. headers = {}
  389. headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEY}"
  390. headers["Content-Type"] = "application/json"
  391. if ENABLE_FORWARD_USER_INFO_HEADERS:
  392. headers["X-OpenWebUI-User-Name"] = user.name
  393. headers["X-OpenWebUI-User-Id"] = user.id
  394. headers["X-OpenWebUI-User-Email"] = user.email
  395. headers["X-OpenWebUI-User-Role"] = user.role
  396. data = {
  397. "model": (
  398. app.state.config.MODEL
  399. if app.state.config.MODEL != ""
  400. else "dall-e-2"
  401. ),
  402. "prompt": form_data.prompt,
  403. "n": form_data.n,
  404. "size": (
  405. form_data.size if form_data.size else app.state.config.IMAGE_SIZE
  406. ),
  407. "response_format": "b64_json",
  408. }
  409. # Use asyncio.to_thread for the requests.post call
  410. r = await asyncio.to_thread(
  411. requests.post,
  412. url=f"{app.state.config.OPENAI_API_BASE_URL}/images/generations",
  413. json=data,
  414. headers=headers,
  415. )
  416. r.raise_for_status()
  417. res = r.json()
  418. images = []
  419. for image in res["data"]:
  420. image_filename = save_b64_image(image["b64_json"])
  421. images.append({"url": f"/cache/image/generations/{image_filename}"})
  422. file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
  423. with open(file_body_path, "w") as f:
  424. json.dump(data, f)
  425. return images
  426. elif app.state.config.ENGINE == "comfyui":
  427. data = {
  428. "prompt": form_data.prompt,
  429. "width": width,
  430. "height": height,
  431. "n": form_data.n,
  432. }
  433. if app.state.config.IMAGE_STEPS is not None:
  434. data["steps"] = app.state.config.IMAGE_STEPS
  435. if form_data.negative_prompt is not None:
  436. data["negative_prompt"] = form_data.negative_prompt
  437. form_data = ComfyUIGenerateImageForm(
  438. **{
  439. "workflow": ComfyUIWorkflow(
  440. **{
  441. "workflow": app.state.config.COMFYUI_WORKFLOW,
  442. "nodes": app.state.config.COMFYUI_WORKFLOW_NODES,
  443. }
  444. ),
  445. **data,
  446. }
  447. )
  448. res = await comfyui_generate_image(
  449. app.state.config.MODEL,
  450. form_data,
  451. user.id,
  452. app.state.config.COMFYUI_BASE_URL,
  453. )
  454. log.debug(f"res: {res}")
  455. images = []
  456. for image in res["data"]:
  457. image_filename = save_url_image(image["url"])
  458. images.append({"url": f"/cache/image/generations/{image_filename}"})
  459. file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
  460. with open(file_body_path, "w") as f:
  461. json.dump(form_data.model_dump(exclude_none=True), f)
  462. log.debug(f"images: {images}")
  463. return images
  464. elif (
  465. app.state.config.ENGINE == "automatic1111" or app.state.config.ENGINE == ""
  466. ):
  467. if form_data.model:
  468. set_image_model(form_data.model)
  469. data = {
  470. "prompt": form_data.prompt,
  471. "batch_size": form_data.n,
  472. "width": width,
  473. "height": height,
  474. }
  475. if app.state.config.IMAGE_STEPS is not None:
  476. data["steps"] = app.state.config.IMAGE_STEPS
  477. if form_data.negative_prompt is not None:
  478. data["negative_prompt"] = form_data.negative_prompt
  479. if app.state.config.AUTOMATIC1111_CFG_SCALE:
  480. data["cfg_scale"] = app.state.config.AUTOMATIC1111_CFG_SCALE
  481. if app.state.config.AUTOMATIC1111_SAMPLER:
  482. data["sampler_name"] = app.state.config.AUTOMATIC1111_SAMPLER
  483. if app.state.config.AUTOMATIC1111_SCHEDULER:
  484. data["scheduler"] = app.state.config.AUTOMATIC1111_SCHEDULER
  485. # Use asyncio.to_thread for the requests.post call
  486. r = await asyncio.to_thread(
  487. requests.post,
  488. url=f"{app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img",
  489. json=data,
  490. headers={"authorization": get_automatic1111_api_auth()},
  491. )
  492. res = r.json()
  493. log.debug(f"res: {res}")
  494. images = []
  495. for image in res["images"]:
  496. image_filename = save_b64_image(image)
  497. images.append({"url": f"/cache/image/generations/{image_filename}"})
  498. file_body_path = IMAGE_CACHE_DIR.joinpath(f"{image_filename}.json")
  499. with open(file_body_path, "w") as f:
  500. json.dump({**data, "info": res["info"]}, f)
  501. return images
  502. except Exception as e:
  503. error = e
  504. if r != None:
  505. data = r.json()
  506. if "error" in data:
  507. error = data["error"]["message"]
  508. raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(error))