autoglm-gui 0.3.1__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- AutoGLM_GUI/__main__.py +6 -2
- AutoGLM_GUI/adb_plus/__init__.py +8 -1
- AutoGLM_GUI/adb_plus/screenshot.py +1 -4
- AutoGLM_GUI/adb_plus/touch.py +92 -0
- AutoGLM_GUI/api/__init__.py +66 -0
- AutoGLM_GUI/api/agents.py +231 -0
- AutoGLM_GUI/api/control.py +111 -0
- AutoGLM_GUI/api/devices.py +29 -0
- AutoGLM_GUI/api/media.py +163 -0
- AutoGLM_GUI/schemas.py +127 -0
- AutoGLM_GUI/scrcpy_stream.py +65 -28
- AutoGLM_GUI/server.py +2 -491
- AutoGLM_GUI/state.py +33 -0
- AutoGLM_GUI/static/assets/{about-C71SI8ZQ.js → about-gHEqXVMQ.js} +1 -1
- AutoGLM_GUI/static/assets/chat-6a-qTECg.js +25 -0
- AutoGLM_GUI/static/assets/index-C8KPPfxe.js +10 -0
- AutoGLM_GUI/static/assets/index-D2-3f619.css +1 -0
- AutoGLM_GUI/static/assets/{index-DUCan6m6.js → index-DgzeSwgt.js} +1 -1
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/version.py +8 -0
- {autoglm_gui-0.3.1.dist-info → autoglm_gui-0.4.1.dist-info}/METADATA +64 -9
- autoglm_gui-0.4.1.dist-info/RECORD +44 -0
- phone_agent/adb/connection.py +0 -1
- phone_agent/adb/device.py +0 -2
- phone_agent/adb/input.py +0 -1
- phone_agent/adb/screenshot.py +0 -1
- phone_agent/agent.py +1 -1
- AutoGLM_GUI/static/assets/chat-C6WtEfKW.js +0 -14
- AutoGLM_GUI/static/assets/index-Dd1xMRCa.css +0 -1
- AutoGLM_GUI/static/assets/index-RqglIZxV.js +0 -10
- autoglm_gui-0.3.1.dist-info/RECORD +0 -35
- {autoglm_gui-0.3.1.dist-info → autoglm_gui-0.4.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-0.3.1.dist-info → autoglm_gui-0.4.1.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-0.3.1.dist-info → autoglm_gui-0.4.1.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/__main__.py
CHANGED
|
@@ -12,7 +12,9 @@ import webbrowser
|
|
|
12
12
|
DEFAULT_MODEL_NAME = "autoglm-phone-9b"
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def find_available_port(
|
|
15
|
+
def find_available_port(
|
|
16
|
+
start_port: int = 8000, max_attempts: int = 100, host: str = "127.0.0.1"
|
|
17
|
+
) -> int:
|
|
16
18
|
"""Find an available port starting from start_port.
|
|
17
19
|
|
|
18
20
|
Args:
|
|
@@ -52,7 +54,9 @@ def open_browser(host: str, port: int, delay: float = 1.5) -> None:
|
|
|
52
54
|
|
|
53
55
|
def _open():
|
|
54
56
|
time.sleep(delay)
|
|
55
|
-
url =
|
|
57
|
+
url = (
|
|
58
|
+
f"http://127.0.0.1:{port}" if host == "0.0.0.0" else f"http://{host}:{port}"
|
|
59
|
+
)
|
|
56
60
|
try:
|
|
57
61
|
webbrowser.open(url)
|
|
58
62
|
except Exception as e:
|
AutoGLM_GUI/adb_plus/__init__.py
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
"""Lightweight ADB helpers with a more robust screenshot implementation."""
|
|
2
2
|
|
|
3
3
|
from .screenshot import Screenshot, capture_screenshot
|
|
4
|
+
from .touch import touch_down, touch_move, touch_up
|
|
4
5
|
|
|
5
|
-
__all__ = [
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Screenshot",
|
|
8
|
+
"capture_screenshot",
|
|
9
|
+
"touch_down",
|
|
10
|
+
"touch_move",
|
|
11
|
+
"touch_up",
|
|
12
|
+
]
|
|
@@ -10,7 +10,6 @@ import base64
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
from io import BytesIO
|
|
13
|
-
from typing import Iterable
|
|
14
13
|
|
|
15
14
|
from PIL import Image
|
|
16
15
|
|
|
@@ -72,9 +71,7 @@ def capture_screenshot(
|
|
|
72
71
|
return _fallback_screenshot()
|
|
73
72
|
|
|
74
73
|
|
|
75
|
-
def _try_capture(
|
|
76
|
-
device_id: str | None, adb_path: str, timeout: int
|
|
77
|
-
) -> bytes | None:
|
|
74
|
+
def _try_capture(device_id: str | None, adb_path: str, timeout: int) -> bytes | None:
|
|
78
75
|
"""Run exec-out screencap and return raw bytes or None on failure."""
|
|
79
76
|
cmd: list[str | bytes] = [adb_path]
|
|
80
77
|
if device_id:
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Touch control utilities using ADB motion events for real-time dragging."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _get_adb_prefix(device_id: str | None, adb_path: str = "adb") -> list[str]:
|
|
8
|
+
"""Get ADB command prefix with optional device specifier."""
|
|
9
|
+
if device_id:
|
|
10
|
+
return [adb_path, "-s", device_id]
|
|
11
|
+
return [adb_path]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def touch_down(
|
|
15
|
+
x: int,
|
|
16
|
+
y: int,
|
|
17
|
+
device_id: str | None = None,
|
|
18
|
+
delay: float = 0.0,
|
|
19
|
+
adb_path: str = "adb",
|
|
20
|
+
) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Send touch DOWN event at specified coordinates.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
x: X coordinate.
|
|
26
|
+
y: Y coordinate.
|
|
27
|
+
device_id: Optional ADB device ID.
|
|
28
|
+
delay: Delay in seconds after event (default: 0.0 for real-time).
|
|
29
|
+
adb_path: Path to adb binary.
|
|
30
|
+
"""
|
|
31
|
+
adb_prefix = _get_adb_prefix(device_id, adb_path)
|
|
32
|
+
|
|
33
|
+
subprocess.run(
|
|
34
|
+
adb_prefix + ["shell", "input", "motionevent", "DOWN", str(x), str(y)],
|
|
35
|
+
capture_output=True,
|
|
36
|
+
)
|
|
37
|
+
if delay > 0:
|
|
38
|
+
time.sleep(delay)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def touch_move(
|
|
42
|
+
x: int,
|
|
43
|
+
y: int,
|
|
44
|
+
device_id: str | None = None,
|
|
45
|
+
delay: float = 0.0,
|
|
46
|
+
adb_path: str = "adb",
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Send touch MOVE event at specified coordinates.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
x: X coordinate.
|
|
53
|
+
y: Y coordinate.
|
|
54
|
+
device_id: Optional ADB device ID.
|
|
55
|
+
delay: Delay in seconds after event (default: 0.0 for real-time).
|
|
56
|
+
adb_path: Path to adb binary.
|
|
57
|
+
"""
|
|
58
|
+
adb_prefix = _get_adb_prefix(device_id, adb_path)
|
|
59
|
+
|
|
60
|
+
subprocess.run(
|
|
61
|
+
adb_prefix + ["shell", "input", "motionevent", "MOVE", str(x), str(y)],
|
|
62
|
+
capture_output=True,
|
|
63
|
+
)
|
|
64
|
+
if delay > 0:
|
|
65
|
+
time.sleep(delay)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def touch_up(
|
|
69
|
+
x: int,
|
|
70
|
+
y: int,
|
|
71
|
+
device_id: str | None = None,
|
|
72
|
+
delay: float = 0.0,
|
|
73
|
+
adb_path: str = "adb",
|
|
74
|
+
) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Send touch UP event at specified coordinates.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
x: X coordinate.
|
|
80
|
+
y: Y coordinate.
|
|
81
|
+
device_id: Optional ADB device ID.
|
|
82
|
+
delay: Delay in seconds after event (default: 0.0 for real-time).
|
|
83
|
+
adb_path: Path to adb binary.
|
|
84
|
+
"""
|
|
85
|
+
adb_prefix = _get_adb_prefix(device_id, adb_path)
|
|
86
|
+
|
|
87
|
+
subprocess.run(
|
|
88
|
+
adb_prefix + ["shell", "input", "motionevent", "UP", str(x), str(y)],
|
|
89
|
+
capture_output=True,
|
|
90
|
+
)
|
|
91
|
+
if delay > 0:
|
|
92
|
+
time.sleep(delay)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""FastAPI application factory and route registration."""
|
|
2
|
+
|
|
3
|
+
from importlib.resources import files
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
8
|
+
from fastapi.responses import FileResponse
|
|
9
|
+
from fastapi.staticfiles import StaticFiles
|
|
10
|
+
|
|
11
|
+
from AutoGLM_GUI.version import APP_VERSION
|
|
12
|
+
|
|
13
|
+
from . import agents, control, devices, media
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_static_dir() -> Path | None:
|
|
17
|
+
"""Locate packaged static assets."""
|
|
18
|
+
try:
|
|
19
|
+
static_dir = files("AutoGLM_GUI").joinpath("static")
|
|
20
|
+
if hasattr(static_dir, "_path"):
|
|
21
|
+
path = Path(str(static_dir))
|
|
22
|
+
if path.exists():
|
|
23
|
+
return path
|
|
24
|
+
path = Path(str(static_dir))
|
|
25
|
+
if path.exists():
|
|
26
|
+
return path
|
|
27
|
+
except (TypeError, FileNotFoundError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_app() -> FastAPI:
|
|
34
|
+
"""Build the FastAPI app with routers and static assets."""
|
|
35
|
+
app = FastAPI(title="AutoGLM-GUI API", version=APP_VERSION)
|
|
36
|
+
|
|
37
|
+
app.add_middleware(
|
|
38
|
+
CORSMiddleware,
|
|
39
|
+
allow_origins=["http://localhost:3000"],
|
|
40
|
+
allow_credentials=True,
|
|
41
|
+
allow_methods=["*"],
|
|
42
|
+
allow_headers=["*"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
app.include_router(agents.router)
|
|
46
|
+
app.include_router(devices.router)
|
|
47
|
+
app.include_router(control.router)
|
|
48
|
+
app.include_router(media.router)
|
|
49
|
+
|
|
50
|
+
static_dir = _get_static_dir()
|
|
51
|
+
if static_dir is not None and static_dir.exists():
|
|
52
|
+
assets_dir = static_dir / "assets"
|
|
53
|
+
if assets_dir.exists():
|
|
54
|
+
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
|
|
55
|
+
|
|
56
|
+
@app.get("/{full_path:path}")
|
|
57
|
+
async def serve_spa(full_path: str) -> FileResponse:
|
|
58
|
+
file_path = static_dir / full_path
|
|
59
|
+
if file_path.is_file():
|
|
60
|
+
return FileResponse(file_path)
|
|
61
|
+
return FileResponse(static_dir / "index.html")
|
|
62
|
+
|
|
63
|
+
return app
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
app = create_app()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Agent lifecycle and chat routes."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, HTTPException
|
|
6
|
+
from fastapi.responses import StreamingResponse
|
|
7
|
+
from phone_agent import PhoneAgent
|
|
8
|
+
from phone_agent.agent import AgentConfig
|
|
9
|
+
from phone_agent.model import ModelConfig
|
|
10
|
+
|
|
11
|
+
from AutoGLM_GUI.schemas import (
|
|
12
|
+
APIAgentConfig,
|
|
13
|
+
APIModelConfig,
|
|
14
|
+
ChatRequest,
|
|
15
|
+
ChatResponse,
|
|
16
|
+
InitRequest,
|
|
17
|
+
ResetRequest,
|
|
18
|
+
StatusResponse,
|
|
19
|
+
)
|
|
20
|
+
from AutoGLM_GUI.state import (
|
|
21
|
+
DEFAULT_API_KEY,
|
|
22
|
+
DEFAULT_BASE_URL,
|
|
23
|
+
DEFAULT_MODEL_NAME,
|
|
24
|
+
agent_configs,
|
|
25
|
+
agents,
|
|
26
|
+
non_blocking_takeover,
|
|
27
|
+
)
|
|
28
|
+
from AutoGLM_GUI.version import APP_VERSION
|
|
29
|
+
|
|
30
|
+
router = APIRouter()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.post("/api/init")
|
|
34
|
+
def init_agent(request: InitRequest) -> dict:
|
|
35
|
+
"""初始化 PhoneAgent(多设备支持)。"""
|
|
36
|
+
req_model_config = request.model or APIModelConfig()
|
|
37
|
+
req_agent_config = request.agent or APIAgentConfig()
|
|
38
|
+
|
|
39
|
+
device_id = req_agent_config.device_id
|
|
40
|
+
if not device_id:
|
|
41
|
+
raise HTTPException(
|
|
42
|
+
status_code=400, detail="device_id is required in agent_config"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
base_url = req_model_config.base_url or DEFAULT_BASE_URL
|
|
46
|
+
api_key = req_model_config.api_key or DEFAULT_API_KEY
|
|
47
|
+
model_name = req_model_config.model_name or DEFAULT_MODEL_NAME
|
|
48
|
+
|
|
49
|
+
if not base_url:
|
|
50
|
+
raise HTTPException(
|
|
51
|
+
status_code=400, detail="base_url is required (in model_config or env)"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
model_config = ModelConfig(
|
|
55
|
+
base_url=base_url,
|
|
56
|
+
api_key=api_key,
|
|
57
|
+
model_name=model_name,
|
|
58
|
+
max_tokens=req_model_config.max_tokens,
|
|
59
|
+
temperature=req_model_config.temperature,
|
|
60
|
+
top_p=req_model_config.top_p,
|
|
61
|
+
frequency_penalty=req_model_config.frequency_penalty,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
agent_config = AgentConfig(
|
|
65
|
+
max_steps=req_agent_config.max_steps,
|
|
66
|
+
device_id=device_id,
|
|
67
|
+
lang=req_agent_config.lang,
|
|
68
|
+
system_prompt=req_agent_config.system_prompt,
|
|
69
|
+
verbose=req_agent_config.verbose,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
agents[device_id] = PhoneAgent(
|
|
73
|
+
model_config=model_config,
|
|
74
|
+
agent_config=agent_config,
|
|
75
|
+
takeover_callback=non_blocking_takeover,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
agent_configs[device_id] = (model_config, agent_config)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"success": True,
|
|
82
|
+
"device_id": device_id,
|
|
83
|
+
"message": f"Agent initialized for device {device_id}",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.post("/api/chat", response_model=ChatResponse)
|
|
88
|
+
def chat(request: ChatRequest) -> ChatResponse:
|
|
89
|
+
"""发送任务给 Agent 并执行。"""
|
|
90
|
+
device_id = request.device_id
|
|
91
|
+
if device_id not in agents:
|
|
92
|
+
raise HTTPException(
|
|
93
|
+
status_code=400, detail="Agent not initialized. Call /api/init first."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
agent = agents[device_id]
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
result = agent.run(request.message)
|
|
100
|
+
steps = agent.step_count
|
|
101
|
+
agent.reset()
|
|
102
|
+
|
|
103
|
+
return ChatResponse(result=result, steps=steps, success=True)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return ChatResponse(result=str(e), steps=0, success=False)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@router.post("/api/chat/stream")
|
|
109
|
+
def chat_stream(request: ChatRequest):
|
|
110
|
+
"""发送任务给 Agent 并实时推送执行进度(SSE,多设备支持)。"""
|
|
111
|
+
device_id = request.device_id
|
|
112
|
+
|
|
113
|
+
if device_id not in agents:
|
|
114
|
+
raise HTTPException(
|
|
115
|
+
status_code=400,
|
|
116
|
+
detail=f"Device {device_id} not initialized. Call /api/init first.",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
agent = agents[device_id]
|
|
120
|
+
|
|
121
|
+
def event_generator():
|
|
122
|
+
"""SSE 事件生成器"""
|
|
123
|
+
try:
|
|
124
|
+
step_result = agent.step(request.message)
|
|
125
|
+
while True:
|
|
126
|
+
event_data = {
|
|
127
|
+
"type": "step",
|
|
128
|
+
"step": agent.step_count,
|
|
129
|
+
"thinking": step_result.thinking,
|
|
130
|
+
"action": step_result.action,
|
|
131
|
+
"success": step_result.success,
|
|
132
|
+
"finished": step_result.finished,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
yield "event: step\n"
|
|
136
|
+
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
137
|
+
|
|
138
|
+
if step_result.finished:
|
|
139
|
+
done_data = {
|
|
140
|
+
"type": "done",
|
|
141
|
+
"message": step_result.message,
|
|
142
|
+
"steps": agent.step_count,
|
|
143
|
+
"success": step_result.success,
|
|
144
|
+
}
|
|
145
|
+
yield "event: done\n"
|
|
146
|
+
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
if agent.step_count >= agent.agent_config.max_steps:
|
|
150
|
+
done_data = {
|
|
151
|
+
"type": "done",
|
|
152
|
+
"message": "Max steps reached",
|
|
153
|
+
"steps": agent.step_count,
|
|
154
|
+
"success": step_result.success,
|
|
155
|
+
}
|
|
156
|
+
yield "event: done\n"
|
|
157
|
+
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
step_result = agent.step()
|
|
161
|
+
|
|
162
|
+
agent.reset()
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
error_data = {
|
|
166
|
+
"type": "error",
|
|
167
|
+
"message": str(e),
|
|
168
|
+
}
|
|
169
|
+
yield "event: error\n"
|
|
170
|
+
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
171
|
+
|
|
172
|
+
return StreamingResponse(
|
|
173
|
+
event_generator(),
|
|
174
|
+
media_type="text/event-stream",
|
|
175
|
+
headers={
|
|
176
|
+
"Cache-Control": "no-cache",
|
|
177
|
+
"Connection": "keep-alive",
|
|
178
|
+
"X-Accel-Buffering": "no",
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@router.get("/api/status", response_model=StatusResponse)
|
|
184
|
+
def get_status(device_id: str | None = None) -> StatusResponse:
|
|
185
|
+
"""获取 Agent 状态和版本信息(多设备支持)。"""
|
|
186
|
+
if device_id is None:
|
|
187
|
+
return StatusResponse(
|
|
188
|
+
version=APP_VERSION,
|
|
189
|
+
initialized=len(agents) > 0,
|
|
190
|
+
step_count=0,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if device_id not in agents:
|
|
194
|
+
return StatusResponse(
|
|
195
|
+
version=APP_VERSION,
|
|
196
|
+
initialized=False,
|
|
197
|
+
step_count=0,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
agent = agents[device_id]
|
|
201
|
+
return StatusResponse(
|
|
202
|
+
version=APP_VERSION,
|
|
203
|
+
initialized=True,
|
|
204
|
+
step_count=agent.step_count,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@router.post("/api/reset")
|
|
209
|
+
def reset_agent(request: ResetRequest) -> dict:
|
|
210
|
+
"""重置 Agent 状态(多设备支持)。"""
|
|
211
|
+
device_id = request.device_id
|
|
212
|
+
|
|
213
|
+
if device_id not in agents:
|
|
214
|
+
raise HTTPException(status_code=404, detail=f"Device {device_id} not found")
|
|
215
|
+
|
|
216
|
+
agent = agents[device_id]
|
|
217
|
+
agent.reset()
|
|
218
|
+
|
|
219
|
+
if device_id in agent_configs:
|
|
220
|
+
model_config, agent_config = agent_configs[device_id]
|
|
221
|
+
agents[device_id] = PhoneAgent(
|
|
222
|
+
model_config=model_config,
|
|
223
|
+
agent_config=agent_config,
|
|
224
|
+
takeover_callback=non_blocking_takeover,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
"success": True,
|
|
229
|
+
"device_id": device_id,
|
|
230
|
+
"message": f"Agent reset for device {device_id}",
|
|
231
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Device control routes (tap/swipe/touch)."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
from AutoGLM_GUI.schemas import (
|
|
6
|
+
SwipeRequest,
|
|
7
|
+
SwipeResponse,
|
|
8
|
+
TapRequest,
|
|
9
|
+
TapResponse,
|
|
10
|
+
TouchDownRequest,
|
|
11
|
+
TouchDownResponse,
|
|
12
|
+
TouchMoveRequest,
|
|
13
|
+
TouchMoveResponse,
|
|
14
|
+
TouchUpRequest,
|
|
15
|
+
TouchUpResponse,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
router = APIRouter()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.post("/api/control/tap", response_model=TapResponse)
|
|
22
|
+
def control_tap(request: TapRequest) -> TapResponse:
|
|
23
|
+
"""Execute tap at specified device coordinates."""
|
|
24
|
+
try:
|
|
25
|
+
from phone_agent.adb import tap
|
|
26
|
+
|
|
27
|
+
tap(
|
|
28
|
+
x=request.x,
|
|
29
|
+
y=request.y,
|
|
30
|
+
device_id=request.device_id,
|
|
31
|
+
delay=request.delay,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return TapResponse(success=True)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
return TapResponse(success=False, error=str(e))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.post("/api/control/swipe", response_model=SwipeResponse)
|
|
40
|
+
def control_swipe(request: SwipeRequest) -> SwipeResponse:
|
|
41
|
+
"""Execute swipe from start to end coordinates."""
|
|
42
|
+
try:
|
|
43
|
+
from phone_agent.adb import swipe
|
|
44
|
+
|
|
45
|
+
swipe(
|
|
46
|
+
start_x=request.start_x,
|
|
47
|
+
start_y=request.start_y,
|
|
48
|
+
end_x=request.end_x,
|
|
49
|
+
end_y=request.end_y,
|
|
50
|
+
duration_ms=request.duration_ms,
|
|
51
|
+
device_id=request.device_id,
|
|
52
|
+
delay=request.delay,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return SwipeResponse(success=True)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return SwipeResponse(success=False, error=str(e))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@router.post("/api/control/touch/down", response_model=TouchDownResponse)
|
|
61
|
+
def control_touch_down(request: TouchDownRequest) -> TouchDownResponse:
|
|
62
|
+
"""Send touch DOWN event at specified device coordinates."""
|
|
63
|
+
try:
|
|
64
|
+
from AutoGLM_GUI.adb_plus import touch_down
|
|
65
|
+
|
|
66
|
+
touch_down(
|
|
67
|
+
x=request.x,
|
|
68
|
+
y=request.y,
|
|
69
|
+
device_id=request.device_id,
|
|
70
|
+
delay=request.delay,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return TouchDownResponse(success=True)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return TouchDownResponse(success=False, error=str(e))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@router.post("/api/control/touch/move", response_model=TouchMoveResponse)
|
|
79
|
+
def control_touch_move(request: TouchMoveRequest) -> TouchMoveResponse:
|
|
80
|
+
"""Send touch MOVE event at specified device coordinates."""
|
|
81
|
+
try:
|
|
82
|
+
from AutoGLM_GUI.adb_plus import touch_move
|
|
83
|
+
|
|
84
|
+
touch_move(
|
|
85
|
+
x=request.x,
|
|
86
|
+
y=request.y,
|
|
87
|
+
device_id=request.device_id,
|
|
88
|
+
delay=request.delay,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return TouchMoveResponse(success=True)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
return TouchMoveResponse(success=False, error=str(e))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@router.post("/api/control/touch/up", response_model=TouchUpResponse)
|
|
97
|
+
def control_touch_up(request: TouchUpRequest) -> TouchUpResponse:
|
|
98
|
+
"""Send touch UP event at specified device coordinates."""
|
|
99
|
+
try:
|
|
100
|
+
from AutoGLM_GUI.adb_plus import touch_up
|
|
101
|
+
|
|
102
|
+
touch_up(
|
|
103
|
+
x=request.x,
|
|
104
|
+
y=request.y,
|
|
105
|
+
device_id=request.device_id,
|
|
106
|
+
delay=request.delay,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return TouchUpResponse(success=True)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
return TouchUpResponse(success=False, error=str(e))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Device discovery routes."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
from AutoGLM_GUI.schemas import DeviceListResponse
|
|
6
|
+
from AutoGLM_GUI.state import agents
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("/api/devices", response_model=DeviceListResponse)
|
|
12
|
+
def list_devices() -> DeviceListResponse:
|
|
13
|
+
"""列出所有 ADB 设备。"""
|
|
14
|
+
from phone_agent.adb import list_devices as adb_list
|
|
15
|
+
|
|
16
|
+
adb_devices = adb_list()
|
|
17
|
+
|
|
18
|
+
return DeviceListResponse(
|
|
19
|
+
devices=[
|
|
20
|
+
{
|
|
21
|
+
"id": d.device_id,
|
|
22
|
+
"model": d.model or "Unknown",
|
|
23
|
+
"status": d.status,
|
|
24
|
+
"connection_type": d.connection_type.value,
|
|
25
|
+
"is_initialized": d.device_id in agents,
|
|
26
|
+
}
|
|
27
|
+
for d in adb_devices
|
|
28
|
+
]
|
|
29
|
+
)
|