{"id":51130142,"url":"https://github.com/plnech/tuparles","last_synced_at":"2026-06-27T22:00:51.805Z","repository":{"id":366783659,"uuid":"1266495006","full_name":"PLNech/TuParles","owner":"PLNech","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-24T21:13:32.000Z","size":820,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-25T11:32:15.519Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PLNech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T17:08:42.000Z","updated_at":"2026-06-24T21:13:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/PLNech/TuParles","commit_stats":null,"previous_names":["plnech/tuparles"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/PLNech/TuParles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PLNech%2FTuParles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PLNech%2FTuParles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PLNech%2FTuParles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PLNech%2FTuParles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PLNech","download_url":"https://codeload.github.com/PLNech/TuParles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PLNech%2FTuParles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34869004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-25T11:30:49.936Z","updated_at":"2026-06-27T22:00:51.792Z","avatar_url":"https://github.com/PLNech.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TuParles\n\n[![CI](https://github.com/PLNech/TuParles/actions/workflows/ci.yml/badge.svg)](https://github.com/PLNech/TuParles/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nLocal, private, push-to-talk dictation for people who code-switch entre le\nfrançais and English mid-sentence — and need their tech vocab (`max_tokens`,\nKPIs, la contingence) to survive transcription.\n\nTap **Right Ctrl+Alt** → a small floating bubble appears with a live waveform\nand the transcript streaming in as you speak → release (or tap again) → the\ntext is typed into whatever window has focus. Everything runs on-device.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/bubble-recording.png\" alt=\"Recording: live waveform, transcript streaming in, freshest words kept visible\"/\u003e\n\u003c/p\u003e\n\n| Vue complète (toggle dans le menu) | Le perchoir | Réglages |\n|:---:|:---:|:---:|\n| \u003cimg src=\".github/bubble-full.png\" alt=\"Full view: the whole take, word-wrapped, growing as you speak\"/\u003e | \u003cimg src=\".github/tray-menu.png\" alt=\"Tray menu: start/stop, copy last, history, settings, view toggle, about, quit\"/\u003e | \u003cimg src=\".github/settings-langues.png\" alt=\"Settings: searchable checklist of 100 languages, selected first\"/\u003e |\n\n*(screens rendered from the actual widgets by `scripts/readme_screens.py` —\nregenerate with `QT_QPA_PLATFORM=offscreen poetry run python scripts/readme_screens.py`)*\n\n## Features\n\n- **Live transcript** — while you speak, the bubble streams a ~1 Hz greedy\n  preview of the last few seconds; on stop, the whole take gets a full\n  beam decode. On a long take that final pass runs batched (VAD-chunked,\n  parallel on GPU): a 3-minute monologue lands in about a second.\n- **A bubble that tells you what's happening** — the waveform tracks your\n  voice on a perceptual scale, so even quiet speech visibly moves the bars\n  (\"I hear you\"); the bars are **green on GPU, blue on CPU**, so you always\n  know which silicon is decoding (and a red flash for errors). That hue holds\n  from first frame to last — while it decodes, a bright pulse sweeps across the\n  bars (\"I'm working\"), and the take lands on a *brighter* flash of the **same**\n  colour (so green only ever means GPU). By default the bubble shows your **whole take**,\n  word-wrapped and growing as you speak (switch to the discreet one-line\n  *minimal* pill in the tray or *Réglages*). The **tray glyph breathes** —\n  calm at rest, livelier while recording, a travelling pulse while decoding, in\n  the engine colour (toggle off in *Réglages*). Optional soft **start tick**\n  (*Réglages*, off by default) confirms recording has begun. A slow decode\n  (past ~3 s) shows a quiet **`(Ns)` counter** so it reads as *working*, not\n  frozen; and if a final is ever lost after a preview was shown, the bubble\n  **never recants** — it holds the salvaged words in **amber** with a `Ctrl+V`\n  hint (it was copied) instead of red-flashing a failure. If the GPU drops to\n  CPU mid-session, a one-time note says so (the bars go green→blue anyway).\n  On multi-monitor,\n  pick which screen the bubble uses in *Réglages* — pin it to a monitor\n  (default: primary), follow the mouse, follow the active window (where your\n  text lands; on Wayland, where the focused window isn't queryable, it falls\n  back to the mouse's screen), or **mirror it on every screen** at once.\n- **Code-switching first-class** — by default the model auto-detects among\n  100 languages per take. In *Réglages* you can confine detection to your\n  own set: one language forces it; several turns on **per-segment**\n  detection, so the language is re-detected segment by segment and a\n  mid-sentence switch from français to English survives intact (\"can I\n  switch to English\" stays English, instead of becoming \"peux-je changer en\n  anglais\"). No more random Cyrillic cameos when you mumble, either.\n- **Fast delivery, X11 and Wayland** — short takes are typed into the focused\n  window (X11 xdotool, modifier-safe); long ones are pasted (Ctrl+V, or\n  Ctrl+Shift+V in terminals). On Wayland (GNOME) everything is pasted via\n  ydotool (never typed — ydotool assumes a US keymap). The clipboard is\n  always set as backup — and *Réglages* can **preserve and restore** it around\n  a take (off by default), so a dictation doesn't clobber what you'd copied. It\n  only ever restores genuine text: an image or file list on the clipboard is\n  left untouched rather than destroyed by a text-only write-back.\n- **Cleanup that knows its place** — spoken punctuation (\"virgule\",\n  \"point\", \"new line\") in both languages, a personal lexicon for your\n  jargon, and deterministic collapse of Whisper repetition loops. No AI\n  rewriting: a visible mishear beats a confident wrong autocorrect.\n- **Voice macros (quick-chat)** — a short spoken trigger expands to a canned\n  text: say \"lgtm\" and get your full review sign-off, \"standup billing\" and get\n  a filled template. Triggers fire only on an exact whole-take match (never\n  inside a sentence), and the pack is a hand-editable JSON file you own. Pick a\n  **role** in onboarding (eng / product / design / marketing / strategy) and a\n  curated built-in pack activates instantly — your own macros always take\n  precedence. Everything you've got shows in `tuparles cheatsheet`. *(Radial\n  activation still coming.)*\n- **History \u0026 stats, local forever** — every take lands in SQLite with\n  its telemetry (duration, decode time, words/min, detected language).\n  `tuparles history \"query\"` searches it; `tuparles stats` shows your\n  dictation profile (débit, decode speed, language mix).\n- **Analytics dashboard, all on your box** — a tray *Analytics…* window\n  with three views: *Ton usage* (which voice commands and syntax features\n  you actually use, and which you've never discovered), *Ta voix* (a tag\n  cloud + keyphrases over your dictation history), and *Ton code* (the\n  cached codebase analysis that seeds the decoder). Feature usage is\n  tracked **locally and opt-out** — nothing leaves the machine; toggle it\n  off or wipe it in *Réglages › Confidentialité*.\n- **PII firewall — minimize before persist** — what you dictate is always\n  pasted verbatim, but the *stored* copy is cleaned first: secrets and\n  checksum-validated identifiers (IBAN, n° de sécu, credit card, API keys)\n  are masked with a `\u003cKIND\u003e` placeholder before they ever reach\n  `history.db`. High-precision detection only, so it destroys ~zero real\n  text; on by default, a toggle in *Réglages › Confidentialité*. The\n  analytics tag cloud also honours a frequency floor so a once-spoken name\n  can be kept from surfacing. A *Pare-feu PII* editor adds your own terms\n  in two tiers — **block** (masked, for confidential project/client names)\n  and **alert** (surfaced, never auto-erased) — case- and accent-insensitive.\n  A **dev-capture** toggle (off by default, *Réglages › Confidentialité*) can\n  save each take's *raw, unredacted* audio locally for replaying a fix — and\n  while it's on, the tray shows a **steady red dot** so it never records you\n  silently (`TUPARLES_DEV` overrides the toggle either way).\n\n## Architecture\n\n```\n            hotkey (Right Ctrl + Right Alt)\n                          │\n   mic ── 16 kHz mono ────┤\n    │                     ▼\n    │              ┌─────────────┐   final: batched beam   ┌─────────────────────┐\n    ├─ levels ───► │   daemon    │ ──────────────────────► │ faster-whisper      │\n    │              │  (Python)   │ ◄────────────────────── │ large-v3-turbo fp16 │\n    ▼              └─────────────┘   partials: ~1 Hz greedy │ (GPU, persistent)   │\n waveform              │      │                            └─────────────────────┘\n  bubble UI ◄──────────┘      ├─► punctuation → lexicon → repeat-collapse\n  (live transcript)           ├─► type/paste into focus (X11 xdotool · Wayland ydotool) + clipboard\n                              └─► history + telemetry + usage events (SQLite)\n```\n\n- **Primary engine**: [faster-whisper](https://github.com/SYSTRAN/faster-whisper)\n  `large-v3-turbo` in float16, persistent on the GPU (~29x realtime\n  measured on an RTX 4080). Finals go through the batched pipeline;\n  partials are cheap greedy decodes of a sliding window.\n- **CPU fallback**: [Qwen3-ASR-0.6B](https://huggingface.co/Qwen/Qwen3-ASR-0.6B)\n  via [antirez/qwen-asr](https://github.com/antirez/qwen-asr), a pure-C\n  inference engine (OpenBLAS) — used automatically when no GPU answers.\n- **CPU live partials**: qwen can't stream, so the preview text on a CPU\n  session comes from a separate small whisper on CPU (`base` by default,\n  greedy, bounded window) — faster-whisper's CT2 CPU backend, no CUDA. The\n  qwen final decode still rules; partials just paint provisional text as you\n  speak. Opt-out in *Réglages* (« Aperçu en direct sur CPU ») on a low-power\n  box, where the bubble falls back to waveform-only.\n- **Self-healing GPU**: if the CUDA context dies mid-session — a laptop\n  suspend/resume is the classic culprit, leaving `nvidia-smi` happy but\n  CUDA unusable — the engine rebuilds the context on the next take, and\n  only drops to the CPU fallback if that also fails. A take never silently\n  yields nothing.\n\n## Mobile (experimental)\n\nTuParles runs on Android too — the same privacy story, on the phone in your\npocket. The whole loop is local: **mic → native whisper.cpp → embedded CPython\n`postprocess()` → text**, sharing *one* Python core with the desktop (no Kotlin\nre-port, no dual-maintenance tax). No `INTERNET` permission is declared; the OS\nitself denies any socket.\n\n**👉 [Download the experimental APK](https://github.com/PLNech/TuParles/releases/tag/android-poc-0.1)**\n(`android-poc-0.1`, ~212 MB — model bundled, installable today on arm64). It's a\nproof of concept: a capture harness that records FR/EN code-switch prompts, runs\nthe desktop pipeline on-device, and lets you export takes to `dev@nech.pl` via a\nlocal intent. Toggles for language (auto/fr/en) and postprocess (on/off). Build\nand model-swap notes in [`android/README.md`](android/README.md).\n\n### The plan\n\nThe embed path (CPython via Chaquopy + whisper.cpp via JNI) was chosen over a\nKotlin port after a research fan-out — see `docs/research/2026-06-27-android-*`.\nIt ships as a phased epic:\n\n| Phase | Issue | Status |\n|---|---|---|\n| Portable core: split `config_core` | [#4](https://github.com/PLNech/TuParles/issues/4) | ✅ ([PR #9](https://github.com/PLNech/TuParles/pull/9)) |\n| Externalise postprocess tables to JSON | [#5](https://github.com/PLNech/TuParles/issues/5) | ✅ ([PR #11](https://github.com/PLNech/TuParles/pull/11)) |\n| On-device engine + packaging | [#3](https://github.com/PLNech/TuParles/issues/3), [#6](https://github.com/PLNech/TuParles/issues/6)–[#8](https://github.com/PLNech/TuParles/issues/8) | 🧪 POC shipped, productionisation open |\n\nEpic: [#2](https://github.com/PLNech/TuParles/issues/2). The spike (commits\n`e10ac02..f87d70b`) merged to `main` as `bc12fc4`; CI un-redding landed in\n[PR #10](https://github.com/PLNech/TuParles/pull/10).\n\n### Honest status\n\nA POC, not a daily driver yet. The bundled `base` model is fast (~1.5 s/clip)\nand keeps French as French, but fumbles some loanwords; `large-v3-turbo` is\nflawless but ~30 s/clip on a mid-range phone (push it manually — see the Android\nREADME). Two findings cost the most to learn: the native build **must** be `-O3`\n(a debug `ggml` build is ~50× slower), and whisper's language **must** be `auto`\n(a hardcoded `\"en\"` silently translated French to English). Tested on a\nFairphone 6; other devices unverified.\n\n## Install\n\nOne-liner (Linux — apt / pacman / dnf / zypper, X11; needs git + poetry.\nWayland adds one step, below):\n\n```bash\ncurl -fsSL https://github.com/PLNech/TuParles/releases/latest/download/install.sh | bash\n```\n\nThis pulls the repo, installs system + Python deps (mapping package names to\nyour distro), builds the CPU fallback engine, downloads the model weights, and\nregisters TuParles in your desktop's app launcher.\n\n\u003cdetails\u003e\n\u003csummary\u003eManual setup\u003c/summary\u003e\n\n```bash\n# system deps — pick your distro:\nsudo apt install libopenblas-dev xdotool xsel libportaudio2 ffmpeg   # Debian/Ubuntu\nsudo pacman -S --needed openblas xdotool xsel portaudio ffmpeg       # Arch\nsudo dnf install openblas-devel xdotool xsel portaudio ffmpeg        # Fedora\n\ngit clone https://github.com/PLNech/TuParles \u0026\u0026 cd TuParles\npoetry install\ngit clone --depth 1 https://github.com/antirez/qwen-asr vendor/qwen-asr\nmake -C vendor/qwen-asr blas\n# model weights: see install.sh for the five files to fetch into models/\ncp vocab.example.txt vocab.txt           # then add your own names/jargon\nbash scripts/install_desktop.sh          # desktop launcher (optional)\npoetry run tuparles\n```\n\n\u003c/details\u003e\n\nGPU (any recent NVIDIA card) is detected automatically and used for the\nprimary faster-whisper engine; without one, the C fallback engine\ntranscribes on CPU.\n\n### Wayland\n\nSame install, then this once — and log out and back in afterwards:\n\n```bash\nbash scripts/setup_wayland.sh   # input group · uinput rule · wl-clipboard/ydotool · daemon · GNOME extension\n```\n\nIt adapts to your ydotool: Ubuntu's daemon-less 0.1.8 needs nothing more, while\nmodern ydotool (≥1.0, Arch/Fedora) gets a `ydotoold` **user service** + socket\nenv so delivery can inject keys. On GNOME it also installs a focus-window\nextension for terminal paste detection (Ctrl+Shift+V); other compositors\n(KDE, etc.) detect terminals by window title and otherwise fall back to Ctrl+V.\n\nThe daemon renders the bubble through **XWayland** on a Wayland session\n(`QT_QPA_PLATFORM=xcb`, set automatically): native Wayland compositors ignore\na client's request to place itself, so the frameless bubble would otherwise be\ncentred and unable to pin to all desktops. The X11 path also works under\nXWayland. If you force native Wayland (`QT_QPA_PLATFORM=wayland`), the daemon\nstill runs but the compositor controls the bubble's position and stickiness.\n\n### Compatibility \u0026 troubleshooting\n\nTuParles probes what your box can do at boot and logs a one-line capability\nreport (`tuparles diag` prints it any time). For the full picture — a per-setup\ntool matrix, the X11/Wayland fallback chains, and fixes by symptom (nothing\npastes, accented-take freeze, queued take in the wrong window) — see\n**[docs/CROSS_ENV.md](docs/CROSS_ENV.md)**. Hitting a bug? `tuparles report\n\"summary\"` opens an issue pre-filled with your environment + capability line.\n\n## Personal glossary\n\nCopy `vocab.example.txt` to `vocab.txt` and put your recurring names and\njargon there — it biases decoding toward your vocabulary. The file stays\nlocal (gitignored), like everything you dictate.\n\nBetter: let your own dictations grow it. `tuparles vocab suggest` mines\nyour history for recurring technical tokens and proper nouns;\n`tuparles vocab review` walks you through them one by one (oui/non) and\nappends the keepers. You approve every word — suggestions never auto-apply,\nbecause a glossary that grows on its own is just autocorrect with extra\nsteps. Changes take effect on the next take, no restart.\n\n## CLI\n\n```bash\ntuparles                  # start the daemon (or launch from your app launcher)\ntuparles history          # last 20 takes\ntuparles history \"tokens\" # search your dictations\ntuparles stats            # local telemetry: takes, débit, decode speed, language mix\ntuparles vocab suggest    # mine your history for glossary candidates\ntuparles vocab review     # accept/reject them interactively\ntuparles report \"bug…\"    # open a prefilled GitHub issue (no account data sent)\ntuparles diag             # this box's capability report (paste into a bug report)\ntuparles update           # check GitHub for a newer release (no token)\ntuparles whatsnew         # the latest changelog section\ntuparles cheatsheet       # every voice command \u0026 syntax phrase (searchable)\ntuparles cheatsheet quote # …filtered (accent/case-insensitive)\n                          # …or just dictate \"que peux-tu faire ?\" hands-free\ntuparles onboarding       # « Comment tu parles ? » — personalize (text view)\ntuparles onboarding --replay  # …re-run it even once configured\n```\n\nEverything lives in `~/.local/share/tuparles/history.db` and\n`~/.config/tuparles/settings.json` — yours, on disk, never synced anywhere.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplnech%2Ftuparles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplnech%2Ftuparles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplnech%2Ftuparles/lists"}