refactor(gui2): Restructure layout into discrete Hubs

Automates the refactoring of the monolithic _gui_func in gui_2.py into separate rendering methods, nested within 'Context Hub', 'AI Settings Hub', 'Discussion Hub', and 'Operations Hub', utilizing tab bars. Adds tests to ensure the new default windows correctly represent this Hub structure.
This commit is contained in:
2026-02-23 22:15:13 -05:00
parent c6a756e754
commit ddb53b250f
3 changed files with 1083 additions and 780 deletions

1574
gui_2.py

File diff suppressed because it is too large Load Diff

243
refactor_gui2.py Normal file
View File

@@ -0,0 +1,243 @@
import re
import sys
def main():
with open("gui_2.py", "r", encoding="utf-8") as f:
content = f.read()
# 1. Update _default_windows dictionary
old_default = """ _default_windows = {
"Projects": True,
"Files": True,
"Screenshots": True,
"Discussion History": True,
"Provider": True,
"Message": True,
"Response": True,
"Tool Calls": True,
"Comms History": True,
"System Prompts": True,
"Theme": True,
"Diagnostics": False,
}"""
new_default = """ _default_windows = {
"Context Hub": True,
"AI Settings Hub": True,
"Discussion Hub": True,
"Operations Hub": True,
"Diagnostics": False,
}"""
if old_default in content:
content = content.replace(old_default, new_default)
else:
print("Could not find _default_windows block")
# 2. Extract panels into methods
panels = {
"Projects": "_render_projects_panel",
"Files": "_render_files_panel",
"Screenshots": "_render_screenshots_panel",
"Discussion History": "_render_discussion_panel",
"Provider": "_render_provider_panel",
"Message": "_render_message_panel",
"Response": "_render_response_panel",
"Tool Calls": "_render_tool_calls_panel",
"Comms History": "_render_comms_history_panel",
"System Prompts": "_render_system_prompts_panel",
"Theme": "_render_theme_panel",
}
methods = []
# We will search for:
# # ---- PanelName
# if self.show_windows["PanelName"]:
# ... (until imgui.end())
for panel_name, method_name in panels.items():
# Build a regex to match the entire panel block
# We need to capture from the comment to the corresponding imgui.end()
# This requires matching balanced indentation or looking for specific end tokens.
# Since each block ends with ` imgui.end()\n`, we can use that.
# But wait, some panels like 'Response' might have different structures.
# A simpler way: split the file by `# ---- ` comments.
pass
# Actually, the safest way is to replace the whole `_gui_func` body from `# ---- Projects` down to just before `# ---- Diagnostics`.
start_marker = " # ---- Projects"
end_marker = " # ---- Diagnostics"
start_idx = content.find(start_marker)
end_idx = content.find(end_marker)
if start_idx == -1 or end_idx == -1:
print("Markers not found!")
sys.exit(1)
panels_text = content[start_idx:end_idx]
# Now split panels_text by `# ---- `
panel_chunks = panels_text.split(" # ---- ")
methods_code = ""
for chunk in panel_chunks:
if not chunk.strip(): continue
# Find the panel name (first line)
lines = chunk.split('\n')
name = lines[0].strip()
if name not in panels:
continue
method_name = panels[name]
# The rest of the lines are the panel logic.
# We need to remove the `if self.show_windows["..."]:` check and the `imgui.begin()`/`imgui.end()` calls.
# But wait! For ImGui, when we move them to child tabs, we DON'T want `imgui.begin` and `imgui.end`.
# We just want the contents inside `if exp:`
# This is critical! A tab item acts as the container.
# Let's extract everything inside `if exp:` or just before `imgui.begin()`.
# Find the line with `imgui.begin(`
begin_line_idx = -1
end_line_idx = -1
for i, line in enumerate(lines):
if "imgui.begin(" in line:
begin_line_idx = i
elif "imgui.end()" in line:
end_line_idx = i
if begin_line_idx == -1 or end_line_idx == -1:
print(f"Could not parse begin/end for {name}")
continue
# Lines before begin (e.g. blinking logic in Response)
pre_begin_lines = lines[2:begin_line_idx] # skipping `if self.show_windows...:`
# Lines between `if exp:` and `imgui.end()`
# Usually it's ` if exp:\n ...`
# We need to check if line after begin is `if exp:`
exp_check_idx = begin_line_idx + 1
content_lines = []
if "if exp:" in lines[exp_check_idx] or "if expanded:" in lines[exp_check_idx]:
content_lines = lines[exp_check_idx+1:end_line_idx]
else:
content_lines = lines[begin_line_idx+1:end_line_idx]
# Post end lines (e.g. pop_style_color in Response)
# Wait, the pop_style_color is BEFORE imgui.end()
# So it's already in content_lines.
# Reconstruct the method body
method_body = []
for line in pre_begin_lines:
# unindent by 12 spaces (was under `if self.show_windows...`)
if line.startswith(" "):
method_body.append(line[12:])
else:
method_body.append(line)
for line in content_lines:
# unindent by 16 spaces (was under `if exp:`)
if line.startswith(" "):
method_body.append(line[8:])
elif line.startswith(" "):
# like pop_style_color which is under `if show_windows`
method_body.append(line[12:])
else:
method_body.append(line)
methods_code += f" def {method_name}(self):\n"
for line in method_body:
methods_code += f" {line}\n"
methods_code += "\n"
# Hub rendering code
hub_code = """
# ---- Context Hub
if self.show_windows.get("Context Hub", False):
exp, self.show_windows["Context Hub"] = imgui.begin("Context Hub", self.show_windows["Context Hub"])
if exp:
if imgui.begin_tab_bar("ContextTabs"):
if imgui.begin_tab_item("Projects")[0]:
self._render_projects_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Files")[0]:
self._render_files_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Screenshots")[0]:
self._render_screenshots_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
# ---- AI Settings Hub
if self.show_windows.get("AI Settings Hub", False):
exp, self.show_windows["AI Settings Hub"] = imgui.begin("AI Settings Hub", self.show_windows["AI Settings Hub"])
if exp:
if imgui.begin_tab_bar("AISettingsTabs"):
if imgui.begin_tab_item("Provider")[0]:
self._render_provider_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("System Prompts")[0]:
self._render_system_prompts_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Theme")[0]:
self._render_theme_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
# ---- Discussion Hub
if self.show_windows.get("Discussion Hub", False):
exp, self.show_windows["Discussion Hub"] = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
if exp:
if imgui.begin_tab_bar("DiscussionTabs"):
if imgui.begin_tab_item("History")[0]:
self._render_discussion_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
# ---- Operations Hub
if self.show_windows.get("Operations Hub", False):
exp, self.show_windows["Operations Hub"] = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
if exp:
if imgui.begin_tab_bar("OperationsTabs"):
if imgui.begin_tab_item("Message")[0]:
self._render_message_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Response")[0]:
self._render_response_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Tool Calls")[0]:
self._render_tool_calls_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Comms History")[0]:
self._render_comms_history_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
"""
# Replace panels_text with hub_code
content = content[:start_idx] + hub_code + content[end_idx:]
# Append methods_code to the end of the App class
# We find the end of the class by looking for `def _load_fonts(self):`
# and inserting methods_code right before it.
fonts_idx = content.find(" def _load_fonts(self):")
content = content[:fonts_idx] + methods_code + content[fonts_idx:]
with open("gui_2.py", "w", encoding="utf-8") as f:
f.write(content)
print("Refactoring complete.")
if __name__ == "__main__":
main()

46
tests/test_gui2_layout.py Normal file
View File

@@ -0,0 +1,46 @@
import pytest
from unittest.mock import patch
from gui_2 import App
@pytest.fixture
def app_instance():
with (
patch('gui_2.load_config', return_value={'gui': {'show_windows': {}}}),
patch('gui_2.save_config'),
patch('gui_2.project_manager'),
patch('gui_2.session_logger'),
patch('gui_2.immapp.run'),
patch.object(App, '_load_active_project'),
patch.object(App, '_fetch_models'),
patch.object(App, '_load_fonts'),
patch.object(App, '_post_init')
):
yield App()
def test_gui2_hubs_exist_in_show_windows(app_instance):
"""
Verifies that the new consolidated Hub windows are defined in the App's show_windows.
This ensures they will be available in the 'Windows' menu.
"""
expected_hubs = [
"Context Hub",
"AI Settings Hub",
"Discussion Hub",
"Operations Hub",
]
for hub in expected_hubs:
assert hub in app_instance.show_windows, f"Expected hub window '{hub}' not found in show_windows"
def test_gui2_old_windows_removed_from_show_windows(app_instance):
"""
Verifies that the old fragmented windows are removed from show_windows.
"""
old_windows = [
"Projects", "Files", "Screenshots",
"Provider", "System Prompts",
"Message", "Response", "Tool Calls", "Comms History"
]
for old_win in old_windows:
assert old_win not in app_instance.show_windows, f"Old window '{old_win}' should have been removed from show_windows"