SiddhJagani commited on
Commit
321d2ec
·
verified ·
1 Parent(s): 0804ab6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -68
app.py CHANGED
@@ -149,112 +149,158 @@ async def v1_chat(request: Request, authorization: str = Header(None)):
149
  # ---------------------------------------------------------------------
150
  # --------------------- /v1/images/generations (FIXED) ---------------
151
  # ---------------------------------------------------------------------
 
 
 
152
  @api.post("/v1/images/generations")
153
  async def v1_images_generations(request: Request, authorization: str = Header(None)):
154
  """
155
- Fully OpenAI-compatible DALL·E-3 via Bytez
156
- → Accepts `model` field (required by Continue.dev, Cursor, etc.)
157
- → Ignores it safely (since Bytez uses URL path, not model name)
158
- Returns proper OpenAI format with url + b64_json
 
 
 
 
 
 
 
 
 
 
 
159
  """
160
  check_key(authorization)
161
 
162
- if not BYTEZ_AUTH_2:
163
- raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY_2 not configured")
 
164
 
 
165
  try:
166
  payload = await request.json()
167
  except json.JSONDecodeError:
168
  raise HTTPException(status_code=400, detail="Invalid JSON")
169
 
170
- prompt = payload.get("prompt")
171
- if not prompt or not prompt.strip():
172
- raise HTTPException(status_code=400, detail="Field 'prompt' is required and cannot be empty")
173
 
174
- # These fields are REQUIRED by UI tools even if we ignore some
175
- model_name = payload.get("model", "dall-e-3") # just for show — we ignore it
176
- n = payload.get("n", 1)
 
177
  size = payload.get("size", "1024x1024")
178
- quality = payload.get("quality", "standard")
179
- style = payload.get("style") # vivid or natural
180
- response_format = payload.get("response_format", "url") # url or b64_json
181
-
182
- # Map OpenAI sizes → Bytez accepts the same strings
183
- if size not in ["1024x1024", "1024x1792", "1792x1024"]:
184
- size = "1024x1024" # fallback
185
 
 
 
186
  bytez_payload = {
187
  "text": prompt,
188
- "num_outputs": n,
189
- "size": size,
190
  }
191
- if quality in ["standard", "hd"]:
192
- bytez_payload["quality"] = quality
193
- if style in ["vivid", "natural"]:
194
- bytez_payload["style"] = style
 
 
 
 
 
195
 
196
  headers = {
197
- "Authorization": BYTEZ_AUTH_2,
198
  "Content-Type": "application/json",
199
  }
200
 
 
201
  async with httpx.AsyncClient(timeout=200) as client:
202
  try:
203
  resp = await client.post(
204
- "https://api.bytez.com/models/v2/openai/dall-e-3",
205
  json=bytez_payload,
206
  headers=headers,
207
  )
208
  resp.raise_for_status()
209
  except httpx.HTTPStatusError as e:
 
 
210
  try:
211
- error_detail = e.response.json()
212
- except:
213
- error_detail = e.response.text
214
- raise HTTPException(status_code=e.response.status_code, detail=error_detail)
 
 
 
215
  except Exception as e:
216
  raise HTTPException(status_code=502, detail=f"Bytez unreachable: {str(e)}")
217
 
218
- try:
219
- bytez_data = resp.json()
220
- except json.JSONDecodeError:
221
- raise HTTPException(status_code=502, detail="Bytez returned invalid JSON")
222
 
223
- # Handle different possible response shapes from Bytez
224
- images = bytez_data.get("images") or bytez_data.get("data") or []
225
- if isinstance(images, str):
226
- images = [images]
227
- if not images:
228
- raise HTTPException(status_code=500, detail="No images returned from Bytez")
229
-
230
- # Build proper OpenAI response
231
- openai_images = []
232
- for img_data in images:
233
- if img_data.startswith("data:image"):
234
- b64 = img_data.split("base64,")[-1]
235
- url = img_data
236
- else:
237
- b64 = img_data
238
- url = f"data:image/png;base64,{img_data}"
239
 
240
- item = {}
241
- if response_format == "b64_json" or response_format is None:
242
- item["b64_json"] = b64
243
- else:
244
- item["url"] = url
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
- # Optional: include revised_prompt if Bytez returns it
247
- if "revised_prompt" in bytez_data:
248
- item["revised_prompt"] = bytez_data["revised_prompt"]
249
 
250
- openai_images.append(item)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
- final_response = {
 
 
 
 
 
 
 
 
 
253
  "created": int(time.time()),
254
- "data": openai_images
255
  }
256
-
257
- return JSONResponse(final_response, headers={"Access-Control-Allow-Origin": "*"})
258
 
259
 
260
 
@@ -422,8 +468,9 @@ with gr.Blocks() as ui:
422
 
423
  demo = gr.mount_gradio_app(api, ui, path="/")
424
 
425
- # ---------------------------------------------------------------------
426
- # Local dev entrypoint
427
- # ---------------------------------------------------------------------
428
  if __name__ == "__main__":
429
- uvicorn.run(demo, host="0.0.0.0", port=7860)
 
 
149
  # ---------------------------------------------------------------------
150
  # --------------------- /v1/images/generations (FIXED) ---------------
151
  # ---------------------------------------------------------------------
152
+ # ---------------------------------------------------------------------
153
+ # --------------------- /v1/images/generations -----------------------
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
 
180
+ # ---------------- parse request ----------------
181
  try:
182
  payload = await request.json()
183
  except json.JSONDecodeError:
184
  raise HTTPException(status_code=400, detail="Invalid JSON")
185
 
186
+ prompt = payload.get("prompt") or payload.get("text")
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", "url") # "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
 
241
+ try:
242
+ bytez_data = resp.json()
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
+ for img in images:
281
+ # If it looks like a data URL already
282
+ if isinstance(img, str) and img.startswith("data:image"):
283
+ # Already a data URL; extract base64 if needed
284
+ b64_part = img.split("base64,", 1)[-1]
285
+ if response_format == "b64_json":
286
+ openai_data.append({"b64_json": b64_part})
287
+ else:
288
+ openai_data.append({"url": img})
289
 
290
+ else:
291
+ # Raw URL or base64 – we can't be sure, so:
292
+ if response_format == "b64_json":
293
+ # Assume it's base64 or treat as a string anyway
294
+ openai_data.append({"b64_json": str(img)})
295
+ else:
296
+ # Assume it's a URL or make it one if needed
297
+ openai_data.append({"url": str(img)})
298
+
299
+ result = {
300
  "created": int(time.time()),
301
+ "data": openai_data,
302
  }
303
+ return JSONResponse(result, headers={"Access-Control-Allow-Origin": "*"})
 
304
 
305
 
306
 
 
468
 
469
  demo = gr.mount_gradio_app(api, ui, path="/")
470
 
471
+ # This makes it work on Render, Railway, Fly.io, etc.
472
+ app = api
473
+
474
  if __name__ == "__main__":
475
+ # Only for local testing with Gradio
476
+ uvicorn.run(demo, host="0.0.0.0", port=8000)