Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions LifeOS/install/LIFEOS/PULSE/PULSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,68 @@ da = false
[voice]
enabled = true

# ── Voice provider chain (optional) ──
#
# Left unset, voice behaves exactly as it always has: ElevenLabs, keyed by
# ELEVENLABS_API_KEY, voices resolved from settings.json daidentity.voices.
#
# Define `[[voice.providers]]` blocks to get an ordered failover chain instead.
# The first provider that returns audio speaks; any failure (non-2xx, timeout,
# connection refused) falls through to the next. If every provider fails, the
# desktop notification still goes out and /notify answers 502 — the daemon
# never dies because a TTS box is down.
#
# A chain brings its own credentials, so an install with no ElevenLabs key at
# all is valid as long as the chain does not contain an `elevenlabs` entry.
#
# Local-first, hosted fallback. The local half here is Kokoro-FastAPI
# (`ghcr.io/remsky/kokoro-fastapi-cpu:v0.7.2`, default port 8880) — pin the tag,
# `latest` moves weekly:
#
# [[voice.providers]]
# type = "openai-compatible"
# base_url = "http://127.0.0.1:8880" # server root, or its /v1 base
# voice = "am_michael" # provider's own voice ID
# model = "kokoro"
# # response_format = "mp3" # mp3 (default), wav, opus, flac, aac
# # # (pcm accepted but unplayable by stock players — see providers.ts)
# # timeout_ms = 10000 # generation budget
# # health_path = "/health" # liveness path, relative to the root
# # health_timeout_ms = 2000 # liveness budget
# # api_key = "${LOCAL_TTS_KEY}" # sent as a bearer token when set
# # send_stream_flag = false # omit Kokoro's stream:false for a STRICT
# # # OpenAI endpoint that rejects the field
# # # (default true — keep it for Kokoro)
#
# [[voice.providers]]
# type = "elevenlabs"
# # voice = "..." # overrides the resolved voice ID here only
# # model = "eleven_turbo_v2_5"
# # api_key = "${ELEVENLABS_API_KEY}" # defaults to the module-level key
#
# Each rung is probed for liveness before it is asked to speak — a cheap GET
# of the server root's /health (2s budget), where ANY HTTP response counts as
# reachable. A 404 is fine: non-Kokoro OpenAI-shaped servers have no /health,
# and the question is only whether anything is listening. Connection refused
# and timeout are the hard failures, and skipping those rungs cheaply is the
# point — otherwise a dead first provider burns its full 10s generation budget
# on every single notification. ElevenLabs is not probed over the network; its
# liveness is whether a key is configured, the same test the pre-chain path made.
#
# `openai-compatible` targets POST {base_url}/v1/audio/speech with
# {model, input, voice, response_format, speed, stream} and expects RAW AUDIO
# BYTES back, never JSON — the OpenAI speech contract, which Kokoro-FastAPI and
# LocalAI both implement. A JSON body on a 200 is treated as a failure.
# `stream: false` is sent on every request: Kokoro-FastAPI streams by default,
# and a streamed reply returns 200 before generation finishes, so a failure
# mid-sentence would arrive as truncated audio instead of falling through.
# The 10s generation default reflects Kokoro on CPU at ~1.3-2x realtime.
#
# Kokoro voice IDs are `{lang}{gender}_{name}`. On the male English side its
# own quality grades put `am_fenrir`, `am_michael` and `am_puck` at the top and
# `am_adam` at the bottom; blends are expressed in the same `voice` string,
# weighted — `voice = "am_fenrir(2)+am_michael(1)"`.

[imessage]
enabled = false

Expand Down
Loading