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

470
gui_2.py
View File

@@ -158,17 +158,10 @@ class App:
self.models_thread: threading.Thread | None = None
_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,
"Context Hub": True,
"AI Settings Hub": True,
"Discussion Hub": True,
"Operations Hub": True,
"Diagnostics": False,
}
saved = self.config.get("gui", {}).get("show_windows", {})
@@ -689,10 +682,235 @@ class App:
# imgui.end_menu()
# imgui.end_main_menu_bar()
# ---- Projects
if self.show_windows["Projects"]:
exp, self.show_windows["Projects"] = imgui.begin("Projects", self.show_windows["Projects"])
# ---- 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()
# ---- Diagnostics
if self.show_windows["Diagnostics"]:
exp, self.show_windows["Diagnostics"] = imgui.begin("Diagnostics", self.show_windows["Diagnostics"])
if exp:
now = time.time()
if now - self._perf_last_update >= 0.5:
self._perf_last_update = now
metrics = self.perf_monitor.get_metrics()
self.perf_history["frame_time"].pop(0)
self.perf_history["frame_time"].append(metrics.get("last_frame_time_ms", 0.0))
self.perf_history["fps"].pop(0)
self.perf_history["fps"].append(metrics.get("fps", 0.0))
self.perf_history["cpu"].pop(0)
self.perf_history["cpu"].append(metrics.get("cpu_percent", 0.0))
self.perf_history["input_lag"].pop(0)
self.perf_history["input_lag"].append(metrics.get("input_lag_ms", 0.0))
metrics = self.perf_monitor.get_metrics()
imgui.text("Performance Telemetry")
imgui.separator()
if imgui.begin_table("perf_table", 2, imgui.TableFlags_.borders_inner_h):
imgui.table_setup_column("Metric")
imgui.table_setup_column("Value")
imgui.table_headers_row()
imgui.table_next_row()
imgui.table_next_column()
imgui.text("FPS")
imgui.table_next_column()
imgui.text(f"{metrics.get('fps', 0.0):.1f}")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Frame Time (ms)")
imgui.table_next_column()
imgui.text(f"{metrics.get('last_frame_time_ms', 0.0):.2f}")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("CPU %")
imgui.table_next_column()
imgui.text(f"{metrics.get('cpu_percent', 0.0):.1f}")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Input Lag (ms)")
imgui.table_next_column()
imgui.text(f"{metrics.get('input_lag_ms', 0.0):.1f}")
imgui.end_table()
imgui.separator()
imgui.text("Frame Time (ms)")
imgui.plot_lines("##ft_plot", np.array(self.perf_history["frame_time"], dtype=np.float32), overlay_text="frame_time", graph_size=imgui.ImVec2(-1, 60))
imgui.text("CPU %")
imgui.plot_lines("##cpu_plot", np.array(self.perf_history["cpu"], dtype=np.float32), overlay_text="cpu", graph_size=imgui.ImVec2(-1, 60))
imgui.end()
self.perf_monitor.end_frame()
# ---- Modals / Popups
with self._pending_dialog_lock:
dlg = self._pending_dialog
if dlg:
if not self._pending_dialog_open:
imgui.open_popup("Approve PowerShell Command")
self._pending_dialog_open = True
else:
self._pending_dialog_open = False
if imgui.begin_popup_modal("Approve PowerShell Command", None, imgui.WindowFlags_.always_auto_resize)[0]:
if dlg:
imgui.text("The AI wants to run the following PowerShell script:")
imgui.text_colored(vec4(200, 200, 100), f"base_dir: {dlg._base_dir}")
imgui.separator()
if imgui.button("[+ Maximize]##confirm"):
self.show_text_viewer = True
self.text_viewer_title = "Confirm Script"
self.text_viewer_content = dlg._script
ch, dlg._script = imgui.input_text_multiline("##confirm_script", dlg._script, imgui.ImVec2(-1, 300))
imgui.separator()
if imgui.button("Approve & Run", imgui.ImVec2(120, 0)):
dlg._approved = True
dlg._event.set()
with self._pending_dialog_lock:
self._pending_dialog = None
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Reject", imgui.ImVec2(120, 0)):
dlg._approved = False
dlg._event.set()
with self._pending_dialog_lock:
self._pending_dialog = None
imgui.close_current_popup()
imgui.end_popup()
if self.show_script_output:
if self._trigger_script_blink:
self._trigger_script_blink = False
self._is_script_blinking = True
self._script_blink_start_time = time.time()
imgui.set_window_focus_str("Last Script Output")
if self._is_script_blinking:
elapsed = time.time() - self._script_blink_start_time
if elapsed > 1.5:
self._is_script_blinking = False
else:
val = math.sin(elapsed * 8 * math.pi)
alpha = 60/255 if val > 0 else 0
imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 100, 255, alpha))
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 100, 255, alpha))
imgui.set_next_window_size(imgui.ImVec2(800, 600), imgui.Cond_.first_use_ever)
expanded, self.show_script_output = imgui.begin("Last Script Output", self.show_script_output)
if expanded:
imgui.text("Script:")
imgui.same_line()
self._render_text_viewer("Last Script", self.ui_last_script_text)
if self.ui_word_wrap:
imgui.begin_child("lso_s_wrap", imgui.ImVec2(-1, 200), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.ui_last_script_text)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##lso_s", self.ui_last_script_text, imgui.ImVec2(-1, 200), imgui.InputTextFlags_.read_only)
imgui.separator()
imgui.text("Output:")
imgui.same_line()
self._render_text_viewer("Last Output", self.ui_last_script_output)
if self.ui_word_wrap:
imgui.begin_child("lso_o_wrap", imgui.ImVec2(-1, -1), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.ui_last_script_output)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##lso_o", self.ui_last_script_output, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
if self._is_script_blinking:
imgui.pop_style_color(2)
imgui.end()
if self.show_text_viewer:
imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
expanded, self.show_text_viewer = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
if expanded:
if self.ui_word_wrap:
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.text_viewer_content)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##tv_c", self.text_viewer_content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
imgui.end()
def _render_projects_panel(self):
proj_name = self.project.get("project", {}).get("name", Path(self.active_project_path).stem)
imgui.text_colored(C_IN, f"Active: {proj_name}")
imgui.separator()
@@ -783,12 +1001,8 @@ class App:
ch, val = imgui.checkbox(f"Enable {t_name}", val)
if ch:
self.ui_agent_tools[t_name] = val
imgui.end()
# ---- Files
if self.show_windows["Files"]:
exp, self.show_windows["Files"] = imgui.begin("Files", self.show_windows["Files"])
if exp:
def _render_files_panel(self):
imgui.text("Base Dir")
ch, self.ui_files_base_dir = imgui.input_text("##f_base", self.ui_files_base_dir)
imgui.same_line()
@@ -821,12 +1035,8 @@ class App:
d = filedialog.askdirectory()
r.destroy()
if d: self.files.append(str(Path(d) / "**" / "*"))
imgui.end()
# ---- Screenshots
if self.show_windows["Screenshots"]:
exp, self.show_windows["Screenshots"] = imgui.begin("Screenshots", self.show_windows["Screenshots"])
if exp:
def _render_screenshots_panel(self):
imgui.text("Base Dir")
ch, self.ui_shots_base_dir = imgui.input_text("##s_base", self.ui_shots_base_dir)
imgui.same_line()
@@ -856,12 +1066,8 @@ class App:
r.destroy()
for p in paths:
if p not in self.screenshots: self.screenshots.append(p)
imgui.end()
# ---- Discussion History
if self.show_windows["Discussion History"]:
exp, self.show_windows["Discussion History"] = imgui.begin("Discussion History", self.show_windows["Discussion History"])
if exp:
def _render_discussion_panel(self):
# THINKING indicator
is_thinking = self.ai_status in ["sending..."]
if is_thinking:
@@ -1067,12 +1273,8 @@ class App:
imgui.set_scroll_here_y(1.0)
self._scroll_disc_to_bottom = False
imgui.end_child()
imgui.end()
# ---- Provider
if self.show_windows["Provider"]:
exp, self.show_windows["Provider"] = imgui.begin("Provider", self.show_windows["Provider"])
if exp:
def _render_provider_panel(self):
imgui.text("Provider")
if imgui.begin_combo("##prov", self.current_provider):
for p in PROVIDERS:
@@ -1113,12 +1315,8 @@ class App:
imgui.progress_bar(self._token_budget_pct, imgui.ImVec2(-1, 0), f"{self._token_budget_current:,} / {self._token_budget_limit:,}")
if self._gemini_cache_text:
imgui.text_colored(C_SUB, self._gemini_cache_text)
imgui.end()
# ---- Message
if self.show_windows["Message"]:
exp, self.show_windows["Message"] = imgui.begin("Message", self.show_windows["Message"])
if exp:
def _render_message_panel(self):
# LIVE indicator
is_live = self.ai_status in ["running powershell...", "fetching url...", "searching web...", "powershell done, awaiting AI..."]
if is_live:
@@ -1212,10 +1410,8 @@ class App:
if imgui.button("-> History"):
if self.ui_ai_input:
self.disc_entries.append({"role": "User", "content": self.ui_ai_input, "collapsed": False, "ts": project_manager.now_ts()})
imgui.end()
# ---- Response
if self.show_windows["Response"]:
def _render_response_panel(self):
if self._trigger_blink:
self._trigger_blink = False
@@ -1233,8 +1429,6 @@ class App:
imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 255, 0, alpha))
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 255, 0, alpha))
exp, self.show_windows["Response"] = imgui.begin("Response", self.show_windows["Response"])
if exp:
if self.ui_word_wrap:
imgui.begin_child("resp_wrap", imgui.ImVec2(-1, -40), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
@@ -1250,12 +1444,8 @@ class App:
if self._is_blinking:
imgui.pop_style_color(2)
imgui.end()
# ---- Tool Calls
if self.show_windows["Tool Calls"]:
exp, self.show_windows["Tool Calls"] = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
if exp:
def _render_tool_calls_panel(self):
imgui.text("Tool call history")
imgui.same_line()
if imgui.button("Clear##tc"):
@@ -1280,12 +1470,8 @@ class App:
imgui.input_text_multiline(f"##tc_res_{i}", result, imgui.ImVec2(-1, 72), imgui.InputTextFlags_.read_only)
imgui.separator()
imgui.end_child()
imgui.end()
# ---- Comms History
if self.show_windows["Comms History"]:
exp, self.show_windows["Comms History"] = imgui.begin("Comms History", self.show_windows["Comms History"])
if exp:
def _render_comms_history_panel(self):
imgui.text_colored(vec4(200, 220, 160), f"Status: {self.ai_status}")
imgui.same_line()
if imgui.button("Clear##comms"):
@@ -1414,23 +1600,15 @@ class App:
imgui.separator()
imgui.pop_id()
imgui.end_child()
imgui.end()
# ---- System Prompts
if self.show_windows["System Prompts"]:
exp, self.show_windows["System Prompts"] = imgui.begin("System Prompts", self.show_windows["System Prompts"])
if exp:
def _render_system_prompts_panel(self):
imgui.text("Global System Prompt (all projects)")
ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100))
imgui.separator()
imgui.text("Project System Prompt")
ch, self.ui_project_system_prompt = imgui.input_text_multiline("##psp", self.ui_project_system_prompt, imgui.ImVec2(-1, 100))
imgui.end()
# ---- Theme
if self.show_windows["Theme"]:
exp, self.show_windows["Theme"] = imgui.begin("Theme", self.show_windows["Theme"])
if exp:
def _render_theme_panel(self):
imgui.text("Palette")
cp = theme.get_current_palette()
if imgui.begin_combo("##pal", cp):
@@ -1467,170 +1645,6 @@ class App:
imgui.text("UI Scale (DPI)")
ch, scale = imgui.slider_float("##scale", theme.get_current_scale(), 0.5, 3.0, "%.2f")
if ch: theme.set_scale(scale)
imgui.end()
# ---- Diagnostics
if self.show_windows["Diagnostics"]:
exp, self.show_windows["Diagnostics"] = imgui.begin("Diagnostics", self.show_windows["Diagnostics"])
if exp:
now = time.time()
if now - self._perf_last_update >= 0.5:
self._perf_last_update = now
metrics = self.perf_monitor.get_metrics()
self.perf_history["frame_time"].pop(0)
self.perf_history["frame_time"].append(metrics.get("last_frame_time_ms", 0.0))
self.perf_history["fps"].pop(0)
self.perf_history["fps"].append(metrics.get("fps", 0.0))
self.perf_history["cpu"].pop(0)
self.perf_history["cpu"].append(metrics.get("cpu_percent", 0.0))
self.perf_history["input_lag"].pop(0)
self.perf_history["input_lag"].append(metrics.get("input_lag_ms", 0.0))
metrics = self.perf_monitor.get_metrics()
imgui.text("Performance Telemetry")
imgui.separator()
if imgui.begin_table("perf_table", 2, imgui.TableFlags_.borders_inner_h):
imgui.table_setup_column("Metric")
imgui.table_setup_column("Value")
imgui.table_headers_row()
imgui.table_next_row()
imgui.table_next_column()
imgui.text("FPS")
imgui.table_next_column()
imgui.text(f"{metrics.get('fps', 0.0):.1f}")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Frame Time (ms)")
imgui.table_next_column()
imgui.text(f"{metrics.get('last_frame_time_ms', 0.0):.2f}")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("CPU %")
imgui.table_next_column()
imgui.text(f"{metrics.get('cpu_percent', 0.0):.1f}")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Input Lag (ms)")
imgui.table_next_column()
imgui.text(f"{metrics.get('input_lag_ms', 0.0):.1f}")
imgui.end_table()
imgui.separator()
imgui.text("Frame Time (ms)")
imgui.plot_lines("##ft_plot", np.array(self.perf_history["frame_time"], dtype=np.float32), overlay_text="frame_time", graph_size=imgui.ImVec2(-1, 60))
imgui.text("CPU %")
imgui.plot_lines("##cpu_plot", np.array(self.perf_history["cpu"], dtype=np.float32), overlay_text="cpu", graph_size=imgui.ImVec2(-1, 60))
imgui.end()
self.perf_monitor.end_frame()
# ---- Modals / Popups
with self._pending_dialog_lock:
dlg = self._pending_dialog
if dlg:
if not self._pending_dialog_open:
imgui.open_popup("Approve PowerShell Command")
self._pending_dialog_open = True
else:
self._pending_dialog_open = False
if imgui.begin_popup_modal("Approve PowerShell Command", None, imgui.WindowFlags_.always_auto_resize)[0]:
if dlg:
imgui.text("The AI wants to run the following PowerShell script:")
imgui.text_colored(vec4(200, 200, 100), f"base_dir: {dlg._base_dir}")
imgui.separator()
if imgui.button("[+ Maximize]##confirm"):
self.show_text_viewer = True
self.text_viewer_title = "Confirm Script"
self.text_viewer_content = dlg._script
ch, dlg._script = imgui.input_text_multiline("##confirm_script", dlg._script, imgui.ImVec2(-1, 300))
imgui.separator()
if imgui.button("Approve & Run", imgui.ImVec2(120, 0)):
dlg._approved = True
dlg._event.set()
with self._pending_dialog_lock:
self._pending_dialog = None
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Reject", imgui.ImVec2(120, 0)):
dlg._approved = False
dlg._event.set()
with self._pending_dialog_lock:
self._pending_dialog = None
imgui.close_current_popup()
imgui.end_popup()
if self.show_script_output:
if self._trigger_script_blink:
self._trigger_script_blink = False
self._is_script_blinking = True
self._script_blink_start_time = time.time()
imgui.set_window_focus_str("Last Script Output")
if self._is_script_blinking:
elapsed = time.time() - self._script_blink_start_time
if elapsed > 1.5:
self._is_script_blinking = False
else:
val = math.sin(elapsed * 8 * math.pi)
alpha = 60/255 if val > 0 else 0
imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 100, 255, alpha))
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 100, 255, alpha))
imgui.set_next_window_size(imgui.ImVec2(800, 600), imgui.Cond_.first_use_ever)
expanded, self.show_script_output = imgui.begin("Last Script Output", self.show_script_output)
if expanded:
imgui.text("Script:")
imgui.same_line()
self._render_text_viewer("Last Script", self.ui_last_script_text)
if self.ui_word_wrap:
imgui.begin_child("lso_s_wrap", imgui.ImVec2(-1, 200), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.ui_last_script_text)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##lso_s", self.ui_last_script_text, imgui.ImVec2(-1, 200), imgui.InputTextFlags_.read_only)
imgui.separator()
imgui.text("Output:")
imgui.same_line()
self._render_text_viewer("Last Output", self.ui_last_script_output)
if self.ui_word_wrap:
imgui.begin_child("lso_o_wrap", imgui.ImVec2(-1, -1), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.ui_last_script_output)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##lso_o", self.ui_last_script_output, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
if self._is_script_blinking:
imgui.pop_style_color(2)
imgui.end()
if self.show_text_viewer:
imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
expanded, self.show_text_viewer = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
if expanded:
if self.ui_word_wrap:
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.text_viewer_content)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##tv_c", self.text_viewer_content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
imgui.end()
def _load_fonts(self):
font_path, font_size = theme.get_font_loading_params()

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"