feat(api): Add /api/gui/state endpoint and live_gui integration tests
This commit is contained in:
@@ -84,6 +84,11 @@ class ApiHookClient:
|
|||||||
"""Retrieves current MMA status (track, tickets, tier, etc.)"""
|
"""Retrieves current MMA status (track, tickets, tier, etc.)"""
|
||||||
return self._make_request('GET', '/api/gui/mma_status')
|
return self._make_request('GET', '/api/gui/mma_status')
|
||||||
|
|
||||||
|
def get_gui_state(self) -> dict | None:
|
||||||
|
"""Retrieves the current GUI state via /api/gui/state."""
|
||||||
|
resp = self._make_request("GET", "/api/gui/state")
|
||||||
|
return resp if resp else None
|
||||||
|
|
||||||
def push_event(self, event_type: str, payload: dict[str, Any]) -> dict[str, Any] | None:
|
def push_event(self, event_type: str, payload: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
|
"""Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint."""
|
||||||
return self.post_gui({
|
return self.post_gui({
|
||||||
|
|||||||
@@ -165,6 +165,27 @@ class HookHandler(BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
self.send_response(504)
|
self.send_response(504)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
elif self.path == '/api/gui/state':
|
||||||
|
event = threading.Event()
|
||||||
|
result = {}
|
||||||
|
def get_state():
|
||||||
|
try:
|
||||||
|
gettable = _get_app_attr(app, "_gettable_fields", {})
|
||||||
|
for key, attr in gettable.items():
|
||||||
|
result[key] = _get_app_attr(app, attr, None)
|
||||||
|
finally: event.set()
|
||||||
|
lock = _get_app_attr(app, "_pending_gui_tasks_lock")
|
||||||
|
tasks = _get_app_attr(app, "_pending_gui_tasks")
|
||||||
|
if lock and tasks is not None:
|
||||||
|
with lock: tasks.append({"action": "custom_callback", "callback": get_state})
|
||||||
|
if event.wait(timeout=10):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(result).encode("utf-8"))
|
||||||
|
else:
|
||||||
|
self.send_response(504)
|
||||||
|
self.end_headers()
|
||||||
else:
|
else:
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|||||||
@@ -707,6 +707,13 @@ class AppController:
|
|||||||
'manual_approve': 'ui_manual_approve'
|
'manual_approve': 'ui_manual_approve'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self._gettable_fields = dict(self._settable_fields)
|
||||||
|
self._gettable_fields.update({
|
||||||
|
'ui_focus_agent': 'ui_focus_agent',
|
||||||
|
'active_discussion': 'active_discussion',
|
||||||
|
'_track_discussion_active': '_track_discussion_active'
|
||||||
|
})
|
||||||
|
|
||||||
self.hook_server = api_hooks.HookServer(app if app else self)
|
self.hook_server = api_hooks.HookServer(app if app else self)
|
||||||
self.hook_server.start()
|
self.hook_server.start()
|
||||||
|
|
||||||
@@ -1004,6 +1011,15 @@ class AppController:
|
|||||||
"""Returns the health status of the API."""
|
"""Returns the health status of the API."""
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
@api.get("/api/gui/state", dependencies=[Depends(get_api_key)])
|
||||||
|
def get_gui_state() -> dict[str, Any]:
|
||||||
|
"""Returns the current GUI state for specific fields."""
|
||||||
|
gettable = getattr(self, "_gettable_fields", {})
|
||||||
|
state = {}
|
||||||
|
for key, attr in gettable.items():
|
||||||
|
state[key] = getattr(self, attr, None)
|
||||||
|
return state
|
||||||
|
|
||||||
@api.get("/status", dependencies=[Depends(get_api_key)])
|
@api.get("/status", dependencies=[Depends(get_api_key)])
|
||||||
def status() -> dict[str, Any]:
|
def status() -> dict[str, Any]:
|
||||||
"""Returns the current status of the application."""
|
"""Returns the current status of the application."""
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
from gui_2 import App
|
from gui_2 import App
|
||||||
from events import UserRequestEvent
|
from events import UserRequestEvent
|
||||||
|
from api_hook_client import ApiHookClient
|
||||||
|
|
||||||
@pytest.mark.timeout(10)
|
@pytest.mark.timeout(10)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -38,7 +39,9 @@ async def test_user_request_integration_flow(mock_app: App) -> None:
|
|||||||
mock_send.assert_called_once_with(
|
mock_send.assert_called_once_with(
|
||||||
"Context", "Hello AI", ".", [], "History",
|
"Context", "Hello AI", ".", [], "History",
|
||||||
pre_tool_callback=ANY,
|
pre_tool_callback=ANY,
|
||||||
qa_callback=ANY
|
qa_callback=ANY,
|
||||||
|
stream=ANY,
|
||||||
|
stream_callback=ANY
|
||||||
)
|
)
|
||||||
# 4. Wait for the response to propagate to _pending_gui_tasks and update UI
|
# 4. Wait for the response to propagate to _pending_gui_tasks and update UI
|
||||||
# We call _process_pending_gui_tasks manually to simulate a GUI frame update.
|
# We call _process_pending_gui_tasks manually to simulate a GUI frame update.
|
||||||
@@ -85,3 +88,22 @@ async def test_user_request_error_handling(mock_app: App) -> None:
|
|||||||
break
|
break
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
assert success, f"Error state was not reflected in UI. status: {app.ai_status}, response: {app.ai_response}"
|
assert success, f"Error state was not reflected in UI. status: {app.ai_status}, response: {app.ai_response}"
|
||||||
|
|
||||||
|
def test_api_gui_state_live(live_gui) -> None:
|
||||||
|
client = ApiHookClient()
|
||||||
|
client.set_value('current_provider', 'anthropic')
|
||||||
|
client.set_value('current_model', 'claude-3-haiku-20240307')
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
success = False
|
||||||
|
while time.time() - start_time < 10:
|
||||||
|
state = client.get_gui_state()
|
||||||
|
if state and state.get('current_provider') == 'anthropic' and state.get('current_model') == 'claude-3-haiku-20240307':
|
||||||
|
success = True
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
assert success, f"GUI state did not update. Got: {client.get_gui_state()}"
|
||||||
|
final_state = client.get_gui_state()
|
||||||
|
assert final_state['current_provider'] == 'anthropic'
|
||||||
|
assert final_state['current_model'] == 'claude-3-haiku-20240307'
|
||||||
|
|||||||
Reference in New Issue
Block a user