|
|
| from flask import Blueprint, request, jsonify
|
| import sys
|
| import io
|
| import threading
|
| import queue
|
| import time
|
| import traceback
|
| import os
|
| import re
|
| from openai import OpenAI
|
| from config import API_KEY, BASE_URL, OPENAI_MODEL
|
|
|
| code_executor_bp = Blueprint('code_executor', __name__)
|
|
|
|
|
| client = OpenAI(
|
| api_key=API_KEY,
|
| base_url=BASE_URL
|
| )
|
|
|
|
|
| execution_contexts = {}
|
|
|
| class CustomStdin:
|
| def __init__(self, input_queue):
|
| self.input_queue = input_queue
|
| self.buffer = ""
|
|
|
| def readline(self):
|
| if not self.buffer:
|
| self.buffer = self.input_queue.get() + "\n"
|
|
|
| result = self.buffer
|
| self.buffer = ""
|
| return result
|
|
|
| class InteractiveExecution:
|
| """管理Python代码的交互式执行"""
|
| def __init__(self, code):
|
| self.code = code
|
| self.context_id = str(time.time())
|
| self.is_complete = False
|
| self.is_waiting_for_input = False
|
| self.stdout_buffer = io.StringIO()
|
| self.last_read_position = 0
|
| self.input_queue = queue.Queue()
|
| self.error = None
|
| self.thread = None
|
| self.should_terminate = False
|
|
|
| def run(self):
|
| """在单独的线程中启动执行"""
|
| self.thread = threading.Thread(target=self._execute)
|
| self.thread.daemon = True
|
| self.thread.start()
|
|
|
|
|
| time.sleep(0.1)
|
| return self.context_id
|
|
|
| def _execute(self):
|
| """执行代码,处理标准输入输出"""
|
| try:
|
|
|
| orig_stdin = sys.stdin
|
| orig_stdout = sys.stdout
|
|
|
|
|
| custom_stdin = CustomStdin(self.input_queue)
|
|
|
|
|
| sys.stdin = custom_stdin
|
| sys.stdout = self.stdout_buffer
|
|
|
| try:
|
|
|
| self._last_check_time = 0
|
|
|
| def check_termination():
|
| if self.should_terminate:
|
| raise KeyboardInterrupt("Execution terminated by user")
|
|
|
|
|
| shared_namespace = {
|
| "__builtins__": __builtins__,
|
| "_check_termination": check_termination,
|
| "time": time,
|
| "__name__": "__main__"
|
| }
|
|
|
|
|
| try:
|
| exec(self.code, shared_namespace)
|
| except KeyboardInterrupt:
|
| print("\nExecution terminated by user")
|
|
|
| except Exception as e:
|
| self.error = {
|
| "error": str(e),
|
| "traceback": traceback.format_exc()
|
| }
|
|
|
| finally:
|
|
|
| sys.stdin = orig_stdin
|
| sys.stdout = orig_stdout
|
|
|
|
|
| self.is_complete = True
|
|
|
| except Exception as e:
|
| self.error = {
|
| "error": str(e),
|
| "traceback": traceback.format_exc()
|
| }
|
| self.is_complete = True
|
|
|
| def terminate(self):
|
| """终止执行"""
|
| self.should_terminate = True
|
|
|
|
|
| if self.is_waiting_for_input:
|
| self.input_queue.put("\n")
|
|
|
|
|
| time.sleep(0.2)
|
|
|
|
|
| self.is_complete = True
|
|
|
| return True
|
|
|
| def provide_input(self, user_input):
|
| """为运行的代码提供输入"""
|
| self.input_queue.put(user_input)
|
| self.is_waiting_for_input = False
|
| return True
|
|
|
| def get_output(self):
|
| """获取stdout缓冲区的当前内容"""
|
| output = self.stdout_buffer.getvalue()
|
| return output
|
|
|
| def get_new_output(self):
|
| """只获取自上次读取以来的新输出"""
|
| current_value = self.stdout_buffer.getvalue()
|
| if self.last_read_position < len(current_value):
|
| new_output = current_value[self.last_read_position:]
|
| self.last_read_position = len(current_value)
|
| return new_output
|
| return ""
|
|
|
| @code_executor_bp.route('/generate', methods=['POST'])
|
| def generate_code():
|
| """使用AI生成Python代码"""
|
| try:
|
| prompt = request.json.get('prompt')
|
| if not prompt:
|
| return jsonify({
|
| "success": False,
|
| "error": "No prompt provided"
|
| })
|
|
|
|
|
| full_prompt = f"""You are a Python programming assistant. Generate Python code based on this requirement:
|
| {prompt}
|
|
|
| Provide only the Python code without any explanation or markdown formatting."""
|
|
|
|
|
| response = client.chat.completions.create(
|
| model=OPENAI_MODEL,
|
| messages=[{"role": "user", "content": full_prompt}]
|
| )
|
|
|
|
|
| code = response.choices[0].message.content.strip()
|
|
|
|
|
| code = re.sub(r'```python\n', '', code)
|
| code = re.sub(r'```', '', code)
|
|
|
| return jsonify({
|
| "success": True,
|
| "code": code
|
| })
|
|
|
| except Exception as e:
|
| return jsonify({
|
| "success": False,
|
| "error": str(e)
|
| })
|
|
|
| @code_executor_bp.route('/execute', methods=['POST'])
|
| def execute_code():
|
| """执行Python代码"""
|
| try:
|
| code = request.json.get('code')
|
| if not code:
|
| return jsonify({
|
| "success": False,
|
| "error": "No code provided"
|
| })
|
|
|
|
|
| execution = InteractiveExecution(code)
|
| context_id = execution.run()
|
|
|
|
|
| execution_contexts[context_id] = execution
|
|
|
|
|
| if execution.error:
|
|
|
| error_info = execution.error
|
| del execution_contexts[context_id]
|
|
|
| return jsonify({
|
| "success": False,
|
| "error": error_info["error"],
|
| "traceback": error_info["traceback"]
|
| })
|
|
|
|
|
| output = execution.get_output()
|
|
|
| execution.last_read_position = len(output)
|
|
|
|
|
| if execution.is_complete:
|
|
|
| del execution_contexts[context_id]
|
|
|
| return jsonify({
|
| "success": True,
|
| "output": output,
|
| "needsInput": False
|
| })
|
| else:
|
|
|
| execution.is_waiting_for_input = True
|
|
|
| return jsonify({
|
| "success": True,
|
| "output": output,
|
| "needsInput": True,
|
| "context_id": context_id
|
| })
|
|
|
| except Exception as e:
|
| return jsonify({
|
| "success": False,
|
| "error": str(e),
|
| "traceback": traceback.format_exc()
|
| })
|
|
|
| @code_executor_bp.route('/input', methods=['POST'])
|
| def provide_input():
|
| """为正在执行的代码提供输入"""
|
| try:
|
| user_input = request.json.get('input', '')
|
| context_id = request.json.get('context_id')
|
|
|
| if not context_id or context_id not in execution_contexts:
|
| return jsonify({
|
| "success": False,
|
| "error": "Invalid or expired execution context"
|
| })
|
|
|
| execution = execution_contexts[context_id]
|
|
|
|
|
| execution.provide_input(user_input)
|
|
|
|
|
| time.sleep(0.1)
|
|
|
|
|
| new_output = execution.get_new_output()
|
|
|
|
|
| if execution.is_complete:
|
|
|
| if execution.error:
|
|
|
| error_info = execution.error
|
| del execution_contexts[context_id]
|
|
|
| return jsonify({
|
| "success": False,
|
| "error": error_info["error"],
|
| "traceback": error_info["traceback"]
|
| })
|
| else:
|
|
|
| del execution_contexts[context_id]
|
|
|
| return jsonify({
|
| "success": True,
|
| "output": new_output,
|
| "needsInput": False
|
| })
|
| else:
|
|
|
| execution.is_waiting_for_input = True
|
|
|
| return jsonify({
|
| "success": True,
|
| "output": new_output,
|
| "needsInput": True
|
| })
|
|
|
| except Exception as e:
|
| return jsonify({
|
| "success": False,
|
| "error": str(e),
|
| "traceback": traceback.format_exc()
|
| })
|
|
|
| @code_executor_bp.route('/stop', methods=['POST'])
|
| def stop_execution():
|
| """停止执行"""
|
| try:
|
| context_id = request.json.get('context_id')
|
|
|
| if not context_id or context_id not in execution_contexts:
|
| return jsonify({
|
| "success": False,
|
| "error": "Invalid or expired execution context"
|
| })
|
|
|
| execution = execution_contexts[context_id]
|
|
|
|
|
| execution.terminate()
|
|
|
|
|
| output = execution.get_output()
|
|
|
|
|
| del execution_contexts[context_id]
|
|
|
| return jsonify({
|
| "success": True,
|
| "output": output,
|
| "message": "Execution terminated"
|
| })
|
|
|
| except Exception as e:
|
| return jsonify({
|
| "success": False,
|
| "error": str(e),
|
| "traceback": traceback.format_exc()
|
| }) |