debug_logger and execution update

This commit is contained in:
leart-ramushi 2026-05-26 16:36:09 +02:00
parent da1d22ec56
commit f589e22e4e
3 changed files with 192 additions and 39 deletions

View File

@ -1,12 +1,72 @@
import ast
import traceback
from datetime import datetime
class DebugLogger:
"""
Platzhalter für das Logging [1]
"""
def __init__(self):
self.logs = []
def log(self, message):
self.logs.append(message)
# ---------------- INTERNAL ----------------
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 ----------------
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)

View File

@ -1,25 +1,62 @@
import ast
import subprocess
import sys
from pathlib import Path
from backend.debug_logger import DebugLogger
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:
self.logger.clear()
path = Path(filepath)
# --- guards ---
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()
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)]
elif ext == ".js":
cmd = ["node", str(path)]
elif ext == ".sh":
cmd = ["bash", str(path)]
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:
result = subprocess.run(
cmd,
@ -28,14 +65,35 @@ class ExecutionEngine:
timeout=30,
cwd=str(path.parent),
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode,
}
self.logger.log_stdout(result.stdout)
self.logger.log_stderr(result.stderr)
self.logger.log_result(result.returncode)
return self._result(result.stdout, result.stderr, result.returncode)
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:
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:
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

View File

@ -19,7 +19,7 @@ from backend.execution_engine import ExecutionEngine
st.set_page_config(
page_title="AI Code Editor",
page_icon="💻",
layout="wide",
layout="wide"
)
# ---------------- FOLDER PICKER ----------------
@ -38,7 +38,6 @@ def render_sidebar():
root_folder = st.session_state.get("root_folder")
with st.sidebar:
# --- Button on top ---
if st.button("📂 Choose Folder", key="pick_folder"):
picked = pick_folder()
if picked:
@ -50,7 +49,6 @@ def render_sidebar():
st.markdown("---")
# --- Tree below ---
if root_folder:
try:
root_path = Path(root_folder)
@ -73,6 +71,58 @@ def render_sidebar():
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 ----------------
def main():
file_manager = FileManager()
@ -103,7 +153,7 @@ def main():
# ---------------- FILE SWITCH ----------------
if current_file != st.session_state.active_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:
content = file_manager.read_file(current_file)
st.session_state.editor_content = content
@ -119,7 +169,6 @@ def main():
col1, col2 = st.columns([3, 2])
with col1:
# --- File name + Run button on same row ---
title_col, btn_col = st.columns([5, 1])
with title_col:
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)
st.success("Saved ✔")
# --- Run execution ---
if run_clicked:
with st.spinner("Running…"):
st.session_state.run_result = execution_engine.run_file(
@ -149,23 +197,10 @@ def main():
chat = ChatInterface()
chat.display_chat(chat_manager)
# --- Output panel ---
result = st.session_state.run_result
if result is not None:
if st.session_state.run_result is not None:
st.markdown("---")
success = result["returncode"] == 0
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.")
st.caption("Output")
render_output(st.session_state.run_result)
else:
st.info("Select a file from the explorer")