AIMaster7 commited on
Commit
64e8d9c
·
verified ·
1 Parent(s): 8f2c22d

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +109 -94
main.py CHANGED
@@ -1,10 +1,10 @@
1
  import base64
2
  import json
3
  import os
4
- import secrets # <-- Import secrets
5
- import string # <-- Import string
6
  import time
7
- from typing import List, Optional
8
 
9
  import httpx
10
  from dotenv import load_dotenv
@@ -28,6 +28,8 @@ AVAILABLE_MODELS = [
28
  {"id": "gpt-4o", "object": "model", "created": int(time.time()), "owned_by": "system"},
29
  {"id": "gpt-3.5-turbo", "object": "model", "created": int(time.time()), "owned_by": "system"},
30
  {"id": "dall-e-3", "object": "model", "created": int(time.time()), "owned_by": "system"},
 
 
31
  ]
32
 
33
  MODEL_ALIASES = {}
@@ -45,9 +47,6 @@ app = FastAPI(
45
  def generate_random_id(prefix: str, length: int = 29) -> str:
46
  """
47
  Generates a cryptographically secure, random alphanumeric ID.
48
- The default length of 29 characters is common for OpenAI IDs.
49
- The example 'bwvaLjbI0KEKMadGmFbSsjYNLgaI' is 30 characters.
50
- You can adjust the length as needed.
51
  """
52
  population = string.ascii_letters + string.digits
53
  random_part = "".join(secrets.choice(population) for _ in range(length))
@@ -77,13 +76,9 @@ class ChatRequest(BaseModel):
77
  async def chat_completion(request: ChatRequest):
78
  """
79
  Handles chat completion requests, supporting both streaming and non-streaming responses.
80
- This endpoint now uses a long, random ID for completions.
81
  """
82
  model_id = MODEL_ALIASES.get(request.model, request.model)
83
-
84
- # Generate the ID once for the entire request
85
  chat_id = generate_random_id("chatcmpl-")
86
-
87
  headers = {
88
  'accept': 'text/event-stream',
89
  'content-type': 'application/json',
@@ -91,7 +86,6 @@ async def chat_completion(request: ChatRequest):
91
  'referer': 'https://www.chatwithmono.xyz/',
92
  'user-agent': 'Mozilla/5.0',
93
  }
94
-
95
  payload = {
96
  "messages": [{"role": msg.role, "content": msg.content} for msg in request.messages],
97
  "model": model_id
@@ -100,7 +94,6 @@ async def chat_completion(request: ChatRequest):
100
  if request.stream:
101
  async def event_stream():
102
  created = int(time.time())
103
-
104
  is_first_chunk = True
105
  usage_info = None
106
 
@@ -108,58 +101,38 @@ async def chat_completion(request: ChatRequest):
108
  async with httpx.AsyncClient(timeout=120) as client:
109
  async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
110
  response.raise_for_status()
111
-
112
  async for line in response.aiter_lines():
113
- if not line:
114
- continue
115
-
116
  if line.startswith("0:"):
117
  try:
118
  content_piece = json.loads(line[2:])
119
- delta = {
120
- "content": content_piece,
121
- "function_call": None,
122
- "tool_calls": None
123
- }
124
  if is_first_chunk:
125
  delta["role"] = "assistant"
126
  is_first_chunk = False
127
-
128
  chunk_data = {
129
- "id": chat_id, # Use the pre-generated ID
130
- "object": "chat.completion.chunk",
131
- "created": created,
132
  "model": model_id,
133
  "choices": [{"index": 0, "delta": delta, "finish_reason": None}],
134
  "usage": None
135
  }
136
  yield f"data: {json.dumps(chunk_data)}\n\n"
137
- except json.JSONDecodeError:
138
- continue
139
-
140
  elif line.startswith(("e:", "d:")):
141
  try:
142
- end_data = json.loads(line[2:])
143
- usage_info = end_data.get("usage")
144
- except (json.JSONDecodeError, AttributeError):
145
- pass
146
  break
147
-
148
  final_usage = None
149
  if usage_info:
150
  prompt_tokens = usage_info.get("promptTokens", 0)
151
  completion_tokens = usage_info.get("completionTokens", 0)
152
  final_usage = {
153
- "prompt_tokens": prompt_tokens,
154
- "completion_tokens": completion_tokens,
155
  "total_tokens": prompt_tokens + completion_tokens,
156
  }
157
-
158
  done_chunk = {
159
- "id": chat_id, # Use the pre-generated ID
160
- "object": "chat.completion.chunk",
161
- "created": created,
162
- "model": model_id,
163
  "choices": [{
164
  "index": 0,
165
  "delta": {"role": "assistant", "content": None, "function_call": None, "tool_calls": None},
@@ -168,54 +141,33 @@ async def chat_completion(request: ChatRequest):
168
  "usage": final_usage
169
  }
170
  yield f"data: {json.dumps(done_chunk)}\n\n"
171
-
172
  except httpx.HTTPStatusError as e:
173
  error_content = {
174
  "error": {
175
  "message": f"Upstream API error: {e.response.status_code}. Details: {e.response.text}",
176
- "type": "upstream_error",
177
- "code": str(e.response.status_code)
178
  }
179
  }
180
  yield f"data: {json.dumps(error_content)}\n\n"
181
-
182
  finally:
183
  yield "data: [DONE]\n\n"
184
-
185
  return StreamingResponse(event_stream(), media_type="text/event-stream")
186
- else:
187
- # Non-streaming logic
188
- assistant_response = ""
189
- usage_info = {}
190
-
191
  try:
192
  async with httpx.AsyncClient(timeout=120) as client:
193
  async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
194
  response.raise_for_status()
195
  async for chunk in response.aiter_lines():
196
  if chunk.startswith("0:"):
197
- try:
198
- piece = json.loads(chunk[2:])
199
- assistant_response += piece
200
- except:
201
- continue
202
  elif chunk.startswith(("e:", "d:")):
203
- try:
204
- data = json.loads(chunk[2:])
205
- usage_info = data.get("usage", {})
206
- except:
207
- continue
208
-
209
  return JSONResponse(content={
210
- "id": chat_id, # Use the pre-generated ID
211
- "object": "chat.completion",
212
- "created": int(time.time()),
213
- "model": model_id,
214
- "choices": [{
215
- "index": 0,
216
- "message": {"role": "assistant", "content": assistant_response},
217
- "finish_reason": "stop"
218
- }],
219
  "usage": {
220
  "prompt_tokens": usage_info.get("promptTokens", 0),
221
  "completion_tokens": usage_info.get("completionTokens", 0),
@@ -223,15 +175,7 @@ async def chat_completion(request: ChatRequest):
223
  }
224
  })
225
  except httpx.HTTPStatusError as e:
226
- return JSONResponse(
227
- status_code=e.response.status_code,
228
- content={
229
- "error": {
230
- "message": f"Upstream API error. Details: {e.response.text}",
231
- "type": "upstream_error"
232
- }
233
- }
234
- )
235
 
236
 
237
  # === Image Generation ===
@@ -247,26 +191,18 @@ class ImageGenerationRequest(BaseModel):
247
  async def generate_images(request: ImageGenerationRequest):
248
  """Handles image generation requests."""
249
  results = []
250
-
251
  try:
252
  async with httpx.AsyncClient(timeout=120) as client:
253
  for _ in range(request.n):
254
  model = request.model or "default"
255
-
256
  if model in ["gpt-image-1", "dall-e-3", "dall-e-2", "nextlm-image-1"]:
257
- headers = {
258
- 'Content-Type': 'application/json',
259
- 'User-Agent': 'Mozilla/5.0',
260
- 'Referer': 'https://www.chatwithmono.xyz/',
261
- }
262
  payload = {"prompt": request.prompt, "model": model}
263
  resp = await client.post("https://www.chatwithmono.xyz/api/image", headers=headers, json=payload)
264
  resp.raise_for_status()
265
  data = resp.json()
266
  b64_image = data.get("image")
267
- if not b64_image:
268
- return JSONResponse(status_code=502, content={"error": "Missing base64 image in response"})
269
-
270
  if SNAPZION_API_KEY:
271
  upload_headers = {"Authorization": SNAPZION_API_KEY}
272
  upload_files = {'file': ('image.png', base64.b64decode(b64_image), 'image/png')}
@@ -276,7 +212,6 @@ async def generate_images(request: ImageGenerationRequest):
276
  image_url = upload_data.get("url")
277
  else:
278
  image_url = f"data:image/png;base64,{b64_image}"
279
-
280
  results.append({"url": image_url, "b64_json": b64_image, "revised_prompt": data.get("revised_prompt")})
281
  else:
282
  params = {"prompt": request.prompt, "aspect_ratio": request.aspect_ratio, "link": "typegpt.net"}
@@ -284,17 +219,97 @@ async def generate_images(request: ImageGenerationRequest):
284
  resp.raise_for_status()
285
  data = resp.json()
286
  results.append({"url": data.get("image_link"), "b64_json": data.get("base64_output")})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  except httpx.HTTPStatusError as e:
288
  return JSONResponse(
289
- status_code=502,
290
- content={"error": f"Image generation failed. Upstream error: {e.response.status_code}", "details": e.response.text}
291
  )
292
  except Exception as e:
293
- return JSONResponse(status_code=500, content={"error": "An internal error occurred.", "details": str(e)})
294
 
 
 
 
 
 
 
 
295
 
296
- return {"created": int(time.time()), "data": results}
297
 
 
298
 
299
  if __name__ == "__main__":
300
  import uvicorn
 
1
  import base64
2
  import json
3
  import os
4
+ import secrets
5
+ import string
6
  import time
7
+ from typing import List, Optional, Union # <-- Added Union
8
 
9
  import httpx
10
  from dotenv import load_dotenv
 
28
  {"id": "gpt-4o", "object": "model", "created": int(time.time()), "owned_by": "system"},
29
  {"id": "gpt-3.5-turbo", "object": "model", "created": int(time.time()), "owned_by": "system"},
30
  {"id": "dall-e-3", "object": "model", "created": int(time.time()), "owned_by": "system"},
31
+ # Added moderation model to the list
32
+ {"id": "text-moderation-stable", "object": "model", "created": int(time.time()), "owned_by": "system"},
33
  ]
34
 
35
  MODEL_ALIASES = {}
 
47
  def generate_random_id(prefix: str, length: int = 29) -> str:
48
  """
49
  Generates a cryptographically secure, random alphanumeric ID.
 
 
 
50
  """
51
  population = string.ascii_letters + string.digits
52
  random_part = "".join(secrets.choice(population) for _ in range(length))
 
76
  async def chat_completion(request: ChatRequest):
77
  """
78
  Handles chat completion requests, supporting both streaming and non-streaming responses.
 
79
  """
80
  model_id = MODEL_ALIASES.get(request.model, request.model)
 
 
81
  chat_id = generate_random_id("chatcmpl-")
 
82
  headers = {
83
  'accept': 'text/event-stream',
84
  'content-type': 'application/json',
 
86
  'referer': 'https://www.chatwithmono.xyz/',
87
  'user-agent': 'Mozilla/5.0',
88
  }
 
89
  payload = {
90
  "messages": [{"role": msg.role, "content": msg.content} for msg in request.messages],
91
  "model": model_id
 
94
  if request.stream:
95
  async def event_stream():
96
  created = int(time.time())
 
97
  is_first_chunk = True
98
  usage_info = None
99
 
 
101
  async with httpx.AsyncClient(timeout=120) as client:
102
  async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
103
  response.raise_for_status()
 
104
  async for line in response.aiter_lines():
105
+ if not line: continue
 
 
106
  if line.startswith("0:"):
107
  try:
108
  content_piece = json.loads(line[2:])
109
+ delta = {"content": content_piece, "function_call": None, "tool_calls": None}
 
 
 
 
110
  if is_first_chunk:
111
  delta["role"] = "assistant"
112
  is_first_chunk = False
 
113
  chunk_data = {
114
+ "id": chat_id, "object": "chat.completion.chunk", "created": created,
 
 
115
  "model": model_id,
116
  "choices": [{"index": 0, "delta": delta, "finish_reason": None}],
117
  "usage": None
118
  }
119
  yield f"data: {json.dumps(chunk_data)}\n\n"
120
+ except json.JSONDecodeError: continue
 
 
121
  elif line.startswith(("e:", "d:")):
122
  try:
123
+ usage_info = json.loads(line[2:]).get("usage")
124
+ except (json.JSONDecodeError, AttributeError): pass
 
 
125
  break
 
126
  final_usage = None
127
  if usage_info:
128
  prompt_tokens = usage_info.get("promptTokens", 0)
129
  completion_tokens = usage_info.get("completionTokens", 0)
130
  final_usage = {
131
+ "prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens,
 
132
  "total_tokens": prompt_tokens + completion_tokens,
133
  }
 
134
  done_chunk = {
135
+ "id": chat_id, "object": "chat.completion.chunk", "created": created, "model": model_id,
 
 
 
136
  "choices": [{
137
  "index": 0,
138
  "delta": {"role": "assistant", "content": None, "function_call": None, "tool_calls": None},
 
141
  "usage": final_usage
142
  }
143
  yield f"data: {json.dumps(done_chunk)}\n\n"
 
144
  except httpx.HTTPStatusError as e:
145
  error_content = {
146
  "error": {
147
  "message": f"Upstream API error: {e.response.status_code}. Details: {e.response.text}",
148
+ "type": "upstream_error", "code": str(e.response.status_code)
 
149
  }
150
  }
151
  yield f"data: {json.dumps(error_content)}\n\n"
 
152
  finally:
153
  yield "data: [DONE]\n\n"
 
154
  return StreamingResponse(event_stream(), media_type="text/event-stream")
155
+ else: # Non-streaming
156
+ assistant_response, usage_info = "", {}
 
 
 
157
  try:
158
  async with httpx.AsyncClient(timeout=120) as client:
159
  async with client.stream("POST", "https://www.chatwithmono.xyz/api/chat", headers=headers, json=payload) as response:
160
  response.raise_for_status()
161
  async for chunk in response.aiter_lines():
162
  if chunk.startswith("0:"):
163
+ try: assistant_response += json.loads(chunk[2:])
164
+ except: continue
 
 
 
165
  elif chunk.startswith(("e:", "d:")):
166
+ try: usage_info = json.loads(chunk[2:]).get("usage", {})
167
+ except: continue
 
 
 
 
168
  return JSONResponse(content={
169
+ "id": chat_id, "object": "chat.completion", "created": int(time.time()), "model": model_id,
170
+ "choices": [{"index": 0, "message": {"role": "assistant", "content": assistant_response}, "finish_reason": "stop"}],
 
 
 
 
 
 
 
171
  "usage": {
172
  "prompt_tokens": usage_info.get("promptTokens", 0),
173
  "completion_tokens": usage_info.get("completionTokens", 0),
 
175
  }
176
  })
177
  except httpx.HTTPStatusError as e:
178
+ return JSONResponse(status_code=e.response.status_code, content={"error": {"message": f"Upstream API error. Details: {e.response.text}", "type": "upstream_error"}})
 
 
 
 
 
 
 
 
179
 
180
 
181
  # === Image Generation ===
 
191
  async def generate_images(request: ImageGenerationRequest):
192
  """Handles image generation requests."""
193
  results = []
 
194
  try:
195
  async with httpx.AsyncClient(timeout=120) as client:
196
  for _ in range(request.n):
197
  model = request.model or "default"
 
198
  if model in ["gpt-image-1", "dall-e-3", "dall-e-2", "nextlm-image-1"]:
199
+ headers = {'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0', 'Referer': 'https://www.chatwithmono.xyz/'}
 
 
 
 
200
  payload = {"prompt": request.prompt, "model": model}
201
  resp = await client.post("https://www.chatwithmono.xyz/api/image", headers=headers, json=payload)
202
  resp.raise_for_status()
203
  data = resp.json()
204
  b64_image = data.get("image")
205
+ if not b64_image: return JSONResponse(status_code=502, content={"error": "Missing base64 image in response"})
 
 
206
  if SNAPZION_API_KEY:
207
  upload_headers = {"Authorization": SNAPZION_API_KEY}
208
  upload_files = {'file': ('image.png', base64.b64decode(b64_image), 'image/png')}
 
212
  image_url = upload_data.get("url")
213
  else:
214
  image_url = f"data:image/png;base64,{b64_image}"
 
215
  results.append({"url": image_url, "b64_json": b64_image, "revised_prompt": data.get("revised_prompt")})
216
  else:
217
  params = {"prompt": request.prompt, "aspect_ratio": request.aspect_ratio, "link": "typegpt.net"}
 
219
  resp.raise_for_status()
220
  data = resp.json()
221
  results.append({"url": data.get("image_link"), "b64_json": data.get("base64_output")})
222
+ except httpx.HTTPStatusError as e:
223
+ return JSONResponse(status_code=502, content={"error": f"Image generation failed. Upstream error: {e.response.status_code}", "details": e.response.text})
224
+ except Exception as e:
225
+ return JSONResponse(status_code=500, content={"error": "An internal error occurred.", "details": str(e)})
226
+ return {"created": int(time.time()), "data": results}
227
+
228
+
229
+ # === NEW: Moderation Endpoint ===
230
+
231
+ class ModerationRequest(BaseModel):
232
+ input: Union[str, List[str]]
233
+ model: Optional[str] = "text-moderation-stable"
234
+
235
+ @app.post("/v1/moderations")
236
+ async def create_moderation(request: ModerationRequest):
237
+ """
238
+ Handles moderation requests, conforming to the OpenAI API specification.
239
+ """
240
+ input_texts = [request.input] if isinstance(request.input, str) else request.input
241
+ if not input_texts:
242
+ return JSONResponse(status_code=400, content={"error": {"message": "Request must have at least one input string.", "type": "invalid_request_error"}})
243
+
244
+ moderation_url = "https://www.chatwithmono.xyz/api/moderation"
245
+ headers = {
246
+ 'Content-Type': 'application/json',
247
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
248
+ 'Referer': 'https://www.chatwithmono.xyz/',
249
+ 'sec-ch-ua-platform': '"Windows"',
250
+ 'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
251
+ 'sec-ch-ua-mobile': '?0',
252
+ }
253
+
254
+ results = []
255
+ try:
256
+ async with httpx.AsyncClient(timeout=30) as client:
257
+ for text_input in input_texts:
258
+ payload = {"text": text_input}
259
+ resp = await client.post(moderation_url, headers=headers, json=payload)
260
+ resp.raise_for_status()
261
+ upstream_data = resp.json()
262
+
263
+ # --- Transform upstream response to OpenAI format ---
264
+ # Based on your example, we assume upstream gives: {"overall_sentiment": "...", "categories": {"hate": ...}}
265
+ upstream_categories = upstream_data.get("categories", {})
266
+
267
+ # OpenAI has more specific categories; we'll map the ones we can.
268
+ openai_categories = {
269
+ "hate": upstream_categories.get("hate", False),
270
+ "hate/threatening": False, # No data from upstream
271
+ "harassment": False, # No data from upstream
272
+ "harassment/threatening": False, # No data from upstream
273
+ "self-harm": upstream_categories.get("self-harm", False),
274
+ "self-harm/intent": False, # No data from upstream
275
+ "self-harm/instructions": False, # No data from upstream
276
+ "sexual": upstream_categories.get("sexual", False),
277
+ "sexual/minors": False, # No data from upstream
278
+ "violence": upstream_categories.get("violence", False),
279
+ "violence/graphic": False, # No data from upstream
280
+ }
281
+
282
+ # Generate scores (1.0 for true, 0.0 for false) as upstream doesn't provide them
283
+ category_scores = {k: 1.0 if v else 0.0 for k, v in openai_categories.items()}
284
+
285
+ # Determine overall 'flagged' status
286
+ flagged = upstream_data.get("overall_sentiment") == "flagged"
287
+
288
+ result_item = {
289
+ "flagged": flagged,
290
+ "categories": openai_categories,
291
+ "category_scores": category_scores,
292
+ }
293
+ results.append(result_item)
294
+
295
  except httpx.HTTPStatusError as e:
296
  return JSONResponse(
297
+ status_code=502, # Bad Gateway
298
+ content={"error": {"message": f"Moderation failed. Upstream error: {e.response.status_code}", "type": "upstream_error", "details": e.response.text}}
299
  )
300
  except Exception as e:
301
+ return JSONResponse(status_code=500, content={"error": {"message": "An internal error occurred during moderation.", "type": "internal_error", "details": str(e)}})
302
 
303
+ # Build the final OpenAI-compatible response
304
+ final_response = {
305
+ "id": generate_random_id("modr-"),
306
+ "model": request.model,
307
+ "results": results,
308
+ }
309
+ return JSONResponse(content=final_response)
310
 
 
311
 
312
+ # --- Main Execution ---
313
 
314
  if __name__ == "__main__":
315
  import uvicorn