| |
|
| | 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()
|
| | }) |