EasyBot / server.py
NitinBot001's picture
Update server.py
2b6e96f verified
# server.py
from flask import Flask, request, jsonify, render_template
from app import EasyFarmsAssistant
from conversation_manager import ConversationManager
import logging
from datetime import datetime
import os
from flask_cors import CORS
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize the Flask application
app = Flask(__name__)
CORS(app)
# --- Initialize Core Services ---
try:
conv_manager = ConversationManager()
assistant = EasyFarmsAssistant(manager=conv_manager)
logger.info("EasyFarmsAssistant and ConversationManager initialized successfully.")
except Exception as e:
logger.error(f"FATAL: Could not initialize services. Error: {e}")
assistant = None
conv_manager = None
# --- Frontend Serving Route ---
@app.route('/')
def index():
"""Serves the main chat application page (index.html)."""
return render_template('index.html')
# --- API Endpoints ---
@app.route('/config', methods=['GET'])
def get_config():
"""Provides public configuration keys to the frontend."""
return jsonify({
'imgbb_api_key': os.getenv('IMGBB_API_KEY')
})
@app.route('/chat', methods=['POST'])
def chat():
"""
Handles incoming user messages and returns the assistant's response.
Request format:
{
"query": "user message text",
"image_url": "optional image URL",
"session_id": "optional chat ID for conversation memory",
"user_id": "user identifier for authentication"
}
Response format:
{
"response": "assistant response",
"chat_id": "unique chat identifier",
"is_new_chat": true/false,
"user_message_id": 1,
"assistant_message_id": 2,
"total_messages": 2
}
"""
if not assistant:
return jsonify({"error": "Assistant is not available due to an initialization error."}), 503
try:
# Handle both JSON and form data
if request.is_json:
data = request.get_json()
user_message = data.get('query')
chat_id = data.get('session_id')
image_url = data.get('image_url')
user_id = data.get('user_id')
else:
# Fallback for form data (backward compatibility)
data = request.form
user_message = data.get('message') or data.get('query')
chat_id = data.get('session_id')
image_url = data.get('image_url')
user_id = data.get('user_id')
# Validate input
if not user_message and not image_url:
return jsonify({"error": "Cannot process an empty message. Provide either 'query' text or 'image_url'."}), 400
if not user_id:
return jsonify({"error": "User ID is required for authentication."}), 400
# Clean up empty/null chat_id
if chat_id in [None, '', 'null', 'undefined']:
chat_id = None
# Process the query with the assistant
result = assistant.process_query(
user_message=user_message or "",
user_id=user_id,
chat_id=chat_id,
image_url=image_url
)
# Handle errors from assistant
if "error" in result:
return jsonify({
"error": result["error"],
"chat_id": result.get("chat_id"),
"is_new_chat": result.get("is_new_chat", True)
}), 500
# Return successful response
return jsonify({
"response": result["response"],
"chat_id": result["chat_id"],
"is_new_chat": result["is_new_chat"],
"user_message_id": result["user_message_id"],
"assistant_message_id": result["assistant_message_id"],
"total_messages": result["total_messages"]
})
except Exception as e:
logger.error(f"Error in chat endpoint: {e}")
return jsonify({"error": f"Server error: {str(e)}"}), 500
@app.route('/chats', methods=['GET'])
def get_all_chats():
"""
Get all chat sessions with basic information for a specific user.
Query parameters:
- user_id: Required user identifier
Response format:
[
{
"session_id": "chat_abc123",
"title": "Crop recommendations for...",
"message_count": 4,
"created_at": "2024-01-01T10:00:00",
"updated_at": "2024-01-01T10:05:00"
}
]
"""
if not conv_manager:
return jsonify({"error": "Conversation manager not available"}), 503
try:
user_id = request.args.get('user_id')
if not user_id:
return jsonify({"error": "User ID is required"}), 400
chats = assistant.get_all_chats(user_id)
return jsonify(chats)
except Exception as e:
logger.error(f"Error fetching all chats: {e}")
return jsonify({"error": "Could not fetch chat list"}), 500
@app.route('/chat/<chat_id>/info', methods=['GET'])
def get_chat_info(chat_id):
"""
Get information about a specific chat for a specific user.
Query parameters:
- user_id: Required user identifier
Response format:
{
"session_id": "chat_abc123",
"message_count": 4,
"created_at": "2024-01-01T10:00:00",
"updated_at": "2024-01-01T10:05:00",
"exists": true
}
"""
if not assistant:
return jsonify({"error": "Assistant not available"}), 503
try:
if not chat_id:
return jsonify({"error": "Chat ID is required"}), 400
user_id = request.args.get('user_id')
if not user_id:
return jsonify({"error": "User ID is required"}), 400
info = assistant.get_chat_info(chat_id, user_id)
return jsonify(info)
except Exception as e:
logger.error(f"Error getting chat info for {chat_id}: {e}")
return jsonify({"error": "Could not fetch chat information"}), 500
@app.route('/chat/<chat_id>/messages', methods=['GET'])
def get_chat_messages(chat_id):
"""
Get all messages for a specific chat and user.
Query parameters:
- user_id: Required user identifier
Response format:
[
{
"message_id": 1,
"role": "user",
"content": "Hello, I need help with farming",
"timestamp": "2024-01-01T10:00:00",
"imageUrl": "optional"
},
{
"message_id": 2,
"role": "assistant",
"content": "Hello! I'd be happy to help...",
"timestamp": "2024-01-01T10:00:05"
}
]
"""
if not assistant:
return jsonify({"error": "Assistant not available"}), 503
try:
if not chat_id:
return jsonify({"error": "Chat ID is required"}), 400
user_id = request.args.get('user_id')
if not user_id:
return jsonify({"error": "User ID is required"}), 400
messages = assistant.get_messages(chat_id, user_id)
return jsonify(messages)
except Exception as e:
logger.error(f"Error fetching messages for chat {chat_id}: {e}")
return jsonify({"error": "Could not fetch chat messages"}), 500
@app.route('/chat/<chat_id>', methods=['DELETE'])
def delete_chat(chat_id):
"""
Delete a specific chat and all its messages for a specific user.
Query parameters:
- user_id: Required user identifier
Response format:
{
"status": "success",
"message": "Chat deleted successfully",
"chat_id": "chat_abc123"
}
"""
if not assistant:
return jsonify({"error": "Assistant not available"}), 503
try:
if not chat_id:
return jsonify({"error": "Chat ID is required"}), 400
user_id = request.args.get('user_id')
if not user_id:
return jsonify({"error": "User ID is required"}), 400
success = assistant.clear_history(chat_id, user_id)
if success:
return jsonify({
"status": "success",
"message": f"Chat {chat_id} deleted successfully",
"chat_id": chat_id
})
else:
return jsonify({
"status": "error",
"message": f"Failed to delete chat {chat_id}",
"chat_id": chat_id
}), 500
except Exception as e:
logger.error(f"Error deleting chat {chat_id}: {e}")
return jsonify({"error": "Could not delete chat"}), 500
# --- New User Management Endpoints ---
@app.route('/users/<user_id>/stats', methods=['GET'])
def get_user_stats(user_id):
"""
Get statistics for a specific user.
Response format:
{
"user_id": "user_abc123",
"total_sessions": 5,
"total_messages": 42,
"recent_sessions_24h": 2,
"average_messages_per_session": 8.4,
"first_session": "2024-01-01T10:00:00",
"last_activity": "2024-01-01T15:30:00"
}
"""
if not conv_manager:
return jsonify({"error": "Conversation manager not available"}), 503
try:
if not user_id:
return jsonify({"error": "User ID is required"}), 400
stats = conv_manager.get_user_stats(user_id)
return jsonify(stats)
except Exception as e:
logger.error(f"Error getting user stats for {user_id}: {e}")
return jsonify({"error": "Could not fetch user statistics"}), 500
@app.route('/users/<user_id>/chats', methods=['DELETE'])
def delete_all_user_chats(user_id):
"""
Delete all chats for a specific user.
Response format:
{
"status": "success",
"message": "All chats deleted successfully",
"user_id": "user_abc123"
}
"""
if not conv_manager:
return jsonify({"error": "Conversation manager not available"}), 503
try:
if not user_id:
return jsonify({"error": "User ID is required"}), 400
success = conv_manager.delete_all_user_data(user_id)
if success:
return jsonify({
"status": "success",
"message": f"All chats deleted for user {user_id}",
"user_id": user_id
})
else:
return jsonify({
"status": "error",
"message": f"Failed to delete chats for user {user_id}",
"user_id": user_id
}), 500
except Exception as e:
logger.error(f"Error deleting all chats for user {user_id}: {e}")
return jsonify({"error": "Could not delete user chats"}), 500
@app.route('/users/<user_id>/export', methods=['GET'])
def export_user_data(user_id):
"""
Export all data for a specific user.
Response format:
{
"user_id": "user_abc123",
"export_date": "2024-01-01T10:00:00",
"stats": {...},
"sessions": [...]
}
"""
if not conv_manager:
return jsonify({"error": "Conversation manager not available"}), 503
try:
if not user_id:
return jsonify({"error": "User ID is required"}), 400
# Get user stats and sessions
stats = conv_manager.get_user_stats(user_id)
sessions = conv_manager.get_all_chat_sessions(user_id)
# Get all messages for each session
detailed_sessions = []
for session in sessions:
session_data = session.copy()
messages = conv_manager.get_history(session['session_id'], user_id)
session_data['messages'] = messages
detailed_sessions.append(session_data)
export_data = {
"user_id": user_id,
"export_date": datetime.utcnow().isoformat(),
"stats": stats,
"sessions": detailed_sessions,
"total_exported_sessions": len(detailed_sessions),
"total_exported_messages": sum(len(s.get('messages', [])) for s in detailed_sessions)
}
return jsonify(export_data)
except Exception as e:
logger.error(f"Error exporting data for user {user_id}: {e}")
return jsonify({"error": "Could not export user data"}), 500
@app.route('/users/<user_id>', methods=['DELETE'])
def delete_user_account(user_id):
"""
Delete a user account and all associated data.
Response format:
{
"status": "success",
"message": "User account deleted successfully",
"user_id": "user_abc123"
}
"""
if not conv_manager:
return jsonify({"error": "Conversation manager not available"}), 503
try:
if not user_id:
return jsonify({"error": "User ID is required"}), 400
# Delete all user data
success = conv_manager.delete_all_user_data(user_id)
if success:
return jsonify({
"status": "success",
"message": f"User account {user_id} deleted successfully",
"user_id": user_id
})
else:
return jsonify({
"status": "error",
"message": f"Failed to delete user account {user_id}",
"user_id": user_id
}), 500
except Exception as e:
logger.error(f"Error deleting user account {user_id}: {e}")
return jsonify({"error": "Could not delete user account"}), 500
# --- Legacy Endpoints for Backward Compatibility ---
@app.route('/history/sessions', methods=['GET'])
def get_sessions():
"""Legacy endpoint - redirects to /chats"""
return get_all_chats()
@app.route('/history/messages/<session_id>', methods=['GET'])
def get_messages(session_id):
"""Legacy endpoint - redirects to /chat/<session_id>/messages"""
return get_chat_messages(session_id)
@app.route('/clear', methods=['POST'])
def clear_history():
"""Legacy endpoint for clearing chat history"""
if not assistant:
return jsonify({"error": "Assistant is not available."}), 503
try:
data = request.get_json()
if not data:
return jsonify({"error": "Request body is required"}), 400
session_id = data.get('session_id')
if not session_id:
return jsonify({"error": "Missing 'session_id' in request body"}), 400
success = assistant.clear_history(session_id)
if success:
return jsonify({
"status": "success",
"message": f"History for session {session_id} was cleared."
})
else:
return jsonify({"error": "Failed to clear history."}), 500
except Exception as e:
logger.error(f"Error in legacy clear endpoint: {e}")
return jsonify({"error": f"Server error: {str(e)}"}), 500
# --- Health Check and Status Endpoints ---
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint for monitoring"""
try:
# Check if core services are available
services_status = {
"assistant": assistant is not None,
"conversation_manager": conv_manager is not None,
"database": False
}
# Test database connectivity
if conv_manager:
try:
# Try to get chat sessions (will test DB connection)
conv_manager.get_all_chat_sessions()
services_status["database"] = True
except:
services_status["database"] = False
all_healthy = all(services_status.values())
return jsonify({
"status": "healthy" if all_healthy else "degraded",
"services": services_status,
"timestamp": conv_manager.manager._get_timestamp() if conv_manager else None
}), 200 if all_healthy else 503
except Exception as e:
logger.error(f"Health check failed: {e}")
return jsonify({
"status": "unhealthy",
"error": str(e)
}), 503
@app.route('/stats', methods=['GET'])
def get_stats():
"""Get system statistics"""
if not conv_manager:
return jsonify({"error": "Conversation manager not available"}), 503
try:
chats = assistant.get_all_chats()
total_chats = len(chats)
total_messages = sum(chat.get('message_count', 0) for chat in chats)
# Recent activity (chats updated in last 24 hours)
from datetime import datetime, timedelta
now = datetime.utcnow()
yesterday = now - timedelta(days=1)
recent_chats = 0
for chat in chats:
updated_at = chat.get('updated_at')
if updated_at:
try:
chat_time = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
if chat_time > yesterday:
recent_chats += 1
except:
pass
return jsonify({
"total_chats": total_chats,
"total_messages": total_messages,
"recent_chats_24h": recent_chats,
"average_messages_per_chat": round(total_messages / total_chats, 2) if total_chats > 0 else 0
})
except Exception as e:
logger.error(f"Error getting stats: {e}")
return jsonify({"error": "Could not fetch statistics"}), 500
# --- Error Handlers ---
@app.errorhandler(404)
def not_found(error):
"""Handle 404 errors"""
return jsonify({"error": "Endpoint not found"}), 404
@app.errorhandler(500)
def internal_error(error):
"""Handle 500 errors"""
logger.error(f"Internal server error: {error}")
return jsonify({"error": "Internal server error"}), 500
# --- Main Execution ---
if __name__ == '__main__':
# Set debug=False for production
port = int(os.environ.get('PORT', 7860))
app.run(debug=False, port=port, host='0.0.0.0')