outsource to ui

This commit is contained in:
leart-ramushi 2026-05-26 17:14:46 +02:00
parent f589e22e4e
commit e3d2fe87b5
3 changed files with 122 additions and 151 deletions

View File

@ -1,10 +1,7 @@
import sys
import os
import tkinter as tk
from tkinter import filedialog
from pathlib import Path
import streamlit as st
from streamlit_tree_select import tree_select
# ---------------- PATH FIX ----------------
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# ---------------- BACKEND ----------------
@ -12,6 +9,7 @@ from backend.file_manager import FileManager
from ui.navigation import FileNavigation
from ui.chat import ChatInterface
from ui.editor import CodeEditor
from ui.output import OutputDisplay
from backend.chat_manager import ChatManager
from backend.execution_engine import ExecutionEngine
@ -22,112 +20,20 @@ st.set_page_config(
layout="wide"
)
# ---------------- FOLDER PICKER ----------------
def pick_folder():
root = tk.Tk()
root.withdraw()
root.wm_attributes("-topmost", 1)
folder = filedialog.askdirectory(title="Select project folder")
root.destroy()
return folder if folder else None
# ---------------- SIDEBAR ----------------
def render_sidebar():
st.sidebar.title("File Explorer")
root_folder = st.session_state.get("root_folder")
with st.sidebar:
if st.button("📂 Choose Folder", key="pick_folder"):
picked = pick_folder()
if picked:
st.session_state.root_folder = picked
st.rerun()
if root_folder:
st.caption(f" {Path(root_folder).name}")
st.markdown("---")
if root_folder:
try:
root_path = Path(root_folder)
if root_path.exists() and root_path.is_dir():
navigation = FileNavigation()
tree_data = navigation.build_tree(root_path)
selection = tree_select(
tree_data,
check_model="leaf",
only_leaf_checkboxes=True
)
if selection and selection.get("checked"):
return selection["checked"][0]
else:
st.warning("Folder not found.")
except Exception:
st.error("Could not load folder.")
else:
st.info("No folder selected yet.")
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.")
st.markdown("""
<style>
#MainMenu, header, footer { display: none !important; }
.main .block-container { padding-top: 1rem !important; }
</style>
""", unsafe_allow_html=True)
# ---------------- MAIN ----------------
def main():
file_manager = FileManager()
navigation = FileNavigation()
chat_manager = ChatManager()
execution_engine = ExecutionEngine()
output_display = OutputDisplay()
# ---------------- STATE ----------------
if "root_folder" not in st.session_state:
@ -144,7 +50,7 @@ def main():
st.session_state.run_result = None
# ---------------- FILE SELECT ----------------
selected_file = render_sidebar()
selected_file = navigation.render_sidebar()
if selected_file:
st.session_state.selected_file = selected_file
@ -200,7 +106,7 @@ def main():
if st.session_state.run_result is not None:
st.markdown("---")
st.caption("Output")
render_output(st.session_state.run_result)
output_display.render_output(st.session_state.run_result)
else:
st.info("Select a file from the explorer")

View File

@ -1,30 +1,45 @@
import tkinter as tk
from tkinter import filedialog
from pathlib import Path
import streamlit as st
from streamlit_tree_select import tree_select
class FileNavigation:
"""
Builds a VS Code-like file tree for streamlit_tree_select
and handles folder picking.
"""
def __init__(self):
self.supported_extensions = {'.py', '.js', '.html', '.css', '.json', '.md'}
def build_tree(self, path=None):
# ---------------- FOLDER PICKER ----------------
def pick_folder(self) -> str | None:
"""Open a native OS folder picker and return the selected path."""
root = tk.Tk()
root.withdraw()
root.wm_attributes("-topmost", 1)
folder = filedialog.askdirectory(title="Select project folder")
root.destroy()
return folder if folder else None
# ---------------- TREE BUILDER ----------------
def build_tree(self, path=None) -> list:
"""Build a VS Code-style tree structure for streamlit_tree_select."""
if path is None:
path = Path.cwd()
path = Path(path)
def build_node(current_path: Path):
def build_node(current_path: Path) -> dict:
is_dir = current_path.is_dir()
node = {
"label": ("📁 " if is_dir else "📄 ") + current_path.name,
"value": str(current_path),
}
if is_dir:
children = []
try:
for item in sorted(
current_path.iterdir(),
@ -34,10 +49,49 @@ class FileNavigation:
children.append(build_node(item))
except PermissionError:
pass
node["children"] = children
return node
# IMPORTANT: must return a LIST
return [build_node(path)]
return [build_node(path)]
# ---------------- SIDEBAR RENDERER ----------------
def render_sidebar(self) -> str | None:
"""
Renders the full sidebar: folder button, folder caption, divider, and tree.
Returns the selected file path or None.
"""
st.sidebar.title("File Explorer")
root_folder = st.session_state.get("root_folder")
with st.sidebar:
if st.button("📂 Choose Folder", key="pick_folder"):
picked = self.pick_folder()
if picked:
st.session_state.root_folder = picked
st.rerun()
if root_folder:
st.caption(f" {Path(root_folder).name}")
st.markdown("---")
if root_folder:
try:
root_path = Path(root_folder)
if root_path.exists() and root_path.is_dir():
tree_data = self.build_tree(root_path)
selection = tree_select(
tree_data,
check_model="leaf",
only_leaf_checkboxes=True
)
if selection and selection.get("checked"):
return selection["checked"][0]
else:
st.warning("Folder not found.")
except Exception:
st.error("Could not load folder.")
else:
st.info("No folder selected yet.")
return None

View File

@ -1,42 +1,53 @@
import streamlit as st
class OutputDisplay:
"""
Display für Execution Results und Debugging [1]
Renders structured execution results and debug logs.
"""
def __init__(self):
self.execution_output = []
def display_execution(self, execution_engine):
"""
Zeigt die Ergebnisse der Code-Ausführung an [1]
"""
st.title("🔍 Execution Output")
# Ausgabe-Fenster erstellen
output_area = st.empty()
# Code Execution Button
if st.button("▶️ Code ausführen"):
with output_area:
with st.spinner("Code wird ausgeführt..."):
result = execution_engine.execute_code()
output_area.code(result, language="python")
# Fehler-Logging anzeigen
if st.checkbox("Debug Logs anzeigen"):
st.subheader("🐛 Debug Logs")
st.text_area("Logs", height=200)
def display_error(self, error_message):
"""
Zeigt Fehlermeldungen an
"""
st.error(f"❌ Fehler: {error_message}")
def display_warning(self, warning_message):
"""
Zeigt Warnungen an
"""
st.warning(f"⚠️ Warnung: {warning_message}")
# ---------------- MAIN RENDERER ----------------
def render_output(self, result: dict):
success = result["returncode"] == 0
status_icon = "" if success else ""
st.markdown(f"**{status_icon} Exited {result['returncode']}**")
if result["stdout"].strip():
st.caption("stdout")
st.code(result["stdout"].rstrip(), language="text")
if result["stderr"].strip():
st.caption("stderr")
st.code(result["stderr"].rstrip(), language="text")
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.")
# ---------------- HELPERS ----------------
def display_error(self, error_message: str):
st.error(f"{error_message}")
def display_warning(self, warning_message: str):
st.warning(f"⚠️ {warning_message}")