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.
Files changed (104) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -4
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. AutoGLM_GUI/actions/handler.py +196 -0
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. AutoGLM_GUI/adb/__init__.py +53 -0
  7. AutoGLM_GUI/adb/apps.py +227 -0
  8. AutoGLM_GUI/adb/connection.py +323 -0
  9. AutoGLM_GUI/adb/device.py +171 -0
  10. AutoGLM_GUI/adb/input.py +67 -0
  11. AutoGLM_GUI/adb/screenshot.py +11 -0
  12. AutoGLM_GUI/adb/timing.py +167 -0
  13. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  14. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  15. AutoGLM_GUI/adb_plus/serial.py +38 -20
  16. AutoGLM_GUI/adb_plus/touch.py +4 -9
  17. AutoGLM_GUI/agents/__init__.py +43 -12
  18. AutoGLM_GUI/agents/events.py +19 -0
  19. AutoGLM_GUI/agents/factory.py +31 -38
  20. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  21. AutoGLM_GUI/agents/glm/agent.py +292 -0
  22. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  23. AutoGLM_GUI/agents/glm/parser.py +110 -0
  24. AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
  25. AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
  26. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  27. AutoGLM_GUI/agents/mai/agent.py +405 -0
  28. AutoGLM_GUI/agents/mai/parser.py +254 -0
  29. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  30. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  31. AutoGLM_GUI/agents/protocols.py +12 -8
  32. AutoGLM_GUI/agents/stream_runner.py +188 -0
  33. AutoGLM_GUI/api/__init__.py +40 -21
  34. AutoGLM_GUI/api/agents.py +157 -240
  35. AutoGLM_GUI/api/control.py +9 -6
  36. AutoGLM_GUI/api/devices.py +102 -12
  37. AutoGLM_GUI/api/history.py +78 -0
  38. AutoGLM_GUI/api/layered_agent.py +67 -15
  39. AutoGLM_GUI/api/media.py +64 -1
  40. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  41. AutoGLM_GUI/config.py +81 -0
  42. AutoGLM_GUI/config_manager.py +68 -51
  43. AutoGLM_GUI/device_manager.py +248 -29
  44. AutoGLM_GUI/device_protocol.py +1 -1
  45. AutoGLM_GUI/devices/adb_device.py +5 -10
  46. AutoGLM_GUI/devices/mock_device.py +4 -2
  47. AutoGLM_GUI/devices/remote_device.py +8 -3
  48. AutoGLM_GUI/history_manager.py +164 -0
  49. AutoGLM_GUI/i18n.py +81 -0
  50. AutoGLM_GUI/model/__init__.py +5 -0
  51. AutoGLM_GUI/model/message_builder.py +69 -0
  52. AutoGLM_GUI/model/types.py +24 -0
  53. AutoGLM_GUI/models/__init__.py +10 -0
  54. AutoGLM_GUI/models/history.py +96 -0
  55. AutoGLM_GUI/models/scheduled_task.py +71 -0
  56. AutoGLM_GUI/parsers/__init__.py +22 -0
  57. AutoGLM_GUI/parsers/base.py +50 -0
  58. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  59. AutoGLM_GUI/phone_agent_manager.py +62 -396
  60. AutoGLM_GUI/platform_utils.py +26 -0
  61. AutoGLM_GUI/prompt_config.py +15 -0
  62. AutoGLM_GUI/prompts/__init__.py +32 -0
  63. AutoGLM_GUI/scheduler_manager.py +304 -0
  64. AutoGLM_GUI/schemas.py +234 -72
  65. AutoGLM_GUI/scrcpy_stream.py +142 -24
  66. AutoGLM_GUI/socketio_server.py +100 -27
  67. AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-BQm96DAl.js} +1 -1
  68. AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
  69. AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
  70. AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
  71. AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
  72. AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
  73. AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
  74. AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-CmZSnDqc.js} +1 -1
  75. AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
  76. AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
  77. AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
  78. AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
  79. AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
  80. AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
  81. AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
  82. AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
  83. AutoGLM_GUI/static/index.html +2 -2
  84. AutoGLM_GUI/types.py +17 -0
  85. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +137 -130
  86. autoglm_gui-1.5.0.dist-info/RECORD +157 -0
  87. AutoGLM_GUI/agents/mai_adapter.py +0 -627
  88. AutoGLM_GUI/api/dual_model.py +0 -317
  89. AutoGLM_GUI/dual_model/__init__.py +0 -53
  90. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  91. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  92. AutoGLM_GUI/dual_model/protocols.py +0 -354
  93. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  94. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  95. AutoGLM_GUI/phone_agent_patches.py +0 -147
  96. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
  97. AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
  98. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
  99. AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
  100. AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
  101. autoglm_gui-1.4.1.dist-info/RECORD +0 -117
  102. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
  103. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
  104. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
- if agent_device_id:
42
- metadata = agent_manager.get_metadata(agent_device_id)
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
- else:
52
- response["agent"] = None
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 phone_agent.adb import ADBConnection
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 phone_agent.adb import ADBConnection
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}"}
@@ -53,9 +53,20 @@ def _clear_session(session_id: str) -> bool:
53
53
 
54
54
 
55
55
  def get_planner_model() -> str:
56
- """获取规划层使用的模型名称,从配置读取."""
57
- config = config_manager.get_effective_config()
58
- return config.decision_model_name or "glm-4.7"
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 客户端,使用 AutoGLM 的配置"""
296
+ """设置 OpenAI 客户端,使用决策模型配置"""
286
297
  config_manager.load_file_config()
287
298
  effective_config = config_manager.get_effective_config()
288
299
 
289
- if not effective_config.base_url:
290
- raise ValueError("base_url not configured")
300
+ # 检查决策模型配置
301
+ decision_base_url = effective_config.decision_base_url
302
+ decision_api_key = effective_config.decision_api_key
291
303
 
292
- planner_model = get_planner_model()
293
- logger.info(f"[LayeredAgent] API Base URL: {effective_config.base_url}")
294
- logger.info(f"[LayeredAgent] Planner Model: {planner_model}")
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=effective_config.base_url,
298
- api_key=effective_config.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
- screenshot = capture_screenshot(device_id=request.device_id)
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