Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
sachin
commited on
Commit
·
39b1c1e
1
Parent(s):
944b400
add-uits
Browse files- src/server/main.py +158 -95
- src/server/utils/crypto.py +13 -0
src/server/main.py
CHANGED
@@ -20,6 +20,9 @@ from Crypto.Cipher import AES
|
|
20 |
# Import from auth.py
|
21 |
from utils.auth import get_current_user, get_current_user_with_admin, login, refresh_token, register, app_register, TokenResponse, Settings, LoginRequest, RegisterRequest, bearer_scheme
|
22 |
|
|
|
|
|
|
|
23 |
# Assuming these are in your project structure
|
24 |
from config.tts_config import SPEED, ResponseFormat, config as tts_config
|
25 |
from config.logging_config import logger
|
@@ -114,6 +117,77 @@ class AudioProcessingResponse(BaseModel):
|
|
114 |
class Config:
|
115 |
schema_extra = {"example": {"result": "Processed audio output"}}
|
116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
# TTS Service Interface
|
118 |
class TTSService(ABC):
|
119 |
@abstractmethod
|
@@ -271,38 +345,14 @@ async def generate_audio(
|
|
271 |
headers=headers
|
272 |
)
|
273 |
|
274 |
-
class ChatRequest(BaseModel):
|
275 |
-
prompt: str = Field(..., description="Text prompt for chat (max 1000 characters)")
|
276 |
-
src_lang: str = Field("kan_Knda", description="Source language code (default: Kannada)")
|
277 |
-
|
278 |
-
@field_validator("prompt")
|
279 |
-
def prompt_must_be_valid(cls, v):
|
280 |
-
if len(v) > 1000:
|
281 |
-
raise ValueError("Prompt cannot exceed 1000 characters")
|
282 |
-
return v.strip()
|
283 |
-
|
284 |
-
class Config:
|
285 |
-
schema_extra = {
|
286 |
-
"example": {
|
287 |
-
"prompt": "Hello, how are you?",
|
288 |
-
"src_lang": "kan_Knda"
|
289 |
-
}
|
290 |
-
}
|
291 |
-
|
292 |
-
class ChatResponse(BaseModel):
|
293 |
-
response: str = Field(..., description="Generated chat response")
|
294 |
-
|
295 |
-
class Config:
|
296 |
-
schema_extra = {"example": {"response": "Hi there, I'm doing great!"}}
|
297 |
-
|
298 |
@app.post("/v1/chat",
|
299 |
response_model=ChatResponse,
|
300 |
summary="Chat with AI",
|
301 |
-
description="Generate a chat response from
|
302 |
tags=["Chat"],
|
303 |
responses={
|
304 |
200: {"description": "Chat response", "model": ChatResponse},
|
305 |
-
400: {"description": "Invalid prompt"},
|
306 |
401: {"description": "Unauthorized - Token required"},
|
307 |
429: {"description": "Rate limit exceeded"},
|
308 |
504: {"description": "Chat service timeout"}
|
@@ -311,17 +361,31 @@ class ChatResponse(BaseModel):
|
|
311 |
async def chat(
|
312 |
request: Request,
|
313 |
chat_request: ChatRequest,
|
314 |
-
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
|
|
|
315 |
):
|
316 |
user_id = await get_current_user(credentials)
|
317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
318 |
raise HTTPException(status_code=400, detail="Prompt cannot be empty")
|
319 |
-
|
|
|
|
|
|
|
320 |
|
321 |
try:
|
322 |
external_url = "https://slabstech-dhwani-internal-api-server.hf.space/v1/chat"
|
323 |
payload = {
|
324 |
-
"prompt":
|
325 |
"src_lang": chat_request.src_lang,
|
326 |
"tgt_lang": chat_request.src_lang
|
327 |
}
|
@@ -405,22 +469,27 @@ async def process_audio(
|
|
405 |
@app.post("/v1/transcribe/",
|
406 |
response_model=TranscriptionResponse,
|
407 |
summary="Transcribe Audio File",
|
408 |
-
description="Transcribe an
|
409 |
tags=["Audio"],
|
410 |
responses={
|
411 |
200: {"description": "Transcription result", "model": TranscriptionResponse},
|
|
|
412 |
401: {"description": "Unauthorized - Token required"},
|
413 |
504: {"description": "Transcription service timeout"}
|
414 |
})
|
415 |
async def transcribe_audio(
|
416 |
-
file: UploadFile = File(..., description="
|
417 |
language: str = Query(..., enum=["kannada", "hindi", "tamil"], description="Language of the audio"),
|
418 |
-
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
|
|
|
419 |
):
|
420 |
user_id = await get_current_user(credentials)
|
|
|
|
|
421 |
start_time = time()
|
422 |
try:
|
423 |
-
|
|
|
424 |
files = {"file": (file.filename, file_content, file.content_type)}
|
425 |
|
426 |
external_url = f"{settings.external_asr_url}/transcribe/?language={language}"
|
@@ -436,7 +505,10 @@ async def transcribe_audio(
|
|
436 |
logger.info(f"Transcription completed in {time() - start_time:.2f} seconds")
|
437 |
return TranscriptionResponse(text=transcription)
|
438 |
|
|
|
|
|
439 |
except requests.Timeout:
|
|
|
440 |
raise HTTPException(status_code=504, detail="Transcription service timeout")
|
441 |
except requests.RequestException as e:
|
442 |
logger.error(f"Transcription request failed: {str(e)}")
|
@@ -480,48 +552,43 @@ async def chat_v2(
|
|
480 |
logger.error(f"Chat_v2 processing failed: {str(e)}", exc_info=True)
|
481 |
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
|
482 |
|
483 |
-
class TranslationRequest(BaseModel):
|
484 |
-
sentences: List[str] = Field(..., description="List of sentences to translate")
|
485 |
-
src_lang: str = Field(..., description="Source language code")
|
486 |
-
tgt_lang: str = Field(..., description="Target language code")
|
487 |
-
|
488 |
-
class Config:
|
489 |
-
schema_extra = {
|
490 |
-
"example": {
|
491 |
-
"sentences": ["Hello", "How are you?"],
|
492 |
-
"src_lang": "en",
|
493 |
-
"tgt_lang": "kan_Knda"
|
494 |
-
}
|
495 |
-
}
|
496 |
-
|
497 |
-
class TranslationResponse(BaseModel):
|
498 |
-
translations: List[str] = Field(..., description="Translated sentences")
|
499 |
-
|
500 |
-
class Config:
|
501 |
-
schema_extra = {"example": {"translations": ["ನಮಸ್ಕಾರ", "ನೀವು ಹೇಗಿದ್ದೀರಿ?"]}}
|
502 |
-
|
503 |
@app.post("/v1/translate",
|
504 |
response_model=TranslationResponse,
|
505 |
summary="Translate Text",
|
506 |
-
description="Translate a list of sentences from source to target language. Requires authentication.",
|
507 |
tags=["Translation"],
|
508 |
responses={
|
509 |
200: {"description": "Translation result", "model": TranslationResponse},
|
|
|
510 |
401: {"description": "Unauthorized - Token required"},
|
511 |
500: {"description": "Translation service error"},
|
512 |
504: {"description": "Translation service timeout"}
|
513 |
})
|
514 |
async def translate(
|
515 |
request: TranslationRequest,
|
516 |
-
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
|
|
|
517 |
):
|
518 |
user_id = await get_current_user(credentials)
|
519 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
520 |
|
521 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/translate?src_lang={request.src_lang}&tgt_lang={request.tgt_lang}"
|
522 |
|
523 |
payload = {
|
524 |
-
"sentences":
|
525 |
"src_lang": request.src_lang,
|
526 |
"tgt_lang": request.tgt_lang
|
527 |
}
|
@@ -541,7 +608,7 @@ async def translate(
|
|
541 |
response_data = response.json()
|
542 |
translations = response_data.get("translations", [])
|
543 |
|
544 |
-
if not translations or len(translations) != len(
|
545 |
logger.warning(f"Unexpected response format: {response_data}")
|
546 |
raise HTTPException(status_code=500, detail="Invalid response from translation service")
|
547 |
|
@@ -558,28 +625,14 @@ async def translate(
|
|
558 |
logger.error(f"Invalid JSON response: {str(e)}")
|
559 |
raise HTTPException(status_code=500, detail="Invalid response format from translation service")
|
560 |
|
561 |
-
class VisualQueryRequest(BaseModel):
|
562 |
-
query: str
|
563 |
-
src_lang: str = "kan_Knda"
|
564 |
-
tgt_lang: str = "kan_Knda"
|
565 |
-
|
566 |
-
@field_validator("query")
|
567 |
-
def query_must_be_valid(cls, v):
|
568 |
-
if len(v) > 1000:
|
569 |
-
raise ValueError("Query cannot exceed 1000 characters")
|
570 |
-
return v.strip()
|
571 |
-
|
572 |
-
class VisualQueryResponse(BaseModel):
|
573 |
-
answer: str
|
574 |
-
|
575 |
@app.post("/v1/visual_query",
|
576 |
response_model=VisualQueryResponse,
|
577 |
summary="Visual Query with Image",
|
578 |
-
description="Process a visual query with an
|
579 |
tags=["Chat"],
|
580 |
responses={
|
581 |
200: {"description": "Query response", "model": VisualQueryResponse},
|
582 |
-
400: {"description": "Invalid query"},
|
583 |
401: {"description": "Unauthorized - Token required"},
|
584 |
429: {"description": "Rate limit exceeded"},
|
585 |
504: {"description": "Visual query service timeout"}
|
@@ -587,19 +640,40 @@ class VisualQueryResponse(BaseModel):
|
|
587 |
@limiter.limit(settings.chat_rate_limit)
|
588 |
async def visual_query(
|
589 |
request: Request,
|
590 |
-
query: str = Form(..., description="
|
591 |
-
file: UploadFile = File(..., description="
|
592 |
src_lang: str = Query(default="kan_Knda", description="Source language code"),
|
593 |
tgt_lang: str = Query(default="kan_Knda", description="Target language code"),
|
594 |
-
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
|
|
|
595 |
):
|
596 |
user_id = await get_current_user(credentials)
|
597 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
598 |
raise HTTPException(status_code=400, detail="Query cannot be empty")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
599 |
|
600 |
logger.info("Processing visual query request", extra={
|
601 |
"endpoint": "/v1/visual_query",
|
602 |
-
"query_length": len(
|
603 |
"file_name": file.filename,
|
604 |
"client_ip": get_remote_address(request),
|
605 |
"user_id": user_id,
|
@@ -610,9 +684,8 @@ async def visual_query(
|
|
610 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/visual_query/?src_lang={src_lang}&tgt_lang={tgt_lang}"
|
611 |
|
612 |
try:
|
613 |
-
|
614 |
-
|
615 |
-
data = {"query": query}
|
616 |
|
617 |
response = requests.post(
|
618 |
external_url,
|
@@ -650,19 +723,9 @@ class SupportedLanguage(str, Enum):
|
|
650 |
hindi = "hindi"
|
651 |
tamil = "tamil"
|
652 |
|
653 |
-
def decrypt_audio(encrypted_data: bytes, key: bytes) -> bytes:
|
654 |
-
try:
|
655 |
-
nonce, ciphertext = encrypted_data[:12], encrypted_data[12:]
|
656 |
-
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
657 |
-
plaintext = cipher.decrypt_and_verify(ciphertext[:-16], ciphertext[-16:])
|
658 |
-
return plaintext
|
659 |
-
except Exception as e:
|
660 |
-
logger.error(f"Audio decryption failed: {str(e)}")
|
661 |
-
raise HTTPException(status_code=400, detail="Invalid encrypted audio")
|
662 |
-
|
663 |
@app.post("/v1/speech_to_speech",
|
664 |
summary="Speech-to-Speech Conversion",
|
665 |
-
description="Convert input speech to processed speech by calling an external speech-to-speech API. Rate limited to 5 requests per minute per user. Requires authentication and X-Session-Key header.",
|
666 |
tags=["Audio"],
|
667 |
responses={
|
668 |
200: {"description": "Audio stream", "content": {"audio/mp3": {"example": "Binary audio data"}}},
|
@@ -693,7 +756,7 @@ async def speech_to_speech(
|
|
693 |
|
694 |
try:
|
695 |
encrypted_content = await file.read()
|
696 |
-
file_content =
|
697 |
files = {"file": (file.filename, file_content, file.content_type)}
|
698 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/speech_to_speech?language={language}"
|
699 |
|
|
|
20 |
# Import from auth.py
|
21 |
from utils.auth import get_current_user, get_current_user_with_admin, login, refresh_token, register, app_register, TokenResponse, Settings, LoginRequest, RegisterRequest, bearer_scheme
|
22 |
|
23 |
+
# Import decryption utility
|
24 |
+
from utils.crypto import decrypt_data
|
25 |
+
|
26 |
# Assuming these are in your project structure
|
27 |
from config.tts_config import SPEED, ResponseFormat, config as tts_config
|
28 |
from config.logging_config import logger
|
|
|
117 |
class Config:
|
118 |
schema_extra = {"example": {"result": "Processed audio output"}}
|
119 |
|
120 |
+
class ChatRequest(BaseModel):
|
121 |
+
prompt: str = Field(..., description="Base64-encoded encrypted prompt (max 1000 characters after decryption)")
|
122 |
+
src_lang: str = Field("kan_Knda", description="Source language code (default: Kannada)")
|
123 |
+
|
124 |
+
@field_validator("prompt")
|
125 |
+
def prompt_must_be_valid(cls, v):
|
126 |
+
try:
|
127 |
+
base64.b64decode(v)
|
128 |
+
except Exception:
|
129 |
+
raise ValueError("Prompt must be valid base64-encoded data")
|
130 |
+
return v
|
131 |
+
|
132 |
+
class Config:
|
133 |
+
schema_extra = {
|
134 |
+
"example": {
|
135 |
+
"prompt": "base64_encoded_encrypted_prompt",
|
136 |
+
"src_lang": "kan_Knda"
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
class ChatResponse(BaseModel):
|
141 |
+
response: str = Field(..., description="Generated chat response")
|
142 |
+
|
143 |
+
class Config:
|
144 |
+
schema_extra = {"example": {"response": "Hi there, I'm doing great!"}}
|
145 |
+
|
146 |
+
class TranslationRequest(BaseModel):
|
147 |
+
sentences: List[str] = Field(..., description="List of base64-encoded encrypted sentences")
|
148 |
+
src_lang: str = Field(..., description="Source language code")
|
149 |
+
tgt_lang: str = Field(..., description="Target language code")
|
150 |
+
|
151 |
+
@field_validator("sentences")
|
152 |
+
def sentences_must_be_valid(cls, v):
|
153 |
+
for sentence in v:
|
154 |
+
try:
|
155 |
+
base64.b64decode(sentence)
|
156 |
+
except Exception:
|
157 |
+
raise ValueError("Each sentence must be valid base64-encoded data")
|
158 |
+
return v
|
159 |
+
|
160 |
+
class Config:
|
161 |
+
schema_extra = {
|
162 |
+
"example": {
|
163 |
+
"sentences": ["base64_encoded_encrypted_hello", "base64_encoded_encrypted_how_are_you"],
|
164 |
+
"src_lang": "en",
|
165 |
+
"tgt_lang": "kan_Knda"
|
166 |
+
}
|
167 |
+
}
|
168 |
+
|
169 |
+
class TranslationResponse(BaseModel):
|
170 |
+
translations: List[str] = Field(..., description="Translated sentences")
|
171 |
+
|
172 |
+
class Config:
|
173 |
+
schema_extra = {"example": {"translations": ["ನಮಸ್ಕಾರ", "ನೀವು ಹೇಗಿದ್ದೀರಿ?"]}}
|
174 |
+
|
175 |
+
class VisualQueryRequest(BaseModel):
|
176 |
+
query: str
|
177 |
+
src_lang: str = "kan_Knda"
|
178 |
+
tgt_lang: str = "kan_Knda"
|
179 |
+
|
180 |
+
@field_validator("query")
|
181 |
+
def query_must_be_valid(cls, v):
|
182 |
+
try:
|
183 |
+
base64.b64decode(v)
|
184 |
+
except Exception:
|
185 |
+
raise ValueError("Query must be valid base64-encoded data")
|
186 |
+
return v
|
187 |
+
|
188 |
+
class VisualQueryResponse(BaseModel):
|
189 |
+
answer: str
|
190 |
+
|
191 |
# TTS Service Interface
|
192 |
class TTSService(ABC):
|
193 |
@abstractmethod
|
|
|
345 |
headers=headers
|
346 |
)
|
347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
348 |
@app.post("/v1/chat",
|
349 |
response_model=ChatResponse,
|
350 |
summary="Chat with AI",
|
351 |
+
description="Generate a chat response from an encrypted prompt in the specified language. Rate limited to 100 requests per minute per user. Requires authentication and X-Session-Key header.",
|
352 |
tags=["Chat"],
|
353 |
responses={
|
354 |
200: {"description": "Chat response", "model": ChatResponse},
|
355 |
+
400: {"description": "Invalid prompt or encrypted data"},
|
356 |
401: {"description": "Unauthorized - Token required"},
|
357 |
429: {"description": "Rate limit exceeded"},
|
358 |
504: {"description": "Chat service timeout"}
|
|
|
361 |
async def chat(
|
362 |
request: Request,
|
363 |
chat_request: ChatRequest,
|
364 |
+
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
|
365 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
366 |
):
|
367 |
user_id = await get_current_user(credentials)
|
368 |
+
session_key = base64.b64decode(x_session_key)
|
369 |
+
|
370 |
+
# Decrypt the prompt
|
371 |
+
try:
|
372 |
+
encrypted_prompt = base64.b64decode(chat_request.prompt)
|
373 |
+
decrypted_prompt = decrypt_data(encrypted_prompt, session_key).decode("utf-8")
|
374 |
+
except Exception as e:
|
375 |
+
logger.error(f"Prompt decryption failed: {str(e)}")
|
376 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted prompt")
|
377 |
+
|
378 |
+
if not decrypted_prompt:
|
379 |
raise HTTPException(status_code=400, detail="Prompt cannot be empty")
|
380 |
+
if len(decrypted_prompt) > 1000:
|
381 |
+
raise HTTPException(status_code=400, detail="Decrypted prompt cannot exceed 1000 characters")
|
382 |
+
|
383 |
+
logger.info(f"Received prompt: {decrypted_prompt}, src_lang: {chat_request.src_lang}, user_id: {user_id}")
|
384 |
|
385 |
try:
|
386 |
external_url = "https://slabstech-dhwani-internal-api-server.hf.space/v1/chat"
|
387 |
payload = {
|
388 |
+
"prompt": decrypted_prompt,
|
389 |
"src_lang": chat_request.src_lang,
|
390 |
"tgt_lang": chat_request.src_lang
|
391 |
}
|
|
|
469 |
@app.post("/v1/transcribe/",
|
470 |
response_model=TranscriptionResponse,
|
471 |
summary="Transcribe Audio File",
|
472 |
+
description="Transcribe an encrypted audio file into text in the specified language. Requires authentication and X-Session-Key header.",
|
473 |
tags=["Audio"],
|
474 |
responses={
|
475 |
200: {"description": "Transcription result", "model": TranscriptionResponse},
|
476 |
+
400: {"description": "Invalid encrypted audio"},
|
477 |
401: {"description": "Unauthorized - Token required"},
|
478 |
504: {"description": "Transcription service timeout"}
|
479 |
})
|
480 |
async def transcribe_audio(
|
481 |
+
file: UploadFile = File(..., description="Encrypted audio file to transcribe"),
|
482 |
language: str = Query(..., enum=["kannada", "hindi", "tamil"], description="Language of the audio"),
|
483 |
+
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
|
484 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
485 |
):
|
486 |
user_id = await get_current_user(credentials)
|
487 |
+
session_key = base64.b64decode(x_session_key)
|
488 |
+
|
489 |
start_time = time()
|
490 |
try:
|
491 |
+
encrypted_content = await file.read()
|
492 |
+
file_content = decrypt_data(encrypted_content, session_key)
|
493 |
files = {"file": (file.filename, file_content, file.content_type)}
|
494 |
|
495 |
external_url = f"{settings.external_asr_url}/transcribe/?language={language}"
|
|
|
505 |
logger.info(f"Transcription completed in {time() - start_time:.2f} seconds")
|
506 |
return TranscriptionResponse(text=transcription)
|
507 |
|
508 |
+
except HTTPException:
|
509 |
+
raise
|
510 |
except requests.Timeout:
|
511 |
+
logger.error("Transcription service timed out")
|
512 |
raise HTTPException(status_code=504, detail="Transcription service timeout")
|
513 |
except requests.RequestException as e:
|
514 |
logger.error(f"Transcription request failed: {str(e)}")
|
|
|
552 |
logger.error(f"Chat_v2 processing failed: {str(e)}", exc_info=True)
|
553 |
raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
|
554 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
555 |
@app.post("/v1/translate",
|
556 |
response_model=TranslationResponse,
|
557 |
summary="Translate Text",
|
558 |
+
description="Translate a list of base64-encoded encrypted sentences from source to target language. Requires authentication and X-Session-Key header.",
|
559 |
tags=["Translation"],
|
560 |
responses={
|
561 |
200: {"description": "Translation result", "model": TranslationResponse},
|
562 |
+
400: {"description": "Invalid encrypted sentences"},
|
563 |
401: {"description": "Unauthorized - Token required"},
|
564 |
500: {"description": "Translation service error"},
|
565 |
504: {"description": "Translation service timeout"}
|
566 |
})
|
567 |
async def translate(
|
568 |
request: TranslationRequest,
|
569 |
+
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
|
570 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
571 |
):
|
572 |
user_id = await get_current_user(credentials)
|
573 |
+
session_key = base64.b64decode(x_session_key)
|
574 |
+
|
575 |
+
# Decrypt sentences
|
576 |
+
decrypted_sentences = []
|
577 |
+
for sentence in request.sentences:
|
578 |
+
try:
|
579 |
+
encrypted_sentence = base64.b64decode(sentence)
|
580 |
+
decrypted_sentence = decrypt_data(encrypted_sentence, session_key).decode("utf-8")
|
581 |
+
decrypted_sentences.append(decrypted_sentence)
|
582 |
+
except Exception as e:
|
583 |
+
logger.error(f"Sentence decryption failed: {str(e)}")
|
584 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted sentence")
|
585 |
+
|
586 |
+
logger.info(f"Received translation request: {decrypted_sentences}, src_lang: {request.src_lang}, tgt_lang: {request.tgt_lang}, user_id: {user_id}")
|
587 |
|
588 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/translate?src_lang={request.src_lang}&tgt_lang={request.tgt_lang}"
|
589 |
|
590 |
payload = {
|
591 |
+
"sentences": decrypted_sentences,
|
592 |
"src_lang": request.src_lang,
|
593 |
"tgt_lang": request.tgt_lang
|
594 |
}
|
|
|
608 |
response_data = response.json()
|
609 |
translations = response_data.get("translations", [])
|
610 |
|
611 |
+
if not translations or len(translations) != len(decrypted_sentences):
|
612 |
logger.warning(f"Unexpected response format: {response_data}")
|
613 |
raise HTTPException(status_code=500, detail="Invalid response from translation service")
|
614 |
|
|
|
625 |
logger.error(f"Invalid JSON response: {str(e)}")
|
626 |
raise HTTPException(status_code=500, detail="Invalid response format from translation service")
|
627 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
628 |
@app.post("/v1/visual_query",
|
629 |
response_model=VisualQueryResponse,
|
630 |
summary="Visual Query with Image",
|
631 |
+
description="Process a visual query with an encrypted text query and encrypted image. Rate limited to 100 requests per minute per user. Requires authentication and X-Session-Key header.",
|
632 |
tags=["Chat"],
|
633 |
responses={
|
634 |
200: {"description": "Query response", "model": VisualQueryResponse},
|
635 |
+
400: {"description": "Invalid query or encrypted data"},
|
636 |
401: {"description": "Unauthorized - Token required"},
|
637 |
429: {"description": "Rate limit exceeded"},
|
638 |
504: {"description": "Visual query service timeout"}
|
|
|
640 |
@limiter.limit(settings.chat_rate_limit)
|
641 |
async def visual_query(
|
642 |
request: Request,
|
643 |
+
query: str = Form(..., description="Base64-encoded encrypted text query"),
|
644 |
+
file: UploadFile = File(..., description="Encrypted image file to analyze"),
|
645 |
src_lang: str = Query(default="kan_Knda", description="Source language code"),
|
646 |
tgt_lang: str = Query(default="kan_Knda", description="Target language code"),
|
647 |
+
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
|
648 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
649 |
):
|
650 |
user_id = await get_current_user(credentials)
|
651 |
+
session_key = base64.b64decode(x_session_key)
|
652 |
+
|
653 |
+
# Decrypt query
|
654 |
+
try:
|
655 |
+
encrypted_query = base64.b64decode(query)
|
656 |
+
decrypted_query = decrypt_data(encrypted_query, session_key).decode("utf-8")
|
657 |
+
except Exception as e:
|
658 |
+
logger.error(f"Query decryption failed: {str(e)}")
|
659 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted query")
|
660 |
+
|
661 |
+
if not decrypted_query.strip():
|
662 |
raise HTTPException(status_code=400, detail="Query cannot be empty")
|
663 |
+
if len(decrypted_query) > 1000:
|
664 |
+
raise HTTPException(status_code=400, detail="Decrypted query cannot exceed 1000 characters")
|
665 |
+
|
666 |
+
# Decrypt image
|
667 |
+
try:
|
668 |
+
encrypted_content = await file.read()
|
669 |
+
decrypted_content = decrypt_data(encrypted_content, session_key)
|
670 |
+
except Exception as e:
|
671 |
+
logger.error(f"Image decryption failed: {str(e)}")
|
672 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted image")
|
673 |
|
674 |
logger.info("Processing visual query request", extra={
|
675 |
"endpoint": "/v1/visual_query",
|
676 |
+
"query_length": len(decrypted_query),
|
677 |
"file_name": file.filename,
|
678 |
"client_ip": get_remote_address(request),
|
679 |
"user_id": user_id,
|
|
|
684 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/visual_query/?src_lang={src_lang}&tgt_lang={tgt_lang}"
|
685 |
|
686 |
try:
|
687 |
+
files = {"file": (file.filename, decrypted_content, file.content_type)}
|
688 |
+
data = {"query": decrypted_query}
|
|
|
689 |
|
690 |
response = requests.post(
|
691 |
external_url,
|
|
|
723 |
hindi = "hindi"
|
724 |
tamil = "tamil"
|
725 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
726 |
@app.post("/v1/speech_to_speech",
|
727 |
summary="Speech-to-Speech Conversion",
|
728 |
+
description="Convert input encrypted speech to processed speech by calling an external speech-to-speech API. Rate limited to 5 requests per minute per user. Requires authentication and X-Session-Key header.",
|
729 |
tags=["Audio"],
|
730 |
responses={
|
731 |
200: {"description": "Audio stream", "content": {"audio/mp3": {"example": "Binary audio data"}}},
|
|
|
756 |
|
757 |
try:
|
758 |
encrypted_content = await file.read()
|
759 |
+
file_content = decrypt_data(encrypted_content, session_key)
|
760 |
files = {"file": (file.filename, file_content, file.content_type)}
|
761 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/speech_to_speech?language={language}"
|
762 |
|
src/server/utils/crypto.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from Crypto.Cipher import AES
|
2 |
+
from fastapi import HTTPException
|
3 |
+
from config.logging_config import logger
|
4 |
+
|
5 |
+
def decrypt_data(encrypted_data: bytes, key: bytes) -> bytes:
|
6 |
+
try:
|
7 |
+
nonce, ciphertext = encrypted_data[:12], encrypted_data[12:]
|
8 |
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
9 |
+
plaintext = cipher.decrypt_and_verify(ciphertext[:-16], ciphertext[-16:])
|
10 |
+
return plaintext
|
11 |
+
except Exception as e:
|
12 |
+
logger.error(f"Decryption failed: {str(e)}")
|
13 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted data")
|