from flask import Flask, request, jsonify import subprocess import os import tempfile import uuid import time import logging from flask_cors import CORS import gradio as gr import requests app = Flask(__name__) CORS(app) # Habilitar CORS para todas las rutas # Configuración de logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Directorio temporal para los archivos de código TEMP_DIR = os.path.join(tempfile.gettempdir(), '42coderunner') os.makedirs(TEMP_DIR, exist_ok=True) # Tiempo máximo de ejecución (en segundos) MAX_EXECUTION_TIME = 10 # Aumentamos un poco para Gradio # URL base de la API (para la interfaz Gradio) # Detecta si estamos en Hugging Face Spaces API_BASE_URL = os.environ.get("SPACE_HOST") if API_BASE_URL: # En Hugging Face, usa la URL pública del espacio # Asegúrate de que el protocolo sea https API_BASE_URL = f"https://{API_BASE_URL}" else: # Localmente, usa localhost API_BASE_URL = "http://localhost:5000" API_EXECUTE_URL = f"{API_BASE_URL}/api/execute" # --- Funciones de la API Flask --- @app.route('/api/execute', methods=['POST']) def execute_code(): try: # Obtener el código C del request data = request.get_json() if not data or 'code' not in data: return jsonify({'success': False, 'error': 'No se proporcionó código'}), 400 code = data['code'] # Crear un ID único para este trabajo job_id = str(uuid.uuid4()) # Crear archivos temporales para el código y la salida code_file = os.path.join(TEMP_DIR, f"{job_id}.c") executable = os.path.join(TEMP_DIR, f"{job_id}.exe") # Guardar el código en un archivo temporal with open(code_file, 'w') as f: f.write(code) # Compilar el código logger.info(f"Compilando código para job {job_id}") compile_process = subprocess.run( ['gcc', code_file, '-o', executable], capture_output=True, text=True ) # Verificar si la compilación fue exitosa if compile_process.returncode != 0: return jsonify({ 'success': False, 'error': compile_process.stderr }) # Ejecutar el código compilado logger.info(f"Ejecutando código para job {job_id}") try: start_time = time.time() run_process = subprocess.run( [executable], capture_output=True, text=True, timeout=MAX_EXECUTION_TIME ) execution_time = time.time() - start_time # Preparar la respuesta result = { 'success': run_process.returncode == 0, 'output': run_process.stdout, 'error': run_process.stderr, 'execution_time': execution_time } except subprocess.TimeoutExpired: result = { 'success': False, 'error': f'La ejecución excedió el tiempo límite de {MAX_EXECUTION_TIME} segundos' } # Limpiar archivos temporales try: os.remove(code_file) if os.path.exists(executable): os.remove(executable) except Exception as e: logger.error(f"Error al limpiar archivos temporales: {e}") return jsonify(result) except Exception as e: logger.error(f"Error inesperado: {e}") return jsonify({'success': False, 'error': f'Error interno del servidor: {str(e)}'}), 500 @app.route('/api/health', methods=['GET']) def health_check(): """Basic health check endpoint.""" logger.info("Health check requested.") # A simple health check is enough for Hugging Face # The previous checks (compiler, temp dir) might fail in the HF environment return jsonify({'status': 'ok', 'timestamp': time.time()}) # --- Interfaz Gradio --- def run_c_code(c_code): """Función que se ejecuta cuando el usuario envía código C a través de Gradio.""" logger.info(f"Solicitud Gradio recibida. URL API: {API_EXECUTE_URL}") try: response = requests.post(API_EXECUTE_URL, json={'code': c_code}, timeout=MAX_EXECUTION_TIME + 5) # Timeout un poco mayor para la request response.raise_for_status() # Lanza excepción para errores HTTP data = response.json() if data.get('success'): output = f"--- Salida ({data.get('execution_time', 0):.4f}s) ---\n{data.get('output', '')}" if data.get('error'): # Mostrar stderr aunque la ejecución sea exitosa (warnings, etc.) output += f"\n\n--- Errores (stderr) ---\n{data['error']}" return output else: return f"--- Error ---\n{data.get('error', 'Error desconocido')}" except requests.exceptions.RequestException as e: logger.error(f"Error de red al llamar a la API: {e}") return f"Error de red al conectar con el backend: {e}" except Exception as e: logger.error(f"Error inesperado en la interfaz Gradio: {e}") return f"Error inesperado en la interfaz: {str(e)}" # Ejemplo de código C para la interfaz EXAMPLE_CODE = """#include int main() { printf("¡Hola desde 42CodeRunner!\n"); return 0; } """ # Crear la interfaz Gradio iface = gr.Interface( fn=run_c_code, inputs=gr.Code(language="c", label="Código C", value=EXAMPLE_CODE), outputs=gr.Textbox(label="Resultado de la Ejecución", lines=15), title="🏊u200d♂️ 42CodeRunner", description="Escribe tu código C, haz clic en 'Submit' y mira la magia suceder. Ejecutado de forma segura en el backend.", allow_flagging='never' ) # --- Integration and Execution --- # Create a main Gradio Blocks app to host both the UI and the API with gr.Blocks(title="🏊u200d♂️ 42CodeRunner") as demo: # Render the defined interface within the Blocks iface.render() # Mount the Flask app (containing the API) onto the Gradio Blocks app under /api # This makes the Flask routes available at paths like /api/execute demo = gr.mount_wsgi_app(demo, app, path="/api") # --- Ejecución de la App --- if __name__ == '__main__': # Use Gradio's default port or environment variable port = int(os.environ.get('PORT', 7860)) # Launch the combined Gradio Blocks app # Allow connection from network (needed for Docker/Spaces) demo.launch(server_name='0.0.0.0', server_port=port)