outsource to ui
This commit is contained in:
parent
f589e22e4e
commit
e3d2fe87b5
116
src/main.py
116
src/main.py
@ -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")
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
def display_execution(self, execution_engine):
|
status_icon = "✅" if success else "❌"
|
||||||
"""
|
st.markdown(f"**{status_icon} Exited {result['returncode']}**")
|
||||||
Zeigt die Ergebnisse der Code-Ausführung an [1]
|
|
||||||
"""
|
if result["stdout"].strip():
|
||||||
st.title("🔍 Execution Output")
|
st.caption("stdout")
|
||||||
|
st.code(result["stdout"].rstrip(), language="text")
|
||||||
# Ausgabe-Fenster erstellen
|
|
||||||
output_area = st.empty()
|
if result["stderr"].strip():
|
||||||
|
st.caption("stderr")
|
||||||
# Code Execution Button
|
st.code(result["stderr"].rstrip(), language="text")
|
||||||
if st.button("▶️ Code ausführen"):
|
|
||||||
with output_area:
|
if result.get("logs"):
|
||||||
with st.spinner("Code wird ausgeführt..."):
|
with st.expander("🪲 Debug log", expanded=False):
|
||||||
result = execution_engine.execute_code()
|
level_colors = {
|
||||||
output_area.code(result, language="python")
|
"INFO": "#6c7086", "OUTPUT": "#a6e3a1", "ERROR": "#f38ba8",
|
||||||
|
"SYNTAX": "#fab387", "EXCEPTION": "#f38ba8",
|
||||||
# Fehler-Logging anzeigen
|
"SUCCESS": "#a6e3a1", "FAIL": "#f38ba8",
|
||||||
if st.checkbox("Debug Logs anzeigen"):
|
}
|
||||||
st.subheader("🐛 Debug Logs")
|
icons = {
|
||||||
st.text_area("Logs", height=200)
|
"INFO": "ℹ", "OUTPUT": "▶", "ERROR": "✖",
|
||||||
|
"SYNTAX": "⚠", "EXCEPTION": "💥", "SUCCESS": "✔", "FAIL": "✖",
|
||||||
def display_error(self, error_message):
|
}
|
||||||
"""
|
lines = []
|
||||||
Zeigt Fehlermeldungen an
|
for e in result["logs"]:
|
||||||
"""
|
color = level_colors.get(e["level"], "#cdd6f4")
|
||||||
st.error(f"❌ Fehler: {error_message}")
|
icon = icons.get(e["level"], "•")
|
||||||
|
lines.append(
|
||||||
def display_warning(self, warning_message):
|
f'<span style="color:#585b70">[{e["time"]}]</span> '
|
||||||
"""
|
f'<span style="color:{color}">{icon} <b>{e["level"]}</b></span> '
|
||||||
Zeigt Warnungen an
|
f'<span style="color:#cdd6f4">{e["message"]}</span>'
|
||||||
"""
|
)
|
||||||
st.warning(f"⚠️ Warnung: {warning_message}")
|
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}")
|
||||||
Loading…
x
Reference in New Issue
Block a user