mission17-ai / app.py
GitHub Action
sync: ci: retrigger HF deploy with fixed workflow (exclude model.h5)
bc8fe08
Raw
History Blame Contribute Delete
4.49 kB
import os
import traceback
import logging
from flask import Flask, request, jsonify
from flask_cors import CORS
from werkzeug.utils import secure_filename
from dotenv import load_dotenv
from utils.anticheat import AntiCheatEngine
from utils.predictor import Predictor
from utils.verdict import get_verdict
load_dotenv()
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Initialize components
app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'webp'}
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # Limit upload to 100MB
logger.info("🧠 Loading the MISSION 17 AI Brain (Ollama Vision)...")
anticheat = AntiCheatEngine()
predictor = Predictor()
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
@app.route('/health', methods=['GET'])
def health():
return jsonify({
"status": "ok",
"model": predictor.get_model_name(),
"anticheat_hashes": anticheat.count()
}), 200
@app.route('/reset-anti-cheat', methods=['POST', 'GET'])
def reset_anti_cheat():
count = anticheat.clear()
logger.info("πŸ›‘οΈ Anti-cheat hash database cleared!")
return jsonify({"message": "Anti-cheat hash database cleared!", "count": count}), 200
@app.route('/predict', methods=['POST'])
def predict():
try:
# πŸ”’ CHECK 1: File Presence
if 'file' not in request.files:
return jsonify({'error': 'No file uploaded'}), 400
file = request.files['file']
# πŸ”’ CHECK 2: Empty File Detection (Bug Fix)
file.seek(0, os.SEEK_END)
if file.tell() == 0:
return jsonify({"error": "Processing failed: Empty file"}), 400
file.seek(0)
# πŸ”’ CHECK 3: Empty Filename
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
# πŸ”’ CHECK 4: File Type Validation
if not allowed_file(file.filename):
return jsonify({'error': 'Invalid file type. Only JPG/PNG allowed.'}), 400
# Read file bytes ONCE and reuse them
file_bytes = file.read()
# 🎯 MODULE 11: Calculate Hash and Check for Cheaters
# skip_anticheat=1 is sent by the admin re-scan endpoint so that
# already-registered hashes don't falsely trigger duplicate detection.
skip_anticheat = request.form.get('skip_anticheat', '0') == '1'
if not skip_anticheat and anticheat.is_duplicate(file_bytes):
logger.warning("🚨 ANTI-CHEAT: Duplicate image detected!")
return jsonify({
"status": "REJECTED",
"error": "Duplicate image detected. You cannot farm points!",
"prediction": "Anti-Cheat: Duplicate"
}), 400
# πŸ€– AI Vision Prediction via Ollama
logger.info("πŸ€– Sending image to Ollama Vision...")
ai_result = predictor.predict(file_bytes)
category = ai_result.get('category', 'Non_SDG_Invalid')
confidence = ai_result.get('confidence', 0)
reason = ai_result.get('reason', '')
# βš–οΈ Get formatted verdict based on AI output
verdict_response = get_verdict(category, confidence, threshold=55)
verdict_response['reason'] = reason
verdict_response['model'] = predictor.get_model_name()
# Only register hash if the image was VERIFIED (save memory/prevent false positives on bad images)
if verdict_response['is_verified']:
anticheat.register(file_bytes)
logger.info(f"βœ… Unique verified image logged to anticheat.")
return jsonify(verdict_response)
except Exception as e:
logger.error(f"❌ Processing Error: {str(e)}")
traceback.print_exc()
return jsonify({'error': "Processing failed", 'detail': str(e)}), 500
if __name__ == '__main__':
# Hugging Face requires the app to listen on 0.0.0.0:7860
app.run(host='0.0.0.0', port=7860, debug=False)