localhost-llm commited on
Commit
e2a9424
·
verified ·
1 Parent(s): bfaf232

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -96
app.py CHANGED
@@ -154,26 +154,10 @@ async def v1_chat(request: Request, authorization: str = Header(None)):
154
  # ---------------------------------------------------------------------
155
  @api.post("/v1/images/generations")
156
  async def v1_images_generations(request: Request, authorization: str = Header(None)):
157
- """
158
- OpenAI-compatible image generation endpoint that forwards to:
159
- https://api.bytez.com/models/v2/openai/dall-e-3
160
-
161
- It accepts the usual OpenAI body:
162
- {
163
- "model": "dall-e-3", # REQUIRED by clients, but ignored
164
- "prompt": "text", # REQUIRED
165
- "n": 1,
166
- "size": "1024x1024",
167
- "response_format": "url" | "b64_json",
168
- "quality": "standard" | "hd",
169
- "style": "vivid" | "natural"
170
- }
171
- and converts it to the Bytez format from your curl:
172
- { "text": "<prompt>" }
173
- """
174
  check_key(authorization)
175
 
176
- # Use SAME key as chat unless you *really* have a separate one
177
  if not BYTEZ_AUTH:
178
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
179
 
@@ -187,54 +171,28 @@ async def v1_images_generations(request: Request, authorization: str = Header(No
187
  if not prompt or not str(prompt).strip():
188
  raise HTTPException(status_code=400, detail="Field 'prompt' is required")
189
 
190
- # These are there only to satisfy OpenAI-compatible clients
191
- _model = payload.get("model", "dall-e-3") # ignored
192
  n = int(payload.get("n", 1))
193
- response_format = payload.get("response_format", "b64_json") # "url" | "b64_json"
194
- size = payload.get("size", "1024x1024")
195
- quality = payload.get("quality")
196
- style = payload.get("style")
197
-
198
- # ---------------- build Bytez request ----------------
199
- # Minimal payload that we KNOW works from your curl:
200
- bytez_payload = {
201
- "text": prompt,
202
- }
203
 
204
- # Only add extras if Bytez actually supports them.
205
- # Comment these out if you’re unsure; or keep them if Bytez docs say so.
206
- # bytez_payload["num_outputs"] = n
207
- # bytez_payload["size"] = size
208
- # if quality in ["standard", "hd"]:
209
- # bytez_payload["quality"] = quality
210
- # if style in ["vivid", "natural"]:
211
- # bytez_payload["style"] = style
212
 
213
  headers = {
214
- "Authorization": BYTEZ_AUTH, # << important
215
  "Content-Type": "application/json",
216
  }
217
 
218
- # ---------------- call Bytez ----------------
219
  async with httpx.AsyncClient(timeout=200) as client:
220
  try:
221
- resp = await client.post(
222
- BYTEZ_IMAGE_URL,
223
- json=bytez_payload,
224
- headers=headers,
225
- )
226
  resp.raise_for_status()
227
  except httpx.HTTPStatusError as e:
228
- # Surface Bytez error message directly to client
229
  detail = None
230
  try:
231
  detail = e.response.json()
232
- except Exception:
233
  detail = e.response.text
234
- raise HTTPException(
235
- status_code=e.response.status_code,
236
- detail={"upstream_error": detail},
237
- )
238
  except Exception as e:
239
  raise HTTPException(status_code=502, detail=f"Bytez unreachable: {str(e)}")
240
 
@@ -243,69 +201,68 @@ async def v1_images_generations(request: Request, authorization: str = Header(No
243
  except json.JSONDecodeError:
244
  raise HTTPException(status_code=502, detail="Bytez returned invalid JSON")
245
 
246
- # ---------------- map Bytez → OpenAI image response ----------------
247
- # We don't know the exact shape, so we handle several possibilities.
248
-
249
  images = []
250
 
251
- # Case 1: { "images": ["url_or_b64", ...] }
252
- if isinstance(bytez_data, dict) and "images" in bytez_data and isinstance(bytez_data["images"], list):
253
  images = bytez_data["images"]
254
 
255
- # Case 2: { "data": [ { "url": "..." } , ... ] }
256
- elif isinstance(bytez_data, dict) and "data" in bytez_data and isinstance(bytez_data["data"], list):
257
  for item in bytez_data["data"]:
258
- if "url" in item:
259
- images.append(item["url"])
260
- elif "b64_json" in item:
261
- images.append(item["b64_json"])
262
- else:
263
- images.append(str(item))
264
-
265
- # Case 3: single string
266
  elif isinstance(bytez_data, str):
267
  images = [bytez_data]
268
 
269
- # Fallback: treat whole thing as one string
270
  else:
271
  images = [str(bytez_data)]
272
 
273
  if not images:
274
- raise HTTPException(status_code=500, detail={"error": "No images returned from Bytez", "raw": bytez_data})
275
 
276
- # truncate to n images
277
  images = images[:n]
278
 
 
279
  openai_data = []
280
 
281
- async def fetch_and_b64(url: str) -> str:
282
- async with httpx.AsyncClient(timeout=60) as client:
283
- r = await client.get(url)
284
- r.raise_for_status()
285
- return base64.b64encode(r.content).decode("utf-8")
286
-
287
- for img in images:
288
- # Case 1: Already base64
289
- if isinstance(img, str) and img.startswith("data:image"):
290
- b64_part = img.split("base64,", 1)[1]
291
- openai_data.append({"b64_json": b64_part})
292
- continue
293
-
294
- # Case 2: Looks like base64 (no URL)
295
- if isinstance(img, str) and not img.startswith("http"):
296
- openai_data.append({"b64_json": img})
297
- continue
298
-
299
- # Case 3: It is a URL → fetch + convert to base64
300
- if isinstance(img, str) and img.startswith("http"):
301
- try:
302
- b64 = await fetch_and_b64(img)
303
  openai_data.append({"b64_json": b64})
304
- except Exception as e:
305
- raise HTTPException(
306
- status_code=502,
307
- detail=f"Failed to fetch image from URL returned by Bytez: {str(e)}"
308
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
 
311
 
 
154
  # ---------------------------------------------------------------------
155
  @api.post("/v1/images/generations")
156
  async def v1_images_generations(request: Request, authorization: str = Header(None)):
157
+ import base64
158
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  check_key(authorization)
160
 
 
161
  if not BYTEZ_AUTH:
162
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
163
 
 
171
  if not prompt or not str(prompt).strip():
172
  raise HTTPException(status_code=400, detail="Field 'prompt' is required")
173
 
 
 
174
  n = int(payload.get("n", 1))
175
+ response_format = payload.get("response_format", "b64_json") # ignored; we always return b64_json
 
 
 
 
 
 
 
 
 
176
 
177
+ # ---------------- Bytez request ----------------
178
+ bytez_payload = { "text": prompt }
 
 
 
 
 
 
179
 
180
  headers = {
181
+ "Authorization": BYTEZ_AUTH,
182
  "Content-Type": "application/json",
183
  }
184
 
 
185
  async with httpx.AsyncClient(timeout=200) as client:
186
  try:
187
+ resp = await client.post(BYTEZ_IMAGE_URL, json=bytez_payload, headers=headers)
 
 
 
 
188
  resp.raise_for_status()
189
  except httpx.HTTPStatusError as e:
 
190
  detail = None
191
  try:
192
  detail = e.response.json()
193
+ except:
194
  detail = e.response.text
195
+ raise HTTPException(status_code=e.response.status_code, detail={"upstream_error": detail})
 
 
 
196
  except Exception as e:
197
  raise HTTPException(status_code=502, detail=f"Bytez unreachable: {str(e)}")
198
 
 
201
  except json.JSONDecodeError:
202
  raise HTTPException(status_code=502, detail="Bytez returned invalid JSON")
203
 
204
+ # ---------------- normalize Bytez → list of images ----------------
 
 
205
  images = []
206
 
207
+ if isinstance(bytez_data, dict) and "images" in bytez_data:
 
208
  images = bytez_data["images"]
209
 
210
+ elif isinstance(bytez_data, dict) and "data" in bytez_data:
 
211
  for item in bytez_data["data"]:
212
+ images.append(item.get("url") or item.get("b64_json") or str(item))
213
+
 
 
 
 
 
 
214
  elif isinstance(bytez_data, str):
215
  images = [bytez_data]
216
 
 
217
  else:
218
  images = [str(bytez_data)]
219
 
220
  if not images:
221
+ raise HTTPException(status_code=500, detail="No images returned from Bytez")
222
 
 
223
  images = images[:n]
224
 
225
+ # ---------------- ALWAYS RETURN BASE64 ----------------
226
  openai_data = []
227
 
228
+ async def fetch_and_b64(url: str) -> str:
229
+ async with httpx.AsyncClient(timeout=60) as client:
230
+ r = await client.get(url)
231
+ r.raise_for_status()
232
+ return base64.b64encode(r.content).decode("utf-8")
233
+
234
+ for img in images:
235
+
236
+ # Case 1: data:image/...;base64,...
237
+ if isinstance(img, str) and img.startswith("data:image"):
238
+ b64 = img.split("base64,", 1)[1]
 
 
 
 
 
 
 
 
 
 
 
239
  openai_data.append({"b64_json": b64})
240
+ continue
241
+
242
+ # Case 2: pure base64
243
+ if isinstance(img, str) and not img.startswith("http"):
244
+ openai_data.append({"b64_json": img})
245
+ continue
246
+
247
+ # Case 3: URL → fetch + convert
248
+ if isinstance(img, str) and img.startswith("http"):
249
+ try:
250
+ b64 = await fetch_and_b64(img)
251
+ openai_data.append({"b64_json": b64})
252
+ except Exception as e:
253
+ raise HTTPException(
254
+ status_code=502,
255
+ detail=f"Failed to fetch image from URL returned by Bytez: {str(e)}"
256
+ )
257
+
258
+ # ---------------- return clean OpenAI-style result ----------------
259
+ return JSONResponse(
260
+ {
261
+ "created": int(time.time()),
262
+ "data": openai_data
263
+ },
264
+ headers={"Access-Control-Allow-Origin": "*"}
265
+ )
266
 
267
 
268