feat(ui): Consolidate GUI into Hub-based layout
This commit is contained in:
425
gui.py
425
gui.py
@@ -447,20 +447,13 @@ class App:
|
|||||||
self.send_thread: threading.Thread | None = None
|
self.send_thread: threading.Thread | None = None
|
||||||
self.models_thread: threading.Thread | None = None
|
self.models_thread: threading.Thread | None = None
|
||||||
self.window_info = {
|
self.window_info = {
|
||||||
"Projects": "win_projects",
|
"Context Hub": "win_context_hub",
|
||||||
"Files": "win_files",
|
"AI Settings Hub": "win_ai_settings_hub",
|
||||||
"Screenshots": "win_screenshots",
|
"Discussion Hub": "win_discussion_hub",
|
||||||
"Discussion History": "win_discussion",
|
"Operations Hub": "win_operations_hub",
|
||||||
"Provider": "win_provider",
|
|
||||||
"Message": "win_message",
|
|
||||||
"Response": "win_response",
|
|
||||||
"Tool Calls": "win_tool_log",
|
|
||||||
"Comms History": "win_comms",
|
|
||||||
"System Prompts": "win_system_prompts",
|
|
||||||
"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",
|
||||||
"Diagnostics": "win_diagnostics",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -870,7 +863,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_diagnostics"):
|
if dpg.is_item_shown("win_operations_hub"):
|
||||||
metrics = self.perf_monitor.get_metrics()
|
metrics = self.perf_monitor.get_metrics()
|
||||||
|
|
||||||
# Update history
|
# Update history
|
||||||
@@ -1796,31 +1789,17 @@ class App:
|
|||||||
format="%.2f",
|
format="%.2f",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_ui(self):
|
def _build_context_hub(self):
|
||||||
# Performance tracking handlers
|
|
||||||
with dpg.handler_registry():
|
|
||||||
dpg.add_mouse_click_handler(callback=lambda: self.perf_monitor.record_input_event())
|
|
||||||
dpg.add_key_press_handler(callback=lambda: self.perf_monitor.record_input_event())
|
|
||||||
|
|
||||||
with dpg.viewport_menu_bar():
|
|
||||||
with dpg.menu(label="Windows"):
|
|
||||||
for label, tag in self.window_info.items():
|
|
||||||
dpg.add_menu_item(label=label, callback=lambda s, a, u: dpg.show_item(u), user_data=tag)
|
|
||||||
with dpg.menu(label="Project"):
|
|
||||||
dpg.add_menu_item(label="Save All", callback=self.cb_save_config)
|
|
||||||
dpg.add_menu_item(label="Reset Session", callback=self.cb_reset_session)
|
|
||||||
dpg.add_menu_item(label="Generate MD Only", callback=self.cb_md_only)
|
|
||||||
|
|
||||||
|
|
||||||
# ---- Projects panel ----
|
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
label="Projects",
|
label="Context Hub",
|
||||||
tag="win_projects",
|
tag="win_context_hub",
|
||||||
pos=(8, 8),
|
pos=(8, 8),
|
||||||
width=400,
|
width=420,
|
||||||
height=380,
|
height=600,
|
||||||
no_close=False,
|
no_close=False,
|
||||||
):
|
):
|
||||||
|
with dpg.tab_bar():
|
||||||
|
with dpg.tab(label="Projects"):
|
||||||
proj_meta = self.project.get("project", {})
|
proj_meta = self.project.get("project", {})
|
||||||
proj_name = proj_meta.get("name", Path(self.active_project_path).stem)
|
proj_name = proj_meta.get("name", Path(self.active_project_path).stem)
|
||||||
dpg.add_text(f"Active: {proj_name}", tag="project_name_text", color=(140, 255, 160))
|
dpg.add_text(f"Active: {proj_name}", tag="project_name_text", color=(140, 255, 160))
|
||||||
@@ -1853,7 +1832,7 @@ class App:
|
|||||||
dpg.add_button(label="Browse##out", callback=self.cb_browse_output)
|
dpg.add_button(label="Browse##out", callback=self.cb_browse_output)
|
||||||
dpg.add_separator()
|
dpg.add_separator()
|
||||||
dpg.add_text("Project Files")
|
dpg.add_text("Project Files")
|
||||||
with dpg.child_window(tag="projects_scroll", height=-60, border=True):
|
with dpg.child_window(tag="projects_scroll", height=120, border=True):
|
||||||
pass
|
pass
|
||||||
with dpg.group(horizontal=True):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_button(label="Add Project", callback=self.cb_add_project)
|
dpg.add_button(label="Add Project", callback=self.cb_add_project)
|
||||||
@@ -1875,21 +1854,13 @@ class App:
|
|||||||
default_value=agent_tools.get(t_name, True)
|
default_value=agent_tools.get(t_name, True)
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- Files panel ----
|
with dpg.tab(label="Files"):
|
||||||
with dpg.window(
|
|
||||||
label="Files",
|
|
||||||
tag="win_files",
|
|
||||||
pos=(8, 396),
|
|
||||||
width=400,
|
|
||||||
height=360,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
dpg.add_text("Base Dir")
|
dpg.add_text("Base Dir")
|
||||||
with dpg.group(horizontal=True):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
tag="files_base_dir",
|
tag="files_base_dir",
|
||||||
default_value=self.project.get("files", {}).get("base_dir", "."),
|
default_value=self.project.get("files", {}).get("base_dir", "."),
|
||||||
width=-220,
|
width=-100,
|
||||||
)
|
)
|
||||||
dpg.add_button(
|
dpg.add_button(
|
||||||
label="Browse##filesbase", callback=self.cb_browse_files_base
|
label="Browse##filesbase", callback=self.cb_browse_files_base
|
||||||
@@ -1903,21 +1874,13 @@ class App:
|
|||||||
dpg.add_button(label="Add File(s)", callback=self.cb_add_files)
|
dpg.add_button(label="Add File(s)", callback=self.cb_add_files)
|
||||||
dpg.add_button(label="Add Wildcard", callback=self.cb_add_wildcard)
|
dpg.add_button(label="Add Wildcard", callback=self.cb_add_wildcard)
|
||||||
|
|
||||||
# ---- Screenshots panel ----
|
with dpg.tab(label="Screenshots"):
|
||||||
with dpg.window(
|
|
||||||
label="Screenshots",
|
|
||||||
tag="win_screenshots",
|
|
||||||
pos=(416, 8),
|
|
||||||
width=400,
|
|
||||||
height=500,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
dpg.add_text("Base Dir")
|
dpg.add_text("Base Dir")
|
||||||
with dpg.group(horizontal=True):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
tag="shots_base_dir",
|
tag="shots_base_dir",
|
||||||
default_value=self.project.get("screenshots", {}).get("base_dir", "."),
|
default_value=self.project.get("screenshots", {}).get("base_dir", "."),
|
||||||
width=-220,
|
width=-100,
|
||||||
)
|
)
|
||||||
dpg.add_button(
|
dpg.add_button(
|
||||||
label="Browse##shotsbase", callback=self.cb_browse_shots_base
|
label="Browse##shotsbase", callback=self.cb_browse_shots_base
|
||||||
@@ -1926,65 +1889,19 @@ class App:
|
|||||||
dpg.add_text("Paths")
|
dpg.add_text("Paths")
|
||||||
with dpg.child_window(tag="shots_scroll", height=-48, border=True):
|
with dpg.child_window(tag="shots_scroll", height=-48, border=True):
|
||||||
pass
|
pass
|
||||||
self._rebuild_shots_list()
|
|
||||||
dpg.add_separator()
|
dpg.add_separator()
|
||||||
dpg.add_button(label="Add Screenshot(s)", callback=self.cb_add_shots)
|
dpg.add_button(label="Add Screenshot(s)", callback=self.cb_add_shots)
|
||||||
|
|
||||||
# ---- Discussion History panel ----
|
def _build_ai_settings_hub(self):
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
label="Discussion History",
|
label="AI Settings Hub",
|
||||||
tag="win_discussion",
|
tag="win_ai_settings_hub",
|
||||||
pos=(824, 8),
|
pos=(8, 616),
|
||||||
width=420,
|
width=420,
|
||||||
height=600,
|
height=556,
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
# Discussion selector section
|
|
||||||
with dpg.collapsing_header(label="Discussions", default_open=True):
|
|
||||||
with dpg.group(tag="disc_selector_group"):
|
|
||||||
pass # populated by _rebuild_discussion_selector
|
|
||||||
|
|
||||||
dpg.add_separator()
|
|
||||||
|
|
||||||
# Entry toolbar
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_button(label="+ Entry", callback=self.cb_disc_append_entry)
|
|
||||||
dpg.add_button(label="-All", callback=self.cb_disc_collapse_all)
|
|
||||||
dpg.add_button(label="+All", callback=self.cb_disc_expand_all)
|
|
||||||
dpg.add_text("Keep Pairs:", color=(160, 160, 160))
|
|
||||||
dpg.add_input_int(tag="disc_truncate_pairs", default_value=2, width=120, min_value=1)
|
|
||||||
dpg.add_button(label="Truncate", callback=self.cb_disc_truncate)
|
|
||||||
dpg.add_button(label="Clear All", callback=self.cb_disc_clear)
|
|
||||||
dpg.add_button(label="Save", callback=self.cb_disc_save)
|
|
||||||
dpg.add_checkbox(
|
|
||||||
tag="auto_add_history",
|
|
||||||
label="Auto-add message & response to history",
|
|
||||||
default_value=self.project.get("discussion", {}).get("auto_add", False)
|
|
||||||
)
|
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.collapsing_header(label="Roles", default_open=False):
|
|
||||||
with dpg.child_window(tag="disc_roles_scroll", height=96, border=True):
|
|
||||||
pass
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_input_text(
|
|
||||||
tag="disc_new_role_input",
|
|
||||||
hint="New role name",
|
|
||||||
width=-72,
|
|
||||||
)
|
|
||||||
dpg.add_button(label="Add", callback=self.cb_disc_add_role)
|
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.child_window(tag="disc_scroll", height=-1, border=False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ---- Provider panel ----
|
|
||||||
with dpg.window(
|
|
||||||
label="Provider",
|
|
||||||
tag="win_provider",
|
|
||||||
pos=(1252, 8),
|
|
||||||
width=420,
|
|
||||||
height=260,
|
|
||||||
no_close=False,
|
no_close=False,
|
||||||
):
|
):
|
||||||
|
with dpg.collapsing_header(label="Provider & Models", default_open=True):
|
||||||
dpg.add_text("Provider")
|
dpg.add_text("Provider")
|
||||||
dpg.add_combo(
|
dpg.add_combo(
|
||||||
tag="provider_combo",
|
tag="provider_combo",
|
||||||
@@ -1993,7 +1910,6 @@ class App:
|
|||||||
width=-1,
|
width=-1,
|
||||||
callback=self.cb_provider_changed,
|
callback=self.cb_provider_changed,
|
||||||
)
|
)
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.group(horizontal=True):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_text("Model")
|
dpg.add_text("Model")
|
||||||
dpg.add_button(label="Fetch Models", callback=self.cb_fetch_models)
|
dpg.add_button(label="Fetch Models", callback=self.cb_fetch_models)
|
||||||
@@ -2011,109 +1927,14 @@ class App:
|
|||||||
dpg.add_progress_bar(tag="token_budget_bar", default_value=0.0, width=-1)
|
dpg.add_progress_bar(tag="token_budget_bar", default_value=0.0, width=-1)
|
||||||
dpg.add_text("0 / 0", tag="token_budget_label")
|
dpg.add_text("0 / 0", tag="token_budget_label")
|
||||||
dpg.add_text("", tag="gemini_cache_label", show=False)
|
dpg.add_text("", tag="gemini_cache_label", show=False)
|
||||||
dpg.add_separator()
|
|
||||||
dpg.add_text("Parameters")
|
with dpg.collapsing_header(label="Parameters", default_open=True):
|
||||||
dpg.add_input_float(tag="ai_temperature", label="Temperature", default_value=self.temperature, min_value=0.0, max_value=2.0)
|
dpg.add_input_float(tag="ai_temperature", label="Temperature", default_value=self.temperature, min_value=0.0, max_value=2.0)
|
||||||
dpg.add_input_int(tag="ai_max_tokens", label="Max Tokens (Output)", default_value=self.max_tokens, step=1024)
|
dpg.add_input_int(tag="ai_max_tokens", label="Max Tokens (Output)", default_value=self.max_tokens, step=1024)
|
||||||
dpg.add_input_int(tag="ai_history_trunc", label="History Truncation Limit", default_value=self.history_trunc_limit, step=1024)
|
dpg.add_input_int(tag="ai_history_trunc", label="History Truncation Limit", default_value=self.history_trunc_limit, step=1024)
|
||||||
|
|
||||||
# ---- Message panel ----
|
with dpg.collapsing_header(label="System Prompts", default_open=False):
|
||||||
with dpg.window(
|
dpg.add_text("Global System Prompt")
|
||||||
label="Message",
|
|
||||||
tag="win_message",
|
|
||||||
pos=(1252, 276),
|
|
||||||
width=420,
|
|
||||||
height=280,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
dpg.add_input_text(
|
|
||||||
tag="ai_input",
|
|
||||||
multiline=True,
|
|
||||||
width=-1,
|
|
||||||
height=-64,
|
|
||||||
)
|
|
||||||
dpg.add_separator()
|
|
||||||
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)
|
|
||||||
|
|
||||||
# ---- Response panel ----
|
|
||||||
with dpg.window(
|
|
||||||
label="Response",
|
|
||||||
tag="win_response",
|
|
||||||
pos=(1252, 564),
|
|
||||||
width=420,
|
|
||||||
height=300,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
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)
|
|
||||||
|
|
||||||
# ---- Tool Calls panel ----
|
|
||||||
with dpg.window(
|
|
||||||
label="Tool Calls",
|
|
||||||
tag="win_tool_log",
|
|
||||||
pos=(1252, 872),
|
|
||||||
width=420,
|
|
||||||
height=300,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_text("Tool call history")
|
|
||||||
dpg.add_button(label="Clear", callback=self.cb_clear_tool_log)
|
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.child_window(tag="tool_log_scroll", height=-1, border=False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ---- Comms History panel ----
|
|
||||||
with dpg.window(
|
|
||||||
label="Comms History",
|
|
||||||
tag="win_comms",
|
|
||||||
pos=(1680, 8),
|
|
||||||
width=520,
|
|
||||||
height=1164,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_text("Status: idle", tag="ai_status", color=(200, 220, 160))
|
|
||||||
dpg.add_spacer(width=16)
|
|
||||||
dpg.add_text("Tokens: 0 (In: 0 Out: 0)", tag="ai_token_usage", color=(180, 255, 180))
|
|
||||||
dpg.add_spacer(width=16)
|
|
||||||
dpg.add_button(label="Clear", callback=self.cb_clear_comms)
|
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.group(horizontal=True):
|
|
||||||
dpg.add_text("OUT", color=_DIR_COLORS["OUT"])
|
|
||||||
dpg.add_text("request", color=_KIND_COLORS["request"])
|
|
||||||
dpg.add_text("tool_call", color=_KIND_COLORS["tool_call"])
|
|
||||||
dpg.add_spacer(width=8)
|
|
||||||
dpg.add_text("IN", color=_DIR_COLORS["IN"])
|
|
||||||
dpg.add_text("response", color=_KIND_COLORS["response"])
|
|
||||||
dpg.add_text("tool_result", color=_KIND_COLORS["tool_result"])
|
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.child_window(tag="comms_scroll", height=-1, border=False, horizontal_scrollbar=True):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ---- System Prompts panel ----
|
|
||||||
with dpg.window(
|
|
||||||
label="System Prompts",
|
|
||||||
tag="win_system_prompts",
|
|
||||||
pos=(416, 804),
|
|
||||||
width=400,
|
|
||||||
height=300,
|
|
||||||
no_close=False,
|
|
||||||
):
|
|
||||||
dpg.add_text("Global System Prompt (all projects)")
|
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
tag="global_system_prompt",
|
tag="global_system_prompt",
|
||||||
default_value=self.config.get("ai", {}).get("system_prompt", ""),
|
default_value=self.config.get("ai", {}).get("system_prompt", ""),
|
||||||
@@ -2131,6 +1952,158 @@ class App:
|
|||||||
height=100,
|
height=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _build_discussion_hub(self):
|
||||||
|
with dpg.window(
|
||||||
|
label="Discussion Hub",
|
||||||
|
tag="win_discussion_hub",
|
||||||
|
pos=(436, 8),
|
||||||
|
width=800,
|
||||||
|
height=1164,
|
||||||
|
no_close=False,
|
||||||
|
):
|
||||||
|
# History at Top
|
||||||
|
with dpg.child_window(tag="disc_history_section", height=-400, border=True):
|
||||||
|
# Discussion selector section
|
||||||
|
with dpg.collapsing_header(label="Discussions", default_open=False):
|
||||||
|
with dpg.group(tag="disc_selector_group"):
|
||||||
|
pass # populated by _rebuild_discussion_selector
|
||||||
|
|
||||||
|
dpg.add_separator()
|
||||||
|
|
||||||
|
# Entry toolbar
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_button(label="+ Entry", callback=self.cb_disc_append_entry)
|
||||||
|
dpg.add_button(label="-All", callback=self.cb_disc_collapse_all)
|
||||||
|
dpg.add_button(label="+All", callback=self.cb_disc_expand_all)
|
||||||
|
dpg.add_text("Keep Pairs:", color=(160, 160, 160))
|
||||||
|
dpg.add_input_int(tag="disc_truncate_pairs", default_value=2, width=80, min_value=1)
|
||||||
|
dpg.add_button(label="Truncate", callback=self.cb_disc_truncate)
|
||||||
|
dpg.add_button(label="Clear All", callback=self.cb_disc_clear)
|
||||||
|
dpg.add_button(label="Save", callback=self.cb_disc_save)
|
||||||
|
|
||||||
|
dpg.add_checkbox(
|
||||||
|
tag="auto_add_history",
|
||||||
|
label="Auto-add message & response to history",
|
||||||
|
default_value=self.project.get("discussion", {}).get("auto_add", False)
|
||||||
|
)
|
||||||
|
dpg.add_separator()
|
||||||
|
with dpg.collapsing_header(label="Roles", default_open=False):
|
||||||
|
with dpg.child_window(tag="disc_roles_scroll", height=96, border=True):
|
||||||
|
pass
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_input_text(tag="disc_new_role_input", hint="New role name", width=-72)
|
||||||
|
dpg.add_button(label="Add", callback=self.cb_disc_add_role)
|
||||||
|
dpg.add_separator()
|
||||||
|
with dpg.child_window(tag="disc_scroll", height=-1, border=False):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Message Composer in Middle
|
||||||
|
dpg.add_text("Message", color=_SUBHDR_COLOR)
|
||||||
|
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()
|
||||||
|
|
||||||
|
# AI Response at Bottom
|
||||||
|
dpg.add_text("AI Response", color=_SUBHDR_COLOR)
|
||||||
|
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):
|
||||||
|
with dpg.window(
|
||||||
|
label="Operations Hub",
|
||||||
|
tag="win_operations_hub",
|
||||||
|
pos=(1244, 8),
|
||||||
|
width=428,
|
||||||
|
height=1164,
|
||||||
|
no_close=False,
|
||||||
|
):
|
||||||
|
with dpg.tab_bar():
|
||||||
|
with dpg.tab(label="Comms Log"):
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_text("Status: idle", tag="ai_status", color=(200, 220, 160))
|
||||||
|
dpg.add_spacer(width=16)
|
||||||
|
dpg.add_button(label="Clear", callback=self.cb_clear_comms)
|
||||||
|
dpg.add_text("Tokens: 0 (In: 0 Out: 0)", tag="ai_token_usage", color=(180, 255, 180))
|
||||||
|
dpg.add_separator()
|
||||||
|
with dpg.child_window(tag="comms_scroll", height=-1, border=False, horizontal_scrollbar=True):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with dpg.tab(label="Tool Log"):
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_text("Tool call history")
|
||||||
|
dpg.add_button(label="Clear", callback=self.cb_clear_tool_log)
|
||||||
|
dpg.add_separator()
|
||||||
|
with dpg.child_window(tag="tool_log_scroll", height=-1, border=False):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with dpg.tab(label="Diagnostics"):
|
||||||
|
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))
|
||||||
|
|
||||||
|
dpg.add_plot(label="Frame Time (ms)", tag="plot_frame", height=120, 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)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
dpg.add_plot(label="CPU Usage (%)", tag="plot_cpu", height=120, width=-1, no_mouse_pos=True)
|
||||||
|
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"):
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _build_ui(self):
|
||||||
|
# Performance tracking handlers
|
||||||
|
with dpg.handler_registry():
|
||||||
|
dpg.add_mouse_click_handler(callback=lambda: self.perf_monitor.record_input_event())
|
||||||
|
dpg.add_key_press_handler(callback=lambda: self.perf_monitor.record_input_event())
|
||||||
|
|
||||||
|
with dpg.viewport_menu_bar():
|
||||||
|
with dpg.menu(label="Windows"):
|
||||||
|
for label, tag in self.window_info.items():
|
||||||
|
dpg.add_menu_item(label=label, callback=lambda s, a, u: dpg.show_item(u), user_data=tag)
|
||||||
|
with dpg.menu(label="Project"):
|
||||||
|
dpg.add_menu_item(label="Save All", callback=self.cb_save_config)
|
||||||
|
dpg.add_menu_item(label="Reset Session", callback=self.cb_reset_session)
|
||||||
|
dpg.add_menu_item(label="Generate MD Only", callback=self.cb_md_only)
|
||||||
|
|
||||||
|
# Build Hubs
|
||||||
|
self._build_context_hub()
|
||||||
|
self._build_ai_settings_hub()
|
||||||
|
self._build_discussion_hub()
|
||||||
|
self._build_operations_hub()
|
||||||
|
|
||||||
self._build_theme_window()
|
self._build_theme_window()
|
||||||
|
|
||||||
# ---- Script Output Popup ----
|
# ---- Script Output Popup ----
|
||||||
@@ -2195,42 +2168,6 @@ class App:
|
|||||||
with dpg.child_window(tag="text_viewer_wrap_container", width=-1, height=-1, border=False, show=False):
|
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)
|
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))
|
|
||||||
|
|
||||||
dpg.add_plot(label="Frame Time (ms)", tag="plot_frame", height=100, 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)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
dpg.add_plot(label="CPU Usage (%)", tag="plot_cpu", height=100, width=-1, no_mouse_pos=True)
|
|
||||||
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"):
|
|
||||||
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)
|
|
||||||
|
|
||||||
def _process_pending_gui_tasks(self):
|
def _process_pending_gui_tasks(self):
|
||||||
"""Processes tasks queued from background threads on the main thread."""
|
"""Processes tasks queued from background threads on the main thread."""
|
||||||
if not self._pending_gui_tasks:
|
if not self._pending_gui_tasks:
|
||||||
@@ -2368,8 +2305,8 @@ class App:
|
|||||||
self._trigger_blink = False
|
self._trigger_blink = False
|
||||||
self._is_blinking = True
|
self._is_blinking = True
|
||||||
self._blink_start_time = time.time()
|
self._blink_start_time = time.time()
|
||||||
if dpg.does_item_exist("win_response"):
|
if dpg.does_item_exist("win_discussion_hub"):
|
||||||
dpg.focus_item("win_response")
|
dpg.focus_item("win_discussion_hub")
|
||||||
|
|
||||||
if self._is_blinking:
|
if self._is_blinking:
|
||||||
elapsed = time.time() - self._blink_start_time
|
elapsed = time.time() - self._blink_start_time
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ def app_instance():
|
|||||||
dpg.destroy_context()
|
dpg.destroy_context()
|
||||||
|
|
||||||
def test_diagnostics_panel_initialization(app_instance):
|
def test_diagnostics_panel_initialization(app_instance):
|
||||||
assert "Diagnostics" in app_instance.window_info
|
assert "Operations Hub" in app_instance.window_info
|
||||||
assert app_instance.window_info["Diagnostics"] == "win_diagnostics"
|
assert app_instance.window_info["Operations Hub"] == "win_operations_hub"
|
||||||
assert "frame_time" in app_instance.perf_history
|
assert "frame_time" in app_instance.perf_history
|
||||||
assert len(app_instance.perf_history["frame_time"]) == 100
|
assert len(app_instance.perf_history["frame_time"]) == 100
|
||||||
|
|
||||||
|
|||||||
61
tests/test_layout_reorganization.py
Normal file
61
tests/test_layout_reorganization.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import pytest
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
# Ensure project root is in path
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
|
||||||
|
# Load gui.py
|
||||||
|
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
|
||||||
|
|
||||||
|
def test_new_hubs_defined_in_window_info():
|
||||||
|
"""
|
||||||
|
Verifies that the new consolidated Hub windows are defined in the App's window_info.
|
||||||
|
This ensures they will be available in the 'Windows' menu.
|
||||||
|
"""
|
||||||
|
# We don't need a full App instance with DPG context for this,
|
||||||
|
# as window_info is initialized in __init__ before DPG starts.
|
||||||
|
# But we mock load_config to avoid file access.
|
||||||
|
from unittest.mock import patch
|
||||||
|
with patch('gui.load_config', return_value={}):
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
expected_hubs = {
|
||||||
|
"Context Hub": "win_context_hub",
|
||||||
|
"AI Settings Hub": "win_ai_settings_hub",
|
||||||
|
"Discussion Hub": "win_discussion_hub",
|
||||||
|
"Operations Hub": "win_operations_hub",
|
||||||
|
}
|
||||||
|
|
||||||
|
for label, tag in expected_hubs.items():
|
||||||
|
assert tag in app.window_info.values(), f"Expected window tag {tag} not found in window_info"
|
||||||
|
# Check if the label matches (or is present)
|
||||||
|
found = False
|
||||||
|
for l, t in app.window_info.items():
|
||||||
|
if t == tag:
|
||||||
|
found = True
|
||||||
|
assert l == label or label in l, f"Label mismatch for {tag}: expected {label}, found {l}"
|
||||||
|
assert found, f"Expected window label {label} not found in window_info"
|
||||||
|
|
||||||
|
def test_old_windows_removed_from_window_info():
|
||||||
|
"""
|
||||||
|
Verifies that the old fragmented windows are removed from window_info.
|
||||||
|
"""
|
||||||
|
from unittest.mock import patch
|
||||||
|
with patch('gui.load_config', return_value={}):
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
old_tags = [
|
||||||
|
"win_projects", "win_files", "win_screenshots",
|
||||||
|
"win_provider", "win_system_prompts",
|
||||||
|
"win_discussion", "win_message", "win_response",
|
||||||
|
"win_comms", "win_tool_log", "win_diagnostics"
|
||||||
|
]
|
||||||
|
|
||||||
|
for tag in old_tags:
|
||||||
|
assert tag not in app.window_info.values(), f"Old window tag {tag} should have been removed from window_info"
|
||||||
Reference in New Issue
Block a user