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 sys
import os import os
import tkinter as tk
from tkinter import filedialog
from pathlib import Path from pathlib import Path
import streamlit as st import streamlit as st
from streamlit_tree_select import tree_select
# ---------------- PATH FIX ---------------- # ---------------- PATH FIX ----------------
sys.path.append(os.path.dirname(os.path.abspath(__file__))) sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# ---------------- BACKEND ---------------- # ---------------- BACKEND ----------------
@ -12,6 +9,7 @@ from backend.file_manager import FileManager
from ui.navigation import FileNavigation from ui.navigation import FileNavigation
from ui.chat import ChatInterface from ui.chat import ChatInterface
from ui.editor import CodeEditor from ui.editor import CodeEditor
from ui.output import OutputDisplay
from backend.chat_manager import ChatManager from backend.chat_manager import ChatManager
from backend.execution_engine import ExecutionEngine from backend.execution_engine import ExecutionEngine
@ -22,112 +20,20 @@ st.set_page_config(
layout="wide" layout="wide"
) )
# ---------------- FOLDER PICKER ---------------- st.markdown("""
def pick_folder(): <style>
root = tk.Tk() #MainMenu, header, footer { display: none !important; }
root.withdraw() .main .block-container { padding-top: 1rem !important; }
root.wm_attributes("-topmost", 1) </style>
folder = filedialog.askdirectory(title="Select project folder") """, unsafe_allow_html=True)
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.")
# ---------------- MAIN ---------------- # ---------------- MAIN ----------------
def main(): def main():
file_manager = FileManager() file_manager = FileManager()
navigation = FileNavigation()
chat_manager = ChatManager() chat_manager = ChatManager()
execution_engine = ExecutionEngine() execution_engine = ExecutionEngine()
output_display = OutputDisplay()
# ---------------- STATE ---------------- # ---------------- STATE ----------------
if "root_folder" not in st.session_state: if "root_folder" not in st.session_state:
@ -144,7 +50,7 @@ def main():
st.session_state.run_result = None st.session_state.run_result = None
# ---------------- FILE SELECT ---------------- # ---------------- FILE SELECT ----------------
selected_file = render_sidebar() selected_file = navigation.render_sidebar()
if selected_file: if selected_file:
st.session_state.selected_file = selected_file st.session_state.selected_file = selected_file
@ -200,7 +106,7 @@ def main():
if st.session_state.run_result is not None: if st.session_state.run_result is not None:
st.markdown("---") st.markdown("---")
st.caption("Output") st.caption("Output")
render_output(st.session_state.run_result) output_display.render_output(st.session_state.run_result)
else: else:
st.info("Select a file from the explorer") 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 from pathlib import Path
import streamlit as st
from streamlit_tree_select import tree_select
class FileNavigation: class FileNavigation:
""" """
Builds a VS Code-like file tree for streamlit_tree_select Builds a VS Code-like file tree for streamlit_tree_select
and handles folder picking.
""" """
def __init__(self): def __init__(self):
self.supported_extensions = {'.py', '.js', '.html', '.css', '.json', '.md'} 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: if path is None:
path = Path.cwd() path = Path.cwd()
path = Path(path) path = Path(path)
def build_node(current_path: Path): def build_node(current_path: Path) -> dict:
is_dir = current_path.is_dir() is_dir = current_path.is_dir()
node = { node = {
"label": ("📁 " if is_dir else "📄 ") + current_path.name, "label": ("📁 " if is_dir else "📄 ") + current_path.name,
"value": str(current_path), "value": str(current_path),
} }
if is_dir: if is_dir:
children = [] children = []
try: try:
for item in sorted( for item in sorted(
current_path.iterdir(), current_path.iterdir(),
@ -34,10 +49,49 @@ class FileNavigation:
children.append(build_node(item)) children.append(build_node(item))
except PermissionError: except PermissionError:
pass pass
node["children"] = children node["children"] = children
return node 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 import streamlit as st
class OutputDisplay: class OutputDisplay:
""" """
Display für Execution Results und Debugging [1] Renders structured execution results and debug logs.
""" """
def __init__(self): # ---------------- MAIN RENDERER ----------------
self.execution_output = [] def render_output(self, result: dict):
success = result["returncode"] == 0
status_icon = "" if success else ""
st.markdown(f"**{status_icon} Exited {result['returncode']}**")
def display_execution(self, execution_engine): if result["stdout"].strip():
""" st.caption("stdout")
Zeigt die Ergebnisse der Code-Ausführung an [1] st.code(result["stdout"].rstrip(), language="text")
"""
st.title("🔍 Execution Output")
# Ausgabe-Fenster erstellen if result["stderr"].strip():
output_area = st.empty() st.caption("stderr")
st.code(result["stderr"].rstrip(), language="text")
# Code Execution Button if result.get("logs"):
if st.button("▶️ Code ausführen"): with st.expander("🪲 Debug log", expanded=False):
with output_area: level_colors = {
with st.spinner("Code wird ausgeführt..."): "INFO": "#6c7086", "OUTPUT": "#a6e3a1", "ERROR": "#f38ba8",
result = execution_engine.execute_code() "SYNTAX": "#fab387", "EXCEPTION": "#f38ba8",
output_area.code(result, language="python") "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)
# Fehler-Logging anzeigen if not result["stdout"].strip() and not result["stderr"].strip():
if st.checkbox("Debug Logs anzeigen"): st.caption("No output.")
st.subheader("🐛 Debug Logs")
st.text_area("Logs", height=200)
def display_error(self, error_message): # ---------------- HELPERS ----------------
""" def display_error(self, error_message: str):
Zeigt Fehlermeldungen an st.error(f"{error_message}")
"""
st.error(f"❌ Fehler: {error_message}")
def display_warning(self, warning_message): def display_warning(self, warning_message: str):
""" st.warning(f"⚠️ {warning_message}")
Zeigt Warnungen an
"""
st.warning(f"⚠️ Warnung: {warning_message}")