Skip to content

feat: add AssemblyAI Universal-2 transcription engine - #3

Open
julianromli wants to merge 1 commit into
digimata:masterfrom
julianromli:feat/assemblyai-engine
Open

feat: add AssemblyAI Universal-2 transcription engine#3
julianromli wants to merge 1 commit into
digimata:masterfrom
julianromli:feat/assemblyai-engine

Conversation

@julianromli

Copy link
Copy Markdown

Summary

This PR adds AssemblyAI as an optional cloud transcription engine alongside the existing Parakeet engine. Users can switch between engines with a single config change. No rebuild required.

What changed

New file: Sources/quill/Transcription/AssemblyAIEngine.swift

A new actor implementing the existing TranscriptionEngine protocol. The flow is:

  1. Upload the .caf file as raw bytes to POST /v2/upload using URLSession.upload(for:fromFile:) (streaming, so large files are never fully loaded into memory)
  2. Submit a transcript job to POST /v2/transcript with speech_model: "universal" and language_detection: true
  3. Poll GET /v2/transcript/{id} every 3 seconds until the job reaches a terminal state (max 2 hours)
  4. Parse the words[] array (timestamps in ms, converted to seconds) and group them into TranscriptSegments using the same segmentation logic as ParakeetEngine (sentence-ending punctuation, silence gap over 1s, or 60-word cap)

Zero new SPM dependencies.

Config.swift

Added assemblyAIApiKey() that reads assemblyai_api_key from config.json. Returns nil when the key is absent or empty so the engine fails fast with a clear error before any recording starts, rather than silently failing mid-session. Updated the transcriptionEngine() doc comment to list both supported engine names.

TranscriptionCoordinator.swift

Replaced the parakeet-only branch in preparedEngine() with a switch on the configured engine name. A missing API key throws AssemblyAIEngine.EngineError.missingAPIKey before the engine is used.

Doctor.swift

checkTranscription() now skips the parakeet model download warning when the configured engine is assemblyai (no local models needed). Added checkAssemblyAI() -> Check? which returns nil when the engine is not assemblyai, so it never appears in quill doctor output for parakeet users. run() uses .compactMap { $0 } to filter out the optional.

README.md

Documented the AssemblyAI engine, added a config snippet, noted that audio leaves the machine when using cloud transcription, and updated the engine list in the Config section.

How to use

Add to ~/.config/quill/config.json:

{
  "transcription": { "engine": "assemblyai" },
  "assemblyai_api_key": "your_key_here"
}

Get an API key at https://www.assemblyai.com/dashboard/home. Run quill doctor to confirm the key is detected before recording.

Privacy note

When using the AssemblyAI engine, audio files are uploaded to AssemblyAI's servers for transcription. The default Parakeet engine remains fully local with nothing leaving the machine.

Testing

Tested by inspection and code review. The engine follows the same TranscriptionEngine protocol contract as ParakeetEngine and uses the same segmentation logic, so transcript output shape is identical. Live testing requires a valid AssemblyAI API key on macOS 15.

Add a cloud STT option alongside the existing Parakeet engine. Users
can switch engines with a single config change, no rebuild required.

Changes:
- Add AssemblyAIEngine.swift: uploads audio to AssemblyAI /v2/upload,
  submits a transcript job with speech_model=universal and
  language_detection=true, polls until complete, and maps word timings
  to TranscriptSegment using the same segmentation logic as ParakeetEngine.
  Uses URLSession streaming upload so large CAF files are never fully
  loaded into memory. Zero new SPM dependencies.
- Config.swift: add assemblyAIApiKey() reading assemblyai_api_key from
  config.json. Update transcriptionEngine() doc comment to list both
  supported engines.
- TranscriptionCoordinator.swift: replace parakeet-only branch with a
  switch on the configured engine name. Missing API key throws a typed
  error before any recording starts.
- Doctor.swift: checkTranscription() skips the parakeet model download
  warning when engine is assemblyai. Add checkAssemblyAI() returning
  Check? so it is invisible in doctor output for parakeet users.
- README.md: document the assemblyai engine, add config snippet, note
  that audio leaves the machine when using cloud transcription.

To use AssemblyAI, set in ~/.config/quill/config.json:
  { "transcription": { "engine": "assemblyai" }, "assemblyai_api_key": "<key>" }

Get an API key at https://www.assemblyai.com/dashboard/home.
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.

1 participant