From e3d2fe87b52c5e4e76c97f47c2ff165eeccbc0f7 Mon Sep 17 00:00:00 2001
From: leart-ramushi <113895250+leart-ramushi@users.noreply.github.com>
Date: Tue, 26 May 2026 17:14:46 +0200
Subject: [PATCH] outsource to ui
---
src/main.py | 116 ++++---------------------------------------
src/ui/navigation.py | 72 +++++++++++++++++++++++----
src/ui/output.py | 85 +++++++++++++++++--------------
3 files changed, 122 insertions(+), 151 deletions(-)
diff --git a/src/main.py b/src/main.py
index 65eb1bf..51df815 100644
--- a/src/main.py
+++ b/src/main.py
@@ -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'[{e["time"]}] '
- f'{icon} {e["level"]} '
- f'{e["message"]}'
- )
- st.markdown(
- "
".join(lines),
- unsafe_allow_html=True
- )
-
- if not result["stdout"].strip() and not result["stderr"].strip():
- st.caption("No output.")
+st.markdown("""
+
+""", 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")
diff --git a/src/ui/navigation.py b/src/ui/navigation.py
index 985c9ba..5722b65 100644
--- a/src/ui/navigation.py
+++ b/src/ui/navigation.py
@@ -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)]
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/src/ui/output.py b/src/ui/output.py
index b0e16df..b06b43d 100644
--- a/src/ui/output.py
+++ b/src/ui/output.py
@@ -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}")
\ No newline at end of file
+
+ # ---------------- 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'[{e["time"]}] '
+ f'{icon} {e["level"]} '
+ f'{e["message"]}'
+ )
+ st.markdown("
".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}")
\ No newline at end of file