feat(ui): Build Diagnostics Panel with real-time plots

This commit is contained in:
2026-02-23 14:50:44 -05:00
parent 0b148325d0
commit 30d838c3a0
2 changed files with 134 additions and 0 deletions

69
gui.py
View File

@@ -460,6 +460,7 @@ class App:
"Theme": "win_theme",
"Last Script Output": "win_script_output",
"Text Viewer": "win_text_viewer",
"Diagnostics": "win_diagnostics",
}
@@ -496,6 +497,12 @@ class App:
self._script_blink_start_time = 0.0
self.perf_monitor = PerformanceMonitor()
self.perf_history = {
"frame_time": [0.0] * 100,
"fps": [0.0] * 100,
"cpu": [0.0] * 100,
"input_lag": [0.0] * 100
}
session_logger.open_session()
ai_client.set_provider(self.current_provider, self.current_model)
@@ -815,6 +822,32 @@ class App:
else:
dpg.configure_item("gemini_cache_label", show=False)
# Update Diagnostics panel
if dpg.is_item_shown("win_diagnostics"):
metrics = self.perf_monitor.get_metrics()
# Update history
self.perf_history["frame_time"].append(metrics['last_frame_time_ms'])
self.perf_history["fps"].append(metrics['fps'])
self.perf_history["cpu"].append(metrics['cpu_percent'])
self.perf_history["input_lag"].append(metrics['input_lag_ms'])
for k in self.perf_history:
if len(self.perf_history[k]) > 100:
self.perf_history[k].pop(0)
# Update labels
dpg.set_value("perf_fps_text", f"{metrics['fps']:.1f}")
dpg.set_value("perf_frame_text", f"{metrics['last_frame_time_ms']:.1f}ms")
dpg.set_value("perf_cpu_text", f"{metrics['cpu_percent']:.1f}%")
dpg.set_value("perf_lag_text", f"{metrics['input_lag_ms']:.1f}ms")
# Update plots
if dpg.does_item_exist("perf_frame_plot"):
dpg.set_value("perf_frame_plot", [list(range(100)), self.perf_history["frame_time"]])
if dpg.does_item_exist("perf_cpu_plot"):
dpg.set_value("perf_cpu_plot", [list(range(100)), self.perf_history["cpu"]])
def _append_comms_entry(self, entry: dict, idx: int):
if not dpg.does_item_exist("comms_scroll"):
return
@@ -2110,6 +2143,42 @@ class App:
with dpg.child_window(tag="text_viewer_wrap_container", width=-1, height=-1, border=False, show=False):
dpg.add_text("", tag="text_viewer_wrap", wrap=0)
# ---- Diagnostics panel ----
with dpg.window(
label="Diagnostics",
tag="win_diagnostics",
pos=(8, 804),
width=400,
height=380,
no_close=False,
):
dpg.add_text("Performance Telemetry")
with dpg.group(horizontal=True):
dpg.add_text("FPS:")
dpg.add_text("0.0", tag="perf_fps_text", color=(180, 255, 180))
dpg.add_spacer(width=20)
dpg.add_text("Frame:")
dpg.add_text("0.0ms", tag="perf_frame_text", color=(100, 200, 255))
with dpg.add_plot(label="Frame Time (ms)", height=100, width=-1, no_mouse_pos=True):
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True)
with dpg.plot_axis(dpg.mvYAxis, label="ms"):
dpg.add_line_series(list(range(100)), self.perf_history["frame_time"], label="frame time", tag="perf_frame_plot")
dpg.set_axis_limits(dpg.last_item(), 0, 50)
with dpg.group(horizontal=True):
dpg.add_text("CPU:")
dpg.add_text("0.0%", tag="perf_cpu_text", color=(255, 220, 100))
dpg.add_spacer(width=20)
dpg.add_text("Input Lag:")
dpg.add_text("0.0ms", tag="perf_lag_text", color=(255, 180, 80))
with dpg.add_plot(label="CPU Usage (%)", height=100, width=-1, no_mouse_pos=True):
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True)
with dpg.plot_axis(dpg.mvYAxis, label="%"):
dpg.add_line_series(list(range(100)), self.perf_history["cpu"], label="cpu usage", tag="perf_cpu_plot")
dpg.set_axis_limits(dpg.last_item(), 0, 100)
def run(self):
dpg.create_context()
dpg.configure_app(docking=True, docking_space=True, init_file="dpg_layout.ini")

View File

@@ -0,0 +1,65 @@
import pytest
from unittest.mock import patch, MagicMock
import importlib.util
import sys
import dearpygui.dearpygui as dpg
# Load gui.py as a module for testing
spec = importlib.util.spec_from_file_location("gui", "gui.py")
gui = importlib.util.module_from_spec(spec)
sys.modules["gui"] = gui
spec.loader.exec_module(gui)
from gui import App
@pytest.fixture
def app_instance():
dpg.create_context()
with patch('dearpygui.dearpygui.create_viewport'), \
patch('dearpygui.dearpygui.setup_dearpygui'), \
patch('dearpygui.dearpygui.show_viewport'), \
patch('dearpygui.dearpygui.start_dearpygui'), \
patch('gui.load_config', return_value={}), \
patch.object(App, '_rebuild_files_list'), \
patch.object(App, '_rebuild_shots_list'), \
patch.object(App, '_rebuild_disc_list'), \
patch.object(App, '_rebuild_disc_roles_list'), \
patch.object(App, '_rebuild_discussion_selector'), \
patch.object(App, '_refresh_project_widgets'):
app = App()
yield app
dpg.destroy_context()
def test_diagnostics_panel_initialization(app_instance):
assert "Diagnostics" in app_instance.window_info
assert app_instance.window_info["Diagnostics"] == "win_diagnostics"
assert "frame_time" in app_instance.perf_history
assert len(app_instance.perf_history["frame_time"]) == 100
def test_diagnostics_panel_updates(app_instance):
# Mock dependencies
mock_metrics = {
'last_frame_time_ms': 10.0,
'fps': 100.0,
'cpu_percent': 50.0,
'input_lag_ms': 5.0
}
app_instance.perf_monitor.get_metrics = MagicMock(return_value=mock_metrics)
with patch('dearpygui.dearpygui.is_item_shown', return_value=True), \
patch('dearpygui.dearpygui.set_value') as mock_set_value, \
patch('dearpygui.dearpygui.configure_item') as mock_configure_item, \
patch('dearpygui.dearpygui.does_item_exist', return_value=True):
# We also need to mock ai_client stats
with patch('ai_client.get_history_bleed_stats', return_value={}):
app_instance._update_telemetry_panel()
# Verify UI updates
mock_set_value.assert_any_call("perf_fps_text", "100.0")
mock_set_value.assert_any_call("perf_frame_text", "10.0ms")
mock_set_value.assert_any_call("perf_cpu_text", "50.0%")
mock_set_value.assert_any_call("perf_lag_text", "5.0ms")
# Verify history update
assert app_instance.perf_history["frame_time"][-1] == 10.0