pro/translatert/decisions
Decisions — TranslateRT
ADR légers · ordre antéchronologique Liens : Architecture · Journal
2026-06-01 — Deltas gpt-realtime-translate sont cumulatifs, pas incrémentaux
Contexte : La doc OpenAI Realtime indique textContent += event.delta (incrémental). Backend utilisait +=. Résultat : concaténation de tous les états intermédiaires dans e.full → duplication visible.
Décision : Utiliser = (assign) au lieu de += dans handleInputTranscript et handleOutputTranscript.
Raison : L’API gpt-realtime-translate (endpoint /v1/realtime/translations) envoie dans chaque delta le texte complet depuis le début de l’utterance (rolling cumulative). Le dernier delta reçu = texte courant complet.
Alternatives écartées : Garder += (doc officielle) — cassé en pratique. Utiliser les events done — se déclenchent par mot, pas par phrase, rendant l’approche inutilisable.
Voir aussi : Journal
2026-06-01 — Debounce display pour éviter affichage de mots partiels
Contexte : Avec =, chaque delta rapide montre un texte partiel (ex: “Mill” avant “Millenium”) avant que le delta suivant arrive. Affichage mot-par-mot non désirable.
Décision : Debounce 400ms dans EventListener.tsx, 200ms par langue dans DisplayView.tsx via useRef timers.
Raison : OpenAI envoie ~1 delta / 50-100ms pendant la parole. Un debounce > temps inter-déltas assure que l’affichage ne se met à jour qu’aux pauses naturelles.
Alternatives écartées : Debounce 200ms (trop court, visible encore). done events (trop fréquents). Affichage live sans debounce (flashs visibles).
Voir aussi : Journal
2026-06-01 — Transcript WS émet e.full au lieu de e.delta
Contexte : Backend émettait chaque delta OpenAI brut via WS. Frontend accumulait avec " " fixe → espaces parasites dans les mots sur tokens sub-mots.
Décision : pipeline.ts envoie text: e.full (texte cumulé complet) plutôt que text: e.delta.
Raison : Backend accumule déjà dans this.metrics.outputTranscript — e.full toujours cohérent. Frontend simplifié sans accumulation manuelle.
Alternatives écartées : Concaténation sans espace (prev.join("") + event.text) — fragile, dépend du formatage OpenAI.
2026-06-01 — webAudioMix: false dans LiveKit Room
Contexte : Son métallique/flanging côté spectateur.
Décision : webAudioMix: false dans constructeur Room (EventListener.tsx).
Raison : webAudioMix: true + audioTrack.attach() = deux chemins lecture simultanés → comb filter.
Alternatives écartées : webAudioMix: true sans attach() — joue tous les tracks, impossible sélectionner une seule langue.
2026-05-xx — Packaging Tauri V2
Contexte : Déploiement Mac mini = Homebrew + launchd multi-process, fragile.
Décision : App Tauri V2 avec Node.js (SEA), livekit-server, caddy comme sidecars.
Raison : Un seul .app, SIGTERM-stoppable, config externalisée TRANSLATERT_HOME.
Alternatives écartées : Docker (incompatible CoreAudio/DVS), Electron (trop lourd).
MVP — Décisions fondatrices
| Décision | Choix | Raison |
|---|---|---|
| Capture audio | ffmpeg avfoundation / dshow | CoreAudio direct |
| IA | OpenAI Realtime S2S | Latence < 3s |
| SFU | LiveKit | WebRTC natif |
| Frontend | Next.js export statique | Pas SSR |
| Backend | Monolithe Fastify | MVP simple |
| DB | Aucune | État mémoire |
| TLS | Caddy DNS-01 | LAN HTTPS |
| Auth admin | Désactivée | LAN privé |
| Format audio | PCM16 24kHz mono | Opus compatible |
| Frame LiveKit | 480 samples (20ms) | Standard VoIP |