feat(logs): Implement Diagnostic Tab and clean up discussion history

This commit is contained in:
2026-03-08 21:07:49 -04:00
parent f9364e173e
commit 8e02c1ecec
2 changed files with 168 additions and 154 deletions

View File

@@ -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,12 +1212,10 @@ 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]:

View File

@@ -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:
@@ -1054,12 +980,11 @@ class App:
imgui.end()
return
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:
# 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()
@@ -1124,6 +1049,97 @@ class App:
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()