nanobot-voice-interface/start.sh

183 lines
5.8 KiB
Bash
Raw Normal View History

2026-02-28 22:12:04 -05:00
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
if [[ ! -d ".venv" ]]; then
python3 -m venv .venv
fi
source .venv/bin/activate
pip install -r requirements.txt >/dev/null
2026-03-14 20:21:44 -04:00
FRONTEND_DIR="${SCRIPT_DIR}/frontend"
: "${FRONTEND_BUILD_ON_START:=1}"
resolve_frontend_runner() {
if command -v bun >/dev/null 2>&1; then
echo "bun"
return 0
fi
if [[ -x "${HOME}/.bun/bin/bun" ]]; then
echo "${HOME}/.bun/bin/bun"
return 0
fi
if command -v npm >/dev/null 2>&1; then
echo "npm"
return 0
fi
return 1
}
build_frontend_bundle() {
if [[ ! -f "${FRONTEND_DIR}/package.json" ]]; then
echo "Frontend package.json not found at ${FRONTEND_DIR}" >&2
return 1
fi
local runner
if ! runner="$(resolve_frontend_runner)"; then
echo "Unable to build frontend: install Bun or npm first." >&2
return 1
fi
if [[ ! -d "${FRONTEND_DIR}/node_modules" ]]; then
echo "Frontend dependencies are missing at ${FRONTEND_DIR}/node_modules." >&2
if [[ "${runner}" == "bun" || "${runner}" == "${HOME}/.bun/bin/bun" ]]; then
(
cd "${FRONTEND_DIR}"
"${runner}" install --frozen-lockfile
)
else
echo "Install frontend dependencies before starting the server." >&2
return 1
fi
fi
echo "Building frontend bundle from ${FRONTEND_DIR}"
if [[ "${runner}" == "npm" ]]; then
(
cd "${FRONTEND_DIR}"
npm run build
)
else
(
cd "${FRONTEND_DIR}"
"${runner}" run build
)
fi
}
if [[ "${FRONTEND_BUILD_ON_START}" == "1" ]]; then
build_frontend_bundle
fi
2026-02-28 22:12:04 -05:00
# Optional local voice settings. Example file: .env.voice
if [[ -f ".env.voice" ]]; then
set -a
# shellcheck disable=SC1091
source ".env.voice"
set +a
fi
# Nanobot command defaults (prefer Nanobot's own virtualenv interpreter).
: "${NANOBOT_WORKDIR:=${HOME}/nanobot}"
if [[ -z "${NANOBOT_COMMAND:-}" ]]; then
if [[ -x "${NANOBOT_WORKDIR}/.venv/bin/python" ]]; then
NANOBOT_COMMAND="${NANOBOT_WORKDIR}/.venv/bin/python -m nanobot agent --no-markdown"
elif [[ -x "${NANOBOT_WORKDIR}/venv/bin/python" ]]; then
NANOBOT_COMMAND="${NANOBOT_WORKDIR}/venv/bin/python -m nanobot agent --no-markdown"
fi
fi
export NANOBOT_WORKDIR NANOBOT_COMMAND
: "${NANOBOT_SUPPRESS_NOISY_UI:=1}"
: "${NANOBOT_OUTPUT_DEDUP_WINDOW_S:=1.5}"
export NANOBOT_SUPPRESS_NOISY_UI NANOBOT_OUTPUT_DEDUP_WINDOW_S
# Host voice pipeline env vars (safe defaults).
: "${HOST_STT_PROVIDER:=faster-whisper}"
: "${HOST_STT_COMMAND:=}"
2026-03-04 08:20:42 -05:00
: "${HOST_STT_MODEL:=distil-large-v3}"
2026-02-28 22:12:04 -05:00
: "${HOST_STT_DEVICE:=auto}"
: "${HOST_STT_COMPUTE_TYPE:=int8}"
: "${HOST_STT_LANGUAGE:=en}"
: "${HOST_STT_BEAM_SIZE:=2}"
: "${HOST_STT_BEST_OF:=2}"
: "${HOST_STT_VAD_FILTER:=0}"
: "${HOST_STT_TEMPERATURE:=0.0}"
: "${HOST_STT_LOG_PROB_THRESHOLD:=-1.0}"
: "${HOST_STT_NO_SPEECH_THRESHOLD:=0.6}"
: "${HOST_STT_COMPRESSION_RATIO_THRESHOLD:=2.4}"
: "${HOST_STT_INITIAL_PROMPT:=Transcribe brief spoken English precisely. Prefer common words over sound effects.}"
2026-03-04 08:20:42 -05:00
: "${HOST_STT_REPETITION_PENALTY:=1.0}"
: "${HOST_STT_HALLUCINATION_SILENCE_THRESHOLD:=}"
2026-02-28 22:12:04 -05:00
: "${HOST_TTS_PROVIDER:=supertonic}"
: "${HOST_TTS_COMMAND:=}"
: "${SUPERTONIC_MODEL:=supertonic-2}"
2026-03-04 08:20:42 -05:00
: "${SUPERTONIC_VOICE_STYLE:=F1}"
2026-02-28 22:12:04 -05:00
: "${SUPERTONIC_LANG:=en}"
2026-03-04 08:20:42 -05:00
: "${SUPERTONIC_TOTAL_STEPS:=8}"
: "${SUPERTONIC_SPEED:=1.5}"
2026-02-28 22:12:04 -05:00
: "${SUPERTONIC_INTRA_OP_THREADS:=1}"
: "${SUPERTONIC_INTER_OP_THREADS:=1}"
: "${SUPERTONIC_AUTO_DOWNLOAD:=1}"
: "${HOST_STT_MIN_PTT_MS:=220}"
: "${HOST_STT_SEGMENT_QUEUE_SIZE:=2}"
: "${HOST_STT_BACKLOG_NOTICE_INTERVAL_S:=6.0}"
: "${HOST_STT_SUPPRESS_DURING_TTS:=1}"
: "${HOST_STT_SUPPRESS_MS_AFTER_TTS:=300}"
: "${HOST_RTC_OUTBOUND_LEAD_IN_MS:=120}"
: "${HOST_RTC_OUTBOUND_IDLE_S:=0.6}"
export HOST_STT_PROVIDER HOST_STT_COMMAND HOST_STT_MODEL HOST_STT_DEVICE
export HOST_STT_COMPUTE_TYPE HOST_STT_LANGUAGE HOST_STT_BEAM_SIZE HOST_STT_BEST_OF HOST_STT_VAD_FILTER
export HOST_STT_TEMPERATURE HOST_STT_LOG_PROB_THRESHOLD HOST_STT_NO_SPEECH_THRESHOLD
export HOST_STT_COMPRESSION_RATIO_THRESHOLD
2026-03-04 08:20:42 -05:00
export HOST_STT_INITIAL_PROMPT HOST_STT_REPETITION_PENALTY HOST_STT_HALLUCINATION_SILENCE_THRESHOLD
2026-02-28 22:12:04 -05:00
export HOST_TTS_PROVIDER HOST_TTS_COMMAND
export SUPERTONIC_MODEL SUPERTONIC_VOICE_STYLE SUPERTONIC_LANG
export SUPERTONIC_TOTAL_STEPS SUPERTONIC_SPEED
export SUPERTONIC_INTRA_OP_THREADS SUPERTONIC_INTER_OP_THREADS SUPERTONIC_AUTO_DOWNLOAD
2026-03-04 08:20:42 -05:00
export HOST_STT_MIN_PTT_MS HOST_STT_SEGMENT_QUEUE_SIZE
2026-02-28 22:12:04 -05:00
export HOST_STT_BACKLOG_NOTICE_INTERVAL_S
export HOST_STT_SUPPRESS_DURING_TTS HOST_STT_SUPPRESS_MS_AFTER_TTS
export HOST_RTC_OUTBOUND_LEAD_IN_MS HOST_RTC_OUTBOUND_IDLE_S
: "${UVICORN_HOST:=0.0.0.0}"
: "${UVICORN_PORT:=8000}"
: "${ENABLE_HTTPS:=1}"
: "${SSL_DAYS:=365}"
: "${SSL_CERT_FILE:=.certs/local-cert.pem}"
: "${SSL_KEY_FILE:=.certs/local-key.pem}"
if [[ "$ENABLE_HTTPS" == "1" ]]; then
mkdir -p "$(dirname "$SSL_CERT_FILE")"
mkdir -p "$(dirname "$SSL_KEY_FILE")"
if [[ ! -f "$SSL_CERT_FILE" || ! -f "$SSL_KEY_FILE" ]]; then
LOCAL_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
SAN_ENTRIES="DNS:localhost,IP:127.0.0.1"
if [[ -n "${LOCAL_IP:-}" ]]; then
SAN_ENTRIES="${SAN_ENTRIES},IP:${LOCAL_IP}"
fi
echo "Generating local TLS certificate at '$SSL_CERT_FILE' (SAN: ${SAN_ENTRIES})"
openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days "$SSL_DAYS" \
-keyout "$SSL_KEY_FILE" \
-out "$SSL_CERT_FILE" \
-subj "/CN=localhost" \
-addext "subjectAltName=${SAN_ENTRIES}" \
-addext "keyUsage=digitalSignature,keyEncipherment" \
-addext "extendedKeyUsage=serverAuth"
fi
echo "Starting HTTPS server on https://localhost:${UVICORN_PORT}"
exec uvicorn app:app --host "$UVICORN_HOST" --port "$UVICORN_PORT" \
--ssl-certfile "$SSL_CERT_FILE" \
--ssl-keyfile "$SSL_KEY_FILE"
fi
echo "Starting HTTP server on http://localhost:${UVICORN_PORT}"
exec uvicorn app:app --host "$UVICORN_HOST" --port "$UVICORN_PORT"