autoglm-gui 1.4.1__py3-none-any.whl → 1.5.1__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
- phone_agent/actions/handler_ios.py → AutoGLM_GUI/actions/handler.py +30 -112
- AutoGLM_GUI/actions/types.py +15 -0
- {phone_agent → AutoGLM_GUI}/adb/__init__.py +25 -23
- {phone_agent → AutoGLM_GUI}/adb/connection.py +5 -40
- {phone_agent → AutoGLM_GUI}/adb/device.py +12 -94
- {phone_agent → AutoGLM_GUI}/adb/input.py +6 -47
- AutoGLM_GUI/adb/screenshot.py +11 -0
- {phone_agent/config → AutoGLM_GUI/adb}/timing.py +1 -1
- 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 +297 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_en.py +7 -9
- {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_zh.py +18 -25
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +408 -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 +193 -0
- AutoGLM_GUI/api/__init__.py +40 -21
- AutoGLM_GUI/api/agents.py +181 -239
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +102 -12
- AutoGLM_GUI/api/history.py +104 -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/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 +140 -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 +350 -0
- AutoGLM_GUI/schemas.py +246 -72
- AutoGLM_GUI/scrcpy_stream.py +142 -24
- AutoGLM_GUI/socketio_server.py +100 -27
- AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-CfwX1Cmc.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-CtGlN2IJ.js +1 -0
- AutoGLM_GUI/static/assets/chat-BYa-foUI.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-t08bEMPO.js +1 -0
- AutoGLM_GUI/static/assets/dialog-FNwZJFwk.js +45 -0
- AutoGLM_GUI/static/assets/eye-D0UPWCWC.js +1 -0
- AutoGLM_GUI/static/assets/history-CRo95B7i.js +1 -0
- AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-BaLMSqd3.js} +1 -1
- AutoGLM_GUI/static/assets/index-CTHbFvKl.js +11 -0
- AutoGLM_GUI/static/assets/index-CV7jGxGm.css +1 -0
- AutoGLM_GUI/static/assets/label-DJFevVmr.js +1 -0
- AutoGLM_GUI/static/assets/logs-RW09DyYY.js +1 -0
- AutoGLM_GUI/static/assets/popover--JTJrE5v.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-DTRKsQXF.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-CPK_K680.js +1 -0
- AutoGLM_GUI/static/assets/textarea-PRmVnWq5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CdcsAoaT.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.1.dist-info}/METADATA +179 -130
- autoglm_gui-1.5.1.dist-info/RECORD +118 -0
- AutoGLM_GUI/agents/mai_adapter.py +0 -627
- AutoGLM_GUI/api/dual_model.py +0 -317
- AutoGLM_GUI/device_adapter.py +0 -263
- 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
- mai_agent/base.py +0 -137
- mai_agent/mai_grounding_agent.py +0 -263
- mai_agent/mai_naivigation_agent.py +0 -526
- mai_agent/prompt.py +0 -148
- mai_agent/unified_memory.py +0 -67
- mai_agent/utils.py +0 -73
- phone_agent/__init__.py +0 -12
- phone_agent/actions/__init__.py +0 -5
- phone_agent/actions/handler.py +0 -400
- phone_agent/adb/screenshot.py +0 -108
- phone_agent/agent.py +0 -253
- phone_agent/agent_ios.py +0 -277
- phone_agent/config/__init__.py +0 -53
- phone_agent/config/apps_harmonyos.py +0 -256
- phone_agent/config/apps_ios.py +0 -339
- phone_agent/config/prompts.py +0 -80
- phone_agent/device_factory.py +0 -166
- phone_agent/hdc/__init__.py +0 -53
- phone_agent/hdc/connection.py +0 -384
- phone_agent/hdc/device.py +0 -269
- phone_agent/hdc/input.py +0 -145
- phone_agent/hdc/screenshot.py +0 -127
- phone_agent/model/__init__.py +0 -5
- phone_agent/model/client.py +0 -290
- phone_agent/xctest/__init__.py +0 -47
- phone_agent/xctest/connection.py +0 -379
- phone_agent/xctest/device.py +0 -472
- phone_agent/xctest/input.py +0 -311
- phone_agent/xctest/screenshot.py +0 -226
- {phone_agent/config → AutoGLM_GUI/adb}/apps.py +0 -0
- {phone_agent/config → AutoGLM_GUI}/i18n.py +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/agents.py
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
"""Agent lifecycle and chat routes."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import queue
|
|
5
|
-
import threading
|
|
6
4
|
|
|
7
5
|
from fastapi import APIRouter, HTTPException
|
|
8
6
|
from fastapi.responses import StreamingResponse
|
|
9
|
-
from phone_agent.agent import StepResult
|
|
10
7
|
from pydantic import ValidationError
|
|
11
8
|
|
|
9
|
+
from AutoGLM_GUI.agents.events import AgentEventType
|
|
10
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
12
11
|
from AutoGLM_GUI.logger import logger
|
|
13
|
-
from AutoGLM_GUI.phone_agent_patches import apply_patches
|
|
14
12
|
from AutoGLM_GUI.schemas import (
|
|
15
13
|
AbortRequest,
|
|
16
|
-
APIAgentConfig,
|
|
17
|
-
APIModelConfig,
|
|
18
14
|
ChatRequest,
|
|
19
15
|
ChatResponse,
|
|
20
16
|
ConfigResponse,
|
|
@@ -27,11 +23,6 @@ from AutoGLM_GUI.state import (
|
|
|
27
23
|
non_blocking_takeover,
|
|
28
24
|
)
|
|
29
25
|
from AutoGLM_GUI.version import APP_VERSION
|
|
30
|
-
from phone_agent.agent import AgentConfig
|
|
31
|
-
from phone_agent.model import ModelConfig
|
|
32
|
-
|
|
33
|
-
# Apply monkey patches to phone_agent
|
|
34
|
-
apply_patches()
|
|
35
26
|
|
|
36
27
|
router = APIRouter()
|
|
37
28
|
|
|
@@ -59,37 +50,6 @@ def _setup_adb_keyboard(device_id: str) -> None:
|
|
|
59
50
|
logger.info(f"✓ Device {device_id}: ADB Keyboard ready")
|
|
60
51
|
|
|
61
52
|
|
|
62
|
-
def _initialize_agent_with_config(
|
|
63
|
-
device_id: str,
|
|
64
|
-
model_config: ModelConfig,
|
|
65
|
-
agent_config: AgentConfig,
|
|
66
|
-
) -> None:
|
|
67
|
-
"""使用给定配置初始化 Agent。
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
device_id: 设备 ID
|
|
71
|
-
model_config: 模型配置
|
|
72
|
-
agent_config: Agent 配置
|
|
73
|
-
|
|
74
|
-
Raises:
|
|
75
|
-
Exception: 初始化失败时抛出异常
|
|
76
|
-
"""
|
|
77
|
-
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
78
|
-
|
|
79
|
-
# Setup ADB Keyboard first
|
|
80
|
-
_setup_adb_keyboard(device_id)
|
|
81
|
-
|
|
82
|
-
# Initialize agent
|
|
83
|
-
manager = PhoneAgentManager.get_instance()
|
|
84
|
-
manager.initialize_agent(
|
|
85
|
-
device_id=device_id,
|
|
86
|
-
model_config=model_config,
|
|
87
|
-
agent_config=agent_config,
|
|
88
|
-
takeover_callback=non_blocking_takeover,
|
|
89
|
-
)
|
|
90
|
-
logger.info(f"Agent initialized successfully for device {device_id}")
|
|
91
|
-
|
|
92
|
-
|
|
93
53
|
SSEPayload = dict[str, str | int | bool | None | dict]
|
|
94
54
|
|
|
95
55
|
|
|
@@ -101,57 +61,50 @@ def _create_sse_event(
|
|
|
101
61
|
return event_data
|
|
102
62
|
|
|
103
63
|
|
|
104
|
-
@router.post("/api/init")
|
|
64
|
+
@router.post("/api/init", deprecated=True)
|
|
105
65
|
def init_agent(request: InitRequest) -> dict:
|
|
106
|
-
"""初始化 PhoneAgent
|
|
107
|
-
from AutoGLM_GUI.config_manager import config_manager
|
|
66
|
+
"""初始化 PhoneAgent(已废弃,多设备支持)。
|
|
108
67
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
111
78
|
|
|
112
|
-
device_id =
|
|
79
|
+
device_id = request.device_id
|
|
113
80
|
if not device_id:
|
|
114
|
-
raise HTTPException(
|
|
115
|
-
status_code=400, detail="device_id is required in agent_config"
|
|
116
|
-
)
|
|
81
|
+
raise HTTPException(status_code=400, detail="device_id is required")
|
|
117
82
|
|
|
118
83
|
# 热重载配置文件(支持运行时手动修改)
|
|
119
84
|
config_manager.load_file_config()
|
|
120
85
|
config_manager.sync_to_env()
|
|
121
86
|
|
|
122
|
-
#
|
|
87
|
+
# 获取有效配置(CLI > ENV > FILE > DEFAULT)
|
|
123
88
|
effective_config = config_manager.get_effective_config()
|
|
124
89
|
|
|
125
|
-
|
|
126
|
-
base_url = req_model_config.base_url or effective_config.base_url
|
|
127
|
-
api_key = req_model_config.api_key or effective_config.api_key
|
|
128
|
-
model_name = req_model_config.model_name or effective_config.model_name
|
|
129
|
-
|
|
130
|
-
# 获取配置的默认最大步数
|
|
131
|
-
max_steps = effective_config.default_max_steps
|
|
132
|
-
|
|
133
|
-
if not base_url:
|
|
90
|
+
if not effective_config.base_url:
|
|
134
91
|
raise HTTPException(
|
|
135
92
|
status_code=400,
|
|
136
93
|
detail="base_url is required. Please configure via Settings or start with --base-url",
|
|
137
94
|
)
|
|
138
95
|
|
|
96
|
+
# 直接使用有效配置构造 ModelConfig 和 AgentConfig
|
|
139
97
|
model_config = ModelConfig(
|
|
140
|
-
base_url=base_url,
|
|
141
|
-
api_key=api_key,
|
|
142
|
-
model_name=model_name,
|
|
143
|
-
max_tokens
|
|
144
|
-
temperature=req_model_config.temperature,
|
|
145
|
-
top_p=req_model_config.top_p,
|
|
146
|
-
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 默认值
|
|
147
102
|
)
|
|
148
103
|
|
|
149
104
|
agent_config = AgentConfig(
|
|
150
|
-
max_steps=
|
|
105
|
+
max_steps=effective_config.default_max_steps,
|
|
151
106
|
device_id=device_id,
|
|
152
|
-
lang
|
|
153
|
-
system_prompt=req_agent_config.system_prompt,
|
|
154
|
-
verbose=req_agent_config.verbose,
|
|
107
|
+
# lang, system_prompt, verbose 使用 AgentConfig 默认值
|
|
155
108
|
)
|
|
156
109
|
|
|
157
110
|
# Initialize agent (includes ADB Keyboard setup)
|
|
@@ -182,8 +135,9 @@ def init_agent(request: InitRequest) -> dict:
|
|
|
182
135
|
force=request.force,
|
|
183
136
|
)
|
|
184
137
|
|
|
185
|
-
logger.
|
|
186
|
-
f"Agent of type '{request.agent_type}' initialized for device {device_id}"
|
|
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."
|
|
187
141
|
)
|
|
188
142
|
except Exception as e:
|
|
189
143
|
logger.error(f"Failed to initialize agent: {e}")
|
|
@@ -192,193 +146,162 @@ def init_agent(request: InitRequest) -> dict:
|
|
|
192
146
|
return {
|
|
193
147
|
"success": True,
|
|
194
148
|
"device_id": device_id,
|
|
195
|
-
"message": f"Agent initialized for device {device_id}",
|
|
149
|
+
"message": f"Agent initialized for device {device_id} (⚠️ /api/init is deprecated)",
|
|
196
150
|
"agent_type": request.agent_type,
|
|
151
|
+
"deprecated": True,
|
|
152
|
+
"hint": "Agent 会在首次使用时自动初始化,无需手动调用此端点",
|
|
197
153
|
}
|
|
198
154
|
|
|
199
155
|
|
|
200
156
|
@router.post("/api/chat", response_model=ChatResponse)
|
|
201
157
|
def chat(request: ChatRequest) -> ChatResponse:
|
|
202
|
-
"""发送任务给 Agent 并执行。
|
|
203
|
-
|
|
158
|
+
"""发送任务给 Agent 并执行。
|
|
159
|
+
|
|
160
|
+
Agent 会在首次使用时自动初始化,无需手动调用 /api/init。
|
|
161
|
+
"""
|
|
162
|
+
from AutoGLM_GUI.exceptions import AgentInitializationError, DeviceBusyError
|
|
204
163
|
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
205
164
|
|
|
206
165
|
device_id = request.device_id
|
|
207
166
|
manager = PhoneAgentManager.get_instance()
|
|
208
167
|
|
|
209
|
-
#
|
|
210
|
-
if not manager.is_initialized(device_id):
|
|
211
|
-
raise HTTPException(
|
|
212
|
-
status_code=400, detail="Agent not initialized. Call /api/init first."
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
# Use context manager for automatic lock management
|
|
168
|
+
# use_agent 默认 auto_initialize=True,会自动初始化 Agent
|
|
216
169
|
try:
|
|
217
170
|
with manager.use_agent(device_id, timeout=None) as agent:
|
|
218
171
|
result = agent.run(request.message)
|
|
219
172
|
steps = agent.step_count
|
|
220
173
|
agent.reset()
|
|
221
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
|
+
)
|
|
222
182
|
except DeviceBusyError:
|
|
223
183
|
raise HTTPException(
|
|
224
184
|
status_code=409, detail=f"Device {device_id} is busy. Please wait."
|
|
225
185
|
)
|
|
226
186
|
except Exception as e:
|
|
187
|
+
logger.exception(f"Unexpected error in chat for {device_id}")
|
|
227
188
|
return ChatResponse(result=str(e), steps=0, success=False)
|
|
228
189
|
|
|
229
190
|
|
|
230
191
|
@router.post("/api/chat/stream")
|
|
231
192
|
def chat_stream(request: ChatRequest):
|
|
232
|
-
"""发送任务给 Agent 并实时推送执行进度(SSE,多设备支持)。
|
|
233
|
-
|
|
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, MessageRecord
|
|
234
204
|
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
235
205
|
|
|
236
206
|
device_id = request.device_id
|
|
237
207
|
manager = PhoneAgentManager.get_instance()
|
|
238
208
|
|
|
239
|
-
# 验证 agent 已初始化
|
|
240
|
-
if not manager.is_initialized(device_id):
|
|
241
|
-
raise HTTPException(
|
|
242
|
-
status_code=400,
|
|
243
|
-
detail=f"Device {device_id} not initialized. Call /api/init first.",
|
|
244
|
-
)
|
|
245
|
-
|
|
246
209
|
def event_generator():
|
|
247
|
-
|
|
248
|
-
|
|
210
|
+
acquired = False
|
|
211
|
+
start_time = datetime.now()
|
|
212
|
+
final_message = ""
|
|
213
|
+
final_success = False
|
|
214
|
+
final_steps = 0
|
|
215
|
+
|
|
216
|
+
# 收集完整对话消息
|
|
217
|
+
messages: list[MessageRecord] = []
|
|
218
|
+
# 添加用户消息
|
|
219
|
+
messages.append(
|
|
220
|
+
MessageRecord(
|
|
221
|
+
role="user",
|
|
222
|
+
content=request.message,
|
|
223
|
+
timestamp=start_time,
|
|
224
|
+
)
|
|
225
|
+
)
|
|
249
226
|
|
|
250
227
|
try:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
# 思考块回调
|
|
255
|
-
def on_thinking_chunk(chunk: str):
|
|
256
|
-
chunk_data = _create_sse_event("thinking_chunk", {"chunk": chunk})
|
|
257
|
-
event_queue.put(("thinking_chunk", chunk_data))
|
|
258
|
-
|
|
259
|
-
# 使用 streaming agent context manager(自动处理所有管理逻辑!)
|
|
260
|
-
with manager.use_streaming_agent(
|
|
261
|
-
device_id, on_thinking_chunk, timeout=0
|
|
262
|
-
) as (streaming_agent, stop_event):
|
|
263
|
-
# 早期 abort 检查
|
|
264
|
-
if stop_event.is_set():
|
|
265
|
-
logger.info(f"[Abort] Chat aborted before starting for {device_id}")
|
|
266
|
-
yield "event: aborted\n"
|
|
267
|
-
yield 'data: {"type": "aborted", "role": "assistant", "message": "Chat aborted by user"}\n\n'
|
|
268
|
-
return
|
|
269
|
-
|
|
270
|
-
# 在线程中运行 agent 步骤
|
|
271
|
-
step_result: list[StepResult | None] = [None]
|
|
272
|
-
error_result: list[Exception | None] = [None]
|
|
273
|
-
|
|
274
|
-
def run_step(is_first: bool = True, task: str | None = None):
|
|
275
|
-
try:
|
|
276
|
-
if stop_event.is_set():
|
|
277
|
-
return
|
|
278
|
-
|
|
279
|
-
result = (
|
|
280
|
-
streaming_agent.step(task)
|
|
281
|
-
if is_first
|
|
282
|
-
else streaming_agent.step()
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
if stop_event.is_set():
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
step_result[0] = result
|
|
289
|
-
except Exception as e:
|
|
290
|
-
error_result[0] = e
|
|
291
|
-
finally:
|
|
292
|
-
event_queue.put(("step_done", None))
|
|
293
|
-
|
|
294
|
-
# 启动第一步
|
|
295
|
-
thread = threading.Thread(
|
|
296
|
-
target=run_step, args=(True, request.message), daemon=True
|
|
297
|
-
)
|
|
298
|
-
thread.start()
|
|
299
|
-
threads.append(thread)
|
|
300
|
-
|
|
301
|
-
# 事件循环
|
|
302
|
-
while not stop_event.is_set():
|
|
303
|
-
try:
|
|
304
|
-
event_type, event_data = event_queue.get(timeout=0.1)
|
|
305
|
-
except queue.Empty:
|
|
306
|
-
continue
|
|
307
|
-
|
|
308
|
-
if event_type == "thinking_chunk":
|
|
309
|
-
yield "event: thinking_chunk\n"
|
|
310
|
-
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
228
|
+
acquired = manager.acquire_device(
|
|
229
|
+
device_id, timeout=0, raise_on_timeout=True, auto_initialize=True
|
|
230
|
+
)
|
|
311
231
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
result = step_result[0]
|
|
317
|
-
if result is None:
|
|
318
|
-
raise RuntimeError("step_result is None after step_done")
|
|
319
|
-
|
|
320
|
-
event_data = _create_sse_event(
|
|
321
|
-
"step",
|
|
322
|
-
{
|
|
323
|
-
"step": streaming_agent.step_count,
|
|
324
|
-
"thinking": result.thinking,
|
|
325
|
-
"action": result.action,
|
|
326
|
-
"success": result.success,
|
|
327
|
-
"finished": result.finished,
|
|
328
|
-
},
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
yield "event: step\n"
|
|
332
|
-
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
232
|
+
try:
|
|
233
|
+
agent = manager.get_agent(device_id)
|
|
234
|
+
streamer = AgentStepStreamer(agent=agent, task=request.message)
|
|
333
235
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
"success": result.success,
|
|
341
|
-
},
|
|
342
|
-
)
|
|
343
|
-
yield "event: done\n"
|
|
344
|
-
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
345
|
-
break
|
|
236
|
+
with streamer.stream_context() as abort_fn:
|
|
237
|
+
manager.register_abort_handler(device_id, abort_fn)
|
|
238
|
+
|
|
239
|
+
for event in streamer:
|
|
240
|
+
event_type = event["type"]
|
|
241
|
+
event_data_dict = event["data"]
|
|
346
242
|
|
|
347
243
|
if (
|
|
348
|
-
|
|
349
|
-
|
|
244
|
+
event_type == AgentEventType.STEP.value
|
|
245
|
+
and event_data_dict.get("step") == -1
|
|
350
246
|
):
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
# 收集每个 step 的消息
|
|
250
|
+
if event_type == AgentEventType.STEP.value:
|
|
251
|
+
messages.append(
|
|
252
|
+
MessageRecord(
|
|
253
|
+
role="assistant",
|
|
254
|
+
content="",
|
|
255
|
+
timestamp=datetime.now(),
|
|
256
|
+
thinking=event_data_dict.get("thinking"),
|
|
257
|
+
action=event_data_dict.get("action"),
|
|
258
|
+
step=event_data_dict.get("step"),
|
|
259
|
+
)
|
|
358
260
|
)
|
|
359
|
-
yield "event: done\n"
|
|
360
|
-
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
361
|
-
break
|
|
362
|
-
|
|
363
|
-
# 启动下一步
|
|
364
|
-
step_result[0] = None
|
|
365
|
-
error_result[0] = None
|
|
366
|
-
thread = threading.Thread(
|
|
367
|
-
target=run_step, args=(False, None), daemon=True
|
|
368
|
-
)
|
|
369
|
-
thread.start()
|
|
370
|
-
threads.append(thread)
|
|
371
|
-
|
|
372
|
-
# 检查是否被中止
|
|
373
|
-
if stop_event.is_set():
|
|
374
|
-
logger.info(f"[Abort] Streaming chat terminated for {device_id}")
|
|
375
|
-
yield "event: aborted\n"
|
|
376
|
-
yield 'data: {"type": "aborted", "role": "assistant", "message": "Chat aborted by user"}\n\n'
|
|
377
|
-
|
|
378
|
-
# 重置原始 agent(context 已由 use_streaming_agent 同步)
|
|
379
|
-
original_agent = manager.get_agent(device_id)
|
|
380
|
-
original_agent.reset()
|
|
381
261
|
|
|
262
|
+
if event_type == AgentEventType.DONE.value:
|
|
263
|
+
final_message = event_data_dict.get("message", "")
|
|
264
|
+
final_success = event_data_dict.get("success", False)
|
|
265
|
+
final_steps = event_data_dict.get("steps", 0)
|
|
266
|
+
|
|
267
|
+
event_data = _create_sse_event(event_type, event_data_dict)
|
|
268
|
+
|
|
269
|
+
yield f"event: {event_type}\n"
|
|
270
|
+
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
271
|
+
|
|
272
|
+
finally:
|
|
273
|
+
if acquired:
|
|
274
|
+
manager.release_device(device_id)
|
|
275
|
+
|
|
276
|
+
device_manager = DeviceManager.get_instance()
|
|
277
|
+
serialno = device_manager.get_serial_by_device_id(device_id)
|
|
278
|
+
if serialno and final_message:
|
|
279
|
+
end_time = datetime.now()
|
|
280
|
+
record = ConversationRecord(
|
|
281
|
+
task_text=request.message,
|
|
282
|
+
final_message=final_message,
|
|
283
|
+
success=final_success,
|
|
284
|
+
steps=final_steps,
|
|
285
|
+
start_time=start_time,
|
|
286
|
+
end_time=end_time,
|
|
287
|
+
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
288
|
+
source="chat",
|
|
289
|
+
error_message=None if final_success else final_message,
|
|
290
|
+
messages=messages,
|
|
291
|
+
)
|
|
292
|
+
history_manager.add_record(serialno, record)
|
|
293
|
+
|
|
294
|
+
except AgentInitializationError as e:
|
|
295
|
+
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
296
|
+
error_data = _create_sse_event(
|
|
297
|
+
"error",
|
|
298
|
+
{
|
|
299
|
+
"message": f"初始化失败: {str(e)}",
|
|
300
|
+
"hint": "请检查全局配置 (base_url, api_key, model_name)",
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
yield "event: error\n"
|
|
304
|
+
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
382
305
|
except DeviceBusyError:
|
|
383
306
|
error_data = _create_sse_event("error", {"message": "Device is busy"})
|
|
384
307
|
yield "event: error\n"
|
|
@@ -389,13 +312,7 @@ def chat_stream(request: ChatRequest):
|
|
|
389
312
|
yield "event: error\n"
|
|
390
313
|
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
391
314
|
finally:
|
|
392
|
-
|
|
393
|
-
stop_event.set()
|
|
394
|
-
|
|
395
|
-
# 等待线程完成(带超时)
|
|
396
|
-
for thread in threads:
|
|
397
|
-
if thread.is_alive():
|
|
398
|
-
thread.join(timeout=5.0)
|
|
315
|
+
manager.unregister_abort_handler(device_id)
|
|
399
316
|
|
|
400
317
|
return StreamingResponse(
|
|
401
318
|
event_generator(),
|
|
@@ -493,15 +410,12 @@ def get_config_endpoint() -> ConfigResponse:
|
|
|
493
410
|
model_name=effective_config.model_name,
|
|
494
411
|
api_key=effective_config.api_key if effective_config.api_key != "EMPTY" else "",
|
|
495
412
|
source=source.value,
|
|
496
|
-
dual_model_enabled=effective_config.dual_model_enabled,
|
|
497
|
-
decision_base_url=effective_config.decision_base_url,
|
|
498
|
-
decision_model_name=effective_config.decision_model_name,
|
|
499
|
-
decision_api_key=effective_config.decision_api_key
|
|
500
|
-
if effective_config.decision_api_key
|
|
501
|
-
else "",
|
|
502
413
|
agent_type=effective_config.agent_type,
|
|
503
414
|
agent_config_params=effective_config.agent_config_params,
|
|
504
415
|
default_max_steps=effective_config.default_max_steps,
|
|
416
|
+
decision_base_url=effective_config.decision_base_url,
|
|
417
|
+
decision_model_name=effective_config.decision_model_name,
|
|
418
|
+
decision_api_key=effective_config.decision_api_key,
|
|
505
419
|
conflicts=[
|
|
506
420
|
{
|
|
507
421
|
"field": c.field,
|
|
@@ -518,8 +432,13 @@ def get_config_endpoint() -> ConfigResponse:
|
|
|
518
432
|
|
|
519
433
|
@router.post("/api/config")
|
|
520
434
|
def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
521
|
-
"""保存配置到文件.
|
|
435
|
+
"""保存配置到文件.
|
|
436
|
+
|
|
437
|
+
副作用:保存配置后会自动销毁所有已初始化的 Agent,
|
|
438
|
+
确保下次使用时所有 Agent 都使用新配置。
|
|
439
|
+
"""
|
|
522
440
|
from AutoGLM_GUI.config_manager import ConfigModel, config_manager
|
|
441
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
523
442
|
|
|
524
443
|
try:
|
|
525
444
|
# Validate incoming configuration
|
|
@@ -534,13 +453,12 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
534
453
|
base_url=request.base_url,
|
|
535
454
|
model_name=request.model_name,
|
|
536
455
|
api_key=request.api_key,
|
|
537
|
-
dual_model_enabled=request.dual_model_enabled,
|
|
538
|
-
decision_base_url=request.decision_base_url,
|
|
539
|
-
decision_model_name=request.decision_model_name,
|
|
540
|
-
decision_api_key=request.decision_api_key,
|
|
541
456
|
agent_type=request.agent_type,
|
|
542
457
|
agent_config_params=request.agent_config_params,
|
|
543
458
|
default_max_steps=request.default_max_steps,
|
|
459
|
+
decision_base_url=request.decision_base_url,
|
|
460
|
+
decision_model_name=request.decision_model_name,
|
|
461
|
+
decision_api_key=request.decision_api_key,
|
|
544
462
|
merge_mode=True,
|
|
545
463
|
)
|
|
546
464
|
|
|
@@ -550,9 +468,26 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
550
468
|
# 同步到环境变量
|
|
551
469
|
config_manager.sync_to_env()
|
|
552
470
|
|
|
471
|
+
# 副作用:销毁所有已初始化的 Agent,确保下次使用新配置
|
|
472
|
+
manager = PhoneAgentManager.get_instance()
|
|
473
|
+
destroyed_agents = manager.list_agents() # 获取需要销毁的 agent 列表
|
|
474
|
+
|
|
475
|
+
for device_id in destroyed_agents:
|
|
476
|
+
try:
|
|
477
|
+
manager.destroy_agent(device_id)
|
|
478
|
+
logger.info(f"Destroyed agent for {device_id} after config change")
|
|
479
|
+
except Exception as e:
|
|
480
|
+
logger.warning(f"Failed to destroy agent for {device_id}: {e}")
|
|
481
|
+
|
|
553
482
|
# 检测冲突并返回警告
|
|
554
483
|
conflicts = config_manager.detect_conflicts()
|
|
555
484
|
|
|
485
|
+
response_message = f"Configuration saved to {config_manager.get_config_path()}"
|
|
486
|
+
if destroyed_agents:
|
|
487
|
+
response_message += (
|
|
488
|
+
f". Destroyed {len(destroyed_agents)} agent(s) to apply new config."
|
|
489
|
+
)
|
|
490
|
+
|
|
556
491
|
if conflicts:
|
|
557
492
|
warnings = [
|
|
558
493
|
f"{c.field}: file value overridden by {c.override_source.value}"
|
|
@@ -560,13 +495,15 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
560
495
|
]
|
|
561
496
|
return {
|
|
562
497
|
"success": True,
|
|
563
|
-
"message":
|
|
498
|
+
"message": response_message,
|
|
564
499
|
"warnings": warnings,
|
|
500
|
+
"destroyed_agents": len(destroyed_agents),
|
|
565
501
|
}
|
|
566
502
|
|
|
567
503
|
return {
|
|
568
504
|
"success": True,
|
|
569
|
-
"message":
|
|
505
|
+
"message": response_message,
|
|
506
|
+
"destroyed_agents": len(destroyed_agents),
|
|
570
507
|
}
|
|
571
508
|
|
|
572
509
|
except ValidationError as e:
|
|
@@ -590,3 +527,8 @@ def delete_config_endpoint() -> dict:
|
|
|
590
527
|
|
|
591
528
|
except Exception as e:
|
|
592
529
|
raise HTTPException(status_code=500, detail=str(e))
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
# ✅ 已删除 /api/agents/reinit-all 端点
|
|
533
|
+
# 原因:配置保存时自动销毁所有 Agent(副作用),无需单独的 reinit 端点
|
|
534
|
+
# 见 /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
|
|