autoglm-gui 1.0.2__py3-none-any.whl → 1.2.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/adb_plus/__init__.py +12 -1
- AutoGLM_GUI/adb_plus/mdns.py +192 -0
- AutoGLM_GUI/adb_plus/pair.py +60 -0
- AutoGLM_GUI/adb_plus/qr_pair.py +372 -0
- AutoGLM_GUI/adb_plus/serial.py +61 -2
- AutoGLM_GUI/adb_plus/version.py +81 -0
- AutoGLM_GUI/api/__init__.py +16 -1
- AutoGLM_GUI/api/agents.py +329 -94
- AutoGLM_GUI/api/devices.py +304 -100
- AutoGLM_GUI/api/workflows.py +70 -0
- AutoGLM_GUI/device_manager.py +760 -0
- AutoGLM_GUI/exceptions.py +18 -0
- AutoGLM_GUI/phone_agent_manager.py +549 -0
- AutoGLM_GUI/phone_agent_patches.py +146 -0
- AutoGLM_GUI/schemas.py +380 -2
- AutoGLM_GUI/state.py +21 -0
- AutoGLM_GUI/static/assets/{about-BOnRPlKQ.js → about-PcGX7dIG.js} +1 -1
- AutoGLM_GUI/static/assets/chat-B0FKL2ne.js +124 -0
- AutoGLM_GUI/static/assets/dialog-BSNX0L1i.js +45 -0
- AutoGLM_GUI/static/assets/index-BjYIY--m.css +1 -0
- AutoGLM_GUI/static/assets/index-CnEYDOXp.js +11 -0
- AutoGLM_GUI/static/assets/index-DOt5XNhh.js +1 -0
- AutoGLM_GUI/static/assets/logo-Cyfm06Ym.png +0 -0
- AutoGLM_GUI/static/assets/workflows-B1hgBC_O.js +1 -0
- AutoGLM_GUI/static/favicon.ico +0 -0
- AutoGLM_GUI/static/index.html +9 -2
- AutoGLM_GUI/static/logo-192.png +0 -0
- AutoGLM_GUI/static/logo-512.png +0 -0
- AutoGLM_GUI/workflow_manager.py +181 -0
- {autoglm_gui-1.0.2.dist-info → autoglm_gui-1.2.0.dist-info}/METADATA +80 -35
- {autoglm_gui-1.0.2.dist-info → autoglm_gui-1.2.0.dist-info}/RECORD +34 -19
- AutoGLM_GUI/static/assets/chat-CGW6uMKB.js +0 -149
- AutoGLM_GUI/static/assets/index-CRFVU0eu.js +0 -1
- AutoGLM_GUI/static/assets/index-DH-Dl4tK.js +0 -10
- AutoGLM_GUI/static/assets/index-DzUQ89YC.css +0 -1
- {autoglm_gui-1.0.2.dist-info → autoglm_gui-1.2.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.0.2.dist-info → autoglm_gui-1.2.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.0.2.dist-info → autoglm_gui-1.2.0.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/api/agents.py
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"""Agent lifecycle and chat routes."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import queue
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Any
|
|
4
7
|
|
|
5
8
|
from fastapi import APIRouter, HTTPException
|
|
6
9
|
from fastapi.responses import StreamingResponse
|
|
7
10
|
from pydantic import ValidationError
|
|
8
11
|
|
|
9
12
|
from AutoGLM_GUI.config import config
|
|
13
|
+
from AutoGLM_GUI.logger import logger
|
|
14
|
+
from AutoGLM_GUI.phone_agent_patches import apply_patches
|
|
10
15
|
from AutoGLM_GUI.schemas import (
|
|
16
|
+
AbortRequest,
|
|
11
17
|
APIAgentConfig,
|
|
12
18
|
APIModelConfig,
|
|
13
19
|
ChatRequest,
|
|
@@ -19,8 +25,6 @@ from AutoGLM_GUI.schemas import (
|
|
|
19
25
|
StatusResponse,
|
|
20
26
|
)
|
|
21
27
|
from AutoGLM_GUI.state import (
|
|
22
|
-
agent_configs,
|
|
23
|
-
agents,
|
|
24
28
|
non_blocking_takeover,
|
|
25
29
|
)
|
|
26
30
|
from AutoGLM_GUI.version import APP_VERSION
|
|
@@ -28,8 +32,29 @@ from phone_agent import PhoneAgent
|
|
|
28
32
|
from phone_agent.agent import AgentConfig
|
|
29
33
|
from phone_agent.model import ModelConfig
|
|
30
34
|
|
|
35
|
+
# Apply monkey patches to phone_agent
|
|
36
|
+
apply_patches()
|
|
37
|
+
|
|
31
38
|
router = APIRouter()
|
|
32
39
|
|
|
40
|
+
# Active chat sessions (device_id -> stop_event)
|
|
41
|
+
# Used for aborting ongoing conversations
|
|
42
|
+
_active_chats: dict[str, threading.Event] = {}
|
|
43
|
+
_active_chats_lock = threading.Lock()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _release_device_lock_when_done(
|
|
47
|
+
device_id: str, threads: list[threading.Thread]
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Block until threads finish, then release the device lock via manager."""
|
|
50
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
51
|
+
|
|
52
|
+
for thread in threads:
|
|
53
|
+
thread.join()
|
|
54
|
+
|
|
55
|
+
manager = PhoneAgentManager.get_instance()
|
|
56
|
+
manager.release_device(device_id)
|
|
57
|
+
|
|
33
58
|
|
|
34
59
|
@router.post("/api/init")
|
|
35
60
|
def init_agent(request: InitRequest) -> dict:
|
|
@@ -95,13 +120,20 @@ def init_agent(request: InitRequest) -> dict:
|
|
|
95
120
|
verbose=req_agent_config.verbose,
|
|
96
121
|
)
|
|
97
122
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
agent_config=agent_config,
|
|
101
|
-
takeover_callback=non_blocking_takeover,
|
|
102
|
-
)
|
|
123
|
+
# Initialize agent via PhoneAgentManager (thread-safe, transactional)
|
|
124
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
103
125
|
|
|
104
|
-
|
|
126
|
+
manager = PhoneAgentManager.get_instance()
|
|
127
|
+
try:
|
|
128
|
+
manager.initialize_agent(
|
|
129
|
+
device_id=device_id,
|
|
130
|
+
model_config=model_config,
|
|
131
|
+
agent_config=agent_config,
|
|
132
|
+
takeover_callback=non_blocking_takeover,
|
|
133
|
+
)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Failed to initialize agent: {e}")
|
|
136
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
105
137
|
|
|
106
138
|
return {
|
|
107
139
|
"success": True,
|
|
@@ -113,20 +145,29 @@ def init_agent(request: InitRequest) -> dict:
|
|
|
113
145
|
@router.post("/api/chat", response_model=ChatResponse)
|
|
114
146
|
def chat(request: ChatRequest) -> ChatResponse:
|
|
115
147
|
"""发送任务给 Agent 并执行。"""
|
|
148
|
+
from AutoGLM_GUI.exceptions import DeviceBusyError
|
|
149
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
150
|
+
|
|
116
151
|
device_id = request.device_id
|
|
117
|
-
|
|
152
|
+
manager = PhoneAgentManager.get_instance()
|
|
153
|
+
|
|
154
|
+
# Check if agent is initialized
|
|
155
|
+
if not manager.is_initialized(device_id):
|
|
118
156
|
raise HTTPException(
|
|
119
157
|
status_code=400, detail="Agent not initialized. Call /api/init first."
|
|
120
158
|
)
|
|
121
159
|
|
|
122
|
-
|
|
123
|
-
|
|
160
|
+
# Use context manager for automatic lock management
|
|
124
161
|
try:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
with manager.use_agent(device_id, timeout=None) as agent:
|
|
163
|
+
result = agent.run(request.message)
|
|
164
|
+
steps = agent.step_count
|
|
165
|
+
agent.reset()
|
|
166
|
+
return ChatResponse(result=result, steps=steps, success=True)
|
|
167
|
+
except DeviceBusyError:
|
|
168
|
+
raise HTTPException(
|
|
169
|
+
status_code=409, detail=f"Device {device_id} is busy. Please wait."
|
|
170
|
+
)
|
|
130
171
|
except Exception as e:
|
|
131
172
|
return ChatResponse(result=str(e), steps=0, success=False)
|
|
132
173
|
|
|
@@ -134,96 +175,279 @@ def chat(request: ChatRequest) -> ChatResponse:
|
|
|
134
175
|
@router.post("/api/chat/stream")
|
|
135
176
|
def chat_stream(request: ChatRequest):
|
|
136
177
|
"""发送任务给 Agent 并实时推送执行进度(SSE,多设备支持)。"""
|
|
178
|
+
from AutoGLM_GUI.exceptions import AgentNotInitializedError, DeviceBusyError
|
|
179
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
180
|
+
|
|
137
181
|
device_id = request.device_id
|
|
182
|
+
manager = PhoneAgentManager.get_instance()
|
|
138
183
|
|
|
139
|
-
if
|
|
184
|
+
# Check if agent is initialized
|
|
185
|
+
if not manager.is_initialized(device_id):
|
|
140
186
|
raise HTTPException(
|
|
141
187
|
status_code=400,
|
|
142
188
|
detail=f"Device {device_id} not initialized. Call /api/init first.",
|
|
143
189
|
)
|
|
144
190
|
|
|
145
|
-
|
|
191
|
+
# Acquire device lock (non-blocking) to prevent concurrent requests
|
|
192
|
+
try:
|
|
193
|
+
manager.acquire_device(device_id, timeout=0, raise_on_timeout=True)
|
|
194
|
+
except DeviceBusyError:
|
|
195
|
+
raise HTTPException(
|
|
196
|
+
status_code=409,
|
|
197
|
+
detail=f"Device {device_id} is already processing a request. Please wait.",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
# Get the original agent to copy its config
|
|
202
|
+
original_agent = manager.get_agent(device_id)
|
|
146
203
|
|
|
147
|
-
|
|
148
|
-
"""SSE 事件生成器"""
|
|
204
|
+
# Get the stored configs for this device
|
|
149
205
|
try:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
206
|
+
model_config, agent_config = manager.get_config(device_id)
|
|
207
|
+
except AgentNotInitializedError:
|
|
208
|
+
manager.release_device(device_id)
|
|
209
|
+
raise HTTPException(
|
|
210
|
+
status_code=400,
|
|
211
|
+
detail=f"Device {device_id} config not found.",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def event_generator():
|
|
215
|
+
"""SSE 事件生成器"""
|
|
216
|
+
threads: list[threading.Thread] = []
|
|
217
|
+
stop_event = threading.Event()
|
|
218
|
+
|
|
219
|
+
# Register stop_event to global mapping for abort support
|
|
220
|
+
with _active_chats_lock:
|
|
221
|
+
_active_chats[device_id] = stop_event
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
# Create a queue to collect events from the agent
|
|
225
|
+
event_queue: queue.Queue[tuple[str, Any]] = queue.Queue()
|
|
226
|
+
|
|
227
|
+
# Create a callback to handle thinking chunks
|
|
228
|
+
def on_thinking_chunk(chunk: str):
|
|
229
|
+
"""Emit thinking chunks as they arrive"""
|
|
230
|
+
if not stop_event.is_set():
|
|
231
|
+
chunk_data = {
|
|
232
|
+
"type": "thinking_chunk",
|
|
233
|
+
"chunk": chunk,
|
|
234
|
+
}
|
|
235
|
+
event_queue.put(("thinking_chunk", chunk_data))
|
|
236
|
+
|
|
237
|
+
# Create a new agent instance
|
|
238
|
+
streaming_agent = PhoneAgent(
|
|
239
|
+
model_config=model_config,
|
|
240
|
+
agent_config=agent_config,
|
|
241
|
+
takeover_callback=non_blocking_takeover,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Copy context from original agent (thread-safe due to device lock)
|
|
245
|
+
streaming_agent._context = original_agent._context.copy()
|
|
246
|
+
streaming_agent._step_count = original_agent._step_count
|
|
247
|
+
|
|
248
|
+
# Monkey-patch the model_client.request to inject the callback
|
|
249
|
+
original_request = streaming_agent.model_client.request
|
|
250
|
+
|
|
251
|
+
def patched_request(messages, **kwargs):
|
|
252
|
+
# Inject the on_thinking_chunk callback
|
|
253
|
+
return original_request(
|
|
254
|
+
messages, on_thinking_chunk=on_thinking_chunk
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
streaming_agent.model_client.request = patched_request
|
|
258
|
+
|
|
259
|
+
# Early abort check (before starting any steps)
|
|
260
|
+
if stop_event.is_set():
|
|
261
|
+
logger.info(
|
|
262
|
+
f"[Abort] Agent for device {device_id} received abort signal before starting steps"
|
|
263
|
+
)
|
|
264
|
+
yield "event: aborted\n"
|
|
265
|
+
yield 'data: {"type": "aborted", "message": "Chat aborted by user"}\n\n'
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
# Run agent step in a separate thread
|
|
269
|
+
step_result: list[Any] = [None]
|
|
270
|
+
error_result: list[Any] = [None]
|
|
271
|
+
|
|
272
|
+
def run_step(is_first: bool = True, task: str | None = None):
|
|
273
|
+
try:
|
|
274
|
+
# Check before starting step
|
|
275
|
+
if stop_event.is_set():
|
|
276
|
+
logger.info(
|
|
277
|
+
f"[Abort] Agent for device {device_id} received abort signal before step execution"
|
|
278
|
+
)
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
if is_first:
|
|
282
|
+
result = streaming_agent.step(task)
|
|
283
|
+
else:
|
|
284
|
+
result = streaming_agent.step()
|
|
285
|
+
|
|
286
|
+
# Check after step completes
|
|
287
|
+
if stop_event.is_set():
|
|
288
|
+
logger.info(
|
|
289
|
+
f"[Abort] Agent for device {device_id} received abort signal after step execution"
|
|
290
|
+
)
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
step_result[0] = result
|
|
294
|
+
except Exception as e:
|
|
295
|
+
error_result[0] = e
|
|
296
|
+
finally:
|
|
297
|
+
event_queue.put(("step_done", None))
|
|
298
|
+
|
|
299
|
+
# Start first step
|
|
300
|
+
thread = threading.Thread(
|
|
301
|
+
target=run_step, args=(True, request.message), daemon=True
|
|
302
|
+
)
|
|
303
|
+
thread.start()
|
|
304
|
+
threads.append(thread)
|
|
305
|
+
|
|
306
|
+
while not stop_event.is_set():
|
|
307
|
+
# Wait for events from the queue
|
|
308
|
+
try:
|
|
309
|
+
event_type, event_data = event_queue.get(timeout=0.1)
|
|
310
|
+
except queue.Empty:
|
|
311
|
+
# Check again on timeout
|
|
312
|
+
if stop_event.is_set():
|
|
313
|
+
break
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
if event_type == "thinking_chunk":
|
|
317
|
+
yield "event: thinking_chunk\n"
|
|
318
|
+
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
319
|
+
|
|
320
|
+
elif event_type == "step_done":
|
|
321
|
+
# Check for errors
|
|
322
|
+
if error_result[0]:
|
|
323
|
+
raise error_result[0]
|
|
324
|
+
|
|
325
|
+
result = step_result[0]
|
|
326
|
+
event_data = {
|
|
327
|
+
"type": "step",
|
|
328
|
+
"step": streaming_agent.step_count,
|
|
329
|
+
"thinking": result.thinking,
|
|
330
|
+
"action": result.action,
|
|
331
|
+
"success": result.success,
|
|
332
|
+
"finished": result.finished,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
yield "event: step\n"
|
|
336
|
+
yield f"data: {json.dumps(event_data, ensure_ascii=False)}\n\n"
|
|
337
|
+
|
|
338
|
+
if result.finished:
|
|
339
|
+
done_data = {
|
|
340
|
+
"type": "done",
|
|
341
|
+
"message": result.message,
|
|
342
|
+
"steps": streaming_agent.step_count,
|
|
343
|
+
"success": result.success,
|
|
344
|
+
}
|
|
345
|
+
yield "event: done\n"
|
|
346
|
+
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
if (
|
|
350
|
+
streaming_agent.step_count
|
|
351
|
+
>= streaming_agent.agent_config.max_steps
|
|
352
|
+
):
|
|
353
|
+
done_data = {
|
|
354
|
+
"type": "done",
|
|
355
|
+
"message": "Max steps reached",
|
|
356
|
+
"steps": streaming_agent.step_count,
|
|
357
|
+
"success": result.success,
|
|
358
|
+
}
|
|
359
|
+
yield "event: done\n"
|
|
360
|
+
yield f"data: {json.dumps(done_data, ensure_ascii=False)}\n\n"
|
|
361
|
+
break
|
|
362
|
+
|
|
363
|
+
# Start next step
|
|
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
|
+
# Check if loop exited due to abort
|
|
373
|
+
if stop_event.is_set():
|
|
374
|
+
logger.info(
|
|
375
|
+
f"[Abort] Agent for device {device_id} event loop terminated due to abort signal"
|
|
376
|
+
)
|
|
377
|
+
yield "event: aborted\n"
|
|
378
|
+
yield 'data: {"type": "aborted", "message": "Chat aborted by user"}\n\n'
|
|
379
|
+
|
|
380
|
+
# Update original agent state (thread-safe due to device lock)
|
|
381
|
+
original_agent._context = streaming_agent._context
|
|
382
|
+
original_agent._step_count = streaming_agent._step_count
|
|
383
|
+
|
|
384
|
+
original_agent.reset()
|
|
385
|
+
|
|
386
|
+
except Exception as e:
|
|
387
|
+
error_data = {
|
|
388
|
+
"type": "error",
|
|
389
|
+
"message": str(e),
|
|
159
390
|
}
|
|
160
|
-
|
|
161
|
-
yield "
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
197
|
-
|
|
198
|
-
return StreamingResponse(
|
|
199
|
-
event_generator(),
|
|
200
|
-
media_type="text/event-stream",
|
|
201
|
-
headers={
|
|
202
|
-
"Cache-Control": "no-cache",
|
|
203
|
-
"Connection": "keep-alive",
|
|
204
|
-
"X-Accel-Buffering": "no",
|
|
205
|
-
},
|
|
206
|
-
)
|
|
391
|
+
yield "event: error\n"
|
|
392
|
+
yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
|
|
393
|
+
finally:
|
|
394
|
+
# Clean up active chats mapping
|
|
395
|
+
with _active_chats_lock:
|
|
396
|
+
_active_chats.pop(device_id, None)
|
|
397
|
+
|
|
398
|
+
# Signal all threads to stop
|
|
399
|
+
stop_event.set()
|
|
400
|
+
|
|
401
|
+
alive_threads = [thread for thread in threads if thread.is_alive()]
|
|
402
|
+
if alive_threads:
|
|
403
|
+
# Release lock after background threads complete
|
|
404
|
+
cleanup_thread = threading.Thread(
|
|
405
|
+
target=_release_device_lock_when_done,
|
|
406
|
+
args=(device_id, alive_threads),
|
|
407
|
+
daemon=True,
|
|
408
|
+
)
|
|
409
|
+
cleanup_thread.start()
|
|
410
|
+
else:
|
|
411
|
+
# Release lock immediately if no threads are alive
|
|
412
|
+
manager.release_device(device_id)
|
|
413
|
+
|
|
414
|
+
return StreamingResponse(
|
|
415
|
+
event_generator(),
|
|
416
|
+
media_type="text/event-stream",
|
|
417
|
+
headers={
|
|
418
|
+
"Cache-Control": "no-cache",
|
|
419
|
+
"Connection": "keep-alive",
|
|
420
|
+
"X-Accel-Buffering": "no",
|
|
421
|
+
},
|
|
422
|
+
)
|
|
423
|
+
except Exception:
|
|
424
|
+
# Release lock if exception occurs before generator starts
|
|
425
|
+
manager.release_device(device_id)
|
|
426
|
+
raise
|
|
207
427
|
|
|
208
428
|
|
|
209
429
|
@router.get("/api/status", response_model=StatusResponse)
|
|
210
430
|
def get_status(device_id: str | None = None) -> StatusResponse:
|
|
211
431
|
"""获取 Agent 状态和版本信息(多设备支持)。"""
|
|
432
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
433
|
+
|
|
434
|
+
manager = PhoneAgentManager.get_instance()
|
|
435
|
+
|
|
212
436
|
if device_id is None:
|
|
213
437
|
return StatusResponse(
|
|
214
438
|
version=APP_VERSION,
|
|
215
|
-
initialized=len(
|
|
439
|
+
initialized=len(manager.list_agents()) > 0,
|
|
216
440
|
step_count=0,
|
|
217
441
|
)
|
|
218
442
|
|
|
219
|
-
if
|
|
443
|
+
if not manager.is_initialized(device_id):
|
|
220
444
|
return StatusResponse(
|
|
221
445
|
version=APP_VERSION,
|
|
222
446
|
initialized=False,
|
|
223
447
|
step_count=0,
|
|
224
448
|
)
|
|
225
449
|
|
|
226
|
-
agent =
|
|
450
|
+
agent = manager.get_agent(device_id)
|
|
227
451
|
return StatusResponse(
|
|
228
452
|
version=APP_VERSION,
|
|
229
453
|
initialized=True,
|
|
@@ -234,27 +458,38 @@ def get_status(device_id: str | None = None) -> StatusResponse:
|
|
|
234
458
|
@router.post("/api/reset")
|
|
235
459
|
def reset_agent(request: ResetRequest) -> dict:
|
|
236
460
|
"""重置 Agent 状态(多设备支持)。"""
|
|
461
|
+
from AutoGLM_GUI.exceptions import AgentNotInitializedError
|
|
462
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
463
|
+
|
|
237
464
|
device_id = request.device_id
|
|
465
|
+
manager = PhoneAgentManager.get_instance()
|
|
238
466
|
|
|
239
|
-
|
|
467
|
+
try:
|
|
468
|
+
manager.reset_agent(device_id)
|
|
469
|
+
return {
|
|
470
|
+
"success": True,
|
|
471
|
+
"device_id": device_id,
|
|
472
|
+
"message": f"Agent reset for device {device_id}",
|
|
473
|
+
}
|
|
474
|
+
except AgentNotInitializedError:
|
|
240
475
|
raise HTTPException(status_code=404, detail=f"Device {device_id} not found")
|
|
241
476
|
|
|
242
|
-
agent = agents[device_id]
|
|
243
|
-
agent.reset()
|
|
244
477
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
agent_config=agent_config,
|
|
250
|
-
takeover_callback=non_blocking_takeover,
|
|
251
|
-
)
|
|
478
|
+
@router.post("/api/chat/abort")
|
|
479
|
+
def abort_chat(request: AbortRequest) -> dict:
|
|
480
|
+
"""中断正在进行的对话流。"""
|
|
481
|
+
from AutoGLM_GUI.logger import logger
|
|
252
482
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
483
|
+
device_id = request.device_id
|
|
484
|
+
|
|
485
|
+
with _active_chats_lock:
|
|
486
|
+
if device_id in _active_chats:
|
|
487
|
+
logger.info(f"Aborting chat for device {device_id}")
|
|
488
|
+
_active_chats[device_id].set() # 设置中断标志
|
|
489
|
+
return {"success": True, "message": "Abort requested"}
|
|
490
|
+
else:
|
|
491
|
+
logger.warning(f"No active chat found for device {device_id}")
|
|
492
|
+
return {"success": False, "message": "No active chat found"}
|
|
258
493
|
|
|
259
494
|
|
|
260
495
|
@router.get("/api/config", response_model=ConfigResponse)
|