From 8e02c1ececee40cb2007b5fd317bf70b5aa4f298 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 8 Mar 2026 21:07:49 -0400 Subject: [PATCH] feat(logs): Implement Diagnostic Tab and clean up discussion history --- src/app_controller.py | 16 +-- src/gui_2.py | 306 ++++++++++++++++++++++-------------------- 2 files changed, 168 insertions(+), 154 deletions(-) diff --git a/src/app_controller.py b/src/app_controller.py index 28b6905..025c287 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -130,7 +130,7 @@ class AppController: PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"] def __init__(self): - # Initialize locks first to avoid initialization order issues + # Initialize locks first to avoid initialization order issues self._send_thread_lock: threading.Lock = threading.Lock() self._disc_entries_lock: threading.Lock = threading.Lock() self._pending_comms_lock: threading.Lock = threading.Lock() @@ -297,6 +297,7 @@ class AppController: self._inject_mode: str = "skeleton" self._inject_preview: str = "" self._show_inject_modal: bool = False + self.diagnostic_log: List[Dict[str, Any]] = [] self._settable_fields: Dict[str, str] = { 'ai_input': 'ui_ai_input', 'project_git_dir': 'ui_project_git_dir', @@ -798,7 +799,6 @@ class AppController: "Tool Calls": False, "Theme": True, "Log Management": False, - "Diagnostics": False, } saved = self.config.get("gui", {}).get("show_windows", {}) self.show_windows = {k: saved.get(k, v) for k, v in _default_windows.items()} @@ -1212,13 +1212,11 @@ class AppController: self._api_event_queue.append({"type": event_name, "payload": payload}) def _on_performance_alert(self, message: str) -> None: - alert_text = f"[PERFORMANCE ALERT] {message}. Please consider optimizing recent changes or reducing load." - with self._pending_history_adds_lock: - self._pending_history_adds.append({ - "role": "System", - "content": alert_text, - "ts": project_manager.now_ts() - }) + self.diagnostic_log.append({ + "ts": project_manager.now_ts(), + "message": message, + "type": "performance" + }) def _confirm_and_run(self, script: str, base_dir: str, qa_callback: Optional[Callable[[str], str]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> Optional[str]: sys.stderr.write(f"[DEBUG] _confirm_and_run called. test_hooks={self.test_hooks_enabled}, manual_approve={getattr(self, 'ui_manual_approve', False)}\n") diff --git a/src/gui_2.py b/src/gui_2.py index 1b7ec32..4382367 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -285,7 +285,7 @@ class App: self._flush_to_config() models.save_config(self.config) except Exception: - pass # silent — don't disrupt the GUI loop + pass # silent — don't disrupt the GUI loop # Sync pending comms with self._pending_comms_lock: if self._pending_comms: @@ -491,81 +491,7 @@ class App: if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management") self._render_log_management() if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management") - if self.show_windows["Diagnostics"]: - exp, opened = imgui.begin("Diagnostics", self.show_windows["Diagnostics"]) - self.show_windows["Diagnostics"] = bool(opened) - if exp: - metrics = self.perf_monitor.get_metrics() - imgui.text("Performance Telemetry") - imgui.same_line() - _, self.perf_profiling_enabled = imgui.checkbox("Enable Profiling", self.perf_profiling_enabled) - imgui.separator() - if imgui.begin_table("perf_table", 3, imgui.TableFlags_.borders_inner_h): - imgui.table_setup_column("Metric") - imgui.table_setup_column("Value") - imgui.table_setup_column("Graph") - imgui.table_headers_row() - - # Core Metrics - for label, key, format_str in [ - ("FPS", "fps", "%.1f"), - ("Frame Time (ms)", "frame_time_ms", "%.2f"), - ("CPU %", "cpu_percent", "%.1f"), - ("Input Lag (ms)", "input_lag_ms", "%.1f") - ]: - imgui.table_next_row() - imgui.table_next_column() - imgui.text(label) - imgui.table_next_column() - if key == "fps": - avg_val = imgui.get_io().framerate - else: - avg_val = metrics.get(f"{key}_avg", metrics.get(key, 0.0)) - imgui.text(format_str % avg_val) - imgui.table_next_column() - self.perf_show_graphs.setdefault(key, False) - _, self.perf_show_graphs[key] = imgui.checkbox(f"##g_{key}", self.perf_show_graphs[key]) - - imgui.end_table() - - if self.perf_profiling_enabled: - imgui.separator() - imgui.text("Detailed Component Timings (Moving Average)") - if imgui.begin_table("comp_timings", 3, imgui.TableFlags_.borders): - imgui.table_setup_column("Component") - imgui.table_setup_column("Avg (ms)") - imgui.table_setup_column("Graph") - imgui.table_headers_row() - # Show all components found in metrics - for key, val in metrics.items(): - if key.startswith("time_") and key.endswith("_ms") and not key.endswith("_avg"): - comp_name = key[5:-3] - avg_val = metrics.get(f"{key}_avg", val) - imgui.table_next_row() - imgui.table_next_column() - imgui.text(comp_name) - imgui.table_next_column() - if avg_val > 10.0: - imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{avg_val:.2f}") - else: - imgui.text(f"{avg_val:.2f}") - imgui.table_next_column() - self.perf_show_graphs.setdefault(comp_name, False) - _, self.perf_show_graphs[comp_name] = imgui.checkbox(f"##g_{comp_name}", self.perf_show_graphs[comp_name]) - imgui.end_table() - - # Render all enabled graphs (core + components) - imgui.separator() - imgui.text("Performance Graphs") - for key, show in self.perf_show_graphs.items(): - if show: - imgui.text(f"History: {key}") - hist_data = self.perf_monitor.get_history(key) - if hist_data: - imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60)) - else: - imgui.text_disabled(f"(no history data for {key})") - imgui.end() + self.perf_monitor.end_frame() # ---- Modals / Popups with self._pending_dialog_lock: @@ -1053,78 +979,168 @@ class App: if not exp: imgui.end() return - - if self._log_registry is None: - self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml")) - else: - # Refresh data occasionally or on demand? For now let's just use the cached object. - # The LogRegistry object loads data into self.data upon __init__. - # We might want a refresh button or to reload every few seconds. - if imgui.button("Refresh Registry"): - self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml")) - imgui.same_line() - if imgui.button("Load Log"): - self.cb_load_prior_log() - imgui.same_line() - if imgui.button("Force Prune Logs"): - self.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"}) - registry = self._log_registry - sessions = registry.data - if imgui.begin_table("sessions_table", 7, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable): - imgui.table_setup_column("Session ID") - imgui.table_setup_column("Start Time") - imgui.table_setup_column("Star") - imgui.table_setup_column("Reason") - imgui.table_setup_column("Size (KB)") - imgui.table_setup_column("Msgs") - imgui.table_setup_column("Actions") - imgui.table_headers_row() - for session_id, s_data in sessions.items(): - imgui.table_next_row() - imgui.table_next_column() - imgui.text(session_id) - imgui.table_next_column() - imgui.text(s_data.get("start_time", "")) - imgui.table_next_column() - whitelisted = s_data.get("whitelisted", False) - if whitelisted: - imgui.text_colored(vec4(255, 215, 0), "YES") + if imgui.begin_tab_bar("log_mgmt_tabs"): + if imgui.begin_tab_item("Sessions")[0]: + if self._log_registry is None: + self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml")) else: - imgui.text("NO") - metadata = s_data.get("metadata") or {} - imgui.table_next_column() - imgui.text(metadata.get("reason", "")) - imgui.table_next_column() - imgui.text(str(metadata.get("size_kb", ""))) - imgui.table_next_column() - imgui.text(str(metadata.get("message_count", ""))) - imgui.table_next_column() - if imgui.button(f"Load##{session_id}"): - self.cb_load_prior_log(s_data.get("path")) + if imgui.button("Refresh Registry"): + self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml")) + imgui.same_line() + if imgui.button("Load Log"): + self.cb_load_prior_log() imgui.same_line() - if whitelisted: - if imgui.button(f"Unstar##{session_id}"): - registry.update_session_metadata( - session_id, - message_count=int(metadata.get("message_count") or 0), - errors=int(metadata.get("errors") or 0), - size_kb=int(metadata.get("size_kb") or 0), - whitelisted=False, - reason=str(metadata.get("reason") or "") - ) - else: - if imgui.button(f"Star##{session_id}"): - registry.update_session_metadata( - session_id, - message_count=int(metadata.get("message_count") or 0), - errors=int(metadata.get("errors") or 0), - size_kb=int(metadata.get("size_kb") or 0), - whitelisted=True, - reason="Manually whitelisted" - ) - imgui.end_table() - + if imgui.button("Force Prune Logs"): + self.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"}) + + registry = self._log_registry + sessions = registry.data + if imgui.begin_table("sessions_table", 7, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable): + imgui.table_setup_column("Session ID") + imgui.table_setup_column("Start Time") + imgui.table_setup_column("Star") + imgui.table_setup_column("Reason") + imgui.table_setup_column("Size (KB)") + imgui.table_setup_column("Msgs") + imgui.table_setup_column("Actions") + imgui.table_headers_row() + for session_id, s_data in sessions.items(): + imgui.table_next_row() + imgui.table_next_column() + imgui.text(session_id) + imgui.table_next_column() + imgui.text(s_data.get("start_time", "")) + imgui.table_next_column() + whitelisted = s_data.get("whitelisted", False) + if whitelisted: + imgui.text_colored(vec4(255, 215, 0), "YES") + else: + imgui.text("NO") + metadata = s_data.get("metadata") or {} + imgui.table_next_column() + imgui.text(metadata.get("reason", "")) + imgui.table_next_column() + imgui.text(str(metadata.get("size_kb", ""))) + imgui.table_next_column() + imgui.text(str(metadata.get("message_count", ""))) + imgui.table_next_column() + if imgui.button(f"Load##{session_id}"): + self.cb_load_prior_log(s_data.get("path")) + imgui.same_line() + if whitelisted: + if imgui.button(f"Unstar##{session_id}"): + registry.update_session_metadata( + session_id, + message_count=int(metadata.get("message_count") or 0), + errors=int(metadata.get("errors") or 0), + size_kb=int(metadata.get("size_kb") or 0), + whitelisted=False, + reason=str(metadata.get("reason") or "") + ) + else: + if imgui.button(f"Star##{session_id}"): + registry.update_session_metadata( + session_id, + message_count=int(metadata.get("message_count") or 0), + errors=int(metadata.get("errors") or 0), + size_kb=int(metadata.get("size_kb") or 0), + whitelisted=True, + reason="Manually whitelisted" + ) + imgui.end_table() + imgui.end_tab_item() + + if imgui.begin_tab_item("System Diagnostics")[0]: + metrics = self.perf_monitor.get_metrics() + imgui.text("Performance Telemetry") + imgui.same_line() + _, self.perf_profiling_enabled = imgui.checkbox("Enable Profiling", self.perf_profiling_enabled) + imgui.separator() + + if imgui.begin_table("perf_table", 3, imgui.TableFlags_.borders_inner_h): + imgui.table_setup_column("Metric") + imgui.table_setup_column("Value") + imgui.table_setup_column("Graph") + imgui.table_headers_row() + + for label, key, format_str in [ + ("FPS", "fps", "%.1f"), + ("Frame Time (ms)", "frame_time_ms", "%.2f"), + ("CPU %", "cpu_percent", "%.1f"), + ("Input Lag (ms)", "input_lag_ms", "%.1f") + ]: + imgui.table_next_row() + imgui.table_next_column() + imgui.text(label) + imgui.table_next_column() + if key == "fps": + avg_val = imgui.get_io().framerate + else: + avg_val = metrics.get(f"{key}_avg", metrics.get(key, 0.0)) + imgui.text(format_str % avg_val) + imgui.table_next_column() + self.perf_show_graphs.setdefault(key, False) + _, self.perf_show_graphs[key] = imgui.checkbox(f"##g_{key}", self.perf_show_graphs[key]) + imgui.end_table() + + if self.perf_profiling_enabled: + imgui.separator() + imgui.text("Detailed Component Timings (Moving Average)") + if imgui.begin_table("comp_timings", 3, imgui.TableFlags_.borders): + imgui.table_setup_column("Component") + imgui.table_setup_column("Avg (ms)") + imgui.table_setup_column("Graph") + imgui.table_headers_row() + for key, val in metrics.items(): + if key.startswith("time_") and key.endswith("_ms") and not key.endswith("_avg"): + comp_name = key[5:-3] + avg_val = metrics.get(f"{key}_avg", val) + imgui.table_next_row() + imgui.table_next_column() + imgui.text(comp_name) + imgui.table_next_column() + if avg_val > 10.0: + imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{avg_val:.2f}") + else: + imgui.text(f"{avg_val:.2f}") + imgui.table_next_column() + self.perf_show_graphs.setdefault(comp_name, False) + _, self.perf_show_graphs[comp_name] = imgui.checkbox(f"##g_{comp_name}", self.perf_show_graphs[comp_name]) + imgui.end_table() + + imgui.separator() + imgui.text("Performance Graphs") + for key, show in self.perf_show_graphs.items(): + if show: + imgui.text(f"History: {key}") + hist_data = self.perf_monitor.get_history(key) + if hist_data: + import numpy as np + imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60)) + else: + imgui.text_disabled(f"(no history data for {key})") + + imgui.separator() + imgui.text("Diagnostic Log") + if imgui.begin_table("diag_log_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable): + imgui.table_setup_column("Timestamp", imgui.TableColumnFlags_.width_fixed, 150) + imgui.table_setup_column("Type", imgui.TableColumnFlags_.width_fixed, 100) + imgui.table_setup_column("Message") + imgui.table_headers_row() + for entry in reversed(self.controller.diagnostic_log): + imgui.table_next_row() + imgui.table_next_column() + imgui.text(entry.get("ts", "")) + imgui.table_next_column() + imgui.text(entry.get("type", "")) + imgui.table_next_column() + imgui.text_wrapped(entry.get("message", "")) + imgui.end_table() + + imgui.end_tab_item() + imgui.end_tab_bar() + if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management") imgui.end()