Implement developer review recommendations: enhanced session management, improved error handling, and dependency updates
Browse files- api/chat.py +20 -15
- api/main.py +27 -4
- app.py +59 -56
- core/session.py +29 -12
- requirements.txt +3 -0
api/chat.py
CHANGED
|
@@ -3,8 +3,11 @@ from fastapi import APIRouter, HTTPException
|
|
| 3 |
from fastapi.responses import StreamingResponse
|
| 4 |
from core.llm import LLMClient
|
| 5 |
from core.memory import save_user_state, load_user_state
|
|
|
|
|
|
|
| 6 |
|
| 7 |
router = APIRouter()
|
|
|
|
| 8 |
|
| 9 |
llm_client = LLMClient(provider="ollama") # Default to Ollama
|
| 10 |
|
|
@@ -12,28 +15,30 @@ llm_client = LLMClient(provider="ollama") # Default to Ollama
|
|
| 12 |
async def chat(user_id: str, message: str):
|
| 13 |
if not message:
|
| 14 |
raise HTTPException(status_code=400, detail="Message is required")
|
| 15 |
-
|
| 16 |
-
# Load user state from Redis
|
| 17 |
-
user_state = load_user_state(user_id)
|
| 18 |
-
conversation_history = json.loads(user_state.get("conversation", "[]")) if user_state else []
|
| 19 |
-
|
| 20 |
-
# Add user message to history
|
| 21 |
-
conversation_history.append({"role": "user", "content": message})
|
| 22 |
-
|
| 23 |
-
# Generate AI response
|
| 24 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
full_response = ""
|
| 26 |
response_stream = llm_client.generate(prompt=message, stream=True)
|
| 27 |
-
|
| 28 |
# Collect streamed response
|
| 29 |
for chunk in response_stream:
|
| 30 |
full_response += chunk
|
| 31 |
-
|
| 32 |
-
# Save updated conversation
|
| 33 |
conversation_history.append({"role": "assistant", "content": full_response})
|
| 34 |
-
|
| 35 |
-
|
|
|
|
| 36 |
return {"response": full_response}
|
| 37 |
-
|
| 38 |
except Exception as e:
|
|
|
|
| 39 |
raise HTTPException(status_code=500, detail=f"LLM generation failed: {e}")
|
|
|
|
| 3 |
from fastapi.responses import StreamingResponse
|
| 4 |
from core.llm import LLMClient
|
| 5 |
from core.memory import save_user_state, load_user_state
|
| 6 |
+
from core.session import session_manager
|
| 7 |
+
import logging
|
| 8 |
|
| 9 |
router = APIRouter()
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
llm_client = LLMClient(provider="ollama") # Default to Ollama
|
| 13 |
|
|
|
|
| 15 |
async def chat(user_id: str, message: str):
|
| 16 |
if not message:
|
| 17 |
raise HTTPException(status_code=400, detail="Message is required")
|
| 18 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
try:
|
| 20 |
+
# Use session manager for better session handling
|
| 21 |
+
session = session_manager.get_session(user_id)
|
| 22 |
+
conversation_history = session.get("conversation", [])
|
| 23 |
+
|
| 24 |
+
# Add user message to history
|
| 25 |
+
conversation_history.append({"role": "user", "content": message})
|
| 26 |
+
|
| 27 |
+
# Generate AI response
|
| 28 |
full_response = ""
|
| 29 |
response_stream = llm_client.generate(prompt=message, stream=True)
|
| 30 |
+
|
| 31 |
# Collect streamed response
|
| 32 |
for chunk in response_stream:
|
| 33 |
full_response += chunk
|
| 34 |
+
|
| 35 |
+
# Save updated conversation using session manager
|
| 36 |
conversation_history.append({"role": "assistant", "content": full_response})
|
| 37 |
+
session_manager.update_session(user_id, {"conversation": conversation_history})
|
| 38 |
+
|
| 39 |
+
logger.info(f"Successfully processed chat for user {user_id}")
|
| 40 |
return {"response": full_response}
|
| 41 |
+
|
| 42 |
except Exception as e:
|
| 43 |
+
logger.error(f"LLM generation failed for user {user_id}: {e}")
|
| 44 |
raise HTTPException(status_code=500, detail=f"LLM generation failed: {e}")
|
api/main.py
CHANGED
|
@@ -2,8 +2,10 @@ from fastapi import FastAPI
|
|
| 2 |
from api.status import router as status_router
|
| 3 |
from api.chat import router as chat_router
|
| 4 |
from core.memory import check_redis_health
|
|
|
|
| 5 |
|
| 6 |
app = FastAPI()
|
|
|
|
| 7 |
|
| 8 |
# Mount routers
|
| 9 |
app.include_router(status_router, prefix="/api")
|
|
@@ -11,13 +13,34 @@ app.include_router(chat_router, prefix="/api")
|
|
| 11 |
|
| 12 |
@app.get("/")
|
| 13 |
async def root():
|
|
|
|
| 14 |
return {"message": "AI Life Coach API is running"}
|
| 15 |
|
| 16 |
@app.get("/health")
|
| 17 |
async def health_check():
|
| 18 |
-
"""
|
| 19 |
redis_healthy = check_redis_health()
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from api.status import router as status_router
|
| 3 |
from api.chat import router as chat_router
|
| 4 |
from core.memory import check_redis_health
|
| 5 |
+
import logging
|
| 6 |
|
| 7 |
app = FastAPI()
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
|
| 10 |
# Mount routers
|
| 11 |
app.include_router(status_router, prefix="/api")
|
|
|
|
| 13 |
|
| 14 |
@app.get("/")
|
| 15 |
async def root():
|
| 16 |
+
logger.info("API root endpoint accessed")
|
| 17 |
return {"message": "AI Life Coach API is running"}
|
| 18 |
|
| 19 |
@app.get("/health")
|
| 20 |
async def health_check():
|
| 21 |
+
"""Comprehensive health check endpoint"""
|
| 22 |
redis_healthy = check_redis_health()
|
| 23 |
+
|
| 24 |
+
# Additional health checks could be added here
|
| 25 |
+
overall_healthy = redis_healthy
|
| 26 |
+
|
| 27 |
+
health_status = {
|
| 28 |
+
"status": "healthy" if overall_healthy else "degraded",
|
| 29 |
+
"services": {
|
| 30 |
+
"redis": "healthy" if redis_healthy else "unhealthy"
|
| 31 |
+
},
|
| 32 |
+
"timestamp": __import__('time').time()
|
| 33 |
}
|
| 34 |
+
|
| 35 |
+
if overall_healthy:
|
| 36 |
+
logger.info("Health check passed")
|
| 37 |
+
else:
|
| 38 |
+
logger.warning("Health check degraded")
|
| 39 |
+
|
| 40 |
+
return health_status
|
| 41 |
+
|
| 42 |
+
# Add startup event for initialization logging
|
| 43 |
+
@app.on_event("startup")
|
| 44 |
+
async def startup_event():
|
| 45 |
+
logger.info("AI Life Coach API starting up...")
|
| 46 |
+
logger.info("Redis health: %s", "healthy" if check_redis_health() else "unhealthy")
|
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# Force redeploy trigger - version 2.
|
| 2 |
import streamlit as st
|
| 3 |
from utils.config import config
|
| 4 |
import requests
|
|
@@ -6,22 +6,29 @@ import json
|
|
| 6 |
import os
|
| 7 |
from core.memory import load_user_state, check_redis_health
|
| 8 |
|
| 9 |
-
# Set page config
|
| 10 |
st.set_page_config(page_title="AI Life Coach", page_icon="🧘", layout="centered")
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
st.session_state[key] = default_value
|
| 25 |
|
| 26 |
# Sidebar for user selection
|
| 27 |
st.sidebar.title("🧘 AI Life Coach")
|
|
@@ -32,11 +39,12 @@ st.sidebar.markdown("---")
|
|
| 32 |
st.sidebar.subheader("Ollama Connection")
|
| 33 |
ngrok_input = st.sidebar.text_input(
|
| 34 |
"Ngrok URL",
|
| 35 |
-
value=st.session_state.ngrok_url,
|
| 36 |
key="ngrok_url_input" # Explicit key
|
| 37 |
)
|
| 38 |
|
| 39 |
-
if st.sidebar.button("Update Ngrok URL", key="update_ngrok_button"):
|
|
|
|
| 40 |
st.session_state.ngrok_url = ngrok_input
|
| 41 |
st.session_state.model_status = "checking"
|
| 42 |
st.session_state.available_models = []
|
|
@@ -54,8 +62,8 @@ NGROK_HEADERS = {
|
|
| 54 |
def fetch_available_models(ngrok_url):
|
| 55 |
try:
|
| 56 |
response = requests.get(
|
| 57 |
-
f"{ngrok_url}/api/tags",
|
| 58 |
-
headers=NGROK_HEADERS,
|
| 59 |
timeout=5
|
| 60 |
)
|
| 61 |
if response.status_code == 200:
|
|
@@ -65,15 +73,14 @@ def fetch_available_models(ngrok_url):
|
|
| 65 |
pass
|
| 66 |
return []
|
| 67 |
|
| 68 |
-
# Update available models
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
st.session_state.selected_model_index = 0
|
| 77 |
|
| 78 |
# Model selector dropdown - REPLACED ENTIRE SECTION
|
| 79 |
st.sidebar.markdown("---")
|
|
@@ -108,7 +115,7 @@ if st.session_state.available_models:
|
|
| 108 |
else:
|
| 109 |
st.sidebar.warning("No models available - check Ollama connection")
|
| 110 |
model_input = st.sidebar.text_input(
|
| 111 |
-
"Or enter model name",
|
| 112 |
value=st.session_state.selected_model,
|
| 113 |
key="manual_model_input" # Explicit key
|
| 114 |
)
|
|
@@ -132,7 +139,6 @@ def get_ollama_status(ngrok_url):
|
|
| 132 |
models = response.json().get("models", [])
|
| 133 |
model_names = [m.get("name") for m in models]
|
| 134 |
st.session_state.available_models = model_names
|
| 135 |
-
|
| 136 |
if models:
|
| 137 |
selected_model_available = st.session_state.selected_model in model_names
|
| 138 |
return {
|
|
@@ -236,11 +242,9 @@ use_fallback = not ollama_status.get("running", False) or config.use_fallback
|
|
| 236 |
# Display Ollama status - Enhanced section with Hugging Face scaling behavior info
|
| 237 |
if use_fallback:
|
| 238 |
st.sidebar.warning("🌐 Using Hugging Face fallback (Ollama not available)")
|
| 239 |
-
|
| 240 |
# Add special note for Hugging Face scaling behavior
|
| 241 |
if config.hf_api_url and "endpoints.huggingface.cloud" in config.hf_api_url:
|
| 242 |
st.sidebar.info("ℹ️ HF Endpoint may be initializing (up to 4 min)")
|
| 243 |
-
|
| 244 |
if "error" in ollama_status:
|
| 245 |
st.sidebar.caption(f"Error: {ollama_status['error'][:50]}...")
|
| 246 |
else:
|
|
@@ -272,18 +276,17 @@ else:
|
|
| 272 |
st.title("🧘 AI Life Coach")
|
| 273 |
st.markdown("Talk to your personal development assistant.")
|
| 274 |
|
| 275 |
-
# Show detailed status
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
st.write("- Redis Health:", check_redis_health())
|
| 287 |
|
| 288 |
# Function to send message to Ollama
|
| 289 |
def send_to_ollama(user_input, conversation_history, ngrok_url, model_name):
|
|
@@ -299,9 +302,9 @@ def send_to_ollama(user_input, conversation_history, ngrok_url, model_name):
|
|
| 299 |
}
|
| 300 |
}
|
| 301 |
response = requests.post(
|
| 302 |
-
f"{ngrok_url}/api/chat",
|
| 303 |
-
json=payload,
|
| 304 |
-
headers=NGROK_HEADERS,
|
| 305 |
timeout=60
|
| 306 |
)
|
| 307 |
if response.status_code == 200:
|
|
@@ -339,7 +342,7 @@ conversation = get_conversation_history(user)
|
|
| 339 |
for msg in conversation:
|
| 340 |
role = msg["role"].capitalize()
|
| 341 |
content = msg["content"]
|
| 342 |
-
st.markdown(f"
|
| 343 |
|
| 344 |
# Chat input - REPLACED SECTION
|
| 345 |
user_input = st.text_input(
|
|
@@ -357,7 +360,7 @@ if send_button:
|
|
| 357 |
st.warning("Please enter a message.")
|
| 358 |
else:
|
| 359 |
# Display user message
|
| 360 |
-
st.markdown(f"
|
| 361 |
|
| 362 |
# Prepare conversation history
|
| 363 |
conversation_history = [{"role": msg["role"], "content": msg["content"]} for msg in conversation[-5:]]
|
|
@@ -370,17 +373,17 @@ if send_button:
|
|
| 370 |
backend_used = "Hugging Face"
|
| 371 |
else:
|
| 372 |
ai_response = send_to_ollama(
|
| 373 |
-
user_input,
|
| 374 |
-
conversation_history,
|
| 375 |
-
st.session_state.ngrok_url,
|
| 376 |
st.session_state.selected_model
|
| 377 |
)
|
| 378 |
backend_used = "Ollama"
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
|
| 385 |
# Clear input after sending (this helps prevent duplicate sends)
|
| 386 |
st.session_state.user_message_input = ""
|
|
|
|
| 1 |
+
# Force redeploy trigger - version 2.3
|
| 2 |
import streamlit as st
|
| 3 |
from utils.config import config
|
| 4 |
import requests
|
|
|
|
| 6 |
import os
|
| 7 |
from core.memory import load_user_state, check_redis_health
|
| 8 |
|
| 9 |
+
# Set page config FIRST (before any other Streamlit commands)
|
| 10 |
st.set_page_config(page_title="AI Life Coach", page_icon="🧘", layout="centered")
|
| 11 |
|
| 12 |
+
# ROBUST SESSION STATE INITIALIZATION
|
| 13 |
+
# This must happen before ANY widget creation
|
| 14 |
+
def init_session_state():
|
| 15 |
+
"""Initialize all session state variables with proper defaults"""
|
| 16 |
+
defaults = {
|
| 17 |
+
'ngrok_url': config.ollama_host,
|
| 18 |
+
'model_status': "checking",
|
| 19 |
+
'available_models': [],
|
| 20 |
+
'selected_model': config.local_model_name,
|
| 21 |
+
'selected_model_index': 0,
|
| 22 |
+
'user_message_input': "",
|
| 23 |
+
'user_selector': "Rob" # Add missing default
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
for key, default_value in defaults.items():
|
| 27 |
+
if key not in st.session_state:
|
| 28 |
+
st.session_state[key] = default_value
|
| 29 |
|
| 30 |
+
# CALL THIS FIRST
|
| 31 |
+
init_session_state()
|
|
|
|
| 32 |
|
| 33 |
# Sidebar for user selection
|
| 34 |
st.sidebar.title("🧘 AI Life Coach")
|
|
|
|
| 39 |
st.sidebar.subheader("Ollama Connection")
|
| 40 |
ngrok_input = st.sidebar.text_input(
|
| 41 |
"Ngrok URL",
|
| 42 |
+
value=st.session_state.ngrok_url,
|
| 43 |
key="ngrok_url_input" # Explicit key
|
| 44 |
)
|
| 45 |
|
| 46 |
+
if st.sidebar.button("Update Ngrok URL", key="update_ngrok_button"):
|
| 47 |
+
# Explicit key
|
| 48 |
st.session_state.ngrok_url = ngrok_input
|
| 49 |
st.session_state.model_status = "checking"
|
| 50 |
st.session_state.available_models = []
|
|
|
|
| 62 |
def fetch_available_models(ngrok_url):
|
| 63 |
try:
|
| 64 |
response = requests.get(
|
| 65 |
+
f"{ngrok_url}/api/tags",
|
| 66 |
+
headers=NGROK_HEADERS,
|
| 67 |
timeout=5
|
| 68 |
)
|
| 69 |
if response.status_code == 200:
|
|
|
|
| 73 |
pass
|
| 74 |
return []
|
| 75 |
|
| 76 |
+
# Update available models if st.session_state.ngrok_url and st.session_state.model_status != "unreachable":
|
| 77 |
+
model_names = fetch_available_models(st.session_state.ngrok_url)
|
| 78 |
+
if model_names:
|
| 79 |
+
st.session_state.available_models = model_names
|
| 80 |
+
# If current selected model not in list, select the first one
|
| 81 |
+
if st.session_state.selected_model not in model_names:
|
| 82 |
+
st.session_state.selected_model = model_names[0]
|
| 83 |
+
st.session_state.selected_model_index = 0
|
|
|
|
| 84 |
|
| 85 |
# Model selector dropdown - REPLACED ENTIRE SECTION
|
| 86 |
st.sidebar.markdown("---")
|
|
|
|
| 115 |
else:
|
| 116 |
st.sidebar.warning("No models available - check Ollama connection")
|
| 117 |
model_input = st.sidebar.text_input(
|
| 118 |
+
"Or enter model name",
|
| 119 |
value=st.session_state.selected_model,
|
| 120 |
key="manual_model_input" # Explicit key
|
| 121 |
)
|
|
|
|
| 139 |
models = response.json().get("models", [])
|
| 140 |
model_names = [m.get("name") for m in models]
|
| 141 |
st.session_state.available_models = model_names
|
|
|
|
| 142 |
if models:
|
| 143 |
selected_model_available = st.session_state.selected_model in model_names
|
| 144 |
return {
|
|
|
|
| 242 |
# Display Ollama status - Enhanced section with Hugging Face scaling behavior info
|
| 243 |
if use_fallback:
|
| 244 |
st.sidebar.warning("🌐 Using Hugging Face fallback (Ollama not available)")
|
|
|
|
| 245 |
# Add special note for Hugging Face scaling behavior
|
| 246 |
if config.hf_api_url and "endpoints.huggingface.cloud" in config.hf_api_url:
|
| 247 |
st.sidebar.info("ℹ️ HF Endpoint may be initializing (up to 4 min)")
|
|
|
|
| 248 |
if "error" in ollama_status:
|
| 249 |
st.sidebar.caption(f"Error: {ollama_status['error'][:50]}...")
|
| 250 |
else:
|
|
|
|
| 276 |
st.title("🧘 AI Life Coach")
|
| 277 |
st.markdown("Talk to your personal development assistant.")
|
| 278 |
|
| 279 |
+
# Show detailed status with st.expander("🔍 Connection Status"):
|
| 280 |
+
st.write("Ollama Status:", ollama_status)
|
| 281 |
+
st.write("Model Status:", st.session_state.model_status)
|
| 282 |
+
st.write("Selected Model:", st.session_state.selected_model)
|
| 283 |
+
st.write("Available Models:", st.session_state.available_models)
|
| 284 |
+
st.write("Environment Info:")
|
| 285 |
+
st.write("- Is HF Space:", IS_HF_SPACE)
|
| 286 |
+
st.write("- Base URL:", BASE_URL or "Not in HF Space")
|
| 287 |
+
st.write("- Current Ngrok URL:", st.session_state.ngrok_url)
|
| 288 |
+
st.write("- Using Fallback:", use_fallback)
|
| 289 |
+
st.write("- Redis Health:", check_redis_health())
|
|
|
|
| 290 |
|
| 291 |
# Function to send message to Ollama
|
| 292 |
def send_to_ollama(user_input, conversation_history, ngrok_url, model_name):
|
|
|
|
| 302 |
}
|
| 303 |
}
|
| 304 |
response = requests.post(
|
| 305 |
+
f"{ngrok_url}/api/chat",
|
| 306 |
+
json=payload,
|
| 307 |
+
headers=NGROK_HEADERS,
|
| 308 |
timeout=60
|
| 309 |
)
|
| 310 |
if response.status_code == 200:
|
|
|
|
| 342 |
for msg in conversation:
|
| 343 |
role = msg["role"].capitalize()
|
| 344 |
content = msg["content"]
|
| 345 |
+
st.markdown(f"{role}: {content}")
|
| 346 |
|
| 347 |
# Chat input - REPLACED SECTION
|
| 348 |
user_input = st.text_input(
|
|
|
|
| 360 |
st.warning("Please enter a message.")
|
| 361 |
else:
|
| 362 |
# Display user message
|
| 363 |
+
st.markdown(f"You: {user_input}")
|
| 364 |
|
| 365 |
# Prepare conversation history
|
| 366 |
conversation_history = [{"role": msg["role"], "content": msg["content"]} for msg in conversation[-5:]]
|
|
|
|
| 373 |
backend_used = "Hugging Face"
|
| 374 |
else:
|
| 375 |
ai_response = send_to_ollama(
|
| 376 |
+
user_input,
|
| 377 |
+
conversation_history,
|
| 378 |
+
st.session_state.ngrok_url,
|
| 379 |
st.session_state.selected_model
|
| 380 |
)
|
| 381 |
backend_used = "Ollama"
|
| 382 |
+
|
| 383 |
+
if ai_response:
|
| 384 |
+
st.markdown(f"AI Coach ({backend_used}): {ai_response}")
|
| 385 |
+
else:
|
| 386 |
+
st.error(f"Failed to get response from {backend_used}.")
|
| 387 |
|
| 388 |
# Clear input after sending (this helps prevent duplicate sends)
|
| 389 |
st.session_state.user_message_input = ""
|
core/session.py
CHANGED
|
@@ -2,6 +2,11 @@ import json
|
|
| 2 |
import time
|
| 3 |
from typing import Dict, Any, Optional
|
| 4 |
from core.memory import load_user_state, save_user_state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class SessionManager:
|
| 7 |
"""Manages user sessions and conversation context"""
|
|
@@ -14,7 +19,7 @@ class SessionManager:
|
|
| 14 |
session_timeout: Session timeout in seconds (default: 1 hour)
|
| 15 |
"""
|
| 16 |
self.session_timeout = session_timeout
|
| 17 |
-
|
| 18 |
def get_session(self, user_id: str) -> Dict[str, Any]:
|
| 19 |
"""
|
| 20 |
Retrieve user session data
|
|
@@ -28,18 +33,20 @@ class SessionManager:
|
|
| 28 |
try:
|
| 29 |
state = load_user_state(user_id)
|
| 30 |
if not state:
|
|
|
|
| 31 |
return self._create_new_session()
|
| 32 |
|
| 33 |
# Check if session has expired
|
| 34 |
-
last_activity = state.get('last_activity', 0)
|
| 35 |
if time.time() - last_activity > self.session_timeout:
|
|
|
|
| 36 |
return self._create_new_session()
|
| 37 |
-
|
| 38 |
return state
|
| 39 |
except Exception as e:
|
| 40 |
-
|
| 41 |
return self._create_new_session()
|
| 42 |
-
|
| 43 |
def update_session(self, user_id: str, data: Dict[str, Any]) -> bool:
|
| 44 |
"""
|
| 45 |
Update user session data
|
|
@@ -60,11 +67,16 @@ class SessionManager:
|
|
| 60 |
session['last_activity'] = time.time()
|
| 61 |
|
| 62 |
# Save updated session
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
except Exception as e:
|
| 65 |
-
|
| 66 |
return False
|
| 67 |
-
|
| 68 |
def clear_session(self, user_id: str) -> bool:
|
| 69 |
"""
|
| 70 |
Clear user session data
|
|
@@ -76,11 +88,14 @@ class SessionManager:
|
|
| 76 |
Boolean indicating success
|
| 77 |
"""
|
| 78 |
try:
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
| 80 |
except Exception as e:
|
| 81 |
-
|
| 82 |
return False
|
| 83 |
-
|
| 84 |
def _create_new_session(self) -> Dict[str, Any]:
|
| 85 |
"""
|
| 86 |
Create a new session with default values
|
|
@@ -88,12 +103,14 @@ class SessionManager:
|
|
| 88 |
Returns:
|
| 89 |
Dictionary containing new session data
|
| 90 |
"""
|
| 91 |
-
|
| 92 |
'conversation': [],
|
| 93 |
'preferences': {},
|
| 94 |
'last_activity': time.time(),
|
| 95 |
'created_at': time.time()
|
| 96 |
}
|
|
|
|
|
|
|
| 97 |
|
| 98 |
# Global session manager instance
|
| 99 |
session_manager = SessionManager()
|
|
|
|
| 2 |
import time
|
| 3 |
from typing import Dict, Any, Optional
|
| 4 |
from core.memory import load_user_state, save_user_state
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
# Set up logging
|
| 8 |
+
logging.basicConfig(level=logging.INFO)
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class SessionManager:
|
| 12 |
"""Manages user sessions and conversation context"""
|
|
|
|
| 19 |
session_timeout: Session timeout in seconds (default: 1 hour)
|
| 20 |
"""
|
| 21 |
self.session_timeout = session_timeout
|
| 22 |
+
|
| 23 |
def get_session(self, user_id: str) -> Dict[str, Any]:
|
| 24 |
"""
|
| 25 |
Retrieve user session data
|
|
|
|
| 33 |
try:
|
| 34 |
state = load_user_state(user_id)
|
| 35 |
if not state:
|
| 36 |
+
logger.info(f"Creating new session for user {user_id}")
|
| 37 |
return self._create_new_session()
|
| 38 |
|
| 39 |
# Check if session has expired
|
| 40 |
+
last_activity = float(state.get('last_activity', 0))
|
| 41 |
if time.time() - last_activity > self.session_timeout:
|
| 42 |
+
logger.info(f"Session expired for user {user_id}, creating new session")
|
| 43 |
return self._create_new_session()
|
| 44 |
+
|
| 45 |
return state
|
| 46 |
except Exception as e:
|
| 47 |
+
logger.error(f"Error retrieving session for user {user_id}: {e}")
|
| 48 |
return self._create_new_session()
|
| 49 |
+
|
| 50 |
def update_session(self, user_id: str, data: Dict[str, Any]) -> bool:
|
| 51 |
"""
|
| 52 |
Update user session data
|
|
|
|
| 67 |
session['last_activity'] = time.time()
|
| 68 |
|
| 69 |
# Save updated session
|
| 70 |
+
result = save_user_state(user_id, session)
|
| 71 |
+
if result:
|
| 72 |
+
logger.debug(f"Successfully updated session for user {user_id}")
|
| 73 |
+
else:
|
| 74 |
+
logger.warning(f"Failed to save session for user {user_id}")
|
| 75 |
+
return result
|
| 76 |
except Exception as e:
|
| 77 |
+
logger.error(f"Error updating session for user {user_id}: {e}")
|
| 78 |
return False
|
| 79 |
+
|
| 80 |
def clear_session(self, user_id: str) -> bool:
|
| 81 |
"""
|
| 82 |
Clear user session data
|
|
|
|
| 88 |
Boolean indicating success
|
| 89 |
"""
|
| 90 |
try:
|
| 91 |
+
result = save_user_state(user_id, {})
|
| 92 |
+
if result:
|
| 93 |
+
logger.info(f"Cleared session for user {user_id}")
|
| 94 |
+
return result
|
| 95 |
except Exception as e:
|
| 96 |
+
logger.error(f"Error clearing session for user {user_id}: {e}")
|
| 97 |
return False
|
| 98 |
+
|
| 99 |
def _create_new_session(self) -> Dict[str, Any]:
|
| 100 |
"""
|
| 101 |
Create a new session with default values
|
|
|
|
| 103 |
Returns:
|
| 104 |
Dictionary containing new session data
|
| 105 |
"""
|
| 106 |
+
session = {
|
| 107 |
'conversation': [],
|
| 108 |
'preferences': {},
|
| 109 |
'last_activity': time.time(),
|
| 110 |
'created_at': time.time()
|
| 111 |
}
|
| 112 |
+
logger.debug("Created new session")
|
| 113 |
+
return session
|
| 114 |
|
| 115 |
# Global session manager instance
|
| 116 |
session_manager = SessionManager()
|
requirements.txt
CHANGED
|
@@ -8,3 +8,6 @@ tavily-python>=0.1.0,<1.0.0
|
|
| 8 |
requests==2.31.0
|
| 9 |
docker==6.1.3
|
| 10 |
pygame==2.5.2
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
requests==2.31.0
|
| 9 |
docker==6.1.3
|
| 10 |
pygame==2.5.2
|
| 11 |
+
# Add these for enhanced functionality
|
| 12 |
+
pydantic==1.10.7
|
| 13 |
+
typing-extensions>=4.5.0
|