Агляд архітэктуры
Версія праграмы: v0.1.1 (форк: DarrellThomas/en-parlant) Стэк: Tauri v2 (Rust) + React 19 (TypeScript) + Vite
Што такое Tauri?
Section titled “Што такое Tauri?”Tauri — гэта фрэймворк для стварэння настольных праграм. Замест таго каб пастаўляць поўны браўзер, як гэта робіць Electron, Tauri выкарыстоўвае ўбудаваны ў аперацыйную сістэму webview для інтэрфейсу і працэс на Rust для бэкенда. У выніку атрымліваецца невялікі і хуткі бінарны файл.
Дзве паловы ўзаемадзейнічаюць праз IPC (міжпрацэсная камунікацыя):
+---------------------------+ 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/Бок Rust: src-tauri/src/
Section titled “Бок Rust: src-tauri/src/”Rust апрацоўвае ўсё, што патрабуе хуткадзейнасці або доступу да сістэмы.
Кропка ўваходу: main.rs
Section titled “Кропка ўваходу: main.rs”Рэгіструе ~50 каманд, якія можа выклікаць фронтэнд, ініцыялізуе плагіны (файлавая сістэма, дыялогі, HTTP, shell, лагіраванне, абнаўленне) і запускае акно праграмы.
Каманды вызначаюцца з дапамогай макраса:
#[tauri::command]async fn get_best_moves(id: String, engine: String, ...) -> Result<...> { // spawn UCI engine, return analysis}Крэйт specta аўтаматычна генеруе вызначэнні тыпаў TypeScript з гэтых функцый Rust, так што фронтэнд атрымлівае поўную тыпабяспеку без якіх-небудзь ручных намаганняў.
Асноўныя модулі
Section titled “Асноўныя модулі”| Модуль | Што ён робіць |
|---|---|
db/mod.rs | База даных SQLite праз Diesel ORM — запыты гульняў, статыстыка гульцоў, імпарт, пошук пазіцый |
game.rs | Рухавік жывой гульні — кіруе гульнямі рухавік-супраць-чалавека і рухавік-супраць-рухавіка, кантроль часу, валідацыя хадоў |
chess.rs | Аналіз рухавіком — запускае рухавікі UCI, перадае вынікі лепшых хадоў на фронтэнд праз падзеі |
engine/ | Рэалізацыя пратаколу UCI — запуск працэсаў, каналы stdin/stdout, падтрымка multi-PV |
pgn.rs | Чытанне/запіс/такенізацыя файлаў PGN |
opening.rs | Пошук назвы дэбюту па FEN (бінарныя даныя ўбудаваны ў праграму) |
puzzle.rs | База задач Lichess — выпадковы доступ з адлюстраваннем у памяць |
fs.rs | Спампоўка файлаў з аднаўленнем, устаноўка правоў на выкананне |
sound.rs | Лакальны HTTP-сервер для патокавага аўдыё (абыходны шлях для аўдыё ў Linux) |
tts.rs | Сістэмны TTS праз speech-dispatcher (Linux) / натыўныя API маўлення АС, плюс кіраванне серверам KittenTTS |
oauth.rs | Паток OAuth2 для прывязкі ўліковых запісаў Lichess/Chess.com |
Шаблоны праектавання
Section titled “Шаблоны праектавання”- Асінхроннасць паўсюль: асяроддзе выканання Tokio, неблакіруючы ўвод/вывад
- Канкурэнтны стан:
DashMap(канкурэнтны HashMap) для працэсаў рухавікоў, злучэнняў з БД, кэшаў - Пул злучэнняў: r2d2 кіруе пуламі злучэнняў SQLite
- Пошук з адлюстраваннем у памяць: пошук пазіцый праз mmap-індэкс для імгненных вынікаў
- Патокавая перадача падзей: Rust выпускае падзеі (лепшыя хады, такты гадзінніка, канец гульні), якія React слухае ў рэальным часе
Бок React/TypeScript: src/
Section titled “Бок React/TypeScript: src/”Зборка: Vite
Section titled “Зборка: Vite”vite.config.ts наладжвае:
- Плагін React з кампілятарам Babel
- Плагін TanStack Router — аўтаматычна генеруе дрэва маршрутаў з тэчкі
routes/ - Vanilla Extract — CSS-in-JS без выканання ў runtime
- Псеўданім шляху:
@адлюстроўваецца на./src - Сервер распрацоўкі на порце 1420
Працэс зборкі:
pnpm dev → Vite on :1420 + Tauri opens webview pointing to itpnpm build → tsc (typecheck) → vite build (bundle to dist/) → tauri build (native binary)Кропка ўваходу: App.tsx
Section titled “Кропка ўваходу: App.tsx”Каранёвы кампанент:
- Ініцыялізуе плагіны Tauri (log, process, updater)
- Загружае карыстальніцкія налады з пастаянных атамаў
- Наладжвае тэму інтэрфейсу Mantine
- Рэгіструе маршрутызатар
- Правярае наяўнасць абнаўленняў праграмы
Кіраванне станам
Section titled “Кіраванне станам”Атамы Jotai (src/state/atoms.ts) — лёгкі рэактыўны стан:
| Катэгорыя | Прыклады |
|---|---|
| Укладкі | tabsAtom, activeTabAtom (шматдакументны інтэрфейс) |
| Каталогі | storedDocumentDirAtom, storedDatabasesDirAtom |
| Налады UI | primaryColorAtom, fontSizeAtom, pieceSetAtom |
| Рухавік | engineMovesFamily, engineProgressFamily (для кожнай укладкі праз atomFamily) |
| TTS | ttsEnabledAtom, ttsProviderAtom, ttsVoiceIdAtom, ttsVolumeAtom, ttsSpeedAtom, ttsLanguageAtom |
Атамы з atomWithStorage() аўтаматычна захоўваюцца ў localStorage.
Сховішчы Zustand для складанага даменнага стану:
src/state/store/tree.ts— навігацыя па дрэве гульні, разгалінаванне хадоў, анатацыі, каментарыі. Выкарыстоўвае Immer для нязменных абнаўленняў.src/state/store/database.ts— фільтры прагляду базы даных, абраная гульня, пагінацыя
Маршрутызацыя: TanStack Router
Section titled “Маршрутызацыя: TanStack Router”Файлавая маршрутызацыя ў 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 managementКампаненты: src/components/
Section titled “Кампаненты: src/components/”| Група | Прызначэнне |
|---|---|
boards/ | Шахматная дошка (chessground), увод хадоў, шкала ацэнкі, адлюстраванне аналізу, мадальнае акно ператварэння, маляванне стрэлак |
panels/ | Бакавыя панэлі: аналіз рухавіком (BestMoves), пошук пазіцый у базе, рэдагаванне анатацый, інфармацыя пра гульню, рэжым практыкі |
databases/ | UI базы даных: табліца гульняў, табліца гульцоў, карткі дэталяў, фільтраванне |
settings/ | Формы налад, шляхі да рухавікоў, налады TTS |
home/ | Карткі ўліковых запісаў, UI імпарту |
common/ | Агульныя: TreeStateContext, адлюстраванне матэрыялу, іконка дынаміка каментарыя |
tabs/ | Панэль з мноствам ўкладак |
Як фронтэнд выклікае Rust
Section titled “Як фронтэнд выклікае Rust”Каманды (запыт/адказ)
Section titled “Каманды (запыт/адказ)”Specta генеруе прывязкі TypeScript у 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...}Кампаненты React выклікаюць іх як звычайныя асінхронныя функцыі:
import { commands } from "@/bindings";const result = await commands.getBestMoves(id, engine, tab, goMode, options);Падзеі (патокавая перадача, з Rust у React)
Section titled “Падзеі (патокавая перадача, з Rust у React)”Для даных у рэальным часе (аналіз рухавіком, такты гадзінніка, хады гульні):
Rust: app.emit("best_moves_payload", BestMovesPayload { depth: 24, ... }) ↓React: listen("best_moves_payload", (event) => updateBestMoves(event.payload))Плагіны Tauri
Section titled “Плагіны Tauri”Праграма выкарыстоўвае некалькі афіцыйных плагінаў для сістэмнага доступу:
| Плагін | Прызначэнне |
|---|---|
@tauri-apps/plugin-fs | Чытанне/запіс файлаў |
@tauri-apps/plugin-dialog | Выбар файлаў, акны паведамленняў |
@tauri-apps/plugin-http | HTTP-кліент (спампоўка рухавікоў, хмарны TTS) |
@tauri-apps/plugin-shell | Запуск рухавікоў UCI |
@tauri-apps/plugin-updater | Аўтаматычная праверка абнаўленняў |
@tauri-apps/plugin-log | Структураванае лагіраванне |
@tauri-apps/plugin-os | Вызначэнне CPU/RAM |
Сінтэз маўлення (TTS): уводзіны
Section titled “Сінтэз маўлення (TTS): уводзіны”En Parlant~ можа чытаць уголас шахматныя хады і каментарыі, калі вы прагортваеце гульню. Гэты раздзел тлумачыць, як пабудавана сістэма TTS — канвеер папярэдняй апрацоўкі, архітэктура правайдараў і стратэгія кэшавання. Інструкцыі па наладжванні глядзіце ў кіраўніцтвах TTS у меню TTS.
Як працуе TTS (кароткая версія)
Section titled “Як працуе TTS (кароткая версія)”Сінтэз маўлення (text-to-speech) пераўтварае пісьмовы тэкст у гукавое аўдыё. Сучасныя сістэмы TTS пабудаваны на глыбокіх нейронных сетках, навучаных на тысячах гадзін чалавечага маўлення. Мадэль вывучае сувязь паміж тэкстам (літары, словы, пунктуацыя) і акустычнымі характарыстыкамі маўлення (вышыня тону, тэмп, акцэнты, паўзы на дыханне). Падчас інферэнсу вы адпраўляеце тэкст і атрымліваеце назад гукавую хвалевую форму.
Ёсць два асноўныя падыходы:
-
Хмарны TTS — тэкст адпраўляецца на аддалены сервер (Google, ElevenLabs і г.д.), які запускае вялікую нейронную сетку на GPU і вяртае аўдыё. Выдатная якасць, але патрабуе інтэрнэту і мае кошт за запыт (хоць большасць правайдараў прапаноўваюць бясплатныя ўзроўні).
-
Лакальны TTS — мадэль працуе непасрэдна на вашай машыне. Інтэрнэт не патрэбны, няма кошту за запыт, і ваш тэкст ніколі не пакідае ваш камп’ютар. Нядаўнія адкрытыя мадэлі (як Kokoro і Piper) значна скарацілі разрыў у якасці.
Калі вам цікава, як мадэлі TTS працуюць унутры, HuggingFace (huggingface.co) размяшчае сотні адкрытых мадэлей сінтэзу маўлення, якія можна даследаваць, спампоўваць і запускаць лакальна. Шукайце “text-to-speech”, каб знайсці мадэлі ад лёгкіх варыянтаў для CPU да найноўшых даследчых мадэлей.
Архітэктура правайдараў
Section titled “Архітэктура правайдараў”Асноўная рэалізацыя TTS знаходзіцца ў src/utils/tts.ts. Яна пабудавана вакол адзінага публічнага інтэрфейсу (speakText()) са зменнымі бэкендамі. Астатняя частка праграмы ніколі не ведае і не клапоціцца пра тое, які правайдар актыўны — яна проста выклікае speakText(), і гучыць аўдыё.
Падтрымліваюцца пяць правайдараў:
| Правайдар | Тып | Бэкенд |
|---|---|---|
| ElevenLabs | Хмарны | Нейронныя галасы праз REST API. Вяртае MP3. |
| Google Cloud TTS | Хмарны | Галасы WaveNet праз REST API. Вяртае MP3 у кадзіроўцы base64. |
| KittenTTS | Лакальны | Убудаваны TTS-сервер, аўтаматычна запускаецца бэкендам Rust. Узаемадзейнічае праз HTTP на localhost. |
| OpenTTS | Лакальны | Самастойна размешчаны TTS-сервер. Падтрымлівае шмат рухавікоў (espeak, MaryTTS, Piper і інш.). |
| System TTS | Лакальны | Натыўны рухавік маўлення АС праз каманды Rust/Tauri (speech-dispatcher на Linux, SAPI на Windows, AVSpeechSynthesizer на macOS). |
Выбар правайдара захоўваецца ў адзіным атаме Jotai (ttsProviderAtom). Пераключэнне правайдараў імгненнае — змяніце атам, і наступны выклік speakText() накіруецца да новага бэкенда.
Выклік: шахматная натацыя — гэта не англійская мова
Section titled “Выклік: шахматная натацыя — гэта не англійская мова”Шахматныя хады запісваюцца ў стандартнай алгебраічнай натацыі (SAN): Nf3, Bxe5+, O-O-O, e8=Q#. Калі падаць гэта напрамую ў рухавік TTS, атрымаецца бязглуздзіца — ён можа паспрабаваць вымавіць “Nf3” як слова або прачытаць “O-O-O” як “о о о”.
Рашэннем з’яўляецца канвеер папярэдняй апрацоўкі, які перакладае шахматную натацыю ў натуральную мову перад тым, як яна трапіць да рухавіка 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"Функцыя sanToSpoken() выкарыстоўвае супастаўленне шаблонаў з рэгулярнымі выразамі для разбору любога радка SAN на яго кампаненты (фігура, адназначнасць, узяцце, мэтавая клетка, ператварэнне, шах/мат) і збірае іх зноў з выкарыстаннем натуральнай мовы з табліцы слоўніка.
Падтрымка шматмоўнасці
Section titled “Падтрымка шматмоўнасці”Шахматны слоўнік перакладзены на шмат моў (ангельская, французская, іспанская, нямецкая, японская, руская, кітайская, карэйская і іншыя). Табліца CHESS_VOCAB адлюстроўвае кожны тэрмін:
English: "Knight takes e5, check"French: "Cavalier prend e5, échec"German: "Springer schlägt e5, Schach"Japanese: "ナイト テイクス e5, チェック"Russian: "Конь берёт e5, шах"Моўная налада вызначае, якая табліца слоўніка выкарыстоўваецца для папярэдняй апрацоўкі і які голас/акцэнт рухавік TTS выкарыстоўвае для сінтэзу.
Ачыстка каментарыяў
Section titled “Ачыстка каментарыяў”Анатацыі гульняў часта ўтрымліваюць разметку, спецыфічную для PGN, якая гучала б жудасна пры чытанні ўголас:
Raw comment: "BLUNDER. 7.Nf3 was better [%eval -2.3] [%cal Gg1f3]"After cleaning: "7, Knight f3 was better"Функцыя cleanCommentForTTS():
- Выдаляе тэгі PGN:
[%eval ...],[%csl ...],[%cal ...],[%clk ...] - Выдаляе дублюючыя словы анатацый (калі ”??” ужо сказала “Blunder”)
- Разгортвае ўбудаваны SAN у тэксце:
"7.Nf3 controls e5"→"7, Knight f3 controls e5" - Выпраўляе шахматныя тэрміны, якія рухавікі TTS няправільна вымаўляюць (напр., “en prise” → “on preez”)
- Разгортвае скарачэнні фігур у тэксце:
"R vs R"→"Rook versus Rook"
Стварэнне поўнай нарацыі
Section titled “Стварэнне поўнай нарацыі”Калі вы пераходзіце да новага хаду, buildNarration() збірае поўны тэкст для агучвання з трох крыніц:
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."Падвойны прабел паміж часткамі дае рухавікам TTS натуральную паўзу для дыхання.
Кэшаванне і прайграванне
Section titled “Кэшаванне і прайграванне”Выклікі хмарнага TTS каштуюць грошай і займаюць час (~200-500 мс у абодва бакі). Каб пазбегнуць паўторнага запыту таго ж аўдыё, кожны згенераваны кліп кэшуецца ў памяці як blob URL:
Cache key: "elevenlabs:pNInz6obpgDQGcFmaJgB:en:12, Knight f3, check."Cache value: blob:http://localhost/abc123 (the MP3 audio in browser memory)Пры трапленні ў кэш прайграванне імгненнае. Ключ кэша складаецца з provider:voice:language:text, так што пераключэнне галасоў або моў стварае асобныя запісы.
Для гульняў з вялікай колькасцю анатацый можна папярэдне закэшаваць усё дрэва гульні ў фонавым рэжыме. Праграма абыходзіць кожны вузел, стварае тэкст нарацыі і робіць паслядоўныя выклікі API, каб запоўніць кэш да таго, як вы пачнёце навігацыю.
Канкурэнтнасць і адмена
Section titled “Канкурэнтнасць і адмена”Хуткая навігацыя клавішамі-стрэлкамі стварае праблему: калі карыстальнік хутка робіць 5 крокаў наперад, вы не хочаце, каб 5 аўдыёкліпаў накладваліся адзін на аднаго. Рашэнне — лічыльнік генерацый:
const thisGeneration = ++requestGeneration;// ... fetch audio ...if (thisGeneration !== requestGeneration) return; // stale — discardКожны новы выклік speakText() павялічвае лічыльнік і спыняе любы незавершаны HTTP-запыт праз AbortController. Калі аўдыё прыходзіць, яно правярае, ці з’яўляецца яго генерацыя ўсё яшчэ бягучай. Калі карыстальнік ужо рухаецца далей, адказ ціха адкідваецца. Гэта забяспечвае чыстае аўдыё без збояў нават пры хуткім праклікванні хадоў.
Дзе TTS убудоўваецца ў праграму
Section titled “Дзе TTS убудоўваецца ў праграму”Кропак інтэграцыі мінімум:
| Файл | Што адбываецца |
|---|---|
src/state/store/tree.ts | Кожная функцыя навігацыі (goToNext, goToPrevious і інш.) выклікае stopSpeaking(). Калі аўтанарацыя ўключана, goToNext таксама выклікае speakMoveNarration(). |
src/components/common/Comment.tsx | Іконка дынаміка побач з кожным каментарыем дазваляе ўручную запусціць TTS для гэтага каментарыя. |
src/components/settings/TTSSettings.tsx | UI налад для выбару правайдара, голасу, мовы, гучнасці, хуткасці і ўводу API-ключоў. |
Калі TTS выключаны, нічога з гэтага кода не выконваецца. Праграма паводзіць сябе ідэнтычна арыгінальнаму En Croissant.
Прыклады патоку даных
Section titled “Прыклады патоку даных”Аналіз рухавіком
Section titled “Аналіз рухавіком”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 flagПошук пазіцыі ў базе даных
Section titled “Пошук пазіцыі ў базе даных”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 tableНарацыя TTS
Section titled “Нарацыя 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 atomsКарта каталогаў
Section titled “Карта каталогаў”en-parlant/├── src-tauri/ # RUST BACKEND│ ├── src/│ │ ├── main.rs # Entry, command registration, plugins│ │ ├── chess.rs # Engine analysis│ │ ├── game.rs # Live game management│ │ ├── db/ # SQLite database (largest module)│ │ ├── engine/ # UCI protocol│ │ ├── pgn.rs # PGN parsing│ │ ├── puzzle.rs # Puzzle database│ │ ├── opening.rs # Opening lookup│ │ └── tts.rs # System TTS + KittenTTS management│ ├── Cargo.toml # Rust dependencies│ ├── tauri.conf.json # Tauri config│ └── capabilities/main.json # Security permissions│├── src/ # REACT/TS FRONTEND│ ├── App.tsx # Root component│ ├── state/│ │ ├── atoms.ts # Jotai atoms (all app state)│ │ └── store/tree.ts # Game tree (Zustand + TTS hooks)│ ├── routes/ # TanStack Router (file-based)│ ├── components/│ │ ├── boards/ # Chessboard + analysis│ │ ├── panels/ # Side panels│ │ ├── databases/ # DB browsing UI│ │ ├── common/ # Comment display (with TTS speaker icon)│ │ └── settings/ # Preferences, TTS settings│ ├── utils/│ │ ├── chess.ts # Game logic│ │ ├── tts.ts # TTS engine (SAN-to-spoken, caching, 5 providers)│ │ └── treeReducer.ts # Tree data structure│ ├── bindings/ # Auto-generated TS from Rust│ └── translation/ # i18n (13 languages)│├── docs/ # Bundled documentation (shown in Help menu)├── vite.config.ts # Build config└── package.json # Frontend depsАсноўныя высновы
Section titled “Асноўныя высновы”-
Rust бярэ на сябе цяжкую працу — рухавікі, база даных, файлавы ўвод/вывад, разбор PGN. React ніколі не звяртаецца да файлавай сістэмы і не запускае працэсы напрамую.
-
Тыпабяспека праз мяжу — Specta генеруе тыпы TypeScript са структур Rust, так што калі каманда Rust зменіць сваю сігнатуру, зборка TypeScript адразу ж зламаецца.
-
Дзве сістэмы стану — Jotai для простага рэактыўнага стану (налады, перавагі UI, стан рухавіка для кожнай укладкі), Zustand для складанага даменнага стану (дрэва гульні з разгалінаваннем і нязменнымі абнаўленнямі).
-
TTS — гэта задача папярэдняй апрацоўкі — складанасць не ў выкліку API маўлення, а ў перакладзе шахматнай натацыі і разметкі PGN у чысты, натуральна гучны тэкст на шматлікіх мовах. Канвееры
sanToSpoken()іcleanCommentForTTS()— вось дзе адбываецца сапраўдная праца. -
Пяць правайдараў, адзін інтэрфейс — незалежна ад таго, адкуль прыходзіць аўдыё: з ElevenLabs, Google Cloud, KittenTTS, OpenTTS або натыўнага рухавіка маўлення вашай АС — астатняя частка праграмы заўсёды выклікае толькі
speakText(). Выбар правайдара — гэта адно пераключэнне атама. -
Зборка стварае адзіны бінарны файл у
src-tauri/target/release/en-parlant, які аб’ядноўвае бэкенд Rust + фронтэнд-асеты, сабраныя Vite.