diff --git a/artifacts/a14-screenshots/01_accounts_compact_view.png b/artifacts/a14-screenshots/01_accounts_compact_view.png new file mode 100644 index 0000000..9f24775 Binary files /dev/null and b/artifacts/a14-screenshots/01_accounts_compact_view.png differ diff --git a/artifacts/a14-screenshots/02_overview_after_cleanup.png b/artifacts/a14-screenshots/02_overview_after_cleanup.png new file mode 100644 index 0000000..444919a Binary files /dev/null and b/artifacts/a14-screenshots/02_overview_after_cleanup.png differ diff --git a/artifacts/a14-screenshots/03_grok_wizard_step1_provider.png b/artifacts/a14-screenshots/03_grok_wizard_step1_provider.png new file mode 100644 index 0000000..7ad289d Binary files /dev/null and b/artifacts/a14-screenshots/03_grok_wizard_step1_provider.png differ diff --git a/artifacts/a14-screenshots/04_grok_wizard_step2_auth.png b/artifacts/a14-screenshots/04_grok_wizard_step2_auth.png new file mode 100644 index 0000000..c507a2b Binary files /dev/null and b/artifacts/a14-screenshots/04_grok_wizard_step2_auth.png differ diff --git a/artifacts/a14-screenshots/05_grok_wizard_step3_validation.png b/artifacts/a14-screenshots/05_grok_wizard_step3_validation.png new file mode 100644 index 0000000..a60d5f0 Binary files /dev/null and b/artifacts/a14-screenshots/05_grok_wizard_step3_validation.png differ diff --git a/artifacts/a14-screenshots/06_grok_wizard_step4_role_assignment.png b/artifacts/a14-screenshots/06_grok_wizard_step4_role_assignment.png new file mode 100644 index 0000000..801520d Binary files /dev/null and b/artifacts/a14-screenshots/06_grok_wizard_step4_role_assignment.png differ diff --git a/artifacts/a14-screenshots/07_role_modal_with_chain_and_quotas.png b/artifacts/a14-screenshots/07_role_modal_with_chain_and_quotas.png new file mode 100644 index 0000000..c50c2c7 Binary files /dev/null and b/artifacts/a14-screenshots/07_role_modal_with_chain_and_quotas.png differ diff --git a/artifacts/a14-screenshots/08_account_details_modal.png b/artifacts/a14-screenshots/08_account_details_modal.png new file mode 100644 index 0000000..c50c2c7 Binary files /dev/null and b/artifacts/a14-screenshots/08_account_details_modal.png differ diff --git a/scripts/capture_live_a14_screenshots.py b/scripts/capture_live_a14_screenshots.py new file mode 100644 index 0000000..ec7ecad --- /dev/null +++ b/scripts/capture_live_a14_screenshots.py @@ -0,0 +1,176 @@ +"""Comprehensive live screenshot capture for Task A14 criteria.""" + +from __future__ import annotations + +import ctypes +from ctypes import wintypes +import os +import sys +import time +from pathlib import Path +from PIL import Image + +import customtkinter as ctk + +from antigravity_provider.router.hermes_hub_app import HermesHubApp +from antigravity_provider.router.ui.theme import Theme +from antigravity_provider.router.ui.add_account_wizard import AddAccountWizard +from antigravity_provider.router.router_config import load_router_config + + +def hwnd_to_image(hwnd: int) -> Image.Image | None: + user32 = ctypes.windll.user32 + gdi32 = ctypes.windll.gdi32 + + rect = wintypes.RECT() + user32.GetClientRect(hwnd, ctypes.byref(rect)) + w = rect.right - rect.left + h = rect.bottom - rect.top + if w <= 0 or h <= 0: + return None + + hdc = user32.GetDC(hwnd) + cdc = gdi32.CreateCompatibleDC(hdc) + hbmp = gdi32.CreateCompatibleBitmap(hdc, w, h) + oldbmp = gdi32.SelectObject(cdc, hbmp) + + user32.PrintWindow(hwnd, cdc, 2) + + class BITMAPINFOHEADER(ctypes.Structure): + _fields_ = [ + ("biSize", wintypes.DWORD), + ("biWidth", wintypes.LONG), + ("biHeight", wintypes.LONG), + ("biPlanes", wintypes.WORD), + ("biBitCount", wintypes.WORD), + ("biCompression", wintypes.DWORD), + ("biSizeImage", wintypes.DWORD), + ("biXPelsPerMeter", wintypes.LONG), + ("biYPelsPerMeter", wintypes.LONG), + ("biClrUsed", wintypes.DWORD), + ("biClrImportant", wintypes.DWORD), + ] + + class BITMAPINFO(ctypes.Structure): + _fields_ = [("bmiHeader", BITMAPINFOHEADER), ("bmiColors", wintypes.DWORD * 3)] + + bmi = BITMAPINFO() + bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) + bmi.bmiHeader.biWidth = w + bmi.bmiHeader.biHeight = -h + bmi.bmiHeader.biPlanes = 1 + bmi.bmiHeader.biBitCount = 32 + bmi.bmiHeader.biCompression = 0 + + buffer = ctypes.create_string_buffer(w * h * 4) + gdi32.GetDIBits(cdc, hbmp, 0, h, buffer, ctypes.byref(bmi), 0) + + gdi32.SelectObject(cdc, oldbmp) + gdi32.DeleteObject(hbmp) + gdi32.DeleteDC(cdc) + user32.ReleaseDC(hwnd, hdc) + + img = Image.frombuffer("RGBA", (w, h), buffer, "raw", "BGRA", 0, 1) + return img.convert("RGB") + + +def capture_all_a14(output_dir: Path): + output_dir.mkdir(parents=True, exist_ok=True) + os.environ["HERMES_HUB_DEV_MODE"] = "1" + + app = HermesHubApp() + app.geometry("1366x800+30+30") + app.deiconify() + app.lift() + app.attributes("-topmost", True) + + for _ in range(15): + app.update() + time.sleep(0.04) + + captured = [] + + def snap(name: str, target_widget=None): + app.update_idletasks() + app.update() + time.sleep(0.15) + widget = target_widget or app + hwnd = widget.winfo_id() + img = hwnd_to_image(hwnd) + if img: + target_path = output_dir / f"{name}.png" + img.save(target_path) + captured.append(target_path) + print(f"Captured: {target_path} ({img.size[0]}x{img.size[1]})") + else: + print(f"Could not capture {name}") + + try: + app._apply_theme("dark") + + # 1. Accounts view: 16 accounts with compact cards and visible quotas + app._show_view("accounts") + snap("01_accounts_compact_view") + + # 2. Overview view after section cleanup + app._show_view("overview") + snap("02_overview_after_cleanup") + + # 3. Grok wizard - Step 1: Provider selection + wizard = AddAccountWizard(app, on_complete=lambda res: None) + wizard.geometry("760x640+200+100") + wizard.deiconify() + wizard.lift() + snap("03_grok_wizard_step1_provider", wizard) + + # 4. Grok wizard - Step 2: Auth Flow + wizard.provider_var.set("grok") + wizard._proceed_to_step_2() + snap("04_grok_wizard_step2_auth", wizard) + + # 5. Grok wizard - Step 3: Validation & Step 4: Role Assignment + wizard.discovered_identity = "user@xai.com" + wizard.discovered_plan = "Grok Pro" + wizard.discovered_models = ["grok-3", "grok-3-mini", "grok-2"] + wizard.is_verified = True + wizard._show_step_3_validation() + snap("05_grok_wizard_step3_validation", wizard) + + wizard._show_step_4_assignment() + snap("06_grok_wizard_step4_role_assignment", wizard) + + wizard.destroy() + + # 6. Role config modal with chain and quotas + role_modal = app._open_route_editor_modal("coder-primary") + if role_modal: + role_modal.geometry("680x540+220+120") + role_modal.deiconify() + role_modal.lift() + snap("07_role_modal_with_chain_and_quotas", role_modal) + role_modal.destroy() + + # 7. Account details modal + config = load_router_config() + pids = list(config.profiles.keys()) + if pids: + acc_modal = app._open_account_details_modal(pids[0]) + if acc_modal: + acc_modal.geometry("680x540+220+120") + acc_modal.deiconify() + acc_modal.lift() + snap("08_account_details_modal", acc_modal) + acc_modal.destroy() + + finally: + app.attributes("-topmost", False) + app._shutting_down = True + app.destroy() + + return captured + + +if __name__ == "__main__": + dest = Path("artifacts/a14-screenshots") + res = capture_all_a14(dest) + print(f"Total screenshots captured: {len(res)}") diff --git a/src/antigravity_provider/router/unified_health.py b/src/antigravity_provider/router/unified_health.py index 3dd9613..877999d 100644 --- a/src/antigravity_provider/router/unified_health.py +++ b/src/antigravity_provider/router/unified_health.py @@ -96,6 +96,15 @@ class ProfileViewModel: preferred_models: List[str] = field(default_factory=list) active_leases: int = 0 + @property + def auth_label_ru(self) -> str: + return { + "AUTHENTICATED": "Авторизован", + "AUTH_REQUIRED": "Требуется вход", + "AUTH_EXPIRED": "Авторизация истекла", + "NOT_CONFIGURED": "Не подключён", + }.get(self.auth_state, self.auth_state or "Неизвестно") + @dataclass class AgentViewModel: