chore: archive tools/ui_inspector snapshot before migration removal.
Keep the last in-template copy on main so history can restore it; product continues in jiangchang-ui-recorder. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
7
tools/ui_inspector/adapters/__init__.py
Normal file
7
tools/ui_inspector/adapters/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""App adapters registry (WeCom-specific rules live here, not in generic layers)."""
|
||||
|
||||
from .base import AppAdapter
|
||||
from .registry import get_adapter, register_adapter
|
||||
from . import wecom as _wecom # noqa: F401 — register on import
|
||||
|
||||
__all__ = ["AppAdapter", "get_adapter", "register_adapter"]
|
||||
17
tools/ui_inspector/adapters/base.py
Normal file
17
tools/ui_inspector/adapters/base.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Adapter base protocol."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional, Protocol
|
||||
|
||||
|
||||
class AppAdapter(Protocol):
|
||||
name: str
|
||||
|
||||
def matches(self, *, class_name: str = "", title: str = "", process_name: str = "") -> bool: ...
|
||||
|
||||
def wake_profile(self, *, class_name: str = "", title: str = "") -> Optional[str]: ...
|
||||
|
||||
def cache_key(self, *, profile: str, hwnd: int = 0) -> str: ...
|
||||
|
||||
def propose_postconditions(self, changes: list[dict[str, Any]]) -> list[dict[str, Any]]: ...
|
||||
31
tools/ui_inspector/adapters/registry.py
Normal file
31
tools/ui_inspector/adapters/registry.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""Adapter registry."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
_REGISTRY: list[Any] = []
|
||||
|
||||
|
||||
def register_adapter(adapter: Any) -> Any:
|
||||
_REGISTRY.append(adapter)
|
||||
return adapter
|
||||
|
||||
|
||||
def get_adapter(
|
||||
*,
|
||||
class_name: str = "",
|
||||
title: str = "",
|
||||
process_name: str = "",
|
||||
) -> Optional[Any]:
|
||||
for ad in _REGISTRY:
|
||||
try:
|
||||
if ad.matches(class_name=class_name, title=title, process_name=process_name):
|
||||
return ad
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def all_adapters() -> list[Any]:
|
||||
return list(_REGISTRY)
|
||||
103
tools/ui_inspector/adapters/wecom.py
Normal file
103
tools/ui_inspector/adapters/wecom.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""WeCom (企业微信) adapter — profile / window / postcondition helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from .registry import register_adapter
|
||||
|
||||
PROFILE_WECOM = "wecom"
|
||||
PROFILE_WECOM_SEARCH = "wecom_search"
|
||||
|
||||
_THRESHOLDS = {
|
||||
PROFILE_WECOM: 80,
|
||||
PROFILE_WECOM_SEARCH: 20,
|
||||
}
|
||||
|
||||
|
||||
class WeComAdapter:
|
||||
name = "wecom"
|
||||
|
||||
def matches(
|
||||
self, *, class_name: str = "", title: str = "", process_name: str = ""
|
||||
) -> bool:
|
||||
return self.wake_profile(class_name=class_name, title=title) is not None or (
|
||||
"WXWork" in (process_name or "")
|
||||
)
|
||||
|
||||
def wake_profile(self, *, class_name: str = "", title: str = "") -> Optional[str]:
|
||||
cls = (class_name or "").strip()
|
||||
title = title or ""
|
||||
if cls == "SearchResultWindow2":
|
||||
return PROFILE_WECOM_SEARCH
|
||||
if cls == "WeWorkWindow":
|
||||
return PROFILE_WECOM
|
||||
if "WeWork" in cls and (
|
||||
"企业微信" in title or "WeCom" in title or "wecom" in title.lower()
|
||||
):
|
||||
return PROFILE_WECOM
|
||||
return None
|
||||
|
||||
def cache_key(self, *, profile: str, hwnd: int = 0) -> str:
|
||||
"""Separate main window vs SearchResultWindow2 warm caches."""
|
||||
return f"{profile}:hwnd:{int(hwnd or 0)}"
|
||||
|
||||
def warm_threshold(self, profile: str) -> int:
|
||||
return int(_THRESHOLDS.get(profile, 80))
|
||||
|
||||
def is_search_popup(self, *, class_name: str = "") -> bool:
|
||||
return (class_name or "") == "SearchResultWindow2"
|
||||
|
||||
def is_main_window(self, *, class_name: str = "") -> bool:
|
||||
return (class_name or "") == "WeWorkWindow"
|
||||
|
||||
def known_ids(self) -> dict[str, str]:
|
||||
return {
|
||||
"searchedit": "search_box",
|
||||
"groupbuddyname": "contact_row_name",
|
||||
"search_name": "search_result_name",
|
||||
"send_rich_edit": "compose_box",
|
||||
"panel_title_label": "panel_title",
|
||||
}
|
||||
|
||||
def propose_postconditions(self, changes: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
props: list[dict[str, Any]] = []
|
||||
for ch in changes or []:
|
||||
kind = ch.get("kind")
|
||||
detail = ch.get("detail") or {}
|
||||
if kind == "popup_disappeared" and "SearchResult" in str(
|
||||
detail.get("class_name") or ""
|
||||
):
|
||||
props.append(
|
||||
{
|
||||
"description": "SearchResultWindow2 disappeared",
|
||||
"kind": "window_absent",
|
||||
"status": "proposed",
|
||||
"detail": detail,
|
||||
}
|
||||
)
|
||||
if kind == "element_appeared":
|
||||
name = str(detail.get("name") or detail.get("automation_id") or "")
|
||||
if name in {"send_rich_edit", "main_chattext", "panel_title_label"}:
|
||||
props.append(
|
||||
{
|
||||
"description": f"WeCom element {name} appeared",
|
||||
"kind": "element_present",
|
||||
"status": "proposed",
|
||||
"detail": detail,
|
||||
}
|
||||
)
|
||||
if kind == "title_changed":
|
||||
props.append(
|
||||
{
|
||||
"description": f"Chat title became {detail.get('to')!r}",
|
||||
"kind": "title_equals",
|
||||
"status": "proposed",
|
||||
"detail": detail,
|
||||
}
|
||||
)
|
||||
return props
|
||||
|
||||
|
||||
wecom_adapter = WeComAdapter()
|
||||
register_adapter(wecom_adapter)
|
||||
Reference in New Issue
Block a user