Arkitekturguide
App-versjon: v0.1.1 (fork: DarrellThomas/en-parlant) Teknologistabel: Tauri v2 (Rust) + React 19 (TypeScript) + Vite
Hva er Tauri?
Section titled “Hva er Tauri?”Tauri er et rammeverk for å bygge skrivebordsapplikasjoner. I stedet for å sende med en fullstendig nettleser slik Electron gjør, bruker Tauri operativsystemets innebygde webview for brukergrensesnittet og en Rust-prosess for backend. Resultatet er en liten, rask binærfil.
De to halvdelene kommuniserer over IPC (inter-prosesskommunikasjon):
+---------------------------+ 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-siden: src-tauri/src/
Section titled “Rust-siden: src-tauri/src/”Rust håndterer alt som må være raskt eller som trenger systemtilgang.
Inngangspunkt: main.rs
Section titled “Inngangspunkt: main.rs”Registrerer ~50 kommandoer som frontend kan kalle, initialiserer plugins (filsystem, dialog, HTTP, shell, logging, oppdatering), og starter appvinduet.
Kommandoer defineres med en makro:
#[tauri::command]async fn get_best_moves(id: String, engine: String, ...) -> Result<...> { // spawn UCI engine, return analysis}specta-craten auto-genererer TypeScript-typedefinisjoner fra disse Rust-funksjonene, slik at frontend får full typesikkerhet uten manuelt arbeid.
Nøkkelmoduler
Section titled “Nøkkelmoduler”| Modul | Hva den gjør |
|---|---|
db/mod.rs | SQLite-database via Diesel ORM — spillforespørsler, spillerstatistikk, import, posisjonssøk |
game.rs | Levende spillmotor — håndterer motor-mot-menneske og motor-mot-motor-spill, tidskontroller, trekkvalidering |
chess.rs | Motoranalyse — starter UCI-motorer, strømmer beste-trekk-resultater tilbake til frontend via hendelser |
engine/ | UCI-protokollimplementasjon — prosessstart, stdin/stdout-rør, multi-PV-støtte |
pgn.rs | PGN-fillesing/-skriving/-tokenisering |
opening.rs | Åpningsnavnoppslag fra FEN (binærdata bakt inn i appen) |
puzzle.rs | Lichess-oppgavedatabase — minnetilordnet tilfeldig tilgang |
fs.rs | Filnedlastinger med gjenopptakelse, innstilling av kjøretillatelser |
sound.rs | Lokal HTTP-server for lydstrømming (Linux-lydløsning) |
tts.rs | System-TTS via speech-dispatcher (Linux) / native OS-tale-APIer, pluss KittenTTS-serveradministrasjon |
oauth.rs | OAuth2-flyt for Lichess/Chess.com-kontokobling |
Designmønstre
Section titled “Designmønstre”- Asynkront overalt: Tokio-kjøretid, ikke-blokkerende I/O
- Samtidig tilstand:
DashMap(samtidig HashMap) for motorprosesser, databasetilkoblinger, hurtiglagre - Tilkoblingspooling: r2d2 håndterer SQLite-tilkoblingspooler
- Minnetilordnet søk: Posisjonsoppslag via mmap’d binærindeks for umiddelbare resultater
- Hendelsesstrømming: Rust sender hendelser (beste trekk, klokkeslett, spill over) som React lytter til i sanntid
React/TypeScript-siden: src/
Section titled “React/TypeScript-siden: src/”Byggepipeline: Vite
Section titled “Byggepipeline: Vite”vite.config.ts konfigurerer:
- React-plugin med Babel-kompilator
- TanStack Router-plugin — auto-genererer rutetre fra
routes/-mappen - Vanilla Extract — nullkjøretids-CSS-in-JS
- Stialias:
@peker til./src - Utviklingsserver på port 1420
Byggeflyt:
pnpm dev → Vite on :1420 + Tauri opens webview pointing to itpnpm build → tsc (typecheck) → vite build (bundle to dist/) → tauri build (native binary)Inngangspunkt: App.tsx
Section titled “Inngangspunkt: App.tsx”Rotkomponenten:
- Initialiserer Tauri-plugins (log, prosess, oppdatering)
- Laster brukerpreferanser fra persistente atomer
- Setter opp Mantine UI-tema
- Registrerer ruteren
- Sjekker etter appoppdateringer
Tilstandshåndtering
Section titled “Tilstandshåndtering”Jotai-atomer (src/state/atoms.ts) — lettvekts reaktiv tilstand:
| Kategori | Eksempler |
|---|---|
| Faner | tabsAtom, activeTabAtom (flerdokumentgrensesnitt) |
| Mapper | storedDocumentDirAtom, storedDatabasesDirAtom |
| Brukergrensesnitt-preferanser | primaryColorAtom, fontSizeAtom, pieceSetAtom |
| Motor | engineMovesFamily, engineProgressFamily (per fane via atomFamily) |
| TTS | ttsEnabledAtom, ttsProviderAtom, ttsVoiceIdAtom, ttsVolumeAtom, ttsSpeedAtom, ttsLanguageAtom |
Atomer med atomWithStorage() lagres automatisk til localStorage.
Zustand-butikker for kompleks domenetilstand:
src/state/store/tree.ts— spilltrenavigasjon, trekkforgreining, annotasjoner, kommentarer. Bruker Immer for uforanderlige oppdateringer.src/state/store/database.ts— databasevisningsfiltre, valgt spill, paginering
Ruting: TanStack Router
Section titled “Ruting: TanStack Router”Filbasert ruting i 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 managementKomponenter: src/components/
Section titled “Komponenter: src/components/”| Gruppe | Formål |
|---|---|
boards/ | Sjakkbrett (chessground), trekkinntasting, evalueringsbar, analysevisning, promoveringsmodal, piltegning |
panels/ | Sidepaneler: motoranalyse (BestMoves), databaseposisjonssøk, annotasjonsredigering, spillinfo, øvingsmodus |
databases/ | Database-brukergrensesnitt: spilltabell, spillertabell, detaljkort, filtrering |
settings/ | Preferanseskjemaer, motorstier, TTS-innstillinger |
home/ | Kontokort, import-brukergrensesnitt |
common/ | Delt: TreeStateContext, materialvisning, kommentar-høyttalerikon |
tabs/ | Flerganebar |
Hvordan frontend kaller Rust
Section titled “Hvordan frontend kaller Rust”Kommandoer (forespørsel/svar)
Section titled “Kommandoer (forespørsel/svar)”Specta genererer TypeScript-bindinger i 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-komponenter kaller dem som vanlige asynkrone funksjoner:
import { commands } from "@/bindings";const result = await commands.getBestMoves(id, engine, tab, goMode, options);Hendelser (strømming, Rust til React)
Section titled “Hendelser (strømming, Rust til React)”For sanntidsdata (motoranalyse, klokkeslett, spilltrekk):
Rust: app.emit("best_moves_payload", BestMovesPayload { depth: 24, ... }) ↓React: listen("best_moves_payload", (event) => updateBestMoves(event.payload))Tauri-plugins
Section titled “Tauri-plugins”Appen bruker flere offisielle plugins for systemtilgang:
| Plugin | Formål |
|---|---|
@tauri-apps/plugin-fs | Lese/skrive filer |
@tauri-apps/plugin-dialog | Filvelgere, meldingsbokser |
@tauri-apps/plugin-http | HTTP-klient (motornedlastinger, sky-TTS) |
@tauri-apps/plugin-shell | Kjøre UCI-motorer |
@tauri-apps/plugin-updater | Automatiske oppdateringssjekker |
@tauri-apps/plugin-log | Strukturert logging |
@tauri-apps/plugin-os | CPU/RAM-gjenkjenning |
Tekst-til-tale (TTS): En innføring
Section titled “Tekst-til-tale (TTS): En innføring”En Parlant~ kan lese sjakktrekk og kommentarer høyt mens du stegvis går gjennom et parti. Denne seksjonen forklarer hvordan TTS-systemet er bygget — forbehandlingspipelinen, leverandørarkitekturen og hurtiglagringsstrategien. For oppsettsinstruksjoner, se TTS-guidene i TTS-menyen.
Hvordan TTS fungerer (kortversjonen)
Section titled “Hvordan TTS fungerer (kortversjonen)”Tekst-til-tale konverterer skrevet tekst til talt lyd. Moderne TTS-systemer er bygget på dype nevrale nettverk trent på tusenvis av timer med menneskelig tale. Modellen lærer sammenhengen mellom tekst (bokstaver, ord, tegnsetting) og de akustiske egenskapene til tale (tonehøyde, timing, betoning, pustpauser). Ved inferens sender du inn tekst og får tilbake en lydbølgeform.
Det finnes to hovedtilnærminger:
-
Sky-TTS — tekst sendes til en ekstern server (Google, ElevenLabs osv.), som kjører et stort nevralt nettverk på GPU-maskinvare og returnerer lyd. Utmerket kvalitet, men krever internett og har kostnad per forespørsel (selv om de fleste leverandører tilbyr gratis nivåer).
-
Lokal TTS — en modell kjører direkte på din maskin. Ingen internett nødvendig, ingen kostnad per forespørsel, og teksten din forlater aldri datamaskinen din. Nyere åpen kildekode-modeller (som Kokoro og Piper) har redusert kvalitetsgapet betraktelig.
Hvis du er nysgjerrig på hvordan TTS-modeller fungerer under panseret, hoster HuggingFace (huggingface.co) hundrevis av åpen kildekode-talesyntese-modeller du kan utforske, laste ned og kjøre lokalt. Søk etter «text-to-speech» for å finne modeller som spenner fra lette CPU-vennlige alternativer til toppmoderne forskningsmodeller.
Leverandørarkitekturen
Section titled “Leverandørarkitekturen”Kjerne-TTS-implementasjonen ligger i src/utils/tts.ts. Den er designet rundt et enkelt offentlig grensesnitt (speakText()) med utbyttbare backends. Resten av appen vet aldri og bryr seg aldri om hvilken leverandør som er aktiv — den kaller bare speakText() og lyd kommer ut.
Fem leverandører støttes:
| Leverandør | Type | Backend |
|---|---|---|
| ElevenLabs | Sky | Nevrale stemmer via REST API. Returnerer MP3. |
| Google Cloud TTS | Sky | WaveNet-stemmer via REST API. Returnerer base64-kodet MP3. |
| KittenTTS | Lokal | Medfølgende TTS-server, auto-startet av Rust-backend. Kommuniserer over HTTP på localhost. |
| OpenTTS | Lokal | Selvhostet TTS-server. Støtter mange motorer (espeak, MaryTTS, Piper osv.). |
| System-TTS | Lokal | OS-native talemotor via Rust/Tauri-kommandoer (speech-dispatcher på Linux, SAPI på Windows, AVSpeechSynthesizer på macOS). |
Leverandørvalg lagres i et enkelt Jotai-atom (ttsProviderAtom). Å bytte leverandør skjer umiddelbart — endre atomet, og neste speakText()-kall rutes til den nye backend-en.
Utfordringen: Sjakknotasjon er ikke engelsk
Section titled “Utfordringen: Sjakknotasjon er ikke engelsk”Sjakktrekk skrives i standard algebraisk notasjon (SAN): Nf3, Bxe5+, O-O-O, e8=Q#. Hvis du mater dette direkte til en TTS-motor, får du tull — den kan prøve å uttale «Nf3» som et ord, eller lese «O-O-O» som «oh oh oh.»
Løsningen er en forbehandlingspipeline som oversetter sjakknotasjon til naturlig språk før det når TTS-motoren:
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"Funksjonen sanToSpoken() bruker regex-mønstermatching for å dekomponere enhver SAN-streng til sine komponenter (brikke, disambiguering, slag, destinasjon, promotering, sjakk/sjakkmatt) og sette dem sammen igjen ved hjelp av naturlig språk fra en ordforråds-tabell.
Flerspråklig støtte
Section titled “Flerspråklig støtte”Sjakkvokabular er oversatt til mange språk (engelsk, fransk, spansk, tysk, japansk, russisk, kinesisk, koreansk og flere). CHESS_VOCAB-tabellen kartlegger hvert begrep:
English: "Knight takes e5, check"French: "Cavalier prend e5, échec"German: "Springer schlägt e5, Schach"Japanese: "ナイト テイクス e5, チェック"Russian: "Конь берёт e5, шах"Språkinnstillingen bestemmer hvilken ordforråds-tabell som brukes til forbehandling og hvilken stemme/aksent TTS-motoren bruker for syntese.
Kommentarrensing
Section titled “Kommentarrensing”Spillannotasjoner inneholder ofte PGN-spesifikk markup som ville hørt forferdelig ut hvis det ble lest høyt:
Raw comment: "BLUNDER. 7.Nf3 was better [%eval -2.3] [%cal Gg1f3]"After cleaning: "7, Knight f3 was better"Funksjonen cleanCommentForTTS():
- Fjerner PGN-tagger:
[%eval ...],[%csl ...],[%cal ...],[%clk ...] - Fjerner dupliserte annotasjonsord (når «??» allerede har sagt «Blunder»)
- Ekspanderer innebygd SAN i prosa:
"7.Nf3 controls e5"→"7, Knight f3 controls e5" - Fikser sjakktermer TTS-motorer uttaler feil (f.eks. «en prise» → «on preez»)
- Ekspanderer brikkeforkortelser i prosa:
"R vs R"→"Rook versus Rook"
Bygge fullstendig narrasjon
Section titled “Bygge fullstendig narrasjon”Når du stegger til et nytt trekk, setter buildNarration() sammen den komplette talte teksten fra tre kilder:
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."Dobbeltmellomrommet mellom delene gir TTS-motorer en naturlig pustpause.
Hurtiglagring og avspilling
Section titled “Hurtiglagring og avspilling”Sky-TTS-kall koster penger og tar tid (~200–500 ms tur-retur). For å unngå å hente samme lyd på nytt, hurtiglagres hvert genererte klipp i minnet som en blob-URL:
Cache key: "elevenlabs:pNInz6obpgDQGcFmaJgB:en:12, Knight f3, check."Cache value: blob:http://localhost/abc123 (the MP3 audio in browser memory)Ved hurtiglagringstreff er avspillingen umiddelbar. Hurtiglageret nøkles etter provider:voice:language:text, slik at bytte av stemme eller språk oppretter separate oppføringer.
For spill med mange annotasjoner kan du forhåndslaste hele spilltreet i bakgrunnen. Appen traverserer hver node, bygger narrasjonsteksten, og sender sekvensielle API-kall for å fylle hurtiglageret før du begynner å navigere.
Samtidighet og avbryting
Section titled “Samtidighet og avbryting”Rask piltastnavigasjon skaper et problem: hvis brukeren stegger fremover 5 ganger raskt, vil du ikke ha 5 overlappende lydklipp som kjemper mot hverandre. Løsningen er en generasjonsteller:
const thisGeneration = ++requestGeneration;// ... fetch audio ...if (thisGeneration !== requestGeneration) return; // stale — discardHvert nye speakText()-kall inkrementerer telleren og avbryter eventuelle pågående HTTP-forespørsler via AbortController. Når lyden ankommer, sjekker den om generasjonen fremdeles er gjeldende. Hvis brukeren allerede har gått videre, forkastes svaret stille. Dette gir ren, feilfri lyd selv ved rask klikking gjennom trekk.
Hvor TTS kobler seg inn i appen
Section titled “Hvor TTS kobler seg inn i appen”Integrasjonspunktene er minimale:
| Fil | Hva som skjer |
|---|---|
src/state/store/tree.ts | Hver navigasjonsfunksjon (goToNext, goToPrevious osv.) kaller stopSpeaking(). Når auto-narrasjon er på, kaller goToNext også speakMoveNarration(). |
src/components/common/Comment.tsx | Et høyttalerikon ved siden av hver kommentar lar deg manuelt utløse TTS for den kommentaren. |
src/components/settings/TTSSettings.tsx | Innstillings-UI for valg av leverandør, stemme, språk, volum, hastighet og inntasting av API-nøkler. |
Når TTS er slått av, kjører ingen av denne koden. Appen oppfører seg identisk med oppstrøms En Croissant.
Eksempler på dataflyt
Section titled “Eksempler på dataflyt”Motoranalyse
Section titled “Motoranalyse”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 flagDatabaseposisjonssøk
Section titled “Databaseposisjonssøk”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 tableTTS-narrasjon
Section titled “TTS-narrasjon”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 atomsKatalogkart
Section titled “Katalogkart”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 depsViktige poenger
Section titled “Viktige poenger”-
Rust gjør det tunge løftet — motorer, database, fil-I/O, PGN-parsing. React berører aldri filsystemet eller starter prosesser direkte.
-
Typesikkerhet over grensen — Specta genererer TypeScript-typer fra Rust-strukturer, så hvis en Rust-kommando endrer signaturen sin, bryter TypeScript-bygget umiddelbart.
-
To tilstandssystemer — Jotai for enkel reaktiv tilstand (innstillinger, UI-preferanser, per-fane-motortilstand), Zustand for kompleks domenetilstand (spilltre med forgreining og uforanderlige oppdateringer).
-
TTS er et forbehandlingsproblem — den vanskelige delen er ikke å kalle et tale-API, det er å oversette sjakknotasjon og PGN-markup til ren, naturlig-klingende tekst på tvers av mange språk.
sanToSpoken()- ogcleanCommentForTTS()-pipelinene er der det virkelige arbeidet skjer. -
Fem leverandører, ett grensesnitt — enten lyden kommer fra ElevenLabs, Google Cloud, KittenTTS, OpenTTS eller operativsystemets talemotor, kaller resten av appen bare
speakText(). Leverandørvalg er en enkel atombryter. -
Bygget produserer én enkelt binærfil på
src-tauri/target/release/en-parlantsom pakker sammen Rust-backend + de Vite-bygde frontend-ressursene.