from flask import Flask, request, jsonify, send_from_directory from flask_cors import CORS from llm_agent import LLM_Agent from data_processor import DataProcessor import os import logging import time from dotenv import load_dotenv from werkzeug.utils import secure_filename load_dotenv() logging.basicConfig(level=logging.INFO) logging.getLogger('matplotlib').setLevel(logging.WARNING) logging.getLogger('PIL').setLevel(logging.WARNING) logging.getLogger('plotly').setLevel(logging.WARNING) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) app = Flask(__name__, static_folder=os.path.join(BASE_DIR, 'static')) CORS(app, origins=[ "https://llm-integrated-excel-plotter-app.vercel.app", "http://localhost:8080", "http://localhost:3000", ], supports_credentials=False) agent = LLM_Agent() UPLOAD_FOLDER = os.path.join(BASE_DIR, 'data', 'uploads') ALLOWED_EXTENSIONS = {'csv', 'xls', 'xlsx'} MAX_UPLOAD_BYTES = 10 * 1024 * 1024 # 10 MB app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_BYTES os.makedirs(UPLOAD_FOLDER, exist_ok=True) STARTED_AT = time.time() def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/') def index(): return jsonify({ "status": "ok", "message": "AI Data Visualization API", "endpoints": ["/health", "/plot", "/upload", "/stats", "/models"] }) @app.route('/health', methods=['GET']) def health(): """Lightweight endpoint used by frontend wake-up checks and cron pings.""" return jsonify({ "status": "ok", "service": "llm-excel-plotter-agent", "uptime_seconds": round(time.time() - STARTED_AT, 2), "timestamp": int(time.time()), }) @app.route('/models', methods=['GET']) def models(): return jsonify({ "models": [ {"id": "qwen", "name": "Qwen2.5-Coder-0.5B", "provider": "Local (transformers)", "free": True}, {"id": "bart", "name": "BART (fine-tuned)", "provider": "Local (transformers)", "free": True}, {"id": "gemini", "name": "Gemini 2.0 Flash", "provider": "Google AI (API key)", "free": False}, {"id": "grok", "name": "Grok-3 Mini", "provider": "xAI (API key)", "free": False}, ], "default": "qwen" }) @app.route('/plot', methods=['POST']) def plot(): t0 = time.time() data = request.get_json(force=True) if not data or not data.get('query'): return jsonify({'error': 'Missing required field: query'}), 400 logging.info(f"Plot request: model={data.get('model','qwen')} query={data.get('query')[:80]}") result = agent.process_request(data) logging.info(f"Plot completed in {time.time() - t0:.2f}s") return jsonify(result) @app.route('/static/') def serve_static(filename): resp = send_from_directory(app.static_folder, filename) resp.headers['Access-Control-Allow-Origin'] = '*' resp.headers['Cache-Control'] = 'public, max-age=300' return resp @app.route('/upload', methods=['POST']) def upload_file(): if 'file' not in request.files: return jsonify({'error': 'No file part in request'}), 400 file = request.files['file'] if not file.filename: return jsonify({'error': 'No file selected'}), 400 if not allowed_file(file.filename): return jsonify({'error': 'File type not allowed. Use CSV, XLS, or XLSX'}), 400 filename = secure_filename(file.filename) file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(file_path) dp = DataProcessor(file_path) return jsonify({ 'message': 'File uploaded successfully', 'columns': dp.get_columns(), 'dtypes': dp.get_dtypes(), 'preview': dp.preview(5), 'file_path': file_path, 'row_count': len(dp.data), }) @app.route('/stats', methods=['POST']) def stats(): data = request.get_json(force=True) or {} file_path = data.get('file_path') dp = DataProcessor(file_path) if file_path and os.path.exists(file_path) else agent.data_processor return jsonify({ 'columns': dp.get_columns(), 'dtypes': dp.get_dtypes(), 'stats': dp.get_stats(), 'row_count': len(dp.data), }) @app.errorhandler(413) def file_too_large(e): return jsonify({'error': f'File too large. Maximum size is {MAX_UPLOAD_BYTES // (1024*1024)} MB'}), 413 if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)