SiddhJagani commited on
Commit
0de40fa
·
verified ·
1 Parent(s): 7549ac8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -28
app.py CHANGED
@@ -7,10 +7,10 @@ from fastapi import FastAPI, Request, Header, HTTPException, File, UploadFile, F
7
  from fastapi.responses import JSONResponse, StreamingResponse, Response
8
  import time
9
  import base64
10
- from io import BytesIO
11
 
12
  # ---------------------------------------------------------------------
13
- # Configuration - FIXED TRAILING SPACES
14
  # ---------------------------------------------------------------------
15
  BYTEZ_CHAT_URL = "https://api.bytez.com/models/v2/openai/v1/chat/completions"
16
  BYTEZ_MODELS_URL = "https://api.bytez.com/models/v2/list/models"
@@ -63,7 +63,7 @@ async def models(authorization: str = Header(None)):
63
  )
64
 
65
  # ---------------------------------------------------------------------
66
- # /v1/chat/completions - FIXED STREAMING
67
  # ---------------------------------------------------------------------
68
  @api.post("/v1/chat/completions")
69
  async def chat(request: Request, authorization: str = Header(None)):
@@ -85,13 +85,13 @@ async def chat(request: Request, authorization: str = Header(None)):
85
  line = line.strip()
86
  if not line:
87
  continue
88
- if line.startswith("data: "):
89
  json_str = line[6:].strip()
90
  else:
91
  json_str = line
92
 
93
  if json_str == "[DONE]":
94
- yield "data: [DONE]\n\n"
95
  break
96
 
97
  try:
@@ -131,8 +131,8 @@ async def chat(request: Request, authorization: str = Header(None)):
131
  "finish_reason": None,
132
  }],
133
  }
134
- yield f"data: {json.dumps(openai_chunk)}\n\n"
135
- yield "data: [DONE]\n\n"
136
 
137
  if stream:
138
  return StreamingResponse(
@@ -149,7 +149,7 @@ async def chat(request: Request, authorization: str = Header(None)):
149
  except json.JSONDecodeError:
150
  raise HTTPException(status_code=502, detail="Upstream returned invalid JSON")
151
 
152
- if "choices" not in data:
153
  content = (
154
  data.get("output")
155
  or data.get("response")
@@ -166,22 +166,36 @@ async def chat(request: Request, authorization: str = Header(None)):
166
  return JSONResponse(data, headers={"Access-Control-Allow-Origin": "*"})
167
 
168
  # ---------------------------------------------------------------------
169
- # /v1/audio/speech → TTS (OpenAI compatible)
170
  # ---------------------------------------------------------------------
171
  @api.post("/v1/audio/speech")
172
- async def tts(request: Request, authorization: str = Header(None)):
 
 
 
 
 
 
 
 
173
  check_key(authorization)
174
  if not BYTEZ_AUTH:
175
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
176
 
177
- try:
178
- payload = await request.json()
179
- except Exception:
180
- raise HTTPException(status_code=400, detail="Invalid JSON payload")
 
 
 
 
 
 
 
181
 
182
- text = payload.get("input", "")
183
  if not text:
184
- raise HTTPException(status_code=400, detail="Missing 'input' text for TTS")
185
 
186
  headers = {
187
  "Authorization": BYTEZ_AUTH,
@@ -192,34 +206,47 @@ async def tts(request: Request, authorization: str = Header(None)):
192
  async with httpx.AsyncClient(timeout=30) as c:
193
  try:
194
  r = await c.post(BYTEZ_TTS_URL, headers=headers, json=bytez_payload)
195
- except httpx.RequestError as e:
196
  raise HTTPException(status_code=502, detail=f"TTS request failed: {str(e)}")
197
 
198
  if r.status_code != 200:
199
  error_detail = r.text[:200] if r.text else f"Status {r.status_code}"
200
  raise HTTPException(status_code=r.status_code, detail=f"TTS failed: {error_detail}")
201
 
202
- # Return raw audio with proper content type
203
  return Response(
204
  content=r.content,
205
- media_type="audio/mpeg",
206
  headers={"Access-Control-Allow-Origin": "*"}
207
  )
208
 
209
  # ---------------------------------------------------------------------
210
- # /v1/audio/transcriptions → STT (OpenAI compatible)
211
  # ---------------------------------------------------------------------
212
  @api.post("/v1/audio/transcriptions")
213
- async def stt(
 
 
 
 
 
 
214
  authorization: str = Header(None),
215
- file: UploadFile = File(...),
216
  model: str = Form("whisper-1")
217
  ):
 
 
 
 
218
  check_key(authorization)
219
  if not BYTEZ_AUTH:
220
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
221
 
222
- # Read audio file
 
 
 
223
  try:
224
  audio_data = await file.read()
225
  except Exception as e:
@@ -242,7 +269,7 @@ async def stt(
242
  async with httpx.AsyncClient(timeout=60) as c:
243
  try:
244
  r = await c.post(BYTEZ_STT_URL, headers=headers, json=bytez_payload)
245
- except httpx.RequestError as e:
246
  raise HTTPException(status_code=502, detail=f"STT request failed: {str(e)}")
247
 
248
  if r.status_code != 200:
@@ -252,9 +279,13 @@ async def stt(
252
  try:
253
  data = r.json()
254
  except json.JSONDecodeError:
255
- raise HTTPException(status_code=502, detail="Invalid STT response format")
 
 
 
 
256
 
257
- # Extract transcript - handle multiple possible formats
258
  transcript = (
259
  data.get("text") or
260
  data.get("transcript") or
@@ -269,11 +300,12 @@ async def stt(
269
  # ---------------------------------------------------------------------
270
  with gr.Blocks() as ui:
271
  gr.Markdown("### ✅ Jwero Bytez → OpenAI Proxy\n"
272
- "Endpoints:\n"
273
  "- `/v1/models`\n"
274
  "- `/v1/chat/completions`\n"
275
  "- `/v1/audio/speech` (TTS)\n"
276
- "- `/v1/audio/transcriptions` (STT)")
 
277
 
278
  demo = gr.mount_gradio_app(api, ui, path="/")
279
 
 
7
  from fastapi.responses import JSONResponse, StreamingResponse, Response
8
  import time
9
  import base64
10
+ import io
11
 
12
  # ---------------------------------------------------------------------
13
+ # Configuration - FIXED TRAILING SPACES AND URLS
14
  # ---------------------------------------------------------------------
15
  BYTEZ_CHAT_URL = "https://api.bytez.com/models/v2/openai/v1/chat/completions"
16
  BYTEZ_MODELS_URL = "https://api.bytez.com/models/v2/list/models"
 
63
  )
64
 
65
  # ---------------------------------------------------------------------
66
+ # /v1/chat/completions - CORRECT STREAMING
67
  # ---------------------------------------------------------------------
68
  @api.post("/v1/chat/completions")
69
  async def chat(request: Request, authorization: str = Header(None)):
 
85
  line = line.strip()
86
  if not line:
87
  continue
88
+ if line.startswith(""):
89
  json_str = line[6:].strip()
90
  else:
91
  json_str = line
92
 
93
  if json_str == "[DONE]":
94
+ yield " [DONE]\n\n"
95
  break
96
 
97
  try:
 
131
  "finish_reason": None,
132
  }],
133
  }
134
+ yield f" {json.dumps(openai_chunk)}\n\n"
135
+ yield " [DONE]\n\n"
136
 
137
  if stream:
138
  return StreamingResponse(
 
149
  except json.JSONDecodeError:
150
  raise HTTPException(status_code=502, detail="Upstream returned invalid JSON")
151
 
152
+ if "choices" not in
153
  content = (
154
  data.get("output")
155
  or data.get("response")
 
166
  return JSONResponse(data, headers={"Access-Control-Allow-Origin": "*"})
167
 
168
  # ---------------------------------------------------------------------
169
+ # TTS ENDPOINTS (BOTH STANDARD AND NON-STANDARD)
170
  # ---------------------------------------------------------------------
171
  @api.post("/v1/audio/speech")
172
+ @api.post("/v1/tts/audio/speech")
173
+ @api.post("/v1/tts")
174
+ @api.post("/v1/tts/audio/transcriptions") # For backward compatibility
175
+ async def tts_endpoint(
176
+ request: Request,
177
+ authorization: str = Header(None),
178
+ text: str = Form(None), # For form-data requests
179
+ file: UploadFile = File(None) # For file uploads (though TTS doesn't need files)
180
+ ):
181
  check_key(authorization)
182
  if not BYTEZ_AUTH:
183
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
184
 
185
+ # Handle JSON payload
186
+ if request.headers.get("Content-Type", "").startswith("application/json"):
187
+ try:
188
+ payload = await request.json()
189
+ text = payload.get("input") or payload.get("text", "")
190
+ except:
191
+ text = ""
192
+ # Handle form data
193
+ elif text is None and file is not None:
194
+ # This shouldn't happen for TTS but handle gracefully
195
+ text = ""
196
 
 
197
  if not text:
198
+ raise HTTPException(status_code=400, detail="Missing text for TTS conversion")
199
 
200
  headers = {
201
  "Authorization": BYTEZ_AUTH,
 
206
  async with httpx.AsyncClient(timeout=30) as c:
207
  try:
208
  r = await c.post(BYTEZ_TTS_URL, headers=headers, json=bytez_payload)
209
+ except Exception as e:
210
  raise HTTPException(status_code=502, detail=f"TTS request failed: {str(e)}")
211
 
212
  if r.status_code != 200:
213
  error_detail = r.text[:200] if r.text else f"Status {r.status_code}"
214
  raise HTTPException(status_code=r.status_code, detail=f"TTS failed: {error_detail}")
215
 
216
+ # Return raw audio
217
  return Response(
218
  content=r.content,
219
+ media_type="audio/mpeg", # Bytez returns MP3
220
  headers={"Access-Control-Allow-Origin": "*"}
221
  )
222
 
223
  # ---------------------------------------------------------------------
224
+ # STT ENDPOINTS (BOTH STANDARD AND NON-STANDARD)
225
  # ---------------------------------------------------------------------
226
  @api.post("/v1/audio/transcriptions")
227
+ @api.post("/v1/stt")
228
+ @api.post("/v1/stt/audio/transcriptions")
229
+ @api.post("/v1/audio/voices") # Fake endpoint for compatibility
230
+ @api.get("/v1/audio/voices") # Fake GET endpoint
231
+ @api.get("/v1/stt/audio/models") # Fake endpoint
232
+ async def stt_endpoint(
233
+ request: Request = None,
234
  authorization: str = Header(None),
235
+ file: UploadFile = File(None),
236
  model: str = Form("whisper-1")
237
  ):
238
+ # Handle fake endpoints that just return empty responses
239
+ if request and request.url.path in ["/v1/audio/voices", "/v1/stt/audio/models"]:
240
+ return JSONResponse({"voices": [], "models": []})
241
+
242
  check_key(authorization)
243
  if not BYTEZ_AUTH:
244
  raise HTTPException(status_code=500, detail="Server BYTEZ_API_KEY not configured")
245
 
246
+ # Handle file upload
247
+ if file is None:
248
+ raise HTTPException(status_code=400, detail="No audio file provided")
249
+
250
  try:
251
  audio_data = await file.read()
252
  except Exception as e:
 
269
  async with httpx.AsyncClient(timeout=60) as c:
270
  try:
271
  r = await c.post(BYTEZ_STT_URL, headers=headers, json=bytez_payload)
272
+ except Exception as e:
273
  raise HTTPException(status_code=502, detail=f"STT request failed: {str(e)}")
274
 
275
  if r.status_code != 200:
 
279
  try:
280
  data = r.json()
281
  except json.JSONDecodeError:
282
+ # If response isn't JSON, try to extract text from raw response
283
+ transcript = r.text.strip()
284
+ if not transcript:
285
+ raise HTTPException(status_code=502, detail="STT returned empty response")
286
+ return JSONResponse({"text": transcript}, headers={"Access-Control-Allow-Origin": "*"})
287
 
288
+ # Extract transcript from various possible formats
289
  transcript = (
290
  data.get("text") or
291
  data.get("transcript") or
 
300
  # ---------------------------------------------------------------------
301
  with gr.Blocks() as ui:
302
  gr.Markdown("### ✅ Jwero Bytez → OpenAI Proxy\n"
303
+ "**Supported Endpoints:**\n"
304
  "- `/v1/models`\n"
305
  "- `/v1/chat/completions`\n"
306
  "- `/v1/audio/speech` (TTS)\n"
307
+ "- `/v1/audio/transcriptions` (STT)\n\n"
308
+ "**Also compatible with non-standard endpoints used by some clients**")
309
 
310
  demo = gr.mount_gradio_app(api, ui, path="/")
311