autoglm-gui 1.4.0__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 -8
- 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/qr_pair.py +8 -8
- 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 +51 -0
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +153 -0
- 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 +27 -0
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +71 -11
- AutoGLM_GUI/api/agents.py +190 -229
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +112 -28
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +306 -181
- AutoGLM_GUI/api/mcp.py +11 -10
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/api/version.py +23 -10
- AutoGLM_GUI/api/workflows.py +2 -1
- AutoGLM_GUI/config.py +72 -14
- AutoGLM_GUI/config_manager.py +98 -27
- AutoGLM_GUI/device_adapter.py +263 -0
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +266 -0
- AutoGLM_GUI/devices/__init__.py +49 -0
- AutoGLM_GUI/devices/adb_device.py +200 -0
- AutoGLM_GUI/devices/mock_device.py +185 -0
- AutoGLM_GUI/devices/remote_device.py +177 -0
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/metrics.py +13 -20
- 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 +118 -367
- AutoGLM_GUI/platform_utils.py +31 -2
- 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 +272 -63
- AutoGLM_GUI/scrcpy_stream.py +159 -37
- AutoGLM_GUI/server.py +3 -1
- AutoGLM_GUI/socketio_server.py +114 -29
- AutoGLM_GUI/state.py +10 -30
- AutoGLM_GUI/static/assets/{about-DeclntHg.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-zQ4KKDHt.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 +142 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- mai_agent/base.py +137 -0
- mai_agent/mai_grounding_agent.py +263 -0
- mai_agent/mai_naivigation_agent.py +526 -0
- mai_agent/prompt.py +148 -0
- mai_agent/unified_memory.py +67 -0
- mai_agent/utils.py +73 -0
- AutoGLM_GUI/api/dual_model.py +0 -311
- 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 -146
- AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
- AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
- AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
- AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
- AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
- autoglm_gui-1.4.0.dist-info/RECORD +0 -100
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/agents.py
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
"""Agent lifecycle and chat routes."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import queue
|
|
5
|
-
import threading
|
|
6
|
-
from typing import Any
|
|
7
4
|
|
|
8
5
|
from fastapi import APIRouter, HTTPException
|
|
9
6
|
from fastapi.responses import StreamingResponse
|
|
10
7
|
from pydantic import ValidationError
|
|
11
8
|
|
|
12
|
-
from AutoGLM_GUI.
|
|
9
|
+
from AutoGLM_GUI.agents.events import AgentEventType
|
|
10
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
13
11
|
from AutoGLM_GUI.logger import logger
|
|
14
|
-
from AutoGLM_GUI.phone_agent_patches import apply_patches
|
|
15
12
|
from AutoGLM_GUI.schemas import (
|
|
16
13
|
AbortRequest,
|
|
17
|
-
APIAgentConfig,
|
|
18
|
-
APIModelConfig,
|
|
19
14
|
ChatRequest,
|
|
20
15
|
ChatResponse,
|
|
21
16
|
ConfigResponse,
|
|
@@ -28,11 +23,6 @@ from AutoGLM_GUI.state import (
|
|
|
28
23
|
non_blocking_takeover,
|
|
29
24
|
)
|
|
30
25
|
from AutoGLM_GUI.version import APP_VERSION
|
|
31
|
-
from phone_agent.agent import AgentConfig
|
|
32
|
-
from phone_agent.model import ModelConfig
|
|
33
|
-
|
|
34
|
-
# Apply monkey patches to phone_agent
|
|
35
|
-
apply_patches()
|
|
36
26
|
|
|
37
27
|
router = APIRouter()
|
|
38
28
|
|
|
@@ -60,95 +50,95 @@ def _setup_adb_keyboard(device_id: str) -> None:
|
|
|
60
50
|
logger.info(f"✓ Device {device_id}: ADB Keyboard ready")
|
|
61
51
|
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
device_id: str,
|
|
65
|
-
model_config: ModelConfig,
|
|
66
|
-
agent_config: AgentConfig,
|
|
67
|
-
) -> None:
|
|
68
|
-
"""使用给定配置初始化 Agent。
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
device_id: 设备 ID
|
|
72
|
-
model_config: 模型配置
|
|
73
|
-
agent_config: Agent 配置
|
|
74
|
-
|
|
75
|
-
Raises:
|
|
76
|
-
Exception: 初始化失败时抛出异常
|
|
77
|
-
"""
|
|
78
|
-
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
79
|
-
|
|
80
|
-
# Setup ADB Keyboard first
|
|
81
|
-
_setup_adb_keyboard(device_id)
|
|
82
|
-
|
|
83
|
-
# Initialize agent
|
|
84
|
-
manager = PhoneAgentManager.get_instance()
|
|
85
|
-
manager.initialize_agent(
|
|
86
|
-
device_id=device_id,
|
|
87
|
-
model_config=model_config,
|
|
88
|
-
agent_config=agent_config,
|
|
89
|
-
takeover_callback=non_blocking_takeover,
|
|
90
|
-
)
|
|
91
|
-
logger.info(f"Agent initialized successfully for device {device_id}")
|
|
53
|
+
SSEPayload = dict[str, str | int | bool | None | dict]
|
|
92
54
|
|
|
93
55
|
|
|
94
56
|
def _create_sse_event(
|
|
95
|
-
event_type: str, data:
|
|
96
|
-
) ->
|
|
57
|
+
event_type: str, data: SSEPayload, role: str = "assistant"
|
|
58
|
+
) -> SSEPayload:
|
|
97
59
|
"""Create an SSE event with standardized fields including role."""
|
|
98
60
|
event_data = {"type": event_type, "role": role, **data}
|
|
99
61
|
return event_data
|
|
100
62
|
|
|
101
63
|
|
|
102
|
-
@router.post("/api/init")
|
|
64
|
+
@router.post("/api/init", deprecated=True)
|
|
103
65
|
def init_agent(request: InitRequest) -> dict:
|
|
104
|
-
"""初始化 PhoneAgent
|
|
105
|
-
from AutoGLM_GUI.config_manager import config_manager
|
|
66
|
+
"""初始化 PhoneAgent(已废弃,多设备支持)。
|
|
106
67
|
|
|
107
|
-
|
|
108
|
-
|
|
68
|
+
⚠️ 此端点已废弃,将在未来版本移除。
|
|
69
|
+
|
|
70
|
+
Agent 现在会在首次使用时自动初始化,无需手动调用此端点。
|
|
71
|
+
如需修改配置,请使用 /api/config 端点或直接修改配置文件 ~/.config/autoglm/config.json。
|
|
72
|
+
配置保存后会自动销毁所有 Agent,确保下次使用时应用新配置。
|
|
73
|
+
|
|
74
|
+
配置完全由 ConfigManager 提供(CLI > ENV > FILE > DEFAULT),
|
|
75
|
+
不接受运行时覆盖。
|
|
76
|
+
"""
|
|
77
|
+
from AutoGLM_GUI.config_manager import config_manager
|
|
109
78
|
|
|
110
|
-
device_id =
|
|
79
|
+
device_id = request.device_id
|
|
111
80
|
if not device_id:
|
|
112
|
-
raise HTTPException(
|
|
113
|
-
status_code=400, detail="device_id is required in agent_config"
|
|
114
|
-
)
|
|
81
|
+
raise HTTPException(status_code=400, detail="device_id is required")
|
|
115
82
|
|
|
116
83
|
# 热重载配置文件(支持运行时手动修改)
|
|
117
84
|
config_manager.load_file_config()
|
|
118
85
|
config_manager.sync_to_env()
|
|
119
|
-
config.refresh_from_env()
|
|
120
86
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
model_name = req_model_config.model_name or config.model_name
|
|
87
|
+
# 获取有效配置(CLI > ENV > FILE > DEFAULT)
|
|
88
|
+
effective_config = config_manager.get_effective_config()
|
|
124
89
|
|
|
125
|
-
if not base_url:
|
|
90
|
+
if not effective_config.base_url:
|
|
126
91
|
raise HTTPException(
|
|
127
92
|
status_code=400,
|
|
128
93
|
detail="base_url is required. Please configure via Settings or start with --base-url",
|
|
129
94
|
)
|
|
130
95
|
|
|
96
|
+
# 直接使用有效配置构造 ModelConfig 和 AgentConfig
|
|
131
97
|
model_config = ModelConfig(
|
|
132
|
-
base_url=base_url,
|
|
133
|
-
api_key=api_key,
|
|
134
|
-
model_name=model_name,
|
|
135
|
-
max_tokens
|
|
136
|
-
temperature=req_model_config.temperature,
|
|
137
|
-
top_p=req_model_config.top_p,
|
|
138
|
-
frequency_penalty=req_model_config.frequency_penalty,
|
|
98
|
+
base_url=effective_config.base_url,
|
|
99
|
+
api_key=effective_config.api_key,
|
|
100
|
+
model_name=effective_config.model_name,
|
|
101
|
+
# max_tokens, temperature, top_p, frequency_penalty 使用 ModelConfig 默认值
|
|
139
102
|
)
|
|
140
103
|
|
|
141
104
|
agent_config = AgentConfig(
|
|
142
|
-
max_steps=
|
|
105
|
+
max_steps=effective_config.default_max_steps,
|
|
143
106
|
device_id=device_id,
|
|
144
|
-
lang
|
|
145
|
-
system_prompt=req_agent_config.system_prompt,
|
|
146
|
-
verbose=req_agent_config.verbose,
|
|
107
|
+
# lang, system_prompt, verbose 使用 AgentConfig 默认值
|
|
147
108
|
)
|
|
148
109
|
|
|
149
110
|
# Initialize agent (includes ADB Keyboard setup)
|
|
150
111
|
try:
|
|
151
|
-
|
|
112
|
+
# Setup ADB Keyboard (common for all agents)
|
|
113
|
+
_setup_adb_keyboard(device_id)
|
|
114
|
+
|
|
115
|
+
# Use agent factory to create agent
|
|
116
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
117
|
+
|
|
118
|
+
manager = PhoneAgentManager.get_instance()
|
|
119
|
+
|
|
120
|
+
# Initialize agent using factory pattern
|
|
121
|
+
from typing import cast
|
|
122
|
+
|
|
123
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
124
|
+
|
|
125
|
+
agent_config_params = cast(
|
|
126
|
+
AgentSpecificConfig, request.agent_config_params or {}
|
|
127
|
+
)
|
|
128
|
+
manager.initialize_agent_with_factory(
|
|
129
|
+
device_id=device_id,
|
|
130
|
+
agent_type=request.agent_type,
|
|
131
|
+
model_config=model_config,
|
|
132
|
+
agent_config=agent_config,
|
|
133
|
+
agent_specific_config=agent_config_params,
|
|
134
|
+
takeover_callback=non_blocking_takeover,
|
|
135
|
+
force=request.force,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
logger.warning(
|
|
139
|
+
f"/api/init is deprecated. Agent of type '{request.agent_type}' initialized for device {device_id}. "
|
|
140
|
+
f"Consider using auto-initialization instead."
|
|
141
|
+
)
|
|
152
142
|
except Exception as e:
|
|
153
143
|
logger.error(f"Failed to initialize agent: {e}")
|
|
154
144
|
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -156,189 +146,137 @@ def init_agent(request: InitRequest) -> dict:
|
|
|
156
146
|
return {
|
|
157
147
|
"success": True,
|
|
158
148
|
"device_id": device_id,
|
|
159
|
-
"message": f"Agent initialized for device {device_id}",
|
|
149
|
+
"message": f"Agent initialized for device {device_id} (⚠️ /api/init is deprecated)",
|
|
150
|
+
"agent_type": request.agent_type,
|
|
151
|
+
"deprecated": True,
|
|
152
|
+
"hint": "Agent 会在首次使用时自动初始化,无需手动调用此端点",
|
|
160
153
|
}
|
|
161
154
|
|
|
162
155
|
|
|
163
156
|
@router.post("/api/chat", response_model=ChatResponse)
|
|
164
157
|
def chat(request: ChatRequest) -> ChatResponse:
|
|
165
|
-
"""发送任务给 Agent 并执行。
|
|
166
|
-
|
|
158
|
+
"""发送任务给 Agent 并执行。
|
|
159
|
+
|
|
160
|
+
Agent 会在首次使用时自动初始化,无需手动调用 /api/init。
|
|
161
|
+
"""
|
|
162
|
+
from AutoGLM_GUI.exceptions import AgentInitializationError, DeviceBusyError
|
|
167
163
|
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
168
164
|
|
|
169
165
|
device_id = request.device_id
|
|
170
166
|
manager = PhoneAgentManager.get_instance()
|
|
171
167
|
|
|
172
|
-
#
|
|
173
|
-
if not manager.is_initialized(device_id):
|
|
174
|
-
raise HTTPException(
|
|
175
|
-
status_code=400, detail="Agent not initialized. Call /api/init first."
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
# Use context manager for automatic lock management
|
|
168
|
+
# use_agent 默认 auto_initialize=True,会自动初始化 Agent
|
|
179
169
|
try:
|
|
180
170
|
with manager.use_agent(device_id, timeout=None) as agent:
|
|
181
171
|
result = agent.run(request.message)
|
|
182
172
|
steps = agent.step_count
|
|
183
173
|
agent.reset()
|
|
184
174
|
return ChatResponse(result=result, steps=steps, success=True)
|
|
175
|
+
except AgentInitializationError as e:
|
|
176
|
+
# 配置错误或初始化失败
|
|
177
|
+
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
178
|
+
raise HTTPException(
|
|
179
|
+
status_code=500,
|
|
180
|
+
detail=f"初始化失败: {str(e)}. 请检查全局配置 (base_url, api_key, model_name)",
|
|
181
|
+
)
|
|
185
182
|
except DeviceBusyError:
|
|
186
183
|
raise HTTPException(
|
|
187
184
|
status_code=409, detail=f"Device {device_id} is busy. Please wait."
|
|
188
185
|
)
|
|
189
186
|
except Exception as e:
|
|
187
|
+
logger.exception(f"Unexpected error in chat for {device_id}")
|
|
190
188
|
return ChatResponse(result=str(e), steps=0, success=False)
|
|
191
189
|
|
|
192
190
|
|
|
193
191
|
@router.post("/api/chat/stream")
|
|
194
192
|
def chat_stream(request: ChatRequest):
|
|
195
|
-
"""发送任务给 Agent 并实时推送执行进度(SSE,多设备支持)。
|
|
196
|
-
|
|
193
|
+
"""发送任务给 Agent 并实时推送执行进度(SSE,多设备支持)。
|
|
194
|
+
|
|
195
|
+
Agent 会在首次使用时自动初始化,无需手动调用 /api/init。
|
|
196
|
+
"""
|
|
197
|
+
from datetime import datetime
|
|
198
|
+
|
|
199
|
+
from AutoGLM_GUI.agents.stream_runner import AgentStepStreamer
|
|
200
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
201
|
+
from AutoGLM_GUI.exceptions import AgentInitializationError, DeviceBusyError
|
|
202
|
+
from AutoGLM_GUI.history_manager import history_manager
|
|
203
|
+
from AutoGLM_GUI.models.history import ConversationRecord
|
|
197
204
|
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
198
205
|
|
|
199
206
|
device_id = request.device_id
|
|
200
207
|
manager = PhoneAgentManager.get_instance()
|
|
201
208
|
|
|
202
|
-
# 验证 agent 已初始化
|
|
203
|
-
if not manager.is_initialized(device_id):
|
|
204
|
-
raise HTTPException(
|
|
205
|
-
status_code=400,
|
|
206
|
-
detail=f"Device {device_id} not initialized. Call /api/init first.",
|
|
207
|
-
)
|
|
208
|
-
|
|
209
209
|
def event_generator():
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
acquired = False
|
|
211
|
+
start_time = datetime.now()
|
|
212
|
+
final_message = ""
|
|
213
|
+
final_success = False
|
|
214
|
+
final_steps = 0
|
|
212
215
|
|
|
213
216
|
try:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# 思考块回调
|
|
218
|
-
def on_thinking_chunk(chunk: str):
|
|
219
|
-
chunk_data = _create_sse_event("thinking_chunk", {"chunk": chunk})
|
|
220
|
-
event_queue.put(("thinking_chunk", chunk_data))
|
|
221
|
-
|
|
222
|
-
# 使用 streaming agent context manager(自动处理所有管理逻辑!)
|
|
223
|
-
with manager.use_streaming_agent(
|
|
224
|
-
device_id, on_thinking_chunk, timeout=0
|
|
225
|
-
) as (streaming_agent, stop_event):
|
|
226
|
-
# 早期 abort 检查
|
|
227
|
-
if stop_event.is_set():
|
|
228
|
-
logger.info(f"[Abort] Chat aborted before starting for {device_id}")
|
|
229
|
-
yield "event: aborted\n"
|
|
230
|
-
yield 'data: {"type": "aborted", "role": "assistant", "message": "Chat aborted by user"}\n\n'
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
# 在线程中运行 agent 步骤
|
|
234
|
-
step_result: list[Any] = [None]
|
|
235
|
-
error_result: list[Any] = [None]
|
|
236
|
-
|
|
237
|
-
def run_step(is_first: bool = True, task: str | None = None):
|
|
238
|
-
try:
|
|
239
|
-
if stop_event.is_set():
|
|
240
|
-
return
|
|
241
|
-
|
|
242
|
-
result = (
|
|
243
|
-
streaming_agent.step(task)
|
|
244
|
-
if is_first
|
|
245
|
-
else streaming_agent.step()
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
if stop_event.is_set():
|
|
249
|
-
return
|
|
250
|
-
|
|
251
|
-
step_result[0] = result
|
|
252
|
-
except Exception as e:
|
|
253
|
-
error_result[0] = e
|
|
254
|
-
finally:
|
|
255
|
-
event_queue.put(("step_done", None))
|
|
256
|
-
|
|
257
|
-
# 启动第一步
|
|
258
|
-
thread = threading.Thread(
|
|
259
|
-
target=run_step, args=(True, request.message), daemon=True
|
|
260
|
-
)
|
|
261
|
-
thread.start()
|
|
262
|
-
threads.append(thread)
|
|
263
|
-
|
|
264
|
-
# 事件循环
|
|
265
|
-
while not stop_event.is_set():
|
|
266
|
-
try:
|
|
267
|
-
event_type, event_data = event_queue.get(timeout=0.1)
|
|
268
|
-
except queue.Empty:
|
|
269
|
-
continue
|
|
270
|
-
|
|
271
|
-
if event_type == "thinking_chunk":
|
|
272
|
-
yield "event: thinking_chunk\n"
|
|
273
|
-
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
217
|
+
acquired = manager.acquire_device(
|
|
218
|
+
device_id, timeout=0, raise_on_timeout=True, auto_initialize=True
|
|
219
|
+
)
|
|
274
220
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
result = step_result[0]
|
|
280
|
-
event_data = _create_sse_event(
|
|
281
|
-
"step",
|
|
282
|
-
{
|
|
283
|
-
"step": streaming_agent.step_count,
|
|
284
|
-
"thinking": result.thinking,
|
|
285
|
-
"action": result.action,
|
|
286
|
-
"success": result.success,
|
|
287
|
-
"finished": result.finished,
|
|
288
|
-
},
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
yield "event: step\n"
|
|
292
|
-
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
221
|
+
try:
|
|
222
|
+
agent = manager.get_agent(device_id)
|
|
223
|
+
streamer = AgentStepStreamer(agent=agent, task=request.message)
|
|
293
224
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"success": result.success,
|
|
301
|
-
},
|
|
302
|
-
)
|
|
303
|
-
yield "event: done\n"
|
|
304
|
-
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
305
|
-
break
|
|
225
|
+
with streamer.stream_context() as abort_fn:
|
|
226
|
+
manager.register_abort_handler(device_id, abort_fn)
|
|
227
|
+
|
|
228
|
+
for event in streamer:
|
|
229
|
+
event_type = event["type"]
|
|
230
|
+
event_data_dict = event["data"]
|
|
306
231
|
|
|
307
232
|
if (
|
|
308
|
-
|
|
309
|
-
|
|
233
|
+
event_type == AgentEventType.STEP.value
|
|
234
|
+
and event_data_dict.get("step") == -1
|
|
310
235
|
):
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
# 启动下一步
|
|
324
|
-
step_result[0] = None
|
|
325
|
-
error_result[0] = None
|
|
326
|
-
thread = threading.Thread(
|
|
327
|
-
target=run_step, args=(False, None), daemon=True
|
|
328
|
-
)
|
|
329
|
-
thread.start()
|
|
330
|
-
threads.append(thread)
|
|
331
|
-
|
|
332
|
-
# 检查是否被中止
|
|
333
|
-
if stop_event.is_set():
|
|
334
|
-
logger.info(f"[Abort] Streaming chat terminated for {device_id}")
|
|
335
|
-
yield "event: aborted\n"
|
|
336
|
-
yield 'data: {"type": "aborted", "role": "assistant", "message": "Chat aborted by user"}\n\n'
|
|
337
|
-
|
|
338
|
-
# 重置原始 agent(context 已由 use_streaming_agent 同步)
|
|
339
|
-
original_agent = manager.get_agent(device_id)
|
|
340
|
-
original_agent.reset()
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
if event_type == AgentEventType.DONE.value:
|
|
239
|
+
final_message = event_data_dict.get("message", "")
|
|
240
|
+
final_success = event_data_dict.get("success", False)
|
|
241
|
+
final_steps = event_data_dict.get("steps", 0)
|
|
242
|
+
|
|
243
|
+
event_data = _create_sse_event(event_type, event_data_dict)
|
|
244
|
+
|
|
245
|
+
yield f"event: {event_type}\n"
|
|
246
|
+
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
341
247
|
|
|
248
|
+
finally:
|
|
249
|
+
if acquired:
|
|
250
|
+
manager.release_device(device_id)
|
|
251
|
+
|
|
252
|
+
device_manager = DeviceManager.get_instance()
|
|
253
|
+
serialno = device_manager.get_serial_by_device_id(device_id)
|
|
254
|
+
if serialno and final_message:
|
|
255
|
+
end_time = datetime.now()
|
|
256
|
+
record = ConversationRecord(
|
|
257
|
+
task_text=request.message,
|
|
258
|
+
final_message=final_message,
|
|
259
|
+
success=final_success,
|
|
260
|
+
steps=final_steps,
|
|
261
|
+
start_time=start_time,
|
|
262
|
+
end_time=end_time,
|
|
263
|
+
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
264
|
+
source="chat",
|
|
265
|
+
error_message=None if final_success else final_message,
|
|
266
|
+
)
|
|
267
|
+
history_manager.add_record(serialno, record)
|
|
268
|
+
|
|
269
|
+
except AgentInitializationError as e:
|
|
270
|
+
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
271
|
+
error_data = _create_sse_event(
|
|
272
|
+
"error",
|
|
273
|
+
{
|
|
274
|
+
"message": f"初始化失败: {str(e)}",
|
|
275
|
+
"hint": "请检查全局配置 (base_url, api_key, model_name)",
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
yield "event: error\n"
|
|
279
|
+
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
342
280
|
except DeviceBusyError:
|
|
343
281
|
error_data = _create_sse_event("error", {"message": "Device is busy"})
|
|
344
282
|
yield "event: error\n"
|
|
@@ -349,14 +287,7 @@ def chat_stream(request: ChatRequest):
|
|
|
349
287
|
yield "event: error\n"
|
|
350
288
|
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
351
289
|
finally:
|
|
352
|
-
|
|
353
|
-
if "stop_event" in locals():
|
|
354
|
-
stop_event.set()
|
|
355
|
-
|
|
356
|
-
# 等待线程完成(带超时)
|
|
357
|
-
for thread in threads:
|
|
358
|
-
if thread.is_alive():
|
|
359
|
-
thread.join(timeout=5.0)
|
|
290
|
+
manager.unregister_abort_handler(device_id)
|
|
360
291
|
|
|
361
292
|
return StreamingResponse(
|
|
362
293
|
event_generator(),
|
|
@@ -454,12 +385,12 @@ def get_config_endpoint() -> ConfigResponse:
|
|
|
454
385
|
model_name=effective_config.model_name,
|
|
455
386
|
api_key=effective_config.api_key if effective_config.api_key != "EMPTY" else "",
|
|
456
387
|
source=source.value,
|
|
457
|
-
|
|
388
|
+
agent_type=effective_config.agent_type,
|
|
389
|
+
agent_config_params=effective_config.agent_config_params,
|
|
390
|
+
default_max_steps=effective_config.default_max_steps,
|
|
458
391
|
decision_base_url=effective_config.decision_base_url,
|
|
459
392
|
decision_model_name=effective_config.decision_model_name,
|
|
460
|
-
decision_api_key=effective_config.decision_api_key
|
|
461
|
-
if effective_config.decision_api_key
|
|
462
|
-
else "",
|
|
393
|
+
decision_api_key=effective_config.decision_api_key,
|
|
463
394
|
conflicts=[
|
|
464
395
|
{
|
|
465
396
|
"field": c.field,
|
|
@@ -476,8 +407,13 @@ def get_config_endpoint() -> ConfigResponse:
|
|
|
476
407
|
|
|
477
408
|
@router.post("/api/config")
|
|
478
409
|
def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
479
|
-
"""保存配置到文件.
|
|
410
|
+
"""保存配置到文件.
|
|
411
|
+
|
|
412
|
+
副作用:保存配置后会自动销毁所有已初始化的 Agent,
|
|
413
|
+
确保下次使用时所有 Agent 都使用新配置。
|
|
414
|
+
"""
|
|
480
415
|
from AutoGLM_GUI.config_manager import ConfigModel, config_manager
|
|
416
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
481
417
|
|
|
482
418
|
try:
|
|
483
419
|
# Validate incoming configuration
|
|
@@ -492,7 +428,9 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
492
428
|
base_url=request.base_url,
|
|
493
429
|
model_name=request.model_name,
|
|
494
430
|
api_key=request.api_key,
|
|
495
|
-
|
|
431
|
+
agent_type=request.agent_type,
|
|
432
|
+
agent_config_params=request.agent_config_params,
|
|
433
|
+
default_max_steps=request.default_max_steps,
|
|
496
434
|
decision_base_url=request.decision_base_url,
|
|
497
435
|
decision_model_name=request.decision_model_name,
|
|
498
436
|
decision_api_key=request.decision_api_key,
|
|
@@ -504,11 +442,27 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
504
442
|
|
|
505
443
|
# 同步到环境变量
|
|
506
444
|
config_manager.sync_to_env()
|
|
507
|
-
|
|
445
|
+
|
|
446
|
+
# 副作用:销毁所有已初始化的 Agent,确保下次使用新配置
|
|
447
|
+
manager = PhoneAgentManager.get_instance()
|
|
448
|
+
destroyed_agents = manager.list_agents() # 获取需要销毁的 agent 列表
|
|
449
|
+
|
|
450
|
+
for device_id in destroyed_agents:
|
|
451
|
+
try:
|
|
452
|
+
manager.destroy_agent(device_id)
|
|
453
|
+
logger.info(f"Destroyed agent for {device_id} after config change")
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.warning(f"Failed to destroy agent for {device_id}: {e}")
|
|
508
456
|
|
|
509
457
|
# 检测冲突并返回警告
|
|
510
458
|
conflicts = config_manager.detect_conflicts()
|
|
511
459
|
|
|
460
|
+
response_message = f"Configuration saved to {config_manager.get_config_path()}"
|
|
461
|
+
if destroyed_agents:
|
|
462
|
+
response_message += (
|
|
463
|
+
f". Destroyed {len(destroyed_agents)} agent(s) to apply new config."
|
|
464
|
+
)
|
|
465
|
+
|
|
512
466
|
if conflicts:
|
|
513
467
|
warnings = [
|
|
514
468
|
f"{c.field}: file value overridden by {c.override_source.value}"
|
|
@@ -516,13 +470,15 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
516
470
|
]
|
|
517
471
|
return {
|
|
518
472
|
"success": True,
|
|
519
|
-
"message":
|
|
473
|
+
"message": response_message,
|
|
520
474
|
"warnings": warnings,
|
|
475
|
+
"destroyed_agents": len(destroyed_agents),
|
|
521
476
|
}
|
|
522
477
|
|
|
523
478
|
return {
|
|
524
479
|
"success": True,
|
|
525
|
-
"message":
|
|
480
|
+
"message": response_message,
|
|
481
|
+
"destroyed_agents": len(destroyed_agents),
|
|
526
482
|
}
|
|
527
483
|
|
|
528
484
|
except ValidationError as e:
|
|
@@ -546,3 +502,8 @@ def delete_config_endpoint() -> dict:
|
|
|
546
502
|
|
|
547
503
|
except Exception as e:
|
|
548
504
|
raise HTTPException(status_code=500, detail=str(e))
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
# ✅ 已删除 /api/agents/reinit-all 端点
|
|
508
|
+
# 原因:配置保存时自动销毁所有 Agent(副作用),无需单独的 reinit 端点
|
|
509
|
+
# 见 /api/config POST 端点的实现
|
AutoGLM_GUI/api/control.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter
|
|
4
4
|
|
|
5
|
+
from AutoGLM_GUI.devices.adb_device import ADBDevice
|
|
5
6
|
from AutoGLM_GUI.schemas import (
|
|
6
7
|
SwipeRequest,
|
|
7
8
|
SwipeResponse,
|
|
@@ -22,12 +23,13 @@ router = APIRouter()
|
|
|
22
23
|
def control_tap(request: TapRequest) -> TapResponse:
|
|
23
24
|
"""Execute tap at specified device coordinates."""
|
|
24
25
|
try:
|
|
25
|
-
|
|
26
|
+
if not request.device_id:
|
|
27
|
+
return TapResponse(success=False, error="device_id is required")
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
device = ADBDevice(request.device_id)
|
|
30
|
+
device.tap(
|
|
28
31
|
x=request.x,
|
|
29
32
|
y=request.y,
|
|
30
|
-
device_id=request.device_id,
|
|
31
33
|
delay=request.delay,
|
|
32
34
|
)
|
|
33
35
|
|
|
@@ -40,15 +42,16 @@ def control_tap(request: TapRequest) -> TapResponse:
|
|
|
40
42
|
def control_swipe(request: SwipeRequest) -> SwipeResponse:
|
|
41
43
|
"""Execute swipe from start to end coordinates."""
|
|
42
44
|
try:
|
|
43
|
-
|
|
45
|
+
if not request.device_id:
|
|
46
|
+
return SwipeResponse(success=False, error="device_id is required")
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
device = ADBDevice(request.device_id)
|
|
49
|
+
device.swipe(
|
|
46
50
|
start_x=request.start_x,
|
|
47
51
|
start_y=request.start_y,
|
|
48
52
|
end_x=request.end_x,
|
|
49
53
|
end_y=request.end_y,
|
|
50
54
|
duration_ms=request.duration_ms,
|
|
51
|
-
device_id=request.device_id,
|
|
52
55
|
delay=request.delay,
|
|
53
56
|
)
|
|
54
57
|
|