handle multi workers

parent 6dc3cbe7
import redis
import json
import base64
import time
from typing import Optional, Dict
class SessionResponseManager:
"""
Manages response state in Redis, keyed by a session ID via cookies.
Implements the same interface as the old in-memory ResponseManager.
"""
def __init__(self, redis_client):
if redis_client is None:
raise ConnectionError("SessionResponseManager requires a valid Redis client.")
self.redis = redis_client
# Redis TTL serves the same purpose as checking timestamps
self.default_ttl = 600 # 10 minutes
def _get_key(self, session_id: str) -> str:
"""Helper to generate the Redis key."""
return f"session_response:{session_id}"
def store_response(self, session_id: str, text: str, audio_filename: Optional[str] = None, audio_bytes: Optional[bytes] = None) -> None:
"""Store response in Redis with TTL."""
key = self._get_key(session_id)
# Base64 encode binary data for JSON storage
encoded_audio = base64.b64encode(audio_bytes).decode('utf-8') if audio_bytes else None
payload = {
"text": text,
"audio_filename": audio_filename,
"audio_bytes_b64": encoded_audio,
# We store the timestamp to perfectly match the old object structure,
# even though Redis handles expiration automatically.
"timestamp": time.time()
}
value = json.dumps(payload)
# setex sets the value and the expiration (TTL) atomically
self.redis.setex(key, self.default_ttl, value)
def get_response(self, session_id: str) -> Dict:
"""
Atomically retrieves and deletes ('pops') the response from Redis.
Returns the dictionary structure expected by the service layer.
"""
key = self._get_key(session_id)
# Use a pipeline to get and delete atomically
pipe = self.redis.pipeline()
pipe.get(key)
pipe.delete(key) # Ensure it's only read once
results = pipe.execute()
json_value = results[0]
# Default empty structure if nothing found (matches old manager behavior)
empty_response = {"text": None, "audio_filename": None, "audio_bytes": None, "timestamp": 0}
if not json_value:
return empty_response
try:
payload = json.loads(json_value)
# Decode Base64 audio back to raw bytes
if payload.get("audio_bytes_b64"):
payload["audio_bytes"] = base64.b64decode(payload["audio_bytes_b64"])
else:
payload["audio_bytes"] = None
# Remove internal base64 key before returning
payload.pop("audio_bytes_b64", None)
return payload
except (TypeError, json.JSONDecodeError):
return empty_response
def clear_response(self, session_id: str) -> None:
"""Manually deletes the response key from Redis."""
key = self._get_key(session_id)
self.redis.delete(key)
def is_response_fresh(self, session_id: str, max_age_seconds: int = 300) -> bool:
"""
Checks if data exists in Redis.
Note: max_age_seconds is ignored because Redis handles TTL automatically,
but kept in signature for compatibility with the old interface.
"""
key = self._get_key(session_id)
# redis.exists returns > 0 if key exists
return self.redis.exists(key) > 0
\ No newline at end of file
......@@ -267,9 +267,12 @@
}
}
async fetchAudioResponse() {
async fetchAudioResponse(studentId) {
try {
const response = await fetch(Config.AUDIO_RESPONSE_URL, {
// Build URL with student_id
const urlWithParam = `${Config.AUDIO_RESPONSE_URL}?student_id=${encodeURIComponent(studentId)}`;
const response = await fetch(urlWithParam, {
method: 'GET',
mode: 'cors',
credentials: 'omit'
......@@ -537,7 +540,7 @@
const response = await this.apiClient.sendFormData(Config.BACKEND_URL, formData);
if (response.status === 'success') {
await this.getAgentResponse();
await this.getAgentResponse(studentId);
return true;
} else {
throw new Error(response.message || 'Unknown error');
......@@ -562,7 +565,7 @@
const response = await this.apiClient.sendFormData(Config.BACKEND_URL, formData);
if (response.status === 'success') {
await this.getAgentResponse();
await this.getAgentResponse(studentId);
return true;
} else {
throw new Error(response.message || 'Unknown error');
......@@ -573,11 +576,11 @@
}
}
async getAgentResponse() {
async getAgentResponse(studentId) {
try {
this.uiManager.showStatus('جاري جلب رد المساعد...', StatusType.PROCESSING);
const { agentText, audioBlob } = await this.apiClient.fetchAudioResponse();
const { agentText, audioBlob } = await this.apiClient.fetchAudioResponse(studentId);
if (!agentText || agentText === "لا يوجد رد متاح") {
throw new Error('لم يتم استلام رد صالح من المساعد');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment