images.py 21 KB

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