debug_logger and execution update
This commit is contained in:
parent
da1d22ec56
commit
f589e22e4e
@ -1,12 +1,72 @@
|
|||||||
|
import ast
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class DebugLogger:
|
class DebugLogger:
|
||||||
"""
|
|
||||||
Platzhalter für das Logging [1]
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logs = []
|
self.logs = []
|
||||||
|
|
||||||
def log(self, message):
|
# ---------------- INTERNAL ----------------
|
||||||
self.logs.append(message)
|
def _entry(self, level: str, message: str) -> dict:
|
||||||
|
entry = {
|
||||||
|
"level": level,
|
||||||
|
"message": message,
|
||||||
|
"time": datetime.now().strftime("%H:%M:%S"),
|
||||||
|
}
|
||||||
|
self.logs.append(entry)
|
||||||
|
return entry
|
||||||
|
|
||||||
def get_logs(self):
|
# ---------------- PUBLIC API ----------------
|
||||||
return self.logs
|
def log(self, message: str):
|
||||||
|
self._entry("INFO", message)
|
||||||
|
|
||||||
|
def log_stdout(self, output: str):
|
||||||
|
if output.strip():
|
||||||
|
self._entry("OUTPUT", output.rstrip())
|
||||||
|
|
||||||
|
def log_stderr(self, error: str):
|
||||||
|
if error.strip():
|
||||||
|
self._entry("ERROR", error.rstrip())
|
||||||
|
|
||||||
|
def log_syntax_error(self, filepath: str, error: SyntaxError):
|
||||||
|
msg = f"SyntaxError in {filepath} (line {error.lineno}): {error.msg}"
|
||||||
|
self._entry("SYNTAX", msg)
|
||||||
|
|
||||||
|
def log_result(self, returncode: int):
|
||||||
|
level = "SUCCESS" if returncode == 0 else "FAIL"
|
||||||
|
msg = f"Process exited with code {returncode}"
|
||||||
|
self._entry(level, msg)
|
||||||
|
|
||||||
|
def log_exception(self, exc: Exception):
|
||||||
|
msg = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
|
||||||
|
self._entry("EXCEPTION", msg.rstrip())
|
||||||
|
|
||||||
|
# ---------------- ACCESSORS ----------------
|
||||||
|
def get_logs(self) -> list:
|
||||||
|
return self.logs
|
||||||
|
|
||||||
|
def get_logs_by_level(self, level: str) -> list:
|
||||||
|
return [e for e in self.logs if e["level"] == level.upper()]
|
||||||
|
|
||||||
|
def has_errors(self) -> bool:
|
||||||
|
return any(e["level"] in ("ERROR", "SYNTAX", "EXCEPTION", "FAIL") for e in self.logs)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.logs = []
|
||||||
|
|
||||||
|
def format(self) -> str:
|
||||||
|
lines = []
|
||||||
|
icons = {
|
||||||
|
"INFO": "ℹ",
|
||||||
|
"OUTPUT": "▶",
|
||||||
|
"ERROR": "✖",
|
||||||
|
"SYNTAX": "⚠",
|
||||||
|
"EXCEPTION": "💥",
|
||||||
|
"SUCCESS": "✔",
|
||||||
|
"FAIL": "✖",
|
||||||
|
}
|
||||||
|
for e in self.logs:
|
||||||
|
icon = icons.get(e["level"], "•")
|
||||||
|
lines.append(f"[{e['time']}] {icon} {e['message']}")
|
||||||
|
return "\n".join(lines)
|
||||||
@ -1,25 +1,62 @@
|
|||||||
|
import ast
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from backend.debug_logger import DebugLogger
|
||||||
|
|
||||||
|
|
||||||
class ExecutionEngine:
|
class ExecutionEngine:
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = DebugLogger()
|
||||||
|
|
||||||
|
# ---------------- SYNTAX CHECK ----------------
|
||||||
|
def check_syntax(self, filepath: str) -> bool:
|
||||||
|
"""Parse the file with ast. Returns True if valid, False if not."""
|
||||||
|
path = Path(filepath)
|
||||||
|
if path.suffix.lower() != ".py":
|
||||||
|
return True # skip non-Python files
|
||||||
|
|
||||||
|
try:
|
||||||
|
source = path.read_text(encoding="utf-8")
|
||||||
|
ast.parse(source, filename=str(path))
|
||||||
|
self.logger.log(f"Syntax OK: {path.name}")
|
||||||
|
return True
|
||||||
|
except SyntaxError as e:
|
||||||
|
self.logger.log_syntax_error(str(path), e)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_exception(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ---------------- RUN ----------------
|
||||||
def run_file(self, filepath: str) -> dict:
|
def run_file(self, filepath: str) -> dict:
|
||||||
|
self.logger.clear()
|
||||||
path = Path(filepath)
|
path = Path(filepath)
|
||||||
|
|
||||||
|
# --- guards ---
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return {"stdout": "", "stderr": f"File not found: {filepath}", "returncode": -1}
|
self.logger.log_stderr(f"File not found: {filepath}")
|
||||||
|
return self._result("", f"File not found: {filepath}", -1)
|
||||||
|
|
||||||
ext = path.suffix.lower()
|
ext = path.suffix.lower()
|
||||||
if ext == ".py":
|
if ext == ".py":
|
||||||
|
# syntax check before running
|
||||||
|
if not self.check_syntax(filepath):
|
||||||
|
err = self.logger.format()
|
||||||
|
return self._result("", err, -1)
|
||||||
cmd = [sys.executable, str(path)]
|
cmd = [sys.executable, str(path)]
|
||||||
elif ext == ".js":
|
elif ext == ".js":
|
||||||
cmd = ["node", str(path)]
|
cmd = ["node", str(path)]
|
||||||
elif ext == ".sh":
|
elif ext == ".sh":
|
||||||
cmd = ["bash", str(path)]
|
cmd = ["bash", str(path)]
|
||||||
else:
|
else:
|
||||||
return {"stdout": "", "stderr": f"Unsupported file type: {ext}", "returncode": -1}
|
msg = f"Unsupported file type: {ext}"
|
||||||
|
self.logger.log_stderr(msg)
|
||||||
|
return self._result("", msg, -1)
|
||||||
|
|
||||||
|
# --- execute ---
|
||||||
|
self.logger.log(f"Running: {path.name}")
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
@ -28,14 +65,35 @@ class ExecutionEngine:
|
|||||||
timeout=30,
|
timeout=30,
|
||||||
cwd=str(path.parent),
|
cwd=str(path.parent),
|
||||||
)
|
)
|
||||||
return {
|
self.logger.log_stdout(result.stdout)
|
||||||
"stdout": result.stdout,
|
self.logger.log_stderr(result.stderr)
|
||||||
"stderr": result.stderr,
|
self.logger.log_result(result.returncode)
|
||||||
"returncode": result.returncode,
|
|
||||||
}
|
return self._result(result.stdout, result.stderr, result.returncode)
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
return {"stdout": "", "stderr": "Timed out after 30 seconds.", "returncode": -1}
|
msg = "Timed out after 30 seconds."
|
||||||
|
self.logger.log_stderr(msg)
|
||||||
|
return self._result("", msg, -1)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
return {"stdout": "", "stderr": f"Runtime not found: {e}", "returncode": -1}
|
msg = f"Runtime not found: {e}"
|
||||||
|
self.logger.log_stderr(msg)
|
||||||
|
return self._result("", msg, -1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"stdout": "", "stderr": f"Error: {e}", "returncode": -1}
|
self.logger.log_exception(e)
|
||||||
|
return self._result("", str(e), -1)
|
||||||
|
|
||||||
|
# ---------------- HELPERS ----------------
|
||||||
|
def _result(self, stdout: str, stderr: str, returncode: int) -> dict:
|
||||||
|
return {
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": stderr,
|
||||||
|
"returncode": returncode,
|
||||||
|
"success": returncode == 0,
|
||||||
|
"logs": self.logger.get_logs(),
|
||||||
|
"log_text": self.logger.format(),
|
||||||
|
"has_errors": self.logger.has_errors(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_logger(self) -> DebugLogger:
|
||||||
|
return self.logger
|
||||||
79
src/main.py
79
src/main.py
@ -19,7 +19,7 @@ from backend.execution_engine import ExecutionEngine
|
|||||||
st.set_page_config(
|
st.set_page_config(
|
||||||
page_title="AI Code Editor",
|
page_title="AI Code Editor",
|
||||||
page_icon="💻",
|
page_icon="💻",
|
||||||
layout="wide",
|
layout="wide"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---------------- FOLDER PICKER ----------------
|
# ---------------- FOLDER PICKER ----------------
|
||||||
@ -38,7 +38,6 @@ def render_sidebar():
|
|||||||
root_folder = st.session_state.get("root_folder")
|
root_folder = st.session_state.get("root_folder")
|
||||||
|
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
# --- Button on top ---
|
|
||||||
if st.button("📂 Choose Folder", key="pick_folder"):
|
if st.button("📂 Choose Folder", key="pick_folder"):
|
||||||
picked = pick_folder()
|
picked = pick_folder()
|
||||||
if picked:
|
if picked:
|
||||||
@ -50,7 +49,6 @@ def render_sidebar():
|
|||||||
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
# --- Tree below ---
|
|
||||||
if root_folder:
|
if root_folder:
|
||||||
try:
|
try:
|
||||||
root_path = Path(root_folder)
|
root_path = Path(root_folder)
|
||||||
@ -73,6 +71,58 @@ def render_sidebar():
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# ---------------- OUTPUT PANEL ----------------
|
||||||
|
def render_output(result: dict):
|
||||||
|
"""Render structured execution output from ExecutionEngine."""
|
||||||
|
success = result["returncode"] == 0
|
||||||
|
status_icon = "✅" if success else "❌"
|
||||||
|
status_text = f"Exited {result['returncode']}"
|
||||||
|
|
||||||
|
st.markdown(f"**{status_icon} {status_text}**")
|
||||||
|
|
||||||
|
# stdout
|
||||||
|
if result["stdout"].strip():
|
||||||
|
st.caption("stdout")
|
||||||
|
st.code(result["stdout"].rstrip(), language="text")
|
||||||
|
|
||||||
|
# stderr
|
||||||
|
if result["stderr"].strip():
|
||||||
|
st.caption("stderr")
|
||||||
|
st.code(result["stderr"].rstrip(), language="text")
|
||||||
|
|
||||||
|
# debug log entries (collapsed by default)
|
||||||
|
if result.get("logs"):
|
||||||
|
with st.expander("🪲 Debug log", expanded=False):
|
||||||
|
level_colors = {
|
||||||
|
"INFO": "#6c7086",
|
||||||
|
"OUTPUT": "#a6e3a1",
|
||||||
|
"ERROR": "#f38ba8",
|
||||||
|
"SYNTAX": "#fab387",
|
||||||
|
"EXCEPTION": "#f38ba8",
|
||||||
|
"SUCCESS": "#a6e3a1",
|
||||||
|
"FAIL": "#f38ba8",
|
||||||
|
}
|
||||||
|
icons = {
|
||||||
|
"INFO": "ℹ", "OUTPUT": "▶", "ERROR": "✖",
|
||||||
|
"SYNTAX": "⚠", "EXCEPTION": "💥", "SUCCESS": "✔", "FAIL": "✖",
|
||||||
|
}
|
||||||
|
lines = []
|
||||||
|
for e in result["logs"]:
|
||||||
|
color = level_colors.get(e["level"], "#cdd6f4")
|
||||||
|
icon = icons.get(e["level"], "•")
|
||||||
|
lines.append(
|
||||||
|
f'<span style="color:#585b70">[{e["time"]}]</span> '
|
||||||
|
f'<span style="color:{color}">{icon} <b>{e["level"]}</b></span> '
|
||||||
|
f'<span style="color:#cdd6f4">{e["message"]}</span>'
|
||||||
|
)
|
||||||
|
st.markdown(
|
||||||
|
"<br>".join(lines),
|
||||||
|
unsafe_allow_html=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if not result["stdout"].strip() and not result["stderr"].strip():
|
||||||
|
st.caption("No output.")
|
||||||
|
|
||||||
# ---------------- MAIN ----------------
|
# ---------------- MAIN ----------------
|
||||||
def main():
|
def main():
|
||||||
file_manager = FileManager()
|
file_manager = FileManager()
|
||||||
@ -103,7 +153,7 @@ def main():
|
|||||||
# ---------------- FILE SWITCH ----------------
|
# ---------------- FILE SWITCH ----------------
|
||||||
if current_file != st.session_state.active_file:
|
if current_file != st.session_state.active_file:
|
||||||
st.session_state.active_file = current_file
|
st.session_state.active_file = current_file
|
||||||
st.session_state.run_result = None # clear output on file switch
|
st.session_state.run_result = None
|
||||||
if current_file:
|
if current_file:
|
||||||
content = file_manager.read_file(current_file)
|
content = file_manager.read_file(current_file)
|
||||||
st.session_state.editor_content = content
|
st.session_state.editor_content = content
|
||||||
@ -119,7 +169,6 @@ def main():
|
|||||||
col1, col2 = st.columns([3, 2])
|
col1, col2 = st.columns([3, 2])
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
# --- File name + Run button on same row ---
|
|
||||||
title_col, btn_col = st.columns([5, 1])
|
title_col, btn_col = st.columns([5, 1])
|
||||||
with title_col:
|
with title_col:
|
||||||
st.subheader(f"✏️ {Path(st.session_state.active_file).name}")
|
st.subheader(f"✏️ {Path(st.session_state.active_file).name}")
|
||||||
@ -138,7 +187,6 @@ def main():
|
|||||||
file_manager.save_file(st.session_state.active_file, new_value)
|
file_manager.save_file(st.session_state.active_file, new_value)
|
||||||
st.success("Saved ✔")
|
st.success("Saved ✔")
|
||||||
|
|
||||||
# --- Run execution ---
|
|
||||||
if run_clicked:
|
if run_clicked:
|
||||||
with st.spinner("Running…"):
|
with st.spinner("Running…"):
|
||||||
st.session_state.run_result = execution_engine.run_file(
|
st.session_state.run_result = execution_engine.run_file(
|
||||||
@ -149,23 +197,10 @@ def main():
|
|||||||
chat = ChatInterface()
|
chat = ChatInterface()
|
||||||
chat.display_chat(chat_manager)
|
chat.display_chat(chat_manager)
|
||||||
|
|
||||||
# --- Output panel ---
|
if st.session_state.run_result is not None:
|
||||||
result = st.session_state.run_result
|
|
||||||
if result is not None:
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
success = result["returncode"] == 0
|
st.caption("Output")
|
||||||
|
render_output(st.session_state.run_result)
|
||||||
status = "✅ Exited 0" if success else f"❌ Exited {result['returncode']}"
|
|
||||||
st.caption(f"Output — {status}")
|
|
||||||
|
|
||||||
if result["stdout"]:
|
|
||||||
st.code(result["stdout"], language="text")
|
|
||||||
|
|
||||||
if result["stderr"]:
|
|
||||||
st.code(result["stderr"], language="text")
|
|
||||||
|
|
||||||
if not result["stdout"] and not result["stderr"]:
|
|
||||||
st.caption("No output.")
|
|
||||||
else:
|
else:
|
||||||
st.info("Select a file from the explorer")
|
st.info("Select a file from the explorer")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user