ArchCoder/llm-excel-plotter-agent
0
1from flask import Flask, request, jsonify, send_from_directory2from flask_cors import CORS3from llm_agent import LLM_Agent4from data_processor import DataProcessor5import os6import logging7import time8from dotenv import load_dotenv9from werkzeug.utils import secure_filename10 11load_dotenv()12 13logging.basicConfig(level=logging.INFO)14logging.getLogger('matplotlib').setLevel(logging.WARNING)15logging.getLogger('PIL').setLevel(logging.WARNING)16logging.getLogger('plotly').setLevel(logging.WARNING)17 18BASE_DIR = os.path.dirname(os.path.abspath(__file__))19 20app = Flask(__name__, static_folder=os.path.join(BASE_DIR, 'static'))21 22CORS(app, origins=[23 "https://llm-integrated-excel-plotter-app.vercel.app",24 "http://localhost:8080",25 "http://localhost:3000",26], supports_credentials=False)27 28agent = LLM_Agent()29 30UPLOAD_FOLDER = os.path.join(BASE_DIR, 'data', 'uploads')31ALLOWED_EXTENSIONS = {'csv', 'xls', 'xlsx'}32MAX_UPLOAD_BYTES = 10 * 1024 * 1024 # 10 MB33 34app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER35app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_BYTES36 37os.makedirs(UPLOAD_FOLDER, exist_ok=True)38STARTED_AT = time.time()39 40 41def allowed_file(filename):42 return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS43 44 45@app.route('/')46def index():47 return jsonify({48 "status": "ok",49 "message": "AI Data Visualization API",50 "endpoints": ["/health", "/plot", "/upload", "/stats", "/models"]51 })52 53 54@app.route('/health', methods=['GET'])55def health():56 """Lightweight endpoint used by frontend wake-up checks and cron pings."""57 return jsonify({58 "status": "ok",59 "service": "llm-excel-plotter-agent",60 "uptime_seconds": round(time.time() - STARTED_AT, 2),61 "timestamp": int(time.time()),62 })63 64 65@app.route('/models', methods=['GET'])66def models():67 return jsonify({68 "models": [69 {"id": "qwen", "name": "Qwen2.5-Coder-0.5B", "provider": "Local (transformers)", "free": True},70 {"id": "bart", "name": "BART (fine-tuned)", "provider": "Local (transformers)", "free": True},71 {"id": "gemini", "name": "Gemini 2.0 Flash", "provider": "Google AI (API key)", "free": False},72 {"id": "grok", "name": "Grok-3 Mini", "provider": "xAI (API key)", "free": False},73 ],74 "default": "qwen"75 })76 77 78@app.route('/plot', methods=['POST'])79def plot():80 t0 = time.time()81 data = request.get_json(force=True)82 if not data or not data.get('query'):83 return jsonify({'error': 'Missing required field: query'}), 40084 85 logging.info(f"Plot request: model={data.get('model','qwen')} query={data.get('query')[:80]}")86 result = agent.process_request(data)87 logging.info(f"Plot completed in {time.time() - t0:.2f}s")88 return jsonify(result)89 90 91@app.route('/static/<path:filename>')92def serve_static(filename):93 resp = send_from_directory(app.static_folder, filename)94 resp.headers['Access-Control-Allow-Origin'] = '*'95 resp.headers['Cache-Control'] = 'public, max-age=300'96 return resp97 98 99@app.route('/upload', methods=['POST'])100def upload_file():101 if 'file' not in request.files:102 return jsonify({'error': 'No file part in request'}), 400103 file = request.files['file']104 if not file.filename:105 return jsonify({'error': 'No file selected'}), 400106 if not allowed_file(file.filename):107 return jsonify({'error': 'File type not allowed. Use CSV, XLS, or XLSX'}), 400108 109 filename = secure_filename(file.filename)110 file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)111 file.save(file_path)112 113 dp = DataProcessor(file_path)114 return jsonify({115 'message': 'File uploaded successfully',116 'columns': dp.get_columns(),117 'dtypes': dp.get_dtypes(),118 'preview': dp.preview(5),119 'file_path': file_path,120 'row_count': len(dp.data),121 })122 123 124@app.route('/stats', methods=['POST'])125def stats():126 data = request.get_json(force=True) or {}127 file_path = data.get('file_path')128 dp = DataProcessor(file_path) if file_path and os.path.exists(file_path) else agent.data_processor129 return jsonify({130 'columns': dp.get_columns(),131 'dtypes': dp.get_dtypes(),132 'stats': dp.get_stats(),133 'row_count': len(dp.data),134 })135 136 137@app.errorhandler(413)138def file_too_large(e):139 return jsonify({'error': f'File too large. Maximum size is {MAX_UPLOAD_BYTES // (1024*1024)} MB'}), 413140 141 142if __name__ == '__main__':143 app.run(host='0.0.0.0', port=7860)