- 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.
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
"""Numbered interactive menus (Enter = default index)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Sequence
|
|
from dataclasses import dataclass
|
|
|
|
from rich.markup import escape
|
|
|
|
Ask = Callable[[str, str], str]
|
|
Show = Callable[[str], None]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MenuItem:
|
|
key: str
|
|
label: str
|
|
|
|
|
|
def resolve_menu_choice(
|
|
raw: str,
|
|
items: Sequence[MenuItem],
|
|
*,
|
|
default_key: str,
|
|
) -> str:
|
|
"""Accept 1-based index or exact key (case-insensitive). Empty → default."""
|
|
text = (raw or "").strip().lower()
|
|
if not text:
|
|
return default_key
|
|
if text.isdigit():
|
|
idx = int(text)
|
|
if 1 <= idx <= len(items):
|
|
return items[idx - 1].key
|
|
raise ValueError(f"выбор: жду 1…{len(items)}, получили {idx}")
|
|
for item in items:
|
|
if item.key.lower() == text:
|
|
return item.key
|
|
keys = ", ".join(f"{i}:{it.key}" for i, it in enumerate(items, 1))
|
|
raise ValueError(f"выбор: жду номер или ключ ({keys}), получили {raw!r}")
|
|
|
|
|
|
def prompt_menu(
|
|
title: str,
|
|
items: Sequence[MenuItem],
|
|
*,
|
|
default: str,
|
|
ask: Ask,
|
|
show: Show | None = None,
|
|
) -> str:
|
|
"""Print numbered list, ask for index (default = current recommendation)."""
|
|
if not items:
|
|
raise ValueError(f"{title}: пустое меню")
|
|
default_idx = 1
|
|
for i, item in enumerate(items, 1):
|
|
if item.key == default:
|
|
default_idx = i
|
|
break
|
|
safe_title = escape(title)
|
|
lines = [f"[bold cyan]{safe_title}[/bold cyan]"]
|
|
for i, item in enumerate(items, 1):
|
|
label = escape(item.label)
|
|
if i == default_idx:
|
|
lines.append(f" [cyan]{i}.[/cyan] {label} [green]←[/green]")
|
|
else:
|
|
lines.append(f" [dim]{i}.[/dim] {label}")
|
|
emit = show
|
|
if emit is None:
|
|
from gpu_rent.term import log as emit # type: ignore[assignment]
|
|
for line in lines:
|
|
emit(line)
|
|
raw = ask(f"{title} [1-{len(items)}]", str(default_idx))
|
|
return resolve_menu_choice(raw, items, default_key=items[default_idx - 1].key)
|