autoglm-gui 1.4.1__py3-none-any.whl → 1.5.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.
- AutoGLM_GUI/__init__.py +11 -0
- AutoGLM_GUI/__main__.py +26 -4
- AutoGLM_GUI/actions/__init__.py +6 -0
- AutoGLM_GUI/actions/handler.py +196 -0
- AutoGLM_GUI/actions/types.py +15 -0
- AutoGLM_GUI/adb/__init__.py +53 -0
- AutoGLM_GUI/adb/apps.py +227 -0
- AutoGLM_GUI/adb/connection.py +323 -0
- AutoGLM_GUI/adb/device.py +171 -0
- AutoGLM_GUI/adb/input.py +67 -0
- AutoGLM_GUI/adb/screenshot.py +11 -0
- AutoGLM_GUI/adb/timing.py +167 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
- AutoGLM_GUI/adb_plus/screenshot.py +22 -1
- AutoGLM_GUI/adb_plus/serial.py +38 -20
- AutoGLM_GUI/adb_plus/touch.py +4 -9
- AutoGLM_GUI/agents/__init__.py +43 -12
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +31 -38
- AutoGLM_GUI/agents/glm/__init__.py +7 -0
- AutoGLM_GUI/agents/glm/agent.py +292 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
- AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +405 -0
- AutoGLM_GUI/agents/mai/parser.py +254 -0
- AutoGLM_GUI/agents/mai/prompts.py +103 -0
- AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
- AutoGLM_GUI/agents/protocols.py +12 -8
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +40 -21
- AutoGLM_GUI/api/agents.py +157 -240
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +102 -12
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +67 -15
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/config.py +81 -0
- AutoGLM_GUI/config_manager.py +68 -51
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +1 -1
- AutoGLM_GUI/devices/adb_device.py +5 -10
- AutoGLM_GUI/devices/mock_device.py +4 -2
- AutoGLM_GUI/devices/remote_device.py +8 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/model/__init__.py +5 -0
- AutoGLM_GUI/model/message_builder.py +69 -0
- AutoGLM_GUI/model/types.py +24 -0
- AutoGLM_GUI/models/__init__.py +10 -0
- AutoGLM_GUI/models/history.py +96 -0
- AutoGLM_GUI/models/scheduled_task.py +71 -0
- AutoGLM_GUI/parsers/__init__.py +22 -0
- AutoGLM_GUI/parsers/base.py +50 -0
- AutoGLM_GUI/parsers/phone_parser.py +58 -0
- AutoGLM_GUI/phone_agent_manager.py +62 -396
- AutoGLM_GUI/platform_utils.py +26 -0
- AutoGLM_GUI/prompt_config.py +15 -0
- AutoGLM_GUI/prompts/__init__.py +32 -0
- AutoGLM_GUI/scheduler_manager.py +304 -0
- AutoGLM_GUI/schemas.py +234 -72
- AutoGLM_GUI/scrcpy_stream.py +142 -24
- AutoGLM_GUI/socketio_server.py +100 -27
- AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-BQm96DAl.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
- AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
- AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-CmZSnDqc.js} +1 -1
- AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
- AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
- AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
- AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +17 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +137 -130
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- AutoGLM_GUI/agents/mai_adapter.py +0 -627
- AutoGLM_GUI/api/dual_model.py +0 -317
- AutoGLM_GUI/dual_model/__init__.py +0 -53
- AutoGLM_GUI/dual_model/decision_model.py +0 -664
- AutoGLM_GUI/dual_model/dual_agent.py +0 -917
- AutoGLM_GUI/dual_model/protocols.py +0 -354
- AutoGLM_GUI/dual_model/vision_model.py +0 -442
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
- AutoGLM_GUI/phone_agent_patches.py +0 -147
- AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
- AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
- AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
- AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
- AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
- autoglm_gui-1.4.1.dist-info/RECORD +0 -117
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/devices.py
CHANGED
|
@@ -16,6 +16,18 @@ from AutoGLM_GUI.logger import logger
|
|
|
16
16
|
from AutoGLM_GUI.schemas import (
|
|
17
17
|
DeviceListResponse,
|
|
18
18
|
DeviceResponse,
|
|
19
|
+
MdnsDeviceResponse,
|
|
20
|
+
MdnsDiscoverResponse,
|
|
21
|
+
QRPairCancelResponse,
|
|
22
|
+
QRPairGenerateResponse,
|
|
23
|
+
QRPairStatusResponse,
|
|
24
|
+
RemoteDeviceAddRequest,
|
|
25
|
+
RemoteDeviceAddResponse,
|
|
26
|
+
RemoteDeviceDiscoverRequest,
|
|
27
|
+
RemoteDeviceDiscoverResponse,
|
|
28
|
+
RemoteDeviceInfo,
|
|
29
|
+
RemoteDeviceRemoveRequest,
|
|
30
|
+
RemoteDeviceRemoveResponse,
|
|
19
31
|
WiFiConnectRequest,
|
|
20
32
|
WiFiConnectResponse,
|
|
21
33
|
WiFiDisconnectRequest,
|
|
@@ -24,23 +36,27 @@ from AutoGLM_GUI.schemas import (
|
|
|
24
36
|
WiFiManualConnectResponse,
|
|
25
37
|
WiFiPairRequest,
|
|
26
38
|
WiFiPairResponse,
|
|
27
|
-
MdnsDiscoverResponse,
|
|
28
|
-
MdnsDeviceResponse,
|
|
29
|
-
QRPairGenerateResponse,
|
|
30
|
-
QRPairStatusResponse,
|
|
31
|
-
QRPairCancelResponse,
|
|
32
39
|
)
|
|
33
40
|
|
|
34
41
|
|
|
35
42
|
def _build_device_response_with_agent(
|
|
36
43
|
device: "ManagedDevice", agent_manager: "PhoneAgentManager"
|
|
37
44
|
) -> DeviceResponse:
|
|
45
|
+
"""聚合设备信息和 Agent 状态(API 层职责).
|
|
46
|
+
|
|
47
|
+
API 层负责协调 DeviceManager 和 PhoneAgentManager,
|
|
48
|
+
通过遍历设备的所有连接来查找已初始化的 Agent。
|
|
49
|
+
"""
|
|
50
|
+
|
|
38
51
|
response = device.to_dict()
|
|
39
|
-
agent_device_id = agent_manager.find_agent_by_serial(device.serial)
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
# 遍历设备的所有连接,查找已初始化的 Agent
|
|
54
|
+
# 使用 device.connections 公开属性(ManagedDevice 提供)
|
|
55
|
+
for conn in device.connections:
|
|
56
|
+
# 只调用 PhoneAgentManager 的公开方法
|
|
57
|
+
metadata = agent_manager.get_metadata(conn.device_id)
|
|
43
58
|
if metadata:
|
|
59
|
+
# 找到了已初始化的 Agent
|
|
44
60
|
response["agent"] = {
|
|
45
61
|
"state": metadata.state, # AgentState is str, Enum, already a string
|
|
46
62
|
"created_at": metadata.created_at,
|
|
@@ -48,9 +64,10 @@ def _build_device_response_with_agent(
|
|
|
48
64
|
"error_message": metadata.error_message,
|
|
49
65
|
"model_name": metadata.model_config.model_name,
|
|
50
66
|
}
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
|
|
68
|
+
break # 找到第一个 Agent 即可退出
|
|
53
69
|
else:
|
|
70
|
+
# 没有找到任何已初始化的 Agent
|
|
54
71
|
response["agent"] = None
|
|
55
72
|
|
|
56
73
|
return DeviceResponse.model_validate(response)
|
|
@@ -229,7 +246,7 @@ def pair_wifi(request: WiFiPairRequest) -> WiFiPairResponse:
|
|
|
229
246
|
@router.get("/api/devices/discover_mdns", response_model=MdnsDiscoverResponse)
|
|
230
247
|
def discover_mdns() -> MdnsDiscoverResponse:
|
|
231
248
|
"""Discover wireless ADB devices via mDNS."""
|
|
232
|
-
from
|
|
249
|
+
from AutoGLM_GUI.adb import ADBConnection
|
|
233
250
|
from AutoGLM_GUI.adb_plus import discover_mdns_devices
|
|
234
251
|
|
|
235
252
|
try:
|
|
@@ -275,7 +292,7 @@ def generate_qr_pairing(timeout: int = 90) -> QRPairGenerateResponse:
|
|
|
275
292
|
QR code payload and session information
|
|
276
293
|
"""
|
|
277
294
|
try:
|
|
278
|
-
from
|
|
295
|
+
from AutoGLM_GUI.adb import ADBConnection
|
|
279
296
|
|
|
280
297
|
conn = ADBConnection()
|
|
281
298
|
session = qr_pairing_manager.create_session(
|
|
@@ -364,3 +381,76 @@ def cancel_qr_pairing(session_id: str) -> QRPairCancelResponse:
|
|
|
364
381
|
success=False,
|
|
365
382
|
message="Session not found or already completed",
|
|
366
383
|
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@router.post(
|
|
387
|
+
"/api/devices/discover_remote", response_model=RemoteDeviceDiscoverResponse
|
|
388
|
+
)
|
|
389
|
+
def discover_remote_devices(
|
|
390
|
+
request: RemoteDeviceDiscoverRequest,
|
|
391
|
+
) -> RemoteDeviceDiscoverResponse:
|
|
392
|
+
"""Discover devices from a remote Device Agent Server."""
|
|
393
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
394
|
+
|
|
395
|
+
device_manager = DeviceManager.get_instance()
|
|
396
|
+
success, message, devices_list = device_manager.discover_remote_devices(
|
|
397
|
+
base_url=request.base_url,
|
|
398
|
+
timeout=request.timeout,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
devices = [RemoteDeviceInfo(**d) for d in devices_list]
|
|
402
|
+
|
|
403
|
+
return RemoteDeviceDiscoverResponse(
|
|
404
|
+
success=success,
|
|
405
|
+
devices=devices,
|
|
406
|
+
message=message,
|
|
407
|
+
error=None if success else message,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@router.post("/api/devices/add_remote", response_model=RemoteDeviceAddResponse)
|
|
412
|
+
def add_remote_device(request: RemoteDeviceAddRequest) -> RemoteDeviceAddResponse:
|
|
413
|
+
"""Add a remote HTTP proxy device manually."""
|
|
414
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
415
|
+
|
|
416
|
+
device_manager = DeviceManager.get_instance()
|
|
417
|
+
success, message, serial = device_manager.add_remote_device(
|
|
418
|
+
base_url=request.base_url,
|
|
419
|
+
device_id=request.device_id,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if success:
|
|
423
|
+
return RemoteDeviceAddResponse(
|
|
424
|
+
success=True,
|
|
425
|
+
message=message,
|
|
426
|
+
serial=serial,
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
error_type = "add_failed"
|
|
430
|
+
if "already exists" in message.lower():
|
|
431
|
+
error_type = "already_exists"
|
|
432
|
+
elif "connection failed" in message.lower():
|
|
433
|
+
error_type = "connection_failed"
|
|
434
|
+
|
|
435
|
+
return RemoteDeviceAddResponse(
|
|
436
|
+
success=False,
|
|
437
|
+
message=message,
|
|
438
|
+
error=error_type,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@router.post("/api/devices/remove_remote", response_model=RemoteDeviceRemoveResponse)
|
|
443
|
+
def remove_remote_device(
|
|
444
|
+
request: RemoteDeviceRemoveRequest,
|
|
445
|
+
) -> RemoteDeviceRemoveResponse:
|
|
446
|
+
"""Remove a remote device."""
|
|
447
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
448
|
+
|
|
449
|
+
device_manager = DeviceManager.get_instance()
|
|
450
|
+
success, message = device_manager.remove_remote_device(request.serial)
|
|
451
|
+
|
|
452
|
+
return RemoteDeviceRemoveResponse(
|
|
453
|
+
success=success,
|
|
454
|
+
message=message,
|
|
455
|
+
error=None if success else "remove_failed",
|
|
456
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""History API routes."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
|
|
5
|
+
from AutoGLM_GUI.history_manager import history_manager
|
|
6
|
+
from AutoGLM_GUI.schemas import HistoryListResponse, HistoryRecordResponse
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("/api/history/{serialno}", response_model=HistoryListResponse)
|
|
12
|
+
def list_history(
|
|
13
|
+
serialno: str, limit: int = 50, offset: int = 0
|
|
14
|
+
) -> HistoryListResponse:
|
|
15
|
+
if limit < 1 or limit > 100:
|
|
16
|
+
raise HTTPException(status_code=400, detail="limit must be between 1 and 100")
|
|
17
|
+
if offset < 0:
|
|
18
|
+
raise HTTPException(status_code=400, detail="offset must be non-negative")
|
|
19
|
+
|
|
20
|
+
records = history_manager.list_records(serialno, limit=limit, offset=offset)
|
|
21
|
+
total = history_manager.get_total_count(serialno)
|
|
22
|
+
|
|
23
|
+
return HistoryListResponse(
|
|
24
|
+
records=[
|
|
25
|
+
HistoryRecordResponse(
|
|
26
|
+
id=r.id,
|
|
27
|
+
task_text=r.task_text,
|
|
28
|
+
final_message=r.final_message,
|
|
29
|
+
success=r.success,
|
|
30
|
+
steps=r.steps,
|
|
31
|
+
start_time=r.start_time.isoformat(),
|
|
32
|
+
end_time=r.end_time.isoformat() if r.end_time else None,
|
|
33
|
+
duration_ms=r.duration_ms,
|
|
34
|
+
source=r.source,
|
|
35
|
+
source_detail=r.source_detail,
|
|
36
|
+
error_message=r.error_message,
|
|
37
|
+
)
|
|
38
|
+
for r in records
|
|
39
|
+
],
|
|
40
|
+
total=total,
|
|
41
|
+
limit=limit,
|
|
42
|
+
offset=offset,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.get("/api/history/{serialno}/{record_id}", response_model=HistoryRecordResponse)
|
|
47
|
+
def get_history_record(serialno: str, record_id: str) -> HistoryRecordResponse:
|
|
48
|
+
record = history_manager.get_record(serialno, record_id)
|
|
49
|
+
if not record:
|
|
50
|
+
raise HTTPException(status_code=404, detail="Record not found")
|
|
51
|
+
|
|
52
|
+
return HistoryRecordResponse(
|
|
53
|
+
id=record.id,
|
|
54
|
+
task_text=record.task_text,
|
|
55
|
+
final_message=record.final_message,
|
|
56
|
+
success=record.success,
|
|
57
|
+
steps=record.steps,
|
|
58
|
+
start_time=record.start_time.isoformat(),
|
|
59
|
+
end_time=record.end_time.isoformat() if record.end_time else None,
|
|
60
|
+
duration_ms=record.duration_ms,
|
|
61
|
+
source=record.source,
|
|
62
|
+
source_detail=record.source_detail,
|
|
63
|
+
error_message=record.error_message,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@router.delete("/api/history/{serialno}/{record_id}")
|
|
68
|
+
def delete_history_record(serialno: str, record_id: str) -> dict:
|
|
69
|
+
success = history_manager.delete_record(serialno, record_id)
|
|
70
|
+
if not success:
|
|
71
|
+
raise HTTPException(status_code=404, detail="Record not found")
|
|
72
|
+
return {"success": True, "message": "Record deleted"}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@router.delete("/api/history/{serialno}")
|
|
76
|
+
def clear_history(serialno: str) -> dict:
|
|
77
|
+
history_manager.clear_device_history(serialno)
|
|
78
|
+
return {"success": True, "message": f"History cleared for {serialno}"}
|
AutoGLM_GUI/api/layered_agent.py
CHANGED
|
@@ -53,9 +53,20 @@ def _clear_session(session_id: str) -> bool:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def get_planner_model() -> str:
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
"""获取规划层使用的模型名称."""
|
|
57
|
+
config_manager.load_file_config()
|
|
58
|
+
effective_config = config_manager.get_effective_config()
|
|
59
|
+
|
|
60
|
+
model_name = effective_config.decision_model_name
|
|
61
|
+
|
|
62
|
+
if not model_name:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
"决策模型未配置。使用分层代理模式需要配置决策模型。\n"
|
|
65
|
+
"请在全局配置中设置决策模型的 Base URL、模型名称和 API Key。"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
logger.info(f"[LayeredAgent] Using decision model: {model_name}")
|
|
69
|
+
return model_name
|
|
59
70
|
|
|
60
71
|
|
|
61
72
|
PLANNER_INSTRUCTIONS = """## 核心目标
|
|
@@ -282,20 +293,31 @@ async def chat(device_id: str, message: str) -> str:
|
|
|
282
293
|
|
|
283
294
|
|
|
284
295
|
def _setup_openai_client() -> AsyncOpenAI:
|
|
285
|
-
"""设置 OpenAI
|
|
296
|
+
"""设置 OpenAI 客户端,使用决策模型配置"""
|
|
286
297
|
config_manager.load_file_config()
|
|
287
298
|
effective_config = config_manager.get_effective_config()
|
|
288
299
|
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
# 检查决策模型配置
|
|
301
|
+
decision_base_url = effective_config.decision_base_url
|
|
302
|
+
decision_api_key = effective_config.decision_api_key
|
|
291
303
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
304
|
+
if not decision_base_url:
|
|
305
|
+
raise ValueError(
|
|
306
|
+
"决策模型 Base URL 未配置。使用分层代理模式需要配置决策模型。\n"
|
|
307
|
+
"请在全局配置中设置决策模型的 Base URL、模型名称和 API Key。"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# decision_api_key 可以为 None(某些本地模型不需要)
|
|
311
|
+
planner_model = get_planner_model() # 这里会再次检查 model_name
|
|
312
|
+
|
|
313
|
+
logger.info("[LayeredAgent] Decision model config:")
|
|
314
|
+
logger.info(f" - Base URL: {decision_base_url}")
|
|
315
|
+
logger.info(f" - Model: {planner_model}")
|
|
316
|
+
logger.info(f" - API Key: {'***' if decision_api_key else 'None'}")
|
|
295
317
|
|
|
296
318
|
return AsyncOpenAI(
|
|
297
|
-
base_url=
|
|
298
|
-
api_key=
|
|
319
|
+
base_url=decision_base_url,
|
|
320
|
+
api_key=decision_api_key or "EMPTY", # 某些本地模型需要非空字符串
|
|
299
321
|
)
|
|
300
322
|
|
|
301
323
|
|
|
@@ -375,18 +397,24 @@ async def layered_agent_chat(request: LayeredAgentRequest):
|
|
|
375
397
|
- done: Final response
|
|
376
398
|
- error: Error occurred
|
|
377
399
|
"""
|
|
400
|
+
from datetime import datetime
|
|
401
|
+
|
|
378
402
|
from agents.stream_events import (
|
|
379
403
|
RawResponsesStreamEvent,
|
|
380
404
|
RunItemStreamEvent,
|
|
381
405
|
)
|
|
382
406
|
|
|
407
|
+
from AutoGLM_GUI.history_manager import history_manager
|
|
408
|
+
from AutoGLM_GUI.models.history import ConversationRecord
|
|
409
|
+
|
|
383
410
|
async def event_generator():
|
|
411
|
+
start_time = datetime.now()
|
|
412
|
+
final_output = ""
|
|
413
|
+
final_success = False
|
|
414
|
+
|
|
384
415
|
try:
|
|
385
|
-
# Ensure agent is initialized
|
|
386
416
|
agent = _ensure_agent()
|
|
387
417
|
|
|
388
|
-
# 获取或创建 session 以保持对话上下文
|
|
389
|
-
# 优先使用 session_id,其次使用 device_id,最后使用默认值
|
|
390
418
|
session_id = request.session_id or request.device_id or "default"
|
|
391
419
|
session = _get_or_create_session(session_id)
|
|
392
420
|
|
|
@@ -574,10 +602,10 @@ async def layered_agent_chat(request: LayeredAgentRequest):
|
|
|
574
602
|
with _active_runs_lock:
|
|
575
603
|
_active_runs.pop(session_id, None)
|
|
576
604
|
|
|
577
|
-
# Final result
|
|
578
605
|
final_output = (
|
|
579
606
|
result.final_output if hasattr(result, "final_output") else ""
|
|
580
607
|
)
|
|
608
|
+
final_success = True
|
|
581
609
|
event_data = {
|
|
582
610
|
"type": "done",
|
|
583
611
|
"content": final_output,
|
|
@@ -587,12 +615,36 @@ async def layered_agent_chat(request: LayeredAgentRequest):
|
|
|
587
615
|
|
|
588
616
|
except Exception as e:
|
|
589
617
|
logger.exception(f"[LayeredAgent] Error: {e}")
|
|
618
|
+
final_output = str(e)
|
|
619
|
+
final_success = False
|
|
590
620
|
event_data = {
|
|
591
621
|
"type": "error",
|
|
592
622
|
"message": str(e),
|
|
593
623
|
}
|
|
594
624
|
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
595
625
|
|
|
626
|
+
finally:
|
|
627
|
+
if request.device_id and final_output:
|
|
628
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
629
|
+
|
|
630
|
+
device_manager = DeviceManager.get_instance()
|
|
631
|
+
serialno = device_manager.get_serial_by_device_id(request.device_id)
|
|
632
|
+
if serialno:
|
|
633
|
+
end_time = datetime.now()
|
|
634
|
+
record = ConversationRecord(
|
|
635
|
+
task_text=request.message,
|
|
636
|
+
final_message=final_output,
|
|
637
|
+
success=final_success,
|
|
638
|
+
steps=0,
|
|
639
|
+
start_time=start_time,
|
|
640
|
+
end_time=end_time,
|
|
641
|
+
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
642
|
+
source="layered",
|
|
643
|
+
source_detail=request.session_id or "",
|
|
644
|
+
error_message=None if final_success else final_output,
|
|
645
|
+
)
|
|
646
|
+
history_manager.add_record(serialno, record)
|
|
647
|
+
|
|
596
648
|
return StreamingResponse(
|
|
597
649
|
event_generator(),
|
|
598
650
|
media_type="text/event-stream",
|
AutoGLM_GUI/api/media.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from fastapi import APIRouter
|
|
6
6
|
|
|
7
7
|
from AutoGLM_GUI.adb_plus import capture_screenshot
|
|
8
|
+
from AutoGLM_GUI.exceptions import DeviceNotAvailableError
|
|
8
9
|
from AutoGLM_GUI.logger import logger
|
|
9
10
|
from AutoGLM_GUI.schemas import ScreenshotRequest, ScreenshotResponse
|
|
10
11
|
from AutoGLM_GUI.socketio_server import stop_streamers
|
|
@@ -29,8 +30,59 @@ async def reset_video_stream(device_id: str | None = None) -> dict:
|
|
|
29
30
|
@router.post("/api/screenshot", response_model=ScreenshotResponse)
|
|
30
31
|
def take_screenshot(request: ScreenshotRequest) -> ScreenshotResponse:
|
|
31
32
|
"""获取设备截图。此操作无副作用,不影响 PhoneAgent 运行。"""
|
|
33
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
34
|
+
|
|
32
35
|
try:
|
|
33
|
-
|
|
36
|
+
device_id = request.device_id
|
|
37
|
+
|
|
38
|
+
if not device_id:
|
|
39
|
+
return ScreenshotResponse(
|
|
40
|
+
success=False,
|
|
41
|
+
image="",
|
|
42
|
+
width=0,
|
|
43
|
+
height=0,
|
|
44
|
+
is_sensitive=False,
|
|
45
|
+
error="device_id is required",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
device_manager = DeviceManager.get_instance()
|
|
49
|
+
serial = device_manager.get_serial_by_device_id(device_id)
|
|
50
|
+
|
|
51
|
+
if not serial:
|
|
52
|
+
return ScreenshotResponse(
|
|
53
|
+
success=False,
|
|
54
|
+
image="",
|
|
55
|
+
width=0,
|
|
56
|
+
height=0,
|
|
57
|
+
is_sensitive=False,
|
|
58
|
+
error=f"Device {device_id} not found",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if serial:
|
|
62
|
+
managed = device_manager._devices.get(serial)
|
|
63
|
+
if managed and managed.connection_type.value == "remote":
|
|
64
|
+
remote_device = device_manager.get_remote_device_instance(serial)
|
|
65
|
+
|
|
66
|
+
if not remote_device:
|
|
67
|
+
return ScreenshotResponse(
|
|
68
|
+
success=False,
|
|
69
|
+
image="",
|
|
70
|
+
width=0,
|
|
71
|
+
height=0,
|
|
72
|
+
is_sensitive=False,
|
|
73
|
+
error=f"Remote device {serial} not found",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
screenshot = remote_device.get_screenshot(timeout=10) # type: ignore
|
|
77
|
+
return ScreenshotResponse(
|
|
78
|
+
success=True,
|
|
79
|
+
image=screenshot.base64_data,
|
|
80
|
+
width=screenshot.width,
|
|
81
|
+
height=screenshot.height,
|
|
82
|
+
is_sensitive=screenshot.is_sensitive,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
screenshot = capture_screenshot(device_id=device_id)
|
|
34
86
|
return ScreenshotResponse(
|
|
35
87
|
success=True,
|
|
36
88
|
image=screenshot.base64_data,
|
|
@@ -38,7 +90,18 @@ def take_screenshot(request: ScreenshotRequest) -> ScreenshotResponse:
|
|
|
38
90
|
height=screenshot.height,
|
|
39
91
|
is_sensitive=screenshot.is_sensitive,
|
|
40
92
|
)
|
|
93
|
+
except DeviceNotAvailableError as e:
|
|
94
|
+
logger.warning("Screenshot failed - device not available: %s", e)
|
|
95
|
+
return ScreenshotResponse(
|
|
96
|
+
success=False,
|
|
97
|
+
image="",
|
|
98
|
+
width=0,
|
|
99
|
+
height=0,
|
|
100
|
+
is_sensitive=False,
|
|
101
|
+
error=str(e),
|
|
102
|
+
)
|
|
41
103
|
except Exception as e:
|
|
104
|
+
logger.exception("Screenshot failed for device %s", request.device_id)
|
|
42
105
|
return ScreenshotResponse(
|
|
43
106
|
success=False,
|
|
44
107
|
image="",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Scheduled tasks API routes."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
|
|
5
|
+
from AutoGLM_GUI.scheduler_manager import scheduler_manager
|
|
6
|
+
from AutoGLM_GUI.schemas import (
|
|
7
|
+
ScheduledTaskCreate,
|
|
8
|
+
ScheduledTaskListResponse,
|
|
9
|
+
ScheduledTaskResponse,
|
|
10
|
+
ScheduledTaskUpdate,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
router = APIRouter()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _task_to_response(task) -> ScheduledTaskResponse:
|
|
17
|
+
next_run = scheduler_manager.get_next_run_time(task.id)
|
|
18
|
+
return ScheduledTaskResponse(
|
|
19
|
+
id=task.id,
|
|
20
|
+
name=task.name,
|
|
21
|
+
workflow_uuid=task.workflow_uuid,
|
|
22
|
+
device_serialno=task.device_serialno,
|
|
23
|
+
cron_expression=task.cron_expression,
|
|
24
|
+
enabled=task.enabled,
|
|
25
|
+
created_at=task.created_at.isoformat(),
|
|
26
|
+
updated_at=task.updated_at.isoformat(),
|
|
27
|
+
last_run_time=task.last_run_time.isoformat() if task.last_run_time else None,
|
|
28
|
+
last_run_success=task.last_run_success,
|
|
29
|
+
last_run_message=task.last_run_message,
|
|
30
|
+
next_run_time=next_run.isoformat() if next_run else None,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.get("/api/scheduled-tasks", response_model=ScheduledTaskListResponse)
|
|
35
|
+
def list_scheduled_tasks() -> ScheduledTaskListResponse:
|
|
36
|
+
tasks = scheduler_manager.list_tasks()
|
|
37
|
+
return ScheduledTaskListResponse(tasks=[_task_to_response(t) for t in tasks])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.post("/api/scheduled-tasks", response_model=ScheduledTaskResponse)
|
|
41
|
+
def create_scheduled_task(request: ScheduledTaskCreate) -> ScheduledTaskResponse:
|
|
42
|
+
from AutoGLM_GUI.workflow_manager import workflow_manager
|
|
43
|
+
|
|
44
|
+
workflow = workflow_manager.get_workflow(request.workflow_uuid)
|
|
45
|
+
if not workflow:
|
|
46
|
+
raise HTTPException(status_code=400, detail="Workflow not found")
|
|
47
|
+
|
|
48
|
+
task = scheduler_manager.create_task(
|
|
49
|
+
name=request.name,
|
|
50
|
+
workflow_uuid=request.workflow_uuid,
|
|
51
|
+
device_serialno=request.device_serialno,
|
|
52
|
+
cron_expression=request.cron_expression,
|
|
53
|
+
enabled=request.enabled,
|
|
54
|
+
)
|
|
55
|
+
return _task_to_response(task)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.get("/api/scheduled-tasks/{task_id}", response_model=ScheduledTaskResponse)
|
|
59
|
+
def get_scheduled_task(task_id: str) -> ScheduledTaskResponse:
|
|
60
|
+
task = scheduler_manager.get_task(task_id)
|
|
61
|
+
if not task:
|
|
62
|
+
raise HTTPException(status_code=404, detail="Task not found")
|
|
63
|
+
return _task_to_response(task)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@router.put("/api/scheduled-tasks/{task_id}", response_model=ScheduledTaskResponse)
|
|
67
|
+
def update_scheduled_task(
|
|
68
|
+
task_id: str, request: ScheduledTaskUpdate
|
|
69
|
+
) -> ScheduledTaskResponse:
|
|
70
|
+
update_data = request.model_dump(exclude_unset=True)
|
|
71
|
+
task = scheduler_manager.update_task(task_id, **update_data)
|
|
72
|
+
if not task:
|
|
73
|
+
raise HTTPException(status_code=404, detail="Task not found")
|
|
74
|
+
return _task_to_response(task)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.delete("/api/scheduled-tasks/{task_id}")
|
|
78
|
+
def delete_scheduled_task(task_id: str) -> dict:
|
|
79
|
+
success = scheduler_manager.delete_task(task_id)
|
|
80
|
+
if not success:
|
|
81
|
+
raise HTTPException(status_code=404, detail="Task not found")
|
|
82
|
+
return {"success": True, "message": "Task deleted"}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@router.post("/api/scheduled-tasks/{task_id}/enable")
|
|
86
|
+
def enable_scheduled_task(task_id: str) -> dict:
|
|
87
|
+
success = scheduler_manager.set_enabled(task_id, True)
|
|
88
|
+
if not success:
|
|
89
|
+
raise HTTPException(status_code=404, detail="Task not found")
|
|
90
|
+
return {"success": True, "message": "Task enabled"}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@router.post("/api/scheduled-tasks/{task_id}/disable")
|
|
94
|
+
def disable_scheduled_task(task_id: str) -> dict:
|
|
95
|
+
success = scheduler_manager.set_enabled(task_id, False)
|
|
96
|
+
if not success:
|
|
97
|
+
raise HTTPException(status_code=404, detail="Task not found")
|
|
98
|
+
return {"success": True, "message": "Task disabled"}
|
AutoGLM_GUI/config.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""AutoGLM-GUI 核心配置定义
|
|
2
|
+
|
|
3
|
+
这个模块定义了项目自己的配置类,替代 phone_agent 的配置,
|
|
4
|
+
实现配置层的解耦和扩展性。
|
|
5
|
+
|
|
6
|
+
设计原则:
|
|
7
|
+
- 配置类提供 AutoGLM-GUI 核心业务逻辑所需的参数
|
|
8
|
+
- 避免在 API 层和业务层直接使用外部库的类型
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ModelConfig:
|
|
17
|
+
"""模型配置
|
|
18
|
+
|
|
19
|
+
OpenAI 兼容 API 的配置参数。
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
base_url: API 端点 URL (例如: "http://localhost:8000/v1")
|
|
23
|
+
api_key: API 认证密钥 (本地部署可选)
|
|
24
|
+
model_name: 模型标识符 (例如: "autoglm-phone-9b")
|
|
25
|
+
max_tokens: 响应最大 token 数 (默认: 3000)
|
|
26
|
+
temperature: 采样温度 0-1 (默认: 0.0)
|
|
27
|
+
top_p: Nucleus 采样阈值 (默认: 0.85)
|
|
28
|
+
frequency_penalty: 频率惩罚 -2 到 2 (默认: 0.2)
|
|
29
|
+
extra_body: 特定后端的额外参数
|
|
30
|
+
lang: UI 消息语言: 'cn' 或 'en'
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
base_url: str = "http://localhost:8000/v1"
|
|
34
|
+
api_key: str = "EMPTY"
|
|
35
|
+
model_name: str = "autoglm-phone-9b"
|
|
36
|
+
max_tokens: int = 3000
|
|
37
|
+
temperature: float = 0.0
|
|
38
|
+
top_p: float = 0.85
|
|
39
|
+
frequency_penalty: float = 0.2
|
|
40
|
+
extra_body: dict[str, Any] = field(default_factory=dict)
|
|
41
|
+
lang: str = "cn"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class AgentConfig:
|
|
46
|
+
"""Agent 配置
|
|
47
|
+
|
|
48
|
+
控制 Agent 的行为参数。
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
max_steps: 单次任务最大执行步数 (默认: 100)
|
|
52
|
+
device_id: 设备标识符 (USB serial 或 IP:port)
|
|
53
|
+
lang: 语言设置 'cn' 或 'en'
|
|
54
|
+
system_prompt: 自定义系统提示词 (None 则使用默认)
|
|
55
|
+
verbose: 是否输出详细日志
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
max_steps: int = 100
|
|
59
|
+
device_id: str | None = None
|
|
60
|
+
lang: str = "cn"
|
|
61
|
+
system_prompt: str | None = None
|
|
62
|
+
verbose: bool = True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class StepResult:
|
|
67
|
+
"""Agent 单步执行结果
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
success: 本步骤是否执行成功
|
|
71
|
+
finished: 整个任务是否已完成
|
|
72
|
+
action: 执行的动作字典 (包含 action type 和参数)
|
|
73
|
+
thinking: Agent 的思考过程
|
|
74
|
+
message: 结果消息 (可选)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
success: bool
|
|
78
|
+
finished: bool
|
|
79
|
+
action: dict[str, Any] | None
|
|
80
|
+
thinking: str
|
|
81
|
+
message: str | None = None
|