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:
470
gui_2.py
470
gui_2.py
@@ -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
243
refactor_gui2.py
Normal 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
46
tests/test_gui2_layout.py
Normal 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"
|
||||
Reference in New Issue
Block a user