Refactor CLI and LLM runtime handling for improved user experience

- Removed deprecated console usage in favor of structured logging functions for error handling and user prompts.
- Enhanced CLI prompts for LLM runtime and preset selection, utilizing menu helpers for better user interaction.
- Updated GPU pool scanning output with improved formatting and error indication for clarity.
- Refactored setup wizard to streamline LLM runtime and preset configuration, ensuring a more intuitive setup process.
- Improved documentation and user feedback in CLI outputs to enhance overall usability.
This commit is contained in:
Leonid Pershin
2026-08-21 06:31:15 +03:00
parent 64f93b4bf6
commit e7784473a2
9 changed files with 420 additions and 133 deletions
+99 -82
View File
@@ -10,7 +10,6 @@ from datetime import datetime, timezone
from typing import Optional
import typer
from rich.console import Console
from rich.table import Table
from gpu_rent import __version__
@@ -23,22 +22,15 @@ from gpu_rent.ssh_ops import interactive_ssh, run_ssh
from gpu_rent.state import load_state, preempt_window_end
from gpu_rent.provision import ensure_swarmui_running, seed_civitai, seed_extensions
from gpu_rent.sync_files import pull_tree, push_tree
from gpu_rent.term import console, err, log, ok, warn
from gpu_rent.tunnel import run_tunnel
if sys.platform == "win32":
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, OSError):
pass
app = typer.Typer(
no_args_is_help=True,
pretty_exceptions_enable=False,
add_completion=False,
help="Прерываемый GPU Selectel + SwarmUI на localhost:17801. Сначала: gpu-rent setup / doctor. Ключи: docs/setup.md",
)
console = Console(highlight=False, legacy_windows=False)
_DEBUG = False
@@ -54,7 +46,7 @@ def _root(
def _die(exc: BaseException) -> None:
if _DEBUG:
traceback.print_exc()
console.print(f"[red]{exc}[/red]")
err(str(exc))
raise typer.Exit(1)
@@ -368,7 +360,7 @@ def setup(
install_watchdog=watchdog if watchdog is not None else (False if yes else None),
confirm=None if yes and watchdog is None else confirm,
ask=None if yes and llm is not None else ask,
log=lambda m: console.print(m),
log=log,
)
except Exception as exc:
_die(exc)
@@ -412,7 +404,6 @@ def up(
from dataclasses import replace
from gpu_rent.llm_runtime import (
PRESET_HELP,
append_vars_llm_runtime,
decide_runtime,
ensure_ollama_manifest_from_example,
@@ -438,11 +429,24 @@ def up(
asked_model_preset = False
if not yes and runtime == "none" and not llm and not ollama and not llamacpp:
choice = typer.prompt(
"Поднять LLM рядом со SwarmUI? [none/ollama/llamacpp]",
default="none",
from gpu_rent.llm_runtime import (
llamacpp_preset_menu,
llm_runtime_menu,
ollama_preset_menu,
)
from gpu_rent.prompts import prompt_menu
def _ask(msg: str, default: str = "") -> str:
return typer.prompt(msg, default=default)
try:
choice = prompt_menu(
"LLM рядом со SwarmUI",
llm_runtime_menu(),
default="none",
ask=_ask,
show=log,
)
runtime = decide_runtime(
flag=choice, ollama_flag=False, llamacpp_flag=False, from_config="none"
)
@@ -452,63 +456,82 @@ def up(
append_vars_llm_runtime(vars_path(), runtime)
if runtime == "ollama":
ensure_ollama_manifest_from_example()
console.print(PRESET_HELP)
preset = typer.prompt("Ollama preset", default="recommended")
if preset.strip().lower() not in {"keep", "example"}:
write_ollama_models_preset(
cfg.ollama_models_manifest, preset.strip().lower()
try:
preset = prompt_menu(
"Ollama preset",
ollama_preset_menu(include_keep=False),
default="recommended",
ask=_ask,
show=log,
)
except ValueError as exc:
raise GpuRentError(str(exc)) from exc
if preset not in {"keep", "example"}:
write_ollama_models_preset(cfg.ollama_models_manifest, preset)
asked_model_preset = True
elif runtime == "llamacpp":
from gpu_rent.llm_runtime import (
LLAMACPP_PRESET_HELP,
ensure_llamacpp_manifest_from_example,
write_llamacpp_models_preset,
)
ensure_llamacpp_manifest_from_example()
console.print(LLAMACPP_PRESET_HELP)
preset = typer.prompt(
"llama.cpp GGUF preset [recommended/light/stock/empty]",
default="recommended",
)
if preset.strip().lower() not in {"keep", "example"}:
write_llamacpp_models_preset(
cfg.llamacpp_models_manifest, preset.strip().lower()
try:
preset = prompt_menu(
"llama.cpp GGUF",
llamacpp_preset_menu(include_keep=False),
default="recommended",
ask=_ask,
show=log,
)
except ValueError as exc:
raise GpuRentError(str(exc)) from exc
if preset not in {"keep", "example"}:
write_llamacpp_models_preset(cfg.llamacpp_models_manifest, preset)
asked_model_preset = True
# Runtime уже в vars (напр. llamacpp) — всё равно спросить модель, default=keep.
if not yes and not asked_model_preset and runtime == "llamacpp":
# Runtime уже в vars спросить пресет, default=keep.
if not yes and not asked_model_preset and runtime in {"llamacpp", "ollama"}:
from gpu_rent.llm_runtime import (
LLAMACPP_PRESET_HELP,
ensure_llamacpp_manifest_from_example,
llamacpp_preset_menu,
ollama_preset_menu,
write_llamacpp_models_preset,
)
from gpu_rent.prompts import prompt_menu
ensure_llamacpp_manifest_from_example()
console.print(LLAMACPP_PRESET_HELP)
preset = typer.prompt(
"llama.cpp GGUF preset [recommended/light/stock/empty/keep]",
default="keep",
)
key = preset.strip().lower()
if key not in {"keep", "example", ""}:
write_llamacpp_models_preset(cfg.llamacpp_models_manifest, key)
elif not yes and not asked_model_preset and runtime == "ollama":
ensure_ollama_manifest_from_example()
console.print(PRESET_HELP)
preset = typer.prompt(
"Ollama preset [recommended/light/stock/alt/empty/keep]",
default="keep",
)
key = preset.strip().lower()
if key not in {"keep", "example", ""}:
write_ollama_models_preset(cfg.ollama_models_manifest, key)
def _ask2(msg: str, default: str = "") -> str:
return typer.prompt(msg, default=default)
try:
if runtime == "llamacpp":
ensure_llamacpp_manifest_from_example()
key = prompt_menu(
"llama.cpp GGUF",
llamacpp_preset_menu(include_keep=True),
default="keep",
ask=_ask2,
show=log,
)
if key not in {"keep", "example", ""}:
write_llamacpp_models_preset(cfg.llamacpp_models_manifest, key)
else:
ensure_ollama_manifest_from_example()
key = prompt_menu(
"Ollama preset",
ollama_preset_menu(include_keep=True),
default="keep",
ask=_ask2,
show=log,
)
if key not in {"keep", "example", ""}:
write_ollama_models_preset(cfg.ollama_models_manifest, key)
except ValueError as exc:
raise GpuRentError(str(exc)) from exc
cfg = replace(cfg, llm_runtime=runtime)
if runtime != "none":
console.print(f"LLM runtime: {runtime}")
ok(f"LLM runtime: {runtime}")
def confirm(msg: str) -> bool:
return typer.confirm(msg)
@@ -525,7 +548,7 @@ def up(
update=False if no_update else None,
confirm=confirm,
ask=None if yes else ask,
log=lambda m: console.print(m),
log=log,
)
if no_tunnel:
from gpu_rent.access_card import print_access_card
@@ -550,7 +573,7 @@ def up(
cfg,
state.floating_ip,
open_browser=open_browser,
log=lambda m: console.print(m),
log=log,
)
except GpuRentError as exc:
_die(exc)
@@ -563,7 +586,7 @@ def stop(
"""Удалить compute и FIP, диски оставить."""
try:
cfg = load_config(require_auth=True)
cmd_stop(cfg, no_pull=no_pull, log=lambda m: console.print(m))
cmd_stop(cfg, no_pull=no_pull, log=log)
except GpuRentError as exc:
_die(exc)
@@ -579,7 +602,7 @@ def destroy(
raise typer.Exit(1)
try:
cfg = load_config(require_auth=True)
cmd_stop(cfg, destroy_disks=True, no_pull=no_pull, log=lambda m: console.print(m))
cmd_stop(cfg, destroy_disks=True, no_pull=no_pull, log=log)
except GpuRentError as exc:
_die(exc)
@@ -636,7 +659,7 @@ def tunnel(
cfg,
state.floating_ip,
open_browser=open_browser,
log=lambda m: console.print(m),
log=log,
)
except GpuRentError as exc:
_die(exc)
@@ -654,9 +677,9 @@ def hold(
cfg, host = _live()
if clear:
clear_hold(cfg, host, log=lambda m: console.print(m))
clear_hold(cfg, host, log=log)
return
set_hold(cfg, host, minutes=minutes, until=until, log=lambda m: console.print(m))
set_hold(cfg, host, minutes=minutes, until=until, log=log)
except GpuRentError as exc:
_die(exc)
@@ -666,7 +689,7 @@ def seed_models() -> None:
"""Докачать Civitai-манифест на живой диск."""
try:
cfg, host = _live()
seed_civitai(cfg, host, log=lambda m: console.print(m))
seed_civitai(cfg, host, log=log)
except GpuRentError as exc:
_die(exc)
@@ -677,9 +700,6 @@ def push_all() -> None:
try:
cfg, host = _live()
def log(msg: str) -> None:
console.print(msg)
push_tree(cfg, host, cfg.local_models_dir, "/mnt/swarm_data/Models", log, models=True)
push_tree(cfg, host, cfg.local_wildcards_dir, "/mnt/swarm_data/Data/Wildcards", log, models=False)
push_tree(cfg, host, cfg.local_workflows_dir, "/mnt/swarm_data/CustomWorkflows", log, models=False)
@@ -697,7 +717,7 @@ def push_models() -> None:
host,
cfg.local_models_dir,
"/mnt/swarm_data/Models",
lambda m: console.print(m),
log,
models=True,
)
except GpuRentError as exc:
@@ -714,7 +734,7 @@ def pull_output_cmd() -> None:
host,
"/mnt/swarm_data/Output",
cfg.local_output_dir,
lambda m: console.print(m),
log,
)
except GpuRentError as exc:
_die(exc)
@@ -726,9 +746,6 @@ def seed_extensions_cmd() -> None:
try:
cfg, host = _live()
def log(msg: str) -> None:
console.print(msg)
seed_extensions(cfg, host, log)
ensure_swarmui_running(cfg, host, log, restart=True)
except GpuRentError as exc:
@@ -768,9 +785,9 @@ def capture_root(
host,
dry_run=dry_run,
kind_filter=kind,
log=lambda m: console.print(m),
log=log,
)
print_report(report, lambda m: console.print(m), dry_run=dry_run)
print_report(report, log, dry_run=dry_run)
except GpuRentError as exc:
_die(exc)
@@ -796,11 +813,11 @@ def capture_models_cmd(
None,
dry_run=dry_run,
kind_filter=kind,
log=lambda m: console.print(m),
log=log,
)
print_report(
report,
lambda m: console.print(m),
log,
dry_run=dry_run,
show_models=True,
show_extensions=False,
@@ -823,11 +840,11 @@ def capture_extensions_cmd(
host,
None,
dry_run=dry_run,
log=lambda m: console.print(m),
log=log,
)
print_report(
report,
lambda m: console.print(m),
log,
dry_run=dry_run,
show_models=False,
show_extensions=True,
@@ -854,9 +871,9 @@ def capture_all_cmd(
host,
dry_run=dry_run,
kind_filter=kind,
log=lambda m: console.print(m),
log=log,
)
print_report(report, lambda m: console.print(m), dry_run=dry_run)
print_report(report, log, dry_run=dry_run)
except GpuRentError as exc:
_die(exc)
@@ -868,7 +885,7 @@ def resize_data(gb: int = typer.Option(..., "--gb", help="Новый разме
from gpu_rent.resize import resize_data_volume
cfg = load_config(require_auth=True)
resize_data_volume(cfg, gb, log=lambda m: console.print(m))
resize_data_volume(cfg, gb, log=log)
except GpuRentError as exc:
_die(exc)
@@ -905,9 +922,9 @@ def watchdog_install(
try:
from gpu_rent.local_watchdog import install_watchdog
install_watchdog(interval_minutes=interval, log=lambda m: console.print(m))
install_watchdog(interval_minutes=interval, log=log)
except Exception as exc:
console.print(f"[red]install fail:[/red] {exc}")
err(f"install fail: {exc}")
raise typer.Exit(1) from exc
@@ -917,9 +934,9 @@ def watchdog_uninstall() -> None:
try:
from gpu_rent.local_watchdog import uninstall_watchdog
uninstall_watchdog(log=lambda m: console.print(m))
uninstall_watchdog(log=log)
except Exception as exc:
console.print(f"[red]uninstall fail:[/red] {exc}")
err(f"uninstall fail: {exc}")
raise typer.Exit(1) from exc
@@ -946,7 +963,7 @@ def watchdog_tick(
try:
from gpu_rent.local_watchdog import run_tick
decision = run_tick(dry_run=dry_run, log=lambda m: console.print(m))
decision = run_tick(dry_run=dry_run, log=log)
if decision.kind == "stop" and not dry_run:
raise typer.Exit(0)
except GpuRentError as exc: