Phase 24, the fr half: langpack, Hunspell dictionary, Piper voice, and the lexicon coverage that turned out to have been measured already (63.1%, better than pt-PT's 62.1%). No migration; not deployed. The plan recorded that build_ptpt_dictionary.py "generalizes" to French. It did not. It handled single-character flags and plain PFX/SFX and stopped on everything else, and fr.aff uses four of the things it stopped on. FLAG long is the dangerous one: French flags are two characters, so the old reader's set(flagstr) yields a bag of unrelated letters and expands every entry through the wrong paradigm without ever erroring. Plus continuation flags (French really does affix an affixed form), NEEDAFFIX on 68,075 of 84,140 stems, and FULLSTRIP. Renamed build_hunspell_dictionary.py with a per-language profile, asserting that CIRCUMFIX and FORBIDDENWORD are still unused rather than assuming it — and it rebuilds pt-PT byte-identical to the shipped asset, which is the only thing that makes "generalized" a claim rather than a hope. Elision was decided by building both halves and measuring. Keeping l'arbre and its thirty-three siblings: 3,159,832 forms, 8.25 MB gzipped. Dropping them: 473,326 and 1.19 MB. They are not new words, but the tokenizer keeps internal apostrophes, so they genuinely would have been underlined — so they moved out of the dictionary into withElision, which splits at a known clitic and still requires the remainder to be a word (l'zzzz stays flagged). Real nspell: 369 ms and 74 MB, against pt-PT's 842 ms and 139 MB, on the larger language. Where the regional trap lives is the mirror image of Portuguese's: every fr_* Piper voice is fr_FR and Debian's fr_FR/fr_CA/fr_BE dictionaries are one shared word list, so nothing can be quietly wrong about the country and the whole decision sits in the copy. What French has instead is the 1990 reform, packaged three ways; comprehensive ships, because Petal never corrects her French and coût and cout are both correct. Then the interim review pass, at the user's suggestion and explicitly "for now": four models read each Latin pack independently, and only findings at least two of them reached on their own were applied — five per pack. It earned its keep on the pack that was already live. pt-PT was carrying pre-Acordo spellings (adjectivos, actualmente) in a file whose own header commits to post-Acordo, plus Brazilian decepção, because the Phase 21 greps checked for Brazilian vocabulary and never checked the pack against its own spelling policy. That grep now exists and was confirmed to fail on the old text before being kept. Where reviewers agreed a line was wrong but split on the fix, the wording is mine and the reasoning is in BUILD_PLAN rather than averaged away. Still owed, and both packs now say so precisely: a quorum of models agreeing is agreement, not authority. No native speaker has read either pack, and none of this has been seen in a browser. go build/vet/test clean, tsc, vite build, vitest 190/190. Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
83 lines
4.0 KiB
Bash
83 lines
4.0 KiB
Bash
# Petal — production environment for the parodia.dev VPS.
|
|
# Copy to the stack directory as `.env` (docker-compose.yml reads it via
|
|
# env_file) and fill in the model names. Values the image already fixes
|
|
# (PORT, DATABASE_PATH, IMAGE_DIR, TTS_CACHE_DIR, TTS endpoints) are set in
|
|
# docker-compose.yml, not here.
|
|
|
|
# --- Routing -----------------------------------------------------------------
|
|
# Must match the DNS A record and the Traefik Host() rule.
|
|
PETAL_HOST=petal.parodia.dev
|
|
# Absolute origin the app knows itself by. Phase 16's OIDC redirect URI is
|
|
# built from this, so it has to be the real public HTTPS origin.
|
|
BASE_URL=https://petal.parodia.dev
|
|
|
|
# The companion's bedtime nag and the night theme read the container clock.
|
|
TZ=Europe/Lisbon
|
|
|
|
# The container runs as this uid/gid so it can write the ./data bind mount.
|
|
# Set both to the output of `id -u` / `id -g` for the account owning the stack
|
|
# directory. Wrong values show up as "unable to open database file (14)".
|
|
PETAL_UID=1001
|
|
PETAL_GID=1001
|
|
|
|
# --- Interim edge gate (delete when Phase 16 auth lands) ---------------------
|
|
# Petal has no authentication of its own yet — StaticResolver hands every
|
|
# request the same local user — so Traefik holds the door with basic auth until
|
|
# the OIDC flow exists. user:bcrypt-hash, as produced by:
|
|
# htpasswd -nbB petal 'your-password'
|
|
# /api/health is deliberately exempt (its own router) so monitoring still works.
|
|
PETAL_BASIC_AUTH=
|
|
|
|
# --- LLM (millenia, over headscale) ------------------------------------------
|
|
# The only cross-VPN dependency. Petal degrades warmly when it's unreachable:
|
|
# spell check, gloss, garden, search, export and read-aloud all keep working and
|
|
# the status bar shows 小助手在休息 · Petal's helper is resting.
|
|
#
|
|
# 100.64.0.2 is millenia on the headscale network. vLLM must be bound to that
|
|
# interface (NOT 0.0.0.0 — this host is public); see deploy/README.md.
|
|
LLM_BACKEND=vllm
|
|
LLM_ENDPOINT=http://100.64.0.2:8000
|
|
LLM_MODEL=
|
|
LLM_CHAT_MODEL=
|
|
# 30s is the local-network default. Over WAN + VPN, with the voice and
|
|
# collocation passes sending a whole document, that truncates real work — the
|
|
# request is a hard deadline on Complete, and a timeout surfaces as the same
|
|
# warm 502 as an unreachable model. 90s leaves headroom without letting a
|
|
# genuinely wedged backend hang the pass forever.
|
|
LLM_TIMEOUT=90s
|
|
|
|
# --- Read-aloud (Piper sidecars) ---------------------------------------------
|
|
# Endpoints are wired in docker-compose.yml; these pick the voice each sidecar
|
|
# loads. Changing one means recreating that container so it downloads the model.
|
|
#
|
|
# A language is routable only when both halves are set — a TTS_ENDPOINT_XX with
|
|
# no TTS_VOICE_XX reads as "no voice for this language" and the browser's own
|
|
# synthesizer takes over, rather than as an instance that errors on every
|
|
# request. Adding es is a compose service plus a pair of lines here.
|
|
#
|
|
# pt_PT-tugão-medium is the only European Portuguese voice Piper ships; every
|
|
# other pt model in the catalogue is Brazilian. French has the opposite
|
|
# property — every fr voice in the catalogue is fr_FR — so there is no wrong
|
|
# country to land on and no non-ASCII name to trip the downloader.
|
|
TTS_VOICE_EN=en_US-amy-medium
|
|
TTS_VOICE_ZH=zh_CN-huayan-medium
|
|
TTS_VOICE_PT=pt_PT-tugão-medium
|
|
TTS_VOICE_FR=fr_FR-siwis-medium
|
|
TTS_AUDIO_FORMAT=mp3
|
|
TTS_TIMEOUT=15s
|
|
|
|
# --- Auth (Authentik OIDC) ---------------------------------------------------
|
|
# Authentik already runs on this host. Set all three and Petal authenticates
|
|
# for itself; leave any unset and it falls back to the single `local` user
|
|
# (which on a public host means the Traefik basic-auth gate must stay).
|
|
#
|
|
# AUTHENTIK_URL is the provider's issuer, and the redirect URI to register in
|
|
# Authentik is https://petal.parodia.dev/auth/callback.
|
|
# AUTHENTIK_URL=https://auth.parodia.dev/application/o/petal/
|
|
# AUTHENTIK_CLIENT_ID=petal
|
|
# AUTHENTIK_CLIENT_SECRET=
|
|
#
|
|
# Who may sign in: comma-separated subject ids and/or emails. Empty = anyone
|
|
# Authentik authenticates, which is wider than this instance wants.
|
|
# PETAL_ALLOWED_SUBS=
|