From b7bbd63bb35eb3e8982db7c12dddf035408f8a72 Mon Sep 17 00:00:00 2001 From: Irina Rueegg Date: Thu, 9 Apr 2026 16:24:52 +0200 Subject: [PATCH 1/2] change UI, Filetree --- frontend/app.py | 22 ++--- frontend/editor.py | 191 +++++++++++++++++++++--------------------- frontend/sidebar.py | 197 ++++++++++++++++++++++---------------------- frontend/state.py | 8 +- 4 files changed, 213 insertions(+), 205 deletions(-) diff --git a/frontend/app.py b/frontend/app.py index bfba19c..bc1456b 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -22,19 +22,15 @@ def main(): init_state() - left, right = st.columns([1, 3]) - - with left: - choice = render_sidebar() + render_sidebar() + + show_editor = st.session_state.get("show_Editor", False) + show_chat = st.session_state.get("show_Chat", False) + + if show_editor: + render_editor() + if show_chat: + render_chat() - with right: - if "Chat with AI Assistant" in choice: - render_chat() - if "Code Editor" in choice: - render_editor() - #elif choice == "File Explorer": - # st.subheader("File Explorer") - # st.info("File Explorer functionality is not implemented yet.") - if __name__ == "__main__": main() diff --git a/frontend/editor.py b/frontend/editor.py index 79f9b98..f959a04 100644 --- a/frontend/editor.py +++ b/frontend/editor.py @@ -43,109 +43,114 @@ def render_editor(): if not st.session_state.open_files: st.info("Please select a file to edit.") return + else: + fm = FileManager() + + try: + active_index = st.session_state.open_files.index(st.session_state.active_file) + except (ValueError, KeyError): + active_index = 0 - fm = FileManager() + tab_names = [Path(f).name for f in st.session_state.open_files] + tabs = st.tabs(tab_names) + + for idx, file_path in enumerate(st.session_state.open_files): + with tabs[idx]: - tab_names = [Path(f).name for f in st.session_state.open_files] - tabs = st.tabs(tab_names) + if file_path not in st.session_state.files_content: + st.session_state.files_content[file_path] = fm.read_file(Path(file_path)) + + file_language = LANG_MAP.get(Path(file_path).suffix, "text") + + code = st_ace.st_ace( + value=st.session_state.files_content[file_path], + language=file_language, + theme="monokai", + key=f"code_editor_{file_path}", + auto_update=True, + height=400, + # tab_size=4, + # font_size=14, + # show_gutter=True, + # show_print_margin=False, + # wrap=True + ) - for idx, file_path in enumerate(st.session_state.open_files): - with tabs[idx]: - st.session_state.active_file = file_path - - if file_path not in st.session_state.files_content: - st.session_state.files_content[file_path] = fm.read_file(Path(file_path)) - - file_language = LANG_MAP.get(Path(file_path).suffix, "text") + if code != st.session_state.files_content[file_path]: + st.session_state.files_content[file_path] = code + + cols = st.columns([1, 1, 1, 1]) + with cols[0]: + if st.button("Save Changes", key=f"save_{file_path}"): + content = st.session_state.files_content[file_path] + if fm.save_file(file_path, content): + st.success("File saved successfully!") - code = st_ace.st_ace( - value=st.session_state.files_content[file_path], - language=file_language, - theme="monokai", - key=f"code_editor_{file_path}", - auto_update=True, - height=400, - tab_size=4, - font_size=14, - show_gutter=True, - show_print_margin=False, - wrap=True - ) - - st.session_state.files_content[file_path] = code - - cols = st.columns([1, 1, 1, 1]) - with cols[0]: - if st.button("Save Changes", key=f"save_{file_path}"): - content = st.session_state.files_content[file_path] - if fm.save_file(file_path, content): - st.success("File saved successfully!") + with cols[1]: + if st.button("Close File", key=f"close_{file_path}"): + st.session_state.open_files.remove(file_path) + del st.session_state.files_content[file_path] + st.success("File closed successfully!") - with cols[1]: - if st.button("Close File", key=f"close_{file_path}"): - st.session_state.open_files.remove(file_path) - del st.session_state.files_content[file_path] - st.success("File closed successfully!") - - if st.session_state.active_file == file_path: - st.session_state.active_file = ( - st.session_state.open_files[0] - if st.session_state.open_files - else None - ) - - st.rerun() # Refresh the page to update the UI - with cols[2]: - if st.button("Rename File", key=f"rename_{file_path}"): - new_name = st.text_input("New File Name", key=f"new_name_{file_path}") - if "/" in new_name or "\\" in new_name: - st.warning("Do not include slashes in names!") - elif new_name: + if st.session_state.active_file == file_path: + st.session_state.active_file = ( + st.session_state.open_files[0] + if st.session_state.open_files + else None + ) + + st.rerun() # Refresh the page to update the UI + with cols[2]: + if st.button("Rename File", key=f"rename_{file_path}"): + new_name = st.text_input("New File Name", key=f"new_name_{file_path}") + if "/" in new_name or "\\" in new_name: + st.warning("Do not include slashes in names!") + elif new_name: + try: + fm.rename_file(file_path, new_name) + st.success(f"File renamed to '{new_name}' successfully!") + st.rerun() + except Exception as e: + st.error(f"Error renaming file: {e}") + + with cols[3]: + if st.button("Delete File", key=f"delete_{file_path}"): try: - fm.rename_file(file_path, new_name) - st.success(f"File renamed to '{new_name}' successfully!") + fm.delete_file(file_path) + st.success(f"File '{Path(file_path).name}' deleted successfully!") st.rerun() except Exception as e: - st.error(f"Error renaming file: {e}") - - with cols[3]: - if st.button("Delete File", key=f"delete_{file_path}"): - try: - fm.delete_file(file_path) - st.success(f"File '{Path(file_path).name}' deleted successfully!") - st.rerun() - except Exception as e: - st.error(f"Error deleting file: {e}") - - if st.button("▶ Run Code", key="run_code"): - result = run_active_file() - if not result: - st.stop() - - st.subheader("Execution Output") - - if result["return_code"] == 0: - st.success(f"Exit code: {result['return_code']}") - else: - st.error(f"Exit code: {result['return_code']}") + st.error(f"Error deleting file: {e}") - if result["stdout"]: - st.text_area( - "Standard Output", - value=result["stdout"], - height=200, - disabled=True, - key="run_stdout") + if st.button("▶ Run Code", key="run_code"): + result = run_active_file() + if not result: + st.stop() + + st.subheader("Execution Output") + + if result["return_code"] == 0: + st.success(f"Exit code: {result['return_code']}") + else: + st.error(f"Exit code: {result['return_code']}") - if result["stderr"]: - st.text_area( - "Standard Error", - value=result["stderr"], - height=200, - disabled=True, - key="run_stderr") - if not result["stdout"] and not result["stderr"]: - st.info("No output produced by the code execution.") + if result["stdout"]: + st.text_area( + "Standard Output", + value=result["stdout"], + height=200, + disabled=True, + key="run_stdout") + + if result["stderr"]: + st.text_area( + "Standard Error", + value=result["stderr"], + height=200, + disabled=True, + key="run_stderr") + if not result["stdout"] and not result["stderr"]: + st.info("No output produced by the code execution.") diff --git a/frontend/sidebar.py b/frontend/sidebar.py index f0720d3..b5482e9 100644 --- a/frontend/sidebar.py +++ b/frontend/sidebar.py @@ -1,4 +1,5 @@ import streamlit as st +from streamlit_arborist import tree_view from pathlib import Path from backend.managers.file_manager import FileManager @@ -23,98 +24,45 @@ SUFFIX_MAP = { "default": "📄" # Unbekannt } -def render_filetree(tree, parent_path=Path()): +def build_arborist_tree(tree, parent_path=Path()): + nodes = [] + for name, content in sorted(tree.items()): full_path = parent_path / name + node_id = str(full_path.as_posix()) - if isinstance(content, dict): # Directory - disp_name = name if len(name) <= 13 else (name[:10] + "...") - - with st.expander(f"📁 {disp_name}", expanded=False): - render_filetree(content, full_path) - with st.popover("+", key=f"popover_{full_path}"): - st.write(f"**Create in {name}**") - action = st.radio("Action", ["New File", "New Folder"], key=f"radio_{full_path}", label_visibility="collapsed") - new_name = st.text_input("Name", key=f"input_{full_path}") - - if st.button("Create", key=f"btn_{full_path}"): - if "/" in new_name or "\\" in new_name: - st.warning("Do not include slashes in names!") - elif new_name and action == "New Folder": - fm.create_folder(str(full_path), new_name) - st.success(f"Folder {new_name} created!") - st.rerun() - elif new_name and action == "New File": - fm.create_file(str(full_path), new_name) - st.success(f"File {new_name} created!") - st.rerun() - - else: # File - suffix = Path(name).suffix - icon = SUFFIX_MAP.get(suffix, SUFFIX_MAP["default"]) - disp_name = name if len(name) <= 13 else (name[:10] + "...") + if isinstance(content, dict): + nodes.append({ + "id": node_id, + "name": f"{name}", + "children": build_arborist_tree(content, full_path) + }) + else: + suffix = Path(name).suffix + icon = SUFFIX_MAP.get(suffix, SUFFIX_MAP["default"]) - col_file, col_opt = st.columns([0.85, 0.15], gap="small") + nodes.append({ + "id": node_id, + "name": f"{icon} {name}", + "key": f"key_{node_id}" + }) - with col_file: - if st.button(f"{icon} {disp_name}", key=str(full_path)): - abs_path = fm.base_path / full_path - - if str(abs_path) not in st.session_state.open_files: - st.session_state.open_files.append(str(abs_path)) - - st.session_state.active_file = str(abs_path) - st.rerun() - - with col_opt: - with st.popover("⋮", key=f"opt_{full_path}"): + return nodes - delete_key = f"confirm_delete_{full_path}" - if delete_key not in st.session_state: - st.session_state[delete_key] = False +def render_filetree_arborist(tree): + data = build_arborist_tree(tree) - if st.button(" **Delete** 🗑️", key=f"del_{full_path}"): - st.session_state[delete_key] = True - - if st.session_state[delete_key]: - st.warning(f"Delete {name}?") + selected = tree_view( + data=data, + icons={"open": "📂", "closed": "📁"}, + height=200, + selection=None, + select_internal_nodes=True, + open_by_default=False + ) - confirm_col, deny_col = st.columns([1, 1], gap="small") - with confirm_col: - if st.button("✔", key=f"yes_{full_path}"): - try: - fm.delete_file(str(full_path)) - st.session_state[delete_key] = False - st.rerun() - except Exception as e: - st.error(f"Error deleting file: {e}") - with deny_col: - if st.button("✖", key=f"cancel_{full_path}"): - st.session_state[delete_key] = False - - rename_key = f"rename_mode_{full_path}" - if rename_key not in st.session_state: - st.session_state[rename_key] = False - - if st.button("**Rename** ✏️", key=f"ren_{full_path}"): - st.session_state[rename_key] = True - - if st.session_state[rename_key]: - new_name = st.text_input("New Name", key=f"input_ren_{full_path}") - - apply_col, cancel_col = st.columns([1, 1], gap="small") - with apply_col: - if st.button("Apply", key=f"apply_ren_{full_path}"): - try: - fm.rename_file(str(full_path), new_name) - st.session_state[rename_key] = False - st.rerun() - except Exception as e: - st.error(f"Error renaming file: {e}") + return selected - with cancel_col: - if st.button("Cancel", key=f"cancel_ren_{full_path}"): - st.session_state[rename_key] = False def render_sidebar(): st.sidebar.title("Navigation") @@ -127,14 +75,72 @@ def render_sidebar(): tree = fm.get_file_tree() workspace = st.container() add_more = st.container() - - with workspace: - if not tree: - st.info("Workspace is empty.") - - else: - render_filetree(tree) + with workspace: + with st.container(border=False): + if not tree: + st.info("Workspace is empty.") + else: + selected = render_filetree_arborist(tree) + + if selected: + selected_path = selected.get("id") + + if st.session_state.last_selected != selected_path: + st.session_state.last_selected = selected_path + abs_path = fm.base_path / selected_path + + if abs_path.is_file(): + if str(abs_path) not in st.session_state.open_files: + st.session_state.open_files.append(str(abs_path)) + + st.session_state.active_file = str(abs_path) + st.rerun() + + elif abs_path.is_dir(): + st.session_state.selected_folder = str(abs_path) + selected_folder = st.session_state.selected_folder + with st.popover( + f"📁 {Path(selected_folder).name}", + use_container_width=True, + key=f"{selected_folder}_popover_options"): + action = st.radio( + "Action", + ["New File", "New Folder"], + key=f"radio_folder_options", + label_visibility="collapsed") + + new_name = st.text_input("Name", key=f"input_folder_options") + + btn_cols = st.columns([1,1,1]) + with btn_cols[0]: + if st.button("Create", key=f"btn_create_subfile_or_subfolder"): + if "/" in new_name or "\\" in new_name: + st.warning("Do not include slashes in names!") + elif new_name: + if action == "New Folder": + fm.create_folder(selected_path, new_name) + else: + fm.create_file(selected_path, new_name) + st.success(f"{action} created!") + st.session_state.selected_folder = None + st.rerun() + + with btn_cols[1]: + if st.button("Cancel", key=f"cancel_folder_options"): + st.session_state.selected_folder = None + st.rerun() + + with btn_cols[2]: + if st.button("Delete Folder", key=f"delete_folder"): + try: + fm.delete_folder(selected_path) + st.success(f"Folder '{Path(selected_folder).name}' deleted successfully!") + st.session_state.selected_folder = None + st.rerun() + except Exception as e: + st.error(f"Error deleting folder: {e}") + with add_more: with st.popover("⚙️ Explorer_Options", key=f"popover_options"): action = st.radio( @@ -160,13 +166,10 @@ def render_sidebar(): with navigation_section: st.sidebar.subheader("Options") - options = ["Code Editor", "Chat with AI Assistant"] - choice = [] - - for option in options: - if st.sidebar.checkbox(option): - choice.append(option) - return choice + st.sidebar.checkbox("Code Editor", key="show_Editor") + st.sidebar.checkbox("Chat with AI Assistant", key="show_Chat") + + return if __name__ == "__main__": render_sidebar() diff --git a/frontend/state.py b/frontend/state.py index 4090932..ffa7b41 100644 --- a/frontend/state.py +++ b/frontend/state.py @@ -1,8 +1,12 @@ import streamlit as st -def init_state(): - # Sidebar state initialization +def init_state(): + # Sidebar + if "last_selected" not in st.session_state: + st.session_state.last_selected = None + if "selected_folder" not in st.session_state: + st.session_state.selected_folder = None # Editor state initialization if "open_files" not in st.session_state: From 6e29be75d4c257e52a57a87107ea593681a702bf Mon Sep 17 00:00:00 2001 From: Livio Meuli Date: Thu, 9 Apr 2026 16:44:59 +0200 Subject: [PATCH 2/2] Fix Ai Chat --- frontend/state.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/state.py b/frontend/state.py index 9614ffa..a45c7fa 100644 --- a/frontend/state.py +++ b/frontend/state.py @@ -3,37 +3,35 @@ from backend.managers.chat_manager import ChatManager def init_state(): # Sidebar state initialization + if "last_selected" not in st.session_state: + st.session_state.last_selected = None + + if "selected_folder" not in st.session_state: + st.session_state.selected_folder = None # Chat manager (persists across reruns) if "chat_manager" not in st.session_state: st.session_state.chat_manager = ChatManager() -def init_state(): - # Sidebar - if "last_selected" not in st.session_state: - st.session_state.last_selected = None - - if "selected_folder" not in st.session_state: - st.session_state.selected_folder = None # Editor state initialization if "open_files" not in st.session_state: st.session_state.open_files = [] - + if "files_content" not in st.session_state: st.session_state.files_content = {} - + if "active_file" not in st.session_state: st.session_state.active_file = None - + if "is_editing" not in st.session_state: st.session_state.is_editing = False - + if "code_suggestions" not in st.session_state: st.session_state.code_suggestions = [] if "code_execution_output" not in st.session_state: st.session_state.code_execution_output = "" - + # Chat state initialization if "chat_history" not in st.session_state: st.session_state.chat_history = []