feat(ui): Implement walkthrough refinements (Diagnostics, Tabs, Selectable text, Session Loading)
This commit is contained in:
238
gui.py
238
gui.py
@@ -128,7 +128,8 @@ def _add_text_field(parent: str, label: str, value: str):
|
|||||||
if len(value) > COMMS_CLAMP_CHARS:
|
if len(value) > COMMS_CLAMP_CHARS:
|
||||||
if wrap:
|
if wrap:
|
||||||
with dpg.child_window(height=80, border=True):
|
with dpg.child_window(height=80, border=True):
|
||||||
dpg.add_text(value, wrap=0, color=_VALUE_COLOR)
|
# add_input_text for selection
|
||||||
|
dpg.add_input_text(default_value=value, multiline=True, readonly=True, width=-1, height=-1, border=False)
|
||||||
else:
|
else:
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
default_value=value,
|
default_value=value,
|
||||||
@@ -138,15 +139,15 @@ def _add_text_field(parent: str, label: str, value: str):
|
|||||||
height=80,
|
height=80,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
dpg.add_text(value if value else "(empty)", wrap=0, color=_VALUE_COLOR)
|
# Short selectable text
|
||||||
|
dpg.add_input_text(default_value=value if value else "(empty)", readonly=True, width=-1, border=False)
|
||||||
|
|
||||||
|
|
||||||
def _add_kv_row(parent: str, key: str, val, val_color=None):
|
def _add_kv_row(parent: str, key: str, val, val_color=None):
|
||||||
"""Single key: value row, horizontally laid out."""
|
"""Single key: value row, horizontally laid out."""
|
||||||
vc = val_color or _VALUE_COLOR
|
|
||||||
with dpg.group(horizontal=True, parent=parent):
|
with dpg.group(horizontal=True, parent=parent):
|
||||||
dpg.add_text(f"{key}:", color=_LABEL_COLOR)
|
dpg.add_text(f"{key}:", color=_LABEL_COLOR)
|
||||||
dpg.add_text(str(val), color=vc)
|
dpg.add_input_text(default_value=str(val), readonly=True, width=-1, border=False)
|
||||||
|
|
||||||
|
|
||||||
def _render_usage(parent: str, usage: dict):
|
def _render_usage(parent: str, usage: dict):
|
||||||
@@ -451,6 +452,7 @@ class App:
|
|||||||
"AI Settings Hub": "win_ai_settings_hub",
|
"AI Settings Hub": "win_ai_settings_hub",
|
||||||
"Discussion Hub": "win_discussion_hub",
|
"Discussion Hub": "win_discussion_hub",
|
||||||
"Operations Hub": "win_operations_hub",
|
"Operations Hub": "win_operations_hub",
|
||||||
|
"Diagnostics": "win_diagnostics",
|
||||||
"Theme": "win_theme",
|
"Theme": "win_theme",
|
||||||
"Last Script Output": "win_script_output",
|
"Last Script Output": "win_script_output",
|
||||||
"Text Viewer": "win_text_viewer",
|
"Text Viewer": "win_text_viewer",
|
||||||
@@ -488,6 +490,8 @@ class App:
|
|||||||
self._trigger_script_blink = False
|
self._trigger_script_blink = False
|
||||||
self._is_script_blinking = False
|
self._is_script_blinking = False
|
||||||
self._script_blink_start_time = 0.0
|
self._script_blink_start_time = 0.0
|
||||||
|
|
||||||
|
self.is_viewing_prior_session = False
|
||||||
|
|
||||||
# Subscribe to API lifecycle events
|
# Subscribe to API lifecycle events
|
||||||
ai_client.events.on("request_start", self._on_api_event)
|
ai_client.events.on("request_start", self._on_api_event)
|
||||||
@@ -863,7 +867,7 @@ class App:
|
|||||||
# Update Diagnostics panel (throttled for smoothness)
|
# Update Diagnostics panel (throttled for smoothness)
|
||||||
if now - self._last_perf_update_time > 0.5:
|
if now - self._last_perf_update_time > 0.5:
|
||||||
self._last_perf_update_time = now
|
self._last_perf_update_time = now
|
||||||
if dpg.is_item_shown("win_operations_hub"):
|
if dpg.is_item_shown("win_diagnostics"):
|
||||||
metrics = self.perf_monitor.get_metrics()
|
metrics = self.perf_monitor.get_metrics()
|
||||||
|
|
||||||
# Update history
|
# Update history
|
||||||
@@ -1310,6 +1314,65 @@ class App:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._update_status(f"error: {e}")
|
self._update_status(f"error: {e}")
|
||||||
|
|
||||||
|
def cb_load_prior_log(self):
|
||||||
|
root = hide_tk_root()
|
||||||
|
path = filedialog.askopenfilename(
|
||||||
|
title="Load Session Log",
|
||||||
|
initialdir="logs",
|
||||||
|
filetypes=[("Log Files", "*.log"), ("JSONL Files", "*.jsonl"), ("All Files", "*.*")]
|
||||||
|
)
|
||||||
|
root.destroy()
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
entries = []
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
if line.strip():
|
||||||
|
entries.append(json.loads(line))
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_viewing_prior_session = True
|
||||||
|
dpg.configure_item("prior_session_indicator", show=True)
|
||||||
|
dpg.configure_item("exit_prior_btn", show=True)
|
||||||
|
|
||||||
|
# Apply Tinted Mode Theme
|
||||||
|
if not dpg.does_item_exist("prior_session_theme"):
|
||||||
|
with dpg.theme(tag="prior_session_theme"):
|
||||||
|
with dpg.theme_component(dpg.mvAll):
|
||||||
|
# Tint everything slightly amber/sepia
|
||||||
|
dpg.add_theme_color(dpg.mvThemeCol_WindowBg, (40, 30, 20, 255))
|
||||||
|
dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (50, 40, 30, 255))
|
||||||
|
|
||||||
|
for hub in ["win_context_hub", "win_ai_settings_hub", "win_discussion_hub", "win_operations_hub", "win_diagnostics"]:
|
||||||
|
if dpg.does_item_exist(hub):
|
||||||
|
dpg.bind_item_theme(hub, "prior_session_theme")
|
||||||
|
|
||||||
|
# Clear and render old entries
|
||||||
|
dpg.delete_item("comms_scroll", children_only=True)
|
||||||
|
for i, entry in enumerate(entries):
|
||||||
|
_render_comms_entry("comms_scroll", entry, i + 1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._update_status(f"Load error: {e}")
|
||||||
|
|
||||||
|
def cb_exit_prior_session(self):
|
||||||
|
self.is_viewing_prior_session = False
|
||||||
|
dpg.configure_item("prior_session_indicator", show=False)
|
||||||
|
dpg.configure_item("exit_prior_btn", show=False)
|
||||||
|
|
||||||
|
# Unbind theme
|
||||||
|
for hub in ["win_context_hub", "win_ai_settings_hub", "win_discussion_hub", "win_operations_hub", "win_diagnostics"]:
|
||||||
|
if dpg.does_item_exist(hub):
|
||||||
|
dpg.bind_item_theme(hub, 0)
|
||||||
|
|
||||||
|
# Restore current session comms
|
||||||
|
self._rebuild_comms_log()
|
||||||
|
|
||||||
def cb_reset_session(self):
|
def cb_reset_session(self):
|
||||||
ai_client.reset_session()
|
ai_client.reset_session()
|
||||||
ai_client.clear_comms_log()
|
ai_client.clear_comms_log()
|
||||||
@@ -1586,8 +1649,14 @@ class App:
|
|||||||
# ---- disc entry list ----
|
# ---- disc entry list ----
|
||||||
|
|
||||||
def _render_disc_entry(self, i: int, entry: dict):
|
def _render_disc_entry(self, i: int, entry: dict):
|
||||||
collapsed = entry.get("collapsed", False)
|
# Default to collapsed and read-mode if not specified
|
||||||
read_mode = entry.get("read_mode", False)
|
if "collapsed" not in entry:
|
||||||
|
entry["collapsed"] = True
|
||||||
|
if "read_mode" not in entry:
|
||||||
|
entry["read_mode"] = True
|
||||||
|
|
||||||
|
collapsed = entry.get("collapsed", True)
|
||||||
|
read_mode = entry.get("read_mode", True)
|
||||||
ts_str = entry.get("ts", "")
|
ts_str = entry.get("ts", "")
|
||||||
|
|
||||||
preview = entry["content"].replace("\n", " ")[:60]
|
preview = entry["content"].replace("\n", " ")[:60]
|
||||||
@@ -1602,6 +1671,11 @@ class App:
|
|||||||
width=24,
|
width=24,
|
||||||
callback=self._make_disc_toggle_cb(i),
|
callback=self._make_disc_toggle_cb(i),
|
||||||
)
|
)
|
||||||
|
dpg.add_button(
|
||||||
|
label="[+ Max]",
|
||||||
|
user_data=i,
|
||||||
|
callback=lambda s, a, u: _show_text_viewer(f"Entry #{u+1}", self.disc_entries[u]["content"])
|
||||||
|
)
|
||||||
dpg.add_combo(
|
dpg.add_combo(
|
||||||
tag=f"disc_role_{i}",
|
tag=f"disc_role_{i}",
|
||||||
items=self.disc_roles,
|
items=self.disc_roles,
|
||||||
@@ -1623,11 +1697,6 @@ class App:
|
|||||||
width=36,
|
width=36,
|
||||||
callback=self._make_disc_insert_cb(i),
|
callback=self._make_disc_insert_cb(i),
|
||||||
)
|
)
|
||||||
dpg.add_button(
|
|
||||||
label="[+ Max]",
|
|
||||||
user_data=i,
|
|
||||||
callback=lambda s, a, u: _show_text_viewer(f"Entry #{u+1}", self.disc_entries[u]["content"])
|
|
||||||
)
|
|
||||||
dpg.add_button(
|
dpg.add_button(
|
||||||
label="Del",
|
label="Del",
|
||||||
width=36,
|
width=36,
|
||||||
@@ -1637,8 +1706,14 @@ class App:
|
|||||||
|
|
||||||
with dpg.group(tag=f"disc_body_{i}", show=not collapsed):
|
with dpg.group(tag=f"disc_body_{i}", show=not collapsed):
|
||||||
if read_mode:
|
if read_mode:
|
||||||
with dpg.child_window(height=150, border=True):
|
# Use a read-only input_text instead of dpg.add_text to allow selection
|
||||||
dpg.add_text(entry["content"], wrap=0, color=(200, 200, 200))
|
dpg.add_input_text(
|
||||||
|
default_value=entry["content"],
|
||||||
|
multiline=True,
|
||||||
|
readonly=True,
|
||||||
|
width=-1,
|
||||||
|
height=150,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
tag=f"disc_content_{i}",
|
tag=f"disc_content_{i}",
|
||||||
@@ -1972,6 +2047,11 @@ class App:
|
|||||||
no_close=False,
|
no_close=False,
|
||||||
no_collapse=True,
|
no_collapse=True,
|
||||||
):
|
):
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_text("DISCUSSION", color=_SUBHDR_COLOR)
|
||||||
|
dpg.add_spacer(width=20)
|
||||||
|
dpg.add_text("THINKING...", tag="thinking_indicator", color=(255, 100, 100), show=False)
|
||||||
|
|
||||||
# History at Top
|
# History at Top
|
||||||
with dpg.child_window(tag="disc_history_section", height=-400, border=True):
|
with dpg.child_window(tag="disc_history_section", height=-400, border=True):
|
||||||
# Discussion selector section
|
# Discussion selector section
|
||||||
@@ -2008,39 +2088,35 @@ class App:
|
|||||||
with dpg.child_window(tag="disc_scroll", height=-1, border=False):
|
with dpg.child_window(tag="disc_scroll", height=-1, border=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Message Composer in Middle
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_text("Message", color=_SUBHDR_COLOR)
|
|
||||||
dpg.add_spacer(width=20)
|
|
||||||
dpg.add_text("THINKING...", tag="thinking_indicator", color=(255, 100, 100), show=False)
|
|
||||||
|
|
||||||
dpg.add_input_text(
|
|
||||||
tag="ai_input",
|
|
||||||
multiline=True,
|
|
||||||
width=-1,
|
|
||||||
height=120,
|
|
||||||
)
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_button(label="Gen + Send", callback=self.cb_generate_send)
|
|
||||||
dpg.add_button(label="MD Only", callback=self.cb_md_only)
|
|
||||||
dpg.add_button(label="Reset", callback=self.cb_reset_session)
|
|
||||||
dpg.add_button(label="-> History", callback=self.cb_append_message_to_history)
|
|
||||||
|
|
||||||
dpg.add_separator()
|
dpg.add_separator()
|
||||||
|
|
||||||
# AI Response at Bottom
|
# Interaction Tabs at Bottom
|
||||||
dpg.add_text("AI Response", color=_SUBHDR_COLOR)
|
with dpg.tab_bar():
|
||||||
dpg.add_input_text(
|
with dpg.tab(label="Message"):
|
||||||
tag="ai_response",
|
dpg.add_input_text(
|
||||||
multiline=True,
|
tag="ai_input",
|
||||||
readonly=True,
|
multiline=True,
|
||||||
width=-1,
|
width=-1,
|
||||||
height=-48,
|
height=200,
|
||||||
)
|
)
|
||||||
with dpg.child_window(tag="ai_response_wrap_container", width=-1, height=-48, border=True, show=False):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_text("", tag="ai_response_wrap", wrap=0)
|
dpg.add_button(label="Gen + Send", callback=self.cb_generate_send)
|
||||||
dpg.add_separator()
|
dpg.add_button(label="MD Only", callback=self.cb_md_only)
|
||||||
dpg.add_button(label="-> History", callback=self.cb_append_response_to_history)
|
dpg.add_button(label="Reset", callback=self.cb_reset_session)
|
||||||
|
dpg.add_button(label="-> History", callback=self.cb_append_message_to_history)
|
||||||
|
|
||||||
|
with dpg.tab(label="AI Response"):
|
||||||
|
dpg.add_input_text(
|
||||||
|
tag="ai_response",
|
||||||
|
multiline=True,
|
||||||
|
readonly=True,
|
||||||
|
width=-1,
|
||||||
|
height=-48,
|
||||||
|
)
|
||||||
|
with dpg.child_window(tag="ai_response_wrap_container", width=-1, height=-48, border=True, show=False):
|
||||||
|
dpg.add_text("", tag="ai_response_wrap", wrap=0)
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_button(label="-> History", callback=self.cb_append_response_to_history)
|
||||||
|
|
||||||
def _build_operations_hub(self):
|
def _build_operations_hub(self):
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
@@ -2063,6 +2139,10 @@ class App:
|
|||||||
dpg.add_text("Status: idle", tag="ai_status", color=(200, 220, 160))
|
dpg.add_text("Status: idle", tag="ai_status", color=(200, 220, 160))
|
||||||
dpg.add_spacer(width=16)
|
dpg.add_spacer(width=16)
|
||||||
dpg.add_button(label="Clear", callback=self.cb_clear_comms)
|
dpg.add_button(label="Clear", callback=self.cb_clear_comms)
|
||||||
|
dpg.add_button(label="Load Log", callback=self.cb_load_prior_log)
|
||||||
|
dpg.add_button(label="Exit Prior", tag="exit_prior_btn", callback=self.cb_exit_prior_session, show=False)
|
||||||
|
|
||||||
|
dpg.add_text("PRIOR SESSION VIEW", tag="prior_session_indicator", color=(255, 100, 100), show=False)
|
||||||
dpg.add_text("Tokens: 0 (In: 0 Out: 0)", tag="ai_token_usage", color=(180, 255, 180))
|
dpg.add_text("Tokens: 0 (In: 0 Out: 0)", tag="ai_token_usage", color=(180, 255, 180))
|
||||||
dpg.add_separator()
|
dpg.add_separator()
|
||||||
with dpg.child_window(tag="comms_scroll", height=-1, border=False, horizontal_scrollbar=True):
|
with dpg.child_window(tag="comms_scroll", height=-1, border=False, horizontal_scrollbar=True):
|
||||||
@@ -2076,36 +2156,45 @@ class App:
|
|||||||
with dpg.child_window(tag="tool_log_scroll", height=-1, border=False):
|
with dpg.child_window(tag="tool_log_scroll", height=-1, border=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with dpg.tab(label="Diagnostics"):
|
def _build_diagnostics_window(self):
|
||||||
dpg.add_text("Performance Telemetry")
|
with dpg.window(
|
||||||
with dpg.table(header_row=False, borders_innerH=True, borders_outerH=True, borders_innerV=True, borders_outerV=True):
|
label="Diagnostics",
|
||||||
dpg.add_table_column()
|
tag="win_diagnostics",
|
||||||
dpg.add_table_column()
|
pos=(1244, 804),
|
||||||
dpg.add_table_column()
|
width=428,
|
||||||
dpg.add_table_column()
|
height=360,
|
||||||
with dpg.table_row():
|
no_close=False,
|
||||||
dpg.add_text("FPS", color=_LABEL_COLOR)
|
no_collapse=True,
|
||||||
dpg.add_text("0.0", tag="perf_fps_text", color=(180, 255, 180))
|
):
|
||||||
dpg.add_text("Frame", color=_LABEL_COLOR)
|
dpg.add_text("Performance Telemetry")
|
||||||
dpg.add_text("0.0ms", tag="perf_frame_text", color=(100, 200, 255))
|
with dpg.table(header_row=False, borders_innerH=True, borders_outerH=True, borders_innerV=True, borders_outerV=True):
|
||||||
with dpg.table_row():
|
dpg.add_table_column()
|
||||||
dpg.add_text("CPU", color=_LABEL_COLOR)
|
dpg.add_table_column()
|
||||||
dpg.add_text("0.0%", tag="perf_cpu_text", color=(255, 220, 100))
|
dpg.add_table_column()
|
||||||
dpg.add_text("Lag", color=_LABEL_COLOR)
|
dpg.add_table_column()
|
||||||
dpg.add_text("0.0ms", tag="perf_lag_text", color=(255, 180, 80))
|
with dpg.table_row():
|
||||||
|
dpg.add_text("FPS", color=_LABEL_COLOR)
|
||||||
dpg.add_spacer(height=4)
|
dpg.add_text("0.0", tag="perf_fps_text", color=(180, 255, 180))
|
||||||
dpg.add_plot(label="Frame Time (ms)", tag="plot_frame", height=120, width=-1, no_mouse_pos=True)
|
dpg.add_text("Frame", color=_LABEL_COLOR)
|
||||||
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True, parent="plot_frame")
|
dpg.add_text("0.0ms", tag="perf_frame_text", color=(100, 200, 255))
|
||||||
with dpg.plot_axis(dpg.mvYAxis, label="ms", tag="axis_frame_y", parent="plot_frame"):
|
with dpg.table_row():
|
||||||
dpg.add_line_series(list(range(100)), self.perf_history["frame_time"], label="frame time", tag="perf_frame_plot")
|
dpg.add_text("CPU", color=_LABEL_COLOR)
|
||||||
dpg.set_axis_limits("axis_frame_y", 0, 50)
|
dpg.add_text("0.0%", tag="perf_cpu_text", color=(255, 220, 100))
|
||||||
|
dpg.add_text("Lag", color=_LABEL_COLOR)
|
||||||
|
dpg.add_text("0.0ms", tag="perf_lag_text", color=(255, 180, 80))
|
||||||
|
|
||||||
|
dpg.add_spacer(height=4)
|
||||||
|
dpg.add_plot(label="Frame Time (ms)", tag="plot_frame", height=140, width=-1, no_mouse_pos=True)
|
||||||
|
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True, parent="plot_frame")
|
||||||
|
with dpg.plot_axis(dpg.mvYAxis, label="ms", tag="axis_frame_y", parent="plot_frame"):
|
||||||
|
dpg.add_line_series(list(range(100)), self.perf_history["frame_time"], label="frame time", tag="perf_frame_plot")
|
||||||
|
dpg.set_axis_limits("axis_frame_y", 0, 50)
|
||||||
|
|
||||||
dpg.add_plot(label="CPU Usage (%)", tag="plot_cpu", height=120, width=-1, no_mouse_pos=True)
|
dpg.add_plot(label="CPU Usage (%)", tag="plot_cpu", height=140, width=-1, no_mouse_pos=True)
|
||||||
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True, parent="plot_cpu")
|
dpg.add_plot_axis(dpg.mvXAxis, label="samples", no_tick_labels=True, parent="plot_cpu")
|
||||||
with dpg.plot_axis(dpg.mvYAxis, label="%", tag="axis_cpu_y", parent="plot_cpu"):
|
with dpg.plot_axis(dpg.mvYAxis, label="%", tag="axis_cpu_y", parent="plot_cpu"):
|
||||||
dpg.add_line_series(list(range(100)), self.perf_history["cpu"], label="cpu usage", tag="perf_cpu_plot")
|
dpg.add_line_series(list(range(100)), self.perf_history["cpu"], label="cpu usage", tag="perf_cpu_plot")
|
||||||
dpg.set_axis_limits("axis_cpu_y", 0, 100)
|
dpg.set_axis_limits("axis_cpu_y", 0, 100)
|
||||||
|
|
||||||
def _build_ui(self):
|
def _build_ui(self):
|
||||||
# Performance tracking handlers
|
# Performance tracking handlers
|
||||||
@@ -2127,6 +2216,7 @@ class App:
|
|||||||
self._build_ai_settings_hub()
|
self._build_ai_settings_hub()
|
||||||
self._build_discussion_hub()
|
self._build_discussion_hub()
|
||||||
self._build_operations_hub()
|
self._build_operations_hub()
|
||||||
|
self._build_diagnostics_window()
|
||||||
|
|
||||||
self._build_theme_window()
|
self._build_theme_window()
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def test_old_windows_removed_from_window_info(app_instance_simple):
|
|||||||
"win_projects", "win_files", "win_screenshots",
|
"win_projects", "win_files", "win_screenshots",
|
||||||
"win_provider", "win_system_prompts",
|
"win_provider", "win_system_prompts",
|
||||||
"win_discussion", "win_message", "win_response",
|
"win_discussion", "win_message", "win_response",
|
||||||
"win_comms", "win_tool_log", "win_diagnostics"
|
"win_comms", "win_tool_log"
|
||||||
]
|
]
|
||||||
|
|
||||||
for tag in old_tags:
|
for tag in old_tags:
|
||||||
|
|||||||
Reference in New Issue
Block a user