autoglm-gui 1.4.1__py3-none-any.whl → 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- AutoGLM_GUI/__init__.py +11 -0
- AutoGLM_GUI/__main__.py +26 -4
- AutoGLM_GUI/actions/__init__.py +6 -0
- AutoGLM_GUI/actions/handler.py +196 -0
- AutoGLM_GUI/actions/types.py +15 -0
- AutoGLM_GUI/adb/__init__.py +53 -0
- AutoGLM_GUI/adb/apps.py +227 -0
- AutoGLM_GUI/adb/connection.py +323 -0
- AutoGLM_GUI/adb/device.py +171 -0
- AutoGLM_GUI/adb/input.py +67 -0
- AutoGLM_GUI/adb/screenshot.py +11 -0
- AutoGLM_GUI/adb/timing.py +167 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
- AutoGLM_GUI/adb_plus/screenshot.py +22 -1
- AutoGLM_GUI/adb_plus/serial.py +38 -20
- AutoGLM_GUI/adb_plus/touch.py +4 -9
- AutoGLM_GUI/agents/__init__.py +43 -12
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +31 -38
- AutoGLM_GUI/agents/glm/__init__.py +7 -0
- AutoGLM_GUI/agents/glm/agent.py +292 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
- AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +405 -0
- AutoGLM_GUI/agents/mai/parser.py +254 -0
- AutoGLM_GUI/agents/mai/prompts.py +103 -0
- AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
- AutoGLM_GUI/agents/protocols.py +12 -8
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +40 -21
- AutoGLM_GUI/api/agents.py +157 -240
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +102 -12
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +67 -15
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/config.py +81 -0
- AutoGLM_GUI/config_manager.py +68 -51
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +1 -1
- AutoGLM_GUI/devices/adb_device.py +5 -10
- AutoGLM_GUI/devices/mock_device.py +4 -2
- AutoGLM_GUI/devices/remote_device.py +8 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/model/__init__.py +5 -0
- AutoGLM_GUI/model/message_builder.py +69 -0
- AutoGLM_GUI/model/types.py +24 -0
- AutoGLM_GUI/models/__init__.py +10 -0
- AutoGLM_GUI/models/history.py +96 -0
- AutoGLM_GUI/models/scheduled_task.py +71 -0
- AutoGLM_GUI/parsers/__init__.py +22 -0
- AutoGLM_GUI/parsers/base.py +50 -0
- AutoGLM_GUI/parsers/phone_parser.py +58 -0
- AutoGLM_GUI/phone_agent_manager.py +62 -396
- AutoGLM_GUI/platform_utils.py +26 -0
- AutoGLM_GUI/prompt_config.py +15 -0
- AutoGLM_GUI/prompts/__init__.py +32 -0
- AutoGLM_GUI/scheduler_manager.py +304 -0
- AutoGLM_GUI/schemas.py +234 -72
- AutoGLM_GUI/scrcpy_stream.py +142 -24
- AutoGLM_GUI/socketio_server.py +100 -27
- AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-BQm96DAl.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
- AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
- AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-CmZSnDqc.js} +1 -1
- AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
- AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
- AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
- AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +17 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +137 -130
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- AutoGLM_GUI/agents/mai_adapter.py +0 -627
- AutoGLM_GUI/api/dual_model.py +0 -317
- AutoGLM_GUI/dual_model/__init__.py +0 -53
- AutoGLM_GUI/dual_model/decision_model.py +0 -664
- AutoGLM_GUI/dual_model/dual_agent.py +0 -917
- AutoGLM_GUI/dual_model/protocols.py +0 -354
- AutoGLM_GUI/dual_model/vision_model.py +0 -442
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
- AutoGLM_GUI/phone_agent_patches.py +0 -147
- AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
- AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
- AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
- AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
- AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
- autoglm_gui-1.4.1.dist-info/RECORD +0 -117
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/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
|
-
|
|
66
|
+
"""初始化 PhoneAgent(已废弃,多设备支持)。
|
|
67
|
+
|
|
68
|
+
⚠️ 此端点已废弃,将在未来版本移除。
|
|
108
69
|
|
|
109
|
-
|
|
110
|
-
|
|
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,137 @@ 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
|
|
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
|
|
249
215
|
|
|
250
216
|
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"
|
|
217
|
+
acquired = manager.acquire_device(
|
|
218
|
+
device_id, timeout=0, raise_on_timeout=True, auto_initialize=True
|
|
219
|
+
)
|
|
311
220
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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"
|
|
221
|
+
try:
|
|
222
|
+
agent = manager.get_agent(device_id)
|
|
223
|
+
streamer = AgentStepStreamer(agent=agent, task=request.message)
|
|
224
|
+
|
|
225
|
+
with streamer.stream_context() as abort_fn:
|
|
226
|
+
manager.register_abort_handler(device_id, abort_fn)
|
|
333
227
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
{
|
|
338
|
-
"message": result.message,
|
|
339
|
-
"steps": streaming_agent.step_count,
|
|
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
|
|
228
|
+
for event in streamer:
|
|
229
|
+
event_type = event["type"]
|
|
230
|
+
event_data_dict = event["data"]
|
|
346
231
|
|
|
347
232
|
if (
|
|
348
|
-
|
|
349
|
-
|
|
233
|
+
event_type == AgentEventType.STEP.value
|
|
234
|
+
and event_data_dict.get("step") == -1
|
|
350
235
|
):
|
|
351
|
-
|
|
352
|
-
"done",
|
|
353
|
-
{
|
|
354
|
-
"message": "Max steps reached",
|
|
355
|
-
"steps": streaming_agent.step_count,
|
|
356
|
-
"success": result.success,
|
|
357
|
-
},
|
|
358
|
-
)
|
|
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()
|
|
236
|
+
continue
|
|
381
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"
|
|
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"
|
|
382
280
|
except DeviceBusyError:
|
|
383
281
|
error_data = _create_sse_event("error", {"message": "Device is busy"})
|
|
384
282
|
yield "event: error\n"
|
|
@@ -389,13 +287,7 @@ def chat_stream(request: ChatRequest):
|
|
|
389
287
|
yield "event: error\n"
|
|
390
288
|
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
391
289
|
finally:
|
|
392
|
-
|
|
393
|
-
stop_event.set()
|
|
394
|
-
|
|
395
|
-
# 等待线程完成(带超时)
|
|
396
|
-
for thread in threads:
|
|
397
|
-
if thread.is_alive():
|
|
398
|
-
thread.join(timeout=5.0)
|
|
290
|
+
manager.unregister_abort_handler(device_id)
|
|
399
291
|
|
|
400
292
|
return StreamingResponse(
|
|
401
293
|
event_generator(),
|
|
@@ -493,15 +385,12 @@ def get_config_endpoint() -> ConfigResponse:
|
|
|
493
385
|
model_name=effective_config.model_name,
|
|
494
386
|
api_key=effective_config.api_key if effective_config.api_key != "EMPTY" else "",
|
|
495
387
|
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
388
|
agent_type=effective_config.agent_type,
|
|
503
389
|
agent_config_params=effective_config.agent_config_params,
|
|
504
390
|
default_max_steps=effective_config.default_max_steps,
|
|
391
|
+
decision_base_url=effective_config.decision_base_url,
|
|
392
|
+
decision_model_name=effective_config.decision_model_name,
|
|
393
|
+
decision_api_key=effective_config.decision_api_key,
|
|
505
394
|
conflicts=[
|
|
506
395
|
{
|
|
507
396
|
"field": c.field,
|
|
@@ -518,8 +407,13 @@ def get_config_endpoint() -> ConfigResponse:
|
|
|
518
407
|
|
|
519
408
|
@router.post("/api/config")
|
|
520
409
|
def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
521
|
-
"""保存配置到文件.
|
|
410
|
+
"""保存配置到文件.
|
|
411
|
+
|
|
412
|
+
副作用:保存配置后会自动销毁所有已初始化的 Agent,
|
|
413
|
+
确保下次使用时所有 Agent 都使用新配置。
|
|
414
|
+
"""
|
|
522
415
|
from AutoGLM_GUI.config_manager import ConfigModel, config_manager
|
|
416
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
523
417
|
|
|
524
418
|
try:
|
|
525
419
|
# Validate incoming configuration
|
|
@@ -534,13 +428,12 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
534
428
|
base_url=request.base_url,
|
|
535
429
|
model_name=request.model_name,
|
|
536
430
|
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
431
|
agent_type=request.agent_type,
|
|
542
432
|
agent_config_params=request.agent_config_params,
|
|
543
433
|
default_max_steps=request.default_max_steps,
|
|
434
|
+
decision_base_url=request.decision_base_url,
|
|
435
|
+
decision_model_name=request.decision_model_name,
|
|
436
|
+
decision_api_key=request.decision_api_key,
|
|
544
437
|
merge_mode=True,
|
|
545
438
|
)
|
|
546
439
|
|
|
@@ -550,9 +443,26 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
550
443
|
# 同步到环境变量
|
|
551
444
|
config_manager.sync_to_env()
|
|
552
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}")
|
|
456
|
+
|
|
553
457
|
# 检测冲突并返回警告
|
|
554
458
|
conflicts = config_manager.detect_conflicts()
|
|
555
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
|
+
|
|
556
466
|
if conflicts:
|
|
557
467
|
warnings = [
|
|
558
468
|
f"{c.field}: file value overridden by {c.override_source.value}"
|
|
@@ -560,13 +470,15 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
|
560
470
|
]
|
|
561
471
|
return {
|
|
562
472
|
"success": True,
|
|
563
|
-
"message":
|
|
473
|
+
"message": response_message,
|
|
564
474
|
"warnings": warnings,
|
|
475
|
+
"destroyed_agents": len(destroyed_agents),
|
|
565
476
|
}
|
|
566
477
|
|
|
567
478
|
return {
|
|
568
479
|
"success": True,
|
|
569
|
-
"message":
|
|
480
|
+
"message": response_message,
|
|
481
|
+
"destroyed_agents": len(destroyed_agents),
|
|
570
482
|
}
|
|
571
483
|
|
|
572
484
|
except ValidationError as e:
|
|
@@ -590,3 +502,8 @@ def delete_config_endpoint() -> dict:
|
|
|
590
502
|
|
|
591
503
|
except Exception as e:
|
|
592
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
|
|