autoglm-gui 1.5.4__tar.gz → 1.5.6__tar.gz
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-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/async_agent.py +3 -50
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/protocols.py +3 -14
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/agents.py +118 -93
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/devices.py +177 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/scheduled_tasks.py +7 -2
- autoglm_gui-1.5.6/AutoGLM_GUI/device_group_manager.py +354 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/models/__init__.py +8 -0
- autoglm_gui-1.5.6/AutoGLM_GUI/models/device_group.py +63 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/models/scheduled_task.py +47 -4
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/scheduler_manager.py +255 -80
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/schemas.py +148 -3
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/about-BZglkj97.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/about-DVviVdH2.js +1 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/alert-dialog-5vNoxwIO.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/alert-dialog-IHmO2JCQ.js +1 -1
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/chat-C_3D0Ao7.js +134 -0
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/dialog-DSAhQHru.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/dialog-DOpd71Lu.js +3 -3
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/eye-Deqw6dbm.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/eye-CZP5ZJ_Y.js +1 -1
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/folder-open-_KlT8ZW7.js +1 -0
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/history-CL-JjUbk.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/history-BXMlCwUV.js +1 -1
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/index-84TrNz7w.css +1 -0
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/index-Bh2f556h.js +1 -0
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/index-DUNWZsFq.js +11 -0
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/label-CEmK7RW4.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/label-CRZhpiYG.js +1 -1
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/logs-DEN9nDRS.js +1 -0
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/popover-DitUZhUk.js +1 -0
- autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/scheduled-tasks-boxDKe87.js +1 -0
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/textarea-nLU4tGQH.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/textarea-CvRHzjfV.js +1 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/workflows-QIA3_mdp.js → autoglm_gui-1.5.6/AutoGLM_GUI/static/assets/workflows-Bg3qN-6j.js +1 -1
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/static/index.html +2 -2
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/PKG-INFO +361 -10
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/README.md +360 -9
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/pyproject.toml +1 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/chat-ta_RqZfZ.js +0 -129
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/circle-alert-CnwO7Du-.js +0 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/index-BjaUZM-7.js +0 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/index-CX4NAYCk.js +0 -11
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/index-DSIMVL8V.css +0 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/logs-C-Pnb4jI.js +0 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/popover-CauTjrhB.js +0 -1
- autoglm_gui-1.5.4/AutoGLM_GUI/static/assets/scheduled-tasks-Ds1WrRVN.js +0 -1
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/.gitignore +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/__main__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/actions/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/actions/handler.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/actions/types.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/apps.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/connection.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/device.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/input.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/screenshot.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb/timing.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/device.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/ip.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/keyboard_installer.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/mdns.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/pair.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/qr_pair.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/screenshot.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/serial.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/touch.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/adb_plus/version.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/events.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/factory.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/agent.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/message_builder.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/parser.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/prompts_en.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/glm/prompts_zh.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/mai/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/mai/agent.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/mai/parser.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/mai/prompts.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/mai/traj_memory.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/agents/stream_runner.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/control.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/health.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/history.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/layered_agent.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/mcp.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/media.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/metrics.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/version.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/api/workflows.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/config.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/config_manager.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/device_manager.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/device_metadata_manager.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/device_protocol.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/devices/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/devices/adb_device.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/devices/mock_device.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/devices/remote_device.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/exceptions.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/history_manager.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/i18n.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/logger.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/metrics.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/model/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/model/message_builder.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/model/types.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/models/history.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/parsers/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/parsers/base.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/parsers/phone_parser.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/phone_agent_manager.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/platform_utils.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/prompt_config.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/prompts/__init__.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/prompts.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/scrcpy_protocol.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/scrcpy_stream.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/server.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/socketio_server.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/state.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/static/assets/logo-Cyfm06Ym.png +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/static/assets/worker-D6BRitjy.js +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/static/favicon.ico +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/static/logo-192.png +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/static/logo-512.png +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/types.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/version.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/AutoGLM_GUI/workflow_manager.py +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/LICENSE +0 -0
- {autoglm_gui-1.5.4 → autoglm_gui-1.5.6}/scrcpy-server-v3.3.3 +0 -0
|
@@ -8,7 +8,8 @@ from typing import Any, AsyncIterator, Callable
|
|
|
8
8
|
from openai import AsyncOpenAI
|
|
9
9
|
|
|
10
10
|
from AutoGLM_GUI.actions import ActionHandler, ActionResult
|
|
11
|
-
from AutoGLM_GUI.
|
|
11
|
+
from AutoGLM_GUI.agents.protocols import AsyncAgent
|
|
12
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
12
13
|
from AutoGLM_GUI.device_protocol import DeviceProtocol
|
|
13
14
|
from AutoGLM_GUI.logger import logger
|
|
14
15
|
from AutoGLM_GUI.prompt_config import get_messages, get_system_prompt
|
|
@@ -17,7 +18,7 @@ from .message_builder import MessageBuilder
|
|
|
17
18
|
from .parser import GLMParser
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class AsyncGLMAgent:
|
|
21
|
+
class AsyncGLMAgent(AsyncAgent):
|
|
21
22
|
"""异步 GLM Agent 实现。
|
|
22
23
|
|
|
23
24
|
核心特性:
|
|
@@ -466,54 +467,6 @@ class AsyncGLMAgent:
|
|
|
466
467
|
final_message = event["data"].get("message", "")
|
|
467
468
|
return final_message
|
|
468
469
|
|
|
469
|
-
async def step(self, task: str | None = None) -> StepResult:
|
|
470
|
-
"""执行单步(兼容接口)。
|
|
471
|
-
|
|
472
|
-
Args:
|
|
473
|
-
task: 任务描述(首步必需,后续可选)
|
|
474
|
-
|
|
475
|
-
Returns:
|
|
476
|
-
StepResult: 步骤结果
|
|
477
|
-
"""
|
|
478
|
-
is_first_execution = len(self._context) == 1 # 只有 system message
|
|
479
|
-
if is_first_execution:
|
|
480
|
-
if not task:
|
|
481
|
-
raise ValueError("Task is required for the first step")
|
|
482
|
-
|
|
483
|
-
# 首次执行:需要先添加用户输入
|
|
484
|
-
try:
|
|
485
|
-
screenshot = await asyncio.to_thread(self.device.get_screenshot)
|
|
486
|
-
current_app = await asyncio.to_thread(self.device.get_current_app)
|
|
487
|
-
except Exception as e:
|
|
488
|
-
logger.error(f"Failed to get device info during initialization: {e}")
|
|
489
|
-
raise RuntimeError(f"Device error: {e}") from e
|
|
490
|
-
|
|
491
|
-
screen_info = MessageBuilder.build_screen_info(current_app)
|
|
492
|
-
initial_message = f"{task}\n\n** Screen Info **\n\n{screen_info}"
|
|
493
|
-
|
|
494
|
-
self._context.append(
|
|
495
|
-
MessageBuilder.create_user_message(
|
|
496
|
-
text=initial_message, image_base64=screenshot.base64_data
|
|
497
|
-
)
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
# 执行步骤
|
|
501
|
-
result = None
|
|
502
|
-
async for event in self._execute_step_async():
|
|
503
|
-
if event["type"] == "step":
|
|
504
|
-
result = StepResult(
|
|
505
|
-
thinking=event["data"]["thinking"],
|
|
506
|
-
action=event["data"]["action"],
|
|
507
|
-
success=event["data"]["success"],
|
|
508
|
-
finished=event["data"]["finished"],
|
|
509
|
-
message=event["data"].get("message"),
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
if result is None:
|
|
513
|
-
raise RuntimeError("Step execution did not produce a result")
|
|
514
|
-
|
|
515
|
-
return result
|
|
516
|
-
|
|
517
470
|
@property
|
|
518
471
|
def step_count(self) -> int:
|
|
519
472
|
return self._step_count
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Protocol
|
|
5
5
|
|
|
6
6
|
from AutoGLM_GUI.config import AgentConfig, ModelConfig, StepResult
|
|
7
7
|
|
|
@@ -76,19 +76,8 @@ class AsyncAgent(Protocol):
|
|
|
76
76
|
"""
|
|
77
77
|
...
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
task: 任务描述(首步必需,后续可选)
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
StepResult: 步骤结果
|
|
87
|
-
"""
|
|
88
|
-
...
|
|
89
|
-
|
|
90
|
-
async def stream(self, task: str) -> AsyncIterator[dict[str, Any]]:
|
|
91
|
-
"""流式执行任务,yield 事件字典。
|
|
79
|
+
def stream(self, task: str) -> Any:
|
|
80
|
+
"""流式执行任务,返回异步生成器。
|
|
92
81
|
|
|
93
82
|
这是核心方法,支持:
|
|
94
83
|
- 实时流式输出 (thinking chunks)
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
|
|
6
|
-
from fastapi import APIRouter, HTTPException
|
|
7
|
-
from fastapi.responses import StreamingResponse
|
|
6
|
+
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
|
7
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
8
8
|
from pydantic import ValidationError
|
|
9
9
|
|
|
10
10
|
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
@@ -203,7 +203,7 @@ async def chat(request: ChatRequest) -> ChatResponse:
|
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
@router.post("/api/chat/stream")
|
|
206
|
-
async def chat_stream(request: ChatRequest):
|
|
206
|
+
async def chat_stream(request: ChatRequest, background_tasks: BackgroundTasks):
|
|
207
207
|
"""发送任务给 Agent 并实时推送执行进度(SSE,多设备支持)。
|
|
208
208
|
|
|
209
209
|
Agent 会在首次使用时自动初始化,无需手动调用 /api/init。
|
|
@@ -221,8 +221,53 @@ async def chat_stream(request: ChatRequest):
|
|
|
221
221
|
device_id = request.device_id
|
|
222
222
|
manager = PhoneAgentManager.get_instance()
|
|
223
223
|
|
|
224
|
+
# ===== 在外层获取设备锁 =====
|
|
225
|
+
acquired = False
|
|
226
|
+
try:
|
|
227
|
+
acquired = await asyncio.to_thread(
|
|
228
|
+
manager.acquire_device,
|
|
229
|
+
device_id,
|
|
230
|
+
timeout=0,
|
|
231
|
+
raise_on_timeout=True,
|
|
232
|
+
auto_initialize=True,
|
|
233
|
+
)
|
|
234
|
+
except DeviceBusyError:
|
|
235
|
+
logger.warning(f"Device {device_id} is busy, returning 409")
|
|
236
|
+
return JSONResponse(
|
|
237
|
+
status_code=409,
|
|
238
|
+
content={"detail": f"Device {device_id} is busy. Please wait."},
|
|
239
|
+
)
|
|
240
|
+
except AgentInitializationError as e:
|
|
241
|
+
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
242
|
+
return JSONResponse(
|
|
243
|
+
status_code=500,
|
|
244
|
+
content={
|
|
245
|
+
"detail": f"初始化失败: {str(e)}. 请检查全局配置 (base_url, api_key, model_name)"
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
logger.info(f"Device lock acquired for {device_id}")
|
|
250
|
+
|
|
251
|
+
# ===== 定义清理函数 =====
|
|
252
|
+
async def cleanup():
|
|
253
|
+
"""Background task: 清理资源"""
|
|
254
|
+
try:
|
|
255
|
+
await asyncio.to_thread(manager.unregister_abort_handler, device_id)
|
|
256
|
+
logger.debug(f"Abort handler unregistered for {device_id}")
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.warning(f"Failed to unregister abort handler for {device_id}: {e}")
|
|
259
|
+
|
|
260
|
+
if acquired:
|
|
261
|
+
try:
|
|
262
|
+
await asyncio.to_thread(manager.release_device, device_id)
|
|
263
|
+
logger.info(f"Device lock released for {device_id} (background task)")
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Failed to release device lock for {device_id}: {e}")
|
|
266
|
+
|
|
267
|
+
# ===== 注册 background task =====
|
|
268
|
+
background_tasks.add_task(cleanup)
|
|
269
|
+
|
|
224
270
|
async def event_generator():
|
|
225
|
-
acquired = False
|
|
226
271
|
start_time = datetime.now()
|
|
227
272
|
final_message = ""
|
|
228
273
|
final_success = False
|
|
@@ -240,93 +285,57 @@ async def chat_stream(request: ChatRequest):
|
|
|
240
285
|
)
|
|
241
286
|
|
|
242
287
|
try:
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
-
manager.
|
|
288
|
+
# 使用 chat context 获取 AsyncAgent
|
|
289
|
+
agent = await asyncio.to_thread(
|
|
290
|
+
manager.get_agent_with_context,
|
|
246
291
|
device_id,
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
auto_initialize=True,
|
|
292
|
+
context="chat",
|
|
293
|
+
agent_type="glm-async",
|
|
250
294
|
)
|
|
251
295
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if event_type == "step":
|
|
279
|
-
messages.append(
|
|
280
|
-
MessageRecord(
|
|
281
|
-
role="assistant",
|
|
282
|
-
content="",
|
|
283
|
-
timestamp=datetime.now(),
|
|
284
|
-
thinking=event_data_dict.get("thinking"),
|
|
285
|
-
action=event_data_dict.get("action"),
|
|
286
|
-
step=event_data_dict.get("step"),
|
|
287
|
-
)
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
if event_type == "done":
|
|
291
|
-
final_message = event_data_dict.get("message", "")
|
|
292
|
-
final_success = event_data_dict.get("success", False)
|
|
293
|
-
final_steps = event_data_dict.get("steps", 0)
|
|
294
|
-
|
|
295
|
-
# 发送 SSE 事件
|
|
296
|
-
sse_event = _create_sse_event(event_type, event_data_dict)
|
|
297
|
-
yield f"event: {event_type}\n"
|
|
298
|
-
yield f"data: {json.dumps(sse_event, ensure_ascii=False)}\n\n"
|
|
299
|
-
|
|
300
|
-
except asyncio.CancelledError:
|
|
301
|
-
logger.info(f"AsyncAgent task cancelled for device {device_id}")
|
|
302
|
-
yield "event: cancelled\n"
|
|
303
|
-
yield f"data: {json.dumps({'message': 'Task cancelled by user'})}\n\n"
|
|
304
|
-
raise
|
|
305
|
-
|
|
306
|
-
finally:
|
|
307
|
-
await asyncio.to_thread(manager.unregister_abort_handler, device_id)
|
|
308
|
-
|
|
309
|
-
finally:
|
|
310
|
-
if acquired:
|
|
311
|
-
await asyncio.to_thread(manager.release_device, device_id)
|
|
312
|
-
|
|
313
|
-
device_manager = DeviceManager.get_instance()
|
|
314
|
-
serialno = device_manager.get_serial_by_device_id(device_id)
|
|
315
|
-
if serialno and final_message:
|
|
316
|
-
end_time = datetime.now()
|
|
317
|
-
record = ConversationRecord(
|
|
318
|
-
task_text=request.message,
|
|
319
|
-
final_message=final_message,
|
|
320
|
-
success=final_success,
|
|
321
|
-
steps=final_steps,
|
|
322
|
-
start_time=start_time,
|
|
323
|
-
end_time=end_time,
|
|
324
|
-
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
325
|
-
source="chat",
|
|
326
|
-
error_message=None if final_success else final_message,
|
|
327
|
-
messages=messages,
|
|
296
|
+
logger.info(f"Using AsyncAgent for device {device_id}")
|
|
297
|
+
|
|
298
|
+
# 注册异步取消处理器
|
|
299
|
+
async def cancel_handler():
|
|
300
|
+
await agent.cancel() # type: ignore[union-attr]
|
|
301
|
+
|
|
302
|
+
await asyncio.to_thread(
|
|
303
|
+
manager.register_abort_handler, device_id, cancel_handler
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# 直接使用 agent.stream()
|
|
307
|
+
async for event in agent.stream(request.message): # type: ignore[union-attr]
|
|
308
|
+
event_type = event["type"]
|
|
309
|
+
event_data_dict = event["data"]
|
|
310
|
+
|
|
311
|
+
# 收集每个 step 的消息
|
|
312
|
+
if event_type == "step":
|
|
313
|
+
messages.append(
|
|
314
|
+
MessageRecord(
|
|
315
|
+
role="assistant",
|
|
316
|
+
content="",
|
|
317
|
+
timestamp=datetime.now(),
|
|
318
|
+
thinking=event_data_dict.get("thinking"),
|
|
319
|
+
action=event_data_dict.get("action"),
|
|
320
|
+
step=event_data_dict.get("step"),
|
|
321
|
+
)
|
|
328
322
|
)
|
|
329
|
-
|
|
323
|
+
|
|
324
|
+
if event_type == "done":
|
|
325
|
+
final_message = event_data_dict.get("message", "")
|
|
326
|
+
final_success = event_data_dict.get("success", False)
|
|
327
|
+
final_steps = event_data_dict.get("steps", 0)
|
|
328
|
+
|
|
329
|
+
# 发送 SSE 事件
|
|
330
|
+
sse_event = _create_sse_event(event_type, event_data_dict)
|
|
331
|
+
yield f"event: {event_type}\n"
|
|
332
|
+
yield f"data: {json.dumps(sse_event, ensure_ascii=False)}\n\n"
|
|
333
|
+
|
|
334
|
+
except asyncio.CancelledError:
|
|
335
|
+
logger.info(f"AsyncAgent task cancelled for device {device_id}")
|
|
336
|
+
yield "event: cancelled\n"
|
|
337
|
+
yield f"data: {json.dumps({'message': 'Task cancelled by user'})}\n\n"
|
|
338
|
+
# ✅ 不再 raise,让 generator 正常结束
|
|
330
339
|
|
|
331
340
|
except AgentInitializationError as e:
|
|
332
341
|
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
@@ -339,17 +348,33 @@ async def chat_stream(request: ChatRequest):
|
|
|
339
348
|
)
|
|
340
349
|
yield "event: error\n"
|
|
341
350
|
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
342
|
-
|
|
343
|
-
error_data = _create_sse_event("error", {"message": "Device is busy"})
|
|
344
|
-
yield "event: error\n"
|
|
345
|
-
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
351
|
+
|
|
346
352
|
except Exception as e:
|
|
347
353
|
logger.exception(f"Error in streaming chat for {device_id}")
|
|
348
354
|
error_data = _create_sse_event("error", {"message": str(e)})
|
|
349
355
|
yield "event: error\n"
|
|
350
356
|
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
351
|
-
|
|
352
|
-
|
|
357
|
+
|
|
358
|
+
# ===== 保存历史记录 =====
|
|
359
|
+
device_manager = DeviceManager.get_instance()
|
|
360
|
+
serialno = device_manager.get_serial_by_device_id(device_id)
|
|
361
|
+
if serialno and final_message:
|
|
362
|
+
end_time = datetime.now()
|
|
363
|
+
record = ConversationRecord(
|
|
364
|
+
task_text=request.message,
|
|
365
|
+
final_message=final_message,
|
|
366
|
+
success=final_success,
|
|
367
|
+
steps=final_steps,
|
|
368
|
+
start_time=start_time,
|
|
369
|
+
end_time=end_time,
|
|
370
|
+
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
371
|
+
source="chat",
|
|
372
|
+
error_message=None if final_success else final_message,
|
|
373
|
+
messages=messages,
|
|
374
|
+
)
|
|
375
|
+
history_manager.add_record(serialno, record)
|
|
376
|
+
|
|
377
|
+
# Generator 正常结束,cleanup 会在 background task 中执行
|
|
353
378
|
|
|
354
379
|
return StreamingResponse(
|
|
355
380
|
event_generator(),
|
|
@@ -14,6 +14,13 @@ from AutoGLM_GUI.adb_plus.qr_pair import qr_pairing_manager
|
|
|
14
14
|
from AutoGLM_GUI.logger import logger
|
|
15
15
|
|
|
16
16
|
from AutoGLM_GUI.schemas import (
|
|
17
|
+
DeviceGroupAssignRequest,
|
|
18
|
+
DeviceGroupCreateRequest,
|
|
19
|
+
DeviceGroupListResponse,
|
|
20
|
+
DeviceGroupOperationResponse,
|
|
21
|
+
DeviceGroupReorderRequest,
|
|
22
|
+
DeviceGroupResponse,
|
|
23
|
+
DeviceGroupUpdateRequest,
|
|
17
24
|
DeviceListResponse,
|
|
18
25
|
DeviceNameResponse,
|
|
19
26
|
DeviceNameUpdateRequest,
|
|
@@ -49,9 +56,13 @@ def _build_device_response_with_agent(
|
|
|
49
56
|
API 层负责协调 DeviceManager 和 PhoneAgentManager,
|
|
50
57
|
通过遍历设备的所有连接来查找已初始化的 Agent。
|
|
51
58
|
"""
|
|
59
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
52
60
|
|
|
53
61
|
response = device.to_dict()
|
|
54
62
|
|
|
63
|
+
# 添加分组信息
|
|
64
|
+
response["group_id"] = device_group_manager.get_device_group(device.serial)
|
|
65
|
+
|
|
55
66
|
# 遍历设备的所有连接,查找已初始化的 Agent
|
|
56
67
|
# 使用 device.connections 公开属性(ManagedDevice 提供)
|
|
57
68
|
for conn in device.connections:
|
|
@@ -526,3 +537,169 @@ def get_device_name(serial: str) -> DeviceNameResponse:
|
|
|
526
537
|
serial=serial,
|
|
527
538
|
error=f"Internal error: {str(e)}",
|
|
528
539
|
)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
# Device Group Routes
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
@router.get("/api/device-groups", response_model=DeviceGroupListResponse)
|
|
546
|
+
def list_device_groups() -> DeviceGroupListResponse:
|
|
547
|
+
"""列出所有设备分组."""
|
|
548
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
549
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
550
|
+
|
|
551
|
+
groups = device_group_manager.list_groups()
|
|
552
|
+
device_manager = DeviceManager.get_instance()
|
|
553
|
+
|
|
554
|
+
# 获取当前所有设备的 serial 列表
|
|
555
|
+
managed_devices = device_manager.get_devices()
|
|
556
|
+
device_serials = {d.serial for d in managed_devices}
|
|
557
|
+
|
|
558
|
+
# 计算每个分组的设备数量
|
|
559
|
+
assignments = device_group_manager.get_all_assignments()
|
|
560
|
+
|
|
561
|
+
group_responses = []
|
|
562
|
+
for group in groups:
|
|
563
|
+
# 统计分配到该分组的设备数量(只计算当前在线/已知的设备)
|
|
564
|
+
if group.id == "default":
|
|
565
|
+
# 默认分组:包含未显式分配的设备
|
|
566
|
+
assigned_to_other = {
|
|
567
|
+
serial for serial, gid in assignments.items() if gid != "default"
|
|
568
|
+
}
|
|
569
|
+
device_count = len(device_serials - assigned_to_other)
|
|
570
|
+
else:
|
|
571
|
+
device_count = sum(
|
|
572
|
+
1
|
|
573
|
+
for serial, gid in assignments.items()
|
|
574
|
+
if gid == group.id and serial in device_serials
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
group_responses.append(
|
|
578
|
+
DeviceGroupResponse(
|
|
579
|
+
id=group.id,
|
|
580
|
+
name=group.name,
|
|
581
|
+
order=group.order,
|
|
582
|
+
created_at=group.created_at.isoformat(),
|
|
583
|
+
updated_at=group.updated_at.isoformat(),
|
|
584
|
+
is_default=group.is_default,
|
|
585
|
+
device_count=device_count,
|
|
586
|
+
)
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
return DeviceGroupListResponse(groups=group_responses)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@router.post("/api/device-groups", response_model=DeviceGroupResponse)
|
|
593
|
+
def create_device_group(request: DeviceGroupCreateRequest) -> DeviceGroupResponse:
|
|
594
|
+
"""创建新的设备分组."""
|
|
595
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
596
|
+
|
|
597
|
+
group = device_group_manager.create_group(request.name)
|
|
598
|
+
|
|
599
|
+
return DeviceGroupResponse(
|
|
600
|
+
id=group.id,
|
|
601
|
+
name=group.name,
|
|
602
|
+
order=group.order,
|
|
603
|
+
created_at=group.created_at.isoformat(),
|
|
604
|
+
updated_at=group.updated_at.isoformat(),
|
|
605
|
+
is_default=group.is_default,
|
|
606
|
+
device_count=0,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@router.put("/api/device-groups/{group_id}", response_model=DeviceGroupResponse)
|
|
611
|
+
def update_device_group(
|
|
612
|
+
group_id: str, request: DeviceGroupUpdateRequest
|
|
613
|
+
) -> DeviceGroupResponse:
|
|
614
|
+
"""更新设备分组名称."""
|
|
615
|
+
from fastapi import HTTPException
|
|
616
|
+
|
|
617
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
618
|
+
|
|
619
|
+
group = device_group_manager.update_group(group_id, request.name)
|
|
620
|
+
if not group:
|
|
621
|
+
raise HTTPException(status_code=404, detail="Group not found")
|
|
622
|
+
|
|
623
|
+
return DeviceGroupResponse(
|
|
624
|
+
id=group.id,
|
|
625
|
+
name=group.name,
|
|
626
|
+
order=group.order,
|
|
627
|
+
created_at=group.created_at.isoformat(),
|
|
628
|
+
updated_at=group.updated_at.isoformat(),
|
|
629
|
+
is_default=group.is_default,
|
|
630
|
+
device_count=0, # 不重新计算,前端可以刷新列表获取
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@router.delete(
|
|
635
|
+
"/api/device-groups/{group_id}", response_model=DeviceGroupOperationResponse
|
|
636
|
+
)
|
|
637
|
+
def delete_device_group(group_id: str) -> DeviceGroupOperationResponse:
|
|
638
|
+
"""删除设备分组(设备移回默认分组)."""
|
|
639
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
640
|
+
from AutoGLM_GUI.models.device_group import DEFAULT_GROUP_ID
|
|
641
|
+
|
|
642
|
+
if group_id == DEFAULT_GROUP_ID:
|
|
643
|
+
return DeviceGroupOperationResponse(
|
|
644
|
+
success=False,
|
|
645
|
+
message="Cannot delete default group",
|
|
646
|
+
error="cannot_delete_default",
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
success = device_group_manager.delete_group(group_id)
|
|
650
|
+
|
|
651
|
+
if success:
|
|
652
|
+
return DeviceGroupOperationResponse(
|
|
653
|
+
success=True,
|
|
654
|
+
message="Group deleted, devices moved to default group",
|
|
655
|
+
)
|
|
656
|
+
else:
|
|
657
|
+
return DeviceGroupOperationResponse(
|
|
658
|
+
success=False,
|
|
659
|
+
message="Group not found",
|
|
660
|
+
error="group_not_found",
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
@router.put("/api/device-groups/reorder", response_model=DeviceGroupOperationResponse)
|
|
665
|
+
def reorder_device_groups(
|
|
666
|
+
request: DeviceGroupReorderRequest,
|
|
667
|
+
) -> DeviceGroupOperationResponse:
|
|
668
|
+
"""调整设备分组顺序."""
|
|
669
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
670
|
+
|
|
671
|
+
success = device_group_manager.reorder_groups(request.group_ids)
|
|
672
|
+
|
|
673
|
+
if success:
|
|
674
|
+
return DeviceGroupOperationResponse(
|
|
675
|
+
success=True,
|
|
676
|
+
message="Groups reordered successfully",
|
|
677
|
+
)
|
|
678
|
+
else:
|
|
679
|
+
return DeviceGroupOperationResponse(
|
|
680
|
+
success=False,
|
|
681
|
+
message="Failed to reorder groups",
|
|
682
|
+
error="reorder_failed",
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
@router.put("/api/devices/{serial}/group", response_model=DeviceGroupOperationResponse)
|
|
687
|
+
def assign_device_to_group(
|
|
688
|
+
serial: str, request: DeviceGroupAssignRequest
|
|
689
|
+
) -> DeviceGroupOperationResponse:
|
|
690
|
+
"""分配设备到指定分组."""
|
|
691
|
+
from AutoGLM_GUI.device_group_manager import device_group_manager
|
|
692
|
+
|
|
693
|
+
success = device_group_manager.assign_device(serial, request.group_id)
|
|
694
|
+
|
|
695
|
+
if success:
|
|
696
|
+
return DeviceGroupOperationResponse(
|
|
697
|
+
success=True,
|
|
698
|
+
message=f"Device assigned to group {request.group_id}",
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
return DeviceGroupOperationResponse(
|
|
702
|
+
success=False,
|
|
703
|
+
message="Failed to assign device to group",
|
|
704
|
+
error="assignment_failed",
|
|
705
|
+
)
|
|
@@ -19,13 +19,17 @@ def _task_to_response(task) -> ScheduledTaskResponse:
|
|
|
19
19
|
id=task.id,
|
|
20
20
|
name=task.name,
|
|
21
21
|
workflow_uuid=task.workflow_uuid,
|
|
22
|
-
|
|
22
|
+
device_serialnos=task.device_serialnos,
|
|
23
|
+
device_group_id=task.device_group_id,
|
|
23
24
|
cron_expression=task.cron_expression,
|
|
24
25
|
enabled=task.enabled,
|
|
25
26
|
created_at=task.created_at.isoformat(),
|
|
26
27
|
updated_at=task.updated_at.isoformat(),
|
|
27
28
|
last_run_time=task.last_run_time.isoformat() if task.last_run_time else None,
|
|
28
29
|
last_run_success=task.last_run_success,
|
|
30
|
+
last_run_status=task.last_run_status,
|
|
31
|
+
last_run_success_count=task.last_run_success_count,
|
|
32
|
+
last_run_total_count=task.last_run_total_count,
|
|
29
33
|
last_run_message=task.last_run_message,
|
|
30
34
|
next_run_time=next_run.isoformat() if next_run else None,
|
|
31
35
|
)
|
|
@@ -48,7 +52,8 @@ def create_scheduled_task(request: ScheduledTaskCreate) -> ScheduledTaskResponse
|
|
|
48
52
|
task = scheduler_manager.create_task(
|
|
49
53
|
name=request.name,
|
|
50
54
|
workflow_uuid=request.workflow_uuid,
|
|
51
|
-
|
|
55
|
+
device_serialnos=request.device_serialnos,
|
|
56
|
+
device_group_id=request.device_group_id,
|
|
52
57
|
cron_expression=request.cron_expression,
|
|
53
58
|
enabled=request.enabled,
|
|
54
59
|
)
|