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.
Files changed (120) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -8
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. AutoGLM_GUI/actions/handler.py +196 -0
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. AutoGLM_GUI/adb/__init__.py +53 -0
  7. AutoGLM_GUI/adb/apps.py +227 -0
  8. AutoGLM_GUI/adb/connection.py +323 -0
  9. AutoGLM_GUI/adb/device.py +171 -0
  10. AutoGLM_GUI/adb/input.py +67 -0
  11. AutoGLM_GUI/adb/screenshot.py +11 -0
  12. AutoGLM_GUI/adb/timing.py +167 -0
  13. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  14. AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
  15. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  16. AutoGLM_GUI/adb_plus/serial.py +38 -20
  17. AutoGLM_GUI/adb_plus/touch.py +4 -9
  18. AutoGLM_GUI/agents/__init__.py +51 -0
  19. AutoGLM_GUI/agents/events.py +19 -0
  20. AutoGLM_GUI/agents/factory.py +153 -0
  21. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  22. AutoGLM_GUI/agents/glm/agent.py +292 -0
  23. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  24. AutoGLM_GUI/agents/glm/parser.py +110 -0
  25. AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
  26. AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
  27. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  28. AutoGLM_GUI/agents/mai/agent.py +405 -0
  29. AutoGLM_GUI/agents/mai/parser.py +254 -0
  30. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  31. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  32. AutoGLM_GUI/agents/protocols.py +27 -0
  33. AutoGLM_GUI/agents/stream_runner.py +188 -0
  34. AutoGLM_GUI/api/__init__.py +71 -11
  35. AutoGLM_GUI/api/agents.py +190 -229
  36. AutoGLM_GUI/api/control.py +9 -6
  37. AutoGLM_GUI/api/devices.py +112 -28
  38. AutoGLM_GUI/api/health.py +13 -0
  39. AutoGLM_GUI/api/history.py +78 -0
  40. AutoGLM_GUI/api/layered_agent.py +306 -181
  41. AutoGLM_GUI/api/mcp.py +11 -10
  42. AutoGLM_GUI/api/media.py +64 -1
  43. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  44. AutoGLM_GUI/api/version.py +23 -10
  45. AutoGLM_GUI/api/workflows.py +2 -1
  46. AutoGLM_GUI/config.py +72 -14
  47. AutoGLM_GUI/config_manager.py +98 -27
  48. AutoGLM_GUI/device_adapter.py +263 -0
  49. AutoGLM_GUI/device_manager.py +248 -29
  50. AutoGLM_GUI/device_protocol.py +266 -0
  51. AutoGLM_GUI/devices/__init__.py +49 -0
  52. AutoGLM_GUI/devices/adb_device.py +200 -0
  53. AutoGLM_GUI/devices/mock_device.py +185 -0
  54. AutoGLM_GUI/devices/remote_device.py +177 -0
  55. AutoGLM_GUI/exceptions.py +3 -3
  56. AutoGLM_GUI/history_manager.py +164 -0
  57. AutoGLM_GUI/i18n.py +81 -0
  58. AutoGLM_GUI/metrics.py +13 -20
  59. AutoGLM_GUI/model/__init__.py +5 -0
  60. AutoGLM_GUI/model/message_builder.py +69 -0
  61. AutoGLM_GUI/model/types.py +24 -0
  62. AutoGLM_GUI/models/__init__.py +10 -0
  63. AutoGLM_GUI/models/history.py +96 -0
  64. AutoGLM_GUI/models/scheduled_task.py +71 -0
  65. AutoGLM_GUI/parsers/__init__.py +22 -0
  66. AutoGLM_GUI/parsers/base.py +50 -0
  67. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  68. AutoGLM_GUI/phone_agent_manager.py +118 -367
  69. AutoGLM_GUI/platform_utils.py +31 -2
  70. AutoGLM_GUI/prompt_config.py +15 -0
  71. AutoGLM_GUI/prompts/__init__.py +32 -0
  72. AutoGLM_GUI/scheduler_manager.py +304 -0
  73. AutoGLM_GUI/schemas.py +272 -63
  74. AutoGLM_GUI/scrcpy_stream.py +159 -37
  75. AutoGLM_GUI/server.py +3 -1
  76. AutoGLM_GUI/socketio_server.py +114 -29
  77. AutoGLM_GUI/state.py +10 -30
  78. AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-BQm96DAl.js} +1 -1
  79. AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
  80. AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
  81. AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
  82. AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
  83. AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
  84. AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
  85. AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-CmZSnDqc.js} +1 -1
  86. AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
  87. AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
  88. AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
  89. AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
  90. AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
  91. AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
  92. AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
  93. AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
  94. AutoGLM_GUI/static/index.html +2 -2
  95. AutoGLM_GUI/types.py +142 -0
  96. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
  97. autoglm_gui-1.5.0.dist-info/RECORD +157 -0
  98. mai_agent/base.py +137 -0
  99. mai_agent/mai_grounding_agent.py +263 -0
  100. mai_agent/mai_naivigation_agent.py +526 -0
  101. mai_agent/prompt.py +148 -0
  102. mai_agent/unified_memory.py +67 -0
  103. mai_agent/utils.py +73 -0
  104. AutoGLM_GUI/api/dual_model.py +0 -311
  105. AutoGLM_GUI/dual_model/__init__.py +0 -53
  106. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  107. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  108. AutoGLM_GUI/dual_model/protocols.py +0 -354
  109. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  110. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  111. AutoGLM_GUI/phone_agent_patches.py +0 -146
  112. AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
  113. AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
  114. AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
  115. AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
  116. AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
  117. autoglm_gui-1.4.0.dist-info/RECORD +0 -100
  118. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
  119. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
  120. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,103 @@
1
+ """MAI Agent 系统提示模板
2
+
3
+ 基于 mai_agent/prompt.py 迁移,针对中文环境和国内应用优化。
4
+ """
5
+
6
+ MAI_MOBILE_SYSTEM_PROMPT = """你是一个 GUI 自动化助手。你会收到一个任务和历史操作记录(包含多张截图),你需要分析当前屏幕状态,执行下一步操作来完成任务。
7
+
8
+ ## 输出格式
9
+ 每次操作必须包含两部分:
10
+ 1. **思考过程**:在 <thinking></thinking> 标签中详细说明你的分析和决策
11
+ 2. **动作指令**:在 <tool_call></tool_call> 标签中返回 JSON 格式的函数调用
12
+
13
+ 示例:
14
+ ```
15
+ <thinking>
16
+ 当前屏幕显示美团首页。我需要点击顶部搜索框输入"霸王茶姬"。搜索框位于屏幕上方中央,坐标大约在 [500, 100]。
17
+ 下一步操作:点击搜索框。
18
+ </thinking>
19
+ <tool_call>
20
+ {"name": "mobile_use", "arguments": {"action": "click", "coordinate": [500, 100]}}
21
+ </tool_call>
22
+ ```
23
+
24
+ ## 动作空间(严格遵守)
25
+
26
+ ### 基础操作
27
+ - **点击**:`{"action": "click", "coordinate": [x, y]}`
28
+ 用于点击按钮、链接、输入框等可点击元素
29
+
30
+ - **长按**:`{"action": "long_press", "coordinate": [x, y]}`
31
+ 用于触发长按菜单或特殊功能
32
+
33
+ - **输入文本**:`{"action": "type", "text": "要输入的文字"}`
34
+ 必须先点击输入框聚焦,再使用此动作输入文本
35
+ 注意:文本中的特殊字符需要转义(\\'、\\"、\\n)
36
+
37
+ ### 滑动操作
38
+ - **滑动**:`{"action": "swipe", "direction": "up|down|left|right", "coordinate": [x, y]}`
39
+ direction 可选值:up(向上滑)、down(向下滑)、left(向左滑)、right(向右滑)
40
+ coordinate 可选:指定滑动起点坐标(用于滑动特定 UI 元素)
41
+
42
+ - **拖动**:`{"action": "drag", "start_coordinate": [x1, y1], "end_coordinate": [x2, y2]}`
43
+ 用于拖拽元素到新位置
44
+
45
+ ### 系统操作
46
+ - **打开应用**:`{"action": "open", "text": "应用名称"}`
47
+ 推荐优先使用此方式打开应用(比手动点击更快)
48
+
49
+ - **系统按键**:`{"action": "system_button", "button": "back|home|menu|enter"}`
50
+ 可选值:back(返回)、home(主页)、menu(菜单)、enter(确认)
51
+
52
+ ### 任务控制
53
+ - **等待**:`{"action": "wait"}`
54
+ 用于等待页面加载或动画完成(建议谨慎使用,大多数情况不需要)
55
+
56
+ - **结束任务**:`{"action": "terminate", "status": "success|fail"}`
57
+ 任务完成或失败时必须调用此动作
58
+
59
+ - **回答问题**:`{"action": "answer", "text": "答案内容"}`
60
+ 当用户要求你查找信息或回答问题时使用
61
+
62
+ ## 坐标系统
63
+ - **范围**:x 和 y 都在 [0, 999] 之间
64
+ - **原点**:(0, 0) 是屏幕左上角
65
+ - **边界**:(999, 999) 是屏幕右下角
66
+ - **精度**:坐标是归一化的,会自动映射到实际屏幕分辨率
67
+
68
+ ## 操作指南
69
+
70
+ ### 思考过程建议
71
+ 在 <thinking> 部分应包含:
72
+ 1. **观察**:当前屏幕显示的内容和状态
73
+ 2. **分析**:识别目标元素的位置和特征
74
+ 3. **决策**:选择最合适的操作和参数
75
+ 4. **总结**:用一句话明确说明下一步要做什么
76
+
77
+ ### 常见应用操作技巧
78
+ **国内常用应用**:
79
+ - 外卖应用(美团、饿了么):优先使用顶部搜索框查找商家
80
+ - 打车应用(滴滴、高德):注意起点/终点输入框的位置区分
81
+ - 电商应用(淘宝、京东):搜索框通常在顶部,商品列表需要向下滑动浏览
82
+ - 社交应用(微信、QQ):注意顶部/底部导航栏的切换
83
+
84
+ **通用技巧**:
85
+ - 如果页面内容未完全显示,使用 swipe 滚动查看
86
+ - 输入文本前必须先 click 输入框获得焦点
87
+ - 遇到加载动画可以 wait 一次,但不要连续 wait
88
+ - 无法找到目标元素时,尝试返回上一级(system_button back)重新导航
89
+
90
+ ### 常见错误避免
91
+ - ❌ 不要在未点击输入框的情况下直接 type
92
+ - ❌ 不要使用超出 [0, 999] 范围的坐标
93
+ - ❌ 不要遗漏 <thinking> 或 <tool_call> 标签
94
+ - ❌ 不要在 JSON 中使用注释或多余的字段
95
+ - ❌ 不要连续执行多个相同的无效操作
96
+
97
+ ## 注意事项
98
+ - 必须严格遵循动作空间,所有动作参数必须符合上述格式
99
+ - 坐标必须是整数,范围在 [0, 999]
100
+ - 文本输入中的引号、换行等特殊字符必须转义
101
+ - 每次只返回一个动作,不要尝试批量操作
102
+ - 仔细观察截图中的 UI 元素位置,准确估算坐标
103
+ """.strip()
@@ -0,0 +1,91 @@
1
+ """轨迹记忆数据结构 - MAI Agent 内部实现
2
+
3
+ 本模块定义了 MAI Agent 的轨迹记忆系统,用于存储和管理 Agent 执行过程中的历史信息。
4
+
5
+ 设计说明:
6
+ - 从 mai_agent/unified_memory.py 迁移而来
7
+ - 适配 Python 3.10+ 类型注解
8
+ - 与 AutoGLM_GUI 架构集成
9
+ """
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+ from PIL import Image
15
+
16
+
17
+ @dataclass
18
+ class TrajStep:
19
+ """轨迹中的单个步骤
20
+
21
+ 记录 Agent 在某一步的完整状态,包括观察、思考、动作和结果。
22
+
23
+ Attributes:
24
+ screenshot: 当前步骤的截图 (PIL Image 格式)
25
+ accessibility_tree: 可访问性树数据(可选,用于辅助 UI 理解)
26
+ prediction: 模型的原始响应文本(包含 <thinking> 和 <tool_call>)
27
+ action: 解析后的动作字典(如 {"action": "click", "coordinate": [0.5, 0.8]})
28
+ conclusion: 本步骤的结论或总结
29
+ thought: 模型的思考过程(从 <thinking> 标签中提取)
30
+ step_index: 步骤索引(从 0 开始)
31
+ agent_type: 生成此步骤的 Agent 类型(如 "InternalMAIAgent")
32
+ model_name: 使用的模型名称(如 "qwen2-vl-7b")
33
+ screenshot_bytes: 截图的字节数据(可选,用于序列化)
34
+ structured_action: 结构化的动作数据(可选,包含额外元数据)
35
+ """
36
+
37
+ screenshot: Image.Image
38
+ accessibility_tree: dict[str, Any] | None
39
+ prediction: str
40
+ action: dict[str, Any]
41
+ conclusion: str
42
+ thought: str
43
+ step_index: int
44
+ agent_type: str
45
+ model_name: str
46
+ screenshot_bytes: bytes | None = None
47
+ structured_action: dict[str, Any] | None = None
48
+
49
+
50
+ @dataclass
51
+ class TrajMemory:
52
+ """完整任务的轨迹记忆容器
53
+
54
+ 存储一个完整任务的所有步骤,提供历史查询和状态管理功能。
55
+
56
+ Attributes:
57
+ task_goal: 任务目标描述(用户的原始指令)
58
+ task_id: 任务唯一标识符
59
+ steps: 步骤列表(按执行顺序)
60
+ """
61
+
62
+ task_goal: str
63
+ task_id: str
64
+ steps: list[TrajStep] = field(default_factory=list)
65
+
66
+ def add_step(self, step: TrajStep) -> None:
67
+ self.steps.append(step)
68
+
69
+ def get_history_images(self, n: int = -1) -> list[bytes]:
70
+ images = [step.screenshot_bytes for step in self.steps if step.screenshot_bytes]
71
+ if n > 0:
72
+ return images[-n:]
73
+ return images
74
+
75
+ def get_history_thoughts(self, n: int = -1) -> list[str]:
76
+ thoughts = [step.thought for step in self.steps if step.thought]
77
+ if n > 0:
78
+ return thoughts[-n:]
79
+ return thoughts
80
+
81
+ def get_history_actions(self, n: int = -1) -> list[dict[str, Any]]:
82
+ actions = [step.action for step in self.steps]
83
+ if n > 0:
84
+ return actions[-n:]
85
+ return actions
86
+
87
+ def clear(self) -> None:
88
+ self.steps.clear()
89
+
90
+ def __len__(self) -> int:
91
+ return len(self.steps)
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Protocol
4
+
5
+ from AutoGLM_GUI.config import AgentConfig, ModelConfig, StepResult
6
+
7
+
8
+ class BaseAgent(Protocol):
9
+ model_config: ModelConfig
10
+ agent_config: AgentConfig
11
+
12
+ def run(self, task: str) -> str: ...
13
+
14
+ def step(self, task: str | None = None) -> StepResult: ...
15
+
16
+ def reset(self) -> None: ...
17
+
18
+ def abort(self) -> None: ...
19
+
20
+ @property
21
+ def step_count(self) -> int: ...
22
+
23
+ @property
24
+ def context(self) -> list[dict[str, Any]]: ...
25
+
26
+ @property
27
+ def is_running(self) -> bool: ...
@@ -0,0 +1,188 @@
1
+ import queue
2
+ import threading
3
+ import typing
4
+ from contextlib import contextmanager
5
+ from typing import Any, Callable, Iterator, Optional
6
+
7
+ from AutoGLM_GUI.agents.events import AgentEvent, AgentEventType
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from AutoGLM_GUI.agents.protocols import BaseAgent
11
+
12
+
13
+ class AgentStepStreamer:
14
+ """
15
+ 流式 Agent 执行器(抽取可复用逻辑).
16
+
17
+ 职责:
18
+ - 管理事件队列
19
+ - 协调 worker 线程
20
+ - 转换 StepResult 为事件
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ agent: "BaseAgent",
26
+ task: str,
27
+ ) -> None:
28
+ self._agent = agent
29
+ self._task = task
30
+ self._event_queue: queue.Queue[Optional[tuple[str, dict[str, Any]]]] = (
31
+ queue.Queue(maxsize=100)
32
+ )
33
+ self._stop_event = threading.Event()
34
+ self._worker_thread: Optional[threading.Thread] = None
35
+
36
+ def __iter__(self) -> Iterator[AgentEvent]:
37
+ """返回迭代器."""
38
+ return self # type: ignore
39
+
40
+ def __next__(self) -> AgentEvent:
41
+ """从队列获取下一个事件."""
42
+ try:
43
+ if self._worker_thread is None:
44
+ self._start_worker()
45
+
46
+ item = self._event_queue.get(timeout=0.1)
47
+
48
+ if item is None:
49
+ raise StopIteration
50
+
51
+ event_type, event_data = item
52
+ return AgentEvent(type=event_type, data=event_data)
53
+
54
+ except queue.Empty:
55
+ if self._worker_thread and self._worker_thread.is_alive():
56
+ return AgentEvent(
57
+ type=AgentEventType.STEP.value,
58
+ data={
59
+ "step": -1,
60
+ "thinking": "",
61
+ "action": None,
62
+ "success": True,
63
+ "finished": False,
64
+ },
65
+ )
66
+ else:
67
+ raise StopIteration
68
+
69
+ except StopIteration:
70
+ raise
71
+
72
+ except Exception as e:
73
+ self._stop_event.set()
74
+ return AgentEvent(type=AgentEventType.ERROR.value, data={"message": str(e)})
75
+
76
+ def _start_worker(self) -> None:
77
+ """启动 worker 线程."""
78
+
79
+ def worker():
80
+ try:
81
+ # 检查停止事件
82
+ if self._stop_event.is_set():
83
+ return
84
+
85
+ # 注入 thinking 回调
86
+ # 这是一个 hack,但为了实现 "Zero Agent Change" 目标
87
+ # 假设 agent 有 _thinking_callback 属性
88
+ original_callback = getattr(self._agent, "_thinking_callback", None)
89
+
90
+ def on_thinking(chunk: str):
91
+ self._event_queue.put(
92
+ (AgentEventType.THINKING.value, {"chunk": chunk})
93
+ )
94
+ if original_callback:
95
+ original_callback(chunk)
96
+
97
+ # Monkey-patch thinking callback
98
+ setattr(self._agent, "_thinking_callback", on_thinking)
99
+
100
+ try:
101
+ # 执行 step 循环
102
+ while not self._stop_event.is_set():
103
+ is_first = self._agent.step_count == 0
104
+ result = self._agent.step(self._task if is_first else None)
105
+
106
+ # 发射 step 事件
107
+ self._event_queue.put(
108
+ (
109
+ AgentEventType.STEP.value,
110
+ {
111
+ "step": self._agent.step_count,
112
+ "thinking": result.thinking,
113
+ "action": result.action,
114
+ "success": result.success,
115
+ "finished": result.finished,
116
+ },
117
+ )
118
+ )
119
+
120
+ # 检查是否完成
121
+ if result.finished:
122
+ # 发射 done 事件
123
+ self._event_queue.put(
124
+ (
125
+ AgentEventType.DONE.value,
126
+ {
127
+ "message": result.message,
128
+ "steps": self._agent.step_count,
129
+ "success": result.success,
130
+ },
131
+ )
132
+ )
133
+ break
134
+
135
+ # 检查步数限制
136
+ if self._agent.step_count >= self._agent.agent_config.max_steps:
137
+ self._event_queue.put(
138
+ (
139
+ AgentEventType.DONE.value,
140
+ {
141
+ "message": "Max steps reached",
142
+ "steps": self._agent.step_count,
143
+ "success": result.success,
144
+ },
145
+ )
146
+ )
147
+ break
148
+ finally:
149
+ # 恢复原始回调
150
+ setattr(self._agent, "_thinking_callback", original_callback)
151
+
152
+ except Exception as e:
153
+ # 发射 error 事件
154
+ self._event_queue.put((AgentEventType.ERROR.value, {"message": str(e)}))
155
+
156
+ finally:
157
+ # 标记完成
158
+ self._event_queue.put(None)
159
+
160
+ self._worker_thread = threading.Thread(target=worker, daemon=True)
161
+ self._worker_thread.start()
162
+
163
+ @contextmanager
164
+ def stream_context(self) -> Iterator[Callable[[], None]]:
165
+ """
166
+ Context manager,自动管理清理.
167
+ """
168
+ self._stop_event.clear()
169
+ try:
170
+ yield self.abort
171
+ finally:
172
+ self._stop_event.set()
173
+ # 等待 worker 完成
174
+ if self._worker_thread and self._worker_thread.is_alive():
175
+ self._worker_thread.join(timeout=5.0)
176
+
177
+ # 清空队列
178
+ while not self._event_queue.empty():
179
+ try:
180
+ self._event_queue.get_nowait()
181
+ except queue.Empty:
182
+ break
183
+
184
+ def abort(self) -> None:
185
+ """中止流式执行."""
186
+ self._stop_event.set()
187
+ if hasattr(self._agent, "abort"):
188
+ self._agent.abort()
@@ -1,6 +1,7 @@
1
1
  """FastAPI application factory and route registration."""
2
2
 
3
3
  import asyncio
4
+ import os
4
5
  import sys
5
6
  from contextlib import asynccontextmanager
6
7
  from importlib.resources import files
@@ -18,25 +19,46 @@ from . import (
18
19
  agents,
19
20
  control,
20
21
  devices,
21
- dual_model,
22
+ health,
23
+ history,
22
24
  layered_agent,
23
25
  mcp,
24
26
  media,
25
27
  metrics,
28
+ scheduled_tasks,
26
29
  version,
27
30
  workflows,
28
31
  )
29
32
 
30
33
 
34
+ def _get_cors_origins() -> list[str]:
35
+ cors_origins_str = os.getenv("AUTOGLM_CORS_ORIGINS", "http://localhost:3000")
36
+ if cors_origins_str == "*":
37
+ return ["*"]
38
+ return [origin.strip() for origin in cors_origins_str.split(",") if origin.strip()]
39
+
40
+
31
41
  def _get_static_dir() -> Path | None:
32
- """Locate packaged static assets."""
33
- # Priority 1: PyInstaller bundled path (for packaged executable)
34
- if getattr(sys, "_MEIPASS", None):
35
- bundled_static = Path(sys._MEIPASS) / "AutoGLM_GUI" / "static"
42
+ meipass = getattr(sys, "_MEIPASS", None)
43
+ if meipass:
44
+ bundled_static = Path(meipass) / "AutoGLM_GUI" / "static"
36
45
  if bundled_static.exists():
37
46
  return bundled_static
38
47
 
39
- # Priority 2: importlib.resources (for installed package)
48
+ # Priority 2: Check filesystem directly (for Docker deployments)
49
+ # This handles the case where static files are copied to the package directory
50
+ # but not included in the Python package itself (e.g., Docker builds)
51
+ try:
52
+ from AutoGLM_GUI import __file__ as package_file
53
+
54
+ package_dir = Path(package_file).parent
55
+ filesystem_static = package_dir / "static"
56
+ if filesystem_static.exists() and filesystem_static.is_dir():
57
+ return filesystem_static
58
+ except (ImportError, AttributeError):
59
+ pass
60
+
61
+ # Priority 3: importlib.resources (for installed package)
40
62
  try:
41
63
  static_dir = files("AutoGLM_GUI").joinpath("static")
42
64
  if hasattr(static_dir, "_path"):
@@ -66,15 +88,20 @@ def create_app() -> FastAPI:
66
88
  asyncio.create_task(qr_pairing_manager.cleanup_expired_sessions())
67
89
 
68
90
  from AutoGLM_GUI.device_manager import DeviceManager
91
+ from AutoGLM_GUI.scheduler_manager import scheduler_manager
69
92
 
70
93
  device_manager = DeviceManager.get_instance()
71
94
  device_manager.start_polling()
72
95
 
96
+ # Start scheduled task scheduler
97
+ scheduler_manager.start()
98
+
73
99
  # Run MCP lifespan
74
100
  async with mcp_app.lifespan(app):
75
101
  yield
76
102
 
77
- # App shutdown (if needed in the future)
103
+ # App shutdown
104
+ scheduler_manager.shutdown()
78
105
 
79
106
  # Create FastAPI app with combined lifespan
80
107
  app = FastAPI(
@@ -83,21 +110,23 @@ def create_app() -> FastAPI:
83
110
 
84
111
  app.add_middleware(
85
112
  CORSMiddleware,
86
- allow_origins=["http://localhost:3000"],
113
+ allow_origins=_get_cors_origins(),
87
114
  allow_credentials=True,
88
115
  allow_methods=["*"],
89
116
  allow_headers=["*"],
90
117
  )
91
118
 
92
119
  app.include_router(agents.router)
120
+ app.include_router(health.router)
121
+ app.include_router(history.router)
93
122
  app.include_router(layered_agent.router)
94
123
  app.include_router(devices.router)
95
124
  app.include_router(control.router)
96
125
  app.include_router(media.router)
97
126
  app.include_router(metrics.router)
127
+ app.include_router(scheduled_tasks.router)
98
128
  app.include_router(version.router)
99
129
  app.include_router(workflows.router)
100
- app.include_router(dual_model.router)
101
130
 
102
131
  # Mount static files BEFORE MCP to ensure they have priority
103
132
  # This is critical: FastAPI processes mounts in order, so static files
@@ -106,14 +135,45 @@ def create_app() -> FastAPI:
106
135
  if static_dir is not None and static_dir.exists():
107
136
  assets_dir = static_dir / "assets"
108
137
  if assets_dir.exists():
138
+ # Vite builds assets with content hashes, so we can cache them long-term
109
139
  app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
110
140
 
111
141
  # Define SPA serving function
112
142
  async def serve_spa(full_path: str) -> FileResponse:
113
143
  file_path = static_dir / full_path
114
144
  if file_path.is_file():
115
- return FileResponse(file_path)
116
- return FileResponse(static_dir / "index.html")
145
+ # Explicitly set media_type for common file types to avoid MIME detection issues
146
+ # This is critical for PyInstaller environments where mimetypes module may fail
147
+ media_type = None
148
+ suffix = file_path.suffix.lower()
149
+ if suffix == ".js":
150
+ media_type = "application/javascript"
151
+ elif suffix == ".css":
152
+ media_type = "text/css"
153
+ elif suffix == ".json":
154
+ media_type = "application/json"
155
+ elif suffix in (".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico"):
156
+ # Let FileResponse auto-detect image types (usually works)
157
+ media_type = None
158
+
159
+ return FileResponse(
160
+ file_path,
161
+ media_type=media_type,
162
+ headers={
163
+ "Cache-Control": "no-cache, no-store, must-revalidate",
164
+ "Pragma": "no-cache",
165
+ "Expires": "0",
166
+ },
167
+ )
168
+ return FileResponse(
169
+ static_dir / "index.html",
170
+ media_type="text/html",
171
+ headers={
172
+ "Cache-Control": "no-cache, no-store, must-revalidate",
173
+ "Pragma": "no-cache",
174
+ "Expires": "0",
175
+ },
176
+ )
117
177
 
118
178
  # Add catch-all route for SPA (handles all non-API routes)
119
179
  app.add_api_route(