autoglm-gui 1.1.0__py3-none-any.whl → 1.2.0__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.
Files changed (35) hide show
  1. AutoGLM_GUI/adb_plus/__init__.py +5 -1
  2. AutoGLM_GUI/adb_plus/serial.py +61 -2
  3. AutoGLM_GUI/adb_plus/version.py +81 -0
  4. AutoGLM_GUI/api/__init__.py +8 -1
  5. AutoGLM_GUI/api/agents.py +329 -94
  6. AutoGLM_GUI/api/devices.py +145 -164
  7. AutoGLM_GUI/api/workflows.py +70 -0
  8. AutoGLM_GUI/device_manager.py +760 -0
  9. AutoGLM_GUI/exceptions.py +18 -0
  10. AutoGLM_GUI/phone_agent_manager.py +549 -0
  11. AutoGLM_GUI/phone_agent_patches.py +146 -0
  12. AutoGLM_GUI/schemas.py +310 -2
  13. AutoGLM_GUI/state.py +21 -0
  14. AutoGLM_GUI/static/assets/{about-Crpy4Xue.js → about-PcGX7dIG.js} +1 -1
  15. AutoGLM_GUI/static/assets/chat-B0FKL2ne.js +124 -0
  16. AutoGLM_GUI/static/assets/dialog-BSNX0L1i.js +45 -0
  17. AutoGLM_GUI/static/assets/index-BjYIY--m.css +1 -0
  18. AutoGLM_GUI/static/assets/index-CnEYDOXp.js +11 -0
  19. AutoGLM_GUI/static/assets/index-DOt5XNhh.js +1 -0
  20. AutoGLM_GUI/static/assets/logo-Cyfm06Ym.png +0 -0
  21. AutoGLM_GUI/static/assets/workflows-B1hgBC_O.js +1 -0
  22. AutoGLM_GUI/static/favicon.ico +0 -0
  23. AutoGLM_GUI/static/index.html +9 -2
  24. AutoGLM_GUI/static/logo-192.png +0 -0
  25. AutoGLM_GUI/static/logo-512.png +0 -0
  26. AutoGLM_GUI/workflow_manager.py +181 -0
  27. {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.0.dist-info}/METADATA +51 -6
  28. {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.0.dist-info}/RECORD +31 -19
  29. AutoGLM_GUI/static/assets/chat-DGFuSj6_.js +0 -149
  30. AutoGLM_GUI/static/assets/index-C1k5Ch1V.js +0 -10
  31. AutoGLM_GUI/static/assets/index-COYnSjzf.js +0 -1
  32. AutoGLM_GUI/static/assets/index-QX6oy21q.css +0 -1
  33. {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.0.dist-info}/WHEEL +0 -0
  34. {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.0.dist-info}/entry_points.txt +0 -0
  35. {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,17 @@
1
1
  """Device discovery routes."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
3
7
  from fastapi import APIRouter
4
8
 
5
- from AutoGLM_GUI.adb_plus import get_wifi_ip, get_device_serial, pair_device
9
+ if TYPE_CHECKING:
10
+ from AutoGLM_GUI.device_manager import ManagedDevice
11
+ from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
12
+
6
13
  from AutoGLM_GUI.adb_plus.qr_pair import qr_pairing_manager
14
+ from AutoGLM_GUI.logger import logger
7
15
 
8
16
  from AutoGLM_GUI.schemas import (
9
17
  DeviceListResponse,
@@ -21,111 +29,126 @@ from AutoGLM_GUI.schemas import (
21
29
  QRPairStatusResponse,
22
30
  QRPairCancelResponse,
23
31
  )
24
- from AutoGLM_GUI.state import agents
32
+
33
+
34
+ def _build_device_response_with_agent(
35
+ device: ManagedDevice, agent_manager: PhoneAgentManager
36
+ ) -> dict:
37
+ """组合设备信息和 Agent 状态(API 层职责)。
38
+
39
+ Args:
40
+ device: ManagedDevice 实例
41
+ agent_manager: PhoneAgentManager 实例
42
+
43
+ Returns:
44
+ dict: 完整的设备响应,匹配 DeviceResponse schema
45
+ """
46
+ # 获取纯设备信息
47
+ response = device.to_dict()
48
+
49
+ # 通过 serial 查找 Agent(支持连接切换)
50
+ agent_device_id = agent_manager.find_agent_by_serial(device.serial)
51
+
52
+ if agent_device_id:
53
+ metadata = agent_manager.get_metadata(agent_device_id)
54
+
55
+ if metadata:
56
+ response["agent"] = {
57
+ "state": metadata.state.value,
58
+ "created_at": metadata.created_at,
59
+ "last_used": metadata.last_used,
60
+ "error_message": metadata.error_message,
61
+ "model_name": metadata.model_config.model_name,
62
+ }
63
+ else:
64
+ response["agent"] = None
65
+ else:
66
+ response["agent"] = None
67
+
68
+ return response
69
+
25
70
 
26
71
  router = APIRouter()
27
72
 
28
73
 
29
74
  @router.get("/api/devices", response_model=DeviceListResponse)
30
75
  def list_devices() -> DeviceListResponse:
31
- """列出所有 ADB 设备。"""
32
- from phone_agent.adb import list_devices as adb_list, ADBConnection
33
-
34
- adb_devices = adb_list()
35
- conn = ADBConnection()
36
-
37
- devices_with_serial = []
38
- for d in adb_devices:
39
- # 使用 adb_plus get_device_serial 获取真实序列号
40
- serial = get_device_serial(d.device_id, conn.adb_path)
41
-
42
- devices_with_serial.append(
43
- {
44
- "id": d.device_id,
45
- "model": d.model or "Unknown",
46
- "status": d.status,
47
- "connection_type": d.connection_type.value,
48
- "is_initialized": d.device_id in agents,
49
- "serial": serial, # 真实序列号
50
- }
51
- )
76
+ """列出所有 ADB 设备及 Agent 状态."""
77
+ from AutoGLM_GUI.device_manager import DeviceManager
78
+ from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
79
+
80
+ device_manager = DeviceManager.get_instance()
81
+ agent_manager = PhoneAgentManager.get_instance()
82
+
83
+ # Fallback: 如果轮询未启动,执行同步获取
84
+ if not device_manager._poll_thread or not device_manager._poll_thread.is_alive():
85
+ logger.warning("Polling not started, performing synchronous device fetch")
86
+ device_manager.force_refresh()
87
+
88
+ managed_devices = device_manager.get_devices()
89
+
90
+ # API 层负责聚合设备信息和 Agent 状态
91
+ devices_with_agents = [
92
+ _build_device_response_with_agent(d, agent_manager) for d in managed_devices
93
+ ]
52
94
 
53
- return DeviceListResponse(devices=devices_with_serial)
95
+ return DeviceListResponse(devices=devices_with_agents)
54
96
 
55
97
 
56
98
  @router.post("/api/devices/connect_wifi", response_model=WiFiConnectResponse)
57
99
  def connect_wifi(request: WiFiConnectRequest) -> WiFiConnectResponse:
58
100
  """从 USB 启用 TCP/IP 并连接到 WiFi。"""
59
- from phone_agent.adb import ADBConnection, ConnectionType
101
+ from AutoGLM_GUI.device_manager import DeviceManager
60
102
 
61
- conn = ADBConnection()
103
+ device_manager = DeviceManager.get_instance()
104
+ success, message, wifi_id = device_manager.connect_wifi(
105
+ device_id=request.device_id,
106
+ port=request.port,
107
+ )
62
108
 
63
- # 优先使用传入的 device_id,否则取第一个在线设备
64
- device_info = conn.get_device_info(request.device_id)
65
- if not device_info:
66
- return WiFiConnectResponse(
67
- success=False,
68
- message="No connected device found",
69
- error="device_not_found",
70
- )
109
+ if success:
110
+ # Immediately refresh device list to show new WiFi device
111
+ device_manager.force_refresh()
71
112
 
72
- # 已经是 WiFi 连接则直接返回
73
- if device_info.connection_type == ConnectionType.REMOTE:
74
- address = device_info.device_id
75
113
  return WiFiConnectResponse(
76
114
  success=True,
77
- message="Already connected over WiFi",
78
- device_id=address,
79
- address=address,
80
- )
81
-
82
- # 1) 启用 tcpip
83
- ok, msg = conn.enable_tcpip(port=request.port, device_id=device_info.device_id)
84
- if not ok:
85
- return WiFiConnectResponse(
86
- success=False, message=msg or "Failed to enable tcpip", error="tcpip"
87
- )
88
-
89
- # 2) 读取设备 IP:先用本地 adb_plus 的 WiFi 优先逻辑,失败再回退上游接口
90
- ip = get_wifi_ip(conn.adb_path, device_info.device_id) or conn.get_device_ip(
91
- device_info.device_id
92
- )
93
- if not ip:
94
- return WiFiConnectResponse(
95
- success=False, message="Failed to get device IP", error="ip"
115
+ message=message,
116
+ device_id=wifi_id,
117
+ address=wifi_id,
96
118
  )
119
+ else:
120
+ # Determine error type from message
121
+ error_type = "connect"
122
+ if "not found" in message.lower():
123
+ error_type = "device_not_found"
124
+ elif "tcpip" in message.lower():
125
+ error_type = "tcpip"
126
+ elif "ip" in message.lower():
127
+ error_type = "ip"
97
128
 
98
- address = f"{ip}:{request.port}"
99
-
100
- # 3) 连接 WiFi
101
- ok, msg = conn.connect(address)
102
- if not ok:
103
129
  return WiFiConnectResponse(
104
130
  success=False,
105
- message=msg or "Failed to connect over WiFi",
106
- error="connect",
131
+ message=message,
132
+ error=error_type,
107
133
  )
108
134
 
109
- return WiFiConnectResponse(
110
- success=True,
111
- message="Switched to WiFi successfully",
112
- device_id=address,
113
- address=address,
114
- )
115
-
116
135
 
117
136
  @router.post("/api/devices/disconnect_wifi", response_model=WiFiDisconnectResponse)
118
137
  def disconnect_wifi(request: WiFiDisconnectRequest) -> WiFiDisconnectResponse:
119
138
  """断开 WiFi 连接。"""
120
- from phone_agent.adb import ADBConnection
139
+ from AutoGLM_GUI.device_manager import DeviceManager
121
140
 
122
- conn = ADBConnection()
123
- ok, msg = conn.disconnect(request.device_id)
141
+ device_manager = DeviceManager.get_instance()
142
+ success, message = device_manager.disconnect_wifi(request.device_id)
143
+
144
+ if success:
145
+ # Refresh device list to update status
146
+ device_manager.force_refresh()
124
147
 
125
148
  return WiFiDisconnectResponse(
126
- success=ok,
127
- message=msg,
128
- error=None if ok else "disconnect_failed",
149
+ success=success,
150
+ message=message,
151
+ error=None if success else "disconnect_failed",
129
152
  )
130
153
 
131
154
 
@@ -136,120 +159,78 @@ def connect_wifi_manual(
136
159
  request: WiFiManualConnectRequest,
137
160
  ) -> WiFiManualConnectResponse:
138
161
  """手动连接到 WiFi 设备 (直接连接,无需 USB)."""
139
- import re
162
+ from AutoGLM_GUI.device_manager import DeviceManager
140
163
 
141
- from phone_agent.adb import ADBConnection
164
+ device_manager = DeviceManager.get_instance()
165
+ success, message, device_id = device_manager.connect_wifi_manual(
166
+ ip=request.ip,
167
+ port=request.port,
168
+ )
142
169
 
143
- # IP 格式验证
144
- ip_pattern = r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"
145
- if not re.match(ip_pattern, request.ip):
146
- return WiFiManualConnectResponse(
147
- success=False,
148
- message="Invalid IP address format",
149
- error="invalid_ip",
150
- )
170
+ if success:
171
+ # Refresh device list to show new device
172
+ device_manager.force_refresh()
151
173
 
152
- # 端口范围验证
153
- if not (1 <= request.port <= 65535):
154
174
  return WiFiManualConnectResponse(
155
- success=False,
156
- message="Port must be between 1 and 65535",
157
- error="invalid_port",
175
+ success=True,
176
+ message=message,
177
+ device_id=device_id,
158
178
  )
179
+ else:
180
+ # Determine error type from message
181
+ error_type = "connect_failed"
182
+ if "Invalid IP" in message:
183
+ error_type = "invalid_ip"
184
+ elif "Port must be" in message:
185
+ error_type = "invalid_port"
159
186
 
160
- conn = ADBConnection()
161
- address = f"{request.ip}:{request.port}"
162
-
163
- # 直接连接
164
- ok, msg = conn.connect(address)
165
- if not ok:
166
187
  return WiFiManualConnectResponse(
167
188
  success=False,
168
- message=msg or f"Failed to connect to {address}",
169
- error="connect_failed",
189
+ message=message,
190
+ error=error_type,
170
191
  )
171
192
 
172
- return WiFiManualConnectResponse(
173
- success=True,
174
- message=f"Successfully connected to {address}",
175
- device_id=address,
176
- )
177
-
178
193
 
179
194
  @router.post("/api/devices/pair_wifi", response_model=WiFiPairResponse)
180
195
  def pair_wifi(request: WiFiPairRequest) -> WiFiPairResponse:
181
196
  """使用无线调试配对并连接到 WiFi 设备 (Android 11+)."""
182
- import re
183
-
184
- from phone_agent.adb import ADBConnection
185
-
186
- # IP 格式验证
187
- ip_pattern = r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"
188
- if not re.match(ip_pattern, request.ip):
189
- return WiFiPairResponse(
190
- success=False,
191
- message="Invalid IP address format",
192
- error="invalid_ip",
193
- )
197
+ from AutoGLM_GUI.device_manager import DeviceManager
194
198
 
195
- # 配对端口验证
196
- if not (1 <= request.pairing_port <= 65535):
197
- return WiFiPairResponse(
198
- success=False,
199
- message="Pairing port must be between 1 and 65535",
200
- error="invalid_port",
201
- )
202
-
203
- # 连接端口验证
204
- if not (1 <= request.connection_port <= 65535):
205
- return WiFiPairResponse(
206
- success=False,
207
- message="Connection port must be between 1 and 65535",
208
- error="invalid_port",
209
- )
210
-
211
- # 配对码验证 (6 位数字)
212
- if not request.pairing_code.isdigit() or len(request.pairing_code) != 6:
213
- return WiFiPairResponse(
214
- success=False,
215
- message="Pairing code must be 6 digits",
216
- error="invalid_pairing_code",
217
- )
218
-
219
- conn = ADBConnection()
220
-
221
- # 步骤 1: 配对设备
222
- ok, msg = pair_device(
199
+ device_manager = DeviceManager.get_instance()
200
+ success, message, device_id = device_manager.pair_wifi(
223
201
  ip=request.ip,
224
- port=request.pairing_port,
202
+ pairing_port=request.pairing_port,
225
203
  pairing_code=request.pairing_code,
226
- adb_path=conn.adb_path,
204
+ connection_port=request.connection_port,
227
205
  )
228
206
 
229
- if not ok:
207
+ if success:
208
+ # Refresh device list to show newly paired device
209
+ device_manager.force_refresh()
210
+
230
211
  return WiFiPairResponse(
231
- success=False,
232
- message=msg,
233
- error="pair_failed",
212
+ success=True,
213
+ message=message,
214
+ device_id=device_id,
234
215
  )
216
+ else:
217
+ # Determine error type from message
218
+ error_type = "connect_failed"
219
+ if "Invalid IP" in message:
220
+ error_type = "invalid_ip"
221
+ elif "port must be" in message.lower():
222
+ error_type = "invalid_port"
223
+ elif "Pairing code must be" in message:
224
+ error_type = "invalid_pairing_code"
225
+ elif "connection failed" not in message.lower():
226
+ error_type = "pair_failed"
235
227
 
236
- # 步骤 2: 使用标准 ADB 端口连接到设备
237
- connection_address = f"{request.ip}:{request.connection_port}"
238
- ok, connect_msg = conn.connect(connection_address)
239
-
240
- if not ok:
241
228
  return WiFiPairResponse(
242
229
  success=False,
243
- message=f"Paired successfully but connection failed: {connect_msg}",
244
- error="connect_failed",
230
+ message=message,
231
+ error=error_type,
245
232
  )
246
233
 
247
- return WiFiPairResponse(
248
- success=True,
249
- message=f"Successfully paired and connected to {connection_address}",
250
- device_id=connection_address,
251
- )
252
-
253
234
 
254
235
  @router.get("/api/devices/discover_mdns", response_model=MdnsDiscoverResponse)
255
236
  def discover_mdns() -> MdnsDiscoverResponse:
@@ -0,0 +1,70 @@
1
+ """Workflow API 路由."""
2
+
3
+ from fastapi import APIRouter, HTTPException
4
+
5
+ from AutoGLM_GUI.schemas import (
6
+ WorkflowCreate,
7
+ WorkflowListResponse,
8
+ WorkflowResponse,
9
+ WorkflowUpdate,
10
+ )
11
+
12
+ router = APIRouter()
13
+
14
+
15
+ @router.get("/api/workflows", response_model=WorkflowListResponse)
16
+ def list_workflows() -> WorkflowListResponse:
17
+ """获取所有 workflows."""
18
+ from AutoGLM_GUI.workflow_manager import workflow_manager
19
+
20
+ workflows = workflow_manager.list_workflows()
21
+ return WorkflowListResponse(workflows=workflows)
22
+
23
+
24
+ @router.get("/api/workflows/{workflow_uuid}", response_model=WorkflowResponse)
25
+ def get_workflow(workflow_uuid: str) -> WorkflowResponse:
26
+ """获取单个 workflow."""
27
+ from AutoGLM_GUI.workflow_manager import workflow_manager
28
+
29
+ workflow = workflow_manager.get_workflow(workflow_uuid)
30
+ if not workflow:
31
+ raise HTTPException(status_code=404, detail="Workflow not found")
32
+ return WorkflowResponse(**workflow)
33
+
34
+
35
+ @router.post("/api/workflows", response_model=WorkflowResponse)
36
+ def create_workflow(request: WorkflowCreate) -> WorkflowResponse:
37
+ """创建新 workflow."""
38
+ from AutoGLM_GUI.workflow_manager import workflow_manager
39
+
40
+ try:
41
+ workflow = workflow_manager.create_workflow(
42
+ name=request.name, text=request.text
43
+ )
44
+ return WorkflowResponse(**workflow)
45
+ except Exception as e:
46
+ raise HTTPException(status_code=500, detail=str(e))
47
+
48
+
49
+ @router.put("/api/workflows/{workflow_uuid}", response_model=WorkflowResponse)
50
+ def update_workflow(workflow_uuid: str, request: WorkflowUpdate) -> WorkflowResponse:
51
+ """更新 workflow."""
52
+ from AutoGLM_GUI.workflow_manager import workflow_manager
53
+
54
+ workflow = workflow_manager.update_workflow(
55
+ uuid=workflow_uuid, name=request.name, text=request.text
56
+ )
57
+ if not workflow:
58
+ raise HTTPException(status_code=404, detail="Workflow not found")
59
+ return WorkflowResponse(**workflow)
60
+
61
+
62
+ @router.delete("/api/workflows/{workflow_uuid}")
63
+ def delete_workflow(workflow_uuid: str) -> dict:
64
+ """删除 workflow."""
65
+ from AutoGLM_GUI.workflow_manager import workflow_manager
66
+
67
+ success = workflow_manager.delete_workflow(workflow_uuid)
68
+ if not success:
69
+ raise HTTPException(status_code=404, detail="Workflow not found")
70
+ return {"success": True, "message": "Workflow deleted"}