Spaces:
Sleeping
Sleeping
| # 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 --- | |
| def index(): | |
| """Serves the main chat application page (index.html).""" | |
| return render_template('index.html') | |
| # --- API Endpoints --- | |
| def get_config(): | |
| """Provides public configuration keys to the frontend.""" | |
| return jsonify({ | |
| 'imgbb_api_key': os.getenv('IMGBB_API_KEY') | |
| }) | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 --- | |
| 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 | |
| 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 | |
| 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 | |
| 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 --- | |
| def get_sessions(): | |
| """Legacy endpoint - redirects to /chats""" | |
| return get_all_chats() | |
| def get_messages(session_id): | |
| """Legacy endpoint - redirects to /chat/<session_id>/messages""" | |
| return get_chat_messages(session_id) | |
| 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 --- | |
| 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 | |
| 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 --- | |
| def not_found(error): | |
| """Handle 404 errors""" | |
| return jsonify({"error": "Endpoint not found"}), 404 | |
| 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') |