KeiSeiKit-1.0/install/lib-onboarding-ui.sh
KeiSei84 83c21327f0 style(install): KeiSei brand colors + ASCII banner in installer output (#37)
KEISEI ASCII logo (голубой) at install start; [install] prefix тёмно-жёлтый
(38;5;178); primitive names + language codes голубой (38;5;39); language names
тёмно-жёлтый. lib-log COLOR now enables under curl|bash (detect terminal via
[ -t 1 ] || /dev/tty — web-install tees stdout so -t 1 was false → colors off).
Color detection, not an interactivity gate. Verified piped-under-pty.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 16:06:17 +08:00

211 lines
8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# shellcheck shell=bash
# lib-onboarding-ui.sh — pick_* функции мастера (whiptail / bash select).
#
# Constructor Pattern: 1 файл = UI слой. Парсеры реестров — в registry.sh,
# state-запись — в state.sh.
#
# Заполняет глобалы:
# ONBOARDING_LANG, ONBOARDING_TRANSPORT, ONBOARDING_PROVIDER, ONBOARDING_MODEL
# ONBOARDING_AUTH_ENV_KEYS[] + ONBOARDING_AUTH_ENV_VALUES[]
#
# Использует:
# - lib-i18n.sh: STR_* словарь + i18n_available_languages + i18n_load_lang
# - lib-onboarding-registry.sh: списки провайдеров/моделей
# Read a validated 1-based menu choice. Non-numeric or out-of-range input is
# rejected with a re-prompt instead of crashing: bash arithmetic $((ans-1))
# treats a non-numeric "ans" (e.g. the user typing "claude") as a variable name
# → "unbound variable" under `set -u`. $1=option count, $2=prompt.
# Echoes a number in [1,$1] on stdout; prompts/warnings go to stderr.
_onb_read_choice() {
local max="$1" prompt="$2" ans
while true; do
read -r -p "$prompt" ans
ans="${ans:-1}"
if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "$max" ]; then
printf '%s' "$ans"; return 0
fi
printf ' ⚠ %s\n' "${STR_PICK_INVALID:-please enter a number from 1 to $max}" >&2
done
}
onboarding_pick_language() {
local langs
langs="$(i18n_available_languages 2>/dev/null)"
if [ -z "$langs" ]; then
langs="$(printf 'en\tEnglish\nru\tРусский\n')"
fi
if command -v whiptail >/dev/null 2>&1; then
local args=() first=1
while IFS=$'\t' read -r code name; do
[ -z "$code" ] && continue
if [ "$first" = "1" ]; then
args+=("$code" "$name" "ON"); first=0
else
args+=("$code" "$name" "OFF")
fi
done <<< "$langs"
ONBOARDING_LANG=$(whiptail --title "KeiSei · Language / Язык / 语言 / 言語 / ..." --radiolist \
"Choose interface language / Выберите язык:" 22 70 16 \
"${args[@]}" 3>&1 1>&2 2>&3) || ONBOARDING_LANG="en"
else
echo "" >&2
echo "Choose language / Выберите язык / 选择语言 / 言語選択:" >&2
declare -a codes=()
local i=1
while IFS=$'\t' read -r code name; do
[ -z "$code" ] && continue
codes+=("$code")
printf " %2d) ${KEI_BLUE:-}%s${KEI_RST:-}${KEI_GOLD:-}%s${KEI_RST:-}\n" "$i" "$code" "$name" >&2
i=$((i+1))
done <<< "$langs"
ans="$(_onb_read_choice "${#codes[@]}" "[1-${#codes[@]}, default 1=en]: ")"
ONBOARDING_LANG="${codes[$((ans-1))]:-en}"
fi
command -v i18n_load_lang >/dev/null 2>&1 && i18n_load_lang "$ONBOARDING_LANG"
}
onboarding_pick_transport() {
local transports
transports=$(onboarding_list_transports)
local prompt="${STR_PICK_TRANSPORT:-Choose connection transport:}"
if command -v whiptail >/dev/null 2>&1; then
local args=()
while IFS= read -r tr; do
local desc
case "$tr" in
direct-api) desc="${STR_TR_DIRECT_API:-Direct provider API}" ;;
aws-bedrock) desc="${STR_TR_AWS_BEDROCK:-AWS Bedrock}" ;;
azure-openai) desc="${STR_TR_AZURE_OPENAI:-Azure OpenAI}" ;;
google-vertex) desc="${STR_TR_GOOGLE_VERTEX:-Google Vertex AI}" ;;
local) desc="${STR_TR_LOCAL:-Local}" ;;
proxy) desc="${STR_TR_PROXY:-Proxy}" ;;
subscription) desc="${STR_TR_SUBSCRIPTION:-OAuth subscription}" ;;
*) desc="$tr" ;;
esac
args+=("$tr" "$desc" "OFF")
done <<< "$transports"
ONBOARDING_TRANSPORT=$(whiptail --title "KeiSei · Transport" --radiolist \
"$prompt" 18 70 7 "${args[@]}" 3>&1 1>&2 2>&3) || ONBOARDING_TRANSPORT="direct-api"
else
echo "" >&2
echo "$prompt" >&2
echo " ${STR_EXPLAIN_TRANSPORT:-How the agents reach the AI. subscription = log in with your plan (no API key); direct-api = your own API key. Default is fine for most.}" >&2
local i=1
declare -a opts=()
while IFS= read -r tr; do
opts+=("$tr")
echo " $i) $tr" >&2
i=$((i+1))
done <<< "$transports"
ans="$(_onb_read_choice "${#opts[@]}" "[1-${#opts[@]}, default 1]: ")"
ONBOARDING_TRANSPORT="${opts[$((ans-1))]:-direct-api}"
fi
}
onboarding_pick_provider() {
local rows; rows=$(onboarding_providers_in_transport "$ONBOARDING_TRANSPORT")
local count; count=$(echo "$rows" | wc -l | tr -d ' ')
# Если провайдер один на транспорт — авто-выбор.
if [ "$count" = "1" ]; then
ONBOARDING_PROVIDER=$(echo "$rows" | awk -F'\t' '{print $1}')
return
fi
if command -v whiptail >/dev/null 2>&1; then
local args=()
while IFS=$'\t' read -r id dn ae; do
args+=("$id" "$dn" "OFF")
done <<< "$rows"
local prompt="${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:"
ONBOARDING_PROVIDER=$(whiptail --title "KeiSei · Provider" --radiolist \
"$prompt" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \
|| ONBOARDING_PROVIDER=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}')
else
echo "" >&2
echo "${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:" >&2
echo " ${STR_EXPLAIN_PROVIDER:-Which AI service. Option 1 is the recommended default.}" >&2
declare -a ids=()
local i=1
while IFS=$'\t' read -r id dn ae; do
ids+=("$id")
echo " $i) $id$dn" >&2
i=$((i+1))
done <<< "$rows"
ans="$(_onb_read_choice "${#ids[@]}" "[1-${#ids[@]}, default 1]: ")"
ONBOARDING_PROVIDER="${ids[$((ans-1))]:-${ids[0]}}"
fi
}
onboarding_pick_model() {
# Для AWS/Azure/Vertex модели идут под parent-провайдером — мапим.
local lookup="$ONBOARDING_PROVIDER"
case "$ONBOARDING_PROVIDER" in
anthropic-bedrock) lookup="anthropic" ;;
openai-azure) lookup="openai" ;;
google-vertex) lookup="google" ;;
esac
local rows; rows=$(onboarding_models_for_provider "$lookup")
[ -z "$rows" ] && rows=$(printf "claude-sonnet-4-6\tClaude Sonnet 4.6 (fallback)\n")
# Single model → auto-select, no dead-end prompt (mirrors provider count==1).
if [ "$(printf '%s\n' "$rows" | grep -c .)" = "1" ]; then
ONBOARDING_MODEL=$(printf '%s\n' "$rows" | head -1 | awk -F'\t' '{print $1}')
return
fi
if command -v whiptail >/dev/null 2>&1; then
local args=()
while IFS=$'\t' read -r id dn; do
args+=("$id" "$dn" "OFF")
done <<< "$rows"
ONBOARDING_MODEL=$(whiptail --title "KeiSei · Model" --radiolist \
"${STR_PICK_MODEL:-Default model:}" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \
|| ONBOARDING_MODEL=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}')
else
echo "" >&2
echo "${STR_PICK_MODEL:-Models for} $lookup:" >&2
echo " ${STR_EXPLAIN_MODEL:-Default model the agents use. Option 1 is the recommended default.}" >&2
declare -a ids=()
local i=1
while IFS=$'\t' read -r id dn; do
ids+=("$id")
echo " $i) $id$dn" >&2
i=$((i+1))
done <<< "$rows"
ans="$(_onb_read_choice "${#ids[@]}" "[1-${#ids[@]}, default 1]: ")"
ONBOARDING_MODEL="${ids[$((ans-1))]:-${ids[0]}}"
fi
}
onboarding_collect_auth() {
ONBOARDING_AUTH_ENV_KEYS=()
ONBOARDING_AUTH_ENV_VALUES=()
local ae; ae=$(onboarding_auth_env_for_provider "$ONBOARDING_PROVIDER")
[ -z "$ae" ] || [ "$ae" = "_" ] && return # local / subscription — нет ключей
echo "" >&2
echo "${STR_AUTH_INTRO:-Auth for} $ONBOARDING_PROVIDER ($ae):" >&2
echo "${STR_AUTH_PROMPT:-Enter values (Enter — leave empty, fill later).}" >&2
local IFS_old="$IFS"; IFS=','
for key in $ae; do
IFS="$IFS_old"
local cur="${!key:-}"
local prompt_msg="$key"
[ -n "$cur" ] && prompt_msg="$key ${STR_AUTH_CURRENT_HINT:-(current: <hidden>)}"
read -r -s -p " $prompt_msg = " val
echo "" >&2
if [ -n "$val" ]; then
ONBOARDING_AUTH_ENV_KEYS+=("$key")
ONBOARDING_AUTH_ENV_VALUES+=("$val")
elif [ -n "$cur" ]; then
ONBOARDING_AUTH_ENV_KEYS+=("$key")
ONBOARDING_AUTH_ENV_VALUES+=("$cur")
fi
done
IFS="$IFS_old"
}