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