feat(hooks): extend ApiHookClient and GUI for tab/listbox control

This commit is contained in:
2026-02-23 19:20:20 -05:00
parent 1d674c3a1e
commit f36d539c36
3 changed files with 121 additions and 7 deletions

View File

@@ -83,3 +83,34 @@ class ApiHookClient:
def post_gui(self, gui_data):
return self._make_request('POST', '/api/gui', data=gui_data)
def select_tab(self, tab_bar, tab):
"""Tells the GUI to switch to a specific tab in a tab bar."""
return self.post_gui({
"action": "select_tab",
"tab_bar": tab_bar,
"tab": tab
})
def select_list_item(self, listbox, item_value):
"""Tells the GUI to select an item in a listbox by its value."""
return self.post_gui({
"action": "select_list_item",
"listbox": listbox,
"item_value": item_value
})
def set_value(self, item, value):
"""Sets the value of a GUI item."""
return self.post_gui({
"action": "set_value",
"item": item,
"value": value
})
def click(self, item):
"""Simulates a click on a GUI button or item."""
return self.post_gui({
"action": "click",
"item": item
})

29
gui.py
View File

@@ -2100,10 +2100,10 @@ class App:
height=200,
)
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_button(label="Gen + Send", tag="btn_gen_send", callback=self.cb_generate_send)
dpg.add_button(label="MD Only", tag="btn_md_only", callback=self.cb_md_only)
dpg.add_button(label="Reset", tag="btn_reset", callback=self.cb_reset_session)
dpg.add_button(label="-> History", tag="btn_to_history", callback=self.cb_append_message_to_history)
with dpg.tab(label="AI Response"):
dpg.add_input_text(
@@ -2133,8 +2133,8 @@ class App:
dpg.add_spacer(width=20)
dpg.add_text("LIVE", tag="operations_live_indicator", color=(100, 255, 100), show=False)
with dpg.tab_bar():
with dpg.tab(label="Comms Log"):
with dpg.tab_bar(tag="operations_tabs"):
with dpg.tab(label="Comms Log", tag="tab_comms"):
with dpg.group(horizontal=True):
dpg.add_text("Status: idle", tag="ai_status", color=(200, 220, 160))
dpg.add_spacer(width=16)
@@ -2148,7 +2148,7 @@ class App:
with dpg.child_window(tag="comms_scroll", height=-1, border=False, horizontal_scrollbar=True):
pass
with dpg.tab(label="Tool Log"):
with dpg.tab(label="Tool Log", tag="tab_tool"):
with dpg.group(horizontal=True):
dpg.add_text("Tool call history")
dpg.add_button(label="Clear", callback=self.cb_clear_tool_log)
@@ -2305,6 +2305,21 @@ class App:
cb = dpg.get_item_callback(item)
if cb:
cb()
elif action == "select_tab":
tab_bar = task.get("tab_bar")
tab = task.get("tab")
if tab_bar and dpg.does_item_exist(tab_bar):
dpg.set_value(tab_bar, tab)
elif action == "select_list_item":
listbox = task.get("listbox")
val = task.get("item_value")
if listbox and dpg.does_item_exist(listbox):
dpg.set_value(listbox, val)
cb = dpg.get_item_callback(listbox)
if cb:
# Dear PyGui callbacks for listbox usually receive (sender, app_data, user_data)
# app_data is the selected value.
cb(listbox, val)
elif action == "refresh_api_metrics":
self._refresh_api_metrics(task.get("payload", {}))
except Exception as e:

View File

@@ -0,0 +1,68 @@
import pytest
import sys
import os
# Ensure project root is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from api_hook_client import ApiHookClient
def test_api_client_has_extensions():
client = ApiHookClient()
# These should fail initially as they are not implemented
assert hasattr(client, 'select_tab')
assert hasattr(client, 'select_list_item')
def test_select_tab_integration(live_gui):
client = ApiHookClient()
# We'll need to make sure the tags exist in gui.py
# For now, this is a placeholder for the integration test
response = client.select_tab("operations_tabs", "tab_tool")
assert response == {'status': 'queued'}
def test_select_list_item_integration(live_gui):
client = ApiHookClient()
# Assuming 'Default' discussion exists or we can just test that it queues
response = client.select_list_item("disc_listbox", "Default")
assert response == {'status': 'queued'}
def test_app_processes_new_actions():
import gui
from unittest.mock import MagicMock, patch
import dearpygui.dearpygui as dpg
dpg.create_context()
try:
with patch('gui.load_config', return_value={}), \
patch('gui.PerformanceMonitor'), \
patch('gui.shell_runner'), \
patch('gui.project_manager'), \
patch.object(gui.App, '_load_active_project'):
app = gui.App()
with patch('dearpygui.dearpygui.set_value') as mock_set_value, \
patch('dearpygui.dearpygui.does_item_exist', return_value=True), \
patch('dearpygui.dearpygui.get_item_callback') as mock_get_cb:
# Test select_tab
app._pending_gui_tasks.append({
"action": "select_tab",
"tab_bar": "some_tab_bar",
"tab": "some_tab"
})
app._process_pending_gui_tasks()
mock_set_value.assert_any_call("some_tab_bar", "some_tab")
# Test select_list_item
mock_cb = MagicMock()
mock_get_cb.return_value = mock_cb
app._pending_gui_tasks.append({
"action": "select_list_item",
"listbox": "some_listbox",
"item_value": "some_value"
})
app._process_pending_gui_tasks()
mock_set_value.assert_any_call("some_listbox", "some_value")
mock_cb.assert_called_with("some_listbox", "some_value")
finally:
dpg.destroy_context()