snackshell commited on
Commit
badccec
·
verified ·
1 Parent(s): 516754b

Update apis/chat_api.py

Browse files
Files changed (1) hide show
  1. apis/chat_api.py +152 -3
apis/chat_api.py CHANGED
@@ -6,11 +6,13 @@ import json
6
 
7
  from fastapi import FastAPI
8
  from fastapi import Request
 
9
  from fastapi.encoders import jsonable_encoder
10
  from fastapi.responses import JSONResponse, StreamingResponse
11
  import uuid
12
  import time
13
  import re
 
14
  try:
15
  import langid
16
  except Exception:
@@ -196,6 +198,36 @@ class ChatAPIApp:
196
  self.app.post(
197
  prefix + "/translate/stream/raw",
198
  summary="stream translated text (plain text body; set ?to_language=am)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  )(self.translate_stream_raw)
200
 
201
  self.app.post(
@@ -203,6 +235,12 @@ class ChatAPIApp:
203
  summary="stream translated text from OpenAI-style chat payload",
204
  )(self.translate_chat_stream)
205
 
 
 
 
 
 
 
206
  def translate_stream(self, item: TranslateCompletionsPostItem):
207
  f = open('apis/lang_name.json', "r")
208
  available_langs = json.loads(f.read())
@@ -264,15 +302,126 @@ class ChatAPIApp:
264
 
265
  return StreamingResponse(event_generator(), media_type="text/event-stream")
266
 
267
- async def translate_stream_raw(self, request: Request, to_language: str = "am"):
268
- body_bytes = await request.body()
269
- input_text = body_bytes.decode("utf-8", errors="ignore")
 
 
 
 
270
  payload = self.TranslateCompletionsPostItem(
271
  to_language=to_language,
272
  input_text=input_text,
273
  )
274
  return self.translate_stream(payload)
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  def translate_chat_stream(self, item: ChatTranslateStreamItem):
277
  # Extract latest user content
278
  input_text = None
 
6
 
7
  from fastapi import FastAPI
8
  from fastapi import Request
9
+ from fastapi import Body
10
  from fastapi.encoders import jsonable_encoder
11
  from fastapi.responses import JSONResponse, StreamingResponse
12
  import uuid
13
  import time
14
  import re
15
+ import requests
16
  try:
17
  import langid
18
  except Exception:
 
198
  self.app.post(
199
  prefix + "/translate/stream/raw",
200
  summary="stream translated text (plain text body; set ?to_language=am)",
201
+ openapi_extra={
202
+ "requestBody": {
203
+ "required": True,
204
+ "content": {
205
+ "text/plain": {
206
+ "schema": {"type": "string", "example": "selam, endet neh?"},
207
+ "examples": {
208
+ "AmharicRomanized": {
209
+ "summary": "Romanized Amharic",
210
+ "value": "selam, endet neh?"
211
+ },
212
+ "Paragraph": {
213
+ "summary": "Multiline plain text",
214
+ "value": "The Ethiopian Alphasyllabary: A Look at Amharic and Tigrinya\nThe writing systems for Amharic and Tigrinya are beautiful and complex examples of an alphasyllabary."
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ },
221
+ responses={
222
+ 200: {
223
+ "description": "SSE stream",
224
+ "content": {
225
+ "text/event-stream": {
226
+ "schema": {"type": "string", "example": "data: {\\\"choices\\\":[{\\\"delta\\\":{\\\"content\\\":\\\"...\\\"}}]}\\n\\n"}
227
+ }
228
+ }
229
+ }
230
+ },
231
  )(self.translate_stream_raw)
232
 
233
  self.app.post(
 
235
  summary="stream translated text from OpenAI-style chat payload",
236
  )(self.translate_chat_stream)
237
 
238
+ # Proxy an OpenAI-style SSE stream via Pollinations, pre/post translating
239
+ self.app.post(
240
+ prefix + "/translate/chat/proxy/stream",
241
+ summary="proxy OpenAI-style chat stream via Pollinations with translation",
242
+ )(self.translate_chat_proxy_stream)
243
+
244
  def translate_stream(self, item: TranslateCompletionsPostItem):
245
  f = open('apis/lang_name.json', "r")
246
  available_langs = json.loads(f.read())
 
302
 
303
  return StreamingResponse(event_generator(), media_type="text/event-stream")
304
 
305
+ async def translate_stream_raw(self, request: Request, to_language: str = "am", text: str = Body(default=None, media_type="text/plain")):
306
+ # Prefer explicit text/plain body if provided, else use raw bytes
307
+ if text is not None:
308
+ input_text = text
309
+ else:
310
+ body_bytes = await request.body()
311
+ input_text = body_bytes.decode("utf-8", errors="ignore")
312
  payload = self.TranslateCompletionsPostItem(
313
  to_language=to_language,
314
  input_text=input_text,
315
  )
316
  return self.translate_stream(payload)
317
 
318
+ class ChatProxyStreamItem(BaseModel):
319
+ model: str = Field(default="gpt-4.1", description="Pollinations model name")
320
+ stream: bool = Field(default=True)
321
+ to_language: str = Field(default="am")
322
+ from_language: str | None = Field(default=None)
323
+ messages: list[dict] = Field(default_factory=list)
324
+ api_url: str | None = Field(default=None, description="Override Pollinations API URL")
325
+
326
+ def translate_chat_proxy_stream(self, item: ChatProxyStreamItem):
327
+ api_url = item.api_url or "https://text.pollinations.ai/openai"
328
+ # Find last user message
329
+ user_text = ""
330
+ for msg in reversed(item.messages or []):
331
+ if msg.get("role") == "user":
332
+ user_text = msg.get("content", "")
333
+ break
334
+
335
+ # Pre-translate user input to English for LLM
336
+ detected_src, _ = self._detect_language_advanced(user_text)
337
+ pre_text = self._preprocess_text_for_translation(user_text, detected_src)
338
+ try:
339
+ llm_input_en = GoogleTranslator(source='auto', target='en').translate(pre_text)
340
+ except Exception:
341
+ llm_input_en = user_text
342
+
343
+ # Build messages with replaced last user message
344
+ proxied_messages = list(item.messages or [])
345
+ for i in range(len(proxied_messages) - 1, -1, -1):
346
+ if proxied_messages[i].get("role") == "user":
347
+ proxied_messages[i] = {**proxied_messages[i], "content": llm_input_en}
348
+ break
349
+
350
+ req_headers = {
351
+ "Content-Type": "application/json",
352
+ "Accept": "text/event-stream",
353
+ }
354
+ req_body = {
355
+ "model": item.model,
356
+ "messages": proxied_messages,
357
+ "stream": True,
358
+ }
359
+
360
+ # Make streaming request to Pollinations
361
+ try:
362
+ resp = requests.post(api_url, headers=req_headers, json=req_body, stream=True, timeout=60)
363
+ resp.raise_for_status()
364
+ except Exception as e:
365
+ def err_gen():
366
+ chunk = {
367
+ "id": f"proxy-{uuid.uuid4()}",
368
+ "object": "chat.completion.chunk",
369
+ "choices": [{"index": 0, "delta": {"content": ""}, "finish_reason": "error"}],
370
+ "error": str(e),
371
+ }
372
+ yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
373
+ yield "data: [DONE]\n\n"
374
+ return StreamingResponse(err_gen(), media_type="text/event-stream")
375
+
376
+ stream_id = f"proxy-{uuid.uuid4()}"
377
+
378
+ def gen():
379
+ buffer = ""
380
+ for line in resp.iter_lines():
381
+ if not line:
382
+ continue
383
+ try:
384
+ s = line.decode("utf-8")
385
+ except Exception:
386
+ continue
387
+ s = s.strip()
388
+ if not s.startswith("data:"):
389
+ continue
390
+ data = s[len("data:"):].strip()
391
+ if data == "[DONE]":
392
+ # Flush remaining buffer
393
+ if buffer:
394
+ try:
395
+ translated = GoogleTranslator(source='en', target=item.to_language).translate(buffer)
396
+ except Exception:
397
+ translated = buffer
398
+ chunk = {"id": stream_id, "object": "chat.completion.chunk", "choices": [{"index": 0, "delta": {"content": translated}, "finish_reason": None}]}
399
+ yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
400
+ buffer = ""
401
+ yield "data: [DONE]\n\n"
402
+ break
403
+ # Parse JSON
404
+ try:
405
+ obj = json.loads(data)
406
+ piece = obj.get("choices", [{}])[0].get("delta", {}).get("content")
407
+ except Exception:
408
+ piece = None
409
+ if piece:
410
+ buffer += piece
411
+ # Translate and flush on sentence boundary or buffer size
412
+ if any(piece.endswith(x) for x in [".", "!", "?", "\n"]) or len(buffer) > 120:
413
+ try:
414
+ translated = GoogleTranslator(source='en', target=item.to_language).translate(buffer)
415
+ except Exception:
416
+ translated = buffer
417
+ chunk = {"id": stream_id, "object": "chat.completion.chunk", "choices": [{"index": 0, "delta": {"content": translated}, "finish_reason": None}]}
418
+ yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
419
+ buffer = ""
420
+ # Safety end
421
+ yield "data: [DONE]\n\n"
422
+
423
+ return StreamingResponse(gen(), media_type="text/event-stream")
424
+
425
  def translate_chat_stream(self, item: ChatTranslateStreamItem):
426
  # Extract latest user content
427
  input_text = None