amd-gaia 0.15.1__py3-none-any.whl → 0.15.2__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.
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +1 -2
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/RECORD +35 -31
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
- gaia/agents/base/agent.py +45 -90
- gaia/agents/base/api_agent.py +0 -1
- gaia/agents/base/console.py +126 -0
- gaia/agents/base/tools.py +7 -2
- gaia/agents/blender/__init__.py +7 -0
- gaia/agents/blender/agent.py +7 -10
- gaia/agents/blender/core/view.py +2 -2
- gaia/agents/chat/agent.py +22 -48
- gaia/agents/chat/app.py +7 -0
- gaia/agents/chat/tools/rag_tools.py +23 -8
- gaia/agents/chat/tools/shell_tools.py +1 -0
- gaia/agents/code/prompts/code_patterns.py +2 -4
- gaia/agents/docker/agent.py +1 -0
- gaia/agents/emr/agent.py +3 -5
- gaia/agents/emr/cli.py +1 -1
- gaia/agents/emr/dashboard/server.py +2 -4
- gaia/apps/llm/app.py +14 -3
- gaia/chat/app.py +2 -4
- gaia/cli.py +511 -333
- gaia/installer/__init__.py +23 -0
- gaia/installer/init_command.py +1275 -0
- gaia/installer/lemonade_installer.py +619 -0
- gaia/llm/__init__.py +2 -1
- gaia/llm/lemonade_client.py +284 -99
- gaia/llm/providers/lemonade.py +12 -14
- gaia/rag/sdk.py +1 -1
- gaia/security.py +24 -4
- gaia/talk/app.py +2 -4
- gaia/version.py +2 -2
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +0 -0
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +0 -0
- {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
gaia/cli.py
CHANGED
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
|
|
12
12
|
from dotenv import load_dotenv
|
|
13
13
|
|
|
14
|
+
from gaia.agents.base.console import AgentConsole
|
|
14
15
|
from gaia.llm import create_client
|
|
15
16
|
from gaia.llm.lemonade_client import (
|
|
16
17
|
DEFAULT_HOST,
|
|
@@ -55,25 +56,6 @@ DEFAULT_EXPERIMENTS_DIR = "output/experiments"
|
|
|
55
56
|
DEFAULT_EVALUATIONS_DIR = "output/evaluations"
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
# Helper functions for download progress display
|
|
59
|
-
def _format_bytes(b: int) -> str:
|
|
60
|
-
"""Format bytes to human readable string."""
|
|
61
|
-
if b >= 1024 * 1024 * 1024:
|
|
62
|
-
return f"{b / (1024 * 1024 * 1024):.1f} GB"
|
|
63
|
-
elif b >= 1024 * 1024:
|
|
64
|
-
return f"{b / (1024 * 1024):.1f} MB"
|
|
65
|
-
elif b >= 1024:
|
|
66
|
-
return f"{b / 1024:.1f} KB"
|
|
67
|
-
return f"{b} B"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _make_progress_bar(percent: int, width: int = 20) -> str:
|
|
71
|
-
"""Create a progress bar string."""
|
|
72
|
-
filled = int(width * percent / 100)
|
|
73
|
-
empty = width - filled
|
|
74
|
-
return f"[{'█' * filled}{'░' * empty}]"
|
|
75
|
-
|
|
76
|
-
|
|
77
59
|
def check_lemonade_health(host=None, port=None):
|
|
78
60
|
"""Check if Lemonade server is running and healthy using LemonadeClient."""
|
|
79
61
|
log = get_logger(__name__)
|
|
@@ -189,7 +171,7 @@ def ensure_agent_models(
|
|
|
189
171
|
host: str = DEFAULT_HOST,
|
|
190
172
|
port: int = DEFAULT_PORT,
|
|
191
173
|
quiet: bool = False,
|
|
192
|
-
|
|
174
|
+
_timeout: int = 1800, # Reserved for future use
|
|
193
175
|
) -> bool:
|
|
194
176
|
"""
|
|
195
177
|
Ensure all models required for an agent are downloaded.
|
|
@@ -229,86 +211,72 @@ def ensure_agent_models(
|
|
|
229
211
|
log.debug(f"All models for {agent} agent already available")
|
|
230
212
|
return True
|
|
231
213
|
|
|
214
|
+
# Use AgentConsole for nicely formatted progress display
|
|
215
|
+
console = AgentConsole()
|
|
216
|
+
|
|
232
217
|
if not quiet:
|
|
233
|
-
|
|
234
|
-
f"
|
|
218
|
+
console.print_info(
|
|
219
|
+
f"Downloading {len(models_to_download)} model(s) for {agent} agent"
|
|
235
220
|
)
|
|
236
|
-
print()
|
|
237
|
-
|
|
238
|
-
# Progress tracking
|
|
239
|
-
last_percent = [-1]
|
|
240
|
-
last_file_index = [0]
|
|
241
221
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if quiet:
|
|
245
|
-
|
|
222
|
+
# Download each model with progress display
|
|
223
|
+
for model_id in models_to_download:
|
|
224
|
+
if not quiet:
|
|
225
|
+
console.print_download_start(model_id)
|
|
246
226
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
total_files = data.get("total_files", 1)
|
|
252
|
-
|
|
253
|
-
# Print newline when moving to a new file
|
|
254
|
-
if file_index != last_file_index[0] and last_file_index[0] > 0:
|
|
255
|
-
print() # Newline for previous file
|
|
256
|
-
last_file_index[0] = file_index
|
|
257
|
-
|
|
258
|
-
# Update every 2% for smooth progress
|
|
259
|
-
if percent >= last_percent[0] + 2 or percent == 0 or percent == 100:
|
|
260
|
-
bytes_downloaded = data.get("bytes_downloaded", 0)
|
|
261
|
-
bytes_total = data.get("bytes_total", 0)
|
|
262
|
-
|
|
263
|
-
# Create progress bar
|
|
264
|
-
bar = _make_progress_bar(percent)
|
|
265
|
-
progress_line = (
|
|
266
|
-
f" {bar} {percent:3d}% "
|
|
267
|
-
f"[{file_index}/{total_files}] {file_name}: "
|
|
268
|
-
f"{_format_bytes(bytes_downloaded)}/{_format_bytes(bytes_total)}"
|
|
269
|
-
)
|
|
270
|
-
print(f"\r{progress_line:<100}", end="", flush=True)
|
|
271
|
-
last_percent[0] = percent
|
|
227
|
+
try:
|
|
228
|
+
event_count = 0
|
|
229
|
+
last_bytes = 0
|
|
230
|
+
last_time = time.time()
|
|
272
231
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
last_percent[0] = -1
|
|
277
|
-
last_file_index[0] = 0
|
|
232
|
+
for event in client.pull_model_stream(model_name=model_id):
|
|
233
|
+
event_count += 1
|
|
234
|
+
event_type = event.get("event")
|
|
278
235
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
236
|
+
if event_type == "progress":
|
|
237
|
+
# Skip first 2 spurious events from Lemonade
|
|
238
|
+
if event_count <= 2 or quiet:
|
|
239
|
+
continue
|
|
283
240
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
241
|
+
# Calculate download speed
|
|
242
|
+
current_bytes = event.get("bytes_downloaded", 0)
|
|
243
|
+
current_time = time.time()
|
|
244
|
+
time_delta = current_time - last_time
|
|
245
|
+
|
|
246
|
+
speed_mbps = 0.0
|
|
247
|
+
if time_delta > 0.1 and current_bytes > last_bytes:
|
|
248
|
+
bytes_delta = current_bytes - last_bytes
|
|
249
|
+
speed_mbps = (bytes_delta / time_delta) / (1024 * 1024)
|
|
250
|
+
last_bytes = current_bytes
|
|
251
|
+
last_time = current_time
|
|
252
|
+
|
|
253
|
+
console.print_download_progress(
|
|
254
|
+
percent=event.get("percent", 0),
|
|
255
|
+
bytes_downloaded=current_bytes,
|
|
256
|
+
bytes_total=event.get("bytes_total", 0),
|
|
257
|
+
speed_mbps=speed_mbps,
|
|
258
|
+
)
|
|
287
259
|
|
|
288
|
-
|
|
289
|
-
|
|
260
|
+
elif event_type == "complete":
|
|
261
|
+
if not quiet:
|
|
262
|
+
console.print_download_complete(model_id)
|
|
290
263
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
):
|
|
297
|
-
if event.get("event") == "error":
|
|
264
|
+
elif event_type == "error":
|
|
265
|
+
if not quiet:
|
|
266
|
+
console.print_download_error(
|
|
267
|
+
event.get("error", "Unknown error"), model_id
|
|
268
|
+
)
|
|
298
269
|
log.error(f"Failed to download {model_id}")
|
|
299
270
|
return False
|
|
271
|
+
|
|
300
272
|
except LemonadeClientError as e:
|
|
301
273
|
log.error(f"Failed to download {model_id}: {e}")
|
|
302
274
|
if not quiet:
|
|
303
|
-
|
|
275
|
+
console.print_download_error(str(e), model_id)
|
|
304
276
|
return False
|
|
305
277
|
|
|
306
|
-
if not quiet:
|
|
307
|
-
print()
|
|
308
|
-
|
|
309
278
|
if not quiet:
|
|
310
|
-
|
|
311
|
-
print()
|
|
279
|
+
console.print_success(f"All models ready for {agent} agent")
|
|
312
280
|
|
|
313
281
|
return True
|
|
314
282
|
|
|
@@ -523,14 +491,30 @@ async def async_main(action, **kwargs):
|
|
|
523
491
|
# Create client for actions that use GaiaCliClient (not chat - it uses ChatAgent)
|
|
524
492
|
client = None
|
|
525
493
|
if action in ["prompt", "stats"]:
|
|
526
|
-
# Filter out
|
|
494
|
+
# Filter out parameters that are not accepted by GaiaCliClient
|
|
495
|
+
# GaiaCliClient only accepts: model, max_tokens, show_stats, logging_level
|
|
527
496
|
audio_params = {
|
|
528
497
|
"whisper_model_size",
|
|
529
498
|
"audio_device_index",
|
|
530
499
|
"silence_threshold",
|
|
531
500
|
"no_tts",
|
|
532
501
|
}
|
|
533
|
-
|
|
502
|
+
llm_provider_params = {
|
|
503
|
+
"use_claude",
|
|
504
|
+
"use_chatgpt",
|
|
505
|
+
"claude_model",
|
|
506
|
+
"base_url",
|
|
507
|
+
}
|
|
508
|
+
cli_params = {
|
|
509
|
+
"action",
|
|
510
|
+
"message",
|
|
511
|
+
"stats",
|
|
512
|
+
"assistant_name",
|
|
513
|
+
"stream",
|
|
514
|
+
"no_lemonade_check",
|
|
515
|
+
"list_tools",
|
|
516
|
+
}
|
|
517
|
+
excluded_params = cli_params | audio_params | llm_provider_params
|
|
534
518
|
client_params = {k: v for k, v in kwargs.items() if k not in excluded_params}
|
|
535
519
|
client = GaiaCliClient(**client_params)
|
|
536
520
|
|
|
@@ -1117,82 +1101,6 @@ def main():
|
|
|
1117
1101
|
)
|
|
1118
1102
|
api_parser.set_defaults(action="api")
|
|
1119
1103
|
|
|
1120
|
-
# Add model pull command
|
|
1121
|
-
pull_parser = subparsers.add_parser(
|
|
1122
|
-
"pull",
|
|
1123
|
-
help="Download/install a model from the Lemonade Server registry",
|
|
1124
|
-
parents=[parent_parser],
|
|
1125
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1126
|
-
epilog="""
|
|
1127
|
-
Examples:
|
|
1128
|
-
# Pull a registered model
|
|
1129
|
-
gaia pull Qwen3-0.6B-GGUF
|
|
1130
|
-
|
|
1131
|
-
# Pull and register a custom model from HuggingFace
|
|
1132
|
-
gaia pull user.Custom-Model-GGUF --checkpoint unsloth/Custom-Model-GGUF:Q4_K_M --recipe llamacpp
|
|
1133
|
-
|
|
1134
|
-
# Pull a reasoning model
|
|
1135
|
-
gaia pull user.DeepSeek-GGUF --checkpoint unsloth/DeepSeek-R1-GGUF --recipe llamacpp --reasoning
|
|
1136
|
-
|
|
1137
|
-
# Pull a vision model with mmproj
|
|
1138
|
-
gaia pull user.Vision-Model --checkpoint model/vision:Q4 --recipe llamacpp --vision --mmproj mmproj.gguf
|
|
1139
|
-
""",
|
|
1140
|
-
)
|
|
1141
|
-
pull_parser.add_argument(
|
|
1142
|
-
"model_name",
|
|
1143
|
-
help="Name of the model to pull (use 'user.' prefix for custom models)",
|
|
1144
|
-
)
|
|
1145
|
-
pull_parser.add_argument(
|
|
1146
|
-
"--checkpoint",
|
|
1147
|
-
help="HuggingFace checkpoint for custom models (e.g., unsloth/Model-GGUF:Q4_K_M)",
|
|
1148
|
-
)
|
|
1149
|
-
pull_parser.add_argument(
|
|
1150
|
-
"--recipe",
|
|
1151
|
-
help="Lemonade recipe for custom models (e.g., llamacpp, oga-cpu)",
|
|
1152
|
-
)
|
|
1153
|
-
pull_parser.add_argument(
|
|
1154
|
-
"--reasoning",
|
|
1155
|
-
action="store_true",
|
|
1156
|
-
help="Mark model as a reasoning model (like DeepSeek)",
|
|
1157
|
-
)
|
|
1158
|
-
pull_parser.add_argument(
|
|
1159
|
-
"--vision",
|
|
1160
|
-
action="store_true",
|
|
1161
|
-
help="Mark model as having vision capabilities",
|
|
1162
|
-
)
|
|
1163
|
-
pull_parser.add_argument(
|
|
1164
|
-
"--embedding",
|
|
1165
|
-
action="store_true",
|
|
1166
|
-
help="Mark model as an embedding model",
|
|
1167
|
-
)
|
|
1168
|
-
pull_parser.add_argument(
|
|
1169
|
-
"--reranking",
|
|
1170
|
-
action="store_true",
|
|
1171
|
-
help="Mark model as a reranking model",
|
|
1172
|
-
)
|
|
1173
|
-
pull_parser.add_argument(
|
|
1174
|
-
"--mmproj",
|
|
1175
|
-
help="Multimodal projector file for vision models",
|
|
1176
|
-
)
|
|
1177
|
-
pull_parser.add_argument(
|
|
1178
|
-
"--timeout",
|
|
1179
|
-
type=int,
|
|
1180
|
-
default=1200,
|
|
1181
|
-
help="Timeout in seconds for model download (default: 1200)",
|
|
1182
|
-
)
|
|
1183
|
-
pull_parser.add_argument(
|
|
1184
|
-
"--host",
|
|
1185
|
-
default="localhost",
|
|
1186
|
-
help="Lemonade server host (default: localhost)",
|
|
1187
|
-
)
|
|
1188
|
-
pull_parser.add_argument(
|
|
1189
|
-
"--port",
|
|
1190
|
-
type=int,
|
|
1191
|
-
default=8000,
|
|
1192
|
-
help="Lemonade server port (default: 8000)",
|
|
1193
|
-
)
|
|
1194
|
-
pull_parser.set_defaults(action="pull")
|
|
1195
|
-
|
|
1196
1104
|
# Add model download command
|
|
1197
1105
|
download_parser = subparsers.add_parser(
|
|
1198
1106
|
"download",
|
|
@@ -1330,7 +1238,12 @@ Available agents: chat, code, talk, rag, blender, jira, docker, vlm, minimal, mc
|
|
|
1330
1238
|
"kill", help="Kill process running on specific port", parents=[parent_parser]
|
|
1331
1239
|
)
|
|
1332
1240
|
kill_parser.add_argument(
|
|
1333
|
-
"--port", type=int,
|
|
1241
|
+
"--port", type=int, default=None, help="Port number to kill process on"
|
|
1242
|
+
)
|
|
1243
|
+
kill_parser.add_argument(
|
|
1244
|
+
"--lemonade",
|
|
1245
|
+
action="store_true",
|
|
1246
|
+
help="Kill Lemonade server (port 8000)",
|
|
1334
1247
|
)
|
|
1335
1248
|
|
|
1336
1249
|
# Add LLM app command
|
|
@@ -2111,6 +2024,102 @@ Examples:
|
|
|
2111
2024
|
"--all", action="store_true", help="Clear all caches"
|
|
2112
2025
|
)
|
|
2113
2026
|
|
|
2027
|
+
# Init command (one-stop GAIA setup)
|
|
2028
|
+
init_parser = subparsers.add_parser(
|
|
2029
|
+
"init",
|
|
2030
|
+
help="Initialize GAIA: install Lemonade and download models",
|
|
2031
|
+
parents=[parent_parser],
|
|
2032
|
+
)
|
|
2033
|
+
init_parser.add_argument(
|
|
2034
|
+
"--profile",
|
|
2035
|
+
"-p",
|
|
2036
|
+
default="chat",
|
|
2037
|
+
choices=["minimal", "chat", "code", "rag", "all"],
|
|
2038
|
+
help="Profile to initialize: minimal, chat, code, rag, all (default: chat)",
|
|
2039
|
+
)
|
|
2040
|
+
init_parser.add_argument(
|
|
2041
|
+
"--minimal",
|
|
2042
|
+
action="store_true",
|
|
2043
|
+
help="Use minimal profile (~2.5 GB) - shortcut for --profile minimal",
|
|
2044
|
+
)
|
|
2045
|
+
init_parser.add_argument(
|
|
2046
|
+
"--skip-models",
|
|
2047
|
+
action="store_true",
|
|
2048
|
+
help="Skip model downloads (only install Lemonade)",
|
|
2049
|
+
)
|
|
2050
|
+
init_parser.add_argument(
|
|
2051
|
+
"--force-reinstall",
|
|
2052
|
+
action="store_true",
|
|
2053
|
+
help="Force reinstall even if compatible version exists",
|
|
2054
|
+
)
|
|
2055
|
+
init_parser.add_argument(
|
|
2056
|
+
"--force-models",
|
|
2057
|
+
action="store_true",
|
|
2058
|
+
help="Force re-download models (deletes then re-downloads each model)",
|
|
2059
|
+
)
|
|
2060
|
+
init_parser.add_argument(
|
|
2061
|
+
"--yes",
|
|
2062
|
+
"-y",
|
|
2063
|
+
action="store_true",
|
|
2064
|
+
help="Skip confirmation prompts (non-interactive)",
|
|
2065
|
+
)
|
|
2066
|
+
init_parser.add_argument(
|
|
2067
|
+
"--verbose",
|
|
2068
|
+
action="store_true",
|
|
2069
|
+
help="Enable verbose output",
|
|
2070
|
+
)
|
|
2071
|
+
init_parser.add_argument(
|
|
2072
|
+
"--remote",
|
|
2073
|
+
action="store_true",
|
|
2074
|
+
help="Lemonade is hosted on a remote machine (skip local server start, still check version)",
|
|
2075
|
+
)
|
|
2076
|
+
|
|
2077
|
+
# Install command (install specific components)
|
|
2078
|
+
install_parser = subparsers.add_parser(
|
|
2079
|
+
"install",
|
|
2080
|
+
help="Install GAIA components",
|
|
2081
|
+
parents=[parent_parser],
|
|
2082
|
+
)
|
|
2083
|
+
install_parser.add_argument(
|
|
2084
|
+
"--lemonade",
|
|
2085
|
+
action="store_true",
|
|
2086
|
+
help="Install Lemonade Server",
|
|
2087
|
+
)
|
|
2088
|
+
install_parser.add_argument(
|
|
2089
|
+
"--yes",
|
|
2090
|
+
"-y",
|
|
2091
|
+
action="store_true",
|
|
2092
|
+
help="Skip confirmation prompts",
|
|
2093
|
+
)
|
|
2094
|
+
install_parser.add_argument(
|
|
2095
|
+
"--silent",
|
|
2096
|
+
action="store_true",
|
|
2097
|
+
help="Silent installation (no UI, no desktop shortcuts)",
|
|
2098
|
+
)
|
|
2099
|
+
|
|
2100
|
+
# Uninstall command (uninstall specific components)
|
|
2101
|
+
uninstall_parser = subparsers.add_parser(
|
|
2102
|
+
"uninstall",
|
|
2103
|
+
help="Uninstall GAIA components",
|
|
2104
|
+
parents=[parent_parser],
|
|
2105
|
+
)
|
|
2106
|
+
uninstall_parser.add_argument(
|
|
2107
|
+
"--lemonade",
|
|
2108
|
+
action="store_true",
|
|
2109
|
+
help="Uninstall Lemonade Server",
|
|
2110
|
+
)
|
|
2111
|
+
uninstall_parser.add_argument(
|
|
2112
|
+
"--models",
|
|
2113
|
+
action="store_true",
|
|
2114
|
+
help="Clear all downloaded models (frees disk space)",
|
|
2115
|
+
)
|
|
2116
|
+
uninstall_parser.add_argument(
|
|
2117
|
+
"--yes",
|
|
2118
|
+
"-y",
|
|
2119
|
+
action="store_true",
|
|
2120
|
+
help="Skip confirmation prompts",
|
|
2121
|
+
)
|
|
2122
|
+
|
|
2114
2123
|
args = parser.parse_args()
|
|
2115
2124
|
|
|
2116
2125
|
# Check if action is specified
|
|
@@ -2664,9 +2673,7 @@ Examples:
|
|
|
2664
2673
|
print(f"❌ Error: Failed to initialize TTS: {e}")
|
|
2665
2674
|
return
|
|
2666
2675
|
|
|
2667
|
-
test_text =
|
|
2668
|
-
args.test_text
|
|
2669
|
-
or """
|
|
2676
|
+
test_text = args.test_text or """
|
|
2670
2677
|
Let's play a game of trivia. I'll ask you a series of questions on a particular topic,
|
|
2671
2678
|
and you try to answer them to the best of your ability.
|
|
2672
2679
|
|
|
@@ -2682,7 +2689,6 @@ E) Edgar Allan Poe
|
|
|
2682
2689
|
|
|
2683
2690
|
Let me know your answer!
|
|
2684
2691
|
"""
|
|
2685
|
-
)
|
|
2686
2692
|
|
|
2687
2693
|
if args.test_type == "tts-preprocessing":
|
|
2688
2694
|
tts.test_preprocessing(test_text)
|
|
@@ -2796,106 +2802,48 @@ Let me know your answer!
|
|
|
2796
2802
|
|
|
2797
2803
|
# Handle kill command
|
|
2798
2804
|
if args.action == "kill":
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2805
|
+
if args.lemonade:
|
|
2806
|
+
# Use lemonade-server stop for graceful shutdown
|
|
2807
|
+
try:
|
|
2808
|
+
result = subprocess.run(
|
|
2809
|
+
["lemonade-server", "stop"],
|
|
2810
|
+
capture_output=True,
|
|
2811
|
+
text=True,
|
|
2812
|
+
check=False,
|
|
2813
|
+
)
|
|
2814
|
+
if result.returncode == 0:
|
|
2815
|
+
print("✅ Lemonade server stopped")
|
|
2816
|
+
else:
|
|
2817
|
+
# Fallback to port kill if stop command fails
|
|
2818
|
+
log.warning(f"lemonade-server stop failed: {result.stderr}")
|
|
2819
|
+
port_result = kill_process_by_port(8000)
|
|
2820
|
+
if port_result["success"]:
|
|
2821
|
+
print(f"✅ {port_result['message']}")
|
|
2822
|
+
else:
|
|
2823
|
+
print(f"❌ {port_result['message']}")
|
|
2824
|
+
except FileNotFoundError:
|
|
2825
|
+
# lemonade-server not in PATH, fallback to port kill
|
|
2826
|
+
log.warning("lemonade-server not found, falling back to port kill")
|
|
2827
|
+
port_result = kill_process_by_port(8000)
|
|
2828
|
+
if port_result["success"]:
|
|
2829
|
+
print(f"✅ {port_result['message']}")
|
|
2830
|
+
else:
|
|
2831
|
+
print(f"❌ {port_result['message']}")
|
|
2832
|
+
elif args.port:
|
|
2833
|
+
port = args.port
|
|
2834
|
+
log.info(f"Attempting to kill process on port {port}")
|
|
2835
|
+
result = kill_process_by_port(port)
|
|
2836
|
+
if result["success"]:
|
|
2837
|
+
print(f"✅ {result['message']}")
|
|
2838
|
+
else:
|
|
2839
|
+
print(f"❌ {result['message']}")
|
|
2803
2840
|
else:
|
|
2804
|
-
print(
|
|
2841
|
+
print("❌ Specify --lemonade or --port <number>")
|
|
2805
2842
|
return
|
|
2806
2843
|
|
|
2807
2844
|
# Import LemonadeManager for model commands error handling
|
|
2808
2845
|
from gaia.llm.lemonade_manager import LemonadeManager
|
|
2809
2846
|
|
|
2810
|
-
# Handle model pull command
|
|
2811
|
-
if args.action == "pull":
|
|
2812
|
-
log.info(f"Pulling model: {args.model_name}")
|
|
2813
|
-
verbose = getattr(args, "verbose", False)
|
|
2814
|
-
try:
|
|
2815
|
-
client = LemonadeClient(host=args.host, port=args.port, verbose=verbose)
|
|
2816
|
-
|
|
2817
|
-
# Check if Lemonade server is running
|
|
2818
|
-
if not check_lemonade_health(args.host, args.port):
|
|
2819
|
-
LemonadeManager.print_server_error()
|
|
2820
|
-
return
|
|
2821
|
-
|
|
2822
|
-
print(f"📥 Pulling model: {args.model_name}")
|
|
2823
|
-
|
|
2824
|
-
# Define a CLI progress callback for real-time updates
|
|
2825
|
-
last_percent = [-1] # Use list to allow mutation in closure
|
|
2826
|
-
last_file_index = [0]
|
|
2827
|
-
|
|
2828
|
-
def cli_progress_callback(event_type: str, data: dict) -> None:
|
|
2829
|
-
"""Display download progress in CLI."""
|
|
2830
|
-
if event_type == "progress":
|
|
2831
|
-
percent = data.get("percent", 0)
|
|
2832
|
-
file_name = data.get("file", "unknown")
|
|
2833
|
-
file_index = data.get("file_index", 1)
|
|
2834
|
-
total_files = data.get("total_files", 1)
|
|
2835
|
-
|
|
2836
|
-
# Print newline when moving to a new file
|
|
2837
|
-
if file_index != last_file_index[0] and last_file_index[0] > 0:
|
|
2838
|
-
print() # Newline for previous file
|
|
2839
|
-
last_file_index[0] = file_index
|
|
2840
|
-
|
|
2841
|
-
# Update every 2% for smooth progress
|
|
2842
|
-
if percent >= last_percent[0] + 2 or percent == 0 or percent == 100:
|
|
2843
|
-
bytes_downloaded = data.get("bytes_downloaded", 0)
|
|
2844
|
-
bytes_total = data.get("bytes_total", 0)
|
|
2845
|
-
|
|
2846
|
-
# Create progress bar
|
|
2847
|
-
bar = _make_progress_bar(percent)
|
|
2848
|
-
progress_line = (
|
|
2849
|
-
f" {bar} {percent:3d}% "
|
|
2850
|
-
f"[{file_index}/{total_files}] {file_name}: "
|
|
2851
|
-
f"{_format_bytes(bytes_downloaded)}/{_format_bytes(bytes_total)}"
|
|
2852
|
-
)
|
|
2853
|
-
print(f"\r{progress_line:<100}", end="", flush=True)
|
|
2854
|
-
last_percent[0] = percent
|
|
2855
|
-
|
|
2856
|
-
elif event_type == "complete":
|
|
2857
|
-
print() # Newline after progress
|
|
2858
|
-
print(f"✅ Model downloaded successfully: {args.model_name}")
|
|
2859
|
-
|
|
2860
|
-
elif event_type == "error":
|
|
2861
|
-
print() # Newline after progress
|
|
2862
|
-
error_msg = data.get("error", "Unknown error")
|
|
2863
|
-
print(f"❌ Download failed: {error_msg}")
|
|
2864
|
-
|
|
2865
|
-
# Use streaming pull with progress callback
|
|
2866
|
-
completed = False
|
|
2867
|
-
for event in client.pull_model_stream(
|
|
2868
|
-
model_name=args.model_name,
|
|
2869
|
-
checkpoint=getattr(args, "checkpoint", None),
|
|
2870
|
-
recipe=getattr(args, "recipe", None),
|
|
2871
|
-
reasoning=getattr(args, "reasoning", False) or None,
|
|
2872
|
-
vision=getattr(args, "vision", False) or None,
|
|
2873
|
-
embedding=getattr(args, "embedding", False) or None,
|
|
2874
|
-
reranking=getattr(args, "reranking", False) or None,
|
|
2875
|
-
mmproj=getattr(args, "mmproj", None),
|
|
2876
|
-
timeout=args.timeout,
|
|
2877
|
-
progress_callback=cli_progress_callback,
|
|
2878
|
-
):
|
|
2879
|
-
if event.get("event") == "complete":
|
|
2880
|
-
completed = True
|
|
2881
|
-
elif event.get("event") == "error":
|
|
2882
|
-
sys.exit(1)
|
|
2883
|
-
|
|
2884
|
-
if not completed:
|
|
2885
|
-
print("⚠️ Model pull completed without explicit complete event")
|
|
2886
|
-
|
|
2887
|
-
except LemonadeClientError as e:
|
|
2888
|
-
print(f"❌ Error pulling model: {e}")
|
|
2889
|
-
sys.exit(1)
|
|
2890
|
-
except Exception as e:
|
|
2891
|
-
error_msg = str(e).lower()
|
|
2892
|
-
if "connection" in error_msg or "refused" in error_msg:
|
|
2893
|
-
LemonadeManager.print_server_error()
|
|
2894
|
-
else:
|
|
2895
|
-
print(f"❌ Error: {e}")
|
|
2896
|
-
sys.exit(1)
|
|
2897
|
-
return
|
|
2898
|
-
|
|
2899
2847
|
# Handle model download command
|
|
2900
2848
|
if args.action == "download":
|
|
2901
2849
|
from gaia.llm.lemonade_client import AGENT_PROFILES, MODELS
|
|
@@ -3020,122 +2968,110 @@ Let me know your answer!
|
|
|
3020
2968
|
agent_name = args.agent.lower()
|
|
3021
2969
|
model_ids = client.get_required_models(agent_name)
|
|
3022
2970
|
|
|
2971
|
+
console = AgentConsole()
|
|
2972
|
+
|
|
3023
2973
|
if not model_ids:
|
|
3024
2974
|
if agent_name != "all":
|
|
3025
2975
|
profile = client.get_agent_profile(agent_name)
|
|
3026
2976
|
if not profile:
|
|
3027
|
-
|
|
3028
|
-
|
|
2977
|
+
console.print_error(f"Unknown agent: {agent_name}")
|
|
2978
|
+
console.print_info(
|
|
2979
|
+
f"Available: {', '.join(client.list_agents())}"
|
|
2980
|
+
)
|
|
3029
2981
|
sys.exit(1)
|
|
3030
|
-
|
|
2982
|
+
console.print_info(f"No models to download for '{agent_name}'")
|
|
3031
2983
|
return
|
|
3032
2984
|
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
# Track progress per model
|
|
3037
|
-
current_model = [None]
|
|
3038
|
-
last_percent = [-1]
|
|
3039
|
-
last_file_index = [0]
|
|
3040
|
-
|
|
3041
|
-
def download_progress_callback(event_type: str, data: dict) -> None:
|
|
3042
|
-
"""Display download progress in CLI."""
|
|
3043
|
-
if event_type == "progress":
|
|
3044
|
-
percent = data.get("percent", 0)
|
|
3045
|
-
file_name = data.get("file", "unknown")
|
|
3046
|
-
file_index = data.get("file_index", 1)
|
|
3047
|
-
total_files = data.get("total_files", 1)
|
|
3048
|
-
|
|
3049
|
-
# Print newline when moving to a new file
|
|
3050
|
-
if file_index != last_file_index[0] and last_file_index[0] > 0:
|
|
3051
|
-
print() # Newline for previous file
|
|
3052
|
-
last_file_index[0] = file_index
|
|
3053
|
-
|
|
3054
|
-
# Update every 2% for smooth progress
|
|
3055
|
-
if percent >= last_percent[0] + 2 or percent == 0 or percent == 100:
|
|
3056
|
-
bytes_downloaded = data.get("bytes_downloaded", 0)
|
|
3057
|
-
bytes_total = data.get("bytes_total", 0)
|
|
3058
|
-
|
|
3059
|
-
# Create progress bar
|
|
3060
|
-
bar = _make_progress_bar(percent)
|
|
3061
|
-
progress_line = (
|
|
3062
|
-
f" {bar} {percent:3d}% "
|
|
3063
|
-
f"[{file_index}/{total_files}] {file_name}: "
|
|
3064
|
-
f"{_format_bytes(bytes_downloaded)}/{_format_bytes(bytes_total)}"
|
|
3065
|
-
)
|
|
3066
|
-
print(f"\r{progress_line:<100}", end="", flush=True)
|
|
3067
|
-
last_percent[0] = percent
|
|
3068
|
-
|
|
3069
|
-
elif event_type == "complete":
|
|
3070
|
-
print() # Newline after progress
|
|
3071
|
-
print(" ✅ Download complete")
|
|
3072
|
-
last_percent[0] = -1 # Reset for next model
|
|
3073
|
-
last_file_index[0] = 0
|
|
3074
|
-
|
|
3075
|
-
elif event_type == "error":
|
|
3076
|
-
print() # Newline after progress
|
|
3077
|
-
error_msg = data.get("error", "Unknown error")
|
|
3078
|
-
print(f" ❌ Error: {error_msg}")
|
|
2985
|
+
console.print_info(
|
|
2986
|
+
f"Downloading {len(model_ids)} model(s) for '{agent_name}'"
|
|
2987
|
+
)
|
|
3079
2988
|
|
|
3080
|
-
# Download each model
|
|
2989
|
+
# Download each model with progress display
|
|
3081
2990
|
success_count = 0
|
|
3082
2991
|
skip_count = 0
|
|
3083
2992
|
fail_count = 0
|
|
3084
2993
|
|
|
3085
2994
|
for model_id in model_ids:
|
|
3086
|
-
current_model[0] = model_id
|
|
3087
|
-
last_percent[0] = -1
|
|
3088
|
-
|
|
3089
2995
|
# Check if already available
|
|
3090
2996
|
if client.check_model_available(model_id):
|
|
3091
|
-
|
|
2997
|
+
console.print_download_skipped(model_id)
|
|
3092
2998
|
skip_count += 1
|
|
3093
2999
|
continue
|
|
3094
3000
|
|
|
3095
|
-
|
|
3001
|
+
console.print_download_start(model_id)
|
|
3096
3002
|
|
|
3097
3003
|
try:
|
|
3098
3004
|
completed = False
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
):
|
|
3104
|
-
|
|
3005
|
+
event_count = 0
|
|
3006
|
+
last_bytes = 0
|
|
3007
|
+
last_time = time.time()
|
|
3008
|
+
|
|
3009
|
+
for event in client.pull_model_stream(model_name=model_id):
|
|
3010
|
+
event_count += 1
|
|
3011
|
+
event_type = event.get("event")
|
|
3012
|
+
|
|
3013
|
+
if event_type == "progress":
|
|
3014
|
+
# Skip first 2 spurious events from Lemonade
|
|
3015
|
+
if event_count <= 2:
|
|
3016
|
+
continue
|
|
3017
|
+
|
|
3018
|
+
# Calculate download speed
|
|
3019
|
+
current_bytes = event.get("bytes_downloaded", 0)
|
|
3020
|
+
current_time = time.time()
|
|
3021
|
+
time_delta = current_time - last_time
|
|
3022
|
+
|
|
3023
|
+
speed_mbps = 0.0
|
|
3024
|
+
if time_delta > 0.1 and current_bytes > last_bytes:
|
|
3025
|
+
bytes_delta = current_bytes - last_bytes
|
|
3026
|
+
speed_mbps = (bytes_delta / time_delta) / (1024 * 1024)
|
|
3027
|
+
last_bytes = current_bytes
|
|
3028
|
+
last_time = current_time
|
|
3029
|
+
|
|
3030
|
+
console.print_download_progress(
|
|
3031
|
+
percent=event.get("percent", 0),
|
|
3032
|
+
bytes_downloaded=current_bytes,
|
|
3033
|
+
bytes_total=event.get("bytes_total", 0),
|
|
3034
|
+
speed_mbps=speed_mbps,
|
|
3035
|
+
)
|
|
3036
|
+
|
|
3037
|
+
elif event_type == "complete":
|
|
3038
|
+
console.print_download_complete(model_id)
|
|
3105
3039
|
completed = True
|
|
3106
|
-
|
|
3040
|
+
|
|
3041
|
+
elif event_type == "error":
|
|
3042
|
+
console.print_download_error(
|
|
3043
|
+
event.get("error", "Unknown error"), model_id
|
|
3044
|
+
)
|
|
3107
3045
|
fail_count += 1
|
|
3108
3046
|
break
|
|
3109
3047
|
|
|
3110
3048
|
if completed:
|
|
3111
3049
|
success_count += 1
|
|
3112
3050
|
except LemonadeClientError as e:
|
|
3113
|
-
|
|
3051
|
+
console.print_download_error(str(e), model_id)
|
|
3114
3052
|
fail_count += 1
|
|
3115
3053
|
|
|
3116
|
-
print()
|
|
3117
|
-
|
|
3118
3054
|
# Summary
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3055
|
+
console.print_info("=" * 50)
|
|
3056
|
+
console.print_info("Download Summary:")
|
|
3057
|
+
console.print_success(f"Downloaded: {success_count}")
|
|
3058
|
+
console.print_info(f"Skipped (already available): {skip_count}")
|
|
3123
3059
|
if fail_count > 0:
|
|
3124
|
-
|
|
3125
|
-
|
|
3060
|
+
console.print_error(f"Failed: {fail_count}")
|
|
3061
|
+
console.print_info("=" * 50)
|
|
3126
3062
|
|
|
3127
3063
|
if fail_count > 0:
|
|
3128
3064
|
sys.exit(1)
|
|
3129
3065
|
|
|
3130
3066
|
except LemonadeClientError as e:
|
|
3131
|
-
|
|
3067
|
+
console.print_error(str(e))
|
|
3132
3068
|
sys.exit(1)
|
|
3133
3069
|
except Exception as e:
|
|
3134
3070
|
error_msg = str(e).lower()
|
|
3135
3071
|
if "connection" in error_msg or "refused" in error_msg:
|
|
3136
3072
|
LemonadeManager.print_server_error()
|
|
3137
3073
|
else:
|
|
3138
|
-
|
|
3074
|
+
console.print_error(str(e))
|
|
3139
3075
|
sys.exit(1)
|
|
3140
3076
|
return
|
|
3141
3077
|
|
|
@@ -4091,6 +4027,248 @@ Let me know your answer!
|
|
|
4091
4027
|
handle_visualize_command(args)
|
|
4092
4028
|
return
|
|
4093
4029
|
|
|
4030
|
+
# Handle init command
|
|
4031
|
+
if args.action == "init":
|
|
4032
|
+
from gaia.installer.init_command import run_init
|
|
4033
|
+
|
|
4034
|
+
# --minimal flag overrides --profile
|
|
4035
|
+
profile = "minimal" if args.minimal else args.profile
|
|
4036
|
+
|
|
4037
|
+
exit_code = run_init(
|
|
4038
|
+
profile=profile,
|
|
4039
|
+
skip_models=args.skip_models,
|
|
4040
|
+
force_reinstall=args.force_reinstall,
|
|
4041
|
+
force_models=args.force_models,
|
|
4042
|
+
yes=args.yes,
|
|
4043
|
+
verbose=getattr(args, "verbose", False),
|
|
4044
|
+
remote=getattr(args, "remote", False),
|
|
4045
|
+
)
|
|
4046
|
+
sys.exit(exit_code)
|
|
4047
|
+
|
|
4048
|
+
# Handle install command
|
|
4049
|
+
if args.action == "install":
|
|
4050
|
+
if args.lemonade:
|
|
4051
|
+
from gaia.installer.lemonade_installer import LemonadeInstaller
|
|
4052
|
+
from gaia.version import LEMONADE_VERSION
|
|
4053
|
+
|
|
4054
|
+
installer = LemonadeInstaller()
|
|
4055
|
+
|
|
4056
|
+
# Check if already installed
|
|
4057
|
+
info = installer.check_installation()
|
|
4058
|
+
if info.installed and info.version:
|
|
4059
|
+
installed_ver = info.version.lstrip("v")
|
|
4060
|
+
target_ver = LEMONADE_VERSION.lstrip("v")
|
|
4061
|
+
|
|
4062
|
+
if installed_ver == target_ver:
|
|
4063
|
+
print(f"✅ Lemonade Server v{info.version} is already installed")
|
|
4064
|
+
sys.exit(0)
|
|
4065
|
+
else:
|
|
4066
|
+
print(f"Lemonade Server v{info.version} is installed")
|
|
4067
|
+
print(f"GAIA requires v{LEMONADE_VERSION}")
|
|
4068
|
+
print("")
|
|
4069
|
+
print("To update, run:")
|
|
4070
|
+
print(" gaia uninstall --lemonade")
|
|
4071
|
+
print(" gaia install --lemonade")
|
|
4072
|
+
sys.exit(1)
|
|
4073
|
+
|
|
4074
|
+
# Confirm installation
|
|
4075
|
+
if not args.yes:
|
|
4076
|
+
response = input(f"Install Lemonade v{LEMONADE_VERSION}? [Y/n]: ")
|
|
4077
|
+
if response.lower() == "n":
|
|
4078
|
+
print("Installation cancelled")
|
|
4079
|
+
sys.exit(0)
|
|
4080
|
+
|
|
4081
|
+
# Download and install
|
|
4082
|
+
print("Downloading Lemonade Server...")
|
|
4083
|
+
try:
|
|
4084
|
+
installer_path = installer.download_installer()
|
|
4085
|
+
print("Installing...")
|
|
4086
|
+
result = installer.install(installer_path, silent=args.silent)
|
|
4087
|
+
|
|
4088
|
+
if result.success:
|
|
4089
|
+
# Verify installation
|
|
4090
|
+
verify_info = installer.check_installation()
|
|
4091
|
+
if verify_info.installed:
|
|
4092
|
+
print(f"✅ Installed Lemonade Server v{verify_info.version}")
|
|
4093
|
+
else:
|
|
4094
|
+
print(f"✅ Installed Lemonade Server v{result.version}")
|
|
4095
|
+
sys.exit(0)
|
|
4096
|
+
else:
|
|
4097
|
+
print(f"❌ Installation failed: {result.error}")
|
|
4098
|
+
sys.exit(1)
|
|
4099
|
+
except Exception as e:
|
|
4100
|
+
print(f"❌ Installation failed: {e}")
|
|
4101
|
+
sys.exit(1)
|
|
4102
|
+
else:
|
|
4103
|
+
print("Specify what to install: --lemonade")
|
|
4104
|
+
sys.exit(1)
|
|
4105
|
+
|
|
4106
|
+
# Handle uninstall command
|
|
4107
|
+
if args.action == "uninstall":
|
|
4108
|
+
from rich.console import Console
|
|
4109
|
+
|
|
4110
|
+
console = Console()
|
|
4111
|
+
|
|
4112
|
+
# Handle model cache clearing
|
|
4113
|
+
if args.models:
|
|
4114
|
+
import shutil
|
|
4115
|
+
|
|
4116
|
+
try:
|
|
4117
|
+
# Find HuggingFace cache directory
|
|
4118
|
+
hf_cache = Path.home() / ".cache" / "huggingface" / "hub"
|
|
4119
|
+
if sys.platform == "win32":
|
|
4120
|
+
hf_cache = (
|
|
4121
|
+
Path(os.path.expanduser("~")) / ".cache" / "huggingface" / "hub"
|
|
4122
|
+
)
|
|
4123
|
+
|
|
4124
|
+
if not hf_cache.exists():
|
|
4125
|
+
console.print("[yellow]📦 No model cache found[/yellow]")
|
|
4126
|
+
console.print(f" [dim]Checked: {hf_cache}[/dim]")
|
|
4127
|
+
sys.exit(0)
|
|
4128
|
+
|
|
4129
|
+
# Find all model directories
|
|
4130
|
+
model_dirs = list(hf_cache.glob("models--*"))
|
|
4131
|
+
if not model_dirs:
|
|
4132
|
+
console.print("[green]✅ Model cache is already empty[/green]")
|
|
4133
|
+
console.print(f" [dim]Location: {hf_cache}[/dim]")
|
|
4134
|
+
sys.exit(0)
|
|
4135
|
+
|
|
4136
|
+
# Calculate total size
|
|
4137
|
+
total_size = 0
|
|
4138
|
+
for model_dir in model_dirs:
|
|
4139
|
+
try:
|
|
4140
|
+
total_size += sum(
|
|
4141
|
+
f.stat().st_size
|
|
4142
|
+
for f in model_dir.rglob("*")
|
|
4143
|
+
if f.is_file()
|
|
4144
|
+
)
|
|
4145
|
+
except Exception:
|
|
4146
|
+
pass
|
|
4147
|
+
|
|
4148
|
+
size_gb = total_size / (1024**3)
|
|
4149
|
+
|
|
4150
|
+
# Show what will be deleted
|
|
4151
|
+
console.print()
|
|
4152
|
+
console.print(f"[bold]Found {len(model_dirs)} model(s) in cache[/bold]")
|
|
4153
|
+
console.print(f" [dim]Location: {hf_cache}[/dim]")
|
|
4154
|
+
console.print(f" [dim]Total size: ~{size_gb:.1f} GB[/dim]")
|
|
4155
|
+
console.print()
|
|
4156
|
+
|
|
4157
|
+
# Confirm deletion
|
|
4158
|
+
if not args.yes:
|
|
4159
|
+
console.print(
|
|
4160
|
+
f"[bold]Delete all {len(model_dirs)} model(s)?[/bold] [dim](frees ~{size_gb:.1f} GB)[/dim]"
|
|
4161
|
+
)
|
|
4162
|
+
console.print()
|
|
4163
|
+
console.print(" [y/N]: ", end="")
|
|
4164
|
+
response = input()
|
|
4165
|
+
if response.lower() != "y":
|
|
4166
|
+
console.print("[dim]Model deletion cancelled[/dim]")
|
|
4167
|
+
sys.exit(0)
|
|
4168
|
+
|
|
4169
|
+
console.print()
|
|
4170
|
+
console.print(
|
|
4171
|
+
f"[bold blue]Deleting {len(model_dirs)} model(s)...[/bold blue]"
|
|
4172
|
+
)
|
|
4173
|
+
console.print()
|
|
4174
|
+
|
|
4175
|
+
success_count = 0
|
|
4176
|
+
fail_count = 0
|
|
4177
|
+
|
|
4178
|
+
for model_dir in model_dirs:
|
|
4179
|
+
# Extract model name from directory
|
|
4180
|
+
model_name = model_dir.name.replace("models--", "").replace(
|
|
4181
|
+
"--", "/"
|
|
4182
|
+
)
|
|
4183
|
+
console.print(f" [cyan]{model_name}[/cyan]... ", end="")
|
|
4184
|
+
try:
|
|
4185
|
+
shutil.rmtree(model_dir)
|
|
4186
|
+
console.print("[green]✅[/green]")
|
|
4187
|
+
success_count += 1
|
|
4188
|
+
except PermissionError:
|
|
4189
|
+
console.print("[red]❌ (locked)[/red]")
|
|
4190
|
+
fail_count += 1
|
|
4191
|
+
except Exception as e:
|
|
4192
|
+
console.print(f"[red]❌ ({e})[/red]")
|
|
4193
|
+
fail_count += 1
|
|
4194
|
+
|
|
4195
|
+
# Summary
|
|
4196
|
+
console.print()
|
|
4197
|
+
if success_count > 0:
|
|
4198
|
+
console.print(f"[green]✅ Deleted {success_count} model(s)[/green]")
|
|
4199
|
+
if fail_count > 0:
|
|
4200
|
+
console.print(
|
|
4201
|
+
f"[red]❌ Failed to delete {fail_count} model(s)[/red]"
|
|
4202
|
+
)
|
|
4203
|
+
console.print()
|
|
4204
|
+
console.print(" [bold]If files are locked:[/bold]")
|
|
4205
|
+
console.print(
|
|
4206
|
+
" [dim]1. Close all apps using models (gaia chat, etc.)[/dim]"
|
|
4207
|
+
)
|
|
4208
|
+
console.print(
|
|
4209
|
+
" [dim]2. Stop Lemonade:[/dim] [cyan]gaia kill --lemonade[/cyan]"
|
|
4210
|
+
)
|
|
4211
|
+
console.print(
|
|
4212
|
+
" [dim]3. Re-run:[/dim] [cyan]gaia uninstall --models[/cyan]"
|
|
4213
|
+
)
|
|
4214
|
+
|
|
4215
|
+
sys.exit(0 if fail_count == 0 else 1)
|
|
4216
|
+
|
|
4217
|
+
except Exception as e:
|
|
4218
|
+
console.print(f"[red]❌ Error: {e}[/red]")
|
|
4219
|
+
sys.exit(1)
|
|
4220
|
+
|
|
4221
|
+
# Handle Lemonade Server uninstallation
|
|
4222
|
+
elif args.lemonade:
|
|
4223
|
+
from gaia.installer.lemonade_installer import LemonadeInstaller
|
|
4224
|
+
|
|
4225
|
+
installer = LemonadeInstaller(console=console)
|
|
4226
|
+
|
|
4227
|
+
# Check if installed
|
|
4228
|
+
info = installer.check_installation()
|
|
4229
|
+
if not info.installed:
|
|
4230
|
+
console.print("[green]✅ Lemonade Server is not installed[/green]")
|
|
4231
|
+
sys.exit(0)
|
|
4232
|
+
|
|
4233
|
+
# Show installation details
|
|
4234
|
+
console.print()
|
|
4235
|
+
console.print(f"[bold]Found Lemonade Server v{info.version}[/bold]")
|
|
4236
|
+
if info.path:
|
|
4237
|
+
console.print(f" [dim]Location: {info.path}[/dim]")
|
|
4238
|
+
console.print()
|
|
4239
|
+
|
|
4240
|
+
# Confirm uninstallation
|
|
4241
|
+
if not args.yes:
|
|
4242
|
+
console.print(
|
|
4243
|
+
f"[bold]Uninstall Lemonade Server v{info.version}?[/bold] \\[y/N]: ",
|
|
4244
|
+
end="",
|
|
4245
|
+
)
|
|
4246
|
+
response = input()
|
|
4247
|
+
if response.lower() != "y":
|
|
4248
|
+
console.print("[dim]Uninstall cancelled[/dim]")
|
|
4249
|
+
sys.exit(0)
|
|
4250
|
+
|
|
4251
|
+
# Uninstall
|
|
4252
|
+
console.print()
|
|
4253
|
+
console.print("[bold blue]Uninstalling Lemonade Server...[/bold blue]")
|
|
4254
|
+
result = installer.uninstall(silent=True)
|
|
4255
|
+
|
|
4256
|
+
if result.success:
|
|
4257
|
+
console.print()
|
|
4258
|
+
console.print(
|
|
4259
|
+
"[green]✅ Lemonade Server uninstalled successfully[/green]"
|
|
4260
|
+
)
|
|
4261
|
+
sys.exit(0)
|
|
4262
|
+
else:
|
|
4263
|
+
console.print()
|
|
4264
|
+
console.print(f"[red]❌ Uninstall failed: {result.error}[/red]")
|
|
4265
|
+
sys.exit(1)
|
|
4266
|
+
else:
|
|
4267
|
+
console.print("[yellow]Specify what to uninstall:[/yellow]")
|
|
4268
|
+
console.print(" [cyan]--lemonade[/cyan] Uninstall Lemonade Server")
|
|
4269
|
+
console.print(" [cyan]--models[/cyan] Clear all downloaded models")
|
|
4270
|
+
sys.exit(1)
|
|
4271
|
+
|
|
4094
4272
|
# Log error for unknown action
|
|
4095
4273
|
log.error(f"Unknown action specified: {args.action}")
|
|
4096
4274
|
parser.print_help()
|