Spaces:
Runtime error
Runtime error
| import os | |
| import sys | |
| import platform | |
| import argparse | |
| from pathlib import Path | |
| from flask import Flask, render_template, request, jsonify | |
| import threading | |
| import time | |
| from werkzeug.utils import secure_filename | |
| from werkzeug.security import generate_password_hash, check_password_hash | |
| from flask_sqlalchemy import SQLAlchemy | |
| from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user | |
| from flask_limiter import Limiter | |
| from flask_limiter.util import get_remote_address | |
| import uuid | |
| import requests | |
| # Import the new model functionality | |
| from model import check_ollama_connection, generate_with_ollama, analyze_image_for_disease | |
| from weather import get_current_weather, TN_DISTRICTS | |
| from rag_engine import initialize_knowledge_base, query_rag | |
| app = Flask(__name__) | |
| app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Disable static file caching | |
| # ----- DATABASE, AUTH & LIMITER SETUP ----- | |
| app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default_agrogpt_secret_key') | |
| app.config['SESSION_COOKIE_SAMESITE'] = 'None' | |
| app.config['SESSION_COOKIE_SECURE'] = True | |
| database_url = os.environ.get('DATABASE_URL') | |
| if database_url and database_url.startswith("postgres://"): | |
| database_url = database_url.replace("postgres://", "postgresql://", 1) | |
| app.config['SQLALCHEMY_DATABASE_URI'] = database_url or 'sqlite:///agrogpt.db' | |
| app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False | |
| db = SQLAlchemy(app) | |
| login_manager = LoginManager() | |
| login_manager.init_app(app) | |
| def unauthorized(): | |
| return jsonify({'error': 'Please log in to use this feature.'}), 401 | |
| limiter = Limiter( | |
| get_remote_address, | |
| app=app, | |
| default_limits=["1000 per day", "100 per hour"], | |
| storage_uri="memory://" | |
| ) | |
| class User(UserMixin, db.Model): | |
| id = db.Column(db.Integer, primary_key=True) | |
| email = db.Column(db.String(120), unique=True, nullable=False) | |
| full_name = db.Column(db.String(120), nullable=False) | |
| profession = db.Column(db.String(80), nullable=False) | |
| password_hash = db.Column(db.String(255), nullable=False) | |
| def load_user(user_id): | |
| return db.session.get(User, int(user_id)) | |
| with app.app_context(): | |
| db.create_all() | |
| # ------------------------------------------ | |
| def add_no_cache_headers(response): | |
| """Prevent browser from caching static files during development.""" | |
| if request.path.startswith('/static/'): | |
| response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' | |
| response.headers['Pragma'] = 'no-cache' | |
| response.headers['Expires'] = '0' | |
| return response | |
| # Configuration for file uploads | |
| if os.environ.get('VERCEL_ENV') or os.environ.get('VERCEL'): | |
| UPLOAD_FOLDER = '/tmp' | |
| else: | |
| UPLOAD_FOLDER = 'uploads' | |
| ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp'} | |
| app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER | |
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size | |
| # Create uploads directory if it doesn't exist | |
| os.makedirs(UPLOAD_FOLDER, exist_ok=True) | |
| # Global status | |
| ollama_connected = False | |
| status_message = "Initializing..." | |
| def allowed_file(filename): | |
| return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS | |
| def print_header() -> None: | |
| print("AgroGPT Web Interface (Ollama Edition) starting...", flush=True) | |
| print(f"Python: {platform.python_version()} ({sys.executable})", flush=True) | |
| def check_backend_status(): | |
| """Check Ollama status in background""" | |
| global ollama_connected, status_message | |
| status_message = "Connecting to Ollama (via Groq API)..." | |
| print("Connecting to Ollama...", flush=True) | |
| if check_ollama_connection(): | |
| ollama_connected = True | |
| status_message = "Connected to Ollama! Web interface is ready." | |
| print(status_message, flush=True) | |
| else: | |
| ollama_connected = False | |
| status_message = "Error: Could not connect to Ollama. Ensure GROQ_API_KEY is set in .env." | |
| print(status_message, flush=True) | |
| # Initialize the vector DB in the background | |
| print("Triggering background RAG knowledge base initialization...", flush=True) | |
| threading.Thread(target=initialize_knowledge_base).start() | |
| def index(): | |
| """Serve the main page""" | |
| return app.send_static_file('index.html') | |
| def get_status(): | |
| """Get the current status""" | |
| # Re-check occasionally if not connected | |
| if not ollama_connected: | |
| threading.Thread(target=check_backend_status).start() | |
| return jsonify({ | |
| 'model_loaded': ollama_connected, # Keep key for frontend compatibility | |
| 'loading_progress': status_message | |
| }) | |
| def fetch_weather(): | |
| """Fetch live weather for a district""" | |
| district = request.args.get('district', '').strip() | |
| if not district: | |
| return jsonify({'error': 'District parameter is required.'}), 400 | |
| weather_data = get_current_weather(district) | |
| if weather_data.get('success'): | |
| return jsonify(weather_data) | |
| else: | |
| return jsonify(weather_data), 500 | |
| # ----- AUTHENTICATION ROUTES ----- | |
| def register(): | |
| data = request.get_json() | |
| email = data.get('email') | |
| full_name = data.get('full_name') | |
| profession = data.get('profession') | |
| password = data.get('password') | |
| if not email or not full_name or not profession or not password: | |
| return jsonify({'error': 'All fields are required'}), 400 | |
| if User.query.filter_by(email=email).first(): | |
| return jsonify({'error': 'Email already exists'}), 400 | |
| hashed_password = generate_password_hash(password) | |
| new_user = User(email=email, full_name=full_name, profession=profession, password_hash=hashed_password) | |
| db.session.add(new_user) | |
| db.session.commit() | |
| login_user(new_user) | |
| return jsonify({'success': True, 'email': email, 'full_name': full_name}) | |
| def login(): | |
| data = request.get_json() | |
| email = data.get('email') | |
| password = data.get('password') | |
| user = User.query.filter_by(email=email).first() | |
| if user and check_password_hash(user.password_hash, password): | |
| login_user(user) | |
| return jsonify({'success': True, 'email': user.email, 'full_name': user.full_name}) | |
| return jsonify({'error': 'Invalid email or password'}), 401 | |
| def logout(): | |
| logout_user() | |
| return jsonify({'success': True}) | |
| def get_current_user(): | |
| if current_user.is_authenticated: | |
| return jsonify({'logged_in': True, 'email': current_user.email, 'full_name': current_user.full_name, 'profession': current_user.profession}) | |
| return jsonify({'logged_in': False}) | |
| # --------------------------------- | |
| def ask_question(): | |
| """Handle user questions""" | |
| if not ollama_connected: | |
| return jsonify({'error': 'Ollama is not connected. Please ensure Ollama is running.'}), 503 | |
| data = request.get_json() | |
| question = data.get('question', '').strip() | |
| district = data.get('district', '').strip() | |
| if not question: | |
| return jsonify({'error': 'Please provide a question.'}), 400 | |
| weather_context = "" | |
| if district and district in TN_DISTRICTS: | |
| weather_data = get_current_weather(district) | |
| if weather_data.get('success'): | |
| weather_context = f"\n\nCurrent Weather in {district}, Tamil Nadu: {weather_data['temperature']}°C, Humidity: {weather_data['humidity']}%, Condition: {weather_data['condition']} {weather_data['emoji']}, Wind Speed: {weather_data['wind_speed']} km/h. Please consider this live weather context in your advice if relevant." | |
| try: | |
| # Construct prompt | |
| weather_section = f"\n\n[Weather Context]: {weather_context.strip()}" if weather_context.strip() else "" | |
| # Fetch RAG context | |
| print("Fetching expert PDF knowledge context...", flush=True) | |
| rag_context = query_rag(question) | |
| rag_section = f"\n\n[Expert Advisory Knowledge Base Context]:\n{rag_context}\n[End of Knowledge Base]\n\n" if rag_context else "" | |
| full_prompt = ( | |
| "You are AgroGPT, an expert agriculture assistant with access to detailed agricultural advisory documents. " | |
| f"{rag_section}" | |
| "Answer the following question clearly and concisely in plain text. DO NOT visibly print out or quote the Knowledge Base Context provided above. Synthesize the information naturally into your conversational answer.\n\n" | |
| "Structure your response EXACTLY as follows:\n" | |
| "1. Provide a helpful, robust answer in English. Ensure you include relevant information from the knowledge base if available.\n" | |
| "2. Then write the header 'Malayalam Summary:' followed by the FULL answer translated into native Malayalam script (മലയാളം). Do NOT use English/Latin letters for Malayalam.\n" | |
| "3. Then write the header 'Tamil Summary:' followed by the FULL answer translated into native Tamil script (தமிழ்). Do NOT use English/Latin letters for Tamil.\n" | |
| "Use double line breaks between each section. Do NOT use markdown, asterisks, or bullet points.\n\n" | |
| f"Question: {question}{weather_section}\n\nAnswer:" | |
| ) | |
| print(f"Asking Ollama: {question}", flush=True) | |
| response_text = generate_with_ollama(full_prompt) | |
| return jsonify({ | |
| 'question': question, | |
| 'answer': response_text, | |
| 'full_response': response_text | |
| }) | |
| except Exception as e: | |
| return jsonify({'error': f'Error generating response: {str(e)}'}), 500 | |
| def upload_image(): | |
| """Handle image uploads for disease detection""" | |
| if 'image' not in request.files: | |
| return jsonify({'error': 'No image file provided'}), 400 | |
| file = request.files['image'] | |
| if file.filename == '': | |
| return jsonify({'error': 'No image file selected'}), 400 | |
| if file and allowed_file(file.filename): | |
| # Generate a unique filename to avoid conflicts | |
| filename = secure_filename(file.filename) | |
| unique_filename = f"{uuid.uuid4()}_{filename}" | |
| filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename) | |
| try: | |
| # Save the uploaded file | |
| file.save(filepath) | |
| # Analyze the image for disease (uses Ollama for advice) | |
| analysis_result = analyze_image_for_disease(filepath) | |
| # Clean up the uploaded file after analysis | |
| # Optional: Keep it if we want to log queries, but deleting for privacy/space | |
| if os.path.exists(filepath): | |
| os.remove(filepath) | |
| return jsonify({ | |
| 'success': True, | |
| 'analysis': analysis_result, | |
| 'filename': filename | |
| }) | |
| except Exception as e: | |
| # Clean up file if it exists | |
| if os.path.exists(filepath): | |
| os.remove(filepath) | |
| return jsonify({'error': f'Error processing image: {str(e)}'}), 500 | |
| return jsonify({'error': 'Invalid file type. Please upload a PNG, JPG, JPEG, GIF, or BMP image.'}), 400 | |
| def main(): | |
| """Main function to start the application""" | |
| print_header() | |
| # Check connection first | |
| check_backend_status() | |
| # Start Flask app | |
| port = int(os.environ.get("PORT", 7860)) | |
| print(f"Starting web server...", flush=True) | |
| print(f"Open your browser and go to: http://0.0.0.0:{port}", flush=True) | |
| app.run(host='0.0.0.0', port=port, debug=False) | |
| if __name__ == "__main__": | |
| os.environ.setdefault("PYTHONUNBUFFERED", "1") | |
| main() | |