Skip to content

feat: add local training mode review and export - #1

Open
djurcola-agent wants to merge 144 commits into
djurcola:mainfrom
djurcola-agent:agent/handy-training-mode
Open

feat: add local training mode review and export#1
djurcola-agent wants to merge 144 commits into
djurcola:mainfrom
djurcola-agent:agent/handy-training-mode

Conversation

@djurcola-agent

Copy link
Copy Markdown
Collaborator

Before Submitting This PR

  • I have searched existing issues and pull requests (including closed ones) to ensure this is not a duplicate
  • I have read CONTRIBUTING.md

Human Written Description

TODO for Dan: Please replace this section with your own 2–3 sentence description of the problem this local Training Mode solves and why it matters. This placeholder is intentional to comply with Handy’s contribution requirements.

Related Issues/Discussions

Fixes #
Discussion: None. This is an experimental, local-only feature in an owner fork.

Community Feedback

None gathered. Handy is in feature freeze; this draft is for owner review and build-artifact testing, not an upstream merge request.

Testing

  • cargo fmt --check
  • cargo test managers::history::tests — 19 passed
  • cargo test — 218 passed
  • git diff --check
  • changed-line credential scan
  • independent fail-closed review passed

Frontend package validation was not run because Bun and node_modules were absent; no dependencies were installed.

Screenshots/Videos (if applicable)

Not included; desktop interaction needs artifact-based testing.

AI Assistance

  • No AI was used in this PR
  • AI was used (please describe below)

If AI was used:

  • Tools used: Hermes Coder supervising Pi implementation workers.
  • How extensively: AI-assisted implementation, test development, independent review, and verification; owner review remains required.

cjpais and others added 30 commits June 23, 2026 21:05
If recording is triggered but nothing is said, the empty transcription
was still sent to the post-processing LLM, which replies with a useless
error message such as "you need to provide the transcription". Guard
`post_process_transcription()` (the single funnel for all post-processing
paths) to return early on empty or whitespace-only input, so blank
recordings no longer trigger a model call.

Fixes cjpais#1409

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ed (cjpais#1447)

On Linux the recording_overlay window is created at boot regardless of
the overlay_position setting, so even with overlay_position: none (the
Linux default) its hidden WebKit subprocess receives the mic-level
events emit_levels broadcasts at ~24 Hz during recording. Each event
drives WebKit C++ allocations that accumulate without bound, reaching
multi-GB RSS over long sessions and ending in an OOM kill.

Cache the overlay-enabled state in an AtomicBool, populated at startup
and kept in sync from change_overlay_position_setting. emit_levels does
a single relaxed atomic load and returns early when the overlay is
disabled, so the audio path emits nothing for the default Linux config.
When the overlay is enabled, replace the dual global broadcast with a
single emit_to targeting the overlay window, halving the per-callback
WebKit dispatch work.

Fixes cjpais#1279
* Add Dutch translation file for application

This commit adds a new Dutch translation file for the application, containing various UI strings and model descriptions.

* Add Dutch language support
* move to auto timestamps for all models

* remove comment, just use defaults
…ompt (cjpais#1603)

* fix: gate whisper run extension on model arch, not Feature::InitialPrompt

Non-whisper models such as Voxtral Small 24B advertise
Feature::InitialPrompt but reject the whisper-kind run extension with
INVALID_ARG, which made every offline transcription fail (cjpais#1601).

Probe model.arch() == "whisper" once per run and gate on it:
- the whisper run extension (custom words as initial_prompt)
- the custom_words_already_prompted flag for the fuzzy fallback, so
  models like Voxtral still get fuzzy custom-word correction

Complements cjpais#1602, which handles the timestamps half of the bug.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update comments per Copilot review nits

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Alberto Aldas <12873489+btoaldas@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Update Italian translations

* fix

* format

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
Co-authored-by: Piste <5099194+Piste@users.noreply.github.com>
cjpais#1510)

* fix(build): auto-fall-back to AI stub on Command Line Tools-only macOS

The Command Line Tools SDK ships FoundationModels.framework, so the
existing `framework_path.exists()` check selected the real Apple
Intelligence Swift path even on toolchains that cannot compile it: the
CLT `swiftc` has no FoundationModelsMacros plugin (full Xcode only), so
the build hard-fails for anyone without Xcode installed (issue cjpais#1448).

Detect a CLT-only toolchain via `xcode-select -p` and fall back to the
stub automatically, emitting a cargo:warning that points at installing
Xcode or the existing HANDY_FORCE_AI_STUB=1 explicit override. This lets
a plain `cargo build` / `tauri dev` succeed out of the box on a stock
developer machine while keeping the real path for full-Xcode builds.

The auto-detect is skipped when SWIFTC is overridden, so the existing
non-Xcode standalone-swift route (e.g. nixpkgs) is not forced to the stub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nits

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
…jpais#1444)

Resolves cjpais#1279.

- Throttle emit_levels to ~30 FPS (33ms) via atomic timestamp
- Replace dual global .emit() with single .emit_to("recording_overlay")
  to eliminate redundant evaluate_script calls (~188/s → ~30/s)

Co-authored-by: CJ Pais <cj@cjpais.com>
…alk (cjpais#1344)

The FrameResampler was created once per audio session and reused across
multiple recordings without clearing its internal buffers. The rubato
FftFixedIn overlap buffers retained audio from the previous recording,
causing the first ~30ms of each new recording to contain stale samples.

This manifested as lost/corrupted words at the start of transcriptions
and occasional stale text fragments (e.g. "ology.") from previous
sessions leaking into new ones.

Fix: add FrameResampler::reset() that clears in_buf, pending, and
calls rubato's reset() to zero the FFT overlap buffers. Called on
Cmd::Start alongside the existing processed_samples and VAD resets.

Co-authored-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
Co-authored-by: CJ Pais <cj@cjpais.com>
…cjpais#1354)

During app termination, macOS sends a quit event which triggers
`exit()` → `__cxa_finalize_ranges` → Rust Drop handlers. If a mutex
was poisoned by a prior panic anywhere in the transcription pipeline,
calling `.lock().unwrap()` inside a Drop impl triggers a second panic.
Rust treats panic-during-Drop as unrecoverable and calls `abort()`,
crashing the app instead of exiting cleanly.

Replace `.lock().unwrap()` with `match` + `into_inner()` in both
`TranscriptionManager::drop()` and `LoadingGuard::drop()` so they
gracefully recover poisoned mutexes during cleanup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cjpais and others added 14 commits August 9, 2026 16:52
* wip filler fixes

* translations

* add lid + use transcribe.cpp lid

* minor tweaks
* fix(linux): avoid eager enigo requirement in paste path

Dispatch paste methods before touching Enigo so PasteMethod::None and Linux-native paths no longer fail early when Enigo is unavailable. Reuse a shared Wayland-aware clipboard write helper for both paste and CopyToClipboard, enable Enigo's Wayland backend, and add regression tests for the non-Enigo None path.

* fix(linux): remove Enigo Wayland feature

The lazy Enigo paste fix does not require changing Enigo backend behavior. Restore the upstream feature set and remove its lockfile additions.

* fix: preserve clipboard across paste fallback failures

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
Co-authored-by: Artem Shishkin <ArtemiyShisha@users.noreply.github.com>
…bar (cjpais#1659)

* fix(theme): apply appearance setting to the overlay and macOS title bar

The [data-theme] override blocks lived in App.css, which only the settings
window loads. RecordingOverlay.css imports styles/theme.css alone, so the
overlay never saw them and followed prefers-color-scheme instead. Move the
blocks into theme.css, apply the stored theme in the overlay before render,
and emit a theme-changed event so it updates live.

Widen apply_window_theme from Windows-only to Windows and macOS. The main
window is built with default decorations, so on macOS AppKit paints the
title bar from NSApp.effectiveAppearance regardless of what the webview
does. System clears the override so both follow the OS again.

* clean up css

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
Co-authored-by: Mustafa Özkan <leongrphc@gmail.com>
@djurcola

Copy link
Copy Markdown
Owner

Approved.

@djurcola djurcola closed this Aug 15, 2026
@djurcola djurcola reopened this Aug 15, 2026
@djurcola
djurcola marked this pull request as ready for review August 15, 2026 05:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.