feat: publish workphone SDK deployment and API docs
This commit is contained in:
242
sdk/agent/skills/search.py
Normal file
242
sdk/agent/skills/search.py
Normal file
@@ -0,0 +1,242 @@
|
||||
"""
|
||||
通用搜索技能 - 在任何APP内执行搜索
|
||||
"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
# 兼容独立运行和包导入
|
||||
_agent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _agent_dir not in sys.path:
|
||||
sys.path.insert(0, _agent_dir)
|
||||
|
||||
try:
|
||||
from skills.base import BaseSkill
|
||||
except ImportError:
|
||||
from ..base import BaseSkill
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SearchSkill(BaseSkill):
|
||||
"""通用搜索技能"""
|
||||
|
||||
PACKAGE = "" # 通用技能
|
||||
NAME = "搜索"
|
||||
|
||||
def search_in_app(self, keyword: str, app_package: str = None,
|
||||
search_box_selector: Dict[str, str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
在指定APP内搜索
|
||||
|
||||
Args:
|
||||
keyword: 搜索关键词
|
||||
app_package: APP包名(如果不在当前APP)
|
||||
search_box_selector: 搜索框选择器 {"type": "text", "value": "搜索"}
|
||||
|
||||
Returns:
|
||||
搜索结果
|
||||
"""
|
||||
try:
|
||||
# 如果指定了APP,先打开
|
||||
if app_package:
|
||||
self.d.app_start(app_package)
|
||||
self.sleep(2)
|
||||
|
||||
# 查找搜索框
|
||||
search_clicked = False
|
||||
|
||||
# 使用自定义选择器
|
||||
if search_box_selector:
|
||||
selector_type = search_box_selector.get("type")
|
||||
selector_value = search_box_selector.get("value")
|
||||
|
||||
if selector_type == "text":
|
||||
if self.d(text=selector_value).exists(timeout=2):
|
||||
self.d(text=selector_value).click()
|
||||
search_clicked = True
|
||||
elif selector_type == "description":
|
||||
if self.d(description=selector_value).exists(timeout=2):
|
||||
self.d(description=selector_value).click()
|
||||
search_clicked = True
|
||||
elif selector_type == "resourceId":
|
||||
if self.d(resourceId=selector_value).exists(timeout=2):
|
||||
self.d(resourceId=selector_value).click()
|
||||
search_clicked = True
|
||||
|
||||
# 默认查找方式
|
||||
if not search_clicked:
|
||||
# 尝试多种方式
|
||||
selectors = [
|
||||
("text", "搜索"),
|
||||
("description", "搜索"),
|
||||
("textContains", "搜索"),
|
||||
("resourceId", "search"),
|
||||
("resourceId", "search_box"),
|
||||
]
|
||||
|
||||
for sel_type, sel_value in selectors:
|
||||
try:
|
||||
if sel_type == "text":
|
||||
if self.d(text=sel_value).exists(timeout=1):
|
||||
self.d(text=sel_value).click()
|
||||
search_clicked = True
|
||||
break
|
||||
elif sel_type == "description":
|
||||
if self.d(description=sel_value).exists(timeout=1):
|
||||
self.d(description=sel_value).click()
|
||||
search_clicked = True
|
||||
break
|
||||
elif sel_type == "textContains":
|
||||
if self.d(textContains=sel_value).exists(timeout=1):
|
||||
self.d(textContains=sel_value).click()
|
||||
search_clicked = True
|
||||
break
|
||||
elif sel_type == "resourceId":
|
||||
if self.d(resourceId=sel_value).exists(timeout=1):
|
||||
self.d(resourceId=sel_value).click()
|
||||
search_clicked = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
# 如果还是没找到,尝试点击屏幕上方(常见搜索框位置)
|
||||
if not search_clicked:
|
||||
info = self.d.info
|
||||
# 点击屏幕上方中间
|
||||
self.d.click(info['displayWidth'] // 2, info['displayHeight'] // 8)
|
||||
self.sleep(0.5)
|
||||
|
||||
# 输入搜索关键词
|
||||
self.sleep(0.5)
|
||||
self.input_text(keyword, clear=True)
|
||||
self.sleep(1)
|
||||
|
||||
# 执行搜索
|
||||
search_executed = False
|
||||
|
||||
# 方法1: 回车
|
||||
try:
|
||||
self.d.press("enter")
|
||||
search_executed = True
|
||||
except:
|
||||
pass
|
||||
|
||||
# 方法2: 点击搜索按钮
|
||||
if not search_executed:
|
||||
if self.click_text("搜索") or self.click_text("确定") or self.click_text("Go"):
|
||||
search_executed = True
|
||||
|
||||
self.sleep(1)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"keyword": keyword,
|
||||
"app_package": app_package or self.d.app_current().get("package"),
|
||||
"message": f"已搜索: {keyword}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"搜索失败: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"keyword": keyword
|
||||
}
|
||||
|
||||
def search_with_voice(self, keyword: str, app_package: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
使用语音输入搜索(适用于中文输入)
|
||||
|
||||
Args:
|
||||
keyword: 搜索关键词
|
||||
app_package: APP包名
|
||||
|
||||
Returns:
|
||||
搜索结果
|
||||
"""
|
||||
try:
|
||||
if app_package:
|
||||
self.d.app_start(app_package)
|
||||
self.sleep(2)
|
||||
|
||||
# 查找搜索框并点击
|
||||
if not self.click_text("搜索"):
|
||||
if not self.click_desc("搜索"):
|
||||
info = self.d.info
|
||||
self.d.click(info['displayWidth'] // 2, info['displayHeight'] // 8)
|
||||
|
||||
self.sleep(0.5)
|
||||
|
||||
# 使用ADB输入中文(需要设备支持)
|
||||
# 方法1: 尝试直接输入
|
||||
try:
|
||||
self.input_text(keyword)
|
||||
except:
|
||||
# 方法2: 使用ADB shell输入
|
||||
self.d.shell(f'am broadcast -a ADB_INPUT_TEXT --es msg "{keyword}"')
|
||||
|
||||
self.sleep(1)
|
||||
|
||||
# 执行搜索
|
||||
self.d.press("enter")
|
||||
self.sleep(1)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"keyword": keyword,
|
||||
"method": "voice_input",
|
||||
"message": f"已搜索: {keyword}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"语音搜索失败: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"keyword": keyword
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _parse_search_results_from_ui(xml: str, limit: int) -> List[Dict[str, Any]]:
|
||||
"""从 UI 树解析 text 作为搜索结果占位"""
|
||||
out = []
|
||||
if not xml:
|
||||
return out
|
||||
skip = {"搜索", "取消", "清除", "发送", "输入"}
|
||||
for i, m in enumerate(re.finditer(r'\btext="([^"]{1,150})"', xml)):
|
||||
if i >= limit:
|
||||
break
|
||||
text = m.group(1).strip()
|
||||
if text and text not in skip:
|
||||
out.append({"index": i + 1, "text": text, "type": "text"})
|
||||
return out
|
||||
|
||||
def get_search_results(self, limit: int = 10) -> Dict[str, Any]:
|
||||
"""
|
||||
获取搜索结果列表
|
||||
|
||||
Args:
|
||||
limit: 结果数量限制
|
||||
|
||||
Returns:
|
||||
搜索结果列表
|
||||
"""
|
||||
try:
|
||||
ui_tree = self.d.dump_hierarchy()
|
||||
results = self._parse_search_results_from_ui(ui_tree, limit)
|
||||
return {
|
||||
"success": True,
|
||||
"results": results,
|
||||
"total": len(results)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"results": []
|
||||
}
|
||||
Reference in New Issue
Block a user