images.py 21 KB

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