Introduction à l'architecture
Version de l’application : v0.1.1 (fork : DarrellThomas/en-parlant) Stack : Tauri v2 (Rust) + React 19 (TypeScript) + Vite
Qu’est-ce que Tauri ?
Section intitulée « Qu’est-ce que Tauri ? »Tauri est un framework pour créer des applications de bureau. Au lieu d’embarquer un navigateur complet comme le fait Electron, Tauri utilise la webview intégrée au système d’exploitation pour l’interface et un processus Rust pour le backend. Le résultat est un binaire compact et rapide.
Les deux parties communiquent via IPC (communication inter-processus) :
+---------------------------+ IPC +---------------------------+| Rust Backend | <--------------> | React/TS Frontend || | (commands + | || - Chess engines (UCI) | events) | - Chessboard UI || - SQLite database | | - Analysis panels || - File I/O | | - Settings || - PGN parsing | | - Game tree navigation || - Position search index | | - TTS narration |+---------------------------+ +---------------------------+ src-tauri/src/ src/Le côté Rust : src-tauri/src/
Section intitulée « Le côté Rust : src-tauri/src/ »Rust gère tout ce qui nécessite de la performance ou un accès au système.
Point d’entrée : main.rs
Section intitulée « Point d’entrée : main.rs »Enregistre environ 50 commandes que le frontend peut appeler, initialise les plugins (système de fichiers, dialogues, HTTP, shell, journalisation, mises à jour) et lance la fenêtre de l’application.
Les commandes sont définies à l’aide d’une macro :
#[tauri::command]async fn get_best_moves(id: String, engine: String, ...) -> Result<...> { // spawn UCI engine, return analysis}Le crate specta génère automatiquement les définitions de types TypeScript à partir de ces fonctions Rust, offrant ainsi au frontend une sécurité de typage complète sans aucun effort manuel.
Modules principaux
Section intitulée « Modules principaux »| Module | Rôle |
|---|---|
db/mod.rs | Base de données SQLite via Diesel ORM — requêtes de parties, statistiques de joueurs, imports, recherche de positions |
game.rs | Moteur de jeu en direct — gère les parties moteur-contre-humain et moteur-contre-moteur, contrôles de temps, validation des coups |
chess.rs | Analyse par moteur — lance les moteurs UCI, retransmet les meilleurs coups au frontend via des événements |
engine/ | Implémentation du protocole UCI — lancement de processus, pipes stdin/stdout, support multi-PV |
pgn.rs | Lecture/écriture/tokenisation de fichiers PGN |
opening.rs | Recherche du nom d’ouverture à partir du FEN (données binaires intégrées à l’application) |
puzzle.rs | Base de données de puzzles Lichess — accès aléatoire par mémoire mappée |
fs.rs | Téléchargement de fichiers avec reprise, configuration des permissions d’exécution |
sound.rs | Serveur HTTP local pour le streaming audio (contournement audio sous Linux) |
tts.rs | TTS système via speech-dispatcher (Linux) / API vocales natives de l’OS, plus gestion du serveur KittenTTS |
oauth.rs | Flux OAuth2 pour la liaison des comptes Lichess/Chess.com |
Patrons de conception
Section intitulée « Patrons de conception »- Asynchrone partout : runtime Tokio, E/S non bloquantes
- État concurrent :
DashMap(HashMap concurrent) pour les processus de moteurs, connexions à la base de données, caches - Pool de connexions : r2d2 gère les pools de connexions SQLite
- Recherche par mémoire mappée : recherche de positions via un index binaire mmap’d pour des résultats instantanés
- Flux d’événements : Rust émet des événements (meilleurs coups, tic-tac de l’horloge, fin de partie) que React écoute en temps réel
Le côté React/TypeScript : src/
Section intitulée « Le côté React/TypeScript : src/ »Pipeline de build : Vite
Section intitulée « Pipeline de build : Vite »vite.config.ts configure :
- Plugin React avec le compilateur Babel
- Plugin TanStack Router — génère automatiquement l’arbre de routes à partir du dossier
routes/ - Vanilla Extract — CSS-in-JS sans runtime
- Alias de chemin :
@correspond à./src - Serveur de développement sur le port 1420
Flux de build :
pnpm dev → Vite on :1420 + Tauri opens webview pointing to itpnpm build → tsc (typecheck) → vite build (bundle to dist/) → tauri build (native binary)Point d’entrée : App.tsx
Section intitulée « Point d’entrée : App.tsx »Le composant racine :
- Initialise les plugins Tauri (log, process, updater)
- Charge les préférences utilisateur depuis des atomes persistants
- Configure le thème de l’UI Mantine
- Enregistre le routeur
- Vérifie les mises à jour de l’application
Gestion de l’état
Section intitulée « Gestion de l’état »Atomes Jotai (src/state/atoms.ts) — état réactif léger :
| Catégorie | Exemples |
|---|---|
| Onglets | tabsAtom, activeTabAtom (interface multi-documents) |
| Répertoires | storedDocumentDirAtom, storedDatabasesDirAtom |
| Préférences d’interface | primaryColorAtom, fontSizeAtom, pieceSetAtom |
| Moteur | engineMovesFamily, engineProgressFamily (par onglet via atomFamily) |
| TTS | ttsEnabledAtom, ttsProviderAtom, ttsVoiceIdAtom, ttsVolumeAtom, ttsSpeedAtom, ttsLanguageAtom |
Les atomes utilisant atomWithStorage() sont automatiquement persistés dans le localStorage.
Stores Zustand pour l’état complexe du domaine :
src/state/store/tree.ts— navigation dans l’arbre de partie, ramifications de coups, annotations, commentaires. Utilise Immer pour les mises à jour immuables.src/state/store/database.ts— filtres de la vue base de données, partie sélectionnée, pagination
Routage : TanStack Router
Section intitulée « Routage : TanStack Router »Routage basé sur les fichiers dans src/routes/ :
routes/ __root.tsx # Root layout (AppShell, menu bar) index.tsx # Home/dashboard databases/ # Database browsing accounts.tsx # Lichess/Chess.com accounts settings.tsx # App preferences engines.tsx # Engine managementComposants : src/components/
Section intitulée « Composants : src/components/ »| Groupe | Fonction |
|---|---|
boards/ | Échiquier (chessground), saisie de coups, barre d’évaluation, affichage de l’analyse, modale de promotion, dessin de flèches |
panels/ | Panneaux latéraux : analyse du moteur (BestMoves), recherche de position dans la base de données, édition d’annotations, informations sur la partie, mode d’entraînement |
databases/ | Interface base de données : tableau de parties, tableau de joueurs, fiches détaillées, filtrage |
settings/ | Formulaires de préférences, chemins des moteurs, paramètres TTS |
home/ | Cartes de comptes, interface d’import |
common/ | Éléments partagés : TreeStateContext, affichage du matériel, icône de haut-parleur pour les commentaires |
tabs/ | Barre multi-onglets |
Comment le frontend appelle Rust
Section intitulée « Comment le frontend appelle Rust »Commandes (requête/réponse)
Section intitulée « Commandes (requête/réponse) »Specta génère les bindings TypeScript dans src/bindings/generated.ts :
// Auto-generated from Rust #[tauri::command] functionsexport const commands = { async getBestMoves(id, engine, tab, goMode, options) { return await TAURI_INVOKE("get_best_moves", { id, engine, tab, goMode, options }); }, // ~50 more commands...}Les composants React les appellent comme des fonctions asynchrones classiques :
import { commands } from "@/bindings";const result = await commands.getBestMoves(id, engine, tab, goMode, options);Événements (flux continu, de Rust vers React)
Section intitulée « Événements (flux continu, de Rust vers React) »Pour les données en temps réel (analyse du moteur, tic-tac de l’horloge, coups de jeu) :
Rust: app.emit("best_moves_payload", BestMovesPayload { depth: 24, ... }) ↓React: listen("best_moves_payload", (event) => updateBestMoves(event.payload))Plugins Tauri
Section intitulée « Plugins Tauri »L’application utilise plusieurs plugins officiels pour l’accès au système :
| Plugin | Fonction |
|---|---|
@tauri-apps/plugin-fs | Lecture/écriture de fichiers |
@tauri-apps/plugin-dialog | Sélecteurs de fichiers, boîtes de dialogue |
@tauri-apps/plugin-http | Client HTTP (téléchargement de moteurs, TTS cloud) |
@tauri-apps/plugin-shell | Exécution de moteurs UCI |
@tauri-apps/plugin-updater | Vérification automatique des mises à jour |
@tauri-apps/plugin-log | Journalisation structurée |
@tauri-apps/plugin-os | Détection CPU/RAM |
Synthèse vocale (TTS) : introduction
Section intitulée « Synthèse vocale (TTS) : introduction »En Parlant~ peut lire à voix haute les coups d’échecs et les commentaires lorsque vous parcourez une partie. Cette section explique comment le système TTS est construit — le pipeline de prétraitement, l’architecture des fournisseurs et la stratégie de mise en cache. Pour les instructions de configuration, consultez les guides TTS dans le menu TTS.
Comment fonctionne le TTS (version courte)
Section intitulée « Comment fonctionne le TTS (version courte) »La synthèse vocale convertit du texte écrit en audio parlé. Les systèmes TTS modernes reposent sur des réseaux de neurones profonds entraînés sur des milliers d’heures de parole humaine. Le modèle apprend la relation entre le texte (lettres, mots, ponctuation) et les caractéristiques acoustiques de la parole (hauteur, rythme, accentuation, pauses respiratoires). Au moment de l’inférence, vous envoyez du texte et récupérez une forme d’onde audio.
Il existe deux grandes approches :
-
TTS cloud — le texte est envoyé à un serveur distant (Google, ElevenLabs, etc.), qui exécute un grand réseau de neurones sur du matériel GPU et renvoie l’audio. Qualité excellente, mais nécessite une connexion internet et engendre des coûts par requête (bien que la plupart des fournisseurs proposent des paliers gratuits).
-
TTS local — un modèle s’exécute directement sur votre machine. Pas besoin d’internet, pas de coût par requête, et votre texte ne quitte jamais votre ordinateur. Les modèles open source récents (comme Kokoro et Piper) ont considérablement réduit l’écart de qualité.
Si vous êtes curieux de savoir comment les modèles TTS fonctionnent en détail, HuggingFace (huggingface.co) héberge des centaines de modèles open source de synthèse vocale que vous pouvez explorer, télécharger et exécuter localement. Recherchez « text-to-speech » pour trouver des modèles allant d’options légères adaptées au CPU à des modèles de recherche à la pointe de l’état de l’art.
L’architecture des fournisseurs
Section intitulée « L’architecture des fournisseurs »L’implémentation principale du TTS se trouve dans src/utils/tts.ts. Elle est conçue autour d’une interface publique unique (speakText()) avec des backends interchangeables. Le reste de l’application ne sait ni ne se soucie du fournisseur actif — il appelle simplement speakText() et l’audio est produit.
Cinq fournisseurs sont pris en charge :
| Fournisseur | Type | Backend |
|---|---|---|
| ElevenLabs | Cloud | Voix neuronales via API REST. Retourne du MP3. |
| Google Cloud TTS | Cloud | Voix WaveNet via API REST. Retourne du MP3 encodé en base64. |
| KittenTTS | Local | Serveur TTS intégré, démarré automatiquement par le backend Rust. Communique via HTTP sur localhost. |
| OpenTTS | Local | Serveur TTS auto-hébergé. Supporte de nombreux moteurs (espeak, MaryTTS, Piper, etc.). |
| System TTS | Local | Moteur vocal natif de l’OS via commandes Rust/Tauri (speech-dispatcher sous Linux, SAPI sous Windows, AVSpeechSynthesizer sous macOS). |
La sélection du fournisseur est stockée dans un seul atome Jotai (ttsProviderAtom). Changer de fournisseur est instantané — modifiez l’atome, et le prochain appel à speakText() sera dirigé vers le nouveau backend.
Le défi : la notation échiquéenne n’est pas de l’anglais
Section intitulée « Le défi : la notation échiquéenne n’est pas de l’anglais »Les coups d’échecs sont écrits en Notation Algébrique Standard (SAN) : Nf3, Bxe5+, O-O-O, e8=Q#. Si vous envoyez cela directement à un moteur TTS, vous obtenez du charabia — il pourrait essayer de prononcer « Nf3 » comme un mot, ou lire « O-O-O » comme « oh oh oh ».
La solution est un pipeline de prétraitement qui traduit la notation échiquéenne en langage naturel avant qu’elle n’atteigne le moteur TTS :
SAN Input → Preprocessing → Spoken Output─────────────────────────────────────────────────────"Nf3" → sanToSpoken() → "Knight f3""Bxe5+" → sanToSpoken() → "Bishop takes e5, check""O-O-O" → sanToSpoken() → "castles queenside""e8=Q#" → sanToSpoken() → "e8 promotes to Queen, checkmate"La fonction sanToSpoken() utilise la correspondance par expressions régulières pour décomposer toute chaîne SAN en ses composants (pièce, désambiguïsation, capture, destination, promotion, échec/mat) et les réassemble en langage naturel à partir d’un tableau de vocabulaire.
Support multilingue
Section intitulée « Support multilingue »Le vocabulaire échiquéen est traduit dans de nombreuses langues (anglais, français, espagnol, allemand, japonais, russe, chinois, coréen, et bien d’autres). La table CHESS_VOCAB associe chaque terme :
English: "Knight takes e5, check"French: "Cavalier prend e5, échec"German: "Springer schlägt e5, Schach"Japanese: "ナイト テイクス e5, チェック"Russian: "Конь берёт e5, шах"Le paramètre de langue détermine quel tableau de vocabulaire est utilisé pour le prétraitement et quelle voix/accent le moteur TTS utilise pour la synthèse.
Nettoyage des commentaires
Section intitulée « Nettoyage des commentaires »Les annotations de parties contiennent souvent des balises spécifiques au PGN qui sonneraient affreusement si elles étaient lues à voix haute :
Raw comment: "BLUNDER. 7.Nf3 was better [%eval -2.3] [%cal Gg1f3]"After cleaning: "7, Knight f3 was better"La fonction cleanCommentForTTS() :
- Supprime les balises PGN :
[%eval ...],[%csl ...],[%cal ...],[%clk ...] - Retire les mots d’annotation en double (quand « ?? » a déjà dit « Blunder »)
- Développe le SAN intégré dans la prose :
"7.Nf3 controls e5"→"7, Knight f3 controls e5" - Corrige les termes échiquéens que les moteurs TTS prononcent mal (par ex. « en prise » → « on preez »)
- Développe les abréviations de pièces dans la prose :
"R vs R"→"Rook versus Rook"
Construction de la narration complète
Section intitulée « Construction de la narration complète »Lorsque vous avancez vers un nouveau coup, buildNarration() assemble le texte parlé complet à partir de trois sources :
Move: "12, Knight f3, check." ← from sanToSpoken()Annotation: "Good move." ← from annotation symbol (!)Comment: "Developing with tempo." ← from cleanCommentForTTS()
Full narration: "12, Knight f3, check. Good move. Developing with tempo."Le double espace entre les parties donne aux moteurs TTS une pause respiratoire naturelle.
Mise en cache et lecture
Section intitulée « Mise en cache et lecture »Les appels TTS cloud coûtent de l’argent et prennent du temps (~200-500 ms aller-retour). Pour éviter de re-télécharger le même audio, chaque clip généré est mis en cache en mémoire sous forme de blob URL :
Cache key: "elevenlabs:pNInz6obpgDQGcFmaJgB:en:12, Knight f3, check."Cache value: blob:http://localhost/abc123 (the MP3 audio in browser memory)En cas de cache hit, la lecture est instantanée. Le cache est indexé par provider:voice:language:text, de sorte que changer de voix ou de langue crée des entrées séparées.
Pour les parties avec de nombreuses annotations, vous pouvez pré-mettre en cache l’ensemble de l’arbre de jeu en arrière-plan. L’application parcourt chaque nœud, construit le texte de narration et lance des appels API séquentiels pour remplir le cache avant que vous ne commenciez à naviguer.
Concurrence et annulation
Section intitulée « Concurrence et annulation »La navigation rapide avec les touches fléchées crée un problème : si l’utilisateur avance de 5 coups rapidement, on ne veut pas que 5 clips audio se chevauchent. La solution est un compteur de génération :
const thisGeneration = ++requestGeneration;// ... fetch audio ...if (thisGeneration !== requestGeneration) return; // stale — discardChaque nouvel appel à speakText() incrémente le compteur et annule toute requête HTTP en cours via AbortController. Lorsque l’audio arrive, il vérifie si sa génération est toujours la plus récente. Si l’utilisateur est déjà passé à autre chose, la réponse est silencieusement ignorée. Cela produit un audio propre, sans accroc, même en cliquant rapidement à travers les coups.
Points d’intégration du TTS dans l’application
Section intitulée « Points d’intégration du TTS dans l’application »Les points d’intégration sont minimaux :
| Fichier | Ce qui se passe |
|---|---|
src/state/store/tree.ts | Chaque fonction de navigation (goToNext, goToPrevious, etc.) appelle stopSpeaking(). Lorsque la narration automatique est activée, goToNext appelle également speakMoveNarration(). |
src/components/common/Comment.tsx | Une icône de haut-parleur à côté de chaque commentaire permet de déclencher manuellement le TTS pour ce commentaire. |
src/components/settings/TTSSettings.tsx | Interface de paramètres pour choisir le fournisseur, la voix, la langue, le volume, la vitesse et saisir les clés API. |
Lorsque le TTS est désactivé, aucun de ce code ne s’exécute. L’application se comporte de manière identique à En Croissant en amont.
Exemples de flux de données
Section intitulée « Exemples de flux de données »Analyse par moteur
Section intitulée « Analyse par moteur »User clicks "Analyze" → React calls commands.getBestMoves(position, engine, settings) → Rust spawns UCI engine process, sends position via stdin → Engine writes "info depth 18 score cp 45 pv e2e4 ..." to stdout → Rust parses UCI output, emits BestMovesPayload event → React's EvalListener receives event, updates atoms → UI re-renders: eval bar moves, best move arrows appear → User clicks "Stop" → commands.stopEngine() → Rust sets AtomicBool flagRecherche de position dans la base de données
Section intitulée « Recherche de position dans la base de données »User reaches a position on the board → React calls commands.searchPosition(fen, gameQuery) → Rust queries memory-mapped binary search index → Returns: PositionStats (wins/losses/draws) + matching games → React renders DatabasePanel with results tableNarration TTS
Section intitulée « Narration TTS »User steps forward with arrow key → tree.ts calls stopSpeaking(), then checks isAutoNarrateEnabled() → Calls speakMoveNarration(san, comment, annotations, halfMoves) → buildNarration() assembles text: sanToSpoken("Nf3+") → "Knight f3, check" annotationsToSpoken(["!"]) → "Good move." cleanCommentForTTS(comment) → strips [%eval], expands inline SAN → speakText() checks audioCache HIT → play blob URL instantly MISS → fetch from provider API → cache as blob URL → play → HTMLAudioElement.play() with volume and playbackRate from atomsCarte du répertoire
Section intitulée « Carte du répertoire »en-parlant/├── src-tauri/ # BACKEND RUST│ ├── src/│ │ ├── main.rs # Entrée, enregistrement des commandes, plugins│ │ ├── chess.rs # Analyse par moteur│ │ ├── game.rs # Gestion de partie en direct│ │ ├── db/ # Base de données SQLite (plus gros module)│ │ ├── engine/ # Protocole UCI│ │ ├── pgn.rs # Parsing PGN│ │ ├── puzzle.rs # Base de données de puzzles│ │ ├── opening.rs # Recherche d'ouvertures│ │ └── tts.rs # TTS système + gestion de KittenTTS│ ├── Cargo.toml # Dépendances Rust│ ├── tauri.conf.json # Configuration Tauri│ └── capabilities/main.json # Permissions de sécurité│├── src/ # FRONTEND REACT/TS│ ├── App.tsx # Composant racine│ ├── state/│ │ ├── atoms.ts # Atomes Jotai (tout l'état de l'application)│ │ └── store/tree.ts # Arbre de jeu (Zustand + hooks TTS)│ ├── routes/ # TanStack Router (basé sur les fichiers)│ ├── components/│ │ ├── boards/ # Échiquier + analyse│ │ ├── panels/ # Panneaux latéraux│ │ ├── databases/ # Interface de navigation de la base de données│ │ ├── common/ # Affichage des commentaires (avec icône haut-parleur TTS)│ │ └── settings/ # Préférences, paramètres TTS│ ├── utils/│ │ ├── chess.ts # Logique de jeu│ │ ├── tts.ts # Moteur TTS (SAN-to-spoken, cache, 5 fournisseurs)│ │ └── treeReducer.ts # Structure de données de l'arbre│ ├── bindings/ # TS auto-généré depuis Rust│ └── translation/ # i18n (13 langues)│├── docs/ # Documentation intégrée (affichée dans le menu Aide)├── vite.config.ts # Configuration du build└── package.json # Dépendances frontendPoints clés à retenir
Section intitulée « Points clés à retenir »-
Rust fait le gros du travail — moteurs, base de données, E/S fichiers, parsing PGN. React ne touche jamais au système de fichiers et ne lance jamais de processus directement.
-
Sécurité de typage à travers la frontière — Specta génère les types TypeScript à partir des structures Rust, de sorte que si une commande Rust change de signature, le build TypeScript échoue immédiatement.
-
Deux systèmes d’état — Jotai pour l’état réactif simple (paramètres, préférences d’interface, état du moteur par onglet), Zustand pour l’état complexe du domaine (arbre de jeu avec ramifications et mises à jour immuables).
-
Le TTS est un problème de prétraitement — la partie difficile n’est pas d’appeler une API de synthèse vocale, mais de traduire la notation échiquéenne et les balises PGN en un texte propre et naturel dans de nombreuses langues. Les pipelines
sanToSpoken()etcleanCommentForTTS()sont là où le vrai travail se fait. -
Cinq fournisseurs, une seule interface — que l’audio provienne d’ElevenLabs, Google Cloud, KittenTTS, OpenTTS ou du moteur vocal de votre OS, le reste de l’application ne fait qu’appeler
speakText(). La sélection du fournisseur se résume à un simple basculement d’atome. -
Le build produit un binaire unique situé à
src-tauri/target/release/en-parlantqui embarque le backend Rust + les assets frontend construits par Vite.