autoglm-gui 0.4.9__py3-none-any.whl → 0.4.11__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/__main__.py +63 -15
- AutoGLM_GUI/adb_plus/__init__.py +2 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +380 -0
- AutoGLM_GUI/api/agents.py +123 -1
- AutoGLM_GUI/api/media.py +36 -43
- AutoGLM_GUI/config_manager.py +124 -0
- AutoGLM_GUI/logger.py +85 -0
- AutoGLM_GUI/platform_utils.py +11 -5
- AutoGLM_GUI/resources/apks/ADBKeyBoard.LICENSE.txt +339 -0
- AutoGLM_GUI/resources/apks/ADBKeyBoard.README.txt +1 -0
- AutoGLM_GUI/resources/apks/ADBKeyboard.apk +0 -0
- AutoGLM_GUI/schemas.py +17 -0
- AutoGLM_GUI/scrcpy_stream.py +37 -41
- AutoGLM_GUI/state.py +2 -1
- AutoGLM_GUI/static/assets/{about-BI6OV6gm.js → about-wSo3UgQ-.js} +1 -1
- AutoGLM_GUI/static/assets/chat-BcY2K0yj.js +25 -0
- AutoGLM_GUI/static/assets/{index-Do7ha9Kf.js → index-B5u1xtK1.js} +1 -1
- AutoGLM_GUI/static/assets/index-CHrYo3Qj.css +1 -0
- AutoGLM_GUI/static/assets/{index-Dn3vR6uV.js → index-D5BALRbT.js} +5 -5
- AutoGLM_GUI/static/index.html +2 -2
- {autoglm_gui-0.4.9.dist-info → autoglm_gui-0.4.11.dist-info}/METADATA +14 -2
- {autoglm_gui-0.4.9.dist-info → autoglm_gui-0.4.11.dist-info}/RECORD +25 -19
- AutoGLM_GUI/static/assets/chat-C_2Cot0q.js +0 -25
- AutoGLM_GUI/static/assets/index-DCrxTz-A.css +0 -1
- {autoglm_gui-0.4.9.dist-info → autoglm_gui-0.4.11.dist-info}/WHEEL +0 -0
- {autoglm_gui-0.4.9.dist-info → autoglm_gui-0.4.11.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-0.4.9.dist-info → autoglm_gui-0.4.11.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/__main__.py
CHANGED
|
@@ -76,8 +76,8 @@ def main() -> None:
|
|
|
76
76
|
)
|
|
77
77
|
parser.add_argument(
|
|
78
78
|
"--base-url",
|
|
79
|
-
required=
|
|
80
|
-
help="Base URL of the model API (
|
|
79
|
+
required=False,
|
|
80
|
+
help="Base URL of the model API (e.g., http://localhost:8080/v1)",
|
|
81
81
|
)
|
|
82
82
|
parser.add_argument(
|
|
83
83
|
"--model",
|
|
@@ -110,11 +110,22 @@ def main() -> None:
|
|
|
110
110
|
action="store_true",
|
|
111
111
|
help="Do not open browser automatically",
|
|
112
112
|
)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"--log-level",
|
|
115
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
116
|
+
default="INFO",
|
|
117
|
+
help="Console log level (default: INFO)",
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--log-file",
|
|
121
|
+
default="logs/autoglm_{time:YYYY-MM-DD}.log",
|
|
122
|
+
help="Log file path (default: logs/autoglm_{time:YYYY-MM-DD}.log)",
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--no-log-file",
|
|
126
|
+
action="store_true",
|
|
127
|
+
help="Disable file logging",
|
|
128
|
+
)
|
|
118
129
|
|
|
119
130
|
args = parser.parse_args()
|
|
120
131
|
|
|
@@ -131,16 +142,45 @@ def main() -> None:
|
|
|
131
142
|
|
|
132
143
|
from AutoGLM_GUI import server
|
|
133
144
|
from AutoGLM_GUI.config import config
|
|
145
|
+
from AutoGLM_GUI.config_manager import load_config_file, merge_configs
|
|
146
|
+
from AutoGLM_GUI.logger import configure_logger
|
|
147
|
+
|
|
148
|
+
# Configure logging system
|
|
149
|
+
configure_logger(
|
|
150
|
+
console_level=args.log_level,
|
|
151
|
+
log_file=None if args.no_log_file else args.log_file,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Load configuration from file
|
|
155
|
+
file_config = load_config_file()
|
|
156
|
+
|
|
157
|
+
# Build CLI config dictionary (only include provided arguments)
|
|
158
|
+
cli_config = {}
|
|
159
|
+
if args.base_url:
|
|
160
|
+
cli_config["base_url"] = args.base_url
|
|
161
|
+
if args.model:
|
|
162
|
+
cli_config["model_name"] = args.model
|
|
163
|
+
if args.apikey:
|
|
164
|
+
cli_config["api_key"] = args.apikey
|
|
165
|
+
|
|
166
|
+
# Merge configurations (CLI > file > defaults)
|
|
167
|
+
merged_config = merge_configs(file_config, cli_config)
|
|
134
168
|
|
|
135
169
|
# Set model configuration via environment variables (survives reload)
|
|
136
|
-
os.environ["AUTOGLM_BASE_URL"] =
|
|
137
|
-
os.environ["AUTOGLM_MODEL_NAME"] =
|
|
138
|
-
|
|
139
|
-
os.environ["AUTOGLM_API_KEY"] = args.apikey
|
|
170
|
+
os.environ["AUTOGLM_BASE_URL"] = merged_config["base_url"]
|
|
171
|
+
os.environ["AUTOGLM_MODEL_NAME"] = merged_config["model_name"]
|
|
172
|
+
os.environ["AUTOGLM_API_KEY"] = merged_config["api_key"]
|
|
140
173
|
|
|
141
174
|
# Refresh config from environment variables
|
|
142
175
|
config.refresh_from_env()
|
|
143
176
|
|
|
177
|
+
# Determine configuration source
|
|
178
|
+
config_source = "default"
|
|
179
|
+
if cli_config:
|
|
180
|
+
config_source = "CLI arguments"
|
|
181
|
+
elif file_config:
|
|
182
|
+
config_source = "config file (~/.config/autoglm/config.json)"
|
|
183
|
+
|
|
144
184
|
# Display startup banner
|
|
145
185
|
print()
|
|
146
186
|
print("=" * 50)
|
|
@@ -151,11 +191,19 @@ def main() -> None:
|
|
|
151
191
|
print(f" Server: http://{args.host}:{args.port}")
|
|
152
192
|
print()
|
|
153
193
|
print(" Model Configuration:")
|
|
154
|
-
print(f"
|
|
155
|
-
print(f"
|
|
156
|
-
|
|
157
|
-
|
|
194
|
+
print(f" Source: {config_source}")
|
|
195
|
+
print(f" Base URL: {merged_config['base_url'] or '(not set)'}")
|
|
196
|
+
print(f" Model: {merged_config['model_name']}")
|
|
197
|
+
if merged_config["api_key"] != "EMPTY":
|
|
198
|
+
print(" API Key: (configured)")
|
|
158
199
|
print()
|
|
200
|
+
|
|
201
|
+
# Warning if base_url is not configured
|
|
202
|
+
if not merged_config["base_url"]:
|
|
203
|
+
print(" ⚠️ WARNING: base_url is not configured!")
|
|
204
|
+
print(" Please configure via frontend or use --base-url")
|
|
205
|
+
print()
|
|
206
|
+
|
|
159
207
|
print("=" * 50)
|
|
160
208
|
print(" Press Ctrl+C to stop")
|
|
161
209
|
print("=" * 50)
|
AutoGLM_GUI/adb_plus/__init__.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""Lightweight ADB helpers with a more robust screenshot implementation."""
|
|
2
2
|
|
|
3
|
+
from .keyboard_installer import ADBKeyboardInstaller
|
|
3
4
|
from .screenshot import Screenshot, capture_screenshot
|
|
4
5
|
from .touch import touch_down, touch_move, touch_up
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
8
|
+
"ADBKeyboardInstaller",
|
|
7
9
|
"Screenshot",
|
|
8
10
|
"capture_screenshot",
|
|
9
11
|
"touch_down",
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""ADB Keyboard Auto-Installation and Enablement Tool.
|
|
2
|
+
|
|
3
|
+
This module provides automatic installation, configuration, and enabling of ADB Keyboard,
|
|
4
|
+
without requiring users to manually download and install APK.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import urllib.request
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Tuple
|
|
11
|
+
|
|
12
|
+
from AutoGLM_GUI.logger import logger
|
|
13
|
+
from AutoGLM_GUI.platform_utils import run_cmd_silently
|
|
14
|
+
|
|
15
|
+
ADB_KEYBOARD_PACKAGE = "com.android.adbkeyboard"
|
|
16
|
+
ADB_KEYBOARD_IME = "com.android.adbkeyboard/.AdbIME"
|
|
17
|
+
ADB_KEYBOARD_APK_URL = (
|
|
18
|
+
"https://github.com/senzhk/ADBKeyBoard/raw/master/ADBKeyboard.apk"
|
|
19
|
+
)
|
|
20
|
+
ADB_KEYBOARD_APK_FILENAME = "ADBKeyboard.apk"
|
|
21
|
+
|
|
22
|
+
# APK file in user cache directory (fallback)
|
|
23
|
+
USER_CACHE_APK_PATH = Path.home() / ".cache" / "autoglm" / ADB_KEYBOARD_APK_FILENAME
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ADBKeyboardInstaller:
|
|
27
|
+
"""ADB Keyboard Auto-Installer."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, device_id: Optional[str] = None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the installer.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
device_id: Optional ADB device ID for multi-device scenarios.
|
|
35
|
+
"""
|
|
36
|
+
self.device_id = device_id
|
|
37
|
+
self.adb_prefix = ["adb"]
|
|
38
|
+
if device_id:
|
|
39
|
+
self.adb_prefix.extend(["-s", device_id])
|
|
40
|
+
|
|
41
|
+
logger.debug(
|
|
42
|
+
f"Initialized ADBKeyboardInstaller for device: {device_id or 'default'}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def is_installed(self) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Check if ADB Keyboard is installed (via package name).
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
bool: True if installed, False otherwise.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
logger.debug(
|
|
54
|
+
f"Checking if ADB Keyboard is installed on device {self.device_id or 'default'}"
|
|
55
|
+
)
|
|
56
|
+
result = asyncio.run(
|
|
57
|
+
run_cmd_silently(self.adb_prefix + ["shell", "pm", "list", "packages"])
|
|
58
|
+
)
|
|
59
|
+
package_list = result.stdout.strip()
|
|
60
|
+
installed = ADB_KEYBOARD_PACKAGE in package_list
|
|
61
|
+
logger.debug(f"ADB Keyboard installed: {installed}")
|
|
62
|
+
return installed
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error(f"Error checking keyboard installation status: {e}")
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def is_enabled(self) -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Check if ADB Keyboard is enabled (usable).
|
|
70
|
+
|
|
71
|
+
Determined by checking the list of enabled input methods.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
bool: True if enabled, False otherwise.
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
logger.debug(
|
|
78
|
+
f"Checking if ADB Keyboard is enabled on device {self.device_id or 'default'}"
|
|
79
|
+
)
|
|
80
|
+
# Use ime list -s to check only enabled input methods
|
|
81
|
+
result = asyncio.run(
|
|
82
|
+
run_cmd_silently(self.adb_prefix + ["shell", "ime", "list", "-s"])
|
|
83
|
+
)
|
|
84
|
+
ime_list_enabled = result.stdout.strip()
|
|
85
|
+
enabled = ADB_KEYBOARD_IME in ime_list_enabled
|
|
86
|
+
logger.debug(f"ADB Keyboard enabled: {enabled}")
|
|
87
|
+
return enabled
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.error(f"Error checking keyboard enable status: {e}")
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
def get_apk_path(self) -> Optional[Path]:
|
|
93
|
+
"""
|
|
94
|
+
Get APK file path.
|
|
95
|
+
|
|
96
|
+
Prioritizes returning the APK file within the project (bundled in wheel),
|
|
97
|
+
falls back to user cache if not exists.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Optional[Path]: APK file path, or None if neither exists.
|
|
101
|
+
"""
|
|
102
|
+
# Priority 1: Try bundled resource (packaged in wheel)
|
|
103
|
+
try:
|
|
104
|
+
from importlib.resources import files
|
|
105
|
+
|
|
106
|
+
logger.debug("Searching for bundled APK in wheel package")
|
|
107
|
+
resource = files("AutoGLM_GUI").joinpath(
|
|
108
|
+
"resources/apks", ADB_KEYBOARD_APK_FILENAME
|
|
109
|
+
)
|
|
110
|
+
# Convert to Path
|
|
111
|
+
if hasattr(resource, "read_bytes"):
|
|
112
|
+
# For Python 3.9+, use as_file() context manager
|
|
113
|
+
from importlib.resources import as_file
|
|
114
|
+
|
|
115
|
+
with as_file(resource) as path:
|
|
116
|
+
if path.exists():
|
|
117
|
+
logger.info(f"Found bundled APK: {path}")
|
|
118
|
+
return path
|
|
119
|
+
elif hasattr(resource, "_path"):
|
|
120
|
+
# Fallback for older importlib.resources
|
|
121
|
+
path = Path(str(resource))
|
|
122
|
+
if path.exists():
|
|
123
|
+
logger.info(f"Found bundled APK: {path}")
|
|
124
|
+
return path
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.debug(f"Bundled APK not found: {e}")
|
|
127
|
+
|
|
128
|
+
# Priority 2: Try user cache directory
|
|
129
|
+
if USER_CACHE_APK_PATH.exists():
|
|
130
|
+
logger.info(f"Found cached APK: {USER_CACHE_APK_PATH}")
|
|
131
|
+
return USER_CACHE_APK_PATH
|
|
132
|
+
|
|
133
|
+
logger.warning("APK file not found in bundled resources or cache")
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def download_apk(self, force: bool = False) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Get or download ADB Keyboard APK.
|
|
139
|
+
|
|
140
|
+
Prioritizes using the APK file within the project, downloads from GitHub if not exists.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
force: Whether to force re-download even if file already exists.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: True if APK is successfully obtained, False otherwise.
|
|
147
|
+
"""
|
|
148
|
+
# Check if APK already exists
|
|
149
|
+
existing_apk = self.get_apk_path()
|
|
150
|
+
if existing_apk and not force:
|
|
151
|
+
logger.debug(f"APK already exists at {existing_apk}, skipping download")
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
# Download from GitHub
|
|
155
|
+
logger.info(f"Downloading ADB Keyboard APK from {ADB_KEYBOARD_APK_URL}")
|
|
156
|
+
|
|
157
|
+
# Ensure cache directory exists
|
|
158
|
+
USER_CACHE_APK_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
urllib.request.urlretrieve(ADB_KEYBOARD_APK_URL, USER_CACHE_APK_PATH)
|
|
162
|
+
|
|
163
|
+
if USER_CACHE_APK_PATH.exists() and USER_CACHE_APK_PATH.stat().st_size > 0:
|
|
164
|
+
logger.info(f"APK downloaded successfully to {USER_CACHE_APK_PATH}")
|
|
165
|
+
return True
|
|
166
|
+
else:
|
|
167
|
+
logger.error("Downloaded APK is empty or invalid")
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(f"Failed to download APK: {e}")
|
|
172
|
+
# Clean up incomplete file
|
|
173
|
+
if USER_CACHE_APK_PATH.exists():
|
|
174
|
+
USER_CACHE_APK_PATH.unlink()
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
def install(self) -> Tuple[bool, str]:
|
|
178
|
+
"""
|
|
179
|
+
Install ADB Keyboard APK.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Tuple[bool, str]: (success, message)
|
|
183
|
+
"""
|
|
184
|
+
apk_path = self.get_apk_path()
|
|
185
|
+
if not apk_path or not apk_path.exists():
|
|
186
|
+
error_msg = "APK file not found. Please download first."
|
|
187
|
+
logger.error(error_msg)
|
|
188
|
+
return False, error_msg
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
logger.info(f"Installing ADB Keyboard from {apk_path}")
|
|
192
|
+
result = asyncio.run(
|
|
193
|
+
run_cmd_silently(self.adb_prefix + ["install", "-r", str(apk_path)])
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if "Success" in result.stdout or result.returncode == 0:
|
|
197
|
+
success_msg = "ADB Keyboard installed successfully"
|
|
198
|
+
logger.info(success_msg)
|
|
199
|
+
return True, success_msg
|
|
200
|
+
else:
|
|
201
|
+
error_msg = f"Installation failed: {result.stdout} {result.stderr}"
|
|
202
|
+
logger.error(error_msg)
|
|
203
|
+
return False, error_msg
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
error_msg = f"Installation error: {e}"
|
|
207
|
+
logger.exception("Unexpected error during installation")
|
|
208
|
+
return False, error_msg
|
|
209
|
+
|
|
210
|
+
def enable(self) -> Tuple[bool, str]:
|
|
211
|
+
"""
|
|
212
|
+
Enable ADB Keyboard (enable only, do not modify default input method).
|
|
213
|
+
|
|
214
|
+
Note: This only enables ADB Keyboard, does not set it as default input method.
|
|
215
|
+
In actual usage, Phone Agent will temporarily switch via detect_and_set_adb_keyboard().
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Tuple[bool, str]: (success, message)
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
logger.info("Enabling ADB Keyboard IME")
|
|
222
|
+
# Enable keyboard
|
|
223
|
+
result = asyncio.run(
|
|
224
|
+
run_cmd_silently(
|
|
225
|
+
self.adb_prefix + ["shell", "ime", "enable", ADB_KEYBOARD_IME]
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if result.returncode == 0:
|
|
230
|
+
success_msg = "ADB Keyboard enabled successfully"
|
|
231
|
+
logger.info(success_msg)
|
|
232
|
+
return True, success_msg
|
|
233
|
+
else:
|
|
234
|
+
# Some devices return non-zero but still succeed, verify with is_enabled()
|
|
235
|
+
if self.is_enabled():
|
|
236
|
+
success_msg = "ADB Keyboard enabled (verified)"
|
|
237
|
+
logger.info(success_msg)
|
|
238
|
+
return True, success_msg
|
|
239
|
+
else:
|
|
240
|
+
error_msg = f"Enable failed: {result.stdout} {result.stderr}"
|
|
241
|
+
logger.warning(error_msg)
|
|
242
|
+
return False, error_msg
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
error_msg = f"Enable error: {e}"
|
|
246
|
+
logger.exception("Unexpected error during enable")
|
|
247
|
+
return False, error_msg
|
|
248
|
+
|
|
249
|
+
def auto_setup(self) -> Tuple[bool, str]:
|
|
250
|
+
"""
|
|
251
|
+
Automatically complete installation and enablement process.
|
|
252
|
+
|
|
253
|
+
Intelligent handling:
|
|
254
|
+
1. Installed and enabled - skip, return True
|
|
255
|
+
2. Installed but not enabled - enable only, return result
|
|
256
|
+
3. Not installed - install+enable, return result
|
|
257
|
+
|
|
258
|
+
Note: This method does not interact with users, all user prompts should be handled by the caller.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Tuple[bool, str]: (success, message)
|
|
262
|
+
"""
|
|
263
|
+
logger.debug("Starting auto-setup for ADB Keyboard")
|
|
264
|
+
|
|
265
|
+
# Check current status
|
|
266
|
+
installed = self.is_installed()
|
|
267
|
+
enabled = self.is_enabled()
|
|
268
|
+
|
|
269
|
+
# Status 1: Installed and enabled
|
|
270
|
+
if installed and enabled:
|
|
271
|
+
msg = "ADB Keyboard is ready (already installed and enabled)"
|
|
272
|
+
logger.info(msg)
|
|
273
|
+
return True, msg
|
|
274
|
+
|
|
275
|
+
# Status 2: Installed but not enabled
|
|
276
|
+
if installed and not enabled:
|
|
277
|
+
logger.info("ADB Keyboard is installed but not enabled, enabling now")
|
|
278
|
+
return self.enable()
|
|
279
|
+
|
|
280
|
+
# Status 3: Not installed
|
|
281
|
+
if not installed:
|
|
282
|
+
logger.info("ADB Keyboard is not installed, starting installation")
|
|
283
|
+
|
|
284
|
+
# Step 1: Download APK (if not already available)
|
|
285
|
+
if not self.download_apk():
|
|
286
|
+
error_msg = "Failed to download APK"
|
|
287
|
+
logger.error(error_msg)
|
|
288
|
+
return False, error_msg
|
|
289
|
+
|
|
290
|
+
# Step 2: Install
|
|
291
|
+
success, message = self.install()
|
|
292
|
+
if not success:
|
|
293
|
+
logger.error(f"Installation failed: {message}")
|
|
294
|
+
return False, message
|
|
295
|
+
|
|
296
|
+
# Step 3: Enable
|
|
297
|
+
success, message = self.enable()
|
|
298
|
+
if not success:
|
|
299
|
+
logger.error(f"Enable failed: {message}")
|
|
300
|
+
return False, message
|
|
301
|
+
|
|
302
|
+
# Verify final status
|
|
303
|
+
if self.is_installed() and self.is_enabled():
|
|
304
|
+
success_msg = "ADB Keyboard setup completed successfully"
|
|
305
|
+
logger.info(success_msg)
|
|
306
|
+
return True, success_msg
|
|
307
|
+
else:
|
|
308
|
+
error_msg = "Setup completed but verification failed"
|
|
309
|
+
logger.warning(error_msg)
|
|
310
|
+
return False, error_msg
|
|
311
|
+
|
|
312
|
+
# Default return failure
|
|
313
|
+
error_msg = "Unknown status, setup failed"
|
|
314
|
+
logger.error(error_msg)
|
|
315
|
+
return False, error_msg
|
|
316
|
+
|
|
317
|
+
def get_status(self) -> dict:
|
|
318
|
+
"""
|
|
319
|
+
Get detailed status of ADB Keyboard.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
dict: Dictionary containing installation and enablement status.
|
|
323
|
+
"""
|
|
324
|
+
apk_path = self.get_apk_path()
|
|
325
|
+
installed = self.is_installed()
|
|
326
|
+
enabled = self.is_enabled()
|
|
327
|
+
|
|
328
|
+
# Determine current status
|
|
329
|
+
if installed and enabled:
|
|
330
|
+
status = "ready" # Ready
|
|
331
|
+
elif installed and not enabled:
|
|
332
|
+
status = "installed_but_disabled" # Installed but not enabled
|
|
333
|
+
elif not installed:
|
|
334
|
+
status = "not_installed" # Not installed
|
|
335
|
+
else:
|
|
336
|
+
status = "unknown" # Unknown
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
"installed": installed,
|
|
340
|
+
"enabled": enabled,
|
|
341
|
+
"status": status,
|
|
342
|
+
"status_text": {
|
|
343
|
+
"ready": "Installed and enabled",
|
|
344
|
+
"installed_but_disabled": "Installed but not enabled",
|
|
345
|
+
"not_installed": "Not installed",
|
|
346
|
+
"unknown": "Unknown status",
|
|
347
|
+
}.get(status, "Unknown"),
|
|
348
|
+
"apk_exists": apk_path is not None and apk_path.exists(),
|
|
349
|
+
"apk_path": str(apk_path) if apk_path else "N/A",
|
|
350
|
+
"cache_apk_exists": USER_CACHE_APK_PATH.exists(),
|
|
351
|
+
"cache_apk_path": str(USER_CACHE_APK_PATH),
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def auto_setup_adb_keyboard(device_id: Optional[str] = None) -> Tuple[bool, str]:
|
|
356
|
+
"""
|
|
357
|
+
Convenience function: One-click auto-install and enable ADB Keyboard.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
device_id: Optional device ID.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Tuple[bool, str]: (success, message)
|
|
364
|
+
"""
|
|
365
|
+
installer = ADBKeyboardInstaller(device_id)
|
|
366
|
+
return installer.auto_setup()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def check_and_suggest_installation() -> bool:
|
|
370
|
+
"""
|
|
371
|
+
Check if ADB Keyboard needs installation.
|
|
372
|
+
|
|
373
|
+
Note: This function does not interact with users, only returns boolean value.
|
|
374
|
+
All user prompts should be handled by the caller.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
bool: True if not installed, False otherwise.
|
|
378
|
+
"""
|
|
379
|
+
installer = ADBKeyboardInstaller()
|
|
380
|
+
return not installer.is_installed()
|
AutoGLM_GUI/api/agents.py
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
"""Agent lifecycle and chat routes."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
6
|
from fastapi import APIRouter, HTTPException
|
|
6
7
|
from fastapi.responses import StreamingResponse
|
|
7
8
|
|
|
8
9
|
from AutoGLM_GUI.config import config
|
|
10
|
+
from AutoGLM_GUI.config_manager import (
|
|
11
|
+
delete_config_file,
|
|
12
|
+
get_config_path,
|
|
13
|
+
load_config_file,
|
|
14
|
+
save_config_file,
|
|
15
|
+
)
|
|
9
16
|
from AutoGLM_GUI.schemas import (
|
|
10
17
|
APIAgentConfig,
|
|
11
18
|
APIModelConfig,
|
|
12
19
|
ChatRequest,
|
|
13
20
|
ChatResponse,
|
|
21
|
+
ConfigResponse,
|
|
22
|
+
ConfigSaveRequest,
|
|
14
23
|
InitRequest,
|
|
15
24
|
ResetRequest,
|
|
16
25
|
StatusResponse,
|
|
@@ -31,6 +40,9 @@ router = APIRouter()
|
|
|
31
40
|
@router.post("/api/init")
|
|
32
41
|
def init_agent(request: InitRequest) -> dict:
|
|
33
42
|
"""初始化 PhoneAgent(多设备支持)。"""
|
|
43
|
+
from AutoGLM_GUI.adb_plus import ADBKeyboardInstaller
|
|
44
|
+
from AutoGLM_GUI.logger import logger
|
|
45
|
+
|
|
34
46
|
req_model_config = request.model or APIModelConfig()
|
|
35
47
|
req_agent_config = request.agent or APIAgentConfig()
|
|
36
48
|
|
|
@@ -41,13 +53,29 @@ def init_agent(request: InitRequest) -> dict:
|
|
|
41
53
|
)
|
|
42
54
|
config.refresh_from_env()
|
|
43
55
|
|
|
56
|
+
# 检查并自动安装 ADB Keyboard
|
|
57
|
+
logger.info(f"Checking ADB Keyboard for device {device_id}...")
|
|
58
|
+
installer = ADBKeyboardInstaller(device_id=device_id)
|
|
59
|
+
status = installer.get_status()
|
|
60
|
+
|
|
61
|
+
if not (status["installed"] and status["enabled"]):
|
|
62
|
+
logger.info(f"Setting up ADB Keyboard for device {device_id}...")
|
|
63
|
+
success, message = installer.auto_setup()
|
|
64
|
+
if success:
|
|
65
|
+
logger.info(f"✓ Device {device_id}: {message}")
|
|
66
|
+
else:
|
|
67
|
+
logger.warning(f"✗ Device {device_id}: {message}")
|
|
68
|
+
else:
|
|
69
|
+
logger.info(f"✓ Device {device_id}: ADB Keyboard ready")
|
|
70
|
+
|
|
44
71
|
base_url = req_model_config.base_url or config.base_url
|
|
45
72
|
api_key = req_model_config.api_key or config.api_key
|
|
46
73
|
model_name = req_model_config.model_name or config.model_name
|
|
47
74
|
|
|
48
75
|
if not base_url:
|
|
49
76
|
raise HTTPException(
|
|
50
|
-
status_code=400,
|
|
77
|
+
status_code=400,
|
|
78
|
+
detail="base_url is required. Please configure via Settings or start with --base-url",
|
|
51
79
|
)
|
|
52
80
|
|
|
53
81
|
model_config = ModelConfig(
|
|
@@ -228,3 +256,97 @@ def reset_agent(request: ResetRequest) -> dict:
|
|
|
228
256
|
"device_id": device_id,
|
|
229
257
|
"message": f"Agent reset for device {device_id}",
|
|
230
258
|
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@router.get("/api/config", response_model=ConfigResponse)
|
|
262
|
+
def get_config_endpoint() -> ConfigResponse:
|
|
263
|
+
"""获取当前有效配置."""
|
|
264
|
+
from AutoGLM_GUI.config import config
|
|
265
|
+
|
|
266
|
+
# 加载配置文件
|
|
267
|
+
file_config = load_config_file()
|
|
268
|
+
|
|
269
|
+
# 读取当前实际运行的配置
|
|
270
|
+
current_base_url = os.getenv("AUTOGLM_BASE_URL", config.base_url)
|
|
271
|
+
current_model_name = os.getenv("AUTOGLM_MODEL_NAME", config.model_name)
|
|
272
|
+
current_api_key = os.getenv("AUTOGLM_API_KEY", config.api_key)
|
|
273
|
+
|
|
274
|
+
# 判断配置来源
|
|
275
|
+
# 如果环境变量中有 CLI 参数设置的值,优先级最高
|
|
276
|
+
env_config = {
|
|
277
|
+
"base_url": os.getenv("AUTOGLM_BASE_URL"),
|
|
278
|
+
"model_name": os.getenv("AUTOGLM_MODEL_NAME"),
|
|
279
|
+
"api_key": os.getenv("AUTOGLM_API_KEY"),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# 检查是否有 CLI 参数(环境变量值不同于默认值且文件配置中没有对应值)
|
|
283
|
+
has_cli_config = (
|
|
284
|
+
(env_config["base_url"] and env_config["base_url"] != "") and
|
|
285
|
+
(not file_config or file_config.get("base_url") != env_config["base_url"])
|
|
286
|
+
) or (
|
|
287
|
+
(env_config["model_name"] and env_config["model_name"] != "autoglm-phone-9b") and
|
|
288
|
+
(not file_config or file_config.get("model_name") != env_config["model_name"])
|
|
289
|
+
) or (
|
|
290
|
+
(env_config["api_key"] and env_config["api_key"] != "EMPTY") and
|
|
291
|
+
(not file_config or file_config.get("api_key") != env_config["api_key"])
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if has_cli_config:
|
|
295
|
+
source = "CLI arguments"
|
|
296
|
+
elif file_config:
|
|
297
|
+
source = "config file"
|
|
298
|
+
else:
|
|
299
|
+
source = "default"
|
|
300
|
+
|
|
301
|
+
return ConfigResponse(
|
|
302
|
+
base_url=current_base_url,
|
|
303
|
+
model_name=current_model_name,
|
|
304
|
+
api_key=current_api_key if current_api_key != "EMPTY" else "",
|
|
305
|
+
source=source,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@router.post("/api/config")
|
|
310
|
+
def save_config_endpoint(request: ConfigSaveRequest) -> dict:
|
|
311
|
+
"""保存配置到文件."""
|
|
312
|
+
try:
|
|
313
|
+
config_data = {
|
|
314
|
+
"base_url": request.base_url,
|
|
315
|
+
"model_name": request.model_name,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# 只有提供了 api_key 才保存
|
|
319
|
+
if request.api_key:
|
|
320
|
+
config_data["api_key"] = request.api_key
|
|
321
|
+
|
|
322
|
+
success = save_config_file(config_data)
|
|
323
|
+
|
|
324
|
+
if success:
|
|
325
|
+
# 刷新全局配置
|
|
326
|
+
os.environ["AUTOGLM_BASE_URL"] = request.base_url
|
|
327
|
+
os.environ["AUTOGLM_MODEL_NAME"] = request.model_name
|
|
328
|
+
if request.api_key:
|
|
329
|
+
os.environ["AUTOGLM_API_KEY"] = request.api_key
|
|
330
|
+
config.refresh_from_env()
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
"success": True,
|
|
334
|
+
"message": f"Configuration saved to {get_config_path()}",
|
|
335
|
+
}
|
|
336
|
+
else:
|
|
337
|
+
raise HTTPException(status_code=500, detail="Failed to save config")
|
|
338
|
+
except Exception as e:
|
|
339
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@router.delete("/api/config")
|
|
343
|
+
def delete_config_endpoint() -> dict:
|
|
344
|
+
"""删除配置文件."""
|
|
345
|
+
try:
|
|
346
|
+
success = delete_config_file()
|
|
347
|
+
if success:
|
|
348
|
+
return {"success": True, "message": "Configuration deleted"}
|
|
349
|
+
else:
|
|
350
|
+
raise HTTPException(status_code=500, detail="Failed to delete config")
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise HTTPException(status_code=500, detail=str(e))
|